@@ -4,8 +4,47 @@ import { isRunningInJsDom } from '../utils';
4
4
/** Property name added to HTML Elements to ensure we don't double-patch focus methods on an element. */
5
5
const IS_FOCUS_PATCHED_PROP = '_patched_focus' ;
6
6
7
+ /** Ensures that a single set of matching focus and blur events occur when HTMLElement.focus() is called. */
8
+ class FocusEventWatcher implements EventListenerObject {
9
+
10
+ private readonly _active : Element | null ;
11
+ private _blurred = false ;
12
+ private _focused = false ;
13
+
14
+ constructor ( private readonly _e : HTMLElement ) {
15
+ this . _active = _e . ownerDocument . activeElement ;
16
+ this . _e . addEventListener ( 'focus' , this ) ;
17
+ this . _active ?. addEventListener ( 'blur' , this ) ;
18
+ }
19
+ public handleEvent ( evt : Event ) : void {
20
+ if ( evt . type === 'focus' ) {
21
+ this . _focused = true ;
22
+ }
23
+ else if ( evt . type === 'blur' ) {
24
+ this . _blurred = true ;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * If focus and blur events haven't occurred, fire fake ones.
30
+ */
31
+ public ensureFocusEvents ( ) {
32
+ this . _e . removeEventListener ( 'focus' , this ) ;
33
+ this . _active ?. removeEventListener ( 'blur' , this ) ;
34
+
35
+ // Ensure activeElement is blurred
36
+ if ( this . _active && ! this . _blurred && this . _active === this . _e . ownerDocument . activeElement ) {
37
+ dispatchFakeEvent ( this . _active , 'blur' ) ;
38
+ }
39
+
40
+ if ( ! this . _focused ) {
41
+ dispatchFakeEvent ( this . _e , 'focus' ) ; // Needed to cause focus event
42
+ }
43
+ }
44
+ }
45
+
7
46
/**
8
- * Patches an elements focus and blur methods to emit events consistently and predictably.
47
+ * Patches an element's focus and blur methods to emit events consistently and predictably in tests .
9
48
* This is necessary, because some browsers, like IE11, will call the focus handlers asynchronously,
10
49
* while others won't fire them at all if the browser window is not focused.
11
50
*
@@ -17,15 +56,12 @@ export function patchElementFocus(element: HTMLElement): void {
17
56
if ( ! isRunningInJsDom ( ) && ( element [ IS_FOCUS_PATCHED_PROP ] === undefined ) ) {
18
57
const baseFocus = element . focus . bind ( element ) ;
19
58
element . focus = ( options ) => {
20
- // Blur current active
21
- const active = element . ownerDocument . activeElement ;
22
- if ( active ) {
23
- dispatchFakeEvent ( active , 'blur' ) ;
24
- }
25
-
26
- // Focus
27
- baseFocus ( options ) ; // Needed to set document.activeElement
28
- dispatchFakeEvent ( element , 'focus' ) ; // Needed to cause focus event
59
+ const w = new FocusEventWatcher ( element ) ;
60
+
61
+ // Sets document.activeElement. May or may not send focus + blur events
62
+ baseFocus ( options ) ;
63
+
64
+ w . ensureFocusEvents ( ) ;
29
65
}
30
66
element . blur = ( ) => dispatchFakeEvent ( element , 'blur' ) ;
31
67
element [ IS_FOCUS_PATCHED_PROP ] = true ;
0 commit comments