fix: make autosuggestion accessible (#1183)
* fix: make autosuggestion accessible fixes #129 * remove tabindexes, fix aria-hidden
This commit is contained in:
parent
78715bc098
commit
8d0db2c97c
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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},})$`)
|
||||
|
||||
|
|
20
src/routes/_utils/createAutosuggestAccessibleLabel.js
Normal file
20
src/routes/_utils/createAutosuggestAccessibleLabel.js
Normal 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.`
|
||||
}
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue