Skip to content
Permalink
Browse files
feat(b-pagination, b-pagination-nav): improve aria accessibility (closes
: #4811, #4160) (#4810)

Co-authored-by: Jacob Müller <jacob.mueller.elz@gmail.com>
  • Loading branch information
tmorehouse and jacobmllr95 committed Feb 23, 2020
1 parent 5684905 commit 7ee4baa9a843411cd30a3ee499fc7272b7cf48f2
Showing 6 changed files with 89 additions and 68 deletions.
@@ -1,4 +1,4 @@
# Form tags
# Form Tags

> Lightweight custom tagged input form control, with options for customized interface rendering,
> duplicate tag detection and optional tag validation.
@@ -515,23 +515,14 @@ recommended unless the content of the button textually conveys its purpose.

### Keyboard navigation

`<b-pagination-nav>` supports keyboard navigation out of the box, and follows the
[WAI-ARIA roving tabindex](https://www.w3.org/TR/wai-aria-practices-1.2/#kbd_roving_tabindex)
pattern.

- Tabbing into the pagination component will autofocus the current active page button
- <kbd>LEFT</kbd> (or <kbd>UP</kbd>) and <kbd>RIGHT</kbd> (or <kbd>DOWN</kbd>) arrow keys will focus
the previous and next buttons, respectively, in the page list
- <kbd>ENTER</kbd> or <kbd>SPACE</kbd> keys will select (click) the currently focused page button
- Pressing <kbd>TAB</kbd> will move to the next control or link on the page, while pressing
<kbd>SHIFT</kbd>+<kbd>TAB</kbd> will move to the previous control or link on the page.
`<b-pagination-nav>` supports standard <kbd>TAB</kbd> key navigation.

## See also

Refer to the [Router support](/docs/reference/router-links) reference page for router-link specific
props.

For pagination control of a component (such as `<b-table>`), use the
For pagination control of a component (such as `<b-table>`) or a pagination list, use the
[`<b-pagination>`](/docs/components/pagination) component instead.

<!-- Component reference added automatically from component package.json -->
@@ -32,6 +32,7 @@ describe('pagination-nav', () => {

// NAV Attributes
expect(wrapper.attributes('aria-hidden')).toBe('false')
expect(wrapper.attributes('aria-label')).toBe('Pagination')

// UL Classes
expect($ul.classes()).toContain('pagination')
@@ -41,9 +42,9 @@ describe('pagination-nav', () => {
expect($ul.classes()).not.toContain('justify-content-center')
expect($ul.classes()).not.toContain('justify-content-end')
// UL Attributes
expect($ul.attributes('role')).toBe('menubar')
expect($ul.attributes('role')).not.toBe('menubar')
expect($ul.attributes('aria-disabled')).toBe('false')
expect($ul.attributes('aria-label')).toBe('Pagination')
expect($ul.attributes('aria-label')).not.toBe('Pagination')

wrapper.destroy()
})
@@ -126,7 +127,7 @@ describe('pagination-nav', () => {
expect($ul.classes()).toContain('b-pagination')

// UL Attributes
expect($ul.attributes('role')).toBe('menubar')
expect($ul.attributes('role')).not.toBe('menubar')
expect($ul.attributes('aria-disabled')).toBe('true')

// LI classes
@@ -132,8 +132,8 @@ export const BPagination = /*#__PURE__*/ Vue.extend({
return pageNum
},
linkProps() {
// Always '#' for pagination component
return { href: '#' }
// No props, since we render a plain button
return {}
}
}
})
@@ -56,7 +56,7 @@ describe('pagination', () => {
if (index === 2) {
expect(li.classes()).toContain('active')
expect(li.classes()).not.toContain('disabled')
expect(pageLink.is('a')).toBe(true)
expect(pageLink.is('button')).toBe(true)
} else {
expect(li.classes()).not.toContain('active')
expect(li.classes()).toContain('disabled')
@@ -78,14 +78,13 @@ describe('pagination', () => {
expect(last.find('.page-link').text()).toEqual('»')

// Page button attrs
expect(page.find('.page-link').attributes('href')).toEqual('#')
expect(page.find('.page-link').attributes('type')).toEqual('button')
expect(page.find('.page-link').attributes('role')).toEqual('menuitemradio')
expect(page.find('.page-link').attributes('aria-checked')).toEqual('true')
expect(page.find('.page-link').attributes('aria-posinset')).toEqual('1')
expect(page.find('.page-link').attributes('aria-setsize')).toEqual('1')
expect(page.find('.page-link').attributes('tabindex')).toEqual('0')
expect(page.find('.page-link').attributes('aria-label')).toEqual('Go to page 1')
expect(page.find('.page-link').attributes('target')).toEqual('_self')

wrapper.destroy()
})
@@ -133,7 +132,7 @@ describe('pagination', () => {
disabled: false
})

const $links = wrapper.findAll('a.page-link')
const $links = wrapper.findAll('button.page-link')
expect($links.length).toBe(5)
expect($links.at(0).text()).toBe('Page 1')
expect($links.at(1).text()).toBe('Page 2')
@@ -388,16 +387,16 @@ describe('pagination', () => {
})
expect(wrapper.is('ul')).toBe(true)
expect(wrapper.findAll('li').length).toBe(5)
expect(wrapper.findAll('a.page-link').length).toBe(4)
expect(wrapper.findAll('a.page-link').is('[aria-controls="foo"]')).toBe(true)
expect(wrapper.findAll('button.page-link').length).toBe(4)
expect(wrapper.findAll('button.page-link').is('[aria-controls="foo"]')).toBe(true)

wrapper.setProps({
ariaControls: null
})
await waitNT(wrapper.vm)
expect(wrapper.findAll('li').length).toBe(5)
expect(wrapper.findAll('a.page-link').length).toBe(4)
expect(wrapper.findAll('a.page-link').is('[aria-controls]')).toBe(false)
expect(wrapper.findAll('button.page-link').length).toBe(4)
expect(wrapper.findAll('button.page-link').is('[aria-controls]')).toBe(false)

wrapper.destroy()
})
@@ -414,28 +413,28 @@ describe('pagination', () => {
})
expect(wrapper.is('ul')).toBe(true)
expect(wrapper.findAll('li').length).toBe(5)
expect(wrapper.findAll('a').length).toBe(4)
expect(wrapper.findAll('button').length).toBe(4)
expect(
wrapper
.findAll('a')
.findAll('button')
.at(0)
.attributes('aria-label')
).toBe('Go to page 1')
expect(
wrapper
.findAll('a')
.findAll('button')
.at(1)
.attributes('aria-label')
).toBe('Go to page 2')
expect(
wrapper
.findAll('a')
.findAll('button')
.at(2)
.attributes('aria-label')
).toBe('Go to page 3')
expect(
wrapper
.findAll('a')
.findAll('button')
.at(3)
.attributes('aria-label')
).toBe('Go to next page')
@@ -654,7 +653,7 @@ describe('pagination', () => {
wrapper
.findAll('li')
.at(3)
.find('a')
.find('button')
.trigger('click')
await waitNT(wrapper.vm)
expect(wrapper.vm.computedCurrentPage).toBe(2)
@@ -667,7 +666,7 @@ describe('pagination', () => {
wrapper
.findAll('li')
.at(6)
.find('a')
.find('button')
.trigger('keydown.space') // Generates a click event
await waitNT(wrapper.vm)
expect(wrapper.vm.computedCurrentPage).toBe(3)
@@ -678,7 +677,7 @@ describe('pagination', () => {
wrapper
.findAll('li')
.at(1)
.find('a')
.find('button')
.trigger('click')
await waitNT(wrapper.vm)
expect(wrapper.vm.computedCurrentPage).toBe(2)
@@ -1031,7 +1030,7 @@ describe('pagination', () => {
expect(wrapper.is('ul')).toBe(true)
await waitNT(wrapper.vm)
// Grab the button links (2 bookends + 3 pages + 2 bookends)
const links = wrapper.findAll('a.page-link')
const links = wrapper.findAll('button.page-link')
expect(links.length).toBe(7)

// Sanity check for getBCR override
@@ -1091,7 +1090,7 @@ describe('pagination', () => {
await waitNT(wrapper.vm)
expect(wrapper.is('ul')).toBe(true)
// Grab the button links (2 bookends + 3 pages + 2 bookends)
const links = wrapper.findAll('a.page-link')
const links = wrapper.findAll('button.page-link')
expect(links.length).toBe(7)

// Focus the last button
@@ -1121,14 +1120,14 @@ describe('pagination', () => {
await waitNT(wrapper.vm)
expect(wrapper.is('ul')).toBe(true)
// Grab the button links (2 disabled bookends + 4 pages + (-ellipsis) + 2 bookends)
links = wrapper.findAll('a.page-link')
links = wrapper.findAll('button.page-link')
expect(links.length).toBe(6)

// Click on the 4th button (page 4, index 3)
links.at(3).element.click()
await waitNT(wrapper.vm)
// Links re-rendered with first bookends enabled and an ellipsis
links = wrapper.findAll('a.page-link')
links = wrapper.findAll('button.page-link')
// The 4th link should be page 4, and retain focus
expect(document.activeElement).toEqual(links.at(3).element)

0 comments on commit 7ee4baa

Please sign in to comment.