Closed bjankord closed 5 years ago
This is likely a duplicate of https://github.com/cerner/terra-core/issues/1769
One of the issues I'm seeing with getting VoiceOver on iOS to read the options seems to be related to the markup used, specifically the placement of the aria attributes.
Currently, the aria-attributes are mainly placed on the wrapping div.
Moving these to the div with role="textbox"
helps to enable moving VoiceOver's virtual indicator into the list of options when the list is rendered as a sibling to the div with role="textbox"
.
I'm going to look at the markup patterns from these 3 examples and see if there is some commonality with how they markup the component.
Select
<div class="combo-wrap">
<input
type="text"
class="combobox"
id="combobox-single"
role="combobox"
aria-owns="vlY4dhDQ"
aria-autocomplete="list"
aria-expanded="false"
>
<i aria-hidden="true" class="fa trigger fa-caret-down" data-trigger="single"></i>
<div class="listbox" id="vlY4dhDQ" role="listbox">
<div class="option active" role="option" id="vd0mMU5u">1</div>
<div class="option" role="option" id="0fxbY1Pn" style="">2</div>
<div class="option" role="option" id="9MNqvGWK" style="">3</div>
<div class="option" role="option" id="acAQyCO6" style="">4</div>
</div>
</div>
Multi-Select
<div class="combo-wrap">
<input
type="text"
class="combobox"
id="combobox-multiselect"
role="combobox"
aria-owns="dX6iZUoc"
aria-autocomplete="list"
aria-expanded="false"
>
<i aria-hidden="true" class="fa trigger fa-caret-down" data-trigger="multiselect"></i>
<div class="listbox" id="dX6iZUoc" role="listbox">
<div class="option" role="option" id="vd0mMU5u">1</div>
<div class="option" role="option" id="0fxbY1Pn" style="">2</div>
<div class="option" role="option" id="9MNqvGWK" style="">3</div>
<div class="option" role="option" id="acAQyCO6" style="">4</div>
</div>
</div>
The following is added to the input when the dropdown is opened / an option is selected.
data-active-option="vd0mMU5u"
aria-activedescendant="okAhO6Lt"
It also looks like there is an aria-live region related to the select that updates when options are selected
<div
aria-live="assertive"
role="log"
aria-relevant="additions"
aria-atomic="false"
style="position: absolute; width: 1px; height: 1px; margin-top: -1px; clip: rect(1px, 1px, 1px, 1px); overflow: hidden;"></div>
<div class="combobox-container js-container" data-combobox-prefix-class="combobox-">
<span id="combobox-help-text1" class="combobox-help-text invisible">Use tabulation (or down) key to access and browse suggestions after input. Confirm your choice with enter key, or esc key to close suggestions box.</span>
<div id="combobox-suggestion-text1" class="js-suggestion-text combobox-suggestion-text invisible" aria-live="polite">
<p>There are 4 suggestions.</p>
</div>
<input
aria-autocomplete="list"
aria-describedby="combobox-help-text1"
aria-owns="combobox-suggest_1"
autocapitalize="off"
autocomplete="off"
autocorrect="off"
class="js-combobox"
data-combobox-case-sensitive="no"
data-combobox-prefix-class="combobox"
data-lastval="Kr"
data-number="1"
id="id_mysuper_field"
list="beers"
name="mysuper_field"
spellcheck="false"
type="text"
>
<button
class="js-clear-button combobox-clear-button"
aria-label="clear this field"
title="clear this field"
aria-describedby="label-id-id_mysuper_field"
type="button"
>X</button>
<div id="combobox-suggest_1" class="js-suggest combobox-suggestions">
<div role="listbox">
<div id="suggestion-1-0" class="js-suggestion combobox-suggestion" tabindex="-1" role="option">1</div>
<div id="suggestion-1-1" class="js-suggestion combobox-suggestion" tabindex="-1" role="option">2</div>
<div id="suggestion-1-2" class="js-suggestion combobox-suggestion" tabindex="-1" role="option">3</div>
<div id="suggestion-1-3" class="js-suggestion combobox-suggestion" tabindex="-1" role="option">4</div>
</div>
</div>
</div>
<div id="autocomplete-wrapper">
<div id="announce" class="visually-hidden" aria-live="assertive">
1 suggestions displayed. To navigate use up and down arrow keys.
</div>
<div id="searchfield">
<form _lpchecked="1">
<label for="search">City</label>
<input
aria-autocomplete="list"
aria-expanded="true"
aria-owns="res"
autocomplete="off"
id="search"
role="combobox"
type="text">
<div id="search-autocomplete" style="display: block;">
<div id="res" role="listbox" tabindex="-1">
<div role="option" tabindex="-1" class="autocomplete-suggestion" id="suggestion-1">1</div>
<div role="option" tabindex="-1" class="autocomplete-suggestion" id="suggestion-2">2</div>
<div role="option" tabindex="-1" class="autocomplete-suggestion" id="suggestion-3">3</div>
<div role="option" tabindex="-1" class="autocomplete-suggestion" id="suggestion-4">4</div>
</div>
</div>
<input id="submit" type="submit" value="Search">
</form>
</div>
</div>
Looking into ways to allow VoiceOver on iOS to read through the list of options, one way that will work is to move the drop down to be a sibling DOM element next to the div[role="textboxt]
element.
There are trade-offs to this though. This would require using relative positioning on the select wrapper and absolutely positioning the dropdown within the select wrapper. This can lead to the dropdown getting cut off inside of modals or triggering overflow inside of modals when the select opens. It may also be hard to make the dropdown wider than the select which is something the UX team wants to do as a future enhancement.
With the current select dropdown, it uses hookshot to position the dropdown specifically to work around the trade-offs noted above. There are some trade-offs with the hookshot approach though as well.
By rendering the dropdown DOM not as a sibling to the select div[role='textbox']
element, there is no easy way for VoiceOver users to get to the options using the flick guesture which is common to navigate with VO on iOS. The options are in the DOM and the VoiceOver user could get to them if they swipe through all of the tabbaple elements until they get to the dropdown.
At this point, I want to raise the issue with UX (cc/ @scottwilmarth @neilpfeiffer) and see if they can offer any insight on the issues.
With the option of moving the drop down to be a sibling DOM element next to the div[role="textboxt]
element which provides the neccesary DOM structure to enable usable navigation of the list options when using VoiceOver on iOS, the trade-off noted is that this will lead to the select dropdowns getting cut off inside of modals or triggering overflow inside of modals when the select opens.
overflow: hidden;
style.I've made a codepen example to explore ways to get try and work around this and see if we can get the dropdown to display outside of the modal.
While it is possible to get the dropdown to overflow beyond the modal by moving the position:relative;
style higher up in the DOM, it would require that there is no use of position:relative
throughout the DOM within the modal.
This seems like a very fragile solution. We already useposition: relative
a few times within the modal manager DOM and would need to rework that to see if this could even work.
I'm continuing to look for ways to get this to work how we would want with the dropdown going beyond the bounds of the overflow: hidden;
container but this may just be a restriction of the way these components have been constructed and combined.
We currently position the dropdown in a portal that appends the dropdown to the end of the <body/>
element. We do this to enable better postioning and avoid cases where the dropdown menu would be clipped by overflow: hidden
of ancestor elements like modals or trigger ancestor elements to increase overlfow when the dropdown is open.
As noted in previous comment, by rendering the dropdown DOM not as a sibling to the select div[role='textbox']
element, there is no easy way for VoiceOver users to get to the options using the flick guesture which is common to navigate with VO on iOS.
I looked to see if we could listen for the flick/swipe event when performed on the select element and shift focus to the portaled dropdown, however, VoiceOver seems to prevent gestures from being detected by JavaScript. I tested this using react-swipeable and was unable to detect the swipe event when VoiceOver was enabled, however I am able to detect the swipe event when VoiceOver is not enabled. There is some more info on this stack overflow post about the issue: https://stackoverflow.com/questions/34712961/javascript-capture-swipe-gesture-using-ontouchstart-with-ios-voiceover-turned-o
I've continued exploring ways to shift focus to the dropdown in the portal to enable navigating them with VoiceOver gestures. I've been exploring turning the caret icon in the select into a button. This would allow users a distinct trigger to interact with to shift focus to the dropdown.
While exploring this, I ran into some issues with VoiceOver on iOS with my first attempt at making the icon a toggle button. When a user clicks on the select (that is a variant not equal to default), we move focus to an input within the select. This triggers an onscreen keyboard to appear. Even though the onscreen keyboard is open, VoiceOver users can still flick through interactive elements on the page. So a user could flick to the toggle button, which I thought worked well for this case as then they could tap on the button and we would shift focus to the dropdown allowing them to navigate the select options.
However, there is some odd behavior with this. When opening the onscreen keyboard on iOS when focusing on an input, VoiceOver will shift focus back to the input when the keyboard is closed. This means we can't shift focus to the dropdown, while the onscreen keyboard is open. For example, when the user clicks on the select, we focus the input and this opens the onscreen keyboard. By allowing the user to flick to the toggle button, they can then click on the button. This triggers a blur event on the input which results in the onscreen keyboard closing.
So while we shift the focus to the dropdown on the toggle button click, VoiceOver has heuristics that will shift focus back the element that triggered the onscreen keyboard when the keyboard closes. This results in focus shifting to the dropdown for a brief moment and then VoiceOver taking over focus and shifting it back to the input, which prevents users from flicking/navigating through the select options.
I've updated the code that I mentioned in the previous comment and got this in a working state. To work around the VoiceOver shifting focus weirdness, I don't render the toggle button when the input is focused. By not rendering the button, it avoids the scenario where users might try to shift to the toggle button while the onscreen keyboard is open. This makes the two actions in the select distinct from one another. The users will either type in an option via the input or interact with the toggle button to shift focus to the dropdown.
On a desktop, the experience doesn't change much from what it is currently. There is a change where when you click on the caret icon, the input will not be focused. However, on mobile, by having the two ways to select an option (either by typing on in or toggling to the dropdown) be distinct, it makes it easier to use one or the other while using VoiceOver on iOS.
Bug Report
Description
When focusing on the terra-select and trying to interact with it, VoiceOver on iOS skips reading the options. It seems there is no detection that options exist when using VO.
Steps to Reproduce
Expected Behavior
terra-select should communicate and interact with the select options when using VoiceOver on iOS.
Environment