@@ -22,51 +22,49 @@ export interface FocusVisibleUtility {
2222
2323export const startFocusVisible = ( rootEl ?: HTMLElement ) : FocusVisibleUtility => {
2424 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 ;
2728
2829 const ref = rootEl ? rootEl . shadowRoot ! : document ;
2930 const root = rootEl ? rootEl : document . body ;
3031
32+ // Adds or removes the focused class for styling
3133 const setFocus = ( elements : Element [ ] ) => {
3234 currentFocus . forEach ( ( el ) => el . classList . remove ( ION_FOCUSED ) ) ;
3335 elements . forEach ( ( el ) => el . classList . add ( ION_FOCUSED ) ) ;
3436 currentFocus = elements ;
3537 } ;
3638
39+ // Do not set focus on pointer interactions
3740 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+ }
4246 } ;
4347
48+ // Clear hadPointerEvent so keyboard navigation shows focus
49+ // Also, clear focus if the key is not a navigation key
4450 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 ;
4952
50- // If it's not a focus key, clear focus immediately
53+ const keyboardEvent = ev as KeyboardEvent ;
5154 if ( ! FOCUS_KEYS . includes ( keyboardEvent . key ) ) {
5255 setFocus ( [ ] ) ;
5356 }
5457 } ;
5558
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
5661 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
6062 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 ) ;
7068 }
7169 } ;
7270
@@ -80,16 +78,12 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
8078 ref . addEventListener ( 'focusin' , onFocusin ) ;
8179 ref . addEventListener ( 'focusout' , onFocusout ) ;
8280 ref . addEventListener ( 'pointerdown' , pointerDown , { passive : true } ) ;
83- ref . addEventListener ( 'touchstart' , pointerDown , { passive : true } ) ;
84- ref . addEventListener ( 'mousedown' , pointerDown ) ;
8581
8682 const destroy = ( ) => {
8783 ref . removeEventListener ( 'keydown' , onKeydown ) ;
8884 ref . removeEventListener ( 'focusin' , onFocusin ) ;
8985 ref . removeEventListener ( 'focusout' , onFocusout ) ;
9086 ref . removeEventListener ( 'pointerdown' , pointerDown ) ;
91- ref . removeEventListener ( 'touchstart' , pointerDown ) ;
92- ref . removeEventListener ( 'mousedown' , pointerDown ) ;
9387 } ;
9488
9589 return {
0 commit comments