11const ION_FOCUSED = 'ion-focused' ;
22const 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+ ] ;
317
418export interface FocusVisibleUtility {
519 destroy : ( ) => void ;
@@ -8,6 +22,8 @@ export interface FocusVisibleUtility {
822
923export const startFocusVisible = ( rootEl ?: HTMLElement ) : FocusVisibleUtility => {
1024 let currentFocus : Element [ ] = [ ] ;
25+ let keyboardMode = false ;
26+ let lastPointerType : string | null = null ;
1127
1228 const ref = rootEl ? rootEl . shadowRoot ! : document ;
1329 const root = rootEl ? rootEl : document . body ;
@@ -18,16 +34,40 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
1834 currentFocus = elements ;
1935 } ;
2036
21- const pointerDown = ( ) => {
37+ const pointerDown = ( ev : Event ) => {
38+ const pointerEvent = ev as PointerEvent ;
39+ lastPointerType = pointerEvent . pointerType ;
40+ keyboardMode = false ;
2241 setFocus ( [ ] ) ;
2342 } ;
2443
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+
2556 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 ;
2961
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+ }
3171 } ;
3272
3373 const onFocusout = ( ) => {
@@ -36,14 +76,18 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
3676 }
3777 } ;
3878
79+ ref . addEventListener ( 'keydown' , onKeydown ) ;
3980 ref . addEventListener ( 'focusin' , onFocusin ) ;
4081 ref . addEventListener ( 'focusout' , onFocusout ) ;
82+ ref . addEventListener ( 'pointerdown' , pointerDown , { passive : true } ) ;
4183 ref . addEventListener ( 'touchstart' , pointerDown , { passive : true } ) ;
4284 ref . addEventListener ( 'mousedown' , pointerDown ) ;
4385
4486 const destroy = ( ) => {
87+ ref . removeEventListener ( 'keydown' , onKeydown ) ;
4588 ref . removeEventListener ( 'focusin' , onFocusin ) ;
4689 ref . removeEventListener ( 'focusout' , onFocusout ) ;
90+ ref . removeEventListener ( 'pointerdown' , pointerDown ) ;
4791 ref . removeEventListener ( 'touchstart' , pointerDown ) ;
4892 ref . removeEventListener ( 'mousedown' , pointerDown ) ;
4993 } ;
0 commit comments