Skip to content

Commit

Permalink
Merge pull request #30 from nextcloud-deps/enh/a11y/attributes
Browse files Browse the repository at this point in the history
enh(a11y): Improve accessibility with correct attributes
  • Loading branch information
Pytal authored Jan 9, 2024
2 parents e89074e + ec26624 commit 0bd4d78
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 24 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
{
"path": "./dist/vue-select.js",
"compression": "none",
"maxSize": "22 KB"
"maxSize": "23 KB"
},
{
"path": "./dist/vue-select.css",
Expand Down
69 changes: 48 additions & 21 deletions src/components/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
</style>

<template>
<div :dir="dir" class="v-select" :class="stateClasses">
<div
:id="`v-select-${uid}`"
:dir="dir"
class="v-select"
:class="stateClasses"
>
<slot name="header" v-bind="scope.header" />
<div
:id="`vs${uid}__combobox`"
ref="toggle"
class="vs__dropdown-toggle"
role="combobox"
:aria-expanded="dropdownOpen.toString()"
:aria-owns="`vs${uid}__listbox`"
:aria-label="ariaLabelCombobox"
@mousedown="toggleDropdown($event)"
>
<div ref="selectedOptions" class="vs__selected-options">
<div ref="toggle" class="vs__dropdown-toggle">
<div
ref="selectedOptions"
class="vs__selected-options"
@mousedown="toggleDropdown"
>
<slot
v-for="(option, index) in selectedValue"
name="selected-option-container"
Expand Down Expand Up @@ -70,13 +70,25 @@
<component :is="childComponents.Deselect" />
</button>

<slot name="open-indicator" v-bind="scope.openIndicator">
<component
:is="childComponents.OpenIndicator"
v-if="!noDrop"
v-bind="scope.openIndicator.attributes"
/>
</slot>
<!-- tabindex -1 is used to remove it from the tab sequence as tabbing to the input combobox opens the dropdown -->
<button
v-if="!noDrop"
ref="openIndicatorButton"
class="vs__open-indicator-button"
type="button"
tabindex="-1"
:aria-labelledby="`vs${uid}__listbox`"
:aria-controls="`vs${uid}__listbox`"
:aria-expanded="dropdownOpen.toString()"
@mousedown="toggleDropdown"
>
<slot name="open-indicator" v-bind="scope.openIndicator">
<component
:is="childComponents.OpenIndicator"
v-bind="scope.openIndicator.attributes"
/>
</slot>
</button>

<slot name="spinner" v-bind="scope.spinner">
<div v-show="mutableLoading" class="vs__spinner">Loading...</div>
Expand All @@ -92,6 +104,7 @@
v-append-to-body
class="vs__dropdown-menu"
role="listbox"
:aria-label="ariaLabelListbox"
:aria-multiselectable="multiple"
tabindex="-1"
@mousedown.prevent="onMousedown"
Expand Down Expand Up @@ -131,6 +144,7 @@
v-else
:id="`vs${uid}__listbox`"
role="listbox"
:aria-label="ariaLabelListbox"
style="display: none; visibility: hidden"
></ul>
</transition>
Expand Down Expand Up @@ -305,6 +319,16 @@ export default {
default: 'Search for options',
},
/**
* Allows to customize the `aria-label` set on the listbox element.
* @type {String}
* @default 'Options'
*/
ariaLabelListbox: {
type: String,
default: 'Options',
},
/**
* Allows to customize the `aria-label` set on the clear-selected button
* @type {String}
Expand Down Expand Up @@ -811,14 +835,17 @@ export default {
return {
search: {
attributes: {
id: this.inputId,
disabled: this.disabled,
placeholder: this.searchPlaceholder,
tabindex: this.tabindex,
readonly: !this.searchable,
id: this.inputId,
role: 'combobox',
'aria-autocomplete': 'list',
'aria-labelledby': `vs${this.uid}__combobox`,
'aria-label': this.ariaLabelCombobox,
'aria-controls': `vs${this.uid}__listbox`,
'aria-owns': `vs${this.uid}__listbox`,
'aria-expanded': this.dropdownOpen.toString(),
ref: 'search',
type: 'search',
autocomplete: this.autocomplete,
Expand Down
1 change: 1 addition & 0 deletions src/css/global/states.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
.vs__clear,
.vs__search,
.vs__selected,
.vs__open-indicator-button,
.vs__open-indicator {
cursor: var(--vs-disabled-cursor);
background-color: var(--vs-disabled-bg);
Expand Down
8 changes: 8 additions & 0 deletions src/css/modules/open-indicator-button.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* Open Indicator Button */

.vs__open-indicator-button {
padding: 0;
border: 0;
background-color: transparent;
cursor: pointer;
}
1 change: 1 addition & 0 deletions src/css/vue-select.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@import 'global/states.css';

@import 'modules/dropdown-toggle.css';
@import 'modules/open-indicator-button.css';
@import 'modules/open-indicator.css';
@import 'modules/clear.css';
@import 'modules/dropdown-menu.css';
Expand Down
48 changes: 46 additions & 2 deletions tests/unit/Accessibility.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,61 @@ describe('Search Slot Scope', () => {
).toEqual(`vs${Select.vm.uid}__option-2`)
})
})

describe('aria-expanded', () => {
it('expanded attribute should reflect dropdown open state', async () => {
const Select = mountDefault()
const input = Select.findComponent({ ref: 'search' })

expect(Select.vm.open).toEqual(false)
expect(input.attributes('aria-expanded')).toEqual('false')

Select.vm.open = true
await Select.vm.$nextTick()
expect(Select.vm.open).toEqual(true)
expect(input.attributes('aria-expanded')).toEqual('true')
})
})
})

describe('UID', () => {
it('works with strings', () => {
const Select = mountDefault({ uid: 'hello' })
expect(Select.find('#vshello__combobox').exists()).toBeTruthy()
expect(Select.find('#v-select-hello').exists()).toBeTruthy()
})

it('works with numbers', () => {
const Select = mountDefault({ uid: 2 })
expect(Select.find('#vs2__combobox').exists()).toBeTruthy()
expect(Select.find('#v-select-2').exists()).toBeTruthy()
})
})

describe('Selected Options Wrapper', () => {
it('toggle with mouse', () => {
const Select = mountDefault()
Select.findComponent({ ref: 'selectedOptions' }).trigger('mousedown')
expect(Select.vm.open).toEqual(true)
Select.findComponent({ ref: 'selectedOptions' }).trigger('mousedown')
expect(Select.vm.open).toEqual(false)
})
})

describe('Open Indicator', () => {
it('hidden from keyboard navigation', () => {
const Select = mountDefault()
expect(
Select.findComponent({ ref: 'openIndicatorButton' }).attributes(
'tabindex'
)
).toEqual('-1')
})

it('toggle with mouse', () => {
const Select = mountDefault()
Select.findComponent({ ref: 'openIndicatorButton' }).trigger('mousedown')
expect(Select.vm.open).toEqual(true)
Select.findComponent({ ref: 'openIndicatorButton' }).trigger('mousedown')
expect(Select.vm.open).toEqual(false)
})
})

Expand Down

0 comments on commit 0bd4d78

Please sign in to comment.