Skip to content

Commit 81c9a99

Browse files
committed
fix menu for focus/click on mobile
1 parent a6baa18 commit 81c9a99

File tree

1 file changed

+49
-8
lines changed

1 file changed

+49
-8
lines changed

web_src/js/features/aria.js

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function attachOneDropdownAria($dropdown) {
5454
$items.each((_, item) => prepareMenuItem($(item)));
5555
return $wrapper.html();
5656
};
57-
$dropdown.dropdown('setting', ['templates', dropdownTemplates]);
57+
$dropdown.dropdown('setting', 'templates', dropdownTemplates);
5858

5959
// use tooltip's content as aria-label if there is no aria-label
6060
if ($dropdown.hasClass('tooltip') && $dropdown.attr('data-content') && !$dropdown.attr('aria-label')) {
@@ -77,10 +77,11 @@ function attachOneDropdownAria($dropdown) {
7777
'aria-expanded': 'false',
7878
});
7979

80+
const isMenuVisible = () => $menu.hasClass('visible') || $menu.is('.animating.in');
81+
8082
// update aria attributes according to current active/selected item
8183
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');
8485

8586
let $active = $menu.find('> .item.active');
8687
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) {
103104
// use setTimeout to run the refreshAria in next tick (to make sure the Fomantic UI code has finished its work)
104105
// do not return any value, jQuery has return-value related behaviors.
105106
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-
111107
$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);
112153
}
113154

114155
export function attachDropdownAria($dropdowns) {

0 commit comments

Comments
 (0)