/** * @private */Ext.define('Ext.event.publisher.Focus', { extend: 'Ext.event.publisher.Dom', requires: [ 'Ext.dom.Element', 'Ext.GlobalEvents' ], type: 'focus', handledEvents: ['focusenter', 'focusleave', 'focusmove'], // At this point only Firefox does not support focusin/focusout, see this bug: // https://bugzilla.mozilla.org/show_bug.cgi?id=687787 // TODO: Fix event order: https://github.com/jquery/jquery/issues/3123 handledDomEvents: ['focusin', 'focusout'], publishDelegatedDomEvent: function(e) { var me = this, relatedTarget = e.relatedTarget; //<debug> if (me.$suppressEvents) { return; } //</debug> if (e.type === 'focusout') { // If focus is departing to the document, there will be no forthcoming focusin event // to trigger a focusleave, so fire a focusleave now. if (relatedTarget == null) { me.processFocusIn(e, e.target, document.body); } } else { // IE reports relatedTarget as either an inaccessible object which coercively // equates to null, or just a blank object in the case of focusing from nowhere. if (relatedTarget == null || !relatedTarget.tagName) { relatedTarget = document.body; } me.processFocusIn(e, relatedTarget, e.target); } }, processFocusIn: function(e, fromElement, toElement) { var me = this, commonAncestor, node, targets = [], focusFly = me.focusFly, backwards, event, focusEnterEvent; // If we have suspended focus/blur processing due to framework needing to silently manipulate // focus position, then return early. if ((fromElement && focusFly.attach(fromElement).isFocusSuspended()) || (toElement && focusFly.attach(toElement).isFocusSuspended())) { return; } if (toElement.compareDocumentPosition) { // Flag if the fromElement is DOCUMENT_POSITION_FOLLOWING toElement backwards = !!(toElement.compareDocumentPosition(fromElement) & 4); } // Gather targets for focusleave event from the fromElement to the parentNode (not inclusive) for (node = fromElement, commonAncestor = Ext.dom.Element.getCommonAncestor(toElement, fromElement, true); node && node !== commonAncestor; node = node.parentNode) { targets.push(node); } // Publish the focusleave event for the bubble hierarchy if (targets.length) { event = me.createSyntheticEvent('focusleave', e, fromElement, toElement, fromElement, toElement, backwards); me.publish(event, targets); if (event.stopped) { return; } } // Gather targets for focusenter event from the focus targetElement to the parentNode (not inclusive) targets.length = 0; for (node = toElement; node && node !== commonAncestor; node = node.parentNode) { targets.push(node); } // We always need this event; this is what we pass to the global focus event focusEnterEvent = me.createSyntheticEvent('focusenter', e, toElement, fromElement, fromElement, toElement, backwards); // Publish the focusleave event for the bubble hierarchy if (targets.length) { me.publish(focusEnterEvent, targets); if (focusEnterEvent.stopped) { return; } } // When focus moves within an element, fire a bubbling focusmove event targets = me.getPropagatingTargets(commonAncestor); // Publish the focusleave event for the bubble hierarchy if (targets.length) { event = me.createSyntheticEvent('focusmove', e, toElement, fromElement, fromElement, toElement, backwards); me.publish(event, targets); if (event.stopped) { return; } } if (Ext.GlobalEvents.hasListeners.focus) { Ext.GlobalEvents.fireEvent('focus', { event: focusEnterEvent, toElement: toElement, fromElement: fromElement, backwards: backwards }); } }, createSyntheticEvent: function(eventName, browserEvent, target, relatedTarget, fromElement, toElement, backwards) { var event = new Ext.event.Event(browserEvent); event.type = eventName; event.relatedTarget = relatedTarget; event.target = target; event.fromElement = fromElement; event.toElement = toElement; event.backwards = backwards; return event; }}, function(Focus) { var focusTimeout; Focus.prototype.focusFly = new Ext.dom.Fly(); Focus.instance = new Focus(); // At this point only Firefox does not support focusin/focusout, see this bug: // https://bugzilla.mozilla.org/show_bug.cgi?id=687787 if (!Ext.supports.FocusinFocusoutEvents) { // When focusin/focusout are not available we capture focus event instead, // and fire both focusenter *and* focusleave in the focus handler. this.override({ handledDomEvents: ['focus', 'blur'], publishDelegatedDomEvent: function(e) { var me = this, targetIsElement; me.callSuper([e]); // We need to know if event target was an element or (window || document) targetIsElement = e.target !== window && e.target !== document; // There might be an upcoming focus event, but if none happens // within a minimal timeout, then we treat this as a focus of the body if (e.type === 'blur') { if (!targetIsElement) { // Apparently when focus goes outside of the document, Firefox // will fire blur on the currently focused element, then on the document, // then on the window. Interestingly enough, both follow-up blur events // will have explicitOriginalTarget pointing at the previously focused // element; when that happens we can be reasonably sure that focus // indeed goes out the window. if (e.explicitOriginalTarget === Focus.previousActiveElement) { // But we want that to fire only once, so process window blur // which happens last. if (e.target === window) { Ext.undefer(focusTimeout); focusTimeout = 0; me.processFocusIn(e, Focus.previousActiveElement, document.body); Focus.previousActiveElement = null; } } } else { // If event target is a valid element, blur could have been caused // by removing previously focused element from the DOM, or some // other happening that doesn't involve <strike>Elvis</strike>focus // completely leaving the building. focusTimeout = Ext.defer(function() { focusTimeout = 0; me.processFocusIn(e, e.target, document.body); Focus.previousActiveElement = null; }, 1); // Store the timer in case the element gets destroyed before // the function above has a chance to fire if (targetIsElement && Ext.cache[e.target.id]) { Ext.cache[e.target.id].focusinTimeout = focusTimeout; } } Focus.previousActiveElement = targetIsElement ? e.target : null; } else { Ext.undefer(focusTimeout); focusTimeout = 0; me.processFocusIn( e, Focus.previousActiveElement || document.body, targetIsElement ? e.target : document.body ); } } }); Ext.define(null, { override: 'Ext.dom.Element', destroy: function() { if (this.focusinTimeout) { Ext.undefer(this.focusinTimeout); this.focusinTimeout = null; } this.callParent(); } }); }});