Skip to content

Commit

Permalink
fix(a11y) make sure aria-labelledby/owns are correct (#193)
Browse files Browse the repository at this point in the history
  • Loading branch information
Darren Jennings authored May 29, 2020
1 parent 51e5599 commit 2fba28e
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 86 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,20 @@ Slots for injecting content around the results/input. Useful for header/footer l
</vue-autosuggest>
```

### Adding labels

It is common in forms to add a label next to the `<input />` tag for semantic html / accessibility. You can use the
`before-input` slot to accomplish this in conjunction with the `inputProps.id`:

```html
<vue-autosuggest ...>
<template slot="before-input">
<label :for="inputProps.id">Search here:</label>
</template>
...
</vue-autosuggest>
```

### suggestion item (i.e. default slot)
Used to style each suggestion inside the `<li>` tag. Using [scoped slots](https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots)
you have access to the `suggestion` item inside the `v-for` suggestions loop. This gives you the power of Vue templating, since
Expand Down
139 changes: 70 additions & 69 deletions __tests__/__snapshots__/autosuggest.test.js.snap

Large diffs are not rendered by default.

28 changes: 22 additions & 6 deletions __tests__/autosuggest.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,23 +307,39 @@ describe("Autosuggest", () => {
});

it("is aria complete", async () => {
const wrapper = mount(Autosuggest, {
propsData: defaultProps
});
const propsData = {
...defaultProps,
sectionConfigs: {
default: {
label: "Suggestions",
limit: 5,
onSelected: () => {}
}
}
}
const wrapper = mount(Autosuggest, { propsData });

const input = wrapper.find("input");
expect(input.attributes()["role"]).toBe("combobox");
expect(input.attributes()["aria-autocomplete"]).toBe("list");
expect(input.attributes()["aria-activedescendant"]).toBe("");
expect(input.attributes()["aria-owns"]).toBe("autosuggest__results");
expect(input.attributes()["aria-owns"]).toBe("autosuggest__results");

expect(input.attributes()["aria-owns"]).toBe("autosuggest-autosuggest__results");

// aria owns needs to be an "id", #191
const results = wrapper.find(`#${input.attributes()["aria-owns"]}`)
expect(results.exists()).toBeTruthy()

// TODO: Make sure aria-completeness is actually 2legit2quit.

input.trigger("click");
input.setValue("G");

expect(input.attributes()["aria-haspopup"]).toBe("true");

// make sure aria-labeledby references the section config label, and that it's an "id"
const ul = wrapper.find('ul')
expect(ul.attributes()['aria-labelledby']).toBe('autosuggest-Suggestions')
expect(ul.find(`#${ul.attributes()['aria-labelledby']}`).exists).toBeTruthy()

const mouseDownTimes = 3;
times(mouseDownTimes)(() => {
Expand Down
13 changes: 11 additions & 2 deletions docs/App.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<template>
<div class="demo" v-if="theme">
<main class="demo" v-if="theme">
<button @click="toggleTheme(oppositeTheme)">{{ oppositeTheme === 'light' ? '🌞 Go Light' : 'Go Dark 🌛' }}</button>
<h1>🔍 Vue-autosuggest</h1>
<div>
<vue-autosuggest
v-model="searchText"
componentAttrIdAutosuggest="demo-autosuggest"
@input="(...args) => logEvent('input', args)"
@highlighted="(...args) => logEvent('highlighted', args)"
@selected="onSelected"
Expand All @@ -15,6 +16,9 @@
:should-render-suggestions="(size, loading) => size >= 0 && !loading && searchText !== ''"
ref="autocomplete"
>
<template slot="before-input">
<label :for="inputProps.id">Select a LOTR Character</label>
</template>
<template slot-scope="{suggestion, index, cs}">
<div>{{ suggestion && suggestion.item.Name }}</div>
</template>
Expand Down Expand Up @@ -52,7 +56,7 @@
<span class="evt-name">{{ evt.name }}</span>: <span class="evt-val">{{ evt.value }}</span>
</div>
</div>
</div>
</main>
</template>

<script>
Expand Down Expand Up @@ -203,6 +207,11 @@ h1 {
transition: border-color linear 0.1s;
}
#demo-autosuggest label {
margin-bottom: 1rem;
display:block;
}
#autosuggest__input {
background-color: var(--theme-bg);
caret-color: #ddd;
Expand Down
4 changes: 2 additions & 2 deletions docs/build/app.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/storybook/iframe.html
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
<body>
<div id="root"></div>
<div id="error-display"></div>
<script src="static/preview.a4c7ea3211409c3899df.bundle.js"></script>
<script src="static/preview.f7c8bd8feb12de85cbf0.bundle.js"></script>
</body>
</html>

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions src/Autosuggest.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@
:class="[isOpen ? `${componentAttrPrefix}__input--open` : '', internal_inputProps['class']]"
v-bind="internal_inputProps"
aria-autocomplete="list"
:aria-owns="`${componentAttrPrefix}__results`"
:aria-owns="`${componentAttrIdAutosuggest}-${componentAttrPrefix}__results`"
:aria-activedescendant="isOpen && currentIndex !== null ? `${componentAttrPrefix}__results-item--${currentIndex}` : ''"
:aria-haspopup="isOpen ? 'true' : 'false'"
:aria-expanded="isOpen ? 'true' : 'false'"
@input="inputHandler"
@keydown="handleKeyStroke"
v-on="listeners"
><slot name="after-input" />
<div :class="_componentAttrClassAutosuggestResultsContainer">
<div
:id="`${componentAttrIdAutosuggest}-${componentAttrPrefix}__results`"
:class="_componentAttrClassAutosuggestResultsContainer"
>
<div
v-if="isOpen"
:class="_componentAttrClassAutosuggestResults"
Expand All @@ -33,6 +36,7 @@
:render-suggestion="renderSuggestion"
:section="cs"
:component-attr-prefix="componentAttrPrefix"
:component-attr-id-autosuggest="componentAttrIdAutosuggest"
@updateCurrentIndex="updateCurrentIndex"
>
<template
Expand Down
7 changes: 4 additions & 3 deletions src/parts/DefaultSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const DefaultSection = {
currentIndex: { type: [Number, String], required: false, default: Infinity },
renderSuggestion: { type: Function, required: false },
normalizeItemFunction: { type: Function, required: true },
componentAttrPrefix: { type: String, required: true }
componentAttrPrefix: { type: String, required: true },
componentAttrIdAutosuggest: { type: String, required: true }
},
data: function () {
return {
Expand Down Expand Up @@ -60,11 +61,11 @@ const DefaultSection = {
return h(
"ul",
{
attrs: { role: "listbox", "aria-labelledby": "autosuggest", ...this.section.ulAttributes },
attrs: { role: "listbox", "aria-labelledby": this.section.label && `${this.componentAttrIdAutosuggest}-${this.section.label}` },
class: this.section.ulClass
},
[
before[0] && before[0] || this.section.label && <li class={beforeClassName}>{this.section.label}</li> || '',
before[0] && before[0] || this.section.label && <li class={beforeClassName} id={`${this.componentAttrIdAutosuggest}-${this.section.label}`}>{this.section.label}</li> || '',
this.list.map((val, key) => {
const item = this.normalizeItemFunction(this.section.name, this.section.type, this.section.label, this.section.liClass, val)
const itemIndex = this.getItemIndex(key)
Expand Down

0 comments on commit 2fba28e

Please sign in to comment.