fix: make autosuggestion accessible (#1183)

* fix: make autosuggestion accessible

fixes #129

* remove tabindexes, fix aria-hidden
This commit is contained in:
Nolan Lawson 2019-05-05 22:08:54 -07:00 committed by GitHub
parent 78715bc098
commit 8d0db2c97c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 89 additions and 20 deletions

View File

@ -1,11 +1,16 @@
<div class="compose-autosuggest {shown ? 'shown' : ''} {realm === 'dialog' ? 'is-dialog' : ''}"
aria-hidden="true" >
aria-hidden={!shown}
>
<ComposeAutosuggestionList
items={autosuggestSearchResults}
on:click="onClick(event)"
type={autosuggestType}
selected={autosuggestSelected}
{realm}
/>
<div class="sr-only" aria-live="assertive">
{assertiveAriaText}
</div>
</div>
<style>
.compose-autosuggest {
@ -44,6 +49,7 @@
import { selectAutosuggestItem } from '../../_actions/autosuggest'
import { observe } from 'svelte-extras'
import { once } from '../../_utils/once'
import { createAutosuggestAccessibleLabel } from '../../_utils/createAutosuggestAccessibleLabel'
export default {
oncreate () {
@ -94,7 +100,19 @@
/* eslint-enable camelcase */
shouldBeShown: ({ realm, $autosuggestShown, composeFocused }) => (
!!($autosuggestShown && composeFocused)
)
),
// text that is read to screen readers. based on https://haltersweb.github.io/Accessibility/autocomplete.html
assertiveAriaText: ({ shouldBeShown,
autosuggestSearchResults,
autosuggestSelected,
autosuggestType,
$omitEmojiInDisplayNames }) => {
if (!shouldBeShown || !autosuggestSearchResults || !autosuggestSearchResults.length) {
return ''
}
return createAutosuggestAccessibleLabel(autosuggestType, $omitEmojiInDisplayNames,
autosuggestSelected, autosuggestSearchResults)
}
},
data: () => ({
shown: false

View File

@ -1,10 +1,17 @@
<ul class="compose-autosuggest-list">
<!-- accessible autocomplete, based on https://haltersweb.github.io/Accessibility/autocomplete.html -->
<ul id="compose-autosuggest-list-{realm}"
class="compose-autosuggest-list"
role="listbox"
>
{#each items as item, i (item.shortcode ? `emoji-${item.shortcode}` : `account-${item.id}`)}
<li class="compose-autosuggest-list-item">
<button class="compose-autosuggest-list-button {i === selected ? 'selected' : ''}"
tabindex="0"
on:click="onClick(event, item)">
<div class="compose-autosuggest-list-grid">
<li id="{i === selected ? `compose-autosuggest-active-item-${realm}` : ''}"
class="compose-autosuggest-list-item {i === selected ? 'selected' : ''}"
role="option"
aria-selected="{i === selected}"
aria-label="{ariaLabels[i]}"
on:click="onClick(event, item)"
>
<div class="compose-autosuggest-list-grid" aria-hidden="true">
{#if type === 'account'}
<div class="compose-autosuggest-list-item-avatar">
<Avatar
@ -28,7 +35,6 @@
</span>
{/if}
</div>
</button>
</li>
{/each}
</ul>
@ -43,17 +49,15 @@
.compose-autosuggest-list-item {
border-bottom: 1px solid var(--compose-autosuggest-outline);
display: flex;
padding: 10px;
background: var(--settings-list-item-bg);
margin: 0;
flex: 1;
cursor: pointer;
}
.compose-autosuggest-list-item:last-child {
border-bottom: none;
}
.compose-autosuggest-list-button {
padding: 10px;
background: var(--settings-list-item-bg);
border: none;
margin: 0;
flex: 1;
}
.compose-autosuggest-list-grid {
display: grid;
width: 100%;
@ -90,10 +94,10 @@
text-overflow: ellipsis;
text-align: left;
}
.compose-autosuggest-list-button:hover, .compose-autosuggest-list-button.selected {
.compose-autosuggest-list-item:hover, .compose-autosuggest-list-item.selected {
background: var(--compose-autosuggest-item-hover);
}
.compose-autosuggest-list-button:active {
.compose-autosuggest-list-item:active {
background: var(--compose-autosuggest-item-active);
}
</style>
@ -101,9 +105,17 @@
import Avatar from '../Avatar.html'
import { store } from '../../_store/store'
import AccountDisplayName from '../profile/AccountDisplayName.html'
import { createAutosuggestAccessibleLabel } from '../../_utils/createAutosuggestAccessibleLabel'
export default {
store: () => store,
computed: {
ariaLabels: ({ items, type, $omitEmojiInDisplayNames }) => {
return items.map((item, i) => {
return createAutosuggestAccessibleLabel(type, $omitEmojiInDisplayNames, i, items)
})
}
},
methods: {
onClick (event, item) {
event.preventDefault()

View File

@ -2,6 +2,11 @@
id="the-compose-box-input-{realm}"
class="compose-box-input compose-box-input-realm-{realm}"
placeholder="What's on your mind?"
aria-describedby="compose-box-input-description-{realm}"
aria-owns="compose-autosuggest-list-{realm}"
aria-expanded={autosuggestShownForThisInput}
aria-autocomplete="both"
aria-activedescendant="{autosuggestShownForThisInput ? `compose-autosuggest-active-item-${realm}` : ''}"
ref:textarea
bind:value=rawText
on:blur="onBlur()"
@ -12,6 +17,9 @@
<label for="the-compose-box-input-{realm}" class="sr-only">
What's on your mind?
</label>
<span id="compose-box-input-description-{realm}" class="sr-only">
When autocomplete results are available, press up or down arrows and enter to select.
</span>
<style>
.compose-box-input {
grid-area: input;
@ -59,6 +67,7 @@
clickSelectedAutosuggestionEmoji
} from '../../_actions/autosuggest'
import { observe } from 'svelte-extras'
import { get } from '../../_utils/lodash-lite'
export default {
oncreate () {
@ -214,6 +223,16 @@
data: () => ({
rawText: ''
}),
computed: {
/* eslint-disable camelcase */
composeFocused: ({ $autosuggestData_composeFocused, $currentInstance, realm }) => (
get($autosuggestData_composeFocused, [$currentInstance, realm], false)
),
/* eslint-enable camelcase */
autosuggestShownForThisInput: ({ realm, $autosuggestShown, composeFocused }) => (
!!($autosuggestShown && composeFocused)
)
},
events: {
selectionChange
}

View File

@ -1,6 +1,6 @@
import { get } from '../../_utils/lodash-lite'
const MIN_PREFIX_LENGTH = 1
const MIN_PREFIX_LENGTH = 2
const ACCOUNT_SEARCH_REGEX = new RegExp(`(?:\\s|^)(@\\S{${MIN_PREFIX_LENGTH},})$`)
const EMOJI_SEARCH_REGEX = new RegExp(`(?:\\s|^)(:[^:]{${MIN_PREFIX_LENGTH},})$`)

View File

@ -0,0 +1,20 @@
import { removeEmoji } from './removeEmoji'
export function createAutosuggestAccessibleLabel (
autosuggestType, $omitEmojiInDisplayNames,
selectedIndex, searchResults) {
let selected = searchResults[selectedIndex]
let label
if (autosuggestType === 'emoji') {
label = `${selected.shortcode}`
} else { // account
let displayName = selected.display_name || selected.username
let emojis = selected.emojis || []
displayName = $omitEmojiInDisplayNames
? removeEmoji(displayName, emojis) || displayName
: displayName
label = `${displayName} @${selected.acct}`
}
return `${label} (${selectedIndex + 1} of ${searchResults.length}). ` +
`Press up and down arrows to review and enter to select.`
}

View File

@ -213,7 +213,7 @@ export function getNthPostPrivacyButton (n) {
}
export function getNthAutosuggestionResult (n) {
return $(`.compose-autosuggest-list-item:nth-child(${n}) button`)
return $(`.compose-autosuggest-list-item:nth-child(${n})`)
}
export function getSearchResultByHref (href) {