1
1
const ION_FOCUSED = 'ion-focused' ;
2
2
const ION_FOCUSABLE = 'ion-focusable' ;
3
+ const FOCUS_KEYS = [
4
+ 'Tab' ,
5
+ 'ArrowDown' ,
6
+ 'Space' ,
7
+ 'Escape' ,
8
+ ' ' ,
9
+ 'Shift' ,
10
+ 'Enter' ,
11
+ 'ArrowLeft' ,
12
+ 'ArrowRight' ,
13
+ 'ArrowUp' ,
14
+ 'Home' ,
15
+ 'End' ,
16
+ ] ;
3
17
4
18
export interface FocusVisibleUtility {
5
19
destroy : ( ) => void ;
@@ -8,6 +22,8 @@ export interface FocusVisibleUtility {
8
22
9
23
export const startFocusVisible = ( rootEl ?: HTMLElement ) : FocusVisibleUtility => {
10
24
let currentFocus : Element [ ] = [ ] ;
25
+ let keyboardMode = false ;
26
+ let lastPointerType : string | null = null ;
11
27
12
28
const ref = rootEl ? rootEl . shadowRoot ! : document ;
13
29
const root = rootEl ? rootEl : document . body ;
@@ -18,16 +34,40 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
18
34
currentFocus = elements ;
19
35
} ;
20
36
21
- const pointerDown = ( ) => {
37
+ const pointerDown = ( ev : Event ) => {
38
+ const pointerEvent = ev as PointerEvent ;
39
+ lastPointerType = pointerEvent . pointerType ;
40
+ keyboardMode = false ;
22
41
setFocus ( [ ] ) ;
23
42
} ;
24
43
44
+ const onKeydown = ( ev : Event ) => {
45
+ const keyboardEvent = ev as KeyboardEvent ;
46
+ // Always set keyboard mode to true when any key is pressed
47
+ // This handles the WebKit Tab key bug where keydown might not fire
48
+ keyboardMode = true ;
49
+
50
+ // If it's not a focus key, clear focus immediately
51
+ if ( ! FOCUS_KEYS . includes ( keyboardEvent . key ) ) {
52
+ setFocus ( [ ] ) ;
53
+ }
54
+ } ;
55
+
25
56
const onFocusin = ( ev : Event ) => {
26
- const toFocus = ev . composedPath ( ) . filter ( ( el ) : el is HTMLElement => {
27
- return el instanceof HTMLElement && el . classList . contains ( ION_FOCUSABLE ) ;
28
- } ) ;
57
+ // Check if this focus event is likely from keyboard navigation
58
+ // We can detect this by checking if there was a recent keydown event
59
+ // or if the focus target is a focusable element that typically gets focus via keyboard
60
+ const target = ev . target as HTMLElement ;
29
61
30
- setFocus ( toFocus ) ;
62
+ if ( target . classList . contains ( ION_FOCUSABLE ) ) {
63
+ // If we're in keyboard mode or this looks like keyboard navigation
64
+ if ( keyboardMode || ! lastPointerType ) {
65
+ const toFocus = ev . composedPath ( ) . filter ( ( el ) : el is HTMLElement => {
66
+ return el instanceof HTMLElement && el . classList . contains ( ION_FOCUSABLE ) ;
67
+ } ) ;
68
+ setFocus ( toFocus ) ;
69
+ }
70
+ }
31
71
} ;
32
72
33
73
const onFocusout = ( ) => {
@@ -36,14 +76,18 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
36
76
}
37
77
} ;
38
78
79
+ ref . addEventListener ( 'keydown' , onKeydown ) ;
39
80
ref . addEventListener ( 'focusin' , onFocusin ) ;
40
81
ref . addEventListener ( 'focusout' , onFocusout ) ;
82
+ ref . addEventListener ( 'pointerdown' , pointerDown , { passive : true } ) ;
41
83
ref . addEventListener ( 'touchstart' , pointerDown , { passive : true } ) ;
42
84
ref . addEventListener ( 'mousedown' , pointerDown ) ;
43
85
44
86
const destroy = ( ) => {
87
+ ref . removeEventListener ( 'keydown' , onKeydown ) ;
45
88
ref . removeEventListener ( 'focusin' , onFocusin ) ;
46
89
ref . removeEventListener ( 'focusout' , onFocusout ) ;
90
+ ref . removeEventListener ( 'pointerdown' , pointerDown ) ;
47
91
ref . removeEventListener ( 'touchstart' , pointerDown ) ;
48
92
ref . removeEventListener ( 'mousedown' , pointerDown ) ;
49
93
} ;
0 commit comments