UNPKG

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
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'); } };