bootstrap-vue
Version:
BootstrapVue, with over 40 plugins and more than 80 custom components, custom directives, and over 300 icons, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated WAI-AR
149 lines (110 loc) • 5.21 kB
JavaScript
import looseEqual from '../../utils/loose-equal';
import { addClass, hasAttr, removeAttr, removeClass, setAttr } from '../../utils/dom';
import { isBrowser } from '../../utils/env';
import { bindTargets, getTargets, unbindTargets } from '../../utils/target'; // Target listen types
var listenTypes = {
click: true
}; // Property key for handler storage
var BV_TOGGLE = '__BV_toggle__';
var BV_TOGGLE_STATE = '__BV_toggle_STATE__';
var BV_TOGGLE_CONTROLS = '__BV_toggle_CONTROLS__';
var BV_TOGGLE_TARGETS = '__BV_toggle_TARGETS__'; // Emitted control event for collapse (emitted to collapse)
var EVENT_TOGGLE = 'bv::toggle::collapse'; // Listen to event for toggle state update (emitted by collapse)
var EVENT_STATE = 'bv::collapse::state'; // Private event emitted on $root to ensure the toggle state is always synced.
// Gets emitted even if the state of b-collapse has not changed.
// This event is NOT to be documented as people should not be using it.
var EVENT_STATE_SYNC = 'bv::collapse::sync::state'; // Private event we send to collapse to request state update sync event
var EVENT_STATE_REQUEST = 'bv::request::collapse::state'; // Reset and remove a property from the provided element
var resetProp = function resetProp(el, prop) {
el[prop] = null;
delete el[prop];
}; // Handle targets update
var handleTargets = function handleTargets(_ref) {
var targets = _ref.targets,
vnode = _ref.vnode;
targets.forEach(function (target) {
vnode.context.$root.$emit(EVENT_TOGGLE, target);
});
}; // Handle directive updates
/* istanbul ignore next: not easy to test */
var handleUpdate = function handleUpdate(el, binding, vnode) {
if (!isBrowser) {
return;
}
if (!looseEqual(getTargets(binding), el[BV_TOGGLE_TARGETS])) {
// Targets have changed, so update accordingly
unbindTargets(vnode, binding, listenTypes);
var targets = bindTargets(vnode, binding, listenTypes, handleTargets); // Update targets array to element
el[BV_TOGGLE_TARGETS] = targets; // Add aria attributes to element
el[BV_TOGGLE_CONTROLS] = targets.join(' '); // ensure aria-controls is up to date
setAttr(el, 'aria-controls', el[BV_TOGGLE_CONTROLS]); // Request a state update from targets so that we can ensure
// expanded state is correct
targets.forEach(function (target) {
vnode.context.$root.$emit(EVENT_STATE_REQUEST, target);
});
} // Ensure the collapse class and aria-* attributes persist
// after element is updated (either by parent re-rendering
// or changes to this element or its contents
if (el[BV_TOGGLE_STATE] === true) {
addClass(el, 'collapsed');
setAttr(el, 'aria-expanded', 'true');
} else if (el[BV_TOGGLE_STATE] === false) {
removeClass(el, 'collapsed');
setAttr(el, 'aria-expanded', 'false');
}
setAttr(el, 'aria-controls', el[BV_TOGGLE_CONTROLS]);
};
/*
* Export our directive
*/
export var VBToggle = {
bind: function bind(el, binding, vnode) {
var targets = bindTargets(vnode, binding, listenTypes, handleTargets);
if (isBrowser && vnode.context && targets.length > 0) {
// Add targets array to element
el[BV_TOGGLE_TARGETS] = targets; // Add aria attributes to element
el[BV_TOGGLE_CONTROLS] = targets.join(' '); // State is initially collapsed until we receive a state event
el[BV_TOGGLE_STATE] = false;
setAttr(el, 'aria-controls', el[BV_TOGGLE_CONTROLS]);
setAttr(el, 'aria-expanded', 'false'); // If element is not a button, we add `role="button"` for accessibility
if (el.tagName !== 'BUTTON' && !hasAttr(el, 'role')) {
setAttr(el, 'role', 'button');
} // Toggle state handler
var toggleDirectiveHandler = function toggleDirectiveHandler(id, state) {
var targets = el[BV_TOGGLE_TARGETS] || [];
if (targets.indexOf(id) !== -1) {
// Set aria-expanded state
setAttr(el, 'aria-expanded', state ? 'true' : 'false'); // Set/Clear 'collapsed' class state
el[BV_TOGGLE_STATE] = state;
if (state) {
removeClass(el, 'collapsed');
} else {
addClass(el, 'collapsed');
}
}
}; // Store the toggle handler on the element
el[BV_TOGGLE] = toggleDirectiveHandler; // Listen for toggle state changes (public)
vnode.context.$root.$on(EVENT_STATE, el[BV_TOGGLE]); // Listen for toggle state sync (private)
vnode.context.$root.$on(EVENT_STATE_SYNC, el[BV_TOGGLE]);
}
},
componentUpdated: handleUpdate,
updated: handleUpdate,
unbind: function unbind(el, binding, vnode)
/* istanbul ignore next */
{
unbindTargets(vnode, binding, listenTypes); // Remove our $root listener
if (el[BV_TOGGLE]) {
vnode.context.$root.$off(EVENT_STATE, el[BV_TOGGLE]);
vnode.context.$root.$off(EVENT_STATE_SYNC, el[BV_TOGGLE]);
} // Reset custom props
resetProp(el, BV_TOGGLE);
resetProp(el, BV_TOGGLE_STATE);
resetProp(el, BV_TOGGLE_CONTROLS);
resetProp(el, BV_TOGGLE_TARGETS); // Reset classes/attrs
removeClass(el, 'collapsed');
removeAttr(el, 'aria-expanded');
removeAttr(el, 'aria-controls');
removeAttr(el, 'role');
}
};