WebKit Bugzilla
Attachment 341218 Details for
Bug 185957
: Listbox label is not read with Voiceover + Safari
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
Listbox example
listbox-follows-focus-scrollable.html (text/html), 5.01 KB, created by
Katie Hockman
on 2018-05-24 13:02:19 PDT
(
hide
)
Description:
Listbox example
Filename:
MIME Type:
Creator:
Katie Hockman
Created:
2018-05-24 13:02:19 PDT
Size:
5.01 KB
patch
obsolete
><!DOCTYPE html> ><html lang="en"> > <head> > <title> > Listbox aria-activedescendant with scrolling Blueprint > </title> > <style> > .focused { > background-color: lightgrey; > } > > [role='listbox'] { > max-height: 9em; > width: 250px; > overflow-y: auto; > position: relative; > border: black thin solid; > list-style-type: none; > margin: 0; > padding: 0; > } > > [role='option'] { > border-radius: 0; > font-size: 16px; > text-align: left; > padding: 5px 10px; > position: relative; > } > </style> > <script> > /** @type {!Element} The listbox. */ > let listboxEl; > > /** @type {!NodeList} The options in the listbox. */ > let options; > > /** @type {?number} The index of the option that's selected. */ > let selectedIndex = null; > > function init() { > listboxEl = document.getElementById('fate-listbox'); > options = listboxEl.querySelectorAll('[role=option]'); > setEventListeners(); > } > > function setEventListeners() { > Array.prototype.forEach.call(options, function (option) { > option.addEventListener('mouseup', handleOptionMouseUp, false); > }); > listboxEl.addEventListener('keydown', handleListboxKeyDown, false); > listboxEl.addEventListener('focus', handleListboxFocus, false); > } > > /** > * Focuses on the selected option, if there is one, otherwise on the > * first option. > */ > function handleListboxFocus() { > selectOption(selectedIndex || 0); > } > > /** > * Handles mouse up events by selecting the option. > * @param {!Event} e > */ > function handleOptionMouseUp(e) { > selectOption(Array.prototype.indexOf.call(options, e.target)); > listboxEl.focus(); > } > > /** > * Handles keydown events on the listbox by managing focus via active- > * descendant and selecting options based on user input. > * @param {!Event} e > */ > function handleListboxKeyDown(e) { > switch (e.key) { > case 'ArrowUp': > selectPreviousOption(); > break; > > case 'ArrowDown': > selectNextOption(); > break; > > case 'Home': > selectOption(0); > break; > > case 'End': > selectOption(options.length - 1); > break; > > default: > // Return early if nothing done, to avoid canceling the event. > return; > } > e.preventDefault(); > e.stopPropagation(); > } > > /** Selects the next option, if available. */ > function selectNextOption() { > selectOption(Math.min(selectedIndex + 1, options.length - 1)); > } > > /** Selects the previous option, if available. */ > function selectPreviousOption() { > selectOption(Math.max(selectedIndex - 1, 0)); > } > > /** > * Scrolls the given option into view if it is not already visible. > * @param {!Element} option > */ > function scrollIntoView(option) { > const optionTop = option.offsetTop; > const optionBottom = optionTop + option.clientHeight; > if (option.offsetTop < listboxEl.scrollTop) { > listboxEl.scrollTop = option.offsetTop; > } else if ( > optionBottom > listboxEl.scrollTop + listboxEl.clientHeight) { > listboxEl.scrollTop = optionBottom - listboxEl.clientHeight; > } > } > > /** > * Sets the option at the given index as the active descendant of the > * listbox, and sets the aria-selected state of the option. > * @param {number} index > */ > function selectOption(index) { > if (options[selectedIndex]) { > options[selectedIndex].classList.remove('focused'); > options[selectedIndex].setAttribute('aria-selected', 'false'); > } > listboxEl.setAttribute('aria-activedescendant', options[index].id); > options[index].setAttribute('aria-selected', 'true'); > options[index].classList.add('focused'); > scrollIntoView(options[index]); > selectedIndex = index; > } > > window.addEventListener('load', init, false); > </script> > </head> > <body> > <div id="prompt">Choose your fate</div> > <ul role="listbox" id="fate-listbox" aria-labelledby="prompt" > tabindex="0"> > <li id="lucky" role="option" aria-selected="false">Lucky</li> > <li id="rich" role="option" aria-selected="false">Rich</li> > <li id="smart" role="option" aria-selected="false">Smart</li> > <li id="fortunate" role="option" aria-selected="false">Fortunate</li> > <li id="affluent" role="option" aria-selected="false">Affluent</li> > <li id="intelligent" role="option" aria-selected="false">Intelligent</li> > <li id="fortuitous" role="option" aria-selected="false">Fortuitous</li> > <li id="prosperous" role="option" aria-selected="false">Prosperous</li> > <li id="shrewd" role="option" aria-selected="false">Shrewd</li> > </ul> > </body> ></html>
<!DOCTYPE html> <html lang="en"> <head> <title> Listbox aria-activedescendant with scrolling Blueprint </title> <style> .focused { background-color: lightgrey; } [role='listbox'] { max-height: 9em; width: 250px; overflow-y: auto; position: relative; border: black thin solid; list-style-type: none; margin: 0; padding: 0; } [role='option'] { border-radius: 0; font-size: 16px; text-align: left; padding: 5px 10px; position: relative; } </style> <script> /** @type {!Element} The listbox. */ let listboxEl; /** @type {!NodeList} The options in the listbox. */ let options; /** @type {?number} The index of the option that's selected. */ let selectedIndex = null; function init() { listboxEl = document.getElementById('fate-listbox'); options = listboxEl.querySelectorAll('[role=option]'); setEventListeners(); } function setEventListeners() { Array.prototype.forEach.call(options, function (option) { option.addEventListener('mouseup', handleOptionMouseUp, false); }); listboxEl.addEventListener('keydown', handleListboxKeyDown, false); listboxEl.addEventListener('focus', handleListboxFocus, false); } /** * Focuses on the selected option, if there is one, otherwise on the * first option. */ function handleListboxFocus() { selectOption(selectedIndex || 0); } /** * Handles mouse up events by selecting the option. * @param {!Event} e */ function handleOptionMouseUp(e) { selectOption(Array.prototype.indexOf.call(options, e.target)); listboxEl.focus(); } /** * Handles keydown events on the listbox by managing focus via active- * descendant and selecting options based on user input. * @param {!Event} e */ function handleListboxKeyDown(e) { switch (e.key) { case 'ArrowUp': selectPreviousOption(); break; case 'ArrowDown': selectNextOption(); break; case 'Home': selectOption(0); break; case 'End': selectOption(options.length - 1); break; default: // Return early if nothing done, to avoid canceling the event. return; } e.preventDefault(); e.stopPropagation(); } /** Selects the next option, if available. */ function selectNextOption() { selectOption(Math.min(selectedIndex + 1, options.length - 1)); } /** Selects the previous option, if available. */ function selectPreviousOption() { selectOption(Math.max(selectedIndex - 1, 0)); } /** * Scrolls the given option into view if it is not already visible. * @param {!Element} option */ function scrollIntoView(option) { const optionTop = option.offsetTop; const optionBottom = optionTop + option.clientHeight; if (option.offsetTop < listboxEl.scrollTop) { listboxEl.scrollTop = option.offsetTop; } else if ( optionBottom > listboxEl.scrollTop + listboxEl.clientHeight) { listboxEl.scrollTop = optionBottom - listboxEl.clientHeight; } } /** * Sets the option at the given index as the active descendant of the * listbox, and sets the aria-selected state of the option. * @param {number} index */ function selectOption(index) { if (options[selectedIndex]) { options[selectedIndex].classList.remove('focused'); options[selectedIndex].setAttribute('aria-selected', 'false'); } listboxEl.setAttribute('aria-activedescendant', options[index].id); options[index].setAttribute('aria-selected', 'true'); options[index].classList.add('focused'); scrollIntoView(options[index]); selectedIndex = index; } window.addEventListener('load', init, false); </script> </head> <body> <div id="prompt">Choose your fate</div> <ul role="listbox" id="fate-listbox" aria-labelledby="prompt" tabindex="0"> <li id="lucky" role="option" aria-selected="false">Lucky</li> <li id="rich" role="option" aria-selected="false">Rich</li> <li id="smart" role="option" aria-selected="false">Smart</li> <li id="fortunate" role="option" aria-selected="false">Fortunate</li> <li id="affluent" role="option" aria-selected="false">Affluent</li> <li id="intelligent" role="option" aria-selected="false">Intelligent</li> <li id="fortuitous" role="option" aria-selected="false">Fortuitous</li> <li id="prosperous" role="option" aria-selected="false">Prosperous</li> <li id="shrewd" role="option" aria-selected="false">Shrewd</li> </ul> </body> </html>
View Attachment As Raw
Actions:
View
Attachments on
bug 185957
: 341218