Skip to content

Commit

Permalink
fix(nav-item-dropdown): update dropdown to set correct aria-controls
Browse files Browse the repository at this point in the history
  • Loading branch information
xanf committed Dec 15, 2021
2 parents 49d8a9d + b8ce56a commit 97bb97b
Show file tree
Hide file tree
Showing 12 changed files with 486 additions and 312 deletions.
2 changes: 1 addition & 1 deletion .bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
{
"path": "./dist/bootstrap-vue.common.js",
"maxSize": "355 kB"
"maxSize": "360 kB"
},
{
"path": "./dist/bootstrap-vue.common.min.js",
Expand Down
6 changes: 6 additions & 0 deletions docs/common-props.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@
"exactActiveClass": {
"description": "<router-link> prop: Configure the active CSS class applied when the link is active with exact match. Typically you will want to set this to class name 'active'"
},
"exactPath": {
"description": "<router-link> prop: Allows matching only using the path section of the url, effectively ignoring the query and the hash sections"
},
"exactPathActiveClass": {
"description": "<router-link> prop: Configure the active CSS class applied when the link is active with exact path match. Typically you will want to set this to class name 'active'"
},
"fade": {
"description": "When set to `true`, enables the fade animation/transition on the component"
},
Expand Down
28 changes: 28 additions & 0 deletions docs/markdown/reference/router-links/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,34 @@ With components that support router links (have a `to` prop), you will want to s
`'active'` (or a space separated string that includes `'active'`) to apply Bootstrap's active
styling on the component when the current route matches the `to` prop.

### `exact-path`

- type: `boolean`
- default: `false`
- availability: Vue Router 3.5.0+

Allows matching only using the `path` section of the url, effectively ignoring the `query` and the
`hash` sections.

```html
<!-- this link will also be active at `/search?page=2` or `/search#filters` -->
<router-link to="/search" exact-path> </router-link>
```

### `exact-path-active-class`

- type: `string`
- default: `'router-link-exact-path-active'`
- availability: Vue Router 3.5.0+

Configure the active CSS class applied when the link is active with exact path match. Note the
default value can also be configured globally via the `linkExactPathActiveClass` router constructor
option.

With components that support router links (have a `to` prop), you will want to set this to the class
`'active'` (or a space separated string that includes `'active'`) to apply Bootstrap's active
styling on the component when the current route matches the `to` prop.

## Nuxt.js specific router link props

When BootstrapVue detects that your app is running under [Nuxt.js](https://nuxtjs.org), it will
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@
},
"devDependencies": {
"@babel/cli": "^7.13.16",
"@babel/core": "^7.14.0",
"@babel/core": "^7.16.5",
"@babel/plugin-transform-modules-commonjs": "^7.14.0",
"@babel/plugin-transform-runtime": "^7.13.15",
"@babel/plugin-transform-runtime": "^7.16.5",
"@babel/preset-env": "^7.14.2",
"@babel/standalone": "^7.14.1",
"@nuxt/content": "^1.14.0",
Expand All @@ -116,7 +116,7 @@
"eslint-config-prettier": "^8.3.0",
"eslint-config-standard": "^16.0.2",
"eslint-config-vue": "^2.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-jest": "^24.3.6",
"eslint-plugin-markdown": "^2.1.0",
"eslint-plugin-node": "^11.1.0",
Expand All @@ -143,7 +143,7 @@
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"sass": "^1.32.12",
"sass": "^1.45.0",
"sass-loader": "^10.1.1",
"standard-version": "^9.3.0",
"terser": "^5.7.0",
Expand Down
3 changes: 2 additions & 1 deletion src/components/dropdown/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,8 @@ Providing a unique `id` prop ensures ARIA compliance by automatically adding the
`aria-*` attributes in the rendered markup.

The default ARIA role is set to `menu`, but you can change this default to another role (such as
`navigation`) via the `role` prop, depending on your user case.
`navigation`) via the `role` prop, depending on your user case. The `role` prop value will be used
to determine `aria-haspopup` attribute for the toggle button.

When a menu item doesn't trigger navigation, it is recommended to use the `<b-dropdown-item-button>`
sub-component (which is not announced as a link) instead of `<b-dropdown-item>` (which is presented
Expand Down
3 changes: 2 additions & 1 deletion src/components/dropdown/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export const BDropdown = /*#__PURE__*/ Vue.extend({
$buttonChildren = [h('span', { class: ['sr-only'] }, [this.toggleText])]
buttonContentDomProps = {}
}
const ariaHasPopupRoles = ['menu', 'listbox', 'tree', 'grid', 'dialog']

const $toggle = h(
BButton,
Expand All @@ -151,7 +152,7 @@ export const BDropdown = /*#__PURE__*/ Vue.extend({
...this.toggleAttrs,
// Must have attributes
id: this.safeId('_BV_toggle_'),
'aria-haspopup': 'true',
'aria-haspopup': ariaHasPopupRoles.includes(role) ? role : 'false',
'aria-expanded': toString(visible)
},
props: {
Expand Down
14 changes: 7 additions & 7 deletions src/components/dropdown/dropdown.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe('dropdown', () => {
expect($button.classes()).toContain('dropdown-toggle')
expect($button.classes().length).toBe(3)
expect($button.attributes('aria-haspopup')).toBeDefined()
expect($button.attributes('aria-haspopup')).toEqual('true')
expect($button.attributes('aria-haspopup')).toEqual('menu')
expect($button.attributes('aria-expanded')).toBeDefined()
expect($button.attributes('aria-expanded')).toEqual('false')
expect($button.attributes('id')).toBeDefined()
Expand Down Expand Up @@ -125,7 +125,7 @@ describe('dropdown', () => {
expect($toggle.classes()).toContain('dropdown-toggle-split')
expect($toggle.classes().length).toBe(4)
expect($toggle.attributes('aria-haspopup')).toBeDefined()
expect($toggle.attributes('aria-haspopup')).toEqual('true')
expect($toggle.attributes('aria-haspopup')).toEqual('menu')
expect($toggle.attributes('aria-expanded')).toBeDefined()
expect($toggle.attributes('aria-expanded')).toEqual('false')
expect($toggle.attributes('id')).toBeDefined()
Expand Down Expand Up @@ -551,7 +551,7 @@ describe('dropdown', () => {
expect($dropdown.vm).toBeDefined()

expect($toggle.attributes('aria-haspopup')).toBeDefined()
expect($toggle.attributes('aria-haspopup')).toEqual('true')
expect($toggle.attributes('aria-haspopup')).toEqual('menu')
expect($toggle.attributes('aria-expanded')).toBeDefined()
expect($toggle.attributes('aria-expanded')).toEqual('false')
expect($dropdown.classes()).not.toContain('show')
Expand All @@ -564,7 +564,7 @@ describe('dropdown', () => {
await waitNT(wrapper.vm)

expect($toggle.attributes('aria-haspopup')).toBeDefined()
expect($toggle.attributes('aria-haspopup')).toEqual('true')
expect($toggle.attributes('aria-haspopup')).toEqual('menu')
expect($toggle.attributes('aria-expanded')).toBeDefined()
expect($toggle.attributes('aria-expanded')).toEqual('true')
expect($dropdown.classes()).toContain('show')
Expand Down Expand Up @@ -754,7 +754,7 @@ describe('dropdown', () => {
const $dropdown = wrapper.find('.dropdown')

expect($toggle.attributes('aria-haspopup')).toBeDefined()
expect($toggle.attributes('aria-haspopup')).toEqual('true')
expect($toggle.attributes('aria-haspopup')).toEqual('menu')
expect($toggle.attributes('aria-expanded')).toBeDefined()
expect($toggle.attributes('aria-expanded')).toEqual('false')
expect($dropdown.classes()).not.toContain('show')
Expand All @@ -765,7 +765,7 @@ describe('dropdown', () => {
expect(wrapper.emitted('show')).toBeDefined()
expect(wrapper.emitted('show').length).toBe(1)
expect($toggle.attributes('aria-haspopup')).toBeDefined()
expect($toggle.attributes('aria-haspopup')).toEqual('true')
expect($toggle.attributes('aria-haspopup')).toEqual('menu')
expect($toggle.attributes('aria-expanded')).toBeDefined()
expect($toggle.attributes('aria-expanded')).toEqual('false')
expect($dropdown.classes()).not.toContain('show')
Expand All @@ -777,7 +777,7 @@ describe('dropdown', () => {
expect(wrapper.emitted('show')).toBeDefined()
expect(wrapper.emitted('show').length).toBe(2)
expect($toggle.attributes('aria-haspopup')).toBeDefined()
expect($toggle.attributes('aria-haspopup')).toEqual('true')
expect($toggle.attributes('aria-haspopup')).toEqual('menu')
expect($toggle.attributes('aria-expanded')).toBeDefined()
expect($toggle.attributes('aria-expanded')).toEqual('true')
expect($dropdown.classes()).toContain('show')
Expand Down
2 changes: 1 addition & 1 deletion src/components/form-tags/_form-tags.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
.b-form-tags-list {
margin-top: -0.25rem;

.b-from-tags-field,
.b-form-tags-field,
.b-form-tag {
margin-top: 0.25rem;
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/form-tags/form-tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ export const BFormTags = /*#__PURE__*/ Vue.extend({
const $field = h(
'li',
{
staticClass: 'b-from-tags-field flex-grow-1',
staticClass: 'b-form-tags-field flex-grow-1',
attrs: {
role: 'none',
'aria-live': 'off',
Expand Down
2 changes: 2 additions & 0 deletions src/components/link/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const routerLinkProps = {
event: makeProp(PROP_TYPE_ARRAY_STRING),
exact: makeProp(PROP_TYPE_BOOLEAN, false),
exactActiveClass: makeProp(PROP_TYPE_STRING),
exactPath: makeProp(PROP_TYPE_BOOLEAN, false),
exactPathActiveClass: makeProp(PROP_TYPE_STRING),
replace: makeProp(PROP_TYPE_BOOLEAN, false),
routerTag: makeProp(PROP_TYPE_STRING),
to: makeProp(PROP_TYPE_OBJECT_STRING)
Expand Down
11 changes: 8 additions & 3 deletions src/components/nav/nav-item-dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export const BNavItemDropdown = /*#__PURE__*/ Vue.extend({
toggleId() {
return this.safeId('_BV_toggle_')
},
menuId() {
return this.safeId('_BV_toggle_menu_')
},
dropdownClasses() {
return [this.directionClass, this.boundaryClass, { show: this.visible }]
},
Expand All @@ -57,7 +60,7 @@ export const BNavItemDropdown = /*#__PURE__*/ Vue.extend({
}
},
render(h) {
const { toggleId, visible, hide } = this
const { toggleId, menuId, visible, hide } = this

const $toggle = h(
BLink,
Expand All @@ -72,7 +75,8 @@ export const BNavItemDropdown = /*#__PURE__*/ Vue.extend({
id: toggleId,
role: 'button',
'aria-haspopup': 'true',
'aria-expanded': visible ? 'true' : 'false'
'aria-expanded': visible ? 'true' : 'false',
'aria-controls': menuId
},
on: {
mousedown: this.onMousedown,
Expand All @@ -95,7 +99,8 @@ export const BNavItemDropdown = /*#__PURE__*/ Vue.extend({
class: this.menuClasses,
attrs: {
tabindex: '-1',
'aria-labelledby': toggleId
'aria-labelledby': toggleId,
id: menuId
},
on: {
keydown: this.onKeydown // Handle UP, DOWN and ESC
Expand Down
Loading

0 comments on commit 97bb97b

Please sign in to comment.