/**
 * @class Ext.dom.Element
 * @alternateClassName Ext.Element
 * @mixins Ext.util.Positionable
 * @mixins Ext.mixin.Observable
 *
 * Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser
 * differences.
 *
 * **Note:** The events included in this Class are the ones we've found to be the most commonly
 * used. Many events are not listed here due to the expedient rate of change across browsers.
 * For a more comprehensive list, please visit the following resources:
 *
 * + [Mozilla Event Reference Guide](https://developer.mozilla.org/en-US/docs/Web/Events)
 * + [W3 Pointer Events](http://www.w3.org/TR/pointerevents/)
 * + [W3 Touch Events](http://www.w3.org/TR/touch-events/)
 * + [W3 DOM 2 Events](http://www.w3.org/TR/DOM-Level-2-Events/)
 * + [W3 DOM 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/)
 *
 * ## Usage
 *
 *     // by id
 *     var el = Ext.get("my-div");
 *
 *     // by DOM element reference
 *     var el = Ext.get(myDivElement);
 *
 * ## Selecting Descendant Elements
 *
 * Ext.dom.Element instances can be used to select descendant nodes using CSS selectors.
 * There are 3 methods that can be used for this purpose, each with a slightly different
 * twist:
 *
 * - {@link #method-query}
 * - {@link #method-selectNode}
 * - {@link #method-select}
 *
 * These methods can accept any valid CSS selector since they all use
 * [querySelectorAll](http://www.w3.org/TR/css3-selectors/) under the hood. The primary
 * difference between these three methods is their return type:
 *
 * To get an array of HTMLElement instances matching the selector '.foo' use the query
 * method:
 *
 *     element.query('.foo');
 *
 * This can easily be transformed into an array of Ext.dom.Element instances by setting
 * the `asDom` parameter to `false`:
 *
 *     element.query('.foo', false);
 *
 * If the desired result is only the first matching HTMLElement use the selectNode method:
 *
 *     element.selectNode('.foo');
 *
 * Once again, the dom node can be wrapped in an Ext.dom.Element by setting the `asDom`
 * parameter to `false`:
 *
 *     element.selectNode('.foo', false);
 *
 * The `select` method is used when the desired return type is a {@link
 * Ext.CompositeElementLite CompositeElementLite} or a {@link Ext.CompositeElement
 * CompositeElement}.  These are collections of elements that can be operated on as a
 * group using any of the methods of Ext.dom.Element.  The only difference between the two
 * is that CompositeElementLite is a collection of HTMLElement instances, while
 * CompositeElement is a collection of Ext.dom.Element instances.  To retrieve a
 * CompositeElementLite that represents a collection of HTMLElements for selector '.foo':
 *
 *     element.select('.foo');
 *
 * For a {@link Ext.CompositeElement CompositeElement} simply pass `true` as the
 * `composite` parameter:
 *
 *     element.select('.foo', true);
 *
 * The query selection methods can be used even if you don't have a Ext.dom.Element to
 * start with For example to select an array of all HTMLElements in the document that match the
 * selector '.foo', simply wrap the document object in an Ext.dom.Element instance using
 * {@link Ext#fly}:
 *
 *     Ext.fly(document).query('.foo');
 *
 * # Animations
 *
 * When an element is manipulated, by default there is no animation.
 *
 *     var el = Ext.get("my-div");
 *
 *     // no animation
 *     el.setWidth(100);
 *
 * specified as boolean (true) for default animation effects.
 *
 *     // default animation
 *     el.setWidth(100, true);
 *
 * To configure the effects, an object literal with animation options to use as the Element
 * animation configuration object can also be specified. Note that the supported Element animation
 * configuration options are a subset of the {@link Ext.fx.Anim} animation options specific to Fx
 * effects. The supported Element animation configuration options are:
 *
 *     Option    Default   Description
 *     --------- --------  ---------------------------------------------
 *     duration  350       The duration of the animation in milliseconds
 *     easing    easeOut   The easing method
 *     callback  none      A function to execute when the anim completes
 *     scope     this      The scope (this) of the callback function
 *
 * Usage:
 *
 *     // Element animation options object
 *     var opt = {
 *         duration: 1000,
 *         easing: 'elasticIn',
 *         callback: this.foo,
 *         scope: this
 *     };
 *     // animation with some options set
 *     el.setWidth(100, opt);
 *
 * The Element animation object being used for the animation will be set on the options object
 * as "anim", which allows you to stop or manipulate the animation. Here is an example:
 *
 *     // using the "anim" property to get the Anim object
 *     if(opt.anim.isAnimated()){
 *         opt.anim.stop();
 *     }
 */
