@@ -22,51 +22,49 @@ export interface FocusVisibleUtility {
22
22
23
23
export const startFocusVisible = ( rootEl ?: HTMLElement ) : FocusVisibleUtility => {
24
24
let currentFocus : Element [ ] = [ ] ;
25
- let keyboardMode = false ;
26
- let lastPointerType : string | null = null ;
25
+ // Tracks if the last interaction was a pointer event (mouse, touch, pen)
26
+ // Used to distinguish between pointer and keyboard navigation for focus styling
27
+ let hadPointerEvent = false ;
27
28
28
29
const ref = rootEl ? rootEl . shadowRoot ! : document ;
29
30
const root = rootEl ? rootEl : document . body ;
30
31
32
+ // Adds or removes the focused class for styling
31
33
const setFocus = ( elements : Element [ ] ) => {
32
34
currentFocus . forEach ( ( el ) => el . classList . remove ( ION_FOCUSED ) ) ;
33
35
elements . forEach ( ( el ) => el . classList . add ( ION_FOCUSED ) ) ;
34
36
currentFocus = elements ;
35
37
} ;
36
38
39
+ // Do not set focus on pointer interactions
37
40
const pointerDown = ( ev : Event ) => {
38
- const pointerEvent = ev as PointerEvent ;
39
- lastPointerType = pointerEvent . pointerType ;
40
- keyboardMode = false ;
41
- setFocus ( [ ] ) ;
41
+ if ( ev instanceof PointerEvent && ev . pointerType !== '' ) {
42
+ hadPointerEvent = true ;
43
+ // Reset after the event loop so only the immediate focusin is suppressed
44
+ setTimeout ( ( ) => { hadPointerEvent = false ; } , 0 ) ;
45
+ }
42
46
} ;
43
47
48
+ // Clear hadPointerEvent so keyboard navigation shows focus
49
+ // Also, clear focus if the key is not a navigation key
44
50
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 ;
51
+ hadPointerEvent = false ;
49
52
50
- // If it's not a focus key, clear focus immediately
53
+ const keyboardEvent = ev as KeyboardEvent ;
51
54
if ( ! FOCUS_KEYS . includes ( keyboardEvent . key ) ) {
52
55
setFocus ( [ ] ) ;
53
56
}
54
57
} ;
55
58
59
+ // Set focus if the last interaction was NOT a pointer event
60
+ // This works around iOS/Safari bugs where keydown is not fired for Tab
56
61
const onFocusin = ( ev : Event ) => {
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
62
const target = ev . target as HTMLElement ;
61
-
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
- }
63
+ if ( target . classList . contains ( ION_FOCUSABLE ) && ! hadPointerEvent ) {
64
+ const toFocus = ev . composedPath ( ) . filter ( ( el ) : el is HTMLElement =>
65
+ el instanceof HTMLElement && el . classList . contains ( ION_FOCUSABLE )
66
+ ) ;
67
+ setFocus ( toFocus ) ;
70
68
}
71
69
} ;
72
70
@@ -80,16 +78,12 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
80
78
ref . addEventListener ( 'focusin' , onFocusin ) ;
81
79
ref . addEventListener ( 'focusout' , onFocusout ) ;
82
80
ref . addEventListener ( 'pointerdown' , pointerDown , { passive : true } ) ;
83
- ref . addEventListener ( 'touchstart' , pointerDown , { passive : true } ) ;
84
- ref . addEventListener ( 'mousedown' , pointerDown ) ;
85
81
86
82
const destroy = ( ) => {
87
83
ref . removeEventListener ( 'keydown' , onKeydown ) ;
88
84
ref . removeEventListener ( 'focusin' , onFocusin ) ;
89
85
ref . removeEventListener ( 'focusout' , onFocusout ) ;
90
86
ref . removeEventListener ( 'pointerdown' , pointerDown ) ;
91
- ref . removeEventListener ( 'touchstart' , pointerDown ) ;
92
- ref . removeEventListener ( 'mousedown' , pointerDown ) ;
93
87
} ;
94
88
95
89
return {
0 commit comments