Skip to content

Commit 0d27d7b

Browse files
markhalliwelltmorehouse
authored andcommitted
fix(v-b-modal): open modal using ENTER key on non-button elements for A11Y (#4364)
1 parent e7a7f97 commit 0d27d7b

File tree

2 files changed

+88
-3
lines changed

2 files changed

+88
-3
lines changed

src/directives/modal/modal.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from '../../utils/dom'
1111
import { isString } from '../../utils/inspect'
1212
import { keys } from '../../utils/object'
13+
import KeyCodes from '../../utils/key-codes'
1314

1415
// Emitted show event for modal
1516
const EVENT_SHOW = 'bv::show::modal'
@@ -25,7 +26,7 @@ const getTarget = ({ modifiers = {}, arg, value }) => {
2526
}
2627

2728
const getTriggerElement = el => {
28-
// If root element is a dropdown item or nav item, we
29+
// If root element is a dropdown-item or nav-item, we
2930
// need to target the inner link or button instead
3031
return el && matches(el, '.dropdown-menu > li, li.nav-item') ? select('a, button', el) || el : el
3132
}
@@ -46,8 +47,12 @@ const bind = (el, binding, vnode) => {
4647
const currentTarget = evt.currentTarget
4748
if (!isDisabled(currentTarget)) {
4849
const type = evt.type
50+
const key = evt.keyCode
4951
// Open modal only if trigger is not disabled
50-
if (type === 'click' || (type === 'keydown' && evt.keyCode === 32)) {
52+
if (
53+
type === 'click' ||
54+
(type === 'keydown' && (key === KeyCodes.ENTER || key === KeyCodes.SPACE))
55+
) {
5156
vnode.context.$root.$emit(EVENT_SHOW, target, currentTarget)
5257
}
5358
}
@@ -59,7 +64,7 @@ const bind = (el, binding, vnode) => {
5964
eventOn(trigger, 'click', handler, EVENT_OPTS)
6065
if (trigger.tagName !== 'BUTTON' && getAttr(trigger, 'role') === 'button') {
6166
// If trigger isn't a button but has role button,
62-
// we also listen for `keydown.space`
67+
// we also listen for `keydown.space` && `keydown.enter`
6368
eventOn(trigger, 'keydown', handler, EVENT_OPTS)
6469
}
6570
}

src/directives/modal/modal.spec.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,84 @@ describe('v-b-modal directive', () => {
8282

8383
wrapper.destroy()
8484
})
85+
86+
it('works on non-buttons using keydown space', async () => {
87+
const localVue = new CreateLocalVue()
88+
const spy = jest.fn()
89+
90+
const App = localVue.extend({
91+
directives: {
92+
bModal: VBModal
93+
},
94+
data() {
95+
return {
96+
text: 'span'
97+
}
98+
},
99+
mounted() {
100+
this.$root.$on(EVENT_SHOW, spy)
101+
},
102+
beforeDestroy() {
103+
this.$root.$off(EVENT_SHOW, spy)
104+
},
105+
template: '<span tabindex="0" v-b-modal.test>{{ text }}</span>'
106+
})
107+
const wrapper = mount(App, {
108+
localVue: localVue
109+
})
110+
111+
expect(wrapper.isVueInstance()).toBe(true)
112+
expect(wrapper.is('span')).toBe(true)
113+
expect(spy).not.toHaveBeenCalled()
114+
expect(wrapper.find('span').attributes('role')).toBe('button')
115+
expect(wrapper.find('span').text()).toBe('span')
116+
117+
const $span = wrapper.find('span')
118+
$span.trigger('keydown.space')
119+
expect(spy).toHaveBeenCalledTimes(1)
120+
expect(spy).toBeCalledWith('test', $span.element)
121+
expect(wrapper.find('span').attributes('role')).toBe('button')
122+
123+
wrapper.destroy()
124+
})
125+
126+
it('works on non-buttons using keydown enter', async () => {
127+
const localVue = new CreateLocalVue()
128+
const spy = jest.fn()
129+
130+
const App = localVue.extend({
131+
directives: {
132+
bModal: VBModal
133+
},
134+
data() {
135+
return {
136+
text: 'span'
137+
}
138+
},
139+
mounted() {
140+
this.$root.$on(EVENT_SHOW, spy)
141+
},
142+
beforeDestroy() {
143+
this.$root.$off(EVENT_SHOW, spy)
144+
},
145+
template: '<span tabindex="0" v-b-modal.test>{{ text }}</span>'
146+
})
147+
const wrapper = mount(App, {
148+
localVue: localVue
149+
})
150+
151+
expect(wrapper.isVueInstance()).toBe(true)
152+
expect(wrapper.is('span')).toBe(true)
153+
expect(spy).not.toHaveBeenCalled()
154+
expect(wrapper.find('span').attributes('role')).toBe('button')
155+
expect(wrapper.find('span').text()).toBe('span')
156+
157+
const $span = wrapper.find('span')
158+
$span.trigger('keydown.enter')
159+
expect(spy).toHaveBeenCalledTimes(1)
160+
expect(spy).toBeCalledWith('test', $span.element)
161+
expect(wrapper.find('span').attributes('role')).toBe('button')
162+
163+
wrapper.destroy()
164+
})
85165
})

0 commit comments

Comments
 (0)