Skip to content

Commit

Permalink
Cherry pick "Add aria attributes for screen readers (#587)" (#599)
Browse files Browse the repository at this point in the history
Add more specific and complete aria attributes to allow screen
readers to announce menu and menu items correctly.

Note: adding an alert role that is read by screen readers every time the 'no-results' panel is shown is not implemented in this PR. the default behavior is that it is read only once if the text remains the same. This issue will be addressed in a follow-up PR.

Bug: T290733
Co-authored-by: Lucas Werkmeister <[email protected]>
Co-authored-by: Guergana Tzatchkova <[email protected]>
  • Loading branch information
3 people authored Jun 17, 2022
1 parent fbd9fce commit a0e697a
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 5 deletions.
1 change: 1 addition & 0 deletions vue-components/src/components/Lookup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
@update:searchInput="$emit('update:searchInput', $event)"
@input="$emit('input', $event)"
@scroll="(firstIndex, lastIndex) => $emit('scroll', firstIndex, lastIndex)"
:label="label"
>
<template #no-results>
<slot name="no-results" />
Expand Down
23 changes: 23 additions & 0 deletions vue-components/src/components/LookupInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
:disabled="disabled"
autocomplete="off"
v-bind="$attrs"
:aria-activedescendant="keyboardHoverId"
:aria-owns="optionsMenuId"
aria-autocomplete="list"
aria-haspopup="listbox"
:aria-expanded="showMenu || 'false'"
role="combobox"
/>
<OptionsMenu
class="wikit-LookupInput__menu"
Expand All @@ -23,7 +29,10 @@
@select="onSelect"
@scroll="onScroll"
@esc="onEsc"
@keyboard-hover-change="onKeyboardHoverChange"
ref="menu"
:id="optionsMenuId"
:label="label"
>
<template #no-results>
<slot name="no-results" />
Expand Down Expand Up @@ -59,6 +68,11 @@ interface Props {
* computed property to dynamically update the Lookup's `menuItems` prop.
*/
searchInput?: string;
/**
* Sets the label to be passed down to the inner `<OptionsMenu>` component so it can be properly announced
* by screen readers.
*/
label?: string;
}
const props = withDefaults( defineProps<Props>(), {
Expand All @@ -68,6 +82,7 @@ const props = withDefaults( defineProps<Props>(), {
placeholder: '',
value: null,
searchInput: '',
label: '',
} );
/**
Expand Down Expand Up @@ -113,8 +128,14 @@ function onSelect( menuItem: MenuItem ): void {
emit( 'update:searchInput', menuItem.label );
}
const keyboardHoverId = ref<string|null>( null );
function onEsc(): void {
showMenu.value = false;
keyboardHoverId.value = null;
}
function onKeyboardHoverChange( menuItemId: string ): void {
keyboardHoverId.value = menuItemId;
}
const selectedItemIndex = computed( (): number => {
Expand All @@ -134,6 +155,7 @@ const selectedItemIndex = computed( (): number => {

<script lang="ts">
import { defineComponent } from 'vue';
import generateUid from '@/components/util/generateUid';
export default defineComponent( {
name: 'LookupInput',
Expand All @@ -143,6 +165,7 @@ export default defineComponent( {
showMenu: false,
scrollIndexStart: null as ( number | null ),
scrollIndexEnd: null as ( number | null ),
optionsMenuId: generateUid( 'wikit-OptionsMenu' ),
};
},
methods: {
Expand Down
27 changes: 23 additions & 4 deletions vue-components/src/components/OptionsMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
:class="[ 'wikit', 'wikit-OptionsMenu' ]"
@scroll.passive="onScroll"
ref="lookup-menu"
role="listbox"
:style="{ maxHeight: maxHeight ? maxHeight + 'px' : null }"
:aria-label="label"
>
<div
class="wikit-OptionsMenu__item"
Expand All @@ -18,6 +20,11 @@
@mousedown.prevent="activeItemIndex = index"
@mouseup="activeItemIndex = -1"
ref="menu-items"
role="option"
:aria-label="menuItem.label"
:aria-describedby="`${menuItemId}__description-${index} ${menuItemId}__tag-${index}`"
:id="`${menuItemId}-${index}`"
:aria-selected="index === selectedItemIndex || 'false'"
>
<div class="wikit-OptionsMenu__item__label-wrapper">
<div
Expand All @@ -28,11 +35,11 @@
>
{{ menuItem.label }}
</div>
<div v-if="menuItem.tag" class="wikit-OptionsMenu__item__tag">
<div v-if="menuItem.tag" class="wikit-OptionsMenu__item__tag" :id="`${menuItemId}__tag-${index}`">
{{ menuItem.tag }}
</div>
</div>
<div class="wikit-OptionsMenu__item__description">
<div class="wikit-OptionsMenu__item__description" :id="`${menuItemId}__description-${index}`">
{{ menuItem.description }}
</div>
</div>
Expand All @@ -46,6 +53,7 @@
import { ComponentPublicInstance, defineComponent, PropType } from 'vue';
import debounce from 'lodash/debounce';
import { MenuItem } from './MenuItem';
import generateUid from '@/components/util/generateUid';
/**
* This is an internal component which used by the Lookup component.
Expand All @@ -57,9 +65,10 @@ export default defineComponent( {
maxHeight: null as number|null,
activeItemIndex: -1,
keyboardHoveredItemIndex: -1,
menuItemId: generateUid( 'wikit-OptionsMenu__item__id' ),
};
},
emits: [ 'scroll', 'select', 'esc' ],
emits: [ 'scroll', 'select', 'esc', 'keyboard-hover-change' ],
props: {
menuItems: {
type: Array as PropType<MenuItem[]>,
Expand All @@ -86,6 +95,10 @@ export default defineComponent( {
type: Boolean,
default: false,
},
label: {
type: String,
default: '',
},
},
methods: {
onKeyDown( event: KeyboardEvent ): void {
Expand All @@ -108,7 +121,6 @@ export default defineComponent( {
} else {
this.keyboardHoveredItemIndex = Math.max( 0, this.keyboardHoveredItemIndex - 1 );
}
this.keyboardScroll();
break;
case 'ArrowDown':
Expand Down Expand Up @@ -187,6 +199,13 @@ export default defineComponent( {
this.keyboardHoveredItemIndex = this.selectedItemIndex;
this.resizeMenu();
},
keyboardHoveredItemIndex( hoveredIndex: number ): void {
if ( hoveredIndex > -1 ) {
this.$emit( 'keyboard-hover-change', `${this.menuItemId}-${hoveredIndex}` );
} else {
this.$emit( 'keyboard-hover-change', null );
}
},
},
compatConfig: {
Expand Down
3 changes: 2 additions & 1 deletion vue-components/tests/a11y/AllComponents.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ describe( 'OptionsMenu', () => {
{ label: 'potato', description: 'root vegetable' },
{ label: 'duck', description: 'aquatic bird' },
];
const wrapper = mount( OptionsMenu, { props: { menuItems } } );
const lookupLabel = 'Label of the parent Lookup';
const wrapper = mount( OptionsMenu, { props: { menuItems, label: lookupLabel } } );
const results = await axe( wrapper.element, {
rules: {
'region': { enabled: false },
Expand Down

0 comments on commit a0e697a

Please sign in to comment.