Ext.define('Ext.dom.Element', function(Element) {
    var WIN = window,
        DOC = document,
        docEl = DOC.documentElement,
        WIN_TOP = WIN.top,
        EMPTY = [],
        elementIdCounter,
        windowId,
        documentId,
        WIDTH = 'width',
        HEIGHT = 'height',
        MIN_WIDTH = 'min-width',
        MIN_HEIGHT = 'min-height',
        MAX_WIDTH = 'max-width',
        MAX_HEIGHT = 'max-height',
        TOP = 'top',
        RIGHT = 'right',
        BOTTOM = 'bottom',
        LEFT = 'left',
        VISIBILITY = 'visibility',
        HIDDEN = 'hidden',
        DISPLAY = "display",
        NONE = "none",
        ZINDEX = "z-index",
        POSITION = "position",
        RELATIVE = "relative",
        STATIC = "static",
        wordsRe = /\w/g,
        spacesRe = /\s+/,
        classNameSplitRegex = /[\s]+/,
        transparentRe = /^(?:transparent|(?:rgba[(](?:\s*\d+\s*[,]){3}\s*0\s*[)]))$/i,
        endsQuestionRe = /\?$/,
        topRe = /top/i,
        empty = {},
        borders = {
            t: 'border-top-width',
            r: 'border-right-width',
            b: 'border-bottom-width',
            l: 'border-left-width'
        },
        paddings = {
            t: 'padding-top',
            r: 'padding-right',
            b: 'padding-bottom',
            l: 'padding-left'
        },
        margins = {
            t: 'margin-top',
            r: 'margin-right',
            b: 'margin-bottom',
            l: 'margin-left'
        },
        selectDir = {
            b: 'backward',
            back: 'backward',
            f: 'forward'
        },
        paddingsTLRB = [paddings.l, paddings.r, paddings.t, paddings.b],
        bordersTLRB = [borders.l, borders.r, borders.t, borders.b],
        numberRe = /\d+$/,
        unitRe = /\d+(px|r?em|%|vh|vw|vmin|vmax|en|ch|ex|pt|in|cm|mm|pc)$/i,
        defaultUnit = 'px',
        msRe = /^-ms-/,
        camelRe = /(-[a-z])/gi,
        /* eslint-disable-next-line no-useless-escape */
        cssRe = /([a-z0-9\-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
        pxRe = /^\d+(?:\.\d*)?px$/i,
        relativeUnitRe = /(%|r?em|auto|vh|vw|vmin|vmax|ch|ex)$/i,
 
        propertyCache = {},
        ORIGINALDISPLAY = 'originalDisplay',
        
        camelReplaceFn = function(m, a) {
            return a.charAt(1).toUpperCase();
        },
 
        clearData = function(node, deep) {
            var childNodes, i, len;
 
            // Only Element nodes may have _extData and child nodes to clear.
            // IE8 throws an error attempting to set expandos on non-Element nodes.
            if (node.nodeType === 1) {
                node._extData = null;
                
                if (deep) {
                    childNodes = node.childNodes;
                    
                    for (= 0, len = childNodes.length; i < len; ++i) {
                        clearData(childNodes[i], deep);
                    }
                }
            }
        },
        
        toFloat = function(v) {
            return parseFloat(v) || 0;
        },
 
        opacityCls = Ext.baseCSSPrefix + 'hidden-opacity',
        visibilityCls = Ext.baseCSSPrefix + 'hidden-visibility',
        displayCls = Ext.baseCSSPrefix + 'hidden-display',
        offsetsCls = Ext.baseCSSPrefix + 'hidden-offsets',
        clipCls = Ext.baseCSSPrefix + 'hidden-clip',
        lastFocusChange = 0,
        lastKeyboardClose = 0,
        editableHasFocus = false,
        isVirtualKeyboardOpen = false,
        inputTypeSelectionSupported = /text|password|search|tel|url/i,
        visFly, scrollFly, caFly, wrapFly, grannyFly, activeElFly;
 
    // We use element ID counter to prevent assigning the same id to top and nested
    // window and document objects when Ext is running in <iframe>.
    // Cross-origin access might throw an exception, in which case we can't
    // reference top window. In IE8 simply getting a property does not throw
    // but trying to set it does.
    try {
        elementIdCounter = WIN_TOP.__elementIdCounter__;
        WIN_TOP.__elementIdCounter__ = elementIdCounter;
    }
    catch (e) {
        WIN_TOP = WIN;
    }
 
    WIN_TOP.__elementIdCounter__ = elementIdCounter = (WIN_TOP.__elementIdCounter__ || 0) + 1;
    windowId = 'ext-window-' + elementIdCounter;
    documentId = 'ext-document-' + elementIdCounter;
 
    //<debug>
    if (Object.freeze) {
        Object.freeze(EMPTY);
    }
    //</debug>
 
    return {
        alternateClassName: [ 'Ext.Element' ],
 
        mixins: [
            'Ext.util.Positionable',
            'Ext.mixin.Observable'
        ],
 
        requires: [
            'Ext.dom.Shadow',
            'Ext.dom.Shim',
            'Ext.dom.ElementEvent',
            'Ext.event.publisher.Dom',
            'Ext.event.publisher.Gesture',
            'Ext.event.publisher.ElementSize',
            'Ext.event.publisher.ElementPaint'
        ],
 
        uses: [
            'Ext.dom.Helper',
            'Ext.dom.CompositeElement',
            'Ext.dom.Fly',
            'Ext.dom.TouchAction',
            'Ext.event.publisher.Focus'
        ],
 
        observableType: 'element',
 
        isElement: true,
 
        skipGarbageCollection: true,
 
        $applyConfigs: true,
 
        identifiablePrefix: 'ext-element-',
 
        _selectDir: selectDir,
 
        styleHooks: {
            transform: {
                set: function(dom, value, el) {
                    var result = '',
                        prop;
 
                    if (typeof value !== 'string') {
                        for (prop in value) {
                            if (result) {
                                result += ' ';
                            }
 
                            if (prop.indexOf('translate') === 0) {
                                result += prop + '(' + Element.addUnits(value[prop], 'px') + ')';
                            }
                            else {
                                result += prop + '(' + value[prop] + ')';
                            }
                        }
                        
                        value = result;
                    }
 
                    dom.style.transform = value;
                }
            }
        },
 
        validIdRe: Ext.validIdRe,
 
        blockedEvents: Ext.supports.EmulatedMouseOver
            ? {
                // mobile safari emulates a mouseover event on clickable elements such as
                // anchors. This event is useless because it runs after touchend. We block
                // this event to prevent mouseover handlers from running after tap events. It
                // is up to the individual component to determine if it has an analog for
                // mouseover, and implement the appropriate event handlers.
                mouseover: 1
            }
            : {},
 
        longpressEvents: {
            longpress: 1,
            taphold: 1
        },
 
        /**
         * @property {Ext.Component} component
         * A reference to the `Component` that owns this element. This is `null` if there
         * is no direct owner.
         */
 
        //  Mouse events
        /**
         * @event click
         * Fires when a mouse click is detected within the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event contextmenu
         * Fires when a right click is detected within the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event dblclick
         * Fires when a mouse double click is detected within the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event mousedown
         * Fires when a mousedown is detected within the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event mouseup
         * Fires when a mouseup is detected within the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event mouseover
         * Fires when a mouseover is detected within the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event mousemove
         * Fires when a mousemove is detected with the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event mouseout
         * Fires when a mouseout is detected with the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event mouseenter
         * Fires when the mouse enters the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event mouseleave
         * Fires when the mouse leaves the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
 
        //  Keyboard events
        /**
         * @event keypress
         * Fires when a keypress is detected within the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event keydown
         * Fires when a keydown is detected within the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event keyup
         * Fires when a keyup is detected within the element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
 
        //  HTML frame/object events
        /**
         * @event load
         * Fires when the user agent finishes loading all content within the element. Only supported
         * by window, frames, objects and images.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event unload
         * Fires when the user agent removes all content from a window or frame. For elements, it
         * fires when the target element or any of its content has been removed.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event abort
         * Fires when an object/image is stopped from loading before completely loaded.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event error
         * Fires when an object/image/frame cannot be loaded properly.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event painted
         * Fires whenever this Element actually becomes visible (painted) on the screen. This is
         * useful when you need to perform 'read' operations on the DOM element, i.e: calculating
         * natural sizes and positioning.
         *
         * __Note:__ This event is not available to be used with event delegation. Instead `painted`
         * only fires if you explicitly add at least one listener to it, for performance reasons.
         *
         * @param {Ext.dom.Element} this The component instance.
         */
        /**
         * @event resize
         * Important note: For the best performance on mobile devices, use this only when you
         * absolutely need to monitor a Element's size.
         *
         * __Note:__ This event is not available to be used with event delegation. Instead `resize`
         * only fires if you explicitly add at least one listener to it, for performance reasons.
         *
         * @param {Ext.dom.Element} this The component instance.
         * @param {Object} info The element's new size parameters.
         */
        /**
         * @event scroll
         * Fires when a document view is scrolled.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
 
        //  Form events
        /**
         * @event select
         * Fires when a user selects some text in a text field, including input and textarea.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event change
         * Fires when a control loses the input focus and its value has been modified since gaining
         * focus.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event submit
         * Fires when a form is submitted.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event reset
         * Fires when a form is reset.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event focus
         * Fires when an element receives focus either via the pointing device or by tab navigation.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event blur
         * Fires when an element loses focus either via the pointing device or by tabbing
         * navigation.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event focusmove
         * Fires when focus is moved *within* an element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {Ext.dom.Element} e.target The {@link Ext.dom.Element} element which *recieved*
         * focus.
         * @param {Ext.dom.Element} e.relatedTarget The {@link Ext.dom.Element} element which *lost*
         * focus.
         * @param {HTMLElement} t The target of the event.
         */
 
        //  User Interface events
        /**
         * @event DOMFocusIn
         * Where supported. Similar to HTML focus event, but can be applied to any focusable
         * element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event DOMFocusOut
         * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event DOMActivate
         * Where supported. Fires when an element is activated, for instance, through a mouse click
         * or a keypress.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
 
        //  DOM Mutation events
        /**
         * @event DOMSubtreeModified
         * Where supported. Fires when the subtree is modified.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event DOMNodeInserted
         * Where supported. Fires when a node has been added as a child of another node.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event DOMNodeRemoved
         * Where supported. Fires when a descendant node of the element is removed.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event DOMNodeRemovedFromDocument
         * Where supported. Fires when a node is being removed from a document.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event DOMNodeInsertedIntoDocument
         * Where supported. Fires when a node is being inserted into a document.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event DOMAttrModified
         * Where supported. Fires when an attribute has been modified.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
        /**
         * @event DOMCharacterDataModified
         * Where supported. Fires when the character data has been modified.
         * @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
         * @param {HTMLElement} t The target of the event.
         */
 
        /**
         * Creates new Element directly by passing an id or the HTMLElement.  This
         * constructor should not be called directly.  Always use {@link Ext#get Ext.get()}
         * or {@link Ext#fly Ext#fly()} instead.
         *
         * In older versions of Ext JS and Sencha Touch this constructor checked to see if
         * there was already an instance of this element in the cache and if so, returned
         * the same instance. As of version 5 this behavior has been removed in order to
         * avoid a redundant cache lookup since the most common path is for the Element
         * constructor to be called from {@link Ext#get Ext.get()}, which has already
         * checked for a cache entry.
         *
         * Correct way of creating a new Ext.dom.Element (or retrieving it from the cache):
         *
         *     var el = Ext.get('foo'); // by id
         *
         *     var el = Ext.get(document.getElementById('foo')); // by DOM reference
         *
         * Incorrect way of creating a new Ext.dom.Element
         *
         *     var el = new Ext.dom.Element('foo');
         *
         * For quick and easy access to Ext.dom.Element methods use a flyweight:
         *
         *     Ext.fly('foo').addCls('foo-hovered');
         *
         * This simply attaches the DOM node with id='foo' to the global flyweight Element
         * instance to avoid allocating an extra Ext.dom.Element instance.  If, however,
         * the Element instance has already been cached by a previous call to Ext.get(),
         * then Ext.fly() will return the cached Element instance.  For more info see
         * {@link Ext#fly}.
         *
         * @param {String/HTMLElement} dom
         * @private
         */
        constructor: function(dom) {
            var me = this,
                id;
 
            if (typeof dom === 'string') {
                dom = DOC.getElementById(dom);
            }
 
            if (!dom) {
                //<debug>
                Ext.raise("Invalid domNode reference or an id of an existing domNode: " + dom);
                //</debug>
                
                return null;
            }
            
            //<debug>
            if (Ext.cache[dom.id]) {
                Ext.raise("Element cache already contains an entry for id '" +
                    dom.id + "'.  Use Ext.get() to create or retrieve Element instances.");
            }
            //</debug>
 
            /**
             * The DOM element
             * @property dom
             * @type HTMLElement
             */
            me.dom = dom;
 
            if (!(id = dom.id)) {
                dom.id = id = me.generateAutoId();
            }
 
            me.id = id;
 
            // Uncomment this when debugging orphaned Elements
            // if (id === 'ext-element-5') debugger;
 
            //<debug>
            if (!me.validIdRe.test(me.id)) {
                Ext.raise('Invalid Element "id": "' + me.id + '"');
            }
            //</debug>
 
            // set an "el" property that references "this".  This allows
            // Ext.util.Positionable methods to operate on this.el.dom since it
            // gets mixed into both Element and Component
            me.el = me;
 
            Ext.cache[id] = me;
 
            me.longpressListenerCount = 0;
 
            me.mixins.observable.constructor.call(me);
        },
 
        inheritableStatics: {
            /**
             * @property cache
             * @private
             * @static
             * @inheritable
             */
            cache: Ext.cache = {},
 
            /**
             * @property editableSelector
             * @static
             * @private
             * @inheritable
             */
            editableSelector: 'input,textarea,[contenteditable="true"]',
 
            /**
             * @property {Number} VISIBILITY
             * Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
             * Use the CSS 'visibility' property to hide the element.
             *
             * Note that in this mode, {@link Ext.dom.Element#isVisible isVisible} may return true
             * for an element even though it actually has a parent element that is hidden. For this
             * reason, and in most cases, using the {@link #OFFSETS} mode is a better choice.
             * @static
             * @inheritable
             */
            VISIBILITY: 1,
 
            /**
             * @property {Number} DISPLAY
             * Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
             * Use the CSS 'display' property to hide the element.
             * @static
             * @inheritable
             */
            DISPLAY: 2,
 
            /**
             * @property {Number} OFFSETS
             * Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
             * Use CSS absolute positioning and top/left offsets to hide the element.
             * @static
             * @inheritable
             */
            OFFSETS: 3,
 
            /**
             * @property {Number} CLIP
             * Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
             * Use CSS `clip` property to reduce element's dimensions to 0px by 0px, effectively
             * making it hidden while not being truly invisible. This is useful when an element
             * needs to be published to the Assistive Technologies such as screen readers.
             * @static
             * @inheritable
             */
            CLIP: 4,
 
            /**
             * @property {Number} OPACITY
             * Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
             * Use CSS `opacity` property to reduce element's opacity to 0
             * @static
             * @inheritable
             */
            OPACITY: 5,
 
            /**
             * @property minKeyboardHeight
             * @static
             * @inheritable
             * @private
             * This property indicates a minimum threshold of vertical resize movement for
             * virtual keyboard detection.
             *
             * On some mobile browsers the framework needs to keep track of whether window
             * resize events were triggered by the opening or closing of a virtual keyboard
             * so that it can prevent unnecessary re-layout of the viewport.  It does this
             * by detecting resize events in the horizontal direction that occur immediately
             * after an editable element is focused or blurred.
             */
            minKeyboardHeight: 100,
 
            unitRe: unitRe,
 
            /**
             * @property {Boolean} useDelegatedEvents
             * @private
             * @static
             * @inheritable
             * True to globally disable the delegated event system.  The results of
             * setting this to false are unpredictable since the Gesture publisher relies
             * on delegated events in order to work correctly.  Disabling delegated events
             * may cause Gestures to function incorrectly or to stop working completely.
             * Use at your own risk!
             */
            useDelegatedEvents: true,
 
            /**
             * @property {Object} validNodeTypes
             * @private
             * @static
             * @inheritable
             * The list of valid nodeTypes that are allowed to be wrapped
             */
            validNodeTypes: {
                1: 1, // ELEMENT_NODE
                9: 1 // DOCUMENT_NODE
            },
 
            namespaceURIs: {
                html: 'http://www.w3.org/1999/xhtml',
                svg: 'http://www.w3.org/2000/svg'
            },
 
            selectableCls: Ext.baseCSSPrefix + 'selectable',
            unselectableCls: Ext.baseCSSPrefix + 'unselectable',
 
            /**
             * Determines the maximum size for all ripples
             */
            maxRippleDiameter: 75,
 
            /**
             * Test if size has a unit, otherwise appends the passed unit string, or the default
             * for this Element.
             * @param {Object} size The size to set.
             * @param {String} units The units to append to a numeric size value.
             * @return {String} 
             * @private
             * @static
             * @inheritable
             */
            addUnits: function(size, units) {
                // Most common case first: Size is set to a number
                if (typeof size === 'number') {
                    return size + (units || defaultUnit);
                }
 
                // Values which mean "auto"
                // - ""
                // - "auto"
                // - undefined
                // - null
                if (size === "" || size === "auto" || size == null) {
                    return size || '';
                }
 
                // less common use case: number formatted as a string.  save this case until
                // last to avoid regex execution if possible.
                if (numberRe.test(size)) {
                    return size + (units || defaultUnit);
                }
 
                // Warn if it's not a valid CSS measurement
                if (!unitRe.test(size)) {
                    //<debug>
                    // Don't warn about calc() expressions
                    if (!(Ext.isString(size) && size.indexOf('calc') === 0)) {
                        Ext.Logger.warn("Warning, size detected (" + size +
                                        ") not a valid property value on Element.addUnits.");
                    }
                    //</debug>
                    
                    return size || '';
                }
 
                return size;
            },
 
            /**
             * @private
             * Create method to add support for a DomHelper config. Creates
             * and appends elements/children using document.createElement/appendChild.
             * This method is used by the modern toolkit for a significant performance gain
             * in webkit browsers as opposed to using DomQuery which generates HTML
             * markup and sets it as innerHTML.
             *
             * However, the createElement/appendChild
             * method of creating elements is significantly slower in all versions of IE
             * at the time of this writing (6 - 11), so classic toolkit should not use this method,
             * but should instead use DomHelper methods, or Element methods that use
             * DomHelper under the hood (e.g. createChild).
             * see https:*fiddle.sencha.com/#fiddle/tj
             *
             * @static
             * @inheritable
             */
            create: function(attributes, domNode, namespace) {
                var me = this,
                    classes, element, elementStyle, tag, value, name, i, ln, tmp, ns;
 
                attributes = attributes || {};
                
                if (attributes.isElement) {
                    return domNode ? attributes.dom : attributes;
                }
                else if ('nodeType' in attributes) {
                    return domNode ? attributes : Ext.get(attributes);
                }
 
                if (typeof attributes === 'string') {
                    return DOC.createTextNode(attributes);
                }
 
                tag = attributes.tag;
 
                if (!tag) {
                    tag = 'div';
                }
 
                ns = attributes.namespace || namespace;
 
                if (ns) {
                    element = DOC.createElementNS(me.namespaceURIs[ns] || ns, tag);
                }
                else {
                    element = DOC.createElement(tag);
                }
 
                elementStyle = element.style;
 
                for (name in attributes) {
                    if (name !== 'tag' && name !== 'namespace') {
                        value = attributes[name];
 
                        switch (name) {
                            case 'style':
                                if (typeof value === 'string') {
                                    element.setAttribute(name, value);
                                }
                                else {
                                    for (in value) {
                                        elementStyle[i] = value[i];
                                    }
                                }
                                
                                break;
 
                            case 'className':
                            case 'cls':
                                tmp = value.split(spacesRe);
                                classes = classes ? classes.concat(tmp) : tmp;
                                break;
 
                            case 'classList':
                                classes = classes ? classes.concat(value) : value;
                                break;
 
                            case 'text':
                                element.textContent = value;
                                break;
 
                            case 'html':
                                element.innerHTML = value;
                                break;
 
                            case 'hidden':
                                if (classes) {
                                    classes.push(displayCls);
                                }
                                else {
                                    classes = [displayCls];
                                }
                                
                                break;
 
                            case 'children':
                                if (value != null) {
                                    for (= 0, ln = value.length; i < ln; i++) {
                                        element.appendChild(me.create(value[i], true, ns));
                                    }
                                }
                                
                                break;
 
                            default:
                                if (value != null) { // skip null or undefined values
                                    element.setAttribute(name, value);
                                }
                        }
                    }
                }
 
                if (classes) {
                    element.className = classes.join(' ');
                }
 
                if (domNode) {
                    return element;
                }
                else {
                    return me.get(element);
                }
            },
 
            /**
             * @method fly
             * @inheritdoc Ext#method-fly
             * @inheritable
             * @static
             */
            fly: function(dom, named) {
                return Ext.fly(dom, named);
            },
 
            /**
             * Returns the top Element that is located at the passed coordinates in the current
             * viewport.
             * @param {Number} x The x coordinate
             * @param {Number} y The y coordinate
             * @param {Boolean} [asDom=false] `true` to return a DOM element.
             * @return {Ext.dom.Element/HTMLElement} The found element.
             * @static
             * @inheritable
             * @method
             */
            fromPoint: (function() {
                // IE has a weird bug where elementFromPoint can fail on the first call when inside
                // an iframe. It seems to happen more consistently on older IE, but sometimes crops
                // up even in IE11. This plays havoc especially while running tests.
                var elementFromPointBug;
                
                if (Ext.isIE || Ext.isEdge) {
                    try {
                        elementFromPointBug = window.self !== window.top;
                    }
                    catch (e) {
                        elementFromPointBug = true;
                    }
                }
 
                return function(x, y, asDom) {
                    var el = null;
                    
                    el = DOC.elementFromPoint(x, y);
                    
                    if (!el && elementFromPointBug) {
                        el = DOC.elementFromPoint(x, y);
                    }
                    
                    return asDom ? el : Ext.get(el);
                };
            })(),
 
            /**
             * Returns the top Element that is located at the passed coordinates taking into account
             * the scroll position of the document.
             * @static
             * @inheritable
             * @param {Number} x The x coordinate
             * @param {Number} y The y coordinate
             * @param {Boolean} [asDom=false] `true` to return a DOM element.
             * @return {Ext.dom.Element/HTMLElement} The found element.
             *
             * @since 6.2.0
             */
            fromPagePoint: function(x, y, asDom) {
                var scroll = Ext.getDoc().getScroll();
                
                return Element.fromPoint(- scroll.left, y - scroll.top, asDom);
            },
 
            /**
             * Retrieves Ext.dom.Element objects. {@link Ext#get} is alias for
             * {@link Ext.dom.Element#get}.
             *
             * **This method does not retrieve {@link Ext.Component Component}s.** This method
             * retrieves Ext.dom.Element objects which encapsulate DOM elements. To retrieve
             * a Component by its ID, use {@link Ext.ComponentManager#get}.
             *
             * When passing an id, it should not include the `#` character that is used for a css
             * selector.
             *
             *     // For an element with id 'foo'
             *     Ext.get('foo'); // Correct
             *     Ext.get('#foo'); // Incorrect
             *
             * Uses simple caching to consistently return the same object. Automatically fixes
             * if an object was recreated with the same id via AJAX or DOM.
             *
             * @param {String/HTMLElement/Ext.dom.Element} el The `id` of the node, a DOM Node
             * or an existing Element.
             * @return {Ext.dom.Element} The Element object (or `null` if no matching element
             * was found).
             * @static
             * @inheritable
             */
            get: function(el) {
                var me = this,
                    cache = Ext.cache,
                    nodeType, dom, id, entry, isDoc, isWin, isValidNodeType;
 
                if (!el) {
                    return null;
                }
 
                //<debug>
                function warnDuplicate(id) {
                    Ext.raise("DOM element with id " + id +
                        " in Element cache is not the same as element in the DOM. " +
                        "Make sure to clean up Element instances using destroy()");
                }
                //</debug>
 
                // Ext.get(flyweight) must return an Element instance, not the flyweight
                if (el.isFly) {
                    el = el.dom;
                }
 
                if (typeof el === 'string') {
                    id = el;
                    
                    if (cache.hasOwnProperty(id)) {
                        entry = cache[id];
                        
                        if (entry.skipGarbageCollection || !Ext.isGarbage(entry.dom)) {
                            //<debug>
                            // eslint-disable-next-line max-len
                            dom = Ext.getElementById ? Ext.getElementById(id) : DOC.getElementById(id);
                            
                            if (dom && (dom !== entry.dom)) {
                                warnDuplicate(id);
                            }
                            //</debug>
                            
                            return entry;
                        }
                        else {
                            entry.destroy();
                        }
                    }
 
                    if (id === windowId) {
                        return Element.get(WIN);
                    }
                    else if (id === documentId) {
                        return Element.get(DOC);
                    }
 
                    // using Ext.getElementById() allows us to check the detached
                    // body in addition to the body (Ext JS only).
                    dom = Ext.getElementById ? Ext.getElementById(id) : DOC.getElementById(id);
                    
                    if (dom) {
                        return new Element(dom);
                    }
                }
 
                nodeType = el.nodeType;
 
                if (nodeType) {
                    isDoc = (nodeType === 9);
                    isValidNodeType = me.validNodeTypes[nodeType];
                }
                else {
                    // if an object has a window property that refers to itself we can
                    // reasonably assume that it is a window object.
                    // have to use == instead of === for IE8
                    isWin = (el.window == el); // eslint-disable-line eqeqeq
                }
 
                // check if we have a valid node type or if the el is a window object before
                // proceeding. This allows elements, document fragments, and document/window
                // objects (even those inside iframes) to be wrapped.
                if (isValidNodeType || isWin) {
                    id = el.id;
 
                    if (el === DOC) {
                        el.id = id = documentId;
                    }
                    // Must use == here, otherwise IE fails to recognize the window
                    else if (el == WIN) { // eslint-disable-line eqeqeq
                        el.id = id = windowId;
                    }
 
                    if (cache.hasOwnProperty(id)) {
                        entry = cache[id];
                        
                        // eslint-disable-next-line max-len
                        if (entry.skipGarbageCollection || el === entry.dom || !Ext.isGarbage(entry.dom)) {
                            //<debug>
                            if (el !== entry.dom) {
                                warnDuplicate(id);
                            }
                            //</debug>
                            
                            return entry;
                        }
                        else {
                            entry.destroy();
                        }
                    }
 
                    el = new Element(el);
 
                    if (isWin || isDoc) {
                        // document and window objects can never be garbage
                        el.skipGarbageCollection = true;
                    }
                    
                    return el;
                }
 
                if (el.isElement) {
                    return el;
                }
 
                if (el.isComposite) {
                    return el;
                }
 
                // Test for iterable. Allow the resulting Composite to be based upon an Array
                // or HtmlCollection of nodes.
                if (Ext.isIterable(el)) {
                    return me.select(el);
                }
 
                return null;
            },
 
            /**
             * Returns the active element in the DOM. If the browser supports activeElement
             * on the document, this is returned. If not, the focus is tracked and the active
             * element is maintained internally.
             * @static
             * @inheritable
             *
             * @param {Boolean} asElement Return Ext.Element instance instead of DOM node.
             *
             * @return {HTMLElement} The active (focused) element in the document.
             */
            getActiveElement: function(asElement) {
                var active = DOC.activeElement;
 
                // The activeElement can be null, however there also appears to be a very odd
                // and inconsistent bug in IE where the activeElement is simply an empty object
                // literal. Test if the returned active element has focus, if not, we've hit the bug
                // so just default back to the document body.
                if (!active || !active.focus) {
                    active = DOC.body;
                }
 
                return asElement ? Ext.get(active) : active;
            },
 
            /**
             * Retrieves the document height
             * @static
             * @inheritable
             * @return {Number} documentHeight
             */
            getDocumentHeight: function() {
                // eslint-disable-next-line max-len
                return Math.max(!Ext.isStrict ? DOC.body.scrollHeight : docEl.scrollHeight, this.getViewportHeight());
            },
 
            /**
             * Retrieves the document width
             * @static
             * @inheritable
             * @return {Number} documentWidth
             */
            getDocumentWidth: function() {
                // eslint-disable-next-line max-len
                return Math.max(!Ext.isStrict ? DOC.body.scrollWidth : docEl.scrollWidth, this.getViewportWidth());
            },
 
            /**
             * Retrieves the current orientation of the window. This is calculated by
             * determining if the height is greater than the width.
             * @static
             * @inheritable
             * @return {String} Orientation of window: 'portrait' or 'landscape'
             */
            getOrientation: function() {
                if (Ext.supports.OrientationChange) {
                    /* eslint-disable-next-line eqeqeq */
                    return (WIN.orientation == 0) ? 'portrait' : 'landscape';
                }
 
                return (WIN.innerHeight > WIN.innerWidth) ? 'portrait' : 'landscape';
            },
 
            /**
             * Retrieves the viewport height of the window.
             * @static
             * @inheritable
             * @return {Number} viewportHeight
             */
            getViewportHeight: function() {
                var viewportHeight = Element._viewportHeight;
 
                //<feature legacyBrowser>
                if (Ext.isIE9m) {
                    return DOC.documentElement.clientHeight;
                }
                //</feature>
 
                return (viewportHeight != null) ? viewportHeight : docEl.clientHeight;
            },
 
            /**
             * Retrieves the viewport width of the window.
             * @static
             * @inheritable
             * @return {Number} viewportWidth
             */
            getViewportWidth: function() {
                var viewportWidth = Element._viewportWidth;
 
                //<feature legacyBrowser>
                if (Ext.isIE9m) {
                    return DOC.documentElement.clientWidth;
                }
                //</feature>
 
                return (viewportWidth != null) ? viewportWidth : docEl.clientWidth;
            },
 
            /**
             * Returns the current zoom level of the viewport as a ratio of page pixels to
             * screen pixels.
             * @private
             * @static
             * @return {Number} 
             */
            getViewportScale: function() {
                // on desktop devices, the devicePixel ratio gives us the level of zoom that
                // the user specified using ctrl +/- and or by selecting a zoom level from
                // the menu.
                // On android/iOS devicePixel ratio is a fixed number that represents the
                // screen pixel density (e.g. always "2" on apple retina devices)
 
                // WIN_TOP is guarded against cross-frame access in the closure above
                var top = WIN_TOP;
 
                return ((Ext.isiOS || Ext.isAndroid)
                    ? 1
                    : (top.devicePixelRatio || // modern browsers
                       top.screen.deviceXDPI / top.screen.logicalXDPI)) * // IE10m
                       this.getViewportTouchScale();
            },
 
            /**
             * On touch-screen devices there may be an additional level of zooming
             * that occurs when the user performs a pinch or double-tap to zoom
             * gesture.  This is separate from and in addition to the
             * devicePixelRatio.  We can detect it by comparing the width
             * of the documentElement to window.innerWidth
             * @private
             */
            getViewportTouchScale: function(forceRead) {
                var scale = 1,
                    // WIN_TOP is guarded against cross-frame access in the closure above
                    top = WIN_TOP,
                    cachedScale;
 
                if (!forceRead) {
                    cachedScale = this._viewportTouchScale;
 
                    if (cachedScale) {
                        return cachedScale;
                    }
                }
 
                if (Ext.isIE10p || Ext.isEdge || Ext.isiOS) {
                    scale = docEl.offsetWidth / WIN.innerWidth;
                }
                else if (Ext.isChromeMobile) {
                    scale = top.outerWidth / top.innerWidth;
                }
 
                return scale;
            },
 
            /**
             * Retrieves the viewport size of the window.
             * @static
             * @inheritable
             * @return {Object} object containing width and height properties
             */
            getViewSize: function() {
                return {
                    width: Element.getViewportWidth(),
                    height: Element.getViewportHeight()
                };
            },
 
            /**
             * Checks if the passed size has a css unit attached.
             * @param {String} size The size.
             * @return {Boolean} `true` if the size has a css unit.
             *
             * @since 6.2.1
             */
            hasUnit: function(size) {
                return !!(size && unitRe.test(size));
            },
 
 
            /**
             * Checks if the passed css unit is a relative unit. This includes:
             * - `auto`
             * - `%`
             * - `em`
             * - `rem`
             * - `auto`
             * - `vh`
             * - `vw`
             * - `vmin`
             * - `vmax`
             * - `ex
             * - `ch`
             * @param {String} size The css unit and value.
             * @return {Boolean} `true` if the value is relative.
             *
             * @since 6.2.0
             */
            isRelativeUnit: function(size) {
                return !size || relativeUnitRe.test(size);
            },
 
            /**
             * Mask iframes when shim is true. See {@link Ext.util.Floating#shim}.
             * @private
             */
            maskIframes: function() {
                var iframes = document.getElementsByTagName('iframe'),
                    fly = new Ext.dom.Fly();
 
                Ext.each(iframes, function(iframe) {
                    var myMask;
 
                    myMask = fly.attach(iframe.parentNode).mask();
                    myMask.setStyle('background-color', 'transparent');
                });
            },
 
            /**
             * Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax.
             * For example:
             *
             * - border-width -> borderWidth
             * - padding-top -> paddingTop
             *
             * @static
             * @inheritable
             * @param {String} prop The property to normalize
             * @return {String} The normalized string
             */
            normalize: function(prop) {
                // For '-ms-foo' we need msFoo
                // eslint-disable-next-line max-len
                return propertyCache[prop] || (propertyCache[prop] = prop.replace(msRe, 'ms-').replace(camelRe, camelReplaceFn));
            },
 
 
            /**
             * @private
             * @static
             * @inheritable
             */
            _onWindowFocusChange: function(e) {
                // Tracks the timestamp of focus entering or leaving an editable element
                // so that we can compare this timestamp to the time of the next window
                // resize for the purpose of determining if the virtual keyboard is displayed
                // see _onWindowResize for more details
                if (Ext.fly(e.target).is(Element.editableSelector)) {
                    lastFocusChange = new Date();
                    editableHasFocus = (e.type === 'focusin' || e.type === 'pointerup');
                }
            },
 
            /**
             * @private
             * @static
             * @inheritable
             */
            _onWindowResize: function() {
                var documentWidth = docEl.clientWidth,
                    documentHeight = docEl.clientHeight,
                    now = new Date(),
                    threshold = 1000,
                    deltaX, deltaY;
 
                deltaX = documentWidth - Element._documentWidth;
                deltaY = documentHeight - Element._documentHeight;
 
                Element._documentWidth = documentWidth;
                Element._documentHeight = documentHeight;
 
                // If the focus entered or left an editable element within a brief threshold
                // of time, then this resize event MAY be due to a virtual keyboard being
                // shown or hidden.  Let's do some additional checking to find out.
                if (((now - lastFocusChange) < threshold) || ((now - lastKeyboardClose) < threshold)) { // eslint-disable-line max-len
                    // If the resize is ONLY in the vertical direction, and an editable
                    // element has the focus, and the vertical movement was significant,
                    // we can be reasonably certain that the resize event was due to
                    // a virtual keyboard being opened.
                    if (deltaX === 0 && (editableHasFocus && (deltaY <= -Element.minKeyboardHeight))) { // eslint-disable-line max-len
                        isVirtualKeyboardOpen = true;
                        
                        return;
                    }
                }
 
                if (isVirtualKeyboardOpen && (deltaX === 0) && (deltaY >= Element.minKeyboardHeight)) { // eslint-disable-line max-len
                    isVirtualKeyboardOpen = false;
                    
                    // when windows tablets are rotated while keyboard is open, the keyboard closes
                    // and then immediately reopens.  Track the timestamp of the last keyboard
                    // close so that we can detect a successive resize event that might indicate
                    // reopening
                    lastKeyboardClose = new Date();
                }
 
                if (isVirtualKeyboardOpen) {
                    return;
                }
 
                // These cached variables are used by getViewportWidth and getViewportHeight
                // They do not get updated if we returned early due to detecting  that the
                // resize event was triggered by virtual keyboard.
                Element._viewportWidth = documentWidth;
                Element._viewportHeight = documentHeight;
            },
 
            /**
             * Parses a number or string representing margin sizes into an object. Supports
             * CSS-style margin declarations (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10"
             * are all valid options and would return the same result)
             * @static
             * @inheritable
             * @param {Number/String} box The encoded margins
             * @return {Object} An object with margin sizes for top, right, bottom and left
             * containing the unit
             */
            parseBox: function(box) {
                var type, parts, ln;
                
                box = box || 0;
                type = typeof box;
 
                if (type === 'number') {
                    return {
                        top: box,
                        right: box,
                        bottom: box,
                        left: box
                    };
                }
                else if (type !== 'string') {
                    // If not a number or a string, assume we've been given a box config.
                    return box;
                }
 
                parts = box.split(' ');
                ln = parts.length;
 
                if (ln === 1) {
                    parts[1] = parts[2] = parts[3] = parts[0];
                }
                else if (ln === 2) {
                    parts[2] = parts[0];
                    parts[3] = parts[1];
                }
                else if (ln === 3) {
                    parts[3] = parts[1];
                }
 
                return {
                    top: parseFloat(parts[0]) || 0,
                    right: parseFloat(parts[1]) || 0,
                    bottom: parseFloat(parts[2]) || 0,
                    left: parseFloat(parts[3]) || 0
                };
            },
 
            /**
             * Converts a CSS string into an object with a property for each style.
             *
             * The sample code below would return an object with 2 properties, one
             * for background-color and one for color.
             *
             *     var css = 'background-color: red; color: blue;';
             *     console.log(Ext.dom.Element.parseStyles(css));
             *
             * @static
             * @inheritable
             * @param {String} styles A CSS string
             * @return {Object} styles
             */
            parseStyles: function(styles) {
                var out = {},
                    matches;
 
                if (styles) {
                    // Since we're using the g flag on the regex, we need to set the lastIndex.
                    // This automatically happens on some implementations, but not others, see:
                    // http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
                    // http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
                    cssRe.lastIndex = 0;
                    
                    while ((matches = cssRe.exec(styles))) {
                        out[matches[1]] = matches[2] || '';
                    }
                }
                
                return out;
            },
 
            /**
             * Selects elements based on the passed CSS selector to enable
             * {@link Ext.dom.Element Element} methods to be applied to many related
             * elements in one statement through the returned
             * {@link Ext.dom.CompositeElementLite CompositeElementLite} object.
             * @static
             * @inheritable
             * @param {String/HTMLElement[]} selector The CSS selector or an array of
             * elements
             * @param {Boolean} [composite=false] Return a CompositeElement as opposed to
             * a CompositeElementLite. Defaults to false.
             * @param {HTMLElement/String} [root] The root element of the query or id of
             * the root
             * @return {Ext.dom.CompositeElementLite/Ext.dom.CompositeElement}
             */
            select: function(selector, composite, root) {
                return Ext.fly(root || DOC).select(selector, composite);
            },
 
            /**
             * Selects child nodes of a given root based on the passed CSS selector.
             * @static
             * @inheritable
             * @param {String} selector The CSS selector.
             * @param {Boolean} [asDom=true] `false` to return an array of Ext.dom.Element
             * @param {HTMLElement/String} [root] The root element of the query or id of
             * the root
             * @return {HTMLElement[]/Ext.dom.Element[]} An Array of elements that match
             * the selector.  If there are no matches, an empty Array is returned.
             */
            query: function(selector, asDom, root) {
                return Ext.fly(root || DOC).query(selector, asDom);
            },
 
            /**
             * Parses a number or string representing margin sizes into an object. Supports
             * CSS-style margin declarations (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10"
             * are all valid options and would return the same result)
             * @static
             * @inheritable
             * @param {Number/String/Object} box The encoded margins, or an object with top, right,
             * @param {String} units The type of units to add
             * @return {String} An string with unitized (px if units is not specified) metrics for
             * top, right, bottom and left
             */
            unitizeBox: function(box, units) {
                var me = this;
                
                box = me.parseBox(box);
 
                return me.addUnits(box.top, units) + ' ' +
                    me.addUnits(box.right, units) + ' ' +
                    me.addUnits(box.bottom, units) + ' ' +
                    me.addUnits(box.left, units);
            },
 
            /**
             * Unmask iframes when shim is true. See {@link Ext.util.Floating#cfg-shim}.
             * @private
             */
            unmaskIframes: function() {
                var iframes = document.getElementsByTagName('iframe'),
                    fly = new Ext.dom.Fly();
 
                Ext.each(iframes, function(iframe) {
                    fly.attach(iframe.parentNode).unmask();
                });
            },
 
            /**
             * Serializes a DOM form into a url encoded string
             * @param {Object} form The form
             * @return {String} The url encoded form
             * @static
             * @inheritable
             */
            serializeForm: function(form) {
                var fElements = form.elements || (DOC.forms[form] || Ext.getDom(form)).elements,
                    hasSubmit = false,
                    encoder = encodeURIComponent,
                    data = '',
                    eLen = fElements.length,
                    element, name, type, options, hasValue, e, o, oLen, opt;
 
                for (= 0; e < eLen; e++) {
                    element = fElements[e];
                    name = element.name;
                    type = element.type;
                    options = element.options;
 
                    if (!element.disabled && name) {
                        if (/select-(one|multiple)/i.test(type)) {
                            oLen = options.length;
                            
                            for (= 0; o < oLen; o++) {
                                opt = options[o];
                                
                                if (opt.selected) {
                                    hasValue = opt.hasAttribute('value');
                                    data += Ext.String.format('{0}={1}&', encoder(name), encoder(hasValue ? opt.value : opt.text)); // eslint-disable-line max-len
                                }
                            }
                        }
                        else if (!(/file|undefined|reset|button/i.test(type))) {
                            if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type === 'submit' && hasSubmit)) { // eslint-disable-line max-len
                                data += encoder(name) + '=' + encoder(element.value) + '&';
                                hasSubmit = /submit/i.test(type);
                            }
                        }
                    }
                }
                
                return data.substr(0, data.length - 1);
            },
 
            /**
             * Returns the common ancestor of the two passed elements.
             * @static
             * @inheritable
             *
             * @param {Ext.dom.Element/HTMLElement} nodeA
             * @param {Ext.dom.Element/HTMLElement} nodeB
             * @param {Boolean} returnDom Pass `true` to return a DOM element. Otherwise an
             * {@link Ext.dom.Element Element} will be returned.
             * @return {Ext.dom.Element/HTMLElement} The common ancestor.
             */
            getCommonAncestor: function(nodeA, nodeB, returnDom) {
                caFly = caFly || new Ext.dom.Fly();
                caFly.attach(Ext.getDom(nodeA));
                
                while (!caFly.isAncestor(nodeB)) {
                    if (caFly.dom.parentNode) {
                        caFly.attach(caFly.dom.parentNode);
                    }
                    // If Any of the nodes in in a detached state, have to use the document.body
                    else {
                        caFly.attach(DOC.body);
                        
                        break;
                    }
                }
                
                return returnDom ? caFly.dom : Ext.get(caFly);
            }
        },
 
        /**
        * Enable text selection for this element (normalized across browsers)
        * @return {Ext.dom.Element} this
        */
        selectable: function() {
            var me = this;
 
            // We clear this property for all browsers, not just Opera. This is so that rendering
            // templates don't need to condition on Opera when making elements unselectable.
            me.dom.unselectable = '';
 
            me.removeCls(Element.unselectableCls);
            me.addCls(Element.selectableCls);
 
            return me;
        },
 
        /**
         * Disables text selection for this element (normalized across browsers)
         * @return {Ext.dom.Element} this
         */
        unselectable: function() {
            // The approach used to disable text selection combines CSS, HTML attributes and DOM
            // events. Importantly the strategy is designed to be expressible in markup, so that
            // elements can be rendered unselectable without needing modifications post-render.
            // e.g.:
            //
            // <div class="x-unselectable" unselectable="on"></div>
            //
            // Changes to this method may need to be reflected elsewhere, e.g. ProtoElement.
            var me = this;
 
            // The unselectable property (or similar) is supported by various browsers but Opera
            // is the only browser that doesn't support any of the other techniques. The problem
            // with it is that it isn't inherited by child elements. Theoretically we could add it
            // to all children but the performance would be terrible. In certain key locations
            // (e.g. panel headers) we add unselectable="on" to extra elements during rendering
            // just for Opera's benefit.
            if (Ext.isOpera) {
                me.dom.unselectable = 'on';
            }
 
            // In Mozilla and WebKit the CSS properties -moz-user-select and -webkit-user-select
            // prevent a selection originating in an element. These are inherited, which is what
            // we want.
            //
            // In IE we rely on a listener for the selectstart event instead. We don't need to
            // register a listener on the individual element, instead we use a single listener
            // and rely on event propagation to listen for the event at the document level.
            // That listener will walk up the DOM looking for nodes that have either of the classes
            // x-selectable or x-unselectable. This simulates the CSS inheritance approach.
            //
            // IE 10 is expected to support -ms-user-select so the listener may not be required.
            me.removeCls(Element.selectableCls);
            me.addCls(Element.unselectableCls);
 
            return me;
        },
 
        // statics
        statics: {
            // This selector will be modified at runtime in the _init() method above
            // to include the elements with saved tabindex in the returned set
            tabbableSelector: Ext.supports.CSS3NegationSelector
                ? 'a[href],button,iframe,input,select,textarea,[tabindex]:not([tabindex="-1"]),[contenteditable="true"]' // eslint-disable-line max-len
                : 'a[href],button,iframe,input,select,textarea,[tabindex],[contenteditable="true"]',
 
            // Anchor and link tags are special; they are only naturally focusable (and tabbable)
            // if they have href attribute, and tabbabledness is further platform/browser specific.
            // Thus we check it separately in the code.
            naturallyFocusableTags: {
                BUTTON: true,
                IFRAME: true,
                EMBED: true,
                INPUT: true,
                OBJECT: true,
                SELECT: true,
                TEXTAREA: true,
                HTML: Ext.isIE ? true : false,
                BODY: Ext.isIE ? false : true
            },
 
            // <object> element is naturally tabbable only in IE8 and below
            naturallyTabbableTags: {
                BUTTON: true,
                IFRAME: true,
                INPUT: true,
                SELECT: true,
                TEXTAREA: true,
                OBJECT: Ext.isIE8m ? true : false
            },
 
            inputTags: {
                INPUT: true,
                TEXTAREA: true
            },
 
            tabbableSavedCounterAttribute: 'data-tabindex-counter',
            tabbableSavedValueAttribute: 'data-tabindex-value',
 
            splitCls: function(cls) {
                if (typeof cls === 'string') {
                    cls = cls.split(spacesRe);
                }
                
                return cls;
            }
        }, // statics
 
        _init: function(E) {
            // Allow overriding the attribute name and/or selector; this is
            // done only once for performance reasons
            E.tabbableSelector += ',[' + E.tabbableSavedCounterAttribute + ']';
        },
 
        /**
         * Adds the given CSS class(es) to this Element.
         * @param {String/String[]} names The CSS classes to add separated by space,
         * or an array of classes
         * @param {String} [prefix] Prefix to prepend to each class. The separator `-` will be
         * appended to the prefix.
         * @param {String} [suffix] Suffix to append to each class. The separator `-` will be
         * prepended to the suffix.
         * @return {Ext.dom.Element} this
         */
        addCls: function(names, prefix, suffix) {
            return this.replaceCls(null, names, prefix, suffix);
        },
 
        /**
         * Sets up event handlers to add and remove a css class when the mouse is down and then up
         * on this element (a click effect)
         * @param {String} className The class to add
         * @param {Function} [testFn] A test function to execute before adding the class. The passed
         * parameter will be the Element instance. If this functions returns false, the class
         * will not be added.
         * @param {Object} [scope] The scope to execute the testFn in.
         * @return {Ext.dom.Element} this
         */
        addClsOnClick: function(className, testFn, scope) {
            var me = this,
                hasTest = Ext.isFunction(testFn);
 
            me.on("mousedown", function() {
                if (hasTest && testFn.call(scope || me, me) === false) {
                    return false;
                }
                
                me.addCls(className);
                
                Ext.getDoc().on({
                    mouseup: function() {
                        // In case me was destroyed prior to mouseup
                        if (me.dom) {
                            me.removeCls(className);
                        }
                    },
                    single: true
                });
            });
            
            return me;
        },
 
        /**
         * Sets up event handlers to add and remove a css class when this element has the focus
         * @param {String} className The class to add
         * @param {Function} [testFn] A test function to execute before adding the class. The passed
         * parameter will be the Element instance. If this functions returns false, the class
         * will not be added.
         * @param {Object} [scope] The scope to execute the testFn in.
         * @return {Ext.dom.Element} this
         */
        addClsOnFocus: function(className, testFn, scope) {
            var me = this,
                hasTest = Ext.isFunction(testFn);
 
            me.on("focus", function() {
                if (hasTest && testFn.call(scope || me, me) === false) {
                    return false;
                }
                
                me.addCls(className);
            });
            
            me.on("blur", function() {
                // In case blur is caused by destruction of me
                if (me.dom) {
                    me.removeCls(className);
                }
            });
            
            return me;
        },
 
        /**
         * Sets up event handlers to add and remove a css class when the mouse is over this element
         * @param {String} className The class to add
         * @param {Function} [testFn] A test function to execute before adding the class. The passed
         * parameter will be the Element instance. If this functions returns false, the class
         * will not be added.
         * @param {Object} [scope] The scope to execute the testFn in.
         * @return {Ext.dom.Element} this
         */
        addClsOnOver: function(className, testFn, scope) {
            var me = this,
                hasTest = Ext.isFunction(testFn);
 
            me.hover(
                function() {
                    if (hasTest && testFn.call(scope || me, me) === false) {
                        return;
                    }
                    
                    me.addCls(className);
                },
                function() {
                    me.removeCls(className);
                }
            );
            
            return me;
        },
 
        addStyles: function(sides, styles) {
            var totalSize = 0,
                sidesArr = (sides || '').match(wordsRe),
                styleSides = [],
                len = sidesArr.length,
                side, i;
 
            if (len === 1) {
                totalSize = parseFloat(this.getStyle(styles[sidesArr[0]])) || 0;
            }
            else if (len) {
                for (= 0; i < len; i++) {
                    side = sidesArr[i];
                    styleSides.push(styles[side]);
                }
                
                // Gather all at once, returning a hash
                styleSides = this.getStyle(styleSides);
 
                for (= 0; i < len; i++) {
                    side = sidesArr[i];
                    totalSize += parseFloat(styleSides[styles[side]]) || 0;
                }
            }
 
            return totalSize;
        },
 
        addUnits: function(size, units) {
            return Element.addUnits(size, units);
        },
 
        // The following 3 methods add just enough of an animation api to make the scroller work
        // in Sencha Touch
        // TODO: unify touch/ext animations
        animate: function(animation) {
            animation = new Ext.fx.Animation(animation);
            animation.setElement(this);
            this._activeAnimation = animation;
            
            animation.on({
                animationend: this._onAnimationEnd,
                scope: this
            });
            
            Ext.Animator.run(animation);
            
            return animation;
        },
 
        _onAnimationEnd: function() {
            this._activeAnimation = null;
        },
 
        getActiveAnimation: function() {
            return this._activeAnimation;
        },
 
        append: function() {
            return this.appendChild.apply(this, arguments);
        },
 
        /**
         * Appends the passed element(s) to this element
         * @param {String/HTMLElement/Ext.dom.Element/Object} el The id or element to insert
         * or a DomHelper config
         * @param {Boolean} [returnDom=false] True to return the raw DOM element instead
         * of Ext.dom.Element
         * @return {Ext.dom.Element/HTMLElement} The inserted Ext.dom.Element (or
         * HTMLElement if _returnDom_ is _true_).
         */
        appendChild: function(el, returnDom) {
            var me = this,
                insertEl,
                eLen, e;
 
            if (el.nodeType || el.dom || typeof el === 'string') { // element
                el = Ext.getDom(el);
                me.dom.appendChild(el);
                
                return !returnDom ? Ext.get(el) : el;
            }
            else if (el.length) {
                // append all elements to a documentFragment
                insertEl = Ext.fly(DOC.createDocumentFragment());
                eLen = el.length;
 
                for (= 0; e < eLen; e++) {
                    insertEl.appendChild(el[e], returnDom);
                }
                
                el = Ext.Array.toArray(insertEl.dom.childNodes);
                me.dom.appendChild(insertEl.dom);
                
                return returnDom ? el : new Ext.dom.CompositeElementLite(el);
            }
            else { // dh config
                return me.createChild(el, null, returnDom);
            }
        },
 
        /**
         * Appends this element to the passed element.
         * @param {String/HTMLElement/Ext.dom.Element} el The new parent element.
         * The id of the node, a DOM Node or an existing Element.
         * @return {Ext.dom.Element} This element.
         */
        appendTo: function(el) {
            Ext.getDom(el).appendChild(this.dom);
            
            return this;
        },
 
        /**
         * More flexible version of {@link #setStyle} for setting style properties.
         *
         * Styles in object form should be a valid DOM element style property.
         * [Valid style property names](http://www.w3schools.com/jsref/dom_obj_style.asp)
         * (_along with the supported CSS version for each_)
         *
         *     // <div id="my-el">Phineas Flynn</div>
         *
         *     var el = Ext.get('my-el');
         *
         *     el.applyStyles('color: white;');
         *
         *     el.applyStyles({
         *         fontWeight: 'bold',
         *         backgroundColor: 'gray',
         *         padding: '10px'
         *     });
         *
         *     el.applyStyles(function () {
         *         if (name.initialConfig.html === 'Phineas Flynn') {
         *             return 'font-style: italic;';
         *             // OR return { fontStyle: 'italic' };
         *         }
         *     });
         *
         * @param {String/Object/Function} styles A style specification string, e.g.
         * "width:100px", or object in the form `{width:"100px"}`, or a function which returns
         * such a specification.
         * @return {Ext.dom.Element} this
         */
        applyStyles: function(styles) {
            if (styles) {
                if (typeof styles === "function") {
                    styles = styles.call();
                }
                
                if (typeof styles === "string") {
                    styles = Element.parseStyles(styles);
                }
                
                if (typeof styles === "object") {
                    this.setStyle(styles);
                }
            }
            
            return this;
        },
 
        /**
         * Tries to blur the element. Any exceptions are caught and ignored.
         * @return {Ext.dom.Element} this
         */
        blur: function() {
            var me = this,
                dom = me.dom;
            
            // In IE, blurring the body can cause the browser window to hide.
            // Blurring the body is redundant, so instead we just focus it
            if (dom !== DOC.body) {
                try {
                    dom.blur();
                }
                catch (e) {
                    // This block is intentionally left blank
                }
                
                return me;
            }
            else {
                return me.focus(undefined, dom);
            }
        },
 
        /**
         * When an element is moved around in the DOM, or is hidden using `display:none`, it loses
         * layout, and therefore all scroll positions of all descendant elements are lost.
         *
         * This function caches them, and returns a function, which when run will restore the cached
         * positions. In the following example, the Panel is moved from one Container to another
         * which will cause it to lose all scroll positions:
         *
         *     var restoreScroll = myPanel.el.cacheScrollValues();
         *     myOtherContainer.add(myPanel);
         *     restoreScroll();
         *
         * @return {Function} A function which will restore all descendant elements of this Element
         * to their scroll positions recorded when this function was executed. Be aware that the
         * returned function is a closure which has captured the scope of `cacheScrollValues`, so
         * take care to dereference it as soon as not needed - if is it is a `var` it will drop out
         * of scope, and the reference will be freed.
         */
        cacheScrollValues: function() {
            var me = this,
                scrollValues = [],
                scrolledDescendants = [],
                descendants, descendant, i, len;
 
            scrollFly = scrollFly || new Ext.dom.Fly();
 
            descendants = me.query('*');
            
            for (= 0, len = descendants.length; i < len; i++) {
                descendant = descendants[i];
                
                // use !== 0 for scrollLeft because it can be a negative number
                // in RTL mode in some browsers.
                if (descendant.scrollTop > 0 || descendant.scrollLeft !== 0) {
                    scrolledDescendants.push(descendant);
                    scrollValues.push(scrollFly.attach(descendant).getScroll());
                }
            }
 
            return function() {
                var scroll, i, len;
 
                for (= 0, len = scrolledDescendants.length; i < len; i++) {
                    scroll = scrollValues[i];
                    scrollFly.attach(scrolledDescendants[i]);
                    scrollFly.setScrollLeft(scroll.left);
                    scrollFly.setScrollTop(scroll.top);
                }
            };
        },
 
        /**
         * Centers the Element in either the viewport, or another Element.
         * @param {String/HTMLElement/Ext.dom.Element} centerIn element in
         * which to center the element.
         * @return {Ext.dom.Element} This element
         *
         * @chainable
         */
        center: function(centerIn) {
            return this.alignTo(centerIn || DOC, 'c-c');
        },
 
        /**
         * Selects a single *direct* child based on the passed CSS selector (the selector
         * should not contain an id).
         * @param {String} selector The CSS selector.
         * @param {Boolean} [returnDom=false] `true` to return the DOM node instead of
         * Ext.dom.Element.
         * @return {HTMLElement/Ext.dom.Element} The child Ext.dom.Element (or DOM node
         * if `returnDom` is `true`)
         */
        child: function(selector, returnDom) {
            var me = this,
                id;
 
            // If possible, avoid caching the root element.
            if (Ext.supports.Selectors2) {
                return me.selectNode(':scope>' + selector, !!returnDom);
            }
            else {
                // Pull the ID from the DOM (Ext.id also ensures that there *is* an ID).
                // If this object is a Flyweight, it will not have an ID
                id = me.id != null ? me.id : Ext.get(me).id;
                
                return me.selectNode(Ext.makeIdSelector(id) + " > " + selector, !!returnDom);
            }
        },
 
        /**
         * Clone this element.
         * @param {Boolean} [deep=false] `true` if the children of the node should also be cloned.
         * @param {Boolean} [returnDom=false] `true` to return the DOM node instead of
         * Ext.dom.Element.
         * @return {HTMLElement/Ext.dom.Element} The newly cloned Ext.dom.Element (or DOM node
         * if `returnDom` is `true`).
         */
        clone: function(deep, returnDom) {
            var clone = this.dom.cloneNode(deep);
            
            if (Ext.supports.CloneNodeCopiesExpando) {
                clearData(clone, deep);
            }
            
            return returnDom ? clone : Ext.get(clone);
        },
 
        constrainScrollLeft: function(left) {
            var dom = this.dom;
            
            return Math.max(Math.min(left, dom.scrollWidth - dom.clientWidth), 0);
        },
 
        constrainScrollTop: function(top) {
            var dom = this.dom;
            
            return Math.max(Math.min(top, dom.scrollHeight - dom.clientHeight), 0);
        },
 
        /**
         * Creates the passed DomHelper config and appends it to this element or optionally
         * inserts it before the passed child element.
         * @param {Object} config DomHelper element config object.  If no tag is specified
         * (e.g., {tag:'input'}) then a div will be automatically generated with the specified
         * attributes.
         * @param {HTMLElement} [insertBefore] a child element of this element
         * @param {Boolean} [returnDom=false] true to return the dom node instead of creating
         * an Element
         * @return {Ext.dom.Element/HTMLElement} The new child element (or HTMLElement if
         * _returnDom_ is _true_)
         */
        createChild: function(config, insertBefore, returnDom) {
            config = config || { tag: 'div' };
            
            if (insertBefore) {
                return Ext.DomHelper.insertBefore(insertBefore, config, returnDom !== true);
            }
            else {
                return Ext.DomHelper.append(this.dom, config, returnDom !== true);
            }
        },
 
        /**
         * Returns `true` if this element is an ancestor of the passed element, or is
         * the element.
         * @param {String/HTMLElement/Ext.dom.Element} element The dom element,
         * Ext.dom.Element, or id (string) of the dom element to check.
         * @return {Boolean} True if this element is an ancestor of el or the el itself, else false
         */
        contains: function(element) {
            if (!element) {
                return false;
            }
 
            /* eslint-disable-next-line vars-on-top */
            var me = this,
                dom = Ext.getDom(element);
 
            // we need el-contains-itself logic here because isAncestor does not do that:
            // https://developer.mozilla.org/en-US/docs/Web/API/Node.contains
            return (dom === me.dom) || me.isAncestor(dom);
        },
 
        /**
         * Destroys this element by removing it from the cache, removing its DOM reference,
         * and removing all of its event listeners.
         */
        destroy: function() {
            var me = this,
                dom = me.dom;
 
            //<debug>
            if (me.destroyed) {
                Ext.Logger.warn("Cannot destroy Element \"" + me.id + "\". Already destroyed.");
                
                return;
            }
 
            if (me.resumeFocusEventsTimer) {
                Ext.unasap(me.resumeFocusEventsTimer);
                me.resumeFocusEventsTimer = null;
            }
 
            if (me.repaintTimer) {
                me.repaintTimer = Ext.undefer(me.repaintTimer);
            }
 
            if (me.deferFocusTimer) {
                me.deferFocusTimer = Ext.undefer(me.deferFocusTimer);
            }
 
            if (dom) {
                if (dom === DOC.body) {
                    Ext.raise("Cannot destroy body element.");
                }
                else if (dom === DOC) {
                    Ext.raise("Cannot destroy document object.");
                }
                else if (dom === WIN) {
                    Ext.raise("Cannot destroy window object");
                }
            }
            //</debug>
 
            if (dom && dom.parentNode) {
                dom.parentNode.removeChild(dom);
            }
 
            if (me.$ripples) {
                me.destroyAllRipples();
            }
 
            me.collect();
        },
 
        detach: function() {
            var dom = this.dom,
                component = this.component;
 
            if (dom && dom.parentNode && dom.tagName !== 'BODY') {
                // Ensure focus is never lost
                if (component) {
                    component.revertFocus();
                }
                
                dom.parentNode.removeChild(dom);
            }
 
            return this;
        },
 
        /**
         * Disables the shadow element created by {@link #enableShadow}.
         * @private
         */
        disableShadow: function() {
            var shadow = this.shadow;
 
            if (shadow) {
                shadow.hide();
                shadow.disabled = true;
            }
        },
 
        /**
         * Disables the shim element created by {@link #enableShim}.
         * @private
         */
        disableShim: function() {
            var shim = this.shim;
 
            if (shim) {
                shim.hide();
                shim.disabled = true;
            }
        },
 
        /**
         * @private
         */
        doReplaceWith: function(element) {
            var dom = this.dom;
            
            dom.parentNode.replaceChild(Ext.getDom(element), dom);
        },
 
        /**
         * @private
         * A scrollIntoView implementation for scrollIntoView/rtlScrollIntoView to call
         * after current scrollX has been determined.
         */
        doScrollIntoView: function(container, hscroll, animate, highlight, getScrollX, scrollTo) {
            scrollFly = scrollFly || new Ext.dom.Fly();
 
            /* eslint-disable-next-line vars-on-top */
            var me = this,
                dom = me.dom,
                scrollX = scrollFly.attach(container)[getScrollX](),
                scrollY = container.scrollTop,
                position = me.getScrollIntoViewXY(container, scrollX, scrollY),
                newScrollX = position.x,
                newScrollY = position.y;
 
            // Highlight upon end of scroll
            if (highlight) {
                if (animate) {
                    animate = Ext.apply({
                        listeners: {
                            afteranimate: function() {
                                scrollFly.attach(dom).highlight();
                            }
                        }
                    }, animate);
                }
                else {
                    scrollFly.attach(dom).highlight();
                }
            }
 
            if (newScrollY !== scrollY) {
                scrollFly.attach(container).scrollTo('top', newScrollY, animate);
            }
 
            if (hscroll !== false && (newScrollX !== scrollX)) {
                scrollFly.attach(container)[scrollTo]('left', newScrollX, animate);
            }
            
            return me;
        },
 
        /**
         * Selects a single child at any depth below this element based on the passed
         * CSS selector (the selector should not contain an id).
         *
         * Use {@link #getById} if you need to get a reference to a child element via id.
         *
         * @param {String} selector The CSS selector
         * @param {Boolean} [returnDom=false] `true` to return the DOM node instead of
         * Ext.dom.Element
         * @return {HTMLElement/Ext.dom.Element} The child Ext.dom.Element (or DOM node
         * if `returnDom` is `true`)
         */
        down: function(selector, returnDom) {
            return this.selectNode(selector, !!returnDom);
        },
 
        /**
         * Enables a shadow element that will always display behind this element
         * @param {Object} [options] Configuration options for the shadow
         * @param {Number} [options.offset=4] Number of pixels to offset the shadow
         * @param {String} [options.mode='sides'] The shadow display mode.  Supports the following
         * options:
         *
         *     - `'sides'`: Shadow displays on both sides and bottom only
         *     - `'frame'`: Shadow displays equally on all four sides
         *     - `'drop'`: Traditional bottom-right drop shadow
         *     - `'bottom'`: Shadow is offset to the bottom
         *
         * @param {Boolean} [options.animate=false] `true` to animate the shadow while
         * the element is animating.  By default the shadow will be hidden during animation.
         * @param {Boolean} isVisible (private)
         * @private
         */
        enableShadow: function(options, isVisible) {
            var me = this,
                shadow = me.shadow || (me.shadow = new Ext.dom.Shadow(Ext.apply({
                    target: me
                }, options))),
                shim = me.shim;
 
            if (shim) {
                shim.offsets = shadow.outerOffsets;
                shim.shadow = shadow;
                shadow.shim = shim;
            }
 
            // Components pass isVisible to avoid the extra dom read to determine
            // whether or not this element is visible.
            if (isVisible === true || (isVisible !== false && me.isVisible())) {
                // the shadow element may have just been retrieved from an OverlayPool, so
                // we need to explicitly show it to be sure hidden styling is removed
                shadow.show();
            }
            else {
                shadow.hide();
            }
 
            shadow.disabled = false;
        },
 
        /**
         * Enables an iframe shim for this element to keep windowed objects from
         * showing through.  The position, size, and visibility of the shim will be
         * automatically synchronized as the position, size, and visibility of this
         * Element are changed.
         * @param {Object} [options] Configuration options for the shim
         * @param {Boolean} isVisible (private)
         * @return {Ext.dom.Shim} The new Shim
         * @private
         */
        enableShim: function(options, isVisible) {
            var me = this,
                shim = me.shim || (me.shim = new Ext.dom.Shim(Ext.apply({
                    target: me
                }, options))),
                shadow = me.shadow;
 
            if (shadow) {
                shim.offsets = shadow.outerOffsets;
                shim.shadow = shadow;
                shadow.shim = shim;
            }
 
            // Components pass isVisible to avoid the extra dom read to determine
            // whether or not this element is visible.
            if (isVisible === true || (isVisible !== false && me.isVisible())) {
                // the shim element may have just been retrieved from an OverlayPool, so
                // we need to explicitly show it to be sure hidden styling is removed
                shim.show();
            }
            else {
                shim.hide();
            }
 
            shim.disabled = false;
            
            return shim;
        },
 
        /**
         * Looks at this node and then at parent nodes for a match of the passed simple selector.
         * @param {String} simpleSelector The simple selector to test. See {@link Ext.dom.Query}
         * for information about simple selectors.
         * @param {Number/String/HTMLElement/Ext.dom.Element} [limit]
         * The max depth to search as a number or an element which causes the upward traversal
         * to stop and is **not** considered for inclusion as the result.
         * (defaults to 50 || document.documentElement)
         * @param {Boolean} [returnEl=false] True to return a Ext.dom.Element object instead of
         * DOM node
         * @return {HTMLElement/Ext.dom.Element} The matching DOM node (or
         * Ext.dom.Element if _returnEl_ is _true_).  Or null if no match was found.
         */
        findParent: function(simpleSelector, limit, returnEl) {
            var me = this,
                target = me.dom,
                topmost = docEl,
                depth = 0;
 
            if (limit || limit === 0) {
                if (typeof limit !== 'number') {
                    topmost = Ext.getDom(limit);
                    limit = Number.MAX_VALUE;
                }
            }
            else {
                // No limit passed, default to 50
                limit = 50;
            }
 
            while (target && target.nodeType === 1 && depth < limit && target !== topmost) {
                if (Ext.fly(target).is(simpleSelector)) {
                    return returnEl ? Ext.get(target) : target;
                }
                
                depth++;
                target = target.parentNode;
            }
            
            return null;
        },
 
        /**
         * Looks at parent nodes for a match of the passed simple selector.
         * @param {String} simpleSelector The simple selector to test. See {@link Ext.dom.Query}
         * for information about simple selectors.
         * @param {Number/String/HTMLElement/Ext.dom.Element} [limit]
         * The max depth to search as a number or an element which causes the upward traversal
         * to stop and is **not** considered for inclusion as the result.
         * (defaults to 50 || document.documentElement)
         * @param {Boolean} [returnEl=false] True to return a Ext.dom.Element object instead of
         * DOM node
         * @return {HTMLElement/Ext.dom.Element} The matching DOM node (or
         * Ext.dom.Element if _returnEl_ is _true_).  Or null if no match was found.
         */
        findParentNode: function(simpleSelector, limit, returnEl) {
            var p = Ext.fly(this.dom.parentNode);
            
            return p ? p.findParent(simpleSelector, limit, returnEl) : null;
        },
 
        /**
         * Gets the first child, skipping text nodes
         * @param {String} [selector] Find the next sibling that matches the passed simple selector.
         * See {@link Ext.dom.Query} for information about simple selectors.
         * @param {Boolean} [returnDom=false] `true` to return a raw DOM node instead of
         * an Ext.dom.Element
         * @return {Ext.dom.Element/HTMLElement} The first child or null
         */
        first: function(selector, returnDom) {
            return this.matchNode('nextSibling', 'firstChild', selector, returnDom);
        },
 
        /**
         * Try to focus the element either immediately or after a timeout
         * if `defer` argument is specified.
         *
         * @param {Number} [defer] Milliseconds to defer the focus
         * @param {HTMLElement} [dom] (private)
         *
         * @return {Ext.dom.Element} this
         */
        focus: function(defer, dom) {
            var me = this;
 
            dom = dom || me.dom;
 
            if (Number(defer)) {
                Ext.defer(me.focus, defer, me, [null, dom]);
            }
            else {
                Ext.fireEvent('beforefocus', dom);
                dom.focus();
            }
 
            return me;
        },
 
        /**
         * @private
         * Removes the element from the cache and removes listeners.
         * Used for cleaning up orphaned elements after they have been removed from the dom.
         * Similar to {@link #destroy} except it assumes the element has already been
         * removed from the dom.
         */
        collect: function() {
            var me = this,
                dom = me.dom,
                shadow = me.shadow,
                shim = me.shim;
 
            // The parent destroy sets the destroy to emptyFn, which we don't
            // want on a shared fly
            if (!me.isFly) {
                me.mixins.observable.destroy.call(me);
                delete Ext.cache[me.id];
                me.el = null;
            }
 
            if (dom) {
                dom._extData = me.dom = null;
            }
 
            // we do not destroy the shadow and shim because they are returned to their
            // OverlayPools for reuse.
            if (shadow) {
                shadow.hide();
                me.shadow = null;
            }
 
            if (shim) {
                shim.hide();
                me.shim = null;
            }
        },
 
        getAnchorToXY: function(el, anchor, local, mySize) {
            return el.getAnchorXY(anchor, local, mySize);
        },
 
        /**
         * Returns the value of an attribute from the element's underlying DOM node.
         * @param {String} name The attribute name.
         * @param {String} [namespace] The namespace in which to look for the attribute.
         * @return {String} The attribute value.
         */
        getAttribute: function(name, namespace) {
            var dom = this.dom;
 
            return namespace
                ? (dom.getAttributeNS(namespace, name) || dom.getAttribute(namespace + ":" + name))
                : (dom.getAttribute(name) || dom[name] || null);
        },
 
        /**
         * Returns an object containing a map of all attributes of this element's DOM node.
         *
         * @return {Object} Key/value pairs of attribute names and their values.
         */
        getAttributes: function() {
            var attributes = this.dom.attributes,
                result = {},
                attr, i, len;
 
            for (= 0, len = attributes.length; i < len; i++) {
                attr = attributes[i];
 
                result[attr.name] = attr.value;
            }
 
            return result;
        },
 
        /**
         * Gets the bottom Y coordinate of the element (element Y position + element height)
         * @param {Boolean} local True to get the local css position instead of page
         * coordinate
         * @return {Number} 
         */
        getBottom: function(local) {
            return (local ? this.getLocalY() : this.getY()) + this.getHeight();
        },
 
        /**
         * Returns a child element of this element given its `id`.
         * @param {String} id The id of the desired child element.
         * @param {Boolean} [asDom=false] True to return the DOM element, false to return a
         * wrapped Element object.
         * @return {Ext.dom.Element/HTMLElement} The child element (or HTMLElement if
         * _asDom_ is _true_).  Or null if no match was found.
         */
        getById: function(id, asDom) {
            // for normal elements getElementById is the best solution, but if the el is
            // not part of the document.body, we have to resort to querySelector
            var dom = DOC.getElementById(id) ||
                this.dom.querySelector(Ext.makeIdSelector(id));
            
            return asDom ? dom : (dom ? Ext.get(dom) : null);
        },
 
        getBorderPadding: function() {
            var paddingWidth = this.getStyle(paddingsTLRB),
                bordersWidth = this.getStyle(bordersTLRB);
 
            /* eslint-disable max-len */
            return {
                beforeX: (parseFloat(bordersWidth[borders.l]) || 0) + (parseFloat(paddingWidth[paddings.l]) || 0),
                afterX: (parseFloat(bordersWidth[borders.r]) || 0) + (parseFloat(paddingWidth[paddings.r]) || 0),
                beforeY: (parseFloat(bordersWidth[borders.t]) || 0) + (parseFloat(paddingWidth[paddings.t]) || 0),
                afterY: (parseFloat(bordersWidth[borders.b]) || 0) + (parseFloat(paddingWidth[paddings.b]) || 0)
            };
            /* eslint-enable max-len */
        },
 
        /**
         * @private
         */
        getBorders: function() {
            var bordersWidth = this.getStyle(bordersTLRB);
 
            return {
                beforeX: (parseFloat(bordersWidth[borders.l]) || 0),
                afterX: (parseFloat(bordersWidth[borders.r]) || 0),
                beforeY: (parseFloat(bordersWidth[borders.t]) || 0),
                afterY: (parseFloat(bordersWidth[borders.b]) || 0)
            };
        },
 
        /**
         * Gets the width of the border(s) for the specified side(s)
         * @param {String} side Can be t, l, r, b or any combination of those to add
         * multiple values. For example, passing `'lr'` would get the border **l**eft
         * width + the border **r**ight width.
         * @return {Number} The width of the sides passed added together
         */
        getBorderWidth: function(side) {
            return this.addStyles(side, borders);
        },
 
        /**
         * Returns an object holding as properties this element's CSS classes. The object
         * can be modified to effect arbitrary class manipulations that won't immediately
         * go to the DOM. When completed, the DOM node can be updated by calling
         * {@link #setClassMap setClassMap}. The values of the properties should either
         * be set to truthy values (such as `1` or `true`) or removed via `delete`.
         *
         *      var classes = el.getClassMap();
         *
         *      // Add the 'foo' class:
         *      classes.foo = 1;
         *
         *      // Remove the 'bar' class:
         *      delete classes.bar;
         *
         *      // Update the DOM in one step:
         *      el.setClassMap(classes);
         *
         * @param {Boolean} [clone=true] (private) Pass `false` to return the underlying
         * (readonly) object.
         * @return {Object} 
         * @since 6.5.0
         */
        getClassMap: function(clone) {
            var data = this.getData();
 
            if (data) {
                data = data.classMap;
 
                if (clone !== false) {
                    data = Ext.apply({}, data);
                }
            }
 
            return data;
        },
 
        /**
         * Returns this element's data object. This object holds options that may be
         * needed by an `Ext.fly()` call when there is no (cached) `Ext.dom.Element`
         * instance tracked.
         *
         * This method will only return `null` if this instance has no associated DOM
         * node. If the DOM node does not have a data object, one will be created. To
         * avoid this creation, use {@link #peekData peekData()} instead.
         *
         * @param {Boolean} [sync=true] (private) Pass `false` to skip synchronization.
         * @return {Object} 
         * @private
         */
        getData: function(sync) {
            var dom = this.dom,
                data;
 
            if (dom) {
                data = dom._extData || (dom._extData = {});
 
                if (sync !== false && !data.isSynchronized) {
                    this.synchronize();
                }
            }
 
            return data || null;
        },
 
        getFirstChild: function() {
            return Ext.get(this.dom.firstElementChild);
        },
 
        getLastChild: function() {
            return Ext.get(this.dom.lastElementChild);
        },
 
        /**
         * Returns the offset height of the element.
         * @param {Boolean} [contentHeight] `true` to get the height minus borders and padding.
         * @param {Boolean} [preciseHeight] `true` to get the precise height
         * @return {Number} The element's height.
         */
        getHeight: function(contentHeight, preciseHeight) {
            var me = this,
                dom = me.dom,
                hidden = me.isStyle('display', 'none'),
                height,
                floating;
 
            if (hidden) {
                return 0;
            }
 
            // Use the viewport height if they are asking for body height
            if (dom.nodeName === 'BODY') {
                height = Element.getViewportHeight();
            }
            else {
                if (preciseHeight) {
                    height = dom.getBoundingClientRect().height;
                }
                else {
                    height = dom.offsetHeight;
 
                    // SVG nodes do not have offsetHeight, so use boundingClientRect instead.
                    if (height == null) {
                        height = dom.getBoundingClientRect().height;
                    }
                }
            }
 
            // IE9/10 Direct2D dimension rounding bug
            if (Ext.supports.Direct2DBug) {
                floating = me.adjustDirect2DDimension(HEIGHT);
                
                if (preciseHeight) {
                    height += floating;
                }
                else if (floating > 0 && floating < 0.5) {
                    height++;
                }
            }
 
            if (contentHeight) {
                height -= me.getBorderWidth("tb") + me.getPadding("tb");
            }
 
            return (height < 0) ? 0 : height;
        },
 
        /**
         * Returns the `innerHTML` of an Element or an empty string if the element's
         * dom no longer exists.
         * @return {String} 
         */
        getHtml: function() {
            return this.dom ? this.dom.innerHTML : '';
        },
 
        /**
         * Gets the left X coordinate
         * @param {Boolean} local True to get the local css position instead of
         * page coordinate
         * @return {Number} 
         */
        getLeft: function(local) {
            return local ? this.getLocalX() : this.getX();
        },
 
        getLocalX: function() {
            var me = this,
                offsetParent,
                x = me.getStyle('left');
 
            if (!|| x === 'auto') {
                x = 0;
            }
            else if (pxRe.test(x)) {
                x = parseFloat(x);
            }
            else {
                x = me.getX();
 
                // Reading offsetParent causes forced async layout.
                // Do not do it unless needed.
                offsetParent = me.dom.offsetParent;
                
                if (offsetParent) {
                    x -= Ext.fly(offsetParent).getX();
                }
            }
 
            return x;
        },
 
        getLocalXY: function() {
            var me = this,
                offsetParent,
                style = me.getStyle(['left', 'top']),
                x = style.left,
                y = style.top;
 
            if (!|| x === 'auto') {
                x = 0;
            }
            else if (pxRe.test(x)) {
                x = parseFloat(x);
            }
            else {
                x = me.getX();
 
                // Reading offsetParent causes forced async layout.
                // Do not do it unless needed.
                offsetParent = me.dom.offsetParent;
                
                if (offsetParent) {
                    x -= Ext.fly(offsetParent).getX();
                }
            }
 
            if (!|| y === 'auto') {
                y = 0;
            }
            else if (pxRe.test(y)) {
                y = parseFloat(y);
            }
            else {
                y = me.getY();
 
                // Reading offsetParent causes forced async layout.
                // Do not do it unless needed.
                offsetParent = me.dom.offsetParent;
                
                if (offsetParent) {
                    y -= Ext.fly(offsetParent).getY();
                }
            }
 
            return [x, y];
        },
 
        getLocalY: function() {
            var me = this,
                offsetParent,
                y = me.getStyle('top');
 
            if (!|| y === 'auto') {
                y = 0;
            }
            else if (pxRe.test(y)) {
                y = parseFloat(y);
            }
            else {
                y = me.getY();
 
                // Reading offsetParent causes forced async layout.
                // Do not do it unless needed.
                offsetParent = me.dom.offsetParent;
                
                if (offsetParent) {
                    y -= Ext.fly(offsetParent).getY();
                }
            }
 
            return y;
        },
 
        /**
         * @method
         *
         * Returns an object with properties top, left, right and bottom representing the margins
         * of this element unless sides is passed, then it returns the calculated width
         * of the sides (see {@link #getPadding}).
         * @param {String} [sides] Any combination of 'l', 'r', 't', 'b' to get the sum
         * of those sides.
         * @return {Object/Number}
         */
        getMargin: (function() {
            var hash = { t: "top", l: "left", r: "right", b: "bottom" },
                allMargins = ['margin-top', 'margin-left', 'margin-right', 'margin-bottom'];
 
            return function(side) {
                var me = this,
                    style, key, o;
 
                if (!side) {
                    style = me.getStyle(allMargins);
                    o = {};
                    
                    if (style && typeof style === 'object') {
                        o = {};
                        
                        for (key in margins) {
                            o[key] = o[hash[key]] = parseFloat(style[margins[key]]) || 0;
                        }
                    }
                }
                else {
                    o = me.addStyles(side, margins);
                }
                
                return o;
            };
        })(),
 
        /**
         * Gets the width of the padding(s) for the specified side(s).
         * @param {String} side Can be t, l, r, b or any combination of those to add
         * multiple values. For example, passing `'lr'` would get the padding **l**eft +
         * the padding **r**ight.
         * @return {Number} The padding of the sides passed added together.
         */
        getPadding: function(side) {
            return this.addStyles(side, paddings);
        },
 
        getParent: function() {
            return Ext.get(this.dom.parentNode);
        },
 
        /**
         * Gets the right X coordinate of the element (element X position + element width)
         * @param {Boolean} local True to get the local css position instead of page
         * coordinates
         * @return {Number} 
         */
        getRight: function(local) {
            return (local ? this.getLocalX() : this.getX()) + this.getWidth();
        },
 
        /**
         * Returns the current scroll position of the element.
         * @return {Object} An object containing the scroll position in the format
         * `{left: (scrollLeft), top: (scrollTop)}`
         */
        getScroll: function() {
            var me = this,
                dom = me.dom,
                docElement = docEl,
                left, top,
                body = DOC.body;
 
            if (dom === DOC || dom === body) {
                // the scrollLeft/scrollTop may be either on the body or documentElement,
                // depending on browser. It is possible to use window.pageXOffset/pageYOffset
                // in most modern browsers but this complicates things when in rtl mode because
                // pageXOffset does not always behave the same as scrollLeft when direction is
                // rtl. (e.g. pageXOffset can be an offset from the right, while scrollLeft
                // is offset from the left, one can be positive and the other negative, etc.)
                // To avoid adding an extra layer of feature detection in rtl mode to deal with
                // these differences, it's best just to always use scrollLeft/scrollTop
                left = docElement.scrollLeft || (body ? body.scrollLeft : 0);
                top = docElement.scrollTop || (body ? body.scrollTop : 0);
            }
            else {
                left = dom.scrollLeft;
                top = dom.scrollTop;
            }
 
            return {
                left: left,
                top: top
            };
        },
 
        /**
         * Gets the x and y coordinates needed for scrolling an element into view within
         * a given container.  These coordinates translate into the scrollLeft and scrollTop
         * positions that will need to be set on an ancestor of the element in order to make
         * this element visible within its container.
         * @param {String/HTMLElement/Ext.Element/Ext.util.Region} container The container
         * @param {Number} scrollX The container's current scroll position on the x axis
         * @param {Number} scrollY The container's current scroll position on the y axis
         * @param {Object} [align] The alignment for the scroll.
         * @param {'start'/'center'/'end'} [align.x] The alignment of the x scroll. If not
         * specified, the minimum will be done to make the element visible. The behavior
         * is undefined if the request cannot be honored. If the alignment is suffixed with a ?,
         * the alignment will only take place if the item is not already in the visible area.
         * @param {'start'/'center'/'end'} [align.y] The alignment of the x scroll. If not
         * specified, the minimum will be done to make the element visible. The behavior
         * is undefined if the request cannot be honored. If the alignment is suffixed with a ?,
         * the alignment will only take place if the item is not already in the visible area.
         * @return {Object} An object with "x" and "y" properties
         * @private
         */
        getScrollIntoViewXY: function(container, scrollX, scrollY, align) {
            var me = this,
                dom = me.dom,
                offsets, clientWidth, clientHeight;
 
            align = align || empty;
 
            if (container.isRegion) {
                clientHeight = container.height;
                clientWidth = container.width;
            }
            else {
                container = Ext.getDom(container);
                clientHeight = container.clientHeight;
                clientWidth = container.clientWidth;
            }
            
            offsets = me.getOffsetsTo(container);
 
            return {
                y: me.calcScrollPos(offsets[1] + scrollY, dom.offsetHeight,
                                    scrollY, clientHeight, align.y),
                x: me.calcScrollPos(offsets[0] + scrollX, dom.offsetWidth,
                                    scrollX, clientWidth, align.x)
            };
        },
 
        calcScrollPos: function(start, size, viewStart, viewSize, align) {
            var end = start + size,
                viewEnd = viewStart + viewSize,
                force = align && !endsQuestionRe.test(align),
                ret = viewStart;
 
            if (!force) {
                if (align) {
                    align = align.slice(0, -1);
                }
 
                if (size > viewSize || start < viewStart) {
                    align = align || 'start';
                    force = true;
                }
                else if (end > viewEnd) {
                    align = align || 'end';
                    force = true;
                }
            }
 
            if (force) {
                if (align === 'start') {
                    ret = start;
                }
                else if (align === 'center') {
                    ret = Math.max(0, start - Math.floor((viewSize / 2)));
                }
                else if (align === 'end') {
                    ret = Math.max(0, end - viewSize);
                }
            }
 
            return ret;
        },
 
        /**
         * Gets the left scroll position
         * @return {Number} The left scroll position
         */
        getScrollLeft: function() {
            var dom = this.dom;
 
            if (dom === DOC || dom === DOC.body) {
                return this.getScroll().left;
            }
            else {
                return dom.scrollLeft;
            }
        },
 
        /**
         * Gets the top scroll position
         * @return {Number} The top scroll position
         */
        getScrollTop: function() {
            var dom = this.dom;
 
            if (dom === DOC || dom === DOC.body) {
                return this.getScroll().top;
            }
            else {
                return dom.scrollTop;
            }
        },
 
        /**
         * Returns the size of the element.
         * @param {Boolean} [contentSize] `true` to get the width/size minus borders and padding.
         * @return {Object} An object containing the element's size:
         * @return {Number} return.width
         * @return {Number} return.height
         */
        getSize: function(contentSize) {
            return { width: this.getWidth(contentSize), height: this.getHeight(contentSize) };
        },
 
        /**
         * Returns a named style property based on computed/currentStyle (primary) and
         * inline-style if primary is not available.
         *
         * @param {String/String[]} property The style property (or multiple property names
         * in an array) whose value is returned.
         * @param {Boolean} [inline=false] if `true` only inline styles will be returned.
         * @return {String/Object} The current value of the style property for this element
         * (or a hash of named style values if multiple property arguments are requested).
         * @method
         */
        getStyle: function(property, inline) {
            var me = this,
                dom = me.dom,
                multiple = typeof property !== 'string',
                hooks = me.styleHooks,
                prop = property,
                props = prop,
                len = 1,
                domStyle, camel, values, hook, out, style, i;
 
            if (multiple) {
                values = {};
                prop = props[0];
                i = 0;
                
                if (!(len = props.length)) {
                    return values;
                }
            }
 
            if (!dom || dom.documentElement) {
                return values || '';
            }
 
            domStyle = dom.style;
 
            if (inline) {
                style = domStyle;
            }
            else {
                // Caution: Firefox will not render "presentation" (i.e. computed styles) in
                // iframes that are display:none or those inheriting display:none. Similar
                // issues with legacy Safari.
                //
                style = dom.ownerDocument.defaultView.getComputedStyle(dom, null);
 
                // fallback to inline style if rendering context not available
                if (!style) {
                    inline = true;
                    style = domStyle;
                }
            }
 
            do {
                hook = hooks[prop];
 
                if (!hook) {
                    hooks[prop] = hook = { name: Element.normalize(prop) };
                }
 
                if (hook.get) {
                    out = hook.get(dom, me, inline, style);
                }
                else {
                    camel = hook.name;
                    out = style[camel];
                }
 
                if (!multiple) {
                    return out;
                }
 
                values[prop] = out;
                prop = props[++i];
            } while (< len);
 
            return values;
        },
 
        getStyleValue: function(name) {
            return this.dom.style.getPropertyValue(name);
        },
 
        getCaretPos: function() {
            var dom = this.dom,
                pos, selection;
 
            if (inputTypeSelectionSupported.test(dom.type)) {
                pos = dom.selectionStart;
                selection = (typeof pos !== 'number') && this.getTextSelection();
 
                if (selection) {
                    pos = selection[0];
                }
            }
            //<debug>
            else {
                Ext.raise('Input type of "' + dom.type + '" does not support selectionStart');
            }
            //</debug>
 
            return pos;
        },
 
        setCaretPos: function(pos) {
            this.selectText(pos, pos);
        },
 
        /**
         * Returns the selection range of an input element as an array of three values:
         *
         *      [ start, end, direction ]
         *
         * These have the same meaning as the parameters to `selectText`.
         * @return {Array} 
         * @since 6.5.0
         */
        getTextSelection: function() {
            var dom = this.dom;
 
            if (inputTypeSelectionSupported.test(dom.type)) {
                return [ dom.selectionStart, dom.selectionEnd, dom.selectionDirection ];
            }
            else {
                //<debug>
                Ext.raise('Input type of "' + this.dom.type +
                          '" does not support selectionStart, selectionEnd and selectionDirection');
                //</debug>
                
                return [];
            }
            // FYI - Classic overrides this for older browsers...
        },
 
        /**
         * Selects the specified contents of the input element (all by default).
         * @param {Number} [start=0] The starting index to select.
         * @param {Number} [end] The end index to select (defaults to all remaining text).
         * @param {"f"/"b"/"forward"/"backward"} [direction="f"] Pass "f" for forward,
         * "b" for backwards.
         * @return {Ext.dom.Element} this
         * @chainable
         * @since 6.5.0
         */
        selectText: function(start, end, direction) {
            var me = this,
                //<feature legacyBrowser>
                range,
                //</feature>
                dom = me.dom,
                len;
 
            if (dom && inputTypeSelectionSupported.test(dom.type)) {
                start = start || 0;
                len = dom.value.length;
 
                if (end === undefined) {
                    end = len;
                }
 
                direction = selectDir[direction] || direction || 'forward';
 
                if (dom.setSelectionRange) {
                    dom.setSelectionRange(start, end, direction);
                }
                //<feature legacyBrowser>
                else if (dom.createTextRange) {
                    if (start > end) {
                        start = end;
                    }
                    
                    range = dom.createTextRange();
                    range.moveStart('character', start);
                    range.moveEnd('character', -(len - end));
                    range.select();
                }
                //</feature>
            }
            //<debug>
            else if (!inputTypeSelectionSupported.test(dom.type)) {
                Ext.raise('Input type of "' + dom.type + '" does not support setSelectionRange');
            }
            //</debug>
 
            return me;
        },
 
        /**
         * Gets the top Y coordinate
         * @param {Boolean} local True to get the local css position instead of page
         * coordinates
         * @return {Number} 
         */
        getTop: function(local) {
            return local ? this.getLocalY() : this.getY();
        },
 
        /**
         * Returns this element's touch action.  (see {@link #setTouchAction})
         *
         * The returned object is shared and should not be mutated.
         *
         * @returns {Object} 
         */
        getTouchAction: function() {
            return Ext.dom.TouchAction.get(this.dom);
        },
 
        /**
         * Returns the value of the `value` attribute.
         * @param {Boolean} asNumber `true` to parse the value as a number.
         * @return {String/Number}
         */
        getValue: function(asNumber) {
            var value = this.dom.value;
 
            return asNumber ? parseInt(value, 10) : value;
        },
 
        /**
         * Returns the dimensions of the element available to lay content out in.  For
         * most elements this is the clientHeight/clientWidth.  If the element is
         * the document/document.body the window's innerHeight/innerWidth is returned
         *
         * If the element (or any ancestor element) has CSS style `display: none`, the
         * dimensions will be zero.
         *
         * @return {Object} Object describing width and height.
         * @return {Number} return.width
         * @return {Number} return.height
         */
        getViewSize: function() {
            var dom = this.dom;
 
            if (dom === DOC || dom === DOC.body) {
                return {
                    width: Element.getViewportWidth(),
                    height: Element.getViewportHeight()
                };
            }
            else {
                return {
                    width: dom.clientWidth,
                    height: dom.clientHeight
                };
            }
        },
 
        getVisibilityMode: function() {
            var me = this,
                data = me.getData(),
                mode = data.visibilityMode;
 
            if (mode === undefined) {
                data.visibilityMode = mode = Element.DISPLAY;
            }
 
            return mode;
        },
 
        /**
         * Returns the offset width of the element.
         * @param {Boolean} [contentWidth] `true` to get the width minus borders and padding.
         * @param {Boolean} [preciseWidth] `true` to get the precise width
         * @return {Number} The element's width.
         */
        getWidth: function(contentWidth, preciseWidth) {
            var me = this,
                dom = me.dom,
                hidden = me.isStyle('display', 'none'),
                rect, width, floating;
 
            if (hidden) {
                return 0;
            }
 
            // Gecko will in some cases report an offsetWidth that is actually less than the width
            // of the text contents, because it measures fonts with sub-pixel precision but rounds
            // the calculated value down. Using getBoundingClientRect instead of offsetWidth allows
            // us to get the precise subpixel measurements so we can force them to always be
            // rounded up. See
            // https://bugzilla.mozilla.org/show_bug.cgi?id=458617
            // Rounding up ensures that the width includes the full width of the text contents.
            if (Ext.supports.BoundingClientRect) {
                rect = dom.getBoundingClientRect();
                width = (me.vertical && !Ext.supports.RotatedBoundingClientRect)
                    ? (rect.bottom - rect.top)
                    : (rect.right - rect.left);
                
                width = preciseWidth ? width : Math.ceil(width);
            }
            else {
                width = dom.offsetWidth;
            }
 
            // IE9/10 Direct2D dimension rounding bug: https://sencha.jira.com/browse/EXTJSIV-603
            // there is no need make adjustments for this bug when the element is vertically
            // rotated because the width of a vertical element is its rotated height
            if (Ext.supports.Direct2DBug && !me.vertical) {
                // get the fractional portion of the sub-pixel precision width of the element's text
                // contents
                floating = me.adjustDirect2DDimension(WIDTH);
                
                if (preciseWidth) {
                    width += floating;
                }
                // IE9 also measures fonts with sub-pixel precision, but unlike Gecko, instead of
                // rounding the offsetWidth down, it rounds to the nearest integer. This means that
                // in order to ensure that the width includes the full width of the text contents
                // we need to increment the width by 1 only if the fractional portion is less
                // than 0.5
                else if (floating > 0 && floating < 0.5) {
                    width++;
                }
            }
 
            if (contentWidth) {
                width -= me.getBorderWidth("lr") + me.getPadding("lr");
            }
 
            return (width < 0) ? 0 : width;
        },
 
        /**
         * Gets element X position in page coordinates
         *
         * @return {Number} 
         */
        getX: function() {
            return this.getXY()[0];
        },
 
        /**
         * Gets element X and Y positions in page coordinates
         *
         * @return {Array} [x, y]
         */
        getXY: function() {
            var round = Math.round,
                dom = this.dom,
                body = DOC.body,
                x = 0,
                y = 0,
                bodyRect, rect;
 
            if (dom !== DOC && dom !== body) {
                // IE (including IE10) throws an error when getBoundingClientRect
                // is called on an element not attached to dom
                try {
                    bodyRect = body.getBoundingClientRect();
                    rect = dom.getBoundingClientRect();
 
                    x = rect.left - bodyRect.left;
                    y = rect.top - bodyRect.top;
                }
                catch (ex) {
                    // This block is intentionally left blank
                }
            }
 
            return [round(x), round(y)];
        },
 
        /**
         * Gets element Y position in page coordinates
         *
         * @return {Number} 
         */
        getY: function() {
            return this.getXY()[1];
        },
 
        /**
         * Returns this element's z-index
         * @return {Number} 
         */
        getZIndex: function() {
            return parseInt(this.getStyle('z-index'), 10);
        },
 
        /**
         * Checks if the specified CSS class exists on this element's DOM node.
         * @param {String} name The CSS class to check for.
         * @return {Boolean} `true` if the class exists, else `false`.
         */
        hasCls: function(name) {
            var classMap = this.getClassMap();
 
            return classMap.hasOwnProperty(name);
        },
 
        /**
         * Hide this element - Uses display mode to determine whether to use "display",
         * "visibility", or "offsets". See {@link #setVisible}.
         * @return {Ext.dom.Element} this
         */
        hide: function() {
            return this.setVisible(false);
        },
 
        /**
         * Sets up event handlers to call the passed functions when the mouse is moved into and
         * out of the Element.
         * @param {Function} overFn The function to call when the mouse enters the Element.
         * @param {Function} outFn The function to call when the mouse leaves the Element.
         * @param {Object} [scope] The scope (`this` reference) in which the functions are executed.
         * Defaults to the Element's DOM element.
         * @param {Object} [options] Options for the listener. See
         * {@link Ext.util.Observable#addListener the options parameter}.
         * @return {Ext.dom.Element} this
         */
        hover: function(overFn, outFn, scope, options) {
            var me = this;
            
            me.on('mouseenter', overFn, scope || me.dom, options);
            me.on('mouseleave', outFn, scope || me.dom, options);
            
            return me;
        },
 
        /**
         * Returns the index of the given element in the `childNodes` of this element. If
         * not present, `-1` is returned.
         *
         * @param {String/HTMLElement/Ext.dom.Element} childEl The potential child element.
         * @return {number} The index of `childEl` in `childNodes` or `-1` if not found.
         * @since 6.5.0
         */
        indexOf: function(childEl) {
            var children = this.dom,
                c = childEl && Ext.getDom(childEl);
 
            children = children && children.childNodes;
 
            return (&& children) ? Array.prototype.indexOf.call(children, c) : -1;
        },
 
        /**
         * Inserts this element after the passed element in the DOM.
         * @param {String/HTMLElement/Ext.dom.Element} el The element to insert after.
         * The `id` of the node, a DOM Node or an existing Element.
         * @return {Ext.dom.Element} This element.
         */
        insertAfter: function(el) {
            el = Ext.getDom(el);
            el.parentNode.insertBefore(this.dom, el.nextSibling);
            
            return this;
        },
 
        /**
         * Inserts this element before the passed element in the DOM.
         * @param {String/HTMLElement/Ext.dom.Element} el The element before which this element
         * will be inserted. The id of the node, a DOM Node or an existing Element.
         * @return {Ext.dom.Element} This element.
         */
        insertBefore: function(el) {
            el = Ext.getDom(el);
            el.parentNode.insertBefore(this.dom, el);
            
            return this;
        },
 
        /**
         * Inserts (or creates) an element as the first child of this element
         * @param {String/HTMLElement/Ext.dom.Element/Object} el The id or element to insert
         * or a DomHelper config to create and insert
         * @param {Boolean} [returnDom=false] True to return the raw DOM element instead
         * of Ext.dom.Element
         * @return {Ext.dom.Element/HTMLElement} The new child element (or HTMLElement if
         * _returnDom_ is _true_).
         */
        insertFirst: function(el, returnDom) {
            el = el || {};
            
            if (el.nodeType || el.dom || typeof el === 'string') { // element
                el = Ext.getDom(el);
                this.dom.insertBefore(el, this.dom.firstChild);
                
                return !returnDom ? Ext.get(el) : el;
            }
            else { // dh config
                return this.createChild(el, this.dom.firstChild, returnDom);
            }
        },
 
        /**
         * Inserts an html fragment into this element
         * @param {String} where Where to insert the html in relation to this element - beforeBegin,
         * afterBegin, beforeEnd, afterEnd. See {@link Ext.dom.Helper#insertHtml} for details.
         * @param {String} html The HTML fragment
         * @param {Boolean} [returnEl=false] True to return an Ext.dom.Element
         * @return {HTMLElement/Ext.dom.Element} The inserted node (or nearest related if more than
         * 1 inserted)
         */
        insertHtml: function(where, html, returnEl) {
            var el = Ext.DomHelper.insertHtml(where, this.dom, html);
            
            return returnEl ? Ext.get(el) : el;
        },
 
        /**
         * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this
         * element
         * @param {String/HTMLElement/Ext.dom.Element/Object/Array} el The id, element to insert
         * or a DomHelper config to create and insert *or* an array of any of those.
         * @param {String} [where='before'] 'before' or 'after'
         * @param {Boolean} [returnDom=false] True to return the raw DOM element instead of
         * Ext.dom.Element
         * @return {Ext.dom.Element/HTMLElement} The inserted Ext.dom.Element (or
         * HTMLElement if _returnDom_ is _true_). If an array is passed, the last
         * inserted element is returned.
         */
        insertSibling: function(el, where, returnDom) {
            var me = this,
                DomHelper = Ext.DomHelper,
                isAfter = (where || 'before').toLowerCase() === 'after',
                rt, insertEl, eLen, e;
 
            if (Ext.isIterable(el)) {
                eLen = el.length;
                insertEl = Ext.fly(DOC.createDocumentFragment());
 
                // append all elements to a documentFragment               
                if (Ext.isArray(el)) {
 
                    for (= 0; e < eLen; e++) {
                        rt = insertEl.appendChild(el[e], returnDom);
                    }
                }
                // Iterable, but not an Array, must be an HtmlCollection
                else {
                    for (= 0; e < eLen; e++) {
                        insertEl.dom.appendChild(rt = el[0]);
                    }
                    
                    if (returnDom === false) {
                        rt = Ext.get(rt);
                    }
                }
 
                // Insert fragment into document
                me.dom.parentNode.insertBefore(insertEl.dom, isAfter ? me.dom.nextSibling : me.dom);
                
                return rt;
            }
 
            el = el || {};
 
            if (el.nodeType || el.dom) {
                rt = me.dom.parentNode.insertBefore(Ext.getDom(el), isAfter ? me.dom.nextSibling : me.dom); // eslint-disable-line max-len
                
                if (!returnDom) {
                    rt = Ext.get(rt);
                }
            }
            else {
                if (isAfter && !me.dom.nextSibling) {
                    rt = DomHelper.append(me.dom.parentNode, el, !returnDom);
                }
                else {
                    rt = DomHelper[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el,
                                                                             !returnDom);
                }
            }
            
            return rt;
        },
 
        /**
         * Returns `true` if this element matches the passed simple selector
         * (e.g. 'div.some-class' or 'span:first-child').
         * @param {String/Function} selector The simple selector to test or a function
         * which is passed candidate nodes, and should return `true` for nodes which match.
         * @return {Boolean} `true` if this element matches the selector, else `false`.
         */
        is: function(selector) {
            var dom = this.dom,
                is;
 
            if (!selector) {
                // In Ext 4 is() called through to DomQuery methods, and would always
                // return true if the selector was ''.  The new query() method in v5 uses
                // querySelector/querySelectorAll() which consider '' to be an invalid
                // selector and throw an error as a result.  To maintain compatibility
                // with the various users of is() we have to return true if the selector
                // is an empty string.  For example: el.up('') should return the element's
                // direct parent.
                is = true;
            }
            else if (!dom.tagName) {
                // document and window objects can never match a selector
                is = false;
            }
            else if (Ext.isFunction(selector)) {
                is = selector(dom);
            }
            else {
                is = dom[Ext.supports.matchesSelector](selector);
            }
 
            return is;
        },
 
        /**
         * Returns `true` if this element is an ancestor of the passed element
         * @param {String/HTMLElement/Ext.dom.Element} el The element or id of the element
         * to search for in this elements descendants.
         * @return {Boolean} 
         */
        isAncestor: function(el) {
            var ret = false,
                dom = this.dom,
                child = Ext.getDom(el);
 
            if (dom && child) {
                // This handles the window object, which is not a Node and throws an error
                if (!child.nodeType) {
                    return false;
                }
 
                if (dom.contains) {
                    return dom.contains(child);
                }
                else if (dom.compareDocumentPosition) {
                    return !!(dom.compareDocumentPosition(child) & 16);
                }
                else {
                    while ((child = child.parentNode)) {
                        ret = child === dom || ret;
                    }
                }
            }
            
            return ret;
        },
 
        isPainted: (function() {
            return !Ext.browser.is.IE
                ? function() {
                    var dom = this.dom;
                    
                    return Boolean(dom && dom.offsetParent);
                }
                : function() {
                    var dom = this.dom;
                    
                    return Boolean(dom && (dom.offsetHeight !== 0 || dom.offsetWidth !== 0));
                };
        })(),
 
        /**
         * Returns true if this element is scrollable.
         * @return {Boolean} 
         */
        isScrollable: function() {
            var dom = this.dom;
            
            return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
        },
 
        /**
         * Checks if the current value of a style is equal to a given value.
         * @param {String} style property whose value is returned.
         * @param {String} val to check against.
         * @return {Boolean} `true` for when the current value equals the given value.
         */
        isStyle: function(style, val) {
            return this.getStyle(style) === val;
        },
 
        /**
         * Checks whether the element is currently visible using both visibility and display
         * properties.
         * @param {Boolean} [deep=false] True to walk the dom and see if parent elements are hidden.
         * If false, the function only checks the visibility of the element itself and it may return
         * `true` even though a parent is not visible.
         * @param {Number} [mode=3] Bit flag indicating which CSS properties to test:
         *
         * - `1` - check display only
         * - `2` - check visibility only
         * - `3` - check both visibility and display
         *
         * @return {Boolean} `true` if the element is currently visible, else `false`
         */
        isVisible: function(deep, mode) {
            var dom = this.dom,
                visible = true,
                end;
 
            if (!dom) {
                return false;
            }
 
            mode = mode || 3;
 
            if (!visFly) {
                visFly = new Ext.dom.Fly();
            }
 
            for (end = dom.ownerDocument.documentElement; dom !== end; dom = dom.parentNode) {
                if (!dom || dom.nodeType === 11) {
                    // parent node does not exist or is a document fragment
                    visible = false;
                }
 
                if (visible) {
                    visFly.attach(dom);
 
                    if (mode & 1) {
                        visible = !visFly.isStyle(DISPLAY, NONE);
                    }
 
                    if (visible && (mode & 2)) {
                        visible = !visFly.isStyle(VISIBILITY, HIDDEN);
                    }
                }
 
                if (!visible || !deep) {
                    break;
                }
            }
 
            return visible;
        },
 
        /**
         * Gets the last child, skipping text nodes
         * @param {String} [selector] Find the previous sibling that matches the passed simple
         * selector. See {@link Ext.dom.Query} for information about simple selectors.
         * @param {Boolean} [returnDom=false] `true` to return a raw DOM node instead of an
         * Ext.dom.Element
         * @return {Ext.dom.Element/HTMLElement} The last child Ext.dom.Element (or
         * HTMLElement if _returnDom_ is _true_).  Or null if no match is found.
         */
        last: function(selector, returnDom) {
            return this.matchNode('previousSibling', 'lastChild', selector, returnDom);
        },
 
        /**
         * @cfg listeners
         * @hide
         */
        matchNode: function(dir, start, selector, returnDom) {
            var dom = this.dom,
                n;
 
            if (!dom) {
                return null;
            }
 
            n = dom[start];
            
            while (n) {
                if (n.nodeType === 1 && (!selector || Ext.fly(n, '_matchNode').is(selector))) {
                    return !returnDom ? Ext.get(n) : n;
                }
                
                n = n[dir];
            }
            
            return null;
        },
 
        /**
         * Measures and returns the size of this element. When `dimension` is `null` (or
         * not specified), this will be an object with `width` and `height` properties.
         *
         * If `dimension` is `'w'` the value returned will be this element's width. If
         * `dimension` is `'h'` the returned value will be this element's height.
         *
         * Unlike `getWidth` and `getHeight` this method only returns "precise" (sub-pixel)
         * sizes based on the `getBoundingClientRect` API.
         *
         * @param {'w'/'h'} [dimension] Specifies which dimension is desired. If omitted
         * then an object with `width` and `height` properties is returned.
         * @return {Number/Object} This element's width, height or both as a readonly
         * object. This object may be the direct result of `getBoundingClientRect` and
         * hence immutable on some browsers.
         * @private
         * @since 6.5.0
         */
        measure: function(dimension) {
            // This method doesn't use getBoundingClientRect because
            // the values it returns are affected by transforms (scale etc).
            // For this method we want the real size that's not affected by
            // transforms.
            var me = this,
                dom = me.dom,
                includeWidth = dimension !== 'h',
                includeHeight = dimension !== 'w',
                width = 0,
                height = 0,
                addPadding = !Ext.supports.ComputedSizeIncludesPadding,
                style, rect, offsetParent;
 
            // Use the viewport height if they are asking for body height
            if (dom.nodeName === 'BODY') {
                height = includeHeight && Element.getViewportHeight();
                width = includeWidth && Element.getViewportWidth();
            }
            else {
                //<if legacyBrowser>
                if (Ext.supports.ComputedStyle) {
                //</if legacyBrowser>
                    offsetParent = dom.offsetParent;
                    style = dom.ownerDocument.defaultView.getComputedStyle(dom, null);
 
                    // We also have to add the padding if the element uses content-box sizing
                    addPadding |= style.boxSizing === 'content-box';
 
                    // offsetParent will be null with position fixed
                    if (offsetParent !== null || style.position === 'fixed') {
                        if (includeHeight) {
                            height = toFloat(style.height);
                            
                            if (addPadding) {
                                height += toFloat(style.paddingTop) +
                                          toFloat(style.paddingBottom) +
                                          toFloat(style.borderTopWidth) +
                                          toFloat(style.borderBottomWidth);
                            }
                        }
 
                        if (includeWidth) {
                            width = toFloat(style.width);
                            
                            if (addPadding) {
                                width += toFloat(style.paddingLeft) +
                                         toFloat(style.paddingRight) +
                                         toFloat(style.borderLeftWidth) +
                                         toFloat(style.borderRightWidth);
                            }
                        }
                    }
                //<if legacyBrowser>
                }
                else {
                    // Browsers that don't support computed style don't
                    // support transforms (IE8), so gbcr is good enough.
                    rect = dom.getBoundingClientRect();
                    width = rect.width || rect.right - rect.left;
                    height = rect.height || rect.bottom - rect.top;
                }
 
                // IE9/10 Direct2D dimension rounding bug
                if (Ext.supports.Direct2DBug) {
                    if (includeHeight) {
                        height += me.adjustDirect2DDimension(HEIGHT);
                    }
 
                    if (includeWidth) {
                        width += me.adjustDirect2DDimension(WIDTH);
                    }
                }
                //</if legacyBrowser>
            }
 
            // Don't create a temporary object unless we need to return it...
            rect = dimension ? null : { width: width, height: height };
 
            // NOTE: The modern override ignores all these IE8/9/10 issues
            return dimension ? (includeWidth ? width : height) : rect;
        },
 
        /**
         * Measures and returns this element's content. When `dimension` is `null` (or
         * not specified), this will be an object with `width` and `height` properties.
         *
         * If `dimension` is `'w'` the value returned will be this element's width. If
         * `dimension` is `'h'` the returned value will be this element's height.
         *
         * Unlike `getWidth` and `getHeight` this method only returns "precise" (sub-pixel)
         * sizes based on the `getBoundingClientRect` API.
         *
         * @param {'w'/'h'} [dimension] Specifies which dimension is desired. If omitted
         * then an object with `width` and `height` properties is returned.
         * @return {Number/Object} This element's width, height or both as a readonly
         * object. This object may be the direct result of `getBoundingClientRect` and
         * hence immutable on some browsers.
         * @private
         * @since 6.5.0
         */
        measureContent: function(dimension) {
            var me = this,
                includeWidth = dimension !== 'h',
                size = me.measure(dimension),  // see modern/classic overrides
                h = dimension ? size : size.height,
                w = dimension ? size : size.width;
 
            if (dimension !== 'w') {
                h -= me.getBorderWidth('tb') + me.getPadding('tb');
            }
 
            if (includeWidth) {
                w -= me.getBorderWidth('lr') + me.getPadding('lr');
            }
 
            return dimension ? (includeWidth ? w : h) : { width: w, height: h };
        },
 
        /**
         * Monitors this Element for the mouse leaving. Calls the function after the specified delay
         * only if the mouse was not moved back into the Element within the delay. If the mouse
         * *was* moved back in, the function is not called.
         * @param {Number} delay The delay **in milliseconds** to wait for possible mouse re-entry
         * before calling the handler function.
         * @param {Function} handler The function to call if the mouse remains outside of this
         * Element for the specified time.
         * @param {Object} [scope] The scope (`this` reference) in which the handler function
         * executes. Defaults to this Element.
         * @return {Object} The listeners object which was added to this element so that monitoring
         * can be stopped. Example usage:
         *
         *     // Hide the menu if the mouse moves out for 250ms or more
         *     this.mouseLeaveMonitor = this.menuEl.monitorMouseLeave(250, this.hideMenu, this);
         *
         *     ...
         *     // Remove mouseleave monitor on menu destroy
         *     this.mouseLeaveMonitor.destroy();
         *
         */
        monitorMouseLeave: function(delay, handler, scope) {
            var me = this,
                timer,
                listeners = {
                    mouseleave: function(e) {
                        if (Ext.isIE9m) {
                            e.enableIEAsync();
                        }
                        
                        timer = Ext.defer(handler, delay, scope || me, [ e ]);
                    },
                    mouseenter: function() {
                        Ext.undefer(timer);
                    },
                    destroy: function() {
                        Ext.undefer(timer);
 
                        if (!me.destroyed) {
                            me.un(listeners);
                        }
                    }
                };
 
            me.on(listeners);
            
            return listeners;
        },
 
        /**
         * Gets the next sibling, skipping text nodes
         * @param {String} [selector] Find the next sibling that matches the passed simple selector.
         * See {@link Ext.dom.Query} for information about simple selectors.
         * @param {Boolean} [returnDom=false] `true` to return a raw dom node instead of an
         * Ext.dom.Element
         * @return {Ext.dom.Element/HTMLElement} The next sibling Ext.dom.Element (or
         * HTMLElement if _asDom_ is _true_).  Or null if no match is found.
         */
        next: function(selector, returnDom) {
            return this.matchNode('nextSibling', 'nextSibling', selector, returnDom);
        },
 
        /**
         * Gets the parent node for this element, optionally chaining up trying to match a selector
         * @param {String} [selector] Find a parent node that matches the passed simple selector.
         * See {@link Ext.dom.Query} for information about simple selectors.
         * @param {Boolean} [returnDom=false] True to return a raw dom node instead of an
         * Ext.dom.Element
         * @return {Ext.dom.Element/HTMLElement} The parent node (Ext.dom.Element or
         * HTMLElement if _returnDom_ is _true_).  Or null if no match is found.
         */
        parent: function(selector, returnDom) {
            return this.matchNode('parentNode', 'parentNode', selector, returnDom);
        },
 
        peekData: function() {
            var dom = this.dom;
 
            return dom && dom._extData || null;
        },
 
        /**
         * Initializes positioning on this element. If a desired position is not passed,
         * it will make the the element positioned relative IF it is not already positioned.
         * @param {String} [pos] Positioning to use "relative", "absolute" or "fixed"
         * @param {Number} [zIndex] The zIndex to apply
         * @param {Number} [x] Set the page X position
         * @param {Number} [y] Set the page Y position
         */
        position: function(pos, zIndex, x, y) {
            var me = this;
 
            if (me.dom.tagName !== 'BODY') {
                if (!pos && me.isStyle(POSITION, STATIC)) {
                    me.setStyle(POSITION, RELATIVE);
                }
                else if (pos) {
                    me.setStyle(POSITION, pos);
                }
                
                if (zIndex) {
                    me.setStyle(ZINDEX, zIndex);
                }
                
                if (|| y) {
                    me.setXY([|| false, y || false]);
                }
            }
        },
 
        /**
         * Gets the previous sibling, skipping text nodes
         * @param {String} [selector] Find the previous sibling that matches the passed simple
         * selector. See {@link Ext.dom.Query} for information about simple selectors.
         * @param {Boolean} [returnDom=false] `true` to return a raw DOM node instead of an
         * Ext.dom.Element
         * @return {Ext.dom.Element/HTMLElement} The previous sibling (Ext.dom.Element or
         * HTMLElement if _returnDom_ is _true_).  Or null if no match is found.
         */
        prev: function(selector, returnDom) {
            return this.matchNode('previousSibling', 'previousSibling', selector, returnDom);
        },
 
        /**
         * Selects child nodes based on the passed CSS selector.
         * Delegates to document.querySelectorAll. More information can be found at
         * [http://www.w3.org/TR/css3-selectors/](http://www.w3.org/TR/css3-selectors/)
         *
         * All selectors, attribute filters and pseudos below can be combined infinitely
         * in any order. For example `div.foo:nth-child(odd)[@foo=bar].bar:first` would be
         * a perfectly valid selector.
         *
         * ## Element Selectors:
         *
         * * \* any element
         * * E an element with the tag E
         * * E F All descendant elements of E that have the tag F
         * * E > F or E/F all direct children elements of E that have the tag F
         * * E + F all elements with the tag F that are immediately preceded by an element
         * with the tag E
         * * E ~ F all elements with the tag F that are preceded by a sibling element with the tag E
         *
         * ## Attribute Selectors:
         *
         * The use of @ and quotes are optional. For example, div[@foo='bar'] is also a valid
         * attribute selector.
         *
         * * E[foo] has an attribute "foo"
         * * E[foo=bar] has an attribute "foo" that equals "bar"
         * * E[foo^=bar] has an attribute "foo" that starts with "bar"
         * * E[foo$=bar] has an attribute "foo" that ends with "bar"
         * * E[foo*=bar] has an attribute "foo" that contains the substring "bar"
         * * E[foo%=2] has an attribute "foo" that is evenly divisible by 2
         * * E[foo!=bar] has an attribute "foo" that does not equal "bar"
         *
         * ## Pseudo Classes:
         *
         * * E:first-child E is the first child of its parent
         * * E:last-child E is the last child of its parent
         * * E:nth-child(n) E is the nth child of its parent (1 based as per the spec)
         * * E:nth-child(odd) E is an odd child of its parent
         * * E:nth-child(even) E is an even child of its parent
         * * E:only-child E is the only child of its parent
         * * E:checked E is an element that is has a checked attribute that is true (e.g. a radio
         * or checkbox)
         * * E:first the first E in the resultset
         * * E:last the last E in the resultset
         * * E:nth(n) the nth E in the resultset (1 based)
         * * E:odd shortcut for :nth-child(odd)
         * * E:even shortcut for :nth-child(even)
         * * E:not(S) an E element that does not match simple selector S
         * * E:has(S) an E element that has a descendant that matches simple selector S
         * * E:next(S) an E element whose next sibling matches simple selector S
         * * E:prev(S) an E element whose previous sibling matches simple selector S
         * * E:any(S1|S2|S2) an E element which matches any of the simple selectors S1, S2 or S3
         *
         * ## CSS Value Selectors:
         *
         * * E{display=none} CSS value "display" that equals "none"
         * * E{display^=none} CSS value "display" that starts with "none"
         * * E{display$=none} CSS value "display" that ends with "none"
         * * E{display*=none} CSS value "display" that contains the substring "none"
         * * E{display%=2} CSS value "display" that is evenly divisible by 2
         * * E{display!=none} CSS value "display" that does not equal "none"
         *
         * @param {String} selector The CSS selector.
         * @param {Boolean} [asDom=true] `false` to return an array of Ext.dom.Element
         * @param {Boolean} single (private)
         * @return {HTMLElement[]/Ext.dom.Element[]} An Array of elements (
         * HTMLElement or Ext.dom.Element if _asDom_ is _false_) that match the selector.
         * If there are no matches, an empty Array is returned.
         */
        query: function(selector, asDom, single) {
            var dom = this.dom,
                results, len, nlen, node, nodes, i, j;
 
            if (!dom) {
                return null;
            }
 
            asDom = (asDom !== false);
 
            selector = selector.split(",");
 
            if (!single) {
                // only allocate the results array if the full result set is being
                // requested.  selectNode() uses the 'single' param.
                results = [];
            }
 
            for (= 0, len = selector.length; i < len; i++) {
                if (typeof selector[i] === 'string') {
                    if (single) {
                        // take the "fast path" if single was requested (selectNode)
                        node = dom.querySelector(selector[i]);
                        
                        return asDom ? node : Ext.get(node);
                    }
 
                    nodes = dom.querySelectorAll(selector[i]);
 
                    for (= 0, nlen = nodes.length; j < nlen; j++) {
                        results.push(asDom ? nodes[j] : Ext.get(nodes[j]));
                    }
                }
            }
 
            return results;
        },
 
        /**
         * Adds one or more CSS classes to this element and removes the same class(es) from
         * all siblings.
         * @param {String/String[]} className The CSS class to add, or an array of classes.
         * @return {Ext.dom.Element} this
         */
        radioCls: function(className) {
            var cn = this.dom.parentNode.childNodes,
                v, i, len;
 
            className = Ext.isArray(className) ? className : [className];
            
            for (= 0, len = cn.length; i < len; i++) {
                v = cn[i];
                
                if (&& v.nodeType === 1) {
                    Ext.fly(v).removeCls(className);
                }
            }
            
            return this.addCls(className);
        },
 
        redraw: function() {
            var dom = this.dom,
                domStyle = dom.style;
 
            domStyle.display = 'none';
            dom.offsetHeight;
            domStyle.display = '';
        },
 
        /**
         * @method remove
         * @inheritdoc Ext.dom.Element#method-destroy
         * @deprecated 5.0.0 Please use {@link #destroy} instead.
         */
        remove: function() {
            this.destroy();
        },
 
        removeChild: function(element) {
            this.dom.removeChild(Ext.getDom(element));
 
            return this;
        },
 
        /**
         * Removes the given CSS class(es) from this Element.
         * @param {String/String[]} names The CSS classes to remove separated by space,
         * or an array of classes
         * @param {String} [prefix] Prefix to prepend to each class. The separator `-` will be
         * appended to the prefix.
         * @param {String} [suffix] Suffix to append to each class. The separator `-` will be
         * prepended to the suffix.
         * return {Ext.dom.Element} this
         */
        removeCls: function(names, prefix, suffix) {
            return this.replaceCls(names, null, prefix, suffix);
        },
 
        /**
         * Forces the browser to repaint this element.
         * @return {Ext.dom.Element} this
         */
        repaint: function() {
            var me = this;
 
            me.addCls(Ext.baseCSSPrefix + 'repaint');
 
            if (!me.repaintTimer) {
                me.repaintTimer = Ext.defer(function() {
                    me.repaintTimer = null;
 
                    if (me.dom) {  // may have been removed already on slower UAs
                        me.removeCls(Ext.baseCSSPrefix + 'repaint');
                    }
                }, 1);
            }
 
            return me;
        },
 
        /**
         * Replaces the passed element with this element
         * @param {String/HTMLElement/Ext.dom.Element} el The element to replace.
         * The id of the node, a DOM Node or an existing Element.
         * @param {Boolean} [destroy=true] `false` to prevent destruction of the replaced
         * element
         * @return {Ext.dom.Element} This element
         */
        replace: function(el, destroy) {
            el = Ext.getDom(el);
            
            /* eslint-disable-next-line vars-on-top */
            var parentNode = el.parentNode,
                id = el.id,
                dom = this.dom;
 
            //<debug>
            if (!parentNode) {
                Ext.raise('Cannot replace element "' + id +
                    '". It is not attached to a parent node.');
            }
            //</debug>
 
            if (destroy !== false && id && Ext.cache[id]) {
                parentNode.insertBefore(dom, el);
                Ext.get(el).destroy();
            }
            else {
                parentNode.replaceChild(dom, el);
            }
 
            return this;
        },
 
        /**
         * Replaces one or more CSS classes on this element with other classes. If the old
         * name does not exist, the new name will simply be added.
         *
         * @param {String/String[]} [remove] The CSS class(es) to be removed.
         * @param {String/String[]} [add] The CSS class(es) to be added.
         * @param {String} [prefix] The string to prepend to each class name.
         * @param {String} [suffix] The string to append to each class name.
         * @return {Ext.dom.Element} this
         */
        replaceCls: function(remove, add, prefix, suffix) {
            var me = this,
                dom = me.dom,
                added = 0,
                removed = 0,
                rem = remove,
                data = (add || remove) && me.getData(),
                list, map, i, n, name;
 
            if (data) {
                list = data.classList;
                map = data.classMap;
 
                add = add ? ((typeof add === 'string') ? add.split(spacesRe) : add) : EMPTY;
                rem = rem ? ((typeof rem === 'string') ? rem.split(spacesRe) : rem) : EMPTY;
 
                // Include the '-' but only if the caller hasn't already...
                prefix = prefix || '';
                
                if (prefix && prefix[prefix.length - 1] !== '-') {
                    prefix += '-';
                }
 
                suffix = suffix || '';
                
                if (suffix && suffix[0] !== '-') {
                    suffix = '-' + suffix;
                }
 
                for (= 0, n = rem.length; i < n; i++) {
                    if (!(name = rem[i])) {
                        // Sadly ... 'foo '.split(spacesRe) == ['foo', '']
                        continue;
                    }
 
                    name = prefix + name + suffix;
                    
                    //<debug>
                    if (spacesRe.test(name)) {
                        Ext.raise('Class names in arrays must not contain spaces');
                    }
                    //</debug>
 
                    if (map[name]) {
                        delete map[name];
                        ++removed;
                    }
                }
 
                for (= 0, n = add.length; i < n; i++) {
                    if (!(name = add[i])) {
                        continue;
                    }
 
                    name = prefix + name + suffix;
                    
                    //<debug>
                    if (spacesRe.test(name)) {
                        Ext.raise('Class names in arrays must not contain spaces');
                    }
                    //</debug>
 
                    if (!map[name]) {
                        map[name] = true;
 
                        // If we are only adding, we can be more efficient...
                        if (!removed) {
                            list.push(name);
                            ++added;
                        }
                    }
                }
 
                if (removed) {
                    me.setClassMap(map, /* keep= */ true);
                }
                else if (added) {
                    list = list.join(' ');
 
                    if (!Ext.isIE8 && dom instanceof SVGElement) {
                        // dom.className is an instance of SVGAnimatedString
                        // for SVG elements. Setting the `class` attribute
                        // of an SVG element will update its `dom.className.baseVal`.
                        dom.setAttribute('class', list);
                    }
                    else {
                        dom.className = list;
                    }
                }
            }
 
            return me;
        },
 
        /**
         * Replaces this element with the passed element
         * @param {String/HTMLElement/Ext.dom.Element/Object} el The new element (id of the
         * node, a DOM Node or an existing Element) or a DomHelper config of an element to create
         * @return {Ext.dom.Element} This element
         */
        replaceWith: function(el) {
            var me = this,
                dom = me.dom,
                parent = dom.parentNode,
                cache = Ext.cache,
                newDom;
 
            me.clearListeners();
 
            if (el.nodeType || el.dom || typeof el === 'string') {
                el = Ext.get(el);
                newDom = parent.insertBefore(el.dom, dom);
            }
            else {
                // domhelper config
                newDom = Ext.DomHelper.insertBefore(dom, el);
            }
            
            parent.removeChild(dom);
 
            me.dom = newDom;
            
            if (!me.isFly) {
                delete cache[me.id];
                cache[me.id = Ext.id(newDom)] = me;
            }
 
            return me;
        },
 
        resolveListenerScope: function(defaultScope) {
            // Override this to pass along to our owning component (if we have one).
            var component = this.component;
            
            return component ? component.resolveListenerScope(defaultScope) : this;
        },
 
        /**
         * Scrolls this element the specified direction. Does bounds checking to make sure
         * the scroll is within this element's scrollable range.
         * @param {String} direction Possible values are:
         *
         * - `"l"` (or `"left"`)
         * - `"r"` (or `"right"`)
         * - `"t"` (or `"top"`, or `"up"`)
         * - `"b"` (or `"bottom"`, or `"down"`)
         *
         * @param {Number} distance How far to scroll the element in pixels
         * @param {Boolean/Object} [animate] true for the default animation or a standard Element
         * animation config object
         * @return {Boolean} Returns true if a scroll was triggered or false if the element
         * was scrolled as far as it could go.
         */
        scroll: function(direction, distance, animate) {
            if (!this.isScrollable()) {
                return false;
            }
 
            // Allow full word, or initial to be sent.
            // (Ext.dd package uses full word)
            direction = direction.charAt(0);
            
            /* eslint-disable-next-line vars-on-top */
            var me = this,
                dom = me.dom,
                side = direction === 'r' || direction === 'l' ? 'left' : 'top',
                scrolled = false,
                currentScroll, constrainedScroll;
 
            if (direction === 'l' || direction === 't' || direction === 'u') {
                distance = -distance;
            }
 
            if (side === 'left') {
                currentScroll = dom.scrollLeft;
                constrainedScroll = me.constrainScrollLeft(currentScroll + distance);
            }
            else {
                currentScroll = dom.scrollTop;
                constrainedScroll = me.constrainScrollTop(currentScroll + distance);
            }
 
            if (constrainedScroll !== currentScroll) {
                this.scrollTo(side, constrainedScroll, animate);
                scrolled = true;
            }
 
            return scrolled;
        },
 
        /**
         * Scrolls this element by the passed delta values, optionally animating.
         *
         * All of the following are equivalent:
         *
         *      el.scrollBy(10, 10, true);
         *      el.scrollBy([10, 10], true);
         *      el.scrollBy({ x: 10, y: 10 }, true);
         *
         * @param {Number/Number[]/Object} deltaX Either the x delta, an Array specifying x and y
         * deltas or an object with "x" and "y" properties.
         * @param {Number/Boolean/Object} deltaY Either the y delta, or an animate flag or config
         * object.
         * @param {Boolean/Object} animate Animate flag/config object if the delta values were
         * passed separately.
         * @return {Ext.dom.Element} this
         */
        scrollBy: function(deltaX, deltaY, animate) {
            var me = this,
                dom = me.dom;
 
            // Extract args if deltas were passed as an Array.
            if (deltaX.length) {
                animate = deltaY;
                deltaY = deltaX[1];
                deltaX = deltaX[0];
            }
            else if (typeof deltaX !== 'number') { // or an object
                animate = deltaY;
                deltaY = deltaX.y;
                deltaX = deltaX.x;
            }
 
            if (deltaX) {
                me.scrollTo('left', me.constrainScrollLeft(dom.scrollLeft + deltaX), animate);
            }
            
            if (deltaY) {
                me.scrollTo('top', me.constrainScrollTop(dom.scrollTop + deltaY), animate);
            }
 
            return me;
        },
 
        /**
         * @private
         */
        scrollChildIntoView: function(child, hscroll) {
            // scrollFly is used inside scrollInfoView, must use a method-unique fly here
            Ext.fly(child).scrollIntoView(this, hscroll);
        },
 
        /**
         * Scrolls this element into view within the passed container.
         *
         *       Ext.create('Ext.data.Store', {
         *           storeId:'simpsonsStore',
         *           fields:['name', 'email', 'phone'],
         *           data:{'items':[
         *               { 'name': 'Lisa',  "email":"[email protected]", "phone":"555-111-1224" },
         *               { 'name': 'Bart',  "email":"[email protected]",  "phone":"555-222-1234" },
         *               { 'name': 'Homer', "email":"[email protected]",  "phone":"555-222-1244" },
         *               { 'name': 'Marge', "email":"[email protected]", "phone":"555-222-1254" },
         *               { 'name': 'Milhouse', "email":"[email protected]",
         *                 "phone":"555-222-1244" },
         *               { 'name': 'Willy', "email":"[email protected]", "phone":"555-222-1254" },
         *               { 'name': 'Skinner', "email":"[email protected]",
         *                 "phone":"555-222-1244" },
         *               { 'name': 'Hank (last row)', "email":"[email protected]",
         *                 "phone":"555-222-1254" }
         *           ]},
         *           proxy: {
         *               type: 'memory',
         *               reader: {
         *                   type: 'json',
         *                   rootProperty: 'items'
         *               }
         *           }
         *       });
         *
         *       var grid = Ext.create('Ext.grid.Panel', {
         *           title: 'Simpsons',
         *           store: Ext.data.StoreManager.lookup('simpsonsStore'),
         *           columns: [
         *               { text: 'Name',  dataIndex: 'name', width: 125 },
         *               { text: 'Email', dataIndex: 'email', flex: 1 },
         *               { text: 'Phone', dataIndex: 'phone' }
         *           ],
         *           height: 190,
         *           width: 400,
         *           renderTo: Ext.getBody(),
         *           tbar: [{
         *               text: 'Scroll row 7 into view',
         *               handler: function () {
         *                   var view = grid.getView();
         *
         *                   Ext.get(view.getRow(7)).scrollIntoView(view.getEl(), null, true);
         *               }
         *           }]
         *       });
         *
         * @param {String/HTMLElement/Ext.Element} [container=document.body] The container element
         * to scroll.  Should be a string (id), dom node, or Ext.Element.
         * @param {Boolean} [hscroll=true] False to disable horizontal scroll.
         * @param {Boolean/Object} [animate] true for the default animation or a standard Element
         * animation config object
         * @param {Boolean} [highlight=false] true to {@link #highlight} the element when it is
         * in view.
         * @return {Ext.dom.Element} this
         */
        scrollIntoView: function(container, hscroll, animate, highlight) {
            container = Ext.getDom(container) || Ext.getBody().dom;
 
            return this.doScrollIntoView(container, hscroll, animate, highlight,
                                         'getScrollLeft', 'scrollTo');
        },
 
        /**
         * Scrolls this element the specified scroll point. It does NOT do bounds checking so
         * if you scroll to a weird value it will try to do it. For auto bounds checking,
         * use #scroll.
         * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
         * @param {Number} value The new scroll value
         * @param {Boolean/Object} [animate] true for the default animation or a standard Element
         * animation config object
         * @return {Ext.dom.Element} this
         */
        scrollTo: function(side, value, animate) {
            // check if we're scrolling top or left
            var top = topRe.test(side),
                me = this,
                prop = top ? 'scrollTop' : 'scrollLeft',
                dom = me.dom,
                animCfg;
 
            if (!animate || !me.anim) {
                // just setting the value, so grab the direction
                dom[prop] = value;
                
                // corrects IE, other browsers will ignore
                dom[prop] = value;
            }
            else {
                animCfg = {
                    to: {}
                };
                
                animCfg.to[prop] = value;
                
                if (Ext.isObject(animate)) {
                    Ext.applyIf(animCfg, animate);
                }
                
                me.animate(animCfg);
            }
            
            return me;
        },
 
        /**
         * Selects descendant elements of this element based on the passed CSS selector to
         * enable {@link Ext.dom.Element Element} methods to be applied to many related
         * elements in one statement through the returned
         * {@link Ext.dom.CompositeElementLite CompositeElementLite} object.
         *
         * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
         * @param {Boolean} composite Return a CompositeElement as opposed to a
         * CompositeElementLite. Defaults to false.
         * @return {Ext.dom.CompositeElementLite/Ext.dom.CompositeElement}
         */
        select: function(selector, composite) {
            var isElementArray, elements;
 
            if (typeof selector === "string") {
                elements = this.query(selector, !composite);
            }
            //<debug>
            else if (selector.length === undefined) {
                Ext.raise("Invalid selector specified: " + selector);
            }
            //</debug>
            else {
                // if selector is not a string, assume it is already an array of
                // HTMLElement
                elements = selector;
                isElementArray = true;
            }
 
            // if the selector parameter was a string we will have called through
            // to query, and it will have constructed either an array of
            // HTMLElement or Ext.Element, depending on the composite param we gave
            // it.  If this is the case we can take the fast path through the 
            // CompositeElementLite constructor to avoid calling getDom() or get()
            // on every element in the array.
            return composite
                ? new Ext.CompositeElement(elements, !isElementArray)
                : new Ext.CompositeElementLite(elements, true);
        },
 
        /**
         * Selects a single descendant element of this element using a CSS selector
         * (see {@link #method-query}).
         * @param {String} selector The selector query
         * @param {Boolean} [asDom=true] `false` to return an Ext.dom.Element
         * @return {HTMLElement/Ext.dom.Element} The DOM element (or Ext.dom.Element if
         * _asDom_ is _false_) which matched the selector.
         */
        selectNode: function(selector, asDom) {
            return this.query(selector, asDom, true);
        },
 
        /**
         * Sets the passed attributes as attributes of this element (a `style` attribute
         * can be a string, object or function).
         *
         * Example component (though any Ext.dom.Element would suffice):
         *
         *     var cmp = Ext.create({
         *         xtype: 'component',
         *         html: 'test',
         *         renderTo: Ext.getBody()
         *     });
         *
         * Once the component is rendered, you can fetch a reference to its outer
         * element to use `set`:
         *
         *     cmp.el.set({
         *         foo: 'bar'
         *     });
         *
         * This sets an attribute on the element of **foo="bar"**:
         *
         *     <div class="x-component x-component-default x-border-box"
         *          id="component-1009" foo="bar">test</div>
         *
         * To remove the attribute pass a value of **undefined**:
         *
         *     cmp.el.set({
         *         foo: undefined
         *     });
         *
         * **Note:**
         *
         *  - You cannot remove an attribute by passing `undefined` when the
         * `expandos` param is set to **false**.
         *  - Passing an attribute of `style` results in the request being handed off to
         * {@link #method-applyStyles}.
         *  - Passing an attribute of `cls` results in the element's dom's
         * [className](http://www.w3schools.com/jsref/prop_html_classname.asp) property
         * being set directly.  For additional flexibility when setting / removing
         * classes see:
         *     - {@link #method-addCls}
         *     - {@link #method-removeCls}
         *     - {@link #method-replaceCls}
         *     - {@link #method-setCls}
         *     - {@link #method-toggleCls}
         *
         * @param {Object} attributes The object with the attributes.
         * @param {Boolean} [useSet=true] `false` to override the default `setAttribute`
         * to use [expandos](http://help.dottoro.com/ljvovanq.php).
         * @return {Ext.dom.Element} this
         */
        set: function(attributes, useSet) {
            var me = this,
                dom = me.dom,
                attribute, value;
 
            for (attribute in attributes) {
                if (attributes.hasOwnProperty(attribute)) {
                    value = attributes[attribute];
 
                    if (attribute === 'style') {
                        me.applyStyles(value);
                    }
                    else if (attribute === 'cls') {
                        dom.className = value;
                    }
                    else if (useSet !== false) {
                        if (value === undefined) {
                            dom.removeAttribute(attribute);
                        }
                        else {
                            dom.setAttribute(attribute, value);
                        }
                    }
                    else {
                        dom[attribute] = value;
                    }
                }
            }
 
            return me;
        },
 
        /**
         * Sets the element's CSS bottom style.
         * @param {Number/String} bottom Number of pixels or CSS string value to set as
         * the bottom CSS property value
         * @return {Ext.dom.Element} this
         */
        setBottom: function(bottom) {
            this.dom.style[BOTTOM] = Element.addUnits(bottom);
            
            return this;
        },
 
        /**
         * Sets the CSS classes of this element to the keys of the given object. The
         * `classMap` object is typically returned by {@link #getClassMap}. The values of
         * the properties in the `classMap` should be truthy (such as `1` or `true`).
         *
         * @param {Object} classMap The object whose keys will be the CSS classes.
         * @param {Boolean} [keep=false] Pass `true` to indicate the the `classMap`
         * object can be kept (instead of copied).
         */
        setClassMap: function(classMap, keep) {
            var data = this.getData(/* sync= */ false),
                classList;
 
            if (data) {
                classMap = (keep && classMap) || Ext.apply({}, classMap);
 
                data.classMap = classMap;
                data.classList = classList = Ext.Object.getKeys(classMap);
                data.isSynchronized = true;
 
                // We won't get a data object if !this.dom:
                this.dom.className = classList.join(' ');
            }
        },
 
        /**
         * Sets the specified CSS class on this element's DOM node.
         * @param {String/String[]} className The CSS class to set on this element.
         */
        setCls: function(className) {
            var me = this,
                elementData = me.getData(/* sync= */ false),
                i, ln, map, classList;
 
            if (typeof className === 'string') {
                className = className.split(spacesRe);
            }
 
            elementData.classList = classList = className.slice();
            elementData.classMap = map = {};
 
            for (= 0, ln = classList.length; i < ln; i++) {
                map[classList[i]] = true;
            }
            
            me.dom.className = classList.join(' ');
        },
 
        /**
         * Sets the CSS display property. Uses originalDisplay if the specified value is a
         * boolean true.
         * @param {Boolean/String} value Boolean value to display the element using its
         * default display, or a string to set the display directly.
         * @return {Ext.dom.Element} this
         */
        setDisplayed: function(value) {
            var me = this;
 
            if (typeof value === "boolean") {
                value = value ? me._getDisplay() : NONE;
            }
            
            me.setStyle(DISPLAY, value);
 
            if (me.shadow || me.shim) {
                me.setUnderlaysVisible(value !== NONE);
            }
 
            return me;
        },
 
        /**
         * Set the height of this Element.
         * @param {Number/String} height The new height.
         * @return {Ext.dom.Element} this
         */
        setHeight: function(height) {
            var me = this;
 
            me.dom.style[HEIGHT] = Element.addUnits(height);
 
            if (me.shadow || me.shim) {
                me.syncUnderlays();
            }
 
            return me;
        },
 
        /**
         * Sets the `innerHTML` of this element.
         * @param {String} html The new HTML.
         * @return {Ext.dom.Element} this
         */
        setHtml: function(html) {
            if (this.dom) {
                this.dom.innerHTML = html;
            }
            
            return this;
        },
 
        setId: function(id) {
            var me = this,
                currentId = me.id,
                cache = Ext.cache;
 
            if (currentId) {
                delete cache[currentId];
            }
 
            me.dom.id = id;
 
            /**
             * The DOM element ID
             * @property id
             * @type String
             */
            me.id = id;
 
            cache[id] = me;
 
            return me;
        },
 
        /**
         * Sets the element's left position directly using CSS style
         * (instead of {@link #setX}).
         * @param {Number/String} left Number of pixels or CSS string value to
         * set as the left CSS property value
         * @return {Ext.dom.Element} this
         */
        setLeft: function(left) {
            var me = this;
 
            me.dom.style[LEFT] = Element.addUnits(left);
 
            if (me.shadow || me.shim) {
                me.syncUnderlays();
            }
 
            return me;
        },
 
        setLocalX: function(x) {
            var me = this,
                style = me.dom.style;
 
            // clear right style just in case it was previously set by rtlSetLocalXY
            style.right = '';
            style.left = (=== null) ? 'auto' : x + 'px';
 
            if (me.shadow || me.shim) {
                me.syncUnderlays();
            }
 
            return me;
        },
 
        setLocalXY: function(x, y) {
            var me = this,
                style = me.dom.style;
 
            // clear right style just in case it was previously set by rtlSetLocalXY
            style.right = '';
 
            if (&& x.length) {
                y = x[1];
                x = x[0];
            }
 
            if (=== null) {
                style.left = 'auto';
            }
            else if (!== undefined) {
                style.left = x + 'px';
            }
 
            if (=== null) {
                style.top = 'auto';
            }
            else if (!== undefined) {
                style.top = y + 'px';
            }
 
            if (me.shadow || me.shim) {
                me.syncUnderlays();
            }
 
            return me;
        },
 
        setLocalY: function(y) {
            var me = this;
 
            me.dom.style.top = (=== null) ? 'auto' : y + 'px';
 
            if (me.shadow || me.shim) {
                me.syncUnderlays();
            }
 
            return me;
        },
 
        setMargin: function(margin) {
            var me = this,
                domStyle = me.dom.style;
 
            if (margin || margin === 0) {
                margin = me.self.unitizeBox((margin === true) ? 5 : margin);
                domStyle.setProperty('margin', margin, 'important');
            }
            else {
                domStyle.removeProperty('margin-top');
                domStyle.removeProperty('margin-right');
                domStyle.removeProperty('margin-bottom');
                domStyle.removeProperty('margin-left');
            }
        },
 
        /**
         * Set the maximum height of this Element.
         * @param {Number/String} height The new maximum height.
         * @return {Ext.dom.Element} this
         */
        setMaxHeight: function(height) {
            this.dom.style[MAX_HEIGHT] = Element.addUnits(height);
            
            return this;
        },
 
        /**
         * Set the maximum width of this Element.
         * @param {Number/String} width The new maximum width.
         * @return {Ext.dom.Element} this
         */
        setMaxWidth: function(width) {
            this.dom.style[MAX_WIDTH] = Element.addUnits(width);
            
            return this;
        },
 
        /**
         * Set the minimum height of this Element.
         * @param {Number/String} height The new minimum height.
         * @return {Ext.dom.Element} this
         */
        setMinHeight: function(height) {
            this.dom.style[MIN_HEIGHT] = Element.addUnits(height);
            
            return this;
        },
 
        /**
         * Set the minimum width of this Element.
         * @param {Number/String} width The new minimum width.
         * @return {Ext.dom.Element} this
         */
        setMinWidth: function(width) {
            this.dom.style[MIN_WIDTH] = Element.addUnits(width);
            
            return this;
        },
 
        /**
         * Set the opacity of the element
         * @param {Number} opacity The new opacity. 0 = transparent, .5 = 50% visibile,
         * 1 = fully visible, etc
         * @return {Ext.dom.Element} this
         */
        setOpacity: function(opacity) {
            var me = this;
 
            if (me.dom) {
                me.setStyle('opacity', opacity);
            }
 
            return me;
        },
 
        setPadding: function(padding) {
            var me = this,
                domStyle = me.dom.style;
 
            if (padding || padding === 0) {
                padding = me.self.unitizeBox((padding === true) ? 5 : padding);
                domStyle.setProperty('padding', padding, 'important');
            }
            else {
                domStyle.removeProperty('padding-top');
                domStyle.removeProperty('padding-right');
                domStyle.removeProperty('padding-bottom');
                domStyle.removeProperty('padding-left');
            }
        },
 
        /**
         * Sets the element's CSS right style.
         * @param {Number/String} right Number of pixels or CSS string value to
         * set as the right CSS property value
         * @return {Ext.dom.Element} this
         */
        setRight: function(right) {
            this.dom.style[RIGHT] = Element.addUnits(right);
            
            return this;
        },
 
        /**
         * Sets the left scroll position
         * @param {Number} left The left scroll position
         * @return {Ext.dom.Element} this
         */
        setScrollLeft: function(left) {
            this.dom.scrollLeft = left;
            
            return this;
        },
 
        /**
         * Sets the top scroll position
         * @param {Number} top The top scroll position
         * @return {Ext.dom.Element} this
         */
        setScrollTop: function(top) {
            this.dom.scrollTop = top;
            
            return this;
        },
 
        /**
         * Set the size of this Element.
         *
         * @param {Number/String} width The new width. This may be one of:
         *
         * - A Number specifying the new width in pixels.
         * - A String used to set the CSS width style. Animation may **not** be used.
         * - A size object in the format `{width: widthValue, height: heightValue}`.
         *
         * @param {Number/String} height The new height. This may be one of:
         *
         * - A Number specifying the new height in pixels.
         * - A String used to set the CSS height style. Animation may **not** be used.
         * @return {Ext.dom.Element} this
         */
        setSize: function(width, height) {
            var me = this,
                style = me.dom.style;
 
            if (Ext.isObject(width)) {
                // in case of object from getSize()
                height = width.height;
                width = width.width;
            }
 
            if (width !== undefined) {
                style.width = Element.addUnits(width);
            }
            
            if (height !== undefined) {
                style.height = Element.addUnits(height);
            }
 
            if (me.shadow || me.shim) {
                me.syncUnderlays();
            }
 
            return me;
        },
 
        /**
         * Wrapper for setting style properties, also takes single object parameter of
         * multiple styles.
         *
         * Styles should be a valid DOM element style property.
         * [Valid style property names](http://www.w3schools.com/jsref/dom_obj_style.asp)
         * (_along with the supported CSS version for each_)
         *
         *     // <div id="my-el">Phineas Flynn</div>
         *
         *     var el = Ext.get('my-el');
         *
         *     // two-param syntax
         *     el.setStyle('color', 'white');
         *
         *     // single-param syntax
         *     el.setStyle({
         *         fontWeight: 'bold',
         *         backgroundColor: 'gray',
         *         padding: '10px'
         *     });
         *
         * @param {String/Object} prop The style property to be set, or an object of
         * multiple styles.
         * @param {String} [value] The value to apply to the given property, or null if
         * an object was passed.
         * @return {Ext.dom.Element} this
         */
        setStyle: function(prop, value) {
            var me = this,
                dom = me.dom,
                hooks = me.styleHooks,
                style = dom.style,
                name = prop,
                hook;
 
            // we don't promote the 2-arg form to object-form to avoid the overhead...
            if (typeof name === 'string') {
                hook = hooks[name];
                
                if (!hook) {
                    hooks[name] = hook = { name: Element.normalize(name) };
                }
                
                value = (value == null) ? '' : value; // map null && undefined to ''
                
                if (hook.set) {
                    hook.set(dom, value, me);
                }
                else {
                    style[hook.name] = value;
                }
                
                if (hook.afterSet) {
                    hook.afterSet(dom, value, me);
                }
            }
            else {
                for (name in prop) {
                    hook = hooks[name];
                    
                    if (!hook) {
                        hooks[name] = hook = { name: Element.normalize(name) };
                    }
                    
                    value = prop[name];
                    value = (value == null) ? '' : value; // map null && undefined to ''
                    
                    if (hook.set) {
                        hook.set(dom, value, me);
                    }
                    else {
                        style[hook.name] = value;
                    }
                    
                    if (hook.afterSet) {
                        hook.afterSet(dom, value, me);
                    }
                }
            }
 
            return me;
        },
 
        setText: function(text) {
            this.dom.textContent = text;
        },
 
        getText: function() {
            return this.dom.textContent;
        },
 
        /**
         * Sets the element's top position directly using CSS style
         * (instead of {@link #setY}).
         * @param {Number/String} top Number of pixels or CSS string value to
         * set as the top CSS property value
         * @return {Ext.dom.Element} this
         */
        setTop: function(top) {
            var me = this;
 
            me.dom.style[TOP] = Element.addUnits(top);
 
            if (me.shadow || me.shim) {
                me.syncUnderlays();
            }
 
            return me;
        },
 
        /**
         * Sets the CSS [touch-action](https://www.w3.org/TR/pointerevents/#the-touch-action-css-property)
         * property on this element and emulates its behavior on browsers where touch-action
         * is not supported.
         *
         * @param {Object} touchAction An object with touch-action names as the keys, and
         * boolean values to enable or disable specific touch actions. Accepted keys are:
         *
         * - `panX`
         * - `panY`
         * - `pinchZoom`
         * - `doubleTapZoom`
         *
         * All touch actions are enabled (`true`) by default, so it is usually only necessary
         * to specify which touch actions to disable.  For example, the following disables
         * only vertical scrolling and double-tap-zoom on an element
         *
         *     element.setTouchAction({
         *         panY: false,
         *         doubleTapZoom: false
         *     });
         *
         * @return {Ext.dom.Element} this
         */
        setTouchAction: function(touchAction) {
            Ext.dom.TouchAction.set(this.dom, touchAction);
        },
 
        setUnderlaysVisible: function(visible) {
            var shadow = this.shadow,
                shim = this.shim;
 
            if (shadow && !shadow.disabled) {
                if (visible) {
                    shadow.show();
                }
                else {
                    shadow.hide();
                }
            }
 
            if (shim && !shim.disabled) {
                if (visible) {
                    shim.show();
                }
                else {
                    shim.hide();
                }
            }
        },
 
        /**
         * @private
         */
        setVisibility: function(isVisible) {
            var domStyle = this.dom.style;
 
            if (isVisible) {
                domStyle.removeProperty('visibility');
            }
            else {
                domStyle.setProperty('visibility', 'hidden', 'important');
            }
        },
 
        /* eslint-disable max-len */
        /**
         * Use this to change the visibility mode between {@link #VISIBILITY},
         * {@link #DISPLAY}{@link #OFFSETS}{@link #CLIP}, or {@link #OPACITY}.
         *
         * @param {Ext.dom.Element.VISIBILITY/Ext.dom.Element.DISPLAY/Ext.dom.Element.OFFSETS/Ext.dom.Element.CLIP/Ext.dom.Element.OPACITY} mode
         * The method by which the element will be {@link #hide hidden} (you can
         * also use the {@link #setVisible} or {@link #toggle} method to toggle element
         * visibility).
         *
         * @return {Ext.dom.Element} this
         */
        setVisibilityMode: function(mode) {
        /* eslint-enable max-len */
            //<debug>
            if (mode !== 1 && mode !== 2 && mode !== 3 && mode !== 4 && mode !== 5) {
                Ext.raise("visibilityMode must be one of the following: " +
                    "Ext.Element.DISPLAY, Ext.Element.VISIBILITY, Ext.Element.OFFSETS, " +
                    "Ext.Element.CLIP, or Element.OPACITY");
            }
            //</debug>
            
            this.getData().visibilityMode = mode;
            
            return this;
        },
 
        /**
         * Sets the visibility of the element based on the current visibility mode. Use
         * {@link #setVisibilityMode} to switch between the following visibility modes:
         *
         * - {@link #DISPLAY} (the default)
         * - {@link #VISIBILITY}
         * - {@link #OFFSETS}
         * - {@link #CLIP}
         * - {@link #OPACITY}
         *
         * @param {Boolean} visible Whether the element is visible.
         * @return {Ext.dom.Element} this
         */
        setVisible: function(visible) {
            var me = this,
                mode = me.getVisibilityMode(),
                addOrRemove = visible ? 'removeCls' : 'addCls';
 
            switch (mode) {
                case Element.DISPLAY:
                    me.removeCls([visibilityCls, offsetsCls, clipCls, opacityCls]);
                    me[addOrRemove](displayCls);
                    break;
 
                case Element.VISIBILITY:
                    me.removeCls([displayCls, offsetsCls, clipCls, opacityCls]);
                    me[addOrRemove](visibilityCls);
                    break;
 
                case Element.OFFSETS:
                    me.removeCls([visibilityCls, displayCls, clipCls, opacityCls]);
                    me[addOrRemove](offsetsCls);
                    break;
 
                case Element.CLIP:
                    me.removeCls([visibilityCls, displayCls, offsetsCls, opacityCls]);
                    me[addOrRemove](clipCls);
                    break;
 
                case Element.OPACITY:
                    me.removeCls([visibilityCls, displayCls, offsetsCls, clipCls]);
                    me[addOrRemove](opacityCls);
                    break;
            }
 
            if (me.shadow || me.shim) {
                me.setUnderlaysVisible(visible);
            }
 
            if (!visible && me.$ripples) {
                me.destroyAllRipples();
            }
 
            return me;
        },
 
        /**
         * Set the width of this Element.
         * @param {Number/String} width The new width.
         * @return {Ext.dom.Element} this
         */
        setWidth: function(width) {
            var me = this;
 
            me.dom.style[WIDTH] = Element.addUnits(width);
 
            if (me.shadow || me.shim) {
                me.syncUnderlays();
            }
 
            return me;
        },
 
        /**
         * Sets this Element's page-level x coordinate
         * @param {Number} x 
         * @return {Ext.dom.Element} this
         */
        setX: function(x) {
            return this.setXY([x, false]);
        },
 
        /**
         * Sets this Element's page-level x and y coordinates
         * @param {Number[]} xy 
         * @return {Ext.dom.Element} this
         */
        setXY: function(xy) {
            var me = this,
                pts = me.translatePoints(xy),
                style = me.dom.style,
                pos;
 
            me.position();
 
            // right position may have been previously set by rtlSetLocalXY 
            // so clear it here just in case.
            style.right = '';
            
            for (pos in pts) {
                if (!isNaN(pts[pos])) {
                    style[pos] = pts[pos] + 'px';
                }
            }
 
            if (me.shadow || me.shim) {
                me.syncUnderlays();
            }
 
            return me;
        },
 
        /**
         * Sets this Element's page-level y coordinate
         * @param {Number} y 
         * @return {Ext.dom.Element} this
         */
        setY: function(y) {
            return this.setXY([false, y]);
        },
 
        /**
         * Sets the z-index of this Element and synchronizes the z-index of shadow and/or
         * shim if present.
         *
         * @param {Number} zindex The new z-index to set
         * @return {Ext.dom.Element} this
         */
        setZIndex: function(zindex) {
            var me = this;
 
            if (me.shadow) {
                me.shadow.setZIndex(zindex);
            }
 
            if (me.shim) {
                me.shim.setZIndex(zindex);
            }
 
            return me.setStyle('z-index', zindex);
        },
 
        /**
         * Show this element - Uses display mode to determine whether to use "display",
         * "visibility", "offsets", or "clip". See {@link #setVisible}.
         *
         * @return {Ext.dom.Element} this
         */
        show: function() {
            return this.setVisible(true);
        },
 
        /**
         * Stops the specified event(s) from bubbling and optionally prevents the default action
         *
         *     var store = Ext.create('Ext.data.Store', {
         *         fields: ['name', 'email'],
         *         data: [{
         *             'name': 'Finn',
         *             "email": "[email protected]"
         *         }]
         *     });
         *
         *     Ext.create('Ext.grid.Panel', {
         *         title: 'Land of Ooo',
         *         store: store,
         *         columns: [{
         *             text: 'Name',
         *             dataIndex: 'name'
         *         }, {
         *             text: 'Email <img style="vertical-align:middle;" src="{some-image-src}" />',
         *             dataIndex: 'email',
         *             flex: 1,
         *             listeners: {
         *                 render: function(col) {
         *                     // Swallow the click event when the click occurs on the
         *                     // help icon - preventing the sorting of data by that
         *                     // column and instead performing an action specific to
         *                     // the help icon
         *                     var img = col.getEl().down('img');
         *                     img.swallowEvent(['click', 'mousedown'], true);
         *                     col.on('click', function() {
         *                         // logic to show a help dialog
         *                         console.log('image click handler');
         *                     }, col);
         *                 }
         *             }
         *         }],
         *         height: 200,
         *         width: 400,
         *         renderTo: document.body
         *     });
         *
         * @param {String/String[]} eventName an event / array of events to stop from bubbling
         * @param {Boolean} [preventDefault] true to prevent the default action too
         * @return {Object} Object with a destroy method to unswallow events
         * @return {Function} return.destroy method to clean up any listeners that are swallowing
         * events
         */
        swallowEvent: function(eventName, preventDefault) {
            var me = this,
                e, eLen,
                listeners = {
                    destroyable: true
                },
                fn = function(e) {
                    e.stopPropagation();
                    
                    if (preventDefault) {
                        e.preventDefault();
                    }
                };
 
            if (Ext.isArray(eventName)) {
                eLen = eventName.length;
 
                for (= 0; e < eLen; e++) {
                    listeners[eventName[e]] = fn;
                }
            }
            else {
                listeners[eventName] = fn;
            }
 
            return me.on(listeners);
        },
 
        /**
         * @private
         * @param {String} firstClass 
         * @param {String} secondClass 
         * @param {Boolean} flag 
         * @param {String} prefix 
         * @return {Mixed} 
         */
        swapCls: function(firstClass, secondClass, flag, prefix) {
            if (flag === undefined) {
                flag = true;
            }
 
            /* eslint-disable-next-line vars-on-top */
            var me = this,
                addedClass = flag ? firstClass : secondClass,
                removedClass = flag ? secondClass : firstClass;
 
            if (removedClass) {
                me.removeCls(prefix ? prefix + '-' + removedClass : removedClass);
            }
 
            if (addedClass) {
                me.addCls(prefix ? prefix + '-' + addedClass : addedClass);
            }
 
            return me;
        },
 
        /**
         * @private
         */
        synchronize: function() {
            var me = this,
                dom = me.dom,
                hasClassMap = {},
                className = dom.className,
                classList, i, ln, name,
                elementData = me.getData(/* sync= */ false);
 
            if (className && className.length > 0) {
                classList = dom.className.split(classNameSplitRegex);
 
                for (= 0, ln = classList.length; i < ln; i++) {
                    name = classList[i];
                    hasClassMap[name] = true;
                }
            }
            else {
                classList = [];
            }
 
            elementData.classList = classList;
            elementData.classMap = hasClassMap;
            elementData.isSynchronized = true;
 
            return me;
        },
 
        /**
         * @private
         */
        syncUnderlays: function() {
            var me = this,
                shadow = me.shadow,
                shim = me.shim,
                dom = me.dom,
                xy, x, y, w, h;
 
            if (me.isVisible()) {
                xy = me.getXY();
                x = xy[0];
                y = xy[1];
                w = dom.offsetWidth;
                h = dom.offsetHeight;
 
                if (shadow && !shadow.hidden) {
                    shadow.realign(x, y, w, h);
                }
 
                if (shim && !shim.hidden) {
                    shim.realign(x, y, w, h);
                }
            }
        },
 
        /**
         * Toggles the specified CSS class on this element (removes it if it already exists,
         * otherwise adds it).
         * @param {String} className The CSS class to toggle.
         * @param {Boolean} [state] If specified as `true`, causes the class to be added.
         * If specified as `false`, causes the class to be removed.
         * @return {Ext.dom.Element} this
         */
        toggleCls: function(className, state) {
            if (state == null) {
                state = !this.hasCls(className);
            }
 
            return state ? this.addCls(className) : this.removeCls(className);
        },
 
        /**
         * Toggles the element's visibility, depending on visibility mode.
         * @return {Ext.dom.Element} this
         */
        toggle: function() {
            this.setVisible(!this.isVisible());
            
            return this;
        },
 
        translate: function() {
            var transformStyleName = 'webkitTransform' in DOC.createElement('div').style
                ? 'webkitTransform'
                : 'transform';
 
            return function(x, y, z) {
 
                x = Math.round(x);
                y = Math.round(y);
                z = Math.round(z);
                
                this.dom.style[transformStyleName] = 'translate3d(' + (|| 0) + 'px, ' +
                                                     (|| 0) + 'px, ' + (|| 0) + 'px)';
            };
        }(),
 
        /**
         * @private
         */
        unwrap: function() {
            var dom = this.dom,
                parentNode = dom.parentNode,
                activeElement = (activeElFly || (activeElFly = new Ext.dom.Fly())).attach(Ext.Element.getActiveElement()), // eslint-disable-line max-len
                grandparentNode, cached, resumeFocus, tabIndex;
 
            grannyFly = grannyFly || new Ext.dom.Fly();
 
            cached = Ext.cache[activeElement.dom.id];
 
            // If the element is in the cache, we need to get the instance so
            // we can suspend events on it. If it's not in the cache, it can't
            // have any events so we don't need to suspend on it.
            if (cached) {
                activeElement = cached;
            }
 
            if (this.contains(activeElement)) {
                if (cached) {
                    cached.suspendFocusEvents();
                }
 
                resumeFocus = true;
            }
 
            if (parentNode) {
                grandparentNode = parentNode.parentNode;
 
                // See wrap() for the explanation of this jiggery-trickery
                if (resumeFocus) {
                    tabIndex = grandparentNode.getAttribute('tabIndex');
 
                    grannyFly.attach(grandparentNode);
                    grannyFly.set({ tabIndex: -1 });
                    grannyFly.suspendFocusEvents();
                    grannyFly.focus();
                }
 
                grandparentNode.insertBefore(dom, parentNode);
                grandparentNode.removeChild(parentNode);
            }
            else {
                grandparentNode = DOC.createDocumentFragment();
                grandparentNode.appendChild(dom);
            }
 
            if (resumeFocus) {
                if (cached) {
                    cached.focus();
                    cached.resumeFocusEvents();
                }
                else {
                    activeElement.focus();
                }
 
                if (grannyFly) {
                    grannyFly.resumeFocusEvents();
                    grannyFly.set({ tabIndex: tabIndex });
                }
            }
 
            return this;
        },
 
        /**
         * Walks up the dom looking for a parent node that matches the passed simple selector
         * (e.g. 'div.some-class' or 'span:first-child').
         * This is a shortcut for findParentNode() that always returns an Ext.dom.Element.
         * @param {String} simpleSelector The simple selector to test. See {@link Ext.dom.Query}
         * for information about simple selectors.
         * @param {Number/String/HTMLElement/Ext.dom.Element} [limit]
         * The max depth to search as a number or an element that causes the upward
         * traversal to stop and is **not** considered for inclusion as the result.
         * (defaults to 50 || document.documentElement)
         * @param {Boolean} [returnDom=false] True to return the DOM node instead of Ext.dom.Element
         * @return {Ext.dom.Element/HTMLElement} The matching DOM node (or HTMLElement if
         * _returnDom_ is _true_).  Or null if no match was found.
         */
        up: function(simpleSelector, limit, returnDom) {
            return this.findParentNode(simpleSelector, limit, !returnDom);
        },
 
        /**
         * @method update
         * @inheritdoc Ext.dom.Element#method-setHtml
         * @deprecated 5.0.0 Please use {@link #setHtml} instead.
         */
        update: function(html) {
            return this.setHtml(html);
        },
 
        /**
         * Creates and wraps this element with another element
         * @param {Object} [config] DomHelper element config object for the wrapper element or null
         * for an empty div
         * @param {Boolean} [returnDom=false] True to return the raw DOM element instead of
         * Ext.dom.Element
         * @param {String} [selector] A CSS selector to select a descendant node within the created
         * element to use as the wrapping element.
         * @return {HTMLElement/Ext.dom.Element} The newly created wrapper element
         */
        wrap: function(config, returnDom, selector) {
            var me = this,
                dom = me.dom,
                // In case they pass returnDom: true:
                result = Ext.DomHelper.insertBefore(dom, config || { tag: "div" }, !returnDom),
                newEl = (wrapFly || (wrapFly = new Ext.dom.Fly())).attach(Ext.getDom(result)),
                target = newEl,
                activeElement = (activeElFly || (activeElFly = new Ext.dom.Fly())).attach(Ext.Element.getActiveElement()), // eslint-disable-line max-len
                cached, resumeFocus, tabIndex;
 
            cached = Ext.cache[activeElement.dom.id];
 
            // If the element is in the cache, we need to get the instance so
            // we can suspend events on it. If it's not in the cache, it can't
            // have any events so we don't need to suspend on it.
            if (cached) {
                activeElement = cached;
            }
 
            if (selector) {
                target = newEl.selectNode(selector, returnDom);
            }
 
            if (me.contains(activeElement)) {
                if (cached) {
                    cached.suspendFocusEvents();
                }
 
                // This is workaround for the nasty IE behavior w/r/t removing and adding
                // DOM nodes that contain focus. When this happens, focus will fall back
                // to the document body *after* the present code execution path finishes,
                // with no way to control this. Instead of trying to refocus the element
                // asynchronously in a callback, we're focusing the wrapper instead,
                // adding the dom to the wrapper, and then refocusing the dom;
                // all synchronous and dandy.
                // The only side effect of all this focus juggling is that focus/blur
                // events will fire asynchronously after this code path finishes (in IE),
                // but we deal with that by ignoring these events *on particular elements*.
                // Focus event publisher will look for focus suspension flag on the element,
                // and since the flag is cleared asynchronously in the immediate callback,
                // we have enough cycles to ignore unwanted events to get away with it
                // but not too many to step on someone else's toes (hopefully).
                tabIndex = Ext.getDom(newEl).getAttribute('tabIndex');
                newEl.set({ tabIndex: -1 });
 
                newEl.suspendFocusEvents();
                newEl.focus();
 
                resumeFocus = true;
            }
 
            (target.dom || target).appendChild(dom);
 
            if (resumeFocus) {
                if (cached) {
                    cached.focus();
                    cached.resumeFocusEvents();
                }
                else {
                    activeElement.focus();
                }
 
                newEl.resumeFocusEvents();
 
                // Most often tabIndex will be undefined, and we don't want to
                // make the wrapper focusable by accident.
                newEl.set({ tabIndex: tabIndex });
            }
 
            return result;
        },
 
        /**
         * Checks whether this element can be focused programmatically or by clicking.
         * To check if an element is in the document tab flow, use {@link #isTabbable}.
         *
         * @return {Boolean} True if the element is focusable
         */
        isFocusable: function(skipVisibility) {
            var dom = this.dom,
                focusable = false,
                nodeName;
 
            if (dom && !dom.disabled) {
                nodeName = dom.nodeName;
 
                /*
                 * An element is focusable if:
                 *   - It is naturally focusable, or
                 *   - It is an anchor or link with href attribute, or
                 *   - It has a tabIndex, or
                 *   - It is an editing host (contenteditable="true")
                 *
                 * Also note that we can't check dom.tabIndex because IE will return 0
                 * for elements that have no tabIndex attribute defined, regardless of
                 * whether they are naturally focusable or not.
                 */
                focusable = !!Ext.Element.naturallyFocusableTags[nodeName] ||
                            ((nodeName === 'A' || nodeName === 'LINK') && !!dom.href) ||
                            dom.getAttribute('tabIndex') != null ||
                            dom.contentEditable === 'true';
 
                // In IE8, <input type="hidden"> does not have a corresponding style
                // so isVisible() will assume that it's not hidden.
                if (Ext.isIE8 && nodeName === 'INPUT' && dom.type === 'hidden') {
                    focusable = false;
                }
 
                // Invisible elements cannot be focused, so check that as well
                // uness the caller doesn't care.
                focusable = focusable && (skipVisibility || this.isVisible(true));
            }
 
            return focusable;
        },
 
        /**
         * Returns `true` if this Element is an input field, or is editable in any way.
         * @return {Boolean} `true` if this Element is an input field, or is editable in any way.
         */
        isInputField: function() {
            var dom = this.dom,
                contentEditable = dom.contentEditable;
 
            // contentEditable will default to inherit if not specified, only check if the
            // attribute has been set or explicitly set to true
            // http://html5doctor.com/the-contenteditable-attribute/
            // Also skip <input> tags of type="button", we use them for checkboxes
            // and radio buttons
            if ((Ext.Element.inputTags[dom.tagName] && dom.type !== 'button') ||
                (contentEditable === '' || contentEditable === 'true')) {
                return true;
            }
            
            return false;
        },
 
        /**
         * Checks whether this element participates in the sequential focus navigation,
         * and can be reached by using Tab key.
         *
         * @param {Boolean} [includeHidden=false] pass `true` if hidden, or unattached elements
         * should be returned.
         * @return {Boolean} True if the element is tabbable.
         */
        isTabbable: function(includeHidden) {
            var dom = this.dom,
                tabbable = false,
                nodeName, hasIndex, tabIndex;
 
            if (dom && !dom.disabled) {
                nodeName = dom.nodeName;
 
                // Can't use dom.tabIndex here because IE will return 0 for elements
                // that have no tabindex attribute defined, regardless of whether they are
                // naturally tabbable or not.
                tabIndex = dom.getAttribute('tabIndex');
                hasIndex = tabIndex != null;
 
                tabIndex -= 0;
 
                // Anchors and links are only naturally tabbable if they have href attribute
                // See http://www.w3.org/TR/html5/editing.html#specially-focusable
                if (nodeName === 'A' || nodeName === 'LINK') {
                    if (dom.href) {
                        // It is also possible to make an anchor untabbable by setting
                        // tabIndex < 0 on it
                        tabbable = hasIndex && tabIndex < 0 ? false : true;
                    }
 
                    // Anchor w/o href is tabbable if it has tabIndex >= 0,
                    // or if it's editable 
                    else {
                        if (dom.contentEditable === 'true') {
                            tabbable = !hasIndex || (hasIndex && tabIndex >= 0) ? true : false;
                        }
                        else {
                            tabbable = hasIndex && tabIndex >= 0 ? true : false;
                        }
                    }
                }
 
                // If an element has contenteditable="true" or is naturally tabbable,
                // then it is a potential candidate unless its tabIndex is < 0.
                else if (dom.contentEditable === 'true' ||
                         Ext.Element.naturallyTabbableTags[nodeName]) {
                    tabbable = hasIndex && tabIndex < 0 ? false : true;
                }
 
                // That leaves non-editable elements that can only be made tabbable
                // by slapping tabIndex >= 0 on them
                else {
                    if (hasIndex && tabIndex >= 0) {
                        tabbable = true;
                    }
                }
 
                // In IE8, <input type="hidden"> does not have a corresponding style
                // so isVisible() will assume that it's not hidden.
                if (Ext.isIE8 && nodeName === 'INPUT' && dom.type === 'hidden') {
                    tabbable = false;
                }
 
                // Invisible elements can't be tabbed into. If we have a component ref
                // we'll also check if the component itself is visible before incurring
                // the expense of DOM style reads.
                // Allow caller to specify that hiddens should be included.
                tabbable = tabbable && (includeHidden ||
                           ((!this.component || this.component.isVisible(true)) &&
                           this.isVisible(true)));
            }
 
            return tabbable;
        },
 
        ripplingCls: Ext.baseCSSPrefix + 'rippling',
        ripplingTransitionCls: Ext.baseCSSPrefix + 'ripple-transition',
        ripplingUnboundCls: Ext.baseCSSPrefix + 'rippling-unbound',
        rippleBubbleCls: Ext.baseCSSPrefix + 'ripple-bubble',
        rippleContainerCls: Ext.baseCSSPrefix + 'ripple-container',
        rippleWrapperCls: Ext.baseCSSPrefix + 'ripple-wrapper',
 
        // elements with 'display' property in this map cannot act as ripple container
        noRippleDisplayMap: {
            table: 1,
            'table-row': 1,
            'table-row-group': 1
        },
 
        // elements with tag name in this map cannot act as ripple container
        noRippleTagMap: {
            TABLE: 1,
            TR: 1,
            TBODY: 1
        },
 
        /**
         * Creates a ripple effect over this element. The element should be positioned
         * (either `relative` or `absolute`) prior to calling this method.
         * 
         * @param {String/Event/Ext.event.Event} [event] The event to use for ripple
         * positioning.
         * 
         * @param {Object/String} [options] Ripple options object or color to use for ripple
         * @param {String} [options.color] The color to use for the ripple effect or
         * `'default'` to use the stylesheet default color
         * {@link Global_CSS#$ripple-background-color}. When no `color` is given, the
         * element's `color` style is used.
         * @param {Boolean} [options.release] Optional determines if the ripple should happen
         * on release. Defaults to down/start.
         * @param {String} [options.delegate] Optional selector for which child to add the ripple
         * into
         * @param {String} [options.measureSelector] Optional selector for which child to use
         * to measure for ripple size.
         * @param {Number[]} [options.position] The [x,y] position in which to start the ripple.
         * @param {Boolean} [options.centered] Set to `true` to override all position
         * information and forces the ripple to be centered inside its parent.
         * @param {Boolean/String} [options.bound] Determines if the ripple is bound to
         * the parent container (default). If false ripple will expand outside of container.
         * @param {Boolean/Number} [options.diameterLimit] Maximum size, in pixels, that a ripple
         * can be.
         * A value of `false` or `0` will cause the ripple to fill its container. A value of `true`
         * will cause the ripple to use the default maximum size.
         * @param {Boolean/String} [options.fit=true] For bound ripples only. Determines if
         * ripple should search up the dom for an element that will fit the ripple
         * without clipping. Setting to false will force the unbound ripple into the specified
         * container. Defaults to `true`.
         * @param {Number} [options.destroyTime] The time (in milliseconds) to wait until
         * the ripple is destroyed.
         */
        ripple: function(event, options) {
            if (options === true || !options) {
                options = {};
            }
            else if (Ext.isString(options)) {
                options = {
                    color: options
                };
            }
 
            /* eslint-disable-next-line vars-on-top */
            var me = this,
                rippleParent = Ext.isString(options.delegate) ? me.down(options.delegate) : me,
                rippleMeasureEl = Ext.isString(options.measureSelector) ? me.down(options.measureSelector) : null, // eslint-disable-line max-len
                color = window.getComputedStyle(rippleParent.dom).color,
                unbound = options.bound === false,
                position = options.position,
                ripplingCls = me.ripplingCls,
                ripplingTransitionCls = me.ripplingTransitionCls,
                ripplingUnboundCls = me.ripplingUnboundCls,
                rippleBubbleCls = me.rippleBubbleCls,
                rippleContainerCls = me.rippleContainerCls,
                rippleWrapperCls = me.rippleWrapperCls,
                offset, width, height, rippleDiameter, center,
                measureElWidth, measureElHeight, rippleSize,
                pos, posX, posY, rippleWrapper, rippleContainer, rippleBubble,
                rippleDestructor, rippleClearFn, rippleDestructionTimer, rippleBox, unboundEl,
                unboundElData, timeout;
 
            if (rippleParent) {
                offset = rippleParent.getXY();
                width = rippleParent.getWidth();
                height = rippleParent.getHeight();
 
                timeout = rippleParent.$rippleClearTimeout;
                
                if (timeout) {
                    rippleParent.$rippleClearTimeout = Ext.undefer(timeout);
                }
 
                // If a measure element exists use that to determine the ripple diameter
                // otherwise we will just use the parent
                if (rippleMeasureEl) {
                    measureElWidth = rippleMeasureEl.getWidth();
                    measureElHeight = rippleMeasureEl.getHeight();
                    rippleDiameter = Math.max(measureElWidth, measureElHeight);
                }
                else {
                    rippleDiameter = width > height ? width : height;
                }
 
                // Cap the diameter based on the default or provided limit
                if (options.diameterLimit === undefined || options.diameterLimit === true) {
                    rippleDiameter = Math.min(rippleDiameter, Element.maxRippleDiameter);
                }
                else if (options.diameterLimit && options.diameterLimit !== false &&
                         options.diameterLimit !== 0) {
                    rippleDiameter = Math.min(rippleDiameter, options.diameterLimit);
                }
 
                // determine the center of the element we are going to ripple over
                center = [offset[0] + width / 2, offset[1] + height / 2];
 
                if (unbound) {
                    if (options.fit !== false) {
                        // Compute the bounding rippleBox that is centered over the
                        // target element.
                        rippleSize = rippleDiameter * 2.15;  // anim scales to 2.15
                        rippleBox = rippleParent.getRegion();
                        rippleBox.setPosition(rippleBox.getCenter()).setSize(rippleSize)
                                 .translateBy(-rippleSize / 2, -rippleSize / 2);
 
                        // Find the nearest parent el that will be able to fully contain
                        // the ripple... we assume that the el we are rippling is not too
                        // close to the edge
                        unboundEl = me.up(function(candidate) {
                            var fly = Ext.fly(candidate, 'ripple');
 
                            return !(candidate.tagName in me.noRippleTagMap) &&
                                    !(fly.getStyle('display') in me.noRippleDisplayMap) &&
                                    (fly.getRegion().contains(rippleBox));
                        }) || Ext.getBody();
                    }
                    else {
                        unboundEl = rippleParent;
                    }
                }
 
                // if the first param was a string, it was meant to be a color
                // otherwise if it an unwrapped event lets wrap it up
                if (Ext.isString(event)) {
                    options.color = event;
                    event = null;
                }
                else if (event && !event.isEvent) {
                    event = new Ext.event.Event(event);
                }
 
                // Check for preventRipple on the event and skip everything if its true
                if (event && event.isEvent) {
                    // Prevent ripples from the same event
                    if (event.browserEvent.$preventRipple) {
                        return;
                    }
                    
                    position = event.getXY();
                    event.browserEvent.$preventRipple = true;
                }
 
                //  unbound or centered items always center, otherwise if position is
                // provided use it
                pos = (!unbound && !options.centered && position) || center;
                posX = pos[0] - offset[0] - (rippleDiameter / 2);
                posY = pos[1] - offset[1] - (rippleDiameter / 2);
 
                // Ripple Parent always needs to be notified that it should transition
                // with the ripple bound or not
                rippleParent.addCls(ripplingTransitionCls);
                
                if (!unbound) {
                    rippleParent.addCls(ripplingCls);
                    // Is there already a container for ripples, reuse it.
                    rippleContainer = rippleParent.child('.' + rippleContainerCls);
                }
                else {
                    // unbound ripples are added to the body inside the ripple wrapper.
                    // check to see if this wrapper exists, if not create it
                    unboundElData = unboundEl.getData();
                    rippleWrapper = unboundElData.rippleWrapper;
 
                    if (!rippleWrapper) {
                        // insertFirst is important because of Field margin-top:0 rules
                        // to collapse field spacing in form panels.
                        unboundElData.rippleWrapper = rippleWrapper = unboundEl.insertFirst({
                            style: 'position: absolute; top: 0; left: 0',
                            cls: rippleWrapperCls + ' ' + ripplingCls + ' ' + ripplingUnboundCls
                        });
                    }
                }
 
                if (!rippleContainer) {
                    if (unbound) {
                        // unbound ripples are positioned inside the ripple-wrapper,
                        // which is inside the body
                        rippleContainer = rippleWrapper.append({
                            cls: rippleContainerCls
                        });
                        
                        // position the unbound ripple over-top the element
                        rippleContainer.setXY(offset);
                    }
                    else {
                        // body ripples are positioned inside the rippleParent, which is
                        // the element being rippled
                        rippleContainer = rippleParent.append({
                            cls: rippleContainerCls
                        });
                    }
                }
 
                // create the actual ripple bubble element
                rippleBubble = rippleContainer.append({
                    cls: rippleBubbleCls
                });
 
                if (options.color !== 'default') {
                    rippleBubble.setStyle('backgroundColor', options.color || color);
                }
 
                rippleBubble.setWidth(rippleDiameter);
                rippleBubble.setHeight(rippleDiameter);
                rippleBubble.setTop(posY);
                rippleBubble.setLeft(posX);
 
                rippleClearFn = function() {
                    // Allow for transition to happen then remove classes
                    // we do this instead of a transtionend listener
                    // as we do not know which element is transitioning
                    if (!rippleParent.destroyed) {
                        rippleParent.$rippleClearTimeout = Ext.defer(function() {
                            rippleParent.removeCls([ripplingCls, ripplingTransitionCls]);
                            rippleParent.$rippleClearTimeout = null;
                        }, 50);
                    }
                };
 
                rippleDestructor = function() {
                    var ripple, timeout;
 
                    // destroy the ripple
                    rippleBubble.destroy();
 
                    // remove from lookup
                    if (me.$ripples) {
                        delete me.$ripples[rippleBubble.id];
                    }
 
                    timeout = rippleParent.$rippleClearTimeout;
                    
                    if (timeout) {
                        rippleParent.$rippleClearTimeout = Ext.undefer(timeout);
                    }
 
                    if (unbound) {
                        // always destroy unbound ripple containers as they are never
                        // re-used. only the ripple-wrapper is reused
                        rippleContainer.destroy();
 
                        // Determine if there are any other ripples still active in the wrapper
                        ripple = rippleWrapper.child('.' + rippleContainerCls);
 
                        // If there are no other ripples, clean up all ripple related DOM
                        if (!ripple) {
                            unboundElData.rippleWrapper = null;
                            rippleWrapper.destroy();
                            rippleClearFn();
                        }
                    }
                    else {
                        // Determine if there are any other ripples still active in the parent
                        ripple = rippleContainer.child('.' + rippleBubbleCls);
 
                        // If there are no other ripples, clean up all ripple related DOM
                        if (!ripple) {
                            rippleContainer.destroy();
                            rippleClearFn();
                        }
                    }
 
                };
 
                rippleDestructionTimer = Ext.defer(rippleDestructor,
                                                   options.destroyTime || 1000, me);
 
                // Keep a list of all the current ripples, for cleanup later
                if (!me.$ripples) {
                    me.$ripples = {};
                }
 
                me.$ripples[rippleBubble.id] = {
                    timerId: rippleDestructionTimer,
                    destructor: rippleDestructor
                };
 
                rippleBubble.addCls(Ext.baseCSSPrefix + 'ripple');
            }
        },
 
        destroyAllRipples: function() {
            var me = this,
                rippleEl, ripple;
 
            for (rippleEl in me.$ripples) {
                ripple = me.$ripples[rippleEl];
                Ext.undefer(ripple.timerId);
 
                if (ripple.destructor) {
                    ripple.destructor();
                }
            }
 
            me.$ripples = null;
        },
 
        privates: {
            /**
             * @private
             */
            findTabbableElements: function(options) {
                var skipSelf, skipChildren, excludeRoot, includeSaved, includeHidden,
                    dom = this.dom,
                    cAttr = Ext.Element.tabbableSavedCounterAttribute,
                    selection = [],
                    idx = 0,
                    nodes, node, fly, i, len, tabIndex;
 
                if (!dom) {
                    return selection;
                }
 
                if (options) {
                    skipSelf = options.skipSelf;
                    skipChildren = options.skipChildren;
                    excludeRoot = options.excludeRoot;
                    includeSaved = options.includeSaved;
                    includeHidden = options.includeHidden;
                }
 
                excludeRoot = excludeRoot && Ext.getDom(excludeRoot);
 
                if (excludeRoot && excludeRoot.contains(dom)) {
                    return selection;
                }
 
                if (!skipSelf &&
                    ((includeSaved && dom.hasAttribute(cAttr)) || this.isTabbable(includeHidden))) {
                    selection[idx++] = dom;
                }
 
                if (skipChildren) {
                    return selection;
                }
 
                nodes = dom.querySelectorAll(Ext.Element.tabbableSelector);
                len = nodes.length;
 
                if (!len) {
                    return selection;
                }
 
                fly = new Ext.dom.Fly();
 
                // We're only interested in the elements that an user can *tab into*,
                // not all programmatically focusable elements. So we have to filter
                // these out.
                for (= 0; i < len; i++) {
                    node = nodes[i];
 
                    // A node with tabIndex < 0 absolutely can't be tabbable
                    // so we can save a function call if that is the case.
                    // Note that we can't use node.tabIndex here because IE
                    // will return 0 for elements that have no tabindex
                    // attribute defined, regardless of whether they are
                    // tabbable or not.
                    tabIndex = +node.getAttribute('tabIndex'); // quicker than parseInt
 
                    // tabIndex value may be null for nodes with no tabIndex defined;
                    // most of those may be naturally tabbable. We don't want to
                    // check this here, that's isTabbable()'s job and it's not trivial.
                    // We explicitly check that tabIndex is not negative. The expression
                    // below is purposeful if hairy; this is a very hot code path so care
                    // is taken to minimize the amount of DOM calls that could be avoided.
 
                    // A node may have its tabindex saved by previous calls to
                    // saveTabbableState(); in that case we need to return that node
                    // so that its saved counter could be properly incremented or
                    // decremented.
                    if (((includeSaved && node.hasAttribute(cAttr)) || (!(tabIndex < 0) && fly.attach(node).isTabbable(includeHidden))) && // eslint-disable-line max-len
                        !(excludeRoot && (excludeRoot === node || excludeRoot.contains(node)))) {
                        selection[idx++] = node;
                    }
                }
 
                return selection;
            },
 
            /**
             * @private
             */
            saveTabbableState: function(options) {
                var counterAttr = Ext.Element.tabbableSavedCounterAttribute,
                    savedAttr = Ext.Element.tabbableSavedValueAttribute,
                    counter, nodes, node, i, len;
 
                // By default include already saved tabbables, and just increase their save counter.
                // For example, if a View with saved tabbables is covered by a modal Window,
                // saveTabbableState must disable tabbability for the whole document.
                // But upon unmask, the View must not be restored to tabbability. It must only have
                // its save level decremented.
                // AbstractView#toggleChildrenTabbability however pases this as false so that
                // it may be called upon row add and it does not increment save levels on already
                // saved tabbables.
                if (!options || options.includeSaved == null) {
                    options = Ext.Object.chain(options || null);
                    options.includeSaved = true;
                }
 
                nodes = this.findTabbableElements(options);
 
                for (= 0, len = nodes.length; i < len; i++) {
                    node = nodes[i];
 
                    counter = +node.getAttribute(counterAttr);
 
                    if (counter > 0) {
                        node.setAttribute(counterAttr, ++counter);
                    }
                    else {
                        // tabIndex could be set on both naturally tabbable and generic elements.
                        // Either way we need to save it to restore later.
                        if (node.hasAttribute('tabIndex')) {
                            node.setAttribute(savedAttr, node.getAttribute('tabIndex'));
                        }
 
                        // When no tabIndex is specified, that means a naturally tabbable element.
                        else {
                            node.setAttribute(savedAttr, 'none');
                        }
 
                        // We disable the tabbable state by setting tabIndex to -1.
                        // The element can still be focused programmatically though.
                        node.setAttribute('tabIndex', '-1');
                        node.setAttribute(counterAttr, '1');
                    }
                }
 
                return nodes;
            },
 
            /**
             * @private
             */
            restoreTabbableState: function(options) {
                var dom = this.dom,
                    counterAttr = Ext.Element.tabbableSavedCounterAttribute,
                    savedAttr = Ext.Element.tabbableSavedValueAttribute,
                    nodes = [],
                    skipSelf = options && options.skipSelf,
                    skipChildren = options && options.skipChildren,
                    reset = options && options.reset,
                    idx, counter, node, i, len;
 
                if (!dom) {
                    return this;
                }
 
                if (!skipChildren) {
                    nodes = Ext.Array.from(dom.querySelectorAll('[' + counterAttr + ']'));
                }
 
                if (!skipSelf) {
                    nodes.unshift(dom);
                }
 
                for (= 0, len = nodes.length; i < len; i++) {
                    node = nodes[i];
 
                    if (!node.hasAttribute(counterAttr) || !node.hasAttribute(savedAttr)) {
                        continue;
                    }
 
                    counter = +node.getAttribute(counterAttr);
 
                    if (!reset && counter > 1) {
                        node.setAttribute(counterAttr, --counter);
 
                        continue;
                    }
 
                    idx = node.getAttribute(savedAttr);
 
                    // That is a naturally tabbable element
                    if (idx === 'none') {
                        node.removeAttribute('tabIndex');
                    }
                    else {
                        node.setAttribute('tabIndex', idx);
                    }
 
                    node.removeAttribute(savedAttr);
                    node.removeAttribute(counterAttr);
                }
 
                return nodes;
            },
 
            /**
             * @private
             */
            setTabIndex: function(tabIndex) {
                var dom = this.dom,
                    savedAttr = Ext.Element.tabbableSavedValueAttribute;
 
                if (dom.hasAttribute(savedAttr)) {
                    if (tabIndex == null) {
                        // Equivalent to removing tabIndex while not saved
                        dom.setAttribute(savedAttr, 'none');
                        dom.removeAttribute('tabIndex');
                    }
                    else {
                        dom.setAttribute(savedAttr, tabIndex);
                    }
                }
                else {
                    if (tabIndex == null) {
                        dom.removeAttribute('tabIndex');
                    }
                    else {
                        dom.setAttribute('tabIndex', tabIndex);
                    }
                }
            },
 
            doAddListener: function(eventName, fn, scope, options, order, caller, manager) {
                var me = this,
                    originalName = eventName,
                    observableDoAddListener, additiveEventName,
                    translatedEventName;
 
                // Even though the superclass method does conversion to lowercase, we need
                // to do it here because we need to use the lowercase name for lookup
                // in the event translation map.
                eventName = Ext.canonicalEventName(eventName);
 
                // Blocked events (such as emulated mouseover in mobile webkit) are prevented
                // from firing
                if (!me.blockedEvents[eventName]) {
                    observableDoAddListener = me.mixins.observable.doAddListener;
                    options = options || {};
 
                    if (Element.useDelegatedEvents === false) {
                        options.delegated = options.delegated || false;
                    }
 
                    if (options.translate !== false) {
                        // translate events where applicable.  This allows applications that
                        // were written for desktop to work on mobile devices and vice versa.
                        additiveEventName = me.additiveEvents[eventName];
                        
                        if (additiveEventName) {
                            // additiveEvents means the translation is "additive" - meaning we
                            // need to attach the original event in addition to the translated
                            // one.  An example of this is devices that have both mousedown
                            // and touchstart
                            options.type = eventName;
                            eventName = additiveEventName;
                            observableDoAddListener.call(me, eventName, fn, scope, options, order,
                                                         caller, manager);
                        }
 
                        translatedEventName = me.eventMap[eventName];
                        
                        if (translatedEventName) {
                            // options.type may have already been set above
                            options.type = options.type || eventName;
                            
                            if (manager) {
                                options.managedName = originalName;
                            }
                            
                            eventName = translatedEventName;
                        }
                    }
 
                    if (observableDoAddListener.call(me, eventName, fn, scope, options, order, caller, manager)) { // eslint-disable-line max-len
                        if (me.longpressEvents[eventName] && (++me.longpressListenerCount === 1)) {
                            me.on('MSHoldVisual', 'preventMsHoldVisual', me);
                        }
                    }
 
                    if (manager && translatedEventName) {
                        delete options.managedName;
                    }
 
                    // after the listener has been added to the ListenerStack, it's original
                    // "type" (for translated events) will be stored on the listener object in
                    // the ListenerStack.  We can now delete type from the options object
                    // since it is not a user-supplied option
                    delete options.type;
                }
            },
 
            doRemoveListener: function(eventName, fn, scope) {
                var me = this,
                    observableDoRemoveListener, translatedEventName, additiveEventName,
                    removed;
 
                // Even though the superclass method does conversion to lowercase, we need
                // to do it here because we need to use the lowercase name for lookup
                // in the event translation map.
                eventName = Ext.canonicalEventName(eventName);
 
                // Blocked events (such as emulated mouseover in mobile webkit) are prevented
                // from firing
                if (!me.blockedEvents[eventName]) {
                    observableDoRemoveListener = me.mixins.observable.doRemoveListener;
 
                    // translate events where applicable.  This allows applications that
                    // were written for desktop to work on mobile devices and vice versa.
                    additiveEventName = me.additiveEvents[eventName];
                    
                    if (additiveEventName) {
                        // additiveEvents means the translation is "additive" - meaning we
                        // need to remove the original event in addition to the translated
                        // one.  An example of this is devices that have both mousedown
                        // and touchstart
                        eventName = additiveEventName;
                        observableDoRemoveListener.call(me, eventName, fn, scope);
                    }
 
                    translatedEventName = me.eventMap[eventName];
                    
                    if (translatedEventName) {
                        removed = observableDoRemoveListener.call(me, translatedEventName, fn,
                                                                  scope);
                    }
 
                    // no "else" here because we need to ensure that we remove translate:false
                    // listeners
                    removed = observableDoRemoveListener.call(me, eventName, fn, scope) || removed;
 
                    if (removed) {
                        if (me.longpressEvents[eventName] && !--me.longpressListenerCount) {
                            me.un('MSHoldVisual', 'preventMsHoldVisual', me);
                        }
                    }
                }
            },
 
            _initEvent: function(eventName) {
                return (this.events[eventName] = new Ext.dom.ElementEvent(this, eventName));
            },
 
            _getDisplay: function() {
                var data = this.getData(),
                    display = data[ORIGINALDISPLAY];
 
                if (display === undefined) {
                    data[ORIGINALDISPLAY] = display = '';
                }
                
                return display;
            },
 
            /**
             * Returns the publisher for a given event
             * @param {String} eventName 
             * @param {Boolean} [noTranslate] `true` if the event is a non translated event
             * @private
             * @return {Ext.event.publisher.Publisher} 
             */
            _getPublisher: function(eventName, noTranslate) {
                var Publisher = Ext.event.publisher.Publisher,
                    publisher = Publisher.publishersByEvent[eventName],
                    isNative = noTranslate && !Ext.event.Event.gestureEvents[eventName];
 
                // Dom publisher acts as the default publisher for all events that are
                // not explicitly handled by another publisher.
                // ElementSize handles the 'resize' event, except on the window
                // object, where it is handled by Dom publisher.
                // If the event is a native event (not translated), we want to use the
                // DOM publisher.
                // For example the dragstart gesture would automatically shadow any native
                // drag events, so we force the lower level publisher to be used. The exception
                // is for touch events, they all need to be handled by the gesture publisher
                // so they can be interrogated and produce the correct output.
                if (isNative || !publisher || (this.dom === window && eventName === 'resize')) {
                    publisher = Publisher.publishers.dom;
                }
 
                return publisher;
            },
 
            isFocusSuspended: function() {
                var data = this.peekData();
 
                return data && data.suspendFocusEvents;
            },
 
            preventMsHoldVisual: function(e) {
                e.preventDefault();
            },
 
            suspendFocusEvents: function() {
                if (!this.isFly) {
                    this.suspendEvent('focus', 'blur');
                }
 
                this.getData().suspendFocusEvents = true;
            },
 
            resumeFocusEvents: function() {
                function resumeFn() {
                    var data;
 
                    if (!this.destroyed) {
                        data = this.getData();
 
                        if (data) {
                            data.suspendFocusEvents = false;
                        }
 
                        if (!this.isFly) {
                            this.resumeEvent('focus', 'blur');
                        }
                    }
                }
 
                if (!this.destroyed && this.getData().suspendFocusEvents) {
                    if (Ext.isIE && !this.isFly) {
                        this.resumeFocusEventsTimer = Ext.asap(resumeFn, this);
                    }
                    else {
                        resumeFn.call(this);
                    }
                }
            }
        },
 
        deprecated: {
            '5.0': {
                methods: {
                    /**
                     * @method getHTML
                     * @inheritdoc Ext.dom.Element#getHtml
                     * @deprecated 5.0.0 Please use {@link #getHtml} instead.
                     */
                    getHTML: 'getHtml',
 
                    /**
                     * @method getPageBox
                     * Returns an object defining the area of this Element which can be passed to
                     * {@link Ext.util.Positionable#setBox} to set another Element's size/location
                     * to match this element.
                     *
                     * @param {Boolean} [getRegion] If true an Ext.util.Region will be returned
                     * @return {Object/Ext.util.Region} box An object in the following format:
                     *
                     *     {
                     *         left: <Element's X position>,
                     *         top: <Element's Y position>,
                     *         width: <Element's width>,
                     *         height: <Element's height>,
                     *         bottom: <Element's lower bound>,
                     *         right: <Element's rightmost bound>
                     *     }
                     *
                     * The returned object may also be addressed as an Array where index 0 contains
                     * the X position and index 1 contains the Y position. So the result may also be
                     * used for {@link #setXY}
                     * @deprecated 5.0.0 use {@link Ext.util.Positionable#getBox} to get a box
                     * object, and {@link Ext.util.Positionable#getRegion} to get a
                     * {@link Ext.util.Region Region}.
                     */
                    getPageBox: function(getRegion) {
                        var me = this,
                            dom = me.dom,
                            isDoc = dom.nodeName === 'BODY',
                            w = isDoc ? Element.getViewportWidth() : dom.offsetWidth,
                            h = isDoc ? Element.getViewportHeight() : dom.offsetHeight,
                            xy = me.getXY(),
                            t = xy[1],
                            r = xy[0] + w,
                            b = xy[1] + h,
                            l = xy[0];
 
                        if (getRegion) {
                            return new Ext.util.Region(t, r, b, l);
                        }
                        else {
                            return {
                                left: l,
                                top: t,
                                width: w,
                                height: h,
                                right: r,
                                bottom: b
                            };
                        }
                    },
 
                    /**
                     * @method isTransparent
                     * Returns `true` if the value of the given property is visually transparent.
                     * This may be due to a 'transparent' style value or an rgba value with 0
                     * in the alpha component.
                     * @param {String} prop The style property whose value is to be tested.
                     * @return {Boolean} `true` if the style property is visually transparent.
                     * @deprecated 5.0.0 This method is deprecated.
                     */
                    isTransparent: function(prop) {
                        var value = this.getStyle(prop);
 
                        return value ? transparentRe.test(value) : false;
                    },
 
                    /**
                     * @method purgeAllListeners
                     * @inheritdoc Ext.dom.Element#clearListeners
                     * @deprecated 5.0.0 Please use {@link #clearListeners} instead.
                     */
                    purgeAllListeners: 'clearListeners',
 
                    /**
                     * @method removeAllListeners
                     * @inheritdoc Ext.dom.Element#clearListeners
                     * @deprecated 5.0.0 Please use {@link #clearListeners} instead.
                     */
                    removeAllListeners: 'clearListeners',
 
                    /**
                     * @method setHTML
                     * @inheritdoc Ext.dom.Element#setHtml
                     * @deprecated 5.0.0 Please use {@link #setHtml} instead.
                     */
                    setHTML: 'setHtml'
                }
            }
        }
    };
}, function(Element) {
    var DOC = document,
        docEl = DOC.documentElement,
        prototype = Element.prototype,
        supports = Ext.supports,
        pointerdown = 'pointerdown',
        pointermove = 'pointermove',
        pointerup = 'pointerup',
        pointercancel = 'pointercancel',
        MSPointerDown = 'MSPointerDown',
        MSPointerMove = 'MSPointerMove',
        MSPointerUp = 'MSPointerUp',
        MSPointerCancel = 'MSPointerCancel',
        mousedown = 'mousedown',
        mousemove = 'mousemove',
        mouseup = 'mouseup',
        mouseover = 'mouseover',
        mouseout = 'mouseout',
        mouseenter = 'mouseenter',
        mouseleave = 'mouseleave',
        touchstart = 'touchstart',
        touchmove = 'touchmove',
        touchend = 'touchend',
        touchcancel = 'touchcancel',
        click = 'click',
        dblclick = 'dblclick',
        tap = 'tap',
        doubletap = 'doubletap',
        eventMap = prototype.eventMap = {},
        additiveEvents = prototype.additiveEvents = {},
        oldId = Ext.id,
        eventOptions;
 
    prototype._init(Element);
    delete prototype._init;
 
    /**
     * Generates unique ids. If the element already has an id, it is unchanged
     * @member Ext
     * @param {Object/HTMLElement/Ext.dom.Element} [obj] The element to generate an id for
     * @param {String} prefix (optional) Id prefix (defaults "ext-gen")
     * @return {String} The generated Id.
     */
    Ext.id = function(obj, prefix) {
        var el = obj && Ext.getDom(obj, true),
            sandboxPrefix, id;
 
        if (!el) {
            id = oldId(obj, prefix);
        }
        else if (!(id = el.id)) {
            id = oldId(null, prefix || Element.prototype.identifiablePrefix);
 
            if (Ext.isSandboxed) {
                sandboxPrefix = Ext.sandboxPrefix ||
                    (Ext.sandboxPrefix = Ext.sandboxName.toLowerCase() + '-');
                id = sandboxPrefix + id;
            }
 
            el.id = id;
        }
 
        return id;
    };
 
    if (supports.PointerEvents) {
        eventMap[mousedown] = pointerdown;
        eventMap[mousemove] = pointermove;
        eventMap[mouseup] = pointerup;
        eventMap[touchstart] = pointerdown;
        eventMap[touchmove] = pointermove;
        eventMap[touchend] = pointerup;
        eventMap[touchcancel] = pointercancel;
 
        // On devices that support pointer events we block pointerover, pointerout,
        // pointerenter, and pointerleave when triggered by touch input (see
        // Ext.event.publisher.Dom#blockedPointerEvents).  This is because mouseover
        // behavior is typically not desired when touching the screen.  This covers the
        // use case where user code requested a pointer event, however mouseover/mouseout
        // events are not cancellable, period.
        // http://www.w3.org/TR/pointerevents/#mapping-for-devices-that-do-not-support-hover
        // To ensure mouseover/out handlers don't fire when touching the screen, we need
        // to translate them to their pointer equivalents
        eventMap[mouseover] = 'pointerover';
        eventMap[mouseout] = 'pointerout';
        eventMap[mouseenter] = 'pointerenter';
        
        // No decent way to feature detect this, pointerleave relatedTarget is
        // incorrect on IE11, so force it to use mouseleave here.
        // See: https://connect.microsoft.com/IE/feedback/details/851111/ev-relatedtarget-in-pointerleave-indicates-departure-element-not-destination-element
        if (!Ext.isIE11) {
            eventMap[mouseleave] = 'pointerleave';
        }
    }
    else if (supports.MSPointerEvents) {
        // IE10
        eventMap[pointerdown] = MSPointerDown;
        eventMap[pointermove] = MSPointerMove;
        eventMap[pointerup] = MSPointerUp;
        eventMap[pointercancel] = MSPointerCancel;
        eventMap[mousedown] = MSPointerDown;
        eventMap[mousemove] = MSPointerMove;
        eventMap[mouseup] = MSPointerUp;
        eventMap[touchstart] = MSPointerDown;
        eventMap[touchmove] = MSPointerMove;
        eventMap[touchend] = MSPointerUp;
        eventMap[touchcancel] = MSPointerCancel;
 
        // translate mouseover/out so they can be prevented on touch screens.
        // (see above comment in the PointerEvents section)
        eventMap[mouseover] = 'MSPointerOver';
        eventMap[mouseout] = 'MSPointerOut';
    }
    else if (supports.TouchEvents) {
        eventMap[pointerdown] = touchstart;
        eventMap[pointermove] = touchmove;
        eventMap[pointerup] = touchend;
        eventMap[pointercancel] = touchcancel;
        eventMap[mousedown] = touchstart;
        eventMap[mousemove] = touchmove;
        eventMap[mouseup] = touchend;
        eventMap[click] = tap;
        eventMap[dblclick] = doubletap;
 
        if (Ext.os.is.Desktop) {
            // Touch enabled desktop browsers on windows such as Firefox and Chrome fire
            // both mouse events and touch events, so we have to attach listeners for both
            // kinds when either one is requested.  There are a couple rules to keep in mind:
            // 1. When the mouse is used, only a mouse event is fired
            // 2. When interacting with the touch screen touch events are fired.
            // 3. After a touchstart/touchend sequence, if there was no touchmove in
            // between, the browser will fire a mousemove/mousedown/mousup sequence
            // immediately after.  This can cause problems because if we are listening
            // for both kinds of events, handlers may run twice.  To work around this
            // issue we filter out the duplicate emulated mouse events by checking their
            // coordinates and timing (see Ext.event.publisher.Gesture#onDelegatedEvent)
            eventMap[touchstart] = mousedown;
            eventMap[touchmove] = mousemove;
            eventMap[touchend] = mouseup;
            eventMap[touchcancel] = mouseup;
 
            additiveEvents[mousedown] = mousedown;
            additiveEvents[mousemove] = mousemove;
            additiveEvents[mouseup] = mouseup;
            additiveEvents[touchstart] = touchstart;
            additiveEvents[touchmove] = touchmove;
            additiveEvents[touchend] = touchend;
            additiveEvents[touchcancel] = touchcancel;
 
            additiveEvents[pointerdown] = mousedown;
            additiveEvents[pointermove] = mousemove;
            additiveEvents[pointerup] = mouseup;
            additiveEvents[pointercancel] = mouseup;
        }
    }
    else {
        // browser does not support either pointer or touch events, map all pointer and
        // touch events to their mouse equivalents
        eventMap[pointerdown] = mousedown;
        eventMap[pointermove] = mousemove;
        eventMap[pointerup] = mouseup;
        eventMap[pointercancel] = mouseup;
        eventMap[touchstart] = mousedown;
        eventMap[touchmove] = mousemove;
        eventMap[touchend] = mouseup;
        eventMap[touchcancel] = mouseup;
    }
 
    if (Ext.isWebKit) {
        // These properties were carried forward from touch-2.x. This translation used
        // do be done by DomPublisher.  TODO: do we still need this?
        eventMap.transitionend = Ext.browser.getVendorProperyName('transitionEnd');
        eventMap.animationstart = Ext.browser.getVendorProperyName('animationStart');
        eventMap.animationend = Ext.browser.getVendorProperyName('animationEnd');
    }
 
    if (!Ext.supports.MouseWheel && !Ext.isOpera) {
        eventMap.mousewheel = 'DOMMouseScroll';
    }
 
    eventOptions = prototype.$eventOptions = Ext.Object.chain(prototype.$eventOptions);
    eventOptions.translate = eventOptions.capture = eventOptions.delegate = eventOptions.delegated =
            eventOptions.stopEvent = eventOptions.preventDefault = eventOptions.stopPropagation =
            // Ext.Element also needs "element" as one of its event options.  Even though
            // it does not directly process an element option, it may receive a listeners
            // object that was passed through from a Component with the "element" option
            // included. Including "element" in the event options ensures we don't attempt
            // to process "element" as an event name.
            eventOptions.element = 1;
 
 
    prototype.styleHooks.opacity = {
        name: 'opacity',
        afterSet: function(dom, value, el) {
            var shadow = el.shadow;
            
            if (shadow) {
                shadow.setOpacity(value);
            }
        }
    };
 
    /**
     * @member Ext
     * @private
     * Returns the `X,Y` position of this element without regard to any RTL
     * direction settings.
     */
    prototype.getTrueXY = prototype.getXY;
 
    /**
     * @member Ext
     * @method getViewportHeight
     * @inheritdoc Ext.dom.Element#getViewportHeight
     * @since 6.5.0
     */
    Ext.getViewportHeight = Element.getViewportHeight;
 
    /**
     * @member Ext
     * @method getViewportWidth
     * @inheritdoc Ext.dom.Element#getViewportWidth
     * @since 6.5.0
     */
    Ext.getViewportWidth = Element.getViewportWidth;
 
    /**
     * @member Ext
     * @method select
     * Shorthand for {@link Ext.dom.Element#method-select Ext.dom.Element.select}<br><br>
     * @inheritdoc Ext.dom.Element#method-select
     */
    Ext.select = Element.select;
 
    /**
     * @member Ext
     * @method query
     * Shorthand for {@link Ext.dom.Element#method-query Ext.dom.Element.query}<br><br>
     * @inheritdoc Ext.dom.Element#method-query
     */
    Ext.query = Element.query;
 
    Ext.apply(Ext, {
        /**
         * @member Ext
         * @method get
         */
        get: function(element) {
            return Element.get(element);
        },
 
        /**
         * @member Ext
         * @method getDom
         * Return the dom node for the passed String (id), dom node, or Ext.Element.
         * Here are some examples:
         *
         *     // gets dom node based on id
         *     var elDom = Ext.getDom('elId');
         *
         *     // gets dom node based on the dom node
         *     var elDom1 = Ext.getDom(elDom);
         *
         *     // If we don't know if we are working with an
         *     // Ext.Element or a dom node use Ext.getDom
         *     function(el){
         *         var dom = Ext.getDom(el);
         *         // do something with the dom node
         *     }
         *
         * __Note:__ the dom node to be found actually needs to exist (be rendered, etc)
         * when this method is called to be successful.
         *
         * @param {String/HTMLElement/Ext.dom.Element} el
         * @return {HTMLElement} 
         */
        getDom: function(el) {
            if (!el || !DOC) {
                return null;
            }
 
            // We could be passed an Element whos dom has been nulled on destruction;
            // use 'dom' in rather than truthiness.
            return typeof el === 'string' ? Ext.getElementById(el) : 'dom' in el ? el.dom : el;
        },
 
        /**
         * @member Ext
         * Returns the current document body as an {@link Ext.dom.Element}.
         * @return {Ext.dom.Element} The document body.
         */
        getBody: function() {
            if (!Ext._bodyEl) {
                if (!DOC.body) {
                    throw new Error("[Ext.getBody] document.body does not yet exist");
                }
 
                Ext._bodyEl = Ext.get(DOC.body);
                Ext._bodyEl.skipGarbageCollection = true;
            }
 
            return Ext._bodyEl;
        },
 
        /**
         * @member Ext
         * Returns the current document head as an {@link Ext.dom.Element}.
         * @return {Ext.dom.Element} The document head.
         */
        getHead: function() {
            if (!Ext._headEl) {
                Ext._headEl = Ext.get(DOC.head || DOC.getElementsByTagName('head')[0]);
                Ext._headEl.skipGarbageCollection = true;
            }
 
            return Ext._headEl;
        },
 
        /**
         * @member Ext
         * Returns the current HTML document object as an {@link Ext.dom.Element}.
         * Typically used for attaching event listeners to the document.  Note: since
         * the document object is not an HTMLElement many of the Ext.dom.Element methods
         * are not applicable and may throw errors if called on the returned
         * Element instance.
         * @return {Ext.dom.Element} The document.
         */
        getDoc: function() {
            if (!Ext._docEl) {
                Ext._docEl = Ext.get(DOC);
                Ext._docEl.skipGarbageCollection = true;
            }
 
            return Ext._docEl;
        },
 
        /**
         * @member Ext
         * Returns the current window object as an {@link Ext.dom.Element}.
         * Typically used for attaching event listeners to the window.  Note: since
         * the window object is not an HTMLElement many of the Ext.dom.Element methods
         * are not applicable and may throw errors if called on the returned
         * Element instance.
         * @return {Ext.dom.Element} The window.
         */
        getWin: function() {
            if (!Ext._winEl) {
                Ext._winEl = Ext.get(window);
                Ext._winEl.skipGarbageCollection = true;
            }
 
            return Ext._winEl;
        },
 
        /**
         * @member Ext
         * Removes an HTMLElement from the document.  If the HTMLElement was previously
         * cached by a call to Ext.get(), removeNode will call the {@link Ext.Element#destroy
         * destroy} method of the {@link Ext.dom.Element} instance, which removes all DOM
         * event listeners, and deletes the cache reference.
         * @param {HTMLElement} node The node to remove
         * @method
         */
        removeNode: function(node) {
            node = node.dom || node;
 
            /* eslint-disable-next-line vars-on-top */
            var id = node && node.id,
                el = Ext.cache[id],
                parent;
 
            if (el) {
                el.destroy();
            }
            else if (node && (node.nodeType === 3 || node.tagName.toUpperCase() !== 'BODY')) {
                parent = node.parentNode;
                
                if (parent) {
                    parent.removeChild(node);
                }
            }
        }
    });
 
    // TODO: make @inline work - SDKTOOLS-686
    // @inline
    Ext.isGarbage = function(dom) {
        // determines if the dom element is in the document or in the detached body element
        // use by collectGarbage and Ext.get()
        return dom &&
            // window, document, documentElement, and body can never be garbage.
            dom.nodeType === 1 && dom.tagName !== 'BODY' && dom.tagName !== 'HTML' &&
            // if the element does not have a parent node, it is definitely not in the
            // DOM - we can exit immediately
            (!dom.parentNode ||
            // If the element has an offsetParent we can bail right away, it is
            // definitely in the DOM. If offsetParent is null, the element is detached.
            // If offsetParent is undefined, the element doesn't support offsetParent
            // (e.g. SVGElement) and is not necessarily garbage; parentNode check above
            // should be sufficient in this case.
            (dom.offsetParent === null &&
            // if the element does not have an offsetParent it can mean the element is
            // either not in the dom or it is hidden.  The next step is to check to see
            // if it can be found by id using either document.all or getElementById(),
            // whichever is faster for the current browser.  Normally we would not
            // include IE-specific checks in the core package, however,  in this
            // case the function will be inlined and therefore cannot be overridden in
            // the ext package.
                ((Ext.isIE8 ? DOC.all[dom.id] : DOC.getElementById(dom.id)) !== dom) &&
                // finally if the element was not found in the dom by id, we need to check
                // the detachedBody element
                !(Ext.detachedBodyEl && Ext.detachedBodyEl.isAncestor(dom))));
    };
 
    Ext.onInternalReady(function() {
        var bodyCls = [],
            theme;
 
        // Element.unselectable relies on this listener to prevent selection in IE. Some other
        // browsers support the event too but it is only strictly required for IE. In WebKit
        // this listener causes subtle differences to how the browser handles the non-selection,
        // e.g. whether or not the mouse cursor changes when attempting to select text.
        Ext.getDoc().on('selectstart', function(ev, dom) {
            var selectableCls = Element.selectableCls,
                unselectableCls = Element.unselectableCls,
                tagName = dom && dom.tagName,
                el = new Ext.dom.Fly();
 
            tagName = tagName && tagName.toLowerCase();
 
            // Element.unselectable is not really intended to handle selection within text fields
            // and it is important that fields inside menus or panel headers don't inherit
            // the unselectability. In most browsers this is automatic but in IE 9 the selectstart
            // event can bubble up from text fields so we have to explicitly handle that case.
            if (tagName === 'input' || tagName === 'textarea') {
                return;
            }
 
            // Walk up the DOM checking the nodes. This may be 'slow' but selectstart events
            // don't fire very often
            while (dom && dom.nodeType === 1 && dom !== DOC.documentElement) {
                el.attach(dom);
 
                // If the node has the class x-selectable then stop looking, the text selection
                // is allowed
                if (el.hasCls(selectableCls)) {
                    return;
                }
 
                // If the node has class x-unselectable then the text selection needs to be stopped
                if (el.hasCls(unselectableCls)) {
                    ev.stopEvent();
                    
                    return;
                }
 
                dom = dom.parentNode;
            }
        });
 
        if (Ext.os.is.Android || (Ext.os.is.Windows && Ext.supports.Touch)) {
            // Some mobile devices (android and windows) fire window resize events
            // When the virtual keyboard is displayed. This causes unexpected visual
            // results due to extra layouts of the viewport.  Here we attach a couple
            // of event listeners that will help us detect if the virtual keyboard
            // is open so tha getViewportWidth/getViewportHeight can report the
            // original size as the viewport size while the keyboard is open
            var win = Ext.getWin(); // eslint-disable-line vars-on-top, one-var
 
            Element._documentWidth = Element._viewportWidth = docEl.clientWidth;
            Element._documentHeight = Element._viewportHeight = docEl.clientHeight;
 
            win.on({
                // Focus in/out listeners track the last focus change so we can detect
                // the proximity of the last focus change relative to window resize events
                // alowing us to guess with reasonable certainty that a virtual keyboard
                // is being shown.
                focusin: '_onWindowFocusChange',
                focusout: '_onWindowFocusChange',
                // This pointerup listener is needed because in windowsthe virtual keyboard
                // can be hidden manually while the editable element retains focus by tapping
                // a hide button on the virtual keyboard itself. The virtual keyboard can then
                // be re-shown by tapping on the editable element.  In this case the editable
                // element does not fire a focusin event since it already has the focus, but
                // we still need to track that an event occurred which will cause the virtual
                // keyboard to show momentarily.
                pointerup: '_onWindowFocusChange',
                capture: true,
                delegated: false,
                delay: 1,
                scope: Element
            });
 
            win.on({
                // Resize listener for tracking virtual keyboard state.
                resize: '_onWindowResize',
                priority: 2000,
                scope: Element
            });
        }
 
        if (supports.Touch) {
            bodyCls.push(Ext.baseCSSPrefix + 'touch');
        }
 
        if (Ext.isIE && Ext.isIE9m) {
            bodyCls.push(Ext.baseCSSPrefix + 'ie', Ext.baseCSSPrefix + 'ie9m');
 
            // very often CSS needs to do checks like "IE7+" or "IE6 or 7". To help
            // reduce the clutter (since CSS/SCSS cannot do these tests), we add some
            // additional classes:
            //
            //      x-ie7p      : IE7+      :  7 <= ieVer
            //      x-ie7m      : IE7-      :  ieVer <= 7
            //      x-ie8p      : IE8+      :  8 <= ieVer
            //      x-ie8m      : IE8-      :  ieVer <= 8
            //      x-ie9p      : IE9+      :  9 <= ieVer
            //      x-ie78      : IE7 or 8  :  7 <= ieVer <= 8
            //
            bodyCls.push(Ext.baseCSSPrefix + 'ie8p');
 
            if (Ext.isIE8) {
                bodyCls.push(Ext.baseCSSPrefix + 'ie8');
            }
            else {
                bodyCls.push(Ext.baseCSSPrefix + 'ie9', Ext.baseCSSPrefix + 'ie9p');
            }
 
            if (Ext.isIE8m) {
                bodyCls.push(Ext.baseCSSPrefix + 'ie8m');
            }
        }
 
        if (Ext.isIE10) {
            bodyCls.push(Ext.baseCSSPrefix + 'ie10');
        }
 
        if (Ext.isIE10p) {
            bodyCls.push(Ext.baseCSSPrefix + 'ie10p');
        }
 
        if (Ext.isIE11) {
            bodyCls.push(Ext.baseCSSPrefix + 'ie11');
        }
 
        if (Ext.isEdge) {
            bodyCls.push(Ext.baseCSSPrefix + 'edge');
        }
 
        if (Ext.isGecko) {
            bodyCls.push(Ext.baseCSSPrefix + 'gecko');
        }
        
        if (Ext.isOpera) {
            bodyCls.push(Ext.baseCSSPrefix + 'opera');
        }
        
        if (Ext.isOpera12m) {
            bodyCls.push(Ext.baseCSSPrefix + 'opera12m');
        }
        
        if (Ext.isWebKit) {
            bodyCls.push(Ext.baseCSSPrefix + 'webkit');
        }
        
        if (Ext.isSafari) {
            bodyCls.push(Ext.baseCSSPrefix + 'safari');
        }
        
        if (Ext.isSafari9) {
            bodyCls.push(Ext.baseCSSPrefix + 'safari9');
        }
        
        if (Ext.isSafari10) {
            bodyCls.push(Ext.baseCSSPrefix + 'safari10');
        }
 
        if (Ext.isSafari) {
            if (Ext.browser.version.isLessThan(11)) {
                bodyCls.push(Ext.baseCSSPrefix + 'safari10m');
            }
 
            if (Ext.browser.version.isLessThan(9)) {
                bodyCls.push(Ext.baseCSSPrefix + 'safari8m');
            }
        }
        
        if (Ext.isChrome) {
            bodyCls.push(Ext.baseCSSPrefix + 'chrome');
        }
        
        if (Ext.isMac) {
            bodyCls.push(Ext.baseCSSPrefix + 'mac');
        }
        
        if (Ext.isWindows) {
            bodyCls.push(Ext.baseCSSPrefix + 'windows');
        }
        
        if (Ext.isLinux) {
            bodyCls.push(Ext.baseCSSPrefix + 'linux');
        }
        
        if (!supports.CSS3BorderRadius) {
            bodyCls.push(Ext.baseCSSPrefix + 'nbr');
        }
        
        if (!supports.CSS3LinearGradient) {
            bodyCls.push(Ext.baseCSSPrefix + 'nlg');
        }
        
        if (supports.Touch) {
            bodyCls.push(Ext.baseCSSPrefix + 'touch');
        }
        
        if (Ext.os.deviceType) {
            bodyCls.push(Ext.baseCSSPrefix + Ext.os.deviceType.toLowerCase());
        }
 
        if (Ext.os.is.BlackBerry) {
            bodyCls.push(Ext.baseCSSPrefix + 'bb');
            
            if (Ext.browser.userAgent.match(/Kbd/gi)) {
                // blackberry with physical keyboard
                bodyCls.push(Ext.baseCSSPrefix + 'bb-keyboard');
            }
        }
 
        if (Ext.os.is.iOS && Ext.isSafari) {
            bodyCls.push(Ext.baseCSSPrefix + 'mobile-safari');
        }
 
        if (Ext.os.is.iOS && Ext.browser.is.WebView && !Ext.browser.is.Standalone) {
            // ios cordova app
            bodyCls.push(Ext.baseCSSPrefix + 'ios-native');
        }
 
        if (Ext.supports.FlexBoxBasisBug) {
            bodyCls.push(Ext.baseCSSPrefix + 'has-flexbasis-bug');
        }
 
        Ext.getBody().addCls(bodyCls);
 
        theme = Ext.theme;
        
        if (theme && theme.getDocCls) {
            // hook for theme overrides to add css classes to the <html> element
            Ext.fly(document.documentElement).addCls(theme.getDocCls());
        }
    }, null, { priority: 1500 });
});