-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Remove customized (unmaintained) dropdown, improve aria a11y for drop…
…down
- Loading branch information
1 parent
0e51694
commit 0da4f05
Showing
7 changed files
with
137 additions
and
4,448 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import $ from 'jquery'; | ||
|
||
let ariaIdCounter = 0; | ||
|
||
function generateAriaId() { | ||
return `_aria_auto_id_${ariaIdCounter++}`; | ||
} | ||
|
||
// make the item has role=option, and add an id if there wasn't one yet. | ||
function prepareMenuItem($item) { | ||
$item.attr({'role': 'option'}); | ||
if (!$item.attr('id')) $item.attr('id', generateAriaId()); | ||
} | ||
|
||
// when the menu items are loaded from AJAX requests, the items are created dynamically | ||
const defaultCreateDynamicMenu = $.fn.dropdown.settings.templates.menu; | ||
$.fn.dropdown.settings.templates.menu = function(response, fields, preserveHTML, className) { | ||
const ret = defaultCreateDynamicMenu(response, fields, preserveHTML, className); | ||
const $wrapper = $('<div>').append(ret); | ||
const $items = $wrapper.find('> .item'); | ||
$items.each((_, item) => { | ||
prepareMenuItem($(item)); | ||
}); | ||
return $wrapper.html(); | ||
}; | ||
|
||
function attachOneDropdownAria($dropdown) { | ||
const $textSearch = $dropdown.find('input.search').eq(0); | ||
const $focusable = $textSearch.length ? $textSearch : $dropdown; // see comment below | ||
if (!$focusable.length) return; | ||
|
||
// prepare menu list | ||
const $menu = $dropdown.find('> .menu'); | ||
if (!$menu.attr('id')) $menu.attr('id', generateAriaId()); | ||
$menu.attr('role', 'listbox'); | ||
|
||
// dropdown has 2 different focusing behaviors | ||
// * with search input: the input is focused, and it works perfectly with aria-activedescendant pointing another sibling element. | ||
// * without search input (but the readonly text), the dropdown itself is focused. then the aria-activedescendant points to the element inside dropdown, | ||
// which make the UI flicking when navigating between list options, that's the best effect at the moment. | ||
|
||
$focusable.attr({ | ||
'role': 'combobox', | ||
'aria-controls': $menu.attr('id'), | ||
'aria-expanded': 'false', | ||
}); | ||
|
||
$menu.find('> .item').each((_, item) => { | ||
prepareMenuItem($(item)); | ||
}); | ||
|
||
// update aria attributes according current active/selected item | ||
const refreshAria = () => { | ||
const isMenuVisible = !$menu.is('.hidden') && !$menu.is('.animating.out'); | ||
$focusable.attr('aria-expanded', isMenuVisible ? 'true' : 'false'); | ||
|
||
let $active = $menu.find('> .item.active'); | ||
if (!$active.length) $active = $menu.find('> .item.selected'); // it's strange that we need this fallback at the moment | ||
|
||
// if there is an active item, use its id. if no active item, then the empty string is set | ||
$focusable.attr('aria-activedescendant', $active.attr('id')); | ||
}; | ||
|
||
// use setTimeout to run the refreshAria in next tick | ||
$focusable.on('focus', () => { | ||
setTimeout(refreshAria, 0); | ||
}); | ||
$focusable.on('mouseup', () => { | ||
setTimeout(refreshAria, 0); | ||
}); | ||
$focusable.on('blur', () => { | ||
setTimeout(refreshAria, 0); | ||
}); | ||
$dropdown.on('keyup', (e) => { | ||
const key = e.key; | ||
if (key === 'Tab' || key === 'Space' || key === 'Enter' || key.startsWith('Arrow')) { | ||
setTimeout(refreshAria, 0); | ||
} | ||
}); | ||
} | ||
|
||
export function attachDropdownAria($dropdowns) { | ||
$dropdowns.each((_, e) => attachOneDropdownAria($(e))); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
**This document is used as aria/a11y reference for future developers** | ||
|
||
ARIA Dropdown: | ||
|
||
```html | ||
<div> | ||
<input role="combobox" aria-haspopup="listbox" aria-expanded="false" aria-controls="the-menu-listbox" aria-activedescendant="item-id-123456"> | ||
<ul id="the-menu-listbox" role="listbox"> | ||
<li role="option" id="item-id-123456" aria-selected="true"> | ||
<a tabindex="-1" href="....">....</a> | ||
</li> | ||
</ul> | ||
</div> | ||
``` | ||
|
||
|
||
Fomantic UI Dropdown: | ||
|
||
```html | ||
<!-- read-only dropdown --> | ||
<div class="ui dropdown"> <!-- focused here, then it's not perfect to use aria-activedescendant to point to the menu item --> | ||
<input type="hidden" ...> | ||
<div class="text">Default</div> | ||
<div class="menu transition hidden" tabindex="-1"> | ||
<div class="item active selected">Default</div> | ||
<div class="item">...</div> | ||
</div> | ||
</div> | ||
|
||
<!-- search input dropdown --> | ||
<div class="ui dropdown"> | ||
<input type="hidden" ...> | ||
<input class="search" autocomplete="off" tabindex="0"> <!-- focused here --> | ||
<div class="text"></div> | ||
<div class="menu transition visible" tabindex="-1"> | ||
<div class="item selected">...</div> | ||
<div class="item">...</div> | ||
</div> | ||
</div> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.