From 0d184bfae1ac58b0e74b17b0357a27888a36b3b2 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 00:01:27 -0300 Subject: [PATCH 01/91] feat(v-b-toggle): support target as argument, and value as array of targets --- src/utils/target.js | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/utils/target.js b/src/utils/target.js index a25bff09fe8..80d2265b25f 100644 --- a/src/utils/target.js +++ b/src/utils/target.js @@ -1,21 +1,29 @@ -import { keys } from './object' import { eventOn, eventOff } from './events' +import { isArray, isString } from './inspect' +import { keys } from './object' const allListenTypes = { hover: true, click: true, focus: true } const BVBoundListeners = '__BV_boundEventListeners__' -const getTargets = binding => { - const targets = keys(binding.modifiers || {}).filter(t => !allListenTypes[t]) +export const getTargets = ({ modifiers, arg, value }) => { + const targets = keys(modifiers || {}).filter(t => !allListenTypes[t]) - if (binding.value) { - targets.push(binding.value) + if (arg && isString(arg)) { + targets.push(arg) } - return targets + if (value && isString(value)) { + targets.push(value) + } else if (isArray(value)) { + value.forEach(t => t && isString(t) && targets.push(t)) + } + + // return only unique targets + return targets.filter((target, index, array) => array.indexOf(target) === index) } -const bindTargets = (vnode, binding, listenTypes, fn) => { +export const bindTargets = (vnode, binding, listenTypes, fn) => { const targets = getTargets(binding) const listener = () => { @@ -26,8 +34,7 @@ const bindTargets = (vnode, binding, listenTypes, fn) => { if (listenTypes[type] || binding.modifiers[type]) { eventOn(vnode.elm, type, listener) const boundListeners = vnode.elm[BVBoundListeners] || {} - boundListeners[type] = boundListeners[type] || [] - boundListeners[type].push(listener) + boundListeners[type] = (boundListeners[type] || []).push(listener) vnode.elm[BVBoundListeners] = boundListeners } }) @@ -36,7 +43,7 @@ const bindTargets = (vnode, binding, listenTypes, fn) => { return targets } -const unbindTargets = (vnode, binding, listenTypes) => { +export const unbindTargets = (vnode, binding, listenTypes) => { keys(allListenTypes).forEach(type => { if (listenTypes[type] || binding.modifiers[type]) { const boundListeners = vnode.elm[BVBoundListeners] && vnode.elm[BVBoundListeners][type] @@ -48,6 +55,4 @@ const unbindTargets = (vnode, binding, listenTypes) => { }) } -export { bindTargets, unbindTargets, getTargets } - export default bindTargets From 782b2140338a451dd4a11b7a185bb40b21844c38 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 00:12:26 -0300 Subject: [PATCH 02/91] Update target.js --- src/utils/target.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/utils/target.js b/src/utils/target.js index 80d2265b25f..bdd6af5ab18 100644 --- a/src/utils/target.js +++ b/src/utils/target.js @@ -9,18 +9,18 @@ const BVBoundListeners = '__BV_boundEventListeners__' export const getTargets = ({ modifiers, arg, value }) => { const targets = keys(modifiers || {}).filter(t => !allListenTypes[t]) - if (arg && isString(arg)) { + if (isString(arg)) { targets.push(arg) } - if (value && isString(value)) { + if (isString(value)) { targets.push(value) } else if (isArray(value)) { value.forEach(t => t && isString(t) && targets.push(t)) } // return only unique targets - return targets.filter((target, index, array) => array.indexOf(target) === index) + return targets.filter((target, index, arr) => arr.indexOf(target) === index) } export const bindTargets = (vnode, binding, listenTypes, fn) => { @@ -34,7 +34,8 @@ export const bindTargets = (vnode, binding, listenTypes, fn) => { if (listenTypes[type] || binding.modifiers[type]) { eventOn(vnode.elm, type, listener) const boundListeners = vnode.elm[BVBoundListeners] || {} - boundListeners[type] = (boundListeners[type] || []).push(listener) + boundListeners[type] = boundListeners[type] || [] + boundListeners[type].push(listener) vnode.elm[BVBoundListeners] = boundListeners } }) From 11efa1aca754656a060ea81fc00566fe327a19ca Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 00:38:50 -0300 Subject: [PATCH 03/91] Update toggle.spec.js --- src/directives/toggle/toggle.spec.js | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/directives/toggle/toggle.spec.js b/src/directives/toggle/toggle.spec.js index 3579f895aca..24feafc4e85 100644 --- a/src/directives/toggle/toggle.spec.js +++ b/src/directives/toggle/toggle.spec.js @@ -50,6 +50,7 @@ describe('v-b-toggle directive', () => { wrapper.destroy() }) + it('works on passing ID as directive value', async () => { const localVue = new CreateLocalVue() const spy = jest.fn() @@ -89,6 +90,45 @@ describe('v-b-toggle directive', () => { wrapper.destroy() }) + it('works on passing ID as directive argument', async () => { + const localVue = new CreateLocalVue() + const spy = jest.fn() + + const App = localVue.extend({ + directives: { + bToggle: VBToggle + }, + mounted() { + this.$root.$on(EVENT_TOGGLE, spy) + }, + beforeDestroy() { + this.$root.$off(EVENT_TOGGLE, spy) + }, + template: `` + }) + + const wrapper = mount(App, { + localVue + }) + + expect(wrapper.vm).toBeDefined() + expect(wrapper.element.tagName).toBe('BUTTON') + expect(wrapper.find('button').attributes('aria-controls')).toBe('test') + expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') + expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(spy).not.toHaveBeenCalled() + + const $button = wrapper.find('button') + await $button.trigger('click') + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toBeCalledWith('test') + expect(wrapper.find('button').attributes('aria-controls')).toBe('test') + expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') + expect(wrapper.find('button').classes()).not.toContain('collapsed') + + wrapper.destroy() + }) + it('works on non-buttons', async () => { const localVue = new CreateLocalVue() const spy = jest.fn() From a53dbb984cdae8513f0c1199760a257167b93f04 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 00:42:33 -0300 Subject: [PATCH 04/91] Update target.js --- src/utils/target.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/utils/target.js b/src/utils/target.js index bdd6af5ab18..9d8a1e1eb8c 100644 --- a/src/utils/target.js +++ b/src/utils/target.js @@ -1,3 +1,4 @@ +import { concat } from './array' import { eventOn, eventOff } from './events' import { isArray, isString } from './inspect' import { keys } from './object' @@ -13,14 +14,11 @@ export const getTargets = ({ modifiers, arg, value }) => { targets.push(arg) } - if (isString(value)) { - targets.push(value) - } else if (isArray(value)) { - value.forEach(t => t && isString(t) && targets.push(t)) - } + // Support pasing a single string ID or an array of string IDs + concat(value).forEach(t => t && isString(t) && targets.push(t)) - // return only unique targets - return targets.filter((target, index, arr) => arr.indexOf(target) === index) + // Return only unique targets + return targets.filter((t, index, arr) => arr.indexOf(t) === index) } export const bindTargets = (vnode, binding, listenTypes, fn) => { From 2d4923423eceb877ca1a9886c5f44917fa961541 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 00:52:43 -0300 Subject: [PATCH 05/91] Update target.js --- src/utils/target.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/utils/target.js b/src/utils/target.js index 9d8a1e1eb8c..4ce1ec3feec 100644 --- a/src/utils/target.js +++ b/src/utils/target.js @@ -1,6 +1,6 @@ import { concat } from './array' import { eventOn, eventOff } from './events' -import { isArray, isString } from './inspect' +import { isString } from './inspect' import { keys } from './object' const allListenTypes = { hover: true, click: true, focus: true } @@ -10,15 +10,12 @@ const BVBoundListeners = '__BV_boundEventListeners__' export const getTargets = ({ modifiers, arg, value }) => { const targets = keys(modifiers || {}).filter(t => !allListenTypes[t]) - if (isString(arg)) { - targets.push(arg) - } - - // Support pasing a single string ID or an array of string IDs - concat(value).forEach(t => t && isString(t) && targets.push(t)) + // Add ID from `arg` (if provided), and support value + // as a single string ID or an array of string IDs + concat(arg, value).forEach(t => isString(t) && targets.push(t)) // Return only unique targets - return targets.filter((t, index, arr) => arr.indexOf(t) === index) + return targets.filter((t, index, arr) => t && arr.indexOf(t) === index) } export const bindTargets = (vnode, binding, listenTypes, fn) => { From d7409eb4220272e285a4c78f0ab744447e8682ef Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 01:17:30 -0300 Subject: [PATCH 06/91] Only run handler if default not prevented --- src/utils/target.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/target.js b/src/utils/target.js index 4ce1ec3feec..d0c4a0429cd 100644 --- a/src/utils/target.js +++ b/src/utils/target.js @@ -21,8 +21,10 @@ export const getTargets = ({ modifiers, arg, value }) => { export const bindTargets = (vnode, binding, listenTypes, fn) => { const targets = getTargets(binding) - const listener = () => { - fn({ targets, vnode }) + const listener = evt => { + if (!evt.defaultPrevented) { + fn({ targets, vnode, evt }) + } } keys(allListenTypes).forEach(type => { From 4557148764137a85596513f93209f5248c186a55 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 01:32:18 -0300 Subject: [PATCH 07/91] Update navbar-toggle.js --- src/components/navbar/navbar-toggle.js | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/components/navbar/navbar-toggle.js b/src/components/navbar/navbar-toggle.js index 98993ea0d82..f8658a2288d 100644 --- a/src/components/navbar/navbar-toggle.js +++ b/src/components/navbar/navbar-toggle.js @@ -3,13 +3,7 @@ import { getComponentConfig } from '../../utils/config' import { toString } from '../../utils/string' import listenOnRootMixin from '../../mixins/listen-on-root' import normalizeSlotMixin from '../../mixins/normalize-slot' -import { EVENT_TOGGLE, EVENT_STATE, EVENT_STATE_SYNC } from '../../directives/toggle/toggle' - -// TODO: -// Switch to using `VBToggle` directive, will reduce code footprint -// Although the `click` event will no longer be cancellable -// Instead add `disabled` prop, and have `VBToggle` check element -// disabled state +import { VBToggle, EVENT_STATE, EVENT_STATE_SYNC } from '../../directives/toggle/toggle' // --- Constants --- @@ -21,6 +15,7 @@ const CLASS_NAME = 'navbar-toggler' export const BNavbarToggle = /*#__PURE__*/ Vue.extend({ name: NAME, mixins: [listenOnRootMixin, normalizeSlotMixin], + directives: { BToggle: VBToggle }, props: { label: { type: String, @@ -42,12 +37,12 @@ export const BNavbarToggle = /*#__PURE__*/ Vue.extend({ }, methods: { onClick(evt) { + // Emit courtesy `click` event this.$emit('click', evt) - if (!evt.defaultPrevented) { - this.emitOnRoot(EVENT_TOGGLE, this.target) - } }, handleStateEvt(id, state) { + // We listen for state events so that we can pass the + // boolean expanded state to the default scoped slot if (id === this.target) { this.toggleState = state } @@ -59,12 +54,8 @@ export const BNavbarToggle = /*#__PURE__*/ Vue.extend({ 'button', { staticClass: CLASS_NAME, - attrs: { - type: 'button', - 'aria-label': this.label, - 'aria-controls': this.target, - 'aria-expanded': toString(expanded) - }, + directives: [{ name: 'BToggle', value: this.target }], + attrs: { type: 'button', 'aria-label': this.label }, on: { click: this.onClick } }, [ From 1a09ab4a81b95c79e76f10706bf4be912f2149b2 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 01:36:53 -0300 Subject: [PATCH 08/91] Update navbar-toggle.js --- src/components/navbar/navbar-toggle.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/navbar/navbar-toggle.js b/src/components/navbar/navbar-toggle.js index f8658a2288d..a34623f3609 100644 --- a/src/components/navbar/navbar-toggle.js +++ b/src/components/navbar/navbar-toggle.js @@ -1,6 +1,5 @@ import Vue from '../../utils/vue' import { getComponentConfig } from '../../utils/config' -import { toString } from '../../utils/string' import listenOnRootMixin from '../../mixins/listen-on-root' import normalizeSlotMixin from '../../mixins/normalize-slot' import { VBToggle, EVENT_STATE, EVENT_STATE_SYNC } from '../../directives/toggle/toggle' @@ -14,8 +13,8 @@ const CLASS_NAME = 'navbar-toggler' // @vue/component export const BNavbarToggle = /*#__PURE__*/ Vue.extend({ name: NAME, - mixins: [listenOnRootMixin, normalizeSlotMixin], directives: { BToggle: VBToggle }, + mixins: [listenOnRootMixin, normalizeSlotMixin], props: { label: { type: String, From 12d5e94346567527747c8f4eb0d8dc9bf8b3f685 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 01:53:15 -0300 Subject: [PATCH 09/91] Update package.json --- src/directives/toggle/package.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/directives/toggle/package.json b/src/directives/toggle/package.json index 6bc577fb635..af9318ca80a 100644 --- a/src/directives/toggle/package.json +++ b/src/directives/toggle/package.json @@ -6,6 +6,12 @@ "description": "A light-weight directive for toggling visibility state for collapses and sidebars by ID. It automatically handles the accessibility attributes on the trigger element.", "directive": "VBToggle", "expression": "String", + "arg": { + "version": "2.14.0", + "pattern": "[a-zA-Z][a-zA-Z0-9_\\-]*", + "description": "ID of component to toggle", + "required": false + }, "modifiers": [ { "name": "{componentId}", From 14f1d6e52df9da2a01c582098d4b29969c6e0c3b Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 01:55:52 -0300 Subject: [PATCH 10/91] Update package.json --- src/components/modal/package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/modal/package.json b/src/components/modal/package.json index 54f02726bff..64e0d58f512 100644 --- a/src/components/modal/package.json +++ b/src/components/modal/package.json @@ -12,6 +12,11 @@ "directive": "VBModal", "description": "Directive for opening a modal by ID", "expression": "String", + "arg": { + "pattern": "[a-zA-Z][a-zA-Z0-9_\\-]*", + "description": "Modal ID to open", + "required": false + }, "modifiers": [ { "name": "{modalId}", From e9a905d7894ac0cfc129c21055e571fa4a82c656 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 02:12:26 -0300 Subject: [PATCH 11/91] Update package.json --- src/directives/toggle/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/directives/toggle/package.json b/src/directives/toggle/package.json index af9318ca80a..44c6568268a 100644 --- a/src/directives/toggle/package.json +++ b/src/directives/toggle/package.json @@ -5,7 +5,10 @@ "title": "Toggle", "description": "A light-weight directive for toggling visibility state for collapses and sidebars by ID. It automatically handles the accessibility attributes on the trigger element.", "directive": "VBToggle", - "expression": "String", + "expression": [ + "String", + "Array" + ], "arg": { "version": "2.14.0", "pattern": "[a-zA-Z][a-zA-Z0-9_\\-]*", From 72a0a2c70384f792e6f3852951f42b1ed794905d Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 19:15:40 -0300 Subject: [PATCH 12/91] Update target.js --- src/utils/target.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/utils/target.js b/src/utils/target.js index d0c4a0429cd..900656278fd 100644 --- a/src/utils/target.js +++ b/src/utils/target.js @@ -7,14 +7,19 @@ const allListenTypes = { hover: true, click: true, focus: true } const BVBoundListeners = '__BV_boundEventListeners__' +const RX_SPLIT_SEPARATOR = /[,;\s]+/ + export const getTargets = ({ modifiers, arg, value }) => { + value = isString(value) ? value.split(RX_SPLIT_SEPARATOR) : value + const targets = keys(modifiers || {}).filter(t => !allListenTypes[t]) // Add ID from `arg` (if provided), and support value // as a single string ID or an array of string IDs + // If `value` is not an array or string, then it gets filtered out concat(arg, value).forEach(t => isString(t) && targets.push(t)) - // Return only unique targets + // Return only unique and truthy target IDs return targets.filter((t, index, arr) => t && arr.indexOf(t) === index) } From f966d07e5f47c64f08e4f760b3b6f96d9306658c Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 19:18:42 -0300 Subject: [PATCH 13/91] Update target.js --- src/utils/target.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/target.js b/src/utils/target.js index 900656278fd..7113c6b8efa 100644 --- a/src/utils/target.js +++ b/src/utils/target.js @@ -7,7 +7,7 @@ const allListenTypes = { hover: true, click: true, focus: true } const BVBoundListeners = '__BV_boundEventListeners__' -const RX_SPLIT_SEPARATOR = /[,;\s]+/ +const RX_SPLIT_SEPARATOR = /\s+/ export const getTargets = ({ modifiers, arg, value }) => { value = isString(value) ? value.split(RX_SPLIT_SEPARATOR) : value From 1354140fd3053cc9785a709e73e46d449de690ed Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 19:23:04 -0300 Subject: [PATCH 14/91] Update target.js --- src/utils/target.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/target.js b/src/utils/target.js index 7113c6b8efa..2eec08a5967 100644 --- a/src/utils/target.js +++ b/src/utils/target.js @@ -10,10 +10,12 @@ const BVBoundListeners = '__BV_boundEventListeners__' const RX_SPLIT_SEPARATOR = /\s+/ export const getTargets = ({ modifiers, arg, value }) => { - value = isString(value) ? value.split(RX_SPLIT_SEPARATOR) : value - + // Any modifiers are condisered target IDs const targets = keys(modifiers || {}).filter(t => !allListenTypes[t]) + // If value is a string, split out individual targets (if space delimited) + value = isString(value) ? value.split(RX_SPLIT_SEPARATOR) : value + // Add ID from `arg` (if provided), and support value // as a single string ID or an array of string IDs // If `value` is not an array or string, then it gets filtered out From 4a62f948828332483dfcb1080d0bfcc6200f039f Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 19:36:21 -0300 Subject: [PATCH 15/91] add class `not-collapsed` to trigger element when not collapsed --- src/directives/toggle/toggle.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/directives/toggle/toggle.js b/src/directives/toggle/toggle.js index 490371f369c..88334d46220 100644 --- a/src/directives/toggle/toggle.js +++ b/src/directives/toggle/toggle.js @@ -66,10 +66,12 @@ const handleUpdate = (el, binding, vnode) => { // after element is updated (either by parent re-rendering // or changes to this element or its contents if (el[BV_TOGGLE_STATE] === true) { + removeClass(el, 'not-collapsed') addClass(el, 'collapsed') setAttr(el, 'aria-expanded', 'true') } else if (el[BV_TOGGLE_STATE] === false) { removeClass(el, 'collapsed') + addClass(el, 'not-collapsed') setAttr(el, 'aria-expanded', 'false') } setAttr(el, 'aria-controls', el[BV_TOGGLE_CONTROLS]) @@ -105,7 +107,9 @@ export const VBToggle = { el[BV_TOGGLE_STATE] = state if (state) { removeClass(el, 'collapsed') + addClass(el, 'not-collapsed') } else { + removeClass(el, 'not-collapsed') addClass(el, 'collapsed') } } @@ -136,6 +140,7 @@ export const VBToggle = { resetProp(el, BV_TOGGLE_TARGETS) // Reset classes/attrs removeClass(el, 'collapsed') + removeClass(el, 'not-collapsed') removeAttr(el, 'aria-expanded') removeAttr(el, 'aria-controls') removeAttr(el, 'role') From bde92c0a927aaee87e832c994bce0d63fb15d256 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 19:48:23 -0300 Subject: [PATCH 16/91] Update toggle.js --- src/directives/toggle/toggle.js | 44 +++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/directives/toggle/toggle.js b/src/directives/toggle/toggle.js index 88334d46220..956235240ee 100644 --- a/src/directives/toggle/toggle.js +++ b/src/directives/toggle/toggle.js @@ -1,8 +1,15 @@ import looseEqual from '../../utils/loose-equal' +import { arrayIncludes } from '../../utils/array' import { addClass, hasAttr, removeAttr, removeClass, setAttr } from '../../utils/dom' import { isBrowser } from '../../utils/env' import { bindTargets, getTargets, unbindTargets } from '../../utils/target' +// --- Constants --- + +// Classes to apply to trigger element +const CLASS_VBTOGGLE_COLLAPSED = 'collapsed' +const CLASS_VBTOGGLE_NOT_COLLAPSED = 'not-collapsed' + // Target listen types const listenTypes = { click: true } @@ -25,6 +32,20 @@ export const EVENT_STATE_SYNC = 'bv::collapse::sync::state' // Private event we send to collapse to request state update sync event export const EVENT_STATE_REQUEST = 'bv::request::collapse::state' +// --- Helper methods --- + +const setTriggerState = (el, state) => { + if (state === true) { + removeClass(el, CLASS_VBTOGGLE_NOT_COLLAPSED) + addClass(el, CLASS_VBTOGGLE_COLLAPSED) + setAttr(el, 'aria-expanded', 'true') + } else if (state === false) { + removeClass(el, CLASS_VBTOGGLE_COLLAPSED) + addClass(el, CLASS_VBTOGGLE_NOT_COLLAPSED) + setAttr(el, 'aria-expanded', 'false') + } +} + // Reset and remove a property from the provided element const resetProp = (el, prop) => { el[prop] = null @@ -65,15 +86,7 @@ const handleUpdate = (el, binding, vnode) => { // 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) { - removeClass(el, 'not-collapsed') - addClass(el, 'collapsed') - setAttr(el, 'aria-expanded', 'true') - } else if (el[BV_TOGGLE_STATE] === false) { - removeClass(el, 'collapsed') - addClass(el, 'not-collapsed') - setAttr(el, 'aria-expanded', 'false') - } + setTriggerState(el, el[BV_TOGGLE_STATE]) setAttr(el, 'aria-controls', el[BV_TOGGLE_CONTROLS]) } @@ -100,18 +113,11 @@ export const VBToggle = { // Toggle state handler const toggleDirectiveHandler = (id, state) => { const targets = el[BV_TOGGLE_TARGETS] || [] - if (targets.indexOf(id) !== -1) { - // Set aria-expanded state - setAttr(el, 'aria-expanded', state ? 'true' : 'false') + if (arrayIncludes(targets, id)) { // Set/Clear 'collapsed' class state el[BV_TOGGLE_STATE] = state - if (state) { - removeClass(el, 'collapsed') - addClass(el, 'not-collapsed') - } else { - removeClass(el, 'not-collapsed') - addClass(el, 'collapsed') - } + // Set aria-expanded and class state on trigger element + setTriggerState(el, state) } } From 35529222f34150bfe3b9be3c32ac609de2801eb4 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 19:54:34 -0300 Subject: [PATCH 17/91] Update toggle.js --- src/directives/toggle/toggle.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/directives/toggle/toggle.js b/src/directives/toggle/toggle.js index 956235240ee..9e736e9db1c 100644 --- a/src/directives/toggle/toggle.js +++ b/src/directives/toggle/toggle.js @@ -35,11 +35,11 @@ export const EVENT_STATE_REQUEST = 'bv::request::collapse::state' // --- Helper methods --- const setTriggerState = (el, state) => { - if (state === true) { + if (state) { removeClass(el, CLASS_VBTOGGLE_NOT_COLLAPSED) addClass(el, CLASS_VBTOGGLE_COLLAPSED) setAttr(el, 'aria-expanded', 'true') - } else if (state === false) { + } else { removeClass(el, CLASS_VBTOGGLE_COLLAPSED) addClass(el, CLASS_VBTOGGLE_NOT_COLLAPSED) setAttr(el, 'aria-expanded', 'false') From 28447ed134b66a928d99c8f912eef73cb75d4ecb Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 20:02:28 -0300 Subject: [PATCH 18/91] Update toggle.spec.js --- src/directives/toggle/toggle.spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/directives/toggle/toggle.spec.js b/src/directives/toggle/toggle.spec.js index 24feafc4e85..24ec8d22c30 100644 --- a/src/directives/toggle/toggle.spec.js +++ b/src/directives/toggle/toggle.spec.js @@ -210,6 +210,7 @@ describe('v-b-toggle directive', () => { $root.$emit(EVENT_STATE, 'test', true) await waitNT(wrapper.vm) + await waitNT(wrapper.vm) expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('true') @@ -217,6 +218,7 @@ describe('v-b-toggle directive', () => { $root.$emit(EVENT_STATE, 'test', false) await waitNT(wrapper.vm) + await waitNT(wrapper.vm) expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') @@ -249,6 +251,7 @@ describe('v-b-toggle directive', () => { $root.$emit(EVENT_STATE_SYNC, 'test', true) await waitNT(wrapper.vm) + await waitNT(wrapper.vm) expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('true') @@ -256,6 +259,7 @@ describe('v-b-toggle directive', () => { $root.$emit(EVENT_STATE_SYNC, 'test', false) await waitNT(wrapper.vm) + await waitNT(wrapper.vm) expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') From 6e3c61fe82c04dc316963950a26bc364d3152e4d Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 20:16:25 -0300 Subject: [PATCH 19/91] Update toggle.js --- src/directives/toggle/toggle.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/directives/toggle/toggle.js b/src/directives/toggle/toggle.js index 9e736e9db1c..37e94a465ab 100644 --- a/src/directives/toggle/toggle.js +++ b/src/directives/toggle/toggle.js @@ -36,12 +36,12 @@ export const EVENT_STATE_REQUEST = 'bv::request::collapse::state' const setTriggerState = (el, state) => { if (state) { - removeClass(el, CLASS_VBTOGGLE_NOT_COLLAPSED) - addClass(el, CLASS_VBTOGGLE_COLLAPSED) - setAttr(el, 'aria-expanded', 'true') - } else { removeClass(el, CLASS_VBTOGGLE_COLLAPSED) addClass(el, CLASS_VBTOGGLE_NOT_COLLAPSED) + setAttr(el, 'aria-expanded', 'true') + } else { + removeClass(el, CLASS_VBTOGGLE_NOT_COLLAPSED) + addClass(el, CLASS_VBTOGGLE_COLLAPSED) setAttr(el, 'aria-expanded', 'false') } } From a7a0ee78651a4a2e0021db2f391256c9493dd042 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 20:17:14 -0300 Subject: [PATCH 20/91] Update toggle.spec.js --- src/directives/toggle/toggle.spec.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/directives/toggle/toggle.spec.js b/src/directives/toggle/toggle.spec.js index 24ec8d22c30..24feafc4e85 100644 --- a/src/directives/toggle/toggle.spec.js +++ b/src/directives/toggle/toggle.spec.js @@ -210,7 +210,6 @@ describe('v-b-toggle directive', () => { $root.$emit(EVENT_STATE, 'test', true) await waitNT(wrapper.vm) - await waitNT(wrapper.vm) expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('true') @@ -218,7 +217,6 @@ describe('v-b-toggle directive', () => { $root.$emit(EVENT_STATE, 'test', false) await waitNT(wrapper.vm) - await waitNT(wrapper.vm) expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') @@ -251,7 +249,6 @@ describe('v-b-toggle directive', () => { $root.$emit(EVENT_STATE_SYNC, 'test', true) await waitNT(wrapper.vm) - await waitNT(wrapper.vm) expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('true') @@ -259,7 +256,6 @@ describe('v-b-toggle directive', () => { $root.$emit(EVENT_STATE_SYNC, 'test', false) await waitNT(wrapper.vm) - await waitNT(wrapper.vm) expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') From 43c383af5e6001aba549629c69227b553cd9086f Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 20:34:19 -0300 Subject: [PATCH 21/91] Update toggle.spec.js --- src/directives/toggle/toggle.spec.js | 35 +++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/directives/toggle/toggle.spec.js b/src/directives/toggle/toggle.spec.js index 24feafc4e85..b0d3304e6ca 100644 --- a/src/directives/toggle/toggle.spec.js +++ b/src/directives/toggle/toggle.spec.js @@ -38,15 +38,19 @@ describe('v-b-toggle directive', () => { expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(wrapper.find('button').classes()).toContain('not-collapsed') expect(spy).not.toHaveBeenCalled() const $button = wrapper.find('button') await $button.trigger('click') expect(spy).toHaveBeenCalledTimes(1) expect(spy).toBeCalledWith('test') + // Since there is no target collapse to respond with the + // current state, the classes and attrs remain the same expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(wrapper.find('button').classes()).toContain('not-collapsed') wrapper.destroy() }) @@ -77,15 +81,19 @@ describe('v-b-toggle directive', () => { expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(wrapper.find('button').classes()).toContain('not-collapsed') expect(spy).not.toHaveBeenCalled() const $button = wrapper.find('button') await $button.trigger('click') expect(spy).toHaveBeenCalledTimes(1) expect(spy).toBeCalledWith('test') + // Since there is no target collapse to respond with the + // current state, the classes and attrs remain the same expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(wrapper.find('button').classes()).toContain('not-collapsed') wrapper.destroy() }) @@ -116,15 +124,19 @@ describe('v-b-toggle directive', () => { expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(wrapper.find('button').classes()).toContain('not-collapsed') expect(spy).not.toHaveBeenCalled() const $button = wrapper.find('button') await $button.trigger('click') expect(spy).toHaveBeenCalledTimes(1) expect(spy).toBeCalledWith('test') + // Since there is no target collapse to respond with the + // current state, the classes and attrs remain the same expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(wrapper.find('button').classes()).toContain('not-collapsed') wrapper.destroy() }) @@ -162,6 +174,7 @@ describe('v-b-toggle directive', () => { expect(wrapper.find('span').attributes('aria-controls')).toBe('test') expect(wrapper.find('span').attributes('aria-expanded')).toBe('false') expect(wrapper.find('span').classes()).not.toContain('collapsed') + expect(wrapper.find('span').classes()).toContain('not-collapsed') expect(wrapper.find('span').text()).toBe('span') const $span = wrapper.find('span') @@ -169,9 +182,12 @@ describe('v-b-toggle directive', () => { expect(spy).toHaveBeenCalledTimes(1) expect(spy).toBeCalledWith('test') expect(wrapper.find('span').attributes('role')).toBe('button') + // Since there is no target collapse to respond with the + // current state, the classes and attrs remain the same expect(wrapper.find('span').attributes('aria-controls')).toBe('test') expect(wrapper.find('span').attributes('aria-expanded')).toBe('false') expect(wrapper.find('span').classes()).not.toContain('collapsed') + expect(wrapper.find('span').classes()).toContain('not-collapsed') // Test updating component. should maintain role attribute await wrapper.setData({ @@ -179,9 +195,12 @@ describe('v-b-toggle directive', () => { }) expect(wrapper.find('span').text()).toBe('foobar') expect(wrapper.find('span').attributes('role')).toBe('button') + // Since there is no target collapse to respond with the + // current state, the classes and attrs remain the same expect(wrapper.find('span').attributes('aria-controls')).toBe('test') expect(wrapper.find('span').attributes('aria-expanded')).toBe('false') expect(wrapper.find('span').classes()).not.toContain('collapsed') + expect(wrapper.find('span').classes()).toContain('not-collapsed') wrapper.destroy() }) @@ -205,6 +224,7 @@ describe('v-b-toggle directive', () => { expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(wrapper.find('button').classes()).toContain('not-collapsed') const $root = wrapper.vm.$root @@ -214,6 +234,7 @@ describe('v-b-toggle directive', () => { expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('true') expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(wrapper.find('button').classes()).toContain('not-collapsed') $root.$emit(EVENT_STATE, 'test', false) await waitNT(wrapper.vm) @@ -221,6 +242,15 @@ describe('v-b-toggle directive', () => { expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') expect(wrapper.find('button').classes()).toContain('collapsed') + expect(wrapper.find('button').classes()).not.toContain('not-collapsed') + + $root.$emit(EVENT_STATE, 'test', true) + await waitNT(wrapper.vm) + + expect(wrapper.find('button').attributes('aria-controls')).toBe('test') + expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') + expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(wrapper.find('button').classes()).toContain('not-collapsed') wrapper.destroy() }) @@ -243,7 +273,8 @@ describe('v-b-toggle directive', () => { expect(wrapper.element.tagName).toBe('BUTTON') expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') - expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(wrapper.find('button').classes()).toContain('collapsed') + expect(wrapper.find('button').classes()).not.toContain('not-collapsed') const $root = wrapper.vm.$root @@ -253,6 +284,7 @@ describe('v-b-toggle directive', () => { expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('true') expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(wrapper.find('button').classes()).toContain('not-collapsed') $root.$emit(EVENT_STATE_SYNC, 'test', false) await waitNT(wrapper.vm) @@ -260,6 +292,7 @@ describe('v-b-toggle directive', () => { expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') expect(wrapper.find('button').classes()).toContain('collapsed') + expect(wrapper.find('button').classes()).not.toContain('not-collapsed') wrapper.destroy() }) From 8f3306bba72e421942e2b74848c4e2c2777747a8 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 21:02:42 -0300 Subject: [PATCH 22/91] Update toggle.spec.js --- src/directives/toggle/toggle.spec.js | 36 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/directives/toggle/toggle.spec.js b/src/directives/toggle/toggle.spec.js index b0d3304e6ca..87c01e069ce 100644 --- a/src/directives/toggle/toggle.spec.js +++ b/src/directives/toggle/toggle.spec.js @@ -80,8 +80,8 @@ describe('v-b-toggle directive', () => { expect(wrapper.element.tagName).toBe('BUTTON') expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') - expect(wrapper.find('button').classes()).not.toContain('collapsed') - expect(wrapper.find('button').classes()).toContain('not-collapsed') + expect(wrapper.find('button').classes()).toContain('collapsed') + expect(wrapper.find('button').classes()).not.toContain('not-collapsed') expect(spy).not.toHaveBeenCalled() const $button = wrapper.find('button') @@ -92,8 +92,8 @@ describe('v-b-toggle directive', () => { // current state, the classes and attrs remain the same expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') - expect(wrapper.find('button').classes()).not.toContain('collapsed') - expect(wrapper.find('button').classes()).toContain('not-collapsed') + expect(wrapper.find('button').classes()).toContain('collapsed') + expect(wrapper.find('button').classes()).not.toContain('not-collapsed') wrapper.destroy() }) @@ -123,8 +123,8 @@ describe('v-b-toggle directive', () => { expect(wrapper.element.tagName).toBe('BUTTON') expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') - expect(wrapper.find('button').classes()).not.toContain('collapsed') - expect(wrapper.find('button').classes()).toContain('not-collapsed') + expect(wrapper.find('button').classes()).toContain('collapsed') + expect(wrapper.find('button').classes()).not.toContain('not-collapsed') expect(spy).not.toHaveBeenCalled() const $button = wrapper.find('button') @@ -135,8 +135,8 @@ describe('v-b-toggle directive', () => { // current state, the classes and attrs remain the same expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') - expect(wrapper.find('button').classes()).not.toContain('collapsed') - expect(wrapper.find('button').classes()).toContain('not-collapsed') + expect(wrapper.find('button').classes()).toContain('collapsed') + expect(wrapper.find('button').classes()).not.toContain('not-collapsed') wrapper.destroy() }) @@ -173,8 +173,8 @@ describe('v-b-toggle directive', () => { expect(wrapper.find('span').attributes('role')).toBe('button') expect(wrapper.find('span').attributes('aria-controls')).toBe('test') expect(wrapper.find('span').attributes('aria-expanded')).toBe('false') - expect(wrapper.find('span').classes()).not.toContain('collapsed') - expect(wrapper.find('span').classes()).toContain('not-collapsed') + expect(wrapper.find('span').classes()).toContain('collapsed') + expect(wrapper.find('span').classes()).not.toContain('not-collapsed') expect(wrapper.find('span').text()).toBe('span') const $span = wrapper.find('span') @@ -186,8 +186,8 @@ describe('v-b-toggle directive', () => { // current state, the classes and attrs remain the same expect(wrapper.find('span').attributes('aria-controls')).toBe('test') expect(wrapper.find('span').attributes('aria-expanded')).toBe('false') - expect(wrapper.find('span').classes()).not.toContain('collapsed') - expect(wrapper.find('span').classes()).toContain('not-collapsed') + expect(wrapper.find('span').classes()).toContain('collapsed') + expect(wrapper.find('span').classes()).not.toContain('not-collapsed') // Test updating component. should maintain role attribute await wrapper.setData({ @@ -199,8 +199,8 @@ describe('v-b-toggle directive', () => { // current state, the classes and attrs remain the same expect(wrapper.find('span').attributes('aria-controls')).toBe('test') expect(wrapper.find('span').attributes('aria-expanded')).toBe('false') - expect(wrapper.find('span').classes()).not.toContain('collapsed') - expect(wrapper.find('span').classes()).toContain('not-collapsed') + expect(wrapper.find('span').classes()).toContain('collapsed') + expect(wrapper.find('span').classes()).not.toContain('not-collapsed') wrapper.destroy() }) @@ -223,8 +223,8 @@ describe('v-b-toggle directive', () => { expect(wrapper.element.tagName).toBe('BUTTON') expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') - expect(wrapper.find('button').classes()).not.toContain('collapsed') - expect(wrapper.find('button').classes()).toContain('not-collapsed') + expect(wrapper.find('button').classes()).toContain('collapsed') + expect(wrapper.find('button').classes()).not.toContain('not-collapsed') const $root = wrapper.vm.$root @@ -249,8 +249,8 @@ describe('v-b-toggle directive', () => { expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') - expect(wrapper.find('button').classes()).not.toContain('collapsed') - expect(wrapper.find('button').classes()).toContain('not-collapsed') + expect(wrapper.find('button').classes()).toContain('collapsed') + expect(wrapper.find('button').classes()).not.toContain('not-collapsed') wrapper.destroy() }) From 7cfb6815237948e8341a63b8caa15ab2df0052b2 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 21:42:15 -0300 Subject: [PATCH 23/91] Update toggle.js --- src/directives/toggle/toggle.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/directives/toggle/toggle.js b/src/directives/toggle/toggle.js index 37e94a465ab..a87b8c82cce 100644 --- a/src/directives/toggle/toggle.js +++ b/src/directives/toggle/toggle.js @@ -34,7 +34,8 @@ export const EVENT_STATE_REQUEST = 'bv::request::collapse::state' // --- Helper methods --- -const setTriggerState = (el, state) => { +const setToggleState = (el, state) => { + // State refers to the visibility of the collapse/sidebar if (state) { removeClass(el, CLASS_VBTOGGLE_COLLAPSED) addClass(el, CLASS_VBTOGGLE_NOT_COLLAPSED) @@ -86,7 +87,7 @@ const handleUpdate = (el, binding, vnode) => { // 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 - setTriggerState(el, el[BV_TOGGLE_STATE]) + setToggleState(el, el[BV_TOGGLE_STATE]) setAttr(el, 'aria-controls', el[BV_TOGGLE_CONTROLS]) } @@ -104,20 +105,20 @@ export const VBToggle = { // 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')) { + // If element is not a button or link, we add `role="button"` for accessibility + if (el.tagName !== 'BUTTON' && el.tagName !== 'A' && !hasAttr(el, 'role')) { setAttr(el, 'role', 'button') } + setToggleState(el, el[BV_TOGGLE_STATE]) // Toggle state handler const toggleDirectiveHandler = (id, state) => { const targets = el[BV_TOGGLE_TARGETS] || [] if (arrayIncludes(targets, id)) { - // Set/Clear 'collapsed' class state + // Set/Clear 'collapsed' visibility class state el[BV_TOGGLE_STATE] = state // Set aria-expanded and class state on trigger element - setTriggerState(el, state) + setToggleState(el, state) } } From 534e90fcff08da2ee7773adb1229a29a2c4e21f3 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 21:47:44 -0300 Subject: [PATCH 24/91] Update toggle.spec.js --- src/directives/toggle/toggle.spec.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/directives/toggle/toggle.spec.js b/src/directives/toggle/toggle.spec.js index 87c01e069ce..d59b728d86c 100644 --- a/src/directives/toggle/toggle.spec.js +++ b/src/directives/toggle/toggle.spec.js @@ -37,8 +37,8 @@ describe('v-b-toggle directive', () => { expect(wrapper.element.tagName).toBe('BUTTON') expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') - expect(wrapper.find('button').classes()).not.toContain('collapsed') - expect(wrapper.find('button').classes()).toContain('not-collapsed') + expect(wrapper.find('button').classes()).toContain('collapsed') + expect(wrapper.find('button').classes()).not.toContain('not-collapsed') expect(spy).not.toHaveBeenCalled() const $button = wrapper.find('button') @@ -49,8 +49,8 @@ describe('v-b-toggle directive', () => { // current state, the classes and attrs remain the same expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') - expect(wrapper.find('button').classes()).not.toContain('collapsed') - expect(wrapper.find('button').classes()).toContain('not-collapsed') + expect(wrapper.find('button').classes()).toContain('collapsed') + expect(wrapper.find('button').classes()).not.toContain('not-collapsed') wrapper.destroy() }) @@ -248,7 +248,7 @@ describe('v-b-toggle directive', () => { await waitNT(wrapper.vm) expect(wrapper.find('button').attributes('aria-controls')).toBe('test') - expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') + expect(wrapper.find('button').attributes('aria-expanded')).toBe('true') expect(wrapper.find('button').classes()).toContain('collapsed') expect(wrapper.find('button').classes()).not.toContain('not-collapsed') From fecef1ee0669dafbf26374ef89d82257a6be6831 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 21:52:52 -0300 Subject: [PATCH 25/91] Update toggle.spec.js --- src/directives/toggle/toggle.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/directives/toggle/toggle.spec.js b/src/directives/toggle/toggle.spec.js index d59b728d86c..2b0b5cd9f73 100644 --- a/src/directives/toggle/toggle.spec.js +++ b/src/directives/toggle/toggle.spec.js @@ -249,8 +249,8 @@ describe('v-b-toggle directive', () => { expect(wrapper.find('button').attributes('aria-controls')).toBe('test') expect(wrapper.find('button').attributes('aria-expanded')).toBe('true') - expect(wrapper.find('button').classes()).toContain('collapsed') - expect(wrapper.find('button').classes()).not.toContain('not-collapsed') + expect(wrapper.find('button').classes()).not.toContain('collapsed') + expect(wrapper.find('button').classes()).toContain('not-collapsed') wrapper.destroy() }) From fe8868da1ae561c4e978dfa26f45159d7d540d5c Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 21:58:48 -0300 Subject: [PATCH 26/91] Update navbar-toggle.spec.js --- src/components/navbar/navbar-toggle.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/navbar/navbar-toggle.spec.js b/src/components/navbar/navbar-toggle.spec.js index 20ff22d85e3..12b1098f92e 100644 --- a/src/components/navbar/navbar-toggle.spec.js +++ b/src/components/navbar/navbar-toggle.spec.js @@ -23,7 +23,9 @@ describe('navbar-toggle', () => { }) expect(wrapper.classes()).toContain('navbar-toggler') - expect(wrapper.classes().length).toBe(1) + // Class added by v-b-toggle + expect(wrapper.classes()).toContain('collapsed') + expect(wrapper.classes().length).toBe(2) wrapper.destroy() }) From bfdfeaab2657ac043c4ee5772df54dda8a914ad8 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 22:48:33 -0300 Subject: [PATCH 27/91] Update toggle.js --- src/directives/toggle/toggle.js | 62 ++++++++++++++++----------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/directives/toggle/toggle.js b/src/directives/toggle/toggle.js index a87b8c82cce..d5d51a59179 100644 --- a/src/directives/toggle/toggle.js +++ b/src/directives/toggle/toggle.js @@ -63,7 +63,7 @@ const handleTargets = ({ targets, vnode }) => { // Handle directive updates /* istanbul ignore next: not easy to test */ const handleUpdate = (el, binding, vnode) => { - if (!isBrowser) { + if (!isBrowser || !vnode.context) { return } @@ -76,7 +76,12 @@ const handleUpdate = (el, binding, vnode) => { // 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]) + /* istanbul ignore else */ + if (el[BV_TOGGLE_CONTROLS]) { + setAttr(el, 'aria-controls', el[BV_TOGGLE_CONTROLS]) + } else { + removeAttr(el, 'aria-controls') + } // Request a state update from targets so that we can ensure // expanded state is correct targets.forEach(target => { @@ -84,11 +89,15 @@ const handleUpdate = (el, binding, vnode) => { }) } + // If element is not a button or link, we add `role="button"` for accessibility + if (el.tagName !== 'BUTTON' && el.tagName !== 'A' && !hasAttr(el, 'role')) { + setAttr(el, 'role', 'button') + } + // 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 + // or changes to this element or its contents) setToggleState(el, el[BV_TOGGLE_STATE]) - setAttr(el, 'aria-controls', el[BV_TOGGLE_CONTROLS]) } /* @@ -96,40 +105,31 @@ const handleUpdate = (el, binding, vnode) => { */ export const VBToggle = { bind(el, binding, vnode) { - const 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]) - // If element is not a button or link, we add `role="button"` for accessibility - if (el.tagName !== 'BUTTON' && el.tagName !== 'A' && !hasAttr(el, 'role')) { - setAttr(el, 'role', 'button') - } - setToggleState(el, el[BV_TOGGLE_STATE]) - - // Toggle state handler - const toggleDirectiveHandler = (id, state) => { - const targets = el[BV_TOGGLE_TARGETS] || [] - if (arrayIncludes(targets, id)) { - // Set/Clear 'collapsed' visibility class state - el[BV_TOGGLE_STATE] = state - // Set aria-expanded and class state on trigger element - setToggleState(el, state) - } + // State is initially collapsed until we receive a state event + el[BV_TOGGLE_STATE] = false + el[BV_TOGGLE_TARGETS] = [] + + // Toggle state handler + el[BV_TOGGLE] = toggleDirectiveHandler = (id, state) => { + // `state` will be true of target is expanded + const targets = el[BV_TOGGLE_TARGETS] || [] + if (arrayIncludes(targets, id)) { + // Set/Clear 'collapsed' visibility class state + el[BV_TOGGLE_STATE] = state + // Set aria-expanded and class state on trigger element + setToggleState(el, state) } + } - // Store the toggle handler on the element - el[BV_TOGGLE] = toggleDirectiveHandler - + if (vnode.context) { // 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]) } + + // Initial update of trigger + handleUpdate(el, binding, vnode) }, componentUpdated: handleUpdate, updated: handleUpdate, From aaf099366379bd242bdbb751e1a5a9553a884f79 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 23:02:30 -0300 Subject: [PATCH 28/91] Update toggle.js --- src/directives/toggle/toggle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/directives/toggle/toggle.js b/src/directives/toggle/toggle.js index d5d51a59179..32d862663b8 100644 --- a/src/directives/toggle/toggle.js +++ b/src/directives/toggle/toggle.js @@ -110,7 +110,7 @@ export const VBToggle = { el[BV_TOGGLE_TARGETS] = [] // Toggle state handler - el[BV_TOGGLE] = toggleDirectiveHandler = (id, state) => { + el[BV_TOGGLE] = (id, state) => { // `state` will be true of target is expanded const targets = el[BV_TOGGLE_TARGETS] || [] if (arrayIncludes(targets, id)) { From 5f8f4eabc4ab2f8867a6512f487d134a206350ca Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 9 May 2020 23:17:52 -0300 Subject: [PATCH 29/91] Update README.md --- src/directives/toggle/README.md | 41 +++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/directives/toggle/README.md b/src/directives/toggle/README.md index ecd53793981..2fb80b96108 100644 --- a/src/directives/toggle/README.md +++ b/src/directives/toggle/README.md @@ -12,18 +12,25 @@ visibility state of the [``](/docs/components/collapse) and Besides toggling the visibility of the target component, the directive automatically updates ARIA accessibility attributes on the element it is applied to so that they reflect the visibility state of the target component. Refer to the [Accessibility section](#accessibility) below for additional -details. +details and caveats. ## Directive syntax and usage The directive is applied to the element or component that triggers the visibility of hte target. The -target component can be specified (via ID) as either a directive modifier(s) or as a string passed -to as the directive value: +target component can be specified (via its ID) as either a directive modifier(s), the directive +argument, or as a string passed to as the directive value: -- `v-b-toggle.my-collapse` - the directive modifier (multiple targets allowed) -- `v-b-toggle="'my-collapse'"` - the directive value (as a String, single target only) +- `v-b-toggle.my-collapse` - the directive modifier (multiple targets allowed, each ID is a modifier) +- `v-b-toggle:my-collapse` - the directive argument + ([Vue dynamic argument](https://vuejs.org/v2/guide/syntax.html#Dynamic-Arguments) is supported) + v2.14.0+ +- `v-b-toggle="'my-collapse'"` - the directive value as a string ID +- `v-b-toggle="'my-collapse1 my-collapse2'"` - the directive value as a space separated string of IDs + v2.14.0+ +- `v-b-toggle="['my-collapse1', 'my-collapse2']"` - the directive value as an array of string IDs + v2.14.0+ -Modifiers and the value can be used at the same time. +Modifiers, argument, and the value can be used at the same time when targetting multie components. ### Example usage @@ -58,21 +65,31 @@ Modifiers and the value can be used at the same time. The directive, for accessibility reasons, should be placed on an clickable interactive element such as a `` + }) + + const wrapper = mount(App, { + propsData: { + target: 'test1' + }, + localVue + }) + + expect(wrapper.vm).toBeDefined() + expect(wrapper.element.tagName).toBe('BUTTON') + + const $button = wrapper.find('button') + + expect($button.attributes('aria-controls')).toBe('test1') + expect($button.attributes('aria-expanded')).toBe('false') + expect($button.classes()).toContain('collapsed') + expect($button.classes()).not.toContain('not-collapsed') + expect(spy).not.toHaveBeenCalled() + + await wrapper.setProps({ + target: ['test1', 'test2'] + }) + + expect($button.attributes('aria-controls')).toBe('test1 test2') + expect($button.attributes('aria-expanded')).toBe('false') + expect($button.classes()).toContain('collapsed') + expect($button.classes()).not.toContain('not-collapsed') + expect(spy).not.toHaveBeenCalled() + + await $button.trigger('click') + + expect(spy).toHaveBeenCalledTimes(2) + expect(spy).toHaveBeenNthCalledWith(1,'test1') + expect(spy).toHaveBeenNthCalledWith(2,'test2') + // Since there is no target collapse to respond with the + // current state, the classes and attrs remain the same + expect($button.attributes('aria-controls')).toBe('test1 test2') + expect($button.attributes('aria-expanded')).toBe('false') + expect($button.classes()).toContain('collapsed') + expect($button.classes()).not.toContain('not-collapsed') + + await wrapper.setProps({ + target: ['test2'] + }) + + expect($button.attributes('aria-controls')).toBe('test2') + expect($button.attributes('aria-expanded')).toBe('false') + expect($button.classes()).toContain('collapsed') + expect($button.classes()).not.toContain('not-collapsed') + expect(spy).toHaveBeenCalledTimes(2) + + await $button.trigger('click') + + expect(spy).toHaveBeenCalledTimes(3) + expect(spy).toHaveBeenNthCalledWith(3,'test2') + // Since there is no target collapse to respond with the + // current state, the classes and attrs remain the same + expect($button.attributes('aria-controls')).toBe('test2') + expect($button.attributes('aria-expanded')).toBe('false') + expect($button.classes()).toContain('collapsed') + expect($button.classes()).not.toContain('not-collapsed') + + wrapper.destroy() + }) + it('works on non-buttons', async () => { const localVue = createLocalVue() const spy = jest.fn() From d2e4b91ff6e8094807149cdf06d7e4cdda3df391 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 10 May 2020 22:02:40 -0300 Subject: [PATCH 90/91] Update toggle.spec.js --- src/directives/toggle/toggle.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/directives/toggle/toggle.spec.js b/src/directives/toggle/toggle.spec.js index fa9735f4acd..c7164638453 100644 --- a/src/directives/toggle/toggle.spec.js +++ b/src/directives/toggle/toggle.spec.js @@ -197,8 +197,8 @@ describe('v-b-toggle directive', () => { await $button.trigger('click') expect(spy).toHaveBeenCalledTimes(2) - expect(spy).toHaveBeenNthCalledWith(1,'test1') - expect(spy).toHaveBeenNthCalledWith(2,'test2') + expect(spy).toHaveBeenNthCalledWith(1, 'test1') + expect(spy).toHaveBeenNthCalledWith(2, 'test2') // Since there is no target collapse to respond with the // current state, the classes and attrs remain the same expect($button.attributes('aria-controls')).toBe('test1 test2') @@ -219,7 +219,7 @@ describe('v-b-toggle directive', () => { await $button.trigger('click') expect(spy).toHaveBeenCalledTimes(3) - expect(spy).toHaveBeenNthCalledWith(3,'test2') + expect(spy).toHaveBeenNthCalledWith(3, 'test2') // Since there is no target collapse to respond with the // current state, the classes and attrs remain the same expect($button.attributes('aria-controls')).toBe('test2') From aa414840bae5d890c8daa1a51e6167dd521bf416 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 10 May 2020 22:32:27 -0300 Subject: [PATCH 91/91] Update toggle.spec.js --- src/directives/toggle/toggle.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/directives/toggle/toggle.spec.js b/src/directives/toggle/toggle.spec.js index c7164638453..6aafa17b7f8 100644 --- a/src/directives/toggle/toggle.spec.js +++ b/src/directives/toggle/toggle.spec.js @@ -143,7 +143,7 @@ describe('v-b-toggle directive', () => { wrapper.destroy() }) - it('works with multipl targets', async () => { + it('works with multiple targets, and updates when targets change', async () => { const localVue = createLocalVue() const spy = jest.fn()