bootstrap-vue
Version:
BootstrapVue, with over 40 plugins and more than 80 custom components, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated WAI-ARIA accessibility markup.
107 lines (95 loc) • 3.1 kB
JavaScript
import KeyCodes from '../../utils/key-codes'
import {
eventOn,
eventOff,
getAttr,
hasAttr,
isDisabled,
matches,
select,
setAttr
} from '../../utils/dom'
import { isString } from '../../utils/inspect'
import { keys } from '../../utils/object'
// Emitted show event for modal
const EVENT_SHOW = 'bv::show::modal'
// Prop name we use to store info on root element
const HANDLER = '__bv_modal_directive__'
const EVENT_OPTS = { passive: true }
const getTarget = ({ modifiers = {}, arg, value }) => {
// Try value, then arg, otherwise pick last modifier
return isString(value) ? value : isString(arg) ? arg : keys(modifiers).reverse()[0]
}
const getTriggerElement = el => {
// 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
}
const setRole = trigger => {
// Ensure accessibility on non button elements
if (trigger && trigger.tagName !== 'BUTTON') {
// Only set a role if the trigger element doesn't have one
if (!hasAttr(trigger, 'role')) {
setAttr(trigger, 'role', 'button')
}
// Add a tabindex is not a button or link, and tabindex is not provided
if (trigger.tagName !== 'A' && !hasAttr(trigger, 'tabindex')) {
setAttr(trigger, 'tabindex', '0')
}
}
}
const bind = (el, binding, vnode) => {
const target = getTarget(binding)
const trigger = getTriggerElement(el)
if (target && trigger) {
const handler = evt => {
// `currentTarget` is the element with the listener on it
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' && (key === KeyCodes.ENTER || key === KeyCodes.SPACE))
) {
vnode.context.$root.$emit(EVENT_SHOW, target, currentTarget)
}
}
}
el[HANDLER] = handler
// If element is not a button, we add `role="button"` for accessibility
setRole(trigger)
// Listen for click events
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` && `keydown.enter`
eventOn(trigger, 'keydown', handler, EVENT_OPTS)
}
}
}
const unbind = el => {
const trigger = getTriggerElement(el)
const handler = el ? el[HANDLER] : null
if (trigger && handler) {
eventOff(trigger, 'click', handler, EVENT_OPTS)
eventOff(trigger, 'keydown', handler, EVENT_OPTS)
}
delete el[HANDLER]
}
const componentUpdated = (el, binding, vnode) => {
// We bind and rebind just in case target changes
unbind(el, binding, vnode)
bind(el, binding, vnode)
}
const updated = () => {}
/*
* Export our directive
*/
export const VBModal = {
inserted: componentUpdated,
updated,
componentUpdated,
unbind
}