Skip to content
Permalink
Browse files

fix(v-b-modal): open modal using `ENTER` key on non-button elements f…

…or A11Y (#4364)
  • Loading branch information
markcarver authored and tmorehouse committed Nov 9, 2019
1 parent e7a7f97 commit 0d27d7be2677f1fdada7c91c5b9f58a216a3de4e
Showing with 88 additions and 3 deletions.
  1. +8 −3 src/directives/modal/modal.js
  2. +80 −0 src/directives/modal/modal.spec.js
@@ -10,6 +10,7 @@ import {
} from '../../utils/dom'
import { isString } from '../../utils/inspect'
import { keys } from '../../utils/object'
import KeyCodes from '../../utils/key-codes'

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

const getTriggerElement = el => {
// If root element is a dropdown item or nav item, we
// If root element is a dropdown-item or nav-item, we
// need to target the inner link or button instead
return el && matches(el, '.dropdown-menu > li, li.nav-item') ? select('a, button', el) || el : el
}
@@ -46,8 +47,12 @@ const bind = (el, binding, vnode) => {
const currentTarget = evt.currentTarget
if (!isDisabled(currentTarget)) {
const type = evt.type
const key = evt.keyCode
// Open modal only if trigger is not disabled
if (type === 'click' || (type === 'keydown' && evt.keyCode === 32)) {
if (
type === 'click' ||
(type === 'keydown' && (key === KeyCodes.ENTER || key === KeyCodes.SPACE))
) {
vnode.context.$root.$emit(EVENT_SHOW, target, currentTarget)
}
}
@@ -59,7 +64,7 @@ const bind = (el, binding, vnode) => {
eventOn(trigger, 'click', handler, EVENT_OPTS)
if (trigger.tagName !== 'BUTTON' && getAttr(trigger, 'role') === 'button') {
// If trigger isn't a button but has role button,
// we also listen for `keydown.space`
// we also listen for `keydown.space` && `keydown.enter`
eventOn(trigger, 'keydown', handler, EVENT_OPTS)
}
}
@@ -82,4 +82,84 @@ describe('v-b-modal directive', () => {

wrapper.destroy()
})

it('works on non-buttons using keydown space', async () => {
const localVue = new CreateLocalVue()
const spy = jest.fn()

const App = localVue.extend({
directives: {
bModal: VBModal
},
data() {
return {
text: 'span'
}
},
mounted() {
this.$root.$on(EVENT_SHOW, spy)
},
beforeDestroy() {
this.$root.$off(EVENT_SHOW, spy)
},
template: '<span tabindex="0" v-b-modal.test>{{ text }}</span>'
})
const wrapper = mount(App, {
localVue: localVue
})

expect(wrapper.isVueInstance()).toBe(true)
expect(wrapper.is('span')).toBe(true)
expect(spy).not.toHaveBeenCalled()
expect(wrapper.find('span').attributes('role')).toBe('button')
expect(wrapper.find('span').text()).toBe('span')

const $span = wrapper.find('span')
$span.trigger('keydown.space')
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toBeCalledWith('test', $span.element)
expect(wrapper.find('span').attributes('role')).toBe('button')

wrapper.destroy()
})

it('works on non-buttons using keydown enter', async () => {
const localVue = new CreateLocalVue()
const spy = jest.fn()

const App = localVue.extend({
directives: {
bModal: VBModal
},
data() {
return {
text: 'span'
}
},
mounted() {
this.$root.$on(EVENT_SHOW, spy)
},
beforeDestroy() {
this.$root.$off(EVENT_SHOW, spy)
},
template: '<span tabindex="0" v-b-modal.test>{{ text }}</span>'
})
const wrapper = mount(App, {
localVue: localVue
})

expect(wrapper.isVueInstance()).toBe(true)
expect(wrapper.is('span')).toBe(true)
expect(spy).not.toHaveBeenCalled()
expect(wrapper.find('span').attributes('role')).toBe('button')
expect(wrapper.find('span').text()).toBe('span')

const $span = wrapper.find('span')
$span.trigger('keydown.enter')
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toBeCalledWith('test', $span.element)
expect(wrapper.find('span').attributes('role')).toBe('button')

wrapper.destroy()
})
})

0 comments on commit 0d27d7b

Please sign in to comment.
You can’t perform that action at this time.