/**
 * This mixin implements focus management functionality in Widgets and Components
 */
Ext.define('Ext.mixin.Focusable', {
    mixinId: 'focusable',
    
    $isFocusableEntity: true,
    
    // tabIndex config is now defined in Ext.Component
    
    /**
     * @property {Boolean} focusable
     * @readonly
     *
     * `true` for keyboard interactive Components or Widgets, `false` otherwise.
     * For Containers, this property reflects interactiveness of the
     * Container itself, not its children. See {@link #isFocusable}.
     *
     * **Note:** It is not enough to set this property to `true` to make
     * a component keyboard interactive. You also need to make sure that
     * the component's {@link #focusEl} is reachable via Tab key (tabbable).
     * See also {@link #tabIndex}.
     */
    focusable: false,
 
    /**
     * @property {Boolean} hasFocus `true` if this component's {@link #focusEl} is focused.
     * See also {@link #containsFocus}.
     *
     * @readonly
     */
    hasFocus: false,
    
    /**
     * @property {Boolean} containsFocus `true` if this currently focused element
     * is within this Component's or Container's hierarchy. This property is set separately
     * from {@link #hasFocus}, and can be `true` when `hasFocus` is `false`.
     *
     * Examples:
     *
     * + Text field with input element focused would be:
     *      focusable: true,
     *      hasFocus: true,
     *      containsFocus: true
     *
     * + Date field with drop-down picker currently focused would be:
     *      focusable: true,
     *      hasFocus: false,
     *      containsFocus: true
     *
     * + Form Panel with a child input field currently focused would be:
     *      focusable: false,
     *      hasFocus: false,
     *      containsFocus: true
     *
     * See also {@link #hasFocus}.
     *
     * @readonly
     */
    containsFocus: false,
    
    /**
     * @cfg {String} [focusCls='x-focused'] CSS class that will be added to focused
     * component's {@link #focusClsEl}, and removed when component blurs.
     */
    focusCls: Ext.baseCSSPrefix + 'focused',
    
    /**
     * @property {Ext.dom.Element} focusEl The element that will be focused
     * when {@link #method!focus} method is called on this component. Usually this is
     * the same element that receives focus via mouse clicks, taps, and pressing
     * Tab key.
     */
    focusEl: 'el',
    
    /**
     * @property {Ext.dom.Element} focusClsEl The element that will have the
     * {@link #focusCls} applied when component's {@link #focusEl} is focused.
     */
    
    /**
     * @event focus
     * Fires when this Component's {@link #focusEl} receives focus.
     * @param {Ext.Component/Ext.Widget} this
     * @param {Ext.event.Event} event The focus event.
     */
 
    /**
     * @event blur
     * Fires when this Component's {@link #focusEl} loses focus.
     * @param {Ext.Component} this 
     * @param {Ext.event.Event} event The blur event.
     */
    
    /**
     * @event focusenter
     * Fires when focus enters this Component's hierarchy.
     * @param {Ext.Component} this 
     * @param {Ext.event.Event} event The focusenter event.
     */
    
    /**
     * @event focusleave
     * Fires when focus leaves this Component's hierarchy.
     * @param {Ext.Component} this 
     * @param {Ext.event.Event} event The focusleave event.
     */
    
    /**
     * Returns the main focus holder element associated with this Focusable, i.e.
     * the element that will be focused when Focusable's {@link #method!focus} method is
     * called. For most Focusables, this will be the {@link #focusEl}.
     *
     * @return {Ext.dom.Element} 
     * @protected
     */
    getFocusEl: function(/* private e */) {
        var focusEl = this.focusEl;
        
        return focusEl && focusEl.dom ? focusEl : null;
    },
    
    /**
     * Returns the element used to apply focus styling CSS class when Focusable's
     * {@link #focusEl} becomes focused. By default it is {@link #focusEl}.
     *
     * @param {Ext.dom.Element} [focusEl] Return focus styling element for the given
     * focused element. This is used by Components implementing multiple focusable
     * elements.
     *
     * @return {Ext.dom.Element} The focus styling element.
     * @protected
     */
    getFocusClsEl: function() {
        return this.getFocusEl();
    },
    
    /**
     * Template method to do any Focusable related initialization that
     * does not involve event listeners creation.
     * @protected
     */
    initFocusable: Ext.emptyFn,
    
    /**
     * Template method to do any event listener initialization for a Focusable.
     * This generally happens after the focusEl is available.
     * @protected
     */
    initFocusableEvents: function(force) {
        // If *not* naturally focusable, then we look for the tabIndex property
        // to be defined which indicates that the element should be made focusable.
        this.initFocusableElement(force);
    },
 
    enableFocusable: Ext.emptyFn,
    
    disableFocusable: function() {
        var me = this;
        
        // If this is disabled while focused, by default, focus would return to document.body.
        // This must be avoided, both for the convenience of keyboard users, and also
        // for when focus is tracked within a tree, such as below an expanded ComboBox.
        if (me.hasFocus) {
            me.revertFocus();
        }
 
        me.removeFocusCls();
    },
 
    destroyFocusable: function() {
        var me = this;
        
        Ext.destroy(me.focusListeners);
        
        me.focusListeners = me.focusEnterEvent = me.focusTask = null;
        me.focusEl = me.ariaEl = null;
    },
    
    /**
     * Determine if this Focusable can receive focus at this time.
     *
     * Note that Containers can be non-focusable themselves while delegating
     * focus treatment to a child Component; see {@link Ext.Container #defaultFocus}
     * for more information.
     *
     * @param {Boolean} [deep=false] Optionally determine if the container itself
     * is focusable, or if container's focus is delegated to a child component
     * and that child is focusable.
     *
     * @return {Boolean} True if component is focusable, false if not.
     */
    isFocusable: function(deep) {
        var me = this,
            focusEl;
        
        if (!me.focusable && (!me.isContainer || !deep)) {
            return false;
        }
        
        focusEl = me.getFocusEl();
        
        if (focusEl && me.canFocus()) {
 
            // getFocusEl might return a Component if a Container wishes to
            // delegate focus to a descendant. Both Component and Element
            // implement isFocusable, so always ask that.
            return focusEl && !focusEl.destroyed && focusEl.isFocusable(deep);
        }
        
        return false;
    },
 
    /**
     * Determines if this Component is inside a Component tree which is destroyed, *or
     * is being destroyed*.
     * @return {boolean} `true` if this Component, or any ancestor is destroyed, or
     * is being destroyed.
     * @private
     */
    isDestructing: function() {
        var c;
        
        for (= this; c; c = c.getRefOwner()) {
            if (c.destroying || c.destroyed) {
                return true;
            }
        }
        
        return false;
    },
 
    canFocus: function(skipVisibility, includeFocusTarget) {
        var me = this,
            ownerFC = me.ownerFocusableContainer,
            focusableIfDisabled = ownerFC && ownerFC.allowFocusingDisabledChildren,
            canFocus;
        
        // Containers may have focusable children while being non-focusable
        // themselves; this is why we only account for me.focusable for
        // ordinary Components here and below.
        // MenuItems must accept focus when disabled.
        canFocus = !me.destroyed && me.rendered && !me.isDestructing() &&
                (me.isContainer || me.focusable) &&
                (!me.isDisabled() || focusableIfDisabled) &&
                (skipVisibility || me.isVisible(true));
        
        return canFocus || (includeFocusTarget && !!me.findFocusTarget());
    },
    
    /**
     * Try to focus this component.
     *
     * If this component is disabled or otherwise not focusable, a close relation
     * will be targeted for focus instead to keep focus localized for keyboard users.
     *
     * @param {Boolean/Number[]} [selectText] If applicable, `true` to also select all the text
     * in this component, or an array consisting of start and end (defaults to start)
     * position of selection.
     *
     * @return {Boolean} `true` if focus target was found and focusing was attempted,
     * `false` if no focusing attempt was made.
     */
    focus: function(selectText) {
        var me = this,
            focusTarget, focusElDom;
 
        if ((!me.focusable && !me.isContainer) || me.destroyed || me.destroying) {
            return false;
        }
        
        // Assignment in conditional here to fall through to else block
        // if me.canFocus() returns true but there is no focus target
        if (me.canFocus() && (focusTarget = me.getFocusEl())) {
            // getFocusEl might return a child Widget or Component if a Container wishes
            // to delegate focus to a descendant via its defaultFocus configuration.
            if (focusTarget.$isFocusableEntity) {
                return focusTarget.focus.apply(focusTarget, arguments);
            }
 
            focusElDom = focusTarget.dom;
 
            // This is an Element instance
            if (focusElDom) {
                // NOT focusElDom.focus() here! Gotta run through possible Element overrides!
                focusTarget.focus();
 
                if (selectText && (me.selectText || focusElDom.select)) {
                    if (me.selectText) {
                        if (Ext.isArray(selectText)) {
                            me.selectText.apply(me, selectText);
                        }
                        else {
                            me.selectText();
                        }
                    }
                    else {
                        focusElDom.select();
                    }
                }
            }
            // Could be a Widget or something else with no dom property but focusable
            else if (focusTarget.focus) {
                focusTarget.focus();
            }
            else {
                return false;
            }
        }
        else {
            // If we are asked to focus while not able to focus though disablement/invisibility etc,
            // focus may revert to document.body if the current focus is being hidden or destroyed.
            // This must be avoided, both for the convenience of keyboard users, and also
            // for when focus is tracked within a tree, such as below an expanded ComboBox.
            focusTarget = me.findFocusTarget();
            
            if (focusTarget && focusTarget !== me) {
                return focusTarget.focus.apply(focusTarget, arguments);
            }
            else {
                return false;
            }
        }
 
        return true;
    },
 
    /**
     * @private
     */
    onBlur: function(e) {
        var me = this,
            container = me.ownerFocusableContainer;
        
        me.hasFocus = false;
        
        if (me.beforeBlur && !me.beforeBlur.$emptyFn) {
            me.beforeBlur(e);
        }
        
        if (container) {
            container.beforeFocusableChildBlur(me, e);
        }
        
        me.removeFocusCls(e);
        
        if (me.hasListeners.blur) {
            me.fireEvent('blur', me, e);
        }
        
        if (me.postBlur && !me.postBlur.$emptyFn) {
            me.postBlur(e);
        }
        
        if (container) {
            container.afterFocusableChildBlur(me, e);
        }
    },
 
    /**
     * @private
     */
    onFocus: function(e) {
        var me = this,
            container = me.ownerFocusableContainer;
 
        if (me.canFocus()) {
            if (me.beforeFocus && !me.beforeFocus.$emptyFn) {
                me.beforeFocus(e);
            }
            
            if (container) {
                container.beforeFocusableChildFocus(me, e);
            }
            
            me.addFocusCls(e);
            
            if (!me.hasFocus) {
                me.hasFocus = true;
                me.fireEvent('focus', me, e);
            }
            
            if (me.postFocus && !me.postFocus.$emptyFn) {
                me.postFocus(e);
            }
            
            if (container) {
                container.afterFocusableChildFocus(me, e);
            }
        }
    },
    
    /**
     * Return the actual tabIndex for this Focusable.
     *
     * @return {Number} tabIndex attribute value
     */
    getTabIndex: function() {
        var me = this,
            el, index;
        
        if (!me.focusable) {
            return;
        }
        
        el = me.getFocusEl();
        
        if (el) {
            // getFocusEl may return a child Widget or Component
            if (el.$isFocusableEntity) {
                index = el.getTabIndex();
            }
            
            else if (el.isElement && el.dom) {
                // We can't query el.dom.tabIndex because IE8 will return 0
                // when tabIndex attribute is not present, and Chrome will
                // return -1. Can't trust a browser to do a simplest thing. :/
                index = el.dom.getAttribute('tabIndex');
                
                // This contraption is here because we can't simply coerce
                // the returned attribute value to a number. If the attribute
                // is not present, the value returned will be null, and coercing
                // null gives 0. Wondrous JavaScript. :/
                if (index !== null) {
                    index -= 0;
                }
            }
            
            // A component can be configured with el: '#id' to look up
            // its main element from the DOM rather than render it; in
            // such case getTabIndex() may happen to be called before
            // said lookup has happened; indeterminate result follows.
            else {
                return;
            }
        }
        
        if (typeof index !== 'number') {
            index = me.tabIndex;
        }
        
        return index;
    },
    
    /**
     * Set the tabIndex property for this Focusable. If the focusEl
     * is available, set tabIndex attribute on it, too.
     *
     * @param {Number} newTabIndex new tabIndex to set
     * @param {HTMLElement} [focusEl] (private)
     */
    setTabIndex: function(newTabIndex, focusEl) {
        var me = this,
            ownerFC = me.ownerFocusableContainer,
            focusableIfDisabled = ownerFC && ownerFC.allowFocusingDisabledChildren,
            wasFocusable = me.focusable,
            el;
        
        // See comments for definition of forceTabIndex as to why this is needed
        // Return early if not focusable unless we are either forcing the tabIndex to
        // be set, or we are removing the tabIndex attribute.
        if (!wasFocusable && !(me.forceTabIndex || newTabIndex == null)) {
            return;
        }
        
        me.tabIndex = newTabIndex;
 
        // We must not do this if we are destroyed, or if we are incapable of being focused.
        // Handle our owning FocusableContainer having allowFocusingDisabledChildren
        if (me.destroying || me.destroyed || (me.isDisabled() && !focusableIfDisabled)) {
            return;
        }
 
        // getFocusEl does not return the element if focusable is false.
        // If we are forcing the tabIndex, or setting it to null, we
        // need it to return the focusEl.
        me.focusable = true;
        el = focusEl || me.getFocusEl();
        me.focusable = wasFocusable;
        
        if (el) {
            // getFocusEl may return a child Widget or Component
            if (el.$isFocusableEntity) {
                el.setTabIndex(newTabIndex);
            }
            
            // Or if a component is configured with el: '#id', it may
            // still be a string by the time setTabIndex is called from
            // owner FocusableContainer.
            else if (el.isElement && el.dom) {
                // setTabIndex is aware of saved tabbable state
                el.setTabIndex(newTabIndex);
            }
        }
    },
    
    /**
     * @template
     * @protected
     * Called when focus enters this Component's hierarchy
     * @param {Object} e 
     * @param {Ext.event.Event} e.event The underlying DOM event.
     * @param {HTMLElement} e.target The element gaining focus.
     * @param {HTMLElement} e.relatedTarget The element losing focus.
     * @param {Ext.Component} e.toComponent The Component gaining focus.
     * @param {Ext.Component} e.fromComponent The Component losing focus.
     * @param {Boolean} e.backwards `true` if the `fromComponent` is *after* the `toComponent*
     * in the DOM tree, indicating that the user used `SHIFT+TAB` to move focus. Note that setting
     * `tabIndex` values to affect tabbing order can cause this to be incorrect. Setting
     * `tabIndex` values is not advised.
     */
    onFocusEnter: function(e) {
        var me = this;
        
        // We DO NOT check if `me` is focusable here. The reason is that
        // non-focusable containers need to track focus entering their
        // children so that revertFocus would work if these children
        // become unavailable.
        if (me.destroying || me.destroyed) {
            return;
        }
 
        // Save all information about how we received focus so that
        // we can do appropriate things when asked to revertFocus
        me.focusEnterEvent = e;
        me.containsFocus = true;
        
        if (me.hasListeners.focusenter) {
            me.fireEvent('focusenter', me, e);
        }
    },
 
    /**
     * @template
     * @protected
     * Called when focus exits from this Component's hierarchy
     * @param {Ext.event.Event} e 
     * @param {Ext.event.Event} e.event The underlying DOM event.
     * @param {HTMLElement} e.target The element gaining focus.
     * @param {HTMLElement} e.relatedTarget The element losing focus.
     * @param {Ext.Component} e.toComponent The Component gaining focus.
     * @param {Ext.Component} e.fromComponent The Component losing focus.
     */
    onFocusLeave: function(e) {
        var me = this;
 
        // Same as in onFocusEnter
        if (me.destroying || me.destroyed) {
            return;
        }
 
        me.focusEnterEvent = null;
        me.containsFocus = false;
        
        if (me.hasListeners.focusleave) {
            me.fireEvent('focusleave', me, e);
        }
    },
 
    /**
     * @template
     * @method
     * @protected
     * Called when focus moves *within* this Component's hierarchy
     * @param {Object} info 
     * @param {Ext.event.Event} info.event The underlying Event object.
     * @param {HTMLElement} info.toElement The element gaining focus.
     * @param {HTMLElement} info.fromElement The element losing focus.
     * @param {Ext.Component} info.toComponent The Component gaining focus.
     * @param {Ext.Component} info.fromComponent The Component losing focus.
     * @param {Boolean} info.backwards `true` if the focus movement is backward in DOM order
     */
    onFocusMove: Ext.emptyFn,
 
    privates: {
        // This private flag was introduced to work around an issue where
        // the tab index would not be stamped onto the component.
        // Consider a tool, which is focusable by default and has tabIndex: 0
        // on the class definition. If a non-focusable tool is required, setting
        // focusable: false is not enough, the tabIndex also needs to be considered.
        // However, the default behaviour is to not modify the tabIndex on non
        // focusable items. This config can go away if that behaviour is changed.
        // Arguably, a non-focusable widget probably shouldn't retain a tab index
        // if it's explicitly configured.
        forceTabIndex: false,
        
        /**
         * Returns focus to the Component or element found in the cached
         * focusEnterEvent.
         *
         * @private
         */
        revertFocus: function() {
            var me = this,
                focusEvent = me.focusEnterEvent,
                activeElement = Ext.Element.getActiveElement(),
                focusTarget, fromComponent, reverted;
 
            // If we have a record of where focus arrived from,
            //  and have not been told to avoid refocusing,
            //  and we contain the activeElement.
            // Then, before hiding, restore focus to what was focused before we were focused.
            if (focusEvent && !me.preventRefocus && me.el.contains(activeElement)) {
 
                fromComponent = focusEvent.fromComponent;
 
                // If the default focus reversion target is in shifting ground, fall back
                // to document.body
                if (fromComponent && (fromComponent.destroyed || fromComponent.isDestructing())) {
                    focusTarget = document.body;
                }
 
                // Preferred focus target is the actual element from which focus entered
                // this component. It will be up to its encapsulating component to handle this
                // in an appropriate way. For example, a grid, upon having focus pushed
                // to a certain cell will set its navigation position to that cell and highlight it
                // as focused. Likewise an input field must handle its field acquiring focus.
                else {
                    focusTarget = focusEvent.relatedTarget;
                }
 
                // If focus was from the body, try to keep it closer than that
                if (focusTarget === document.body) {
                    fromComponent = me.findFocusTarget();
                    
                    if (fromComponent) {
                        focusTarget = fromComponent.getFocusEl();
                    }
                }
 
                if (focusTarget && focusTarget.$isFocusableEntity) {
                    if (!focusTarget.destroyed && focusTarget.isFocusable()) {
                        focusTarget.focus();
                    }
                }
                
                // If the element is in the document and focusable, then we're good. The owning
                // component will handle it.
                else if (Ext.getDoc().contains(focusTarget) && Ext.fly(focusTarget).isFocusable()) {
                    fromComponent = Ext.Component.from(focusTarget);
 
                    // Allow the focus recieving component to modify the focus sequence.
                    if (fromComponent) {
                        fromComponent.revertFocusTo(focusTarget);
                    }
                    else {
                        focusTarget.focus();
                    }
                }
 
                // If the element has gone, or is hidden, we will have to rely on the intelligent
                // focus diversion of components to send focus back to somewhere that is least
                // surprising for the user.
                else if (focusEvent.fromComponent && focusEvent.fromComponent.focus) {
                    reverted = focusEvent.fromComponent.focus();
 
                    // The component was not able to find a suitable target.
                    // Important: Touch platforms do not blur programmatically focused elements
                    // when they become hidden, so we must force the issue in order to maintain
                    // focus tracking.
                    if (!reverted) {
                        activeElement.blur();
                    }
                }
            }
        },
 
        /**
         * This field is on the recieving end of a call from {@link #method!revertFocus}.
         *
         * It is called when focus is being pushed back into this Component from a Component
         * that is focused and is being hidden or disabled.
         *
         * We must focus the passed element.
         *
         * Subclasses may perform some extra processing to prepare for refocusing.
         * @param target
         */
        revertFocusTo: function(target) {
            target.focus();
        },
 
        /**
         * Finds an alternate Component to focus if this Component is disabled while focused, or
         * focused while disabled, or otherwise unable to focus.
         * 
         * In both cases, focus must not be lost to document.body, but must move to an intuitively
         * connectible Component, either a sibling, or uncle or nephew.
         *
         * This is both for the convenience of keyboard users, and also for when focus is tracked
         * within a Component tree such as for ComboBoxes and their dropdowns.
         *
         * For example, a ComboBox with a PagingToolbar in is BoundList. If the "Next Page"
         * button is hit, the LoadMask shows and focuses, the next page is the last page, so
         * the "Next Page" button is disabled. When the LoadMask hides, it attempt to focus the
         * last focused Component which is the disabled "Next Page" button. In this situation,
         * focus should move to a sibling within the PagingToolbar.
         * 
         * @return {Ext.Component} A closely related focusable Component to which focus can move.
         * @private
         */
        findFocusTarget: function() {
            var me = this,
                parentAxis, candidate, len, i, focusTargets, focusIndex;
            
            if (me.preventRefocus) {
                return null;
            }
 
            // Create an axis of visible, enabled, stable parent components which we
            // will walk up looking for ancestors to revert focus to.
            //
            // First, find all enabled parents.
            // eslint-disable-next-line max-len
            for (parentAxis = [], candidate = me.getRefOwner(); candidate; candidate = candidate.getRefOwner()) {
                if (!candidate.isDisabled()) {
                    parentAxis.unshift(candidate);
                }
            }
 
            // Then walk downwards until we encounter a non-targetable parent
            // which means hidden of destroying. All candidates *above* that
            // are potential sources of focus targets.
            for (= 0, len = parentAxis.length; i < len; i++) {
                candidate = parentAxis[i];
                
                if (candidate.destroying || !candidate.isVisible()) {
                    parentAxis.length = i;
                    break;
                }
            }
 
            // Walk up the parent axis checking each parent for focus targets.
            for (= parentAxis.length - 1; i >= 0; i--) {
                candidate = parentAxis[i];
                // Use CQ to find a target that is fully focusable (:canfocus, NOT the theoretical
                // :focusable). Cannot use :focusable(true) because that consults findFocusTarget
                // and would cause infinite recursion.
                // Exclude the component which currently has focus.
                // Cannot use candidate.child() because the parent might not be a Container.
                // Non-Container Components may still have ownership relationships with
                // other Components. eg: BoundList with PagingToolbar
                focusTargets = Ext.ComponentQuery.query(':canfocus()', candidate);
                
                if (focusTargets.length) {
                    // eslint-disable-next-line max-len
                    focusIndex = Ext.Array.indexOf(focusTargets, Ext.ComponentManager.getActiveComponent());
 
                    // Return the next focusable, or the previous focusable, or the first focusable
                    return focusTargets[focusIndex + 1] || focusTargets[focusIndex - 1] ||
                           focusTargets[0];
                }
 
                // We found no focusable siblings in our candidate, but the candidate may itself
                // be focusable, it is not always a Container - could be the owning Field
                // of a BoundList.
                if (candidate.isFocusable && candidate.isFocusable()) {
                    return candidate;
                }
            }
        },
    
        /**
         * Sets up the focus listener on this Component's {@link #getFocusEl focusEl} if it has one.
         *
         * Form Components which must implicitly participate in tabbing order usually have
         * a naturally focusable element as their {@link #getFocusEl focusEl}, and it is
         * the DOM event of that receiving focus which drives the Component's `onFocus` handling,
         * and the DOM event of it being blurred which drives the `onBlur` handling.
         * @private
         */
        initFocusableElement: function(force) {
            var me = this,
                tabIndex = me.getTabIndex(),
                focusEl = me.getFocusEl();
 
            if (focusEl && !focusEl.$isFocusableEntity) {
                // focusEl is not available until after rendering, and rendering tabIndex
                // into focusEl is not always convenient. So we apply it here if Component's
                // tabIndex property is set and Component is otherwise focusable.
                // Note that for Widgets and Modern Components we might not have the rendered
                // flag set yet but can force setting the property.
                if (tabIndex != null && (force || me.canFocus(true))) {
                    me.setTabIndex(tabIndex, focusEl);
                }
                
                // This attribute is a shortcut to look up a Component by its Elements
                // It only makes sense on focusable elements, so we set it here unless
                // our focusEl is delegated to the focusEl of an owned Component and it
                // already has ownership stamped into it.
                if (!focusEl.dom.hasAttribute('data-componentid')) {
                    focusEl.dom.setAttribute('data-componentid', me.id);
                }
            }
        },
        
        addFocusCls: function(e) {
            var focusCls = this.focusCls,
                el;
            
            el = this.getFocusClsEl();
            
            if (focusCls) {
                el = this.getFocusClsEl(e);
                
                if (el) {
                    el.addCls(focusCls);
                }
            }
        },
        
        removeFocusCls: function(e) {
            var focusCls = this.focusCls,
                el;
            
            if (focusCls) {
                el = this.getFocusClsEl(e);
                
                if (el) {
                    el.removeCls(focusCls);
                }
            }
        },
        
        /**
         * @private
         */
        handleFocusEvent: function(info) {
            var me = this,
                event;
            
            if (!me.focusable || me.destroying || me.destroyed) {
                return;
            }
            
            // handleFocusEvent and handleBlurEvent are called by ComponentManager
            // passing the normalized element event that might or might not cause
            // component focus or blur. The component itself makes the decision
            // whether focus/blur happens or not. This is necessary for components
            // that might have more than one focusable element within the component's
            // DOM structure, like Ext.button.Split.
            if (me.isFocusing(info)) {
                event = new Ext.event.Event(info.event);
                event.type = 'focus';
                event.relatedTarget = info.fromElement;
                event.target = info.toElement;
                
                me.onFocus(event);
            }
        },
        
        /**
         * @private
         */
        handleBlurEvent: function(info) {
            var me = this,
                event;
            
            if (!me.focusable || me.destroying || me.destroyed) {
                return;
            }
 
            // Must process blur if toElement is document.body
            // Blurs caused by the app losing focus are processed synchronously
            // for obvious reasons, so the activeElement will still be the
            // focusEl, so isBlurring will return false, and reject this op.
            if (info.toElement === document.body || me.isBlurring(info)) {
                event = new Ext.event.Event(info.event);
                event.type = 'blur';
                event.target = info.fromElement;
                event.relatedTarget = info.toElement;
                
                me.onBlur(event);
            }
        },
        
        /**
         * @private
         */
        isFocusing: function(e) {
            var focusEl = this.getFocusEl();
        
            if (focusEl) {
                if (focusEl.isFocusing) {
                    return focusEl.isFocusing(e);
                }
                else {
                    // Sometimes focusing an element may cause reaction from other entities
                    // that will focus something else instead. So before unraveling the
                    // event chain we better make sure our focusEl is *indeed* focused.
                    return focusEl.dom === document.activeElement &&
                           e.toElement === focusEl.dom && e.fromElement !== e.toElement;
                }
            }
            
            return false;
        },
        
        /**
         * @private
         */
        isBlurring: function(e) {
            var focusEl = this.getFocusEl();
        
            if (focusEl) {
                if (focusEl.isFocusing) {
                    return focusEl.isBlurring(e);
                }
                else {
                    // Ditto for blurring: in some cases freshly blurred element can be
                    // refocused by external forces so we need to check if our focusEl
                    // *indeed* is not focused before we can call it blurring.
                    return focusEl.dom !== document.activeElement &&
                           e.fromElement === focusEl.dom && e.fromElement !== e.toElement;
                }
            }
            
            return false;
        },
        
        /**
         * @private
         */
        blur: function() {
            var me = this,
                focusEl;
            
            if (!me.focusable || !me.canFocus()) {
                return;
            }
            
            focusEl = me.getFocusEl();
 
            if (focusEl) {
                me.blurring = true;
                focusEl.blur();
                delete me.blurring;
            }
        },
        
        isTabbable: function() {
            var me = this,
                focusEl;
            
            if (me.focusable) {
                focusEl = me.getFocusEl();
                
                if (focusEl && focusEl.isTabbable()) {
                    return focusEl.isTabbable();
                }
            }
            
            return false;
        },
        
        disableTabbing: function() {
            var me = this,
                el = me.el,
                focusEl;
            
            // We DO NOT check for me.focusable here, because this should work
            // for containers with focus delegates, too!
            if (me.destroying || me.destroyed) {
                return;
            }
            
            // We're disabling tabbability for all elements of a given Component;
            // focusEl might be outside of the hierarchy which is checked below.
            if (el) {
                el.saveTabbableState();
            }
            
            focusEl = me.getFocusEl();
            
            if (focusEl) {
                // focusEl may happen to be a focus delegate for a container
                if (focusEl.$isFocusableEntity) {
                    focusEl.disableTabbing();
                }
                
                // Alternatively focusEl may happen to be outside of the main el,
                // or else it can be a string reference to an element that
                // has not been resolved yet
                else if (focusEl.isElement && el && !el.contains(focusEl)) {
                    focusEl.saveTabbableState();
                }
            }
        },
        
        enableTabbing: function(reset) {
            var me = this,
                el = me.el,
                focusEl;
            
            // We DO NOT check for me.focusable here, because this should work
            // for containers with focus delegates, too!
            if (me.destroying || me.destroyed) {
                return;
            }
            
            focusEl = me.getFocusEl();
            
            if (focusEl) {
                if (focusEl.$isFocusableEntity) {
                    focusEl.enableTabbing();
                }
                else if (focusEl.isElement && el && !el.contains(focusEl)) {
                    focusEl.restoreTabbableState();
                }
            }
            
            if (el) {
                el.restoreTabbableState({ reset: reset });
            }
        }
    }
}, function() {
    var keyboardModeCls = Ext.baseCSSPrefix + 'keyboard-mode',
        keyboardMode = false;
    
    /**
     * @cfg {Boolean} enableKeyboardMode
     * When set to `true`, focus styling will be applied to focused elements based on the
     * user interaction mode: when keyboard was used to focus an element, focus styling
     * will be visible but not when element was focused otherwise (e.g. with mouse, touch,
     * or programmatically). The {@link #keyboardMode} property will then reflect the last
     * user interaction mode.
     * Setting this option to `false` disables keyboard mode tracking and results in focus
     * styling always being applied to focused elements, which is pre-Ext JS 6.5 behavior.
     *
     * Defaults to `false` in desktop environments, `true` on mobile devices.
     * @member Ext
     * @bindable
     * @since 6.6.0
     */
    Ext.enableKeyboardMode = Ext.isModern || !Ext.os.is.Desktop;
    
    /**
     * @property {Boolean} keyboardMode
     * @member Ext
     * A flag which indicates that the last UI interaction from the user was a keyboard gesture
     * @since 6.5.0
     */
 
    /**
     * @property {Boolean} touchMode
     * @member Ext
     * A flag which indicates that the last UI interaction from the user was a touch gesture
     * @since 6.5.0
     */
 
    Ext.setKeyboardMode = Ext.setKeyboardMode || function(keyboardMode) {
        Ext.keyboardMode = keyboardMode;
        Ext.getBody().toggleCls(keyboardModeCls, keyboardMode);
    };
    
    Ext.isTouchMode = function() {
        return (Ext.now() - Ext.lastTouchTime) < 500;
    };
 
    /**
     * @private
     */
    Ext.syncKeyboardMode = function(e) {
        var type;
        
        if (!Ext.enableKeyboardMode) {
            return;
        }
        
        type = e.type;
 
        if (type === 'pointermove') {
            // On pointer move we want to track that the user has switched from keyboard
            // to mouse, but we do not remove the visual focus style until a pointerdown
            // occurs or the focus changes.  This accomodates menus which change focus
            // based on mouseenter of a menu item
            keyboardMode = false;
        }
        else {
            keyboardMode = (type === 'keydown');
            Ext.lastTouchTime = e.pointerType === 'touch' && Ext.now();
            Ext.setKeyboardMode(keyboardMode);
        }
    };
    
    function keyboardModeFocusHandler() {
        // NOT Ext.keyboardMode here; closing over variable local to class callback fn
        if (keyboardMode !== Ext.getBody().hasCls(keyboardModeCls)) {
            Ext.setKeyboardMode(keyboardMode);
        }
    }
    
    Ext.getEnableKeyboardMode = function() {
        return Ext.enableKeyboardMode;
    };
 
    Ext.setEnableKeyboardMode = function(enable) {
        var listeners = {
            pointerdown: Ext.syncKeyboardMode,
            pointermove: Ext.syncKeyboardMode,
            keydown: Ext.syncKeyboardMode,
            capture: true,
            delegated: false
        };
 
        Ext.enableKeyboardMode = !!enable;
 
        if (Ext.enableKeyboardMode) {
            Ext.getWin().on(listeners);
            Ext.on('focus', keyboardModeFocusHandler);
        }
        else {
            Ext.getWin().un(listeners);
            Ext.un('focus', keyboardModeFocusHandler);
        }
    };
 
    Ext.onReady(function() {
        // Add the CSS class once upfront if enableKeyboardMode is disabled
        if (!Ext.enableKeyboardMode) {
            Ext.getBody().addCls(keyboardModeCls);
        }
 
        Ext.setEnableKeyboardMode(Ext.enableKeyboardMode);
    });
});