@@ -54,7 +54,7 @@ function attachOneDropdownAria($dropdown) {
54
54
$items . each ( ( _ , item ) => prepareMenuItem ( $ ( item ) ) ) ;
55
55
return $wrapper . html ( ) ;
56
56
} ;
57
- $dropdown . dropdown ( 'setting' , [ 'templates' , dropdownTemplates ] ) ;
57
+ $dropdown . dropdown ( 'setting' , 'templates' , dropdownTemplates ) ;
58
58
59
59
// use tooltip's content as aria-label if there is no aria-label
60
60
if ( $dropdown . hasClass ( 'tooltip' ) && $dropdown . attr ( 'data-content' ) && ! $dropdown . attr ( 'aria-label' ) ) {
@@ -77,10 +77,11 @@ function attachOneDropdownAria($dropdown) {
77
77
'aria-expanded' : 'false' ,
78
78
} ) ;
79
79
80
+ const isMenuVisible = ( ) => $menu . hasClass ( 'visible' ) || $menu . is ( '.animating.in' ) ;
81
+
80
82
// update aria attributes according to current active/selected item
81
83
const refreshAria = ( ) => {
82
- const isMenuVisible = ! $menu . is ( '.hidden' ) && ! $menu . is ( '.animating.out' ) ;
83
- $focusable . attr ( 'aria-expanded' , isMenuVisible ? 'true' : 'false' ) ;
84
+ $focusable . attr ( 'aria-expanded' , isMenuVisible ( ) ? 'true' : 'false' ) ;
84
85
85
86
let $active = $menu . find ( '> .item.active' ) ;
86
87
if ( ! $active . length ) $active = $menu . find ( '> .item.selected' ) ; // it's strange that we need this fallback at the moment
@@ -103,12 +104,52 @@ function attachOneDropdownAria($dropdown) {
103
104
// use setTimeout to run the refreshAria in next tick (to make sure the Fomantic UI code has finished its work)
104
105
// do not return any value, jQuery has return-value related behaviors.
105
106
const deferredRefreshAria = ( ) => { setTimeout ( refreshAria , 0 ) } ;
106
- $focusable . on ( 'focus' , deferredRefreshAria ) ;
107
- $focusable . on ( 'mouseup' , deferredRefreshAria ) ;
108
- // Fomantic may stop propagation of blur event, use capture to make sure we can still get the event
109
- $focusable [ 0 ] . addEventListener ( 'blur' , deferredRefreshAria , true ) ;
110
-
111
107
$dropdown . on ( 'keyup' , ( e ) => { if ( e . key . startsWith ( 'Arrow' ) ) deferredRefreshAria ( ) ; } ) ;
108
+
109
+ // if the dropdown has been opened by focus, do not trigger the next click event again.
110
+ // otherwise the dropdown will be closed immediately, especially on Android with TalkBack
111
+ // * desktop event sequence: mousedown -> focus -> mouseup -> click
112
+ // * mobile event sequence: focus -> mousedown -> mouseup -> click
113
+ // Fomantic may stop propagation of blur event, use capture to make sure we can still get the event
114
+ // keep the debug code for developers who want to confirm&debug this code for different browsers (without attaching a remote debugger)
115
+ const showDebug = false ;
116
+ const debug = ( msg ) => showDebug && $ ( '.page-content' ) . append ( $ ( '<div>' ) . text ( `${ $menu . attr ( 'id' ) } ${ msg } , menu visible=${ isMenuVisible ( ) } ` ) ) ;
117
+ let ignoreClickPreEvents = 0 , ignoreClickPreVisible = 0 ;
118
+ $dropdown [ 0 ] . addEventListener ( 'mousedown' , ( e ) => {
119
+ debug ( e . type ) ;
120
+ ignoreClickPreVisible += isMenuVisible ( ) ? 1 : 0 ;
121
+ ignoreClickPreEvents ++ ;
122
+ } , true ) ;
123
+ $dropdown [ 0 ] . addEventListener ( 'focus' , ( e ) => {
124
+ debug ( e . type ) ;
125
+ ignoreClickPreVisible += isMenuVisible ( ) ? 1 : 0 ;
126
+ ignoreClickPreEvents ++ ;
127
+ deferredRefreshAria ( ) ;
128
+ } , true ) ;
129
+ $dropdown [ 0 ] . addEventListener ( 'blur' , ( e ) => {
130
+ debug ( e . type ) ;
131
+ ignoreClickPreVisible = ignoreClickPreEvents = 0 ;
132
+ deferredRefreshAria ( ) ;
133
+ } , true ) ;
134
+ $dropdown [ 0 ] . addEventListener ( 'mouseup' , ( e ) => {
135
+ debug ( e . type ) ;
136
+ setTimeout ( ( ) => {
137
+ debug ( `${ e . type } (deferred)` ) ;
138
+ ignoreClickPreVisible = ignoreClickPreEvents = 0 ;
139
+ deferredRefreshAria ( ) ;
140
+ } , 0 ) ;
141
+ } , true ) ;
142
+ $dropdown [ 0 ] . addEventListener ( 'click' , ( e ) => {
143
+ debug ( `${ e . type } , pre-visible=${ ignoreClickPreVisible } , pre-events=${ ignoreClickPreEvents } ` ) ;
144
+ if ( isMenuVisible ( ) &&
145
+ ignoreClickPreVisible !== 2 && // dropdown is switch from invisible to visible
146
+ ignoreClickPreEvents === 2 // the click event is related to mousedown+focus
147
+ ) {
148
+ debug ( `${ e . type } , stop click propagation` ) ;
149
+ e . stopPropagation ( ) ; // if the dropdown menu has been opened by focus, do not trigger the next click event again
150
+ }
151
+ ignoreClickPreEvents = ignoreClickPreVisible = 0 ;
152
+ } , true ) ;
112
153
}
113
154
114
155
export function attachDropdownAria ( $dropdowns ) {
0 commit comments