From 02a543bacb5d64e02d6b824445dbba76f3c25175 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 14:18:46 -0300 Subject: [PATCH 01/13] Update collapse.js --- src/components/collapse/collapse.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/collapse/collapse.js b/src/components/collapse/collapse.js index 93779958a17..9bca82aedc8 100644 --- a/src/components/collapse/collapse.js +++ b/src/components/collapse/collapse.js @@ -5,6 +5,9 @@ import { closest, matches, reflow, getCS, getBCR, eventOn, eventOff } from '../. // Events we emit on $root const EVENT_STATE = 'bv::collapse::state' const EVENT_ACCORDION = 'bv::collapse::accordion' +// Private event we emit on $root to ensure the toggle state is always synced +// Gets emited even if the state has not changed! +const EVENT_STATE_SYNC = 'bv::collapse::sync::state' // Events we listen to on $root const EVENT_TOGGLE = 'bv::toggle::collapse' @@ -80,7 +83,14 @@ export default { this.setWindowEvents(true) this.handleResize() } - this.emitState() + this.$nextTick(() => { + this.emitState() + }) + }, + updated() { + // Emit a private event every time this component updates + // to ensure the toggle button is in sync with the collapse's state + this.$root.$emit(EVENT_STATE_SYNC, this.id, this.show) }, deactivated() /* istanbul ignore next */ { if (this.isNav && inBrowser) { @@ -91,6 +101,7 @@ export default { if (this.isNav && inBrowser) { this.setWindowEvents(true) } + this.$root.$emit(EVENT_STATE_SYNC, this.id, this.show) }, beforeDestroy() { // Trigger state emit if needed From fdd7b2029dd8439389fa1371d8c9e6fccd08f726 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 14:32:01 -0300 Subject: [PATCH 02/13] Update toggle.js --- src/directives/toggle/toggle.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/directives/toggle/toggle.js b/src/directives/toggle/toggle.js index b8a0d066d72..8b5e7866513 100644 --- a/src/directives/toggle/toggle.js +++ b/src/directives/toggle/toggle.js @@ -9,6 +9,7 @@ const listenTypes = { click: true } const BV_TOGGLE = '__BV_toggle__' const BV_TOGGLE_STATE = '__BV_toggle_STATE__' const BV_TOGGLE_CONTROLS = '__BV_toggle_CONTROLS__' +const BV_TOGGLE_TARGETS = '__BV_toggle_CONTROLS__' // Emitted control event for collapse (emitted to collapse) const EVENT_TOGGLE = 'bv::toggle::collapse' @@ -16,6 +17,10 @@ const EVENT_TOGGLE = 'bv::toggle::collapse' // Listen to event for toggle state update (emitted by collapse) const 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 +const EVENT_STATE_SYNC = 'bv::collapse::sync::state' + // Reset and remove a property from the provided element const resetProp = (el, prop) => { el[prop] = null @@ -53,6 +58,8 @@ export default { }) if (inBrowser && 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 @@ -66,6 +73,7 @@ export default { // Toggle state handler, stored on element el[BV_TOGGLE] = function 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') @@ -79,8 +87,10 @@ export default { } } - // Listen for toggle state changes + // 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, @@ -90,11 +100,13 @@ export default { // 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') From 577f5d3330032aee78a59cee50456f5bcfa1af6b Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 14:36:52 -0300 Subject: [PATCH 03/13] Update toggle.spec.js --- src/directives/toggle/toggle.spec.js | 46 +++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/directives/toggle/toggle.spec.js b/src/directives/toggle/toggle.spec.js index cb3bdf32190..a42ce23b661 100644 --- a/src/directives/toggle/toggle.spec.js +++ b/src/directives/toggle/toggle.spec.js @@ -7,6 +7,9 @@ const EVENT_TOGGLE = 'bv::toggle::collapse' // Listen to event for toggle state update (emitted by collapse) const EVENT_STATE = 'bv::collapse::state' +// Listen to event for toggle sync state update (emitted by collapse) +const EVENT_STATE_SYNC = 'bv::collapse::sync::state' + describe('v-b-toggle directive', () => { it('works on buttons', async () => { const localVue = new CreateLocalVue() @@ -49,7 +52,6 @@ describe('v-b-toggle directive', () => { wrapper.destroy() }) - it('works on passing ID as directive value', async () => { const localVue = new CreateLocalVue() const spy = jest.fn() @@ -190,4 +192,46 @@ describe('v-b-toggle directive', () => { wrapper.destroy() }) + + it('responds to private sync state update events', async () => { + const localVue = new CreateLocalVue() + + const App = localVue.extend({ + directives: { + bToggle: toggleDirective + }, + data() { + return {} + }, + template: '' + }) + + const wrapper = mount(App, { + localVue: localVue + }) + + expect(wrapper.isVueInstance()).toBe(true) + expect(wrapper.is('button')).toBe(true) + 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') + + const $root = wrapper.vm.$root + + $root.$emit(EVENT_STATE_SYNC, 'test', true) + await wrapper.vm.$nextTick() + + 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') + + $root.$emit(EVENT_STATE_SYNC, 'test', false) + await wrapper.vm.$nextTick() + + 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') + + wrapper.destroy() + }) }) From 1faee1d0ce3c9c8e8f4ee4d3ab7b1329a29af6d1 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 14:39:38 -0300 Subject: [PATCH 04/13] Update navbar-toggle.js --- src/components/navbar/navbar-toggle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/navbar/navbar-toggle.js b/src/components/navbar/navbar-toggle.js index c03c0213e2d..5b1b3ae8fd9 100644 --- a/src/components/navbar/navbar-toggle.js +++ b/src/components/navbar/navbar-toggle.js @@ -21,11 +21,11 @@ export default { }, created() { this.listenOnRoot('bv::collapse::state', this.handleStateEvt) + this.listenOnRoot('bv::collapse::sync::state', this.handleStateEvt) }, methods: { onClick(evt) { this.$emit('click', evt) - /* istanbul ignore next */ if (!evt.defaultPrevented) { this.$root.$emit('bv::toggle::collapse', this.target) } From 7d990dcbb352ad4693dacb7024f35e27a5e9ba02 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 14:51:32 -0300 Subject: [PATCH 05/13] Update navbar-toggle.spec.js --- src/components/navbar/navbar-toggle.spec.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/navbar/navbar-toggle.spec.js b/src/components/navbar/navbar-toggle.spec.js index 163dcae8f9a..980a388420b 100644 --- a/src/components/navbar/navbar-toggle.spec.js +++ b/src/components/navbar/navbar-toggle.spec.js @@ -74,17 +74,27 @@ describe('navbar-toggle', () => { wrapper.vm.$root.$off('bv::toggle::collapse', onRootClick) }) - it('sets areia-expanded when receives root emit for target', async () => { + it('sets area-expanded when receives root emit for target', async () => { const wrapper = mount(NavbarToggle, { propsData: { target: 'target' } }) + + // Private state event wrapper.vm.$root.$emit('bv::collapse::state', 'target', true) expect(wrapper.attributes('aria-expanded')).toBe('true') wrapper.vm.$root.$emit('bv::collapse::state', 'target', false) expect(wrapper.attributes('aria-expanded')).toBe('false') wrapper.vm.$root.$emit('bv::collapse::state', 'foo', true) expect(wrapper.attributes('aria-expanded')).toBe('false') + + // Private sync event + wrapper.vm.$root.$emit('bv::collapse::sync::state', 'target', true) + expect(wrapper.attributes('aria-expanded')).toBe('true') + wrapper.vm.$root.$emit('bv::collapse::sync::state', 'target', false) + expect(wrapper.attributes('aria-expanded')).toBe('false') + wrapper.vm.$root.$emit('bv::collapse::sync::state', 'foo', true) + expect(wrapper.attributes('aria-expanded')).toBe('false') }) }) From 7e58db5328f5a131991ca1394e6552d7c6f34c00 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 14:52:39 -0300 Subject: [PATCH 06/13] Update collapse.js --- src/components/collapse/collapse.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/collapse/collapse.js b/src/components/collapse/collapse.js index 9bca82aedc8..54d87967ebd 100644 --- a/src/components/collapse/collapse.js +++ b/src/components/collapse/collapse.js @@ -7,6 +7,7 @@ const EVENT_STATE = 'bv::collapse::state' const EVENT_ACCORDION = 'bv::collapse::accordion' // Private event we emit on $root to ensure the toggle state is always synced // Gets emited even if the state has not changed! +// This event is NOT to be documented as people should not be using it. const EVENT_STATE_SYNC = 'bv::collapse::sync::state' // Events we listen to on $root const EVENT_TOGGLE = 'bv::toggle::collapse' From 59e3e4fa767c6ea0d5e94e203b47e762efe2d537 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 15:00:04 -0300 Subject: [PATCH 07/13] Update config.js --- src/utils/config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/config.js b/src/utils/config.js index 47493a509b5..ea8bf2fa164 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -81,6 +81,9 @@ const DEFAULTS = { okTitle: 'OK', okVariant: 'primary', headerCloseLabel: 'Close' + }, + BNavbarToggle: { + label: 'Toggle navigation' } } From 5c359c6f2c5a022d744a5bb3e94d3d1115e5b881 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 15:00:59 -0300 Subject: [PATCH 08/13] Update navbar-toggle.js --- src/components/navbar/navbar-toggle.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/components/navbar/navbar-toggle.js b/src/components/navbar/navbar-toggle.js index 5b1b3ae8fd9..10615a9eb15 100644 --- a/src/components/navbar/navbar-toggle.js +++ b/src/components/navbar/navbar-toggle.js @@ -1,13 +1,24 @@ import listenOnRootMixin from '../../mixins/listen-on-root' +import { getComponentConfig } from '../../utils/config' + +const NAME = 'BNavbarToggle' + +// Events we emit on $root +const EVENT_TOGGLE 'bv::toggle::collapse' + +// Events we listen to on $root +const EVENT_STATE = 'bv::collapse::state' +// This private event is NOT to be documented as people should not be using it. +const EVENT_STATE_SYNC = 'bv::collapse::sync::state' // @vue/component export default { - name: 'BNavbarToggle', + name: NAME, mixins: [listenOnRootMixin], props: { label: { type: String, - default: 'Toggle navigation' + default: () => String(getComponentConfig(NAME, 'label') || '') }, target: { type: String, @@ -20,14 +31,14 @@ export default { } }, created() { - this.listenOnRoot('bv::collapse::state', this.handleStateEvt) - this.listenOnRoot('bv::collapse::sync::state', this.handleStateEvt) + this.listenOnRoot(EVENT_STATE, this.handleStateEvt) + this.listenOnRoot(EVENT_STATE_SYNC, this.handleStateEvt) }, methods: { onClick(evt) { this.$emit('click', evt) if (!evt.defaultPrevented) { - this.$root.$emit('bv::toggle::collapse', this.target) + this.$root.$emit(EVENT_TOGGLE, this.target) } }, handleStateEvt(id, state) { From 76e4e63d95b510efc69ef296884fce61f1ed80df Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 15:06:07 -0300 Subject: [PATCH 09/13] Update collapse.js --- src/components/collapse/collapse.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/collapse/collapse.js b/src/components/collapse/collapse.js index 54d87967ebd..556a51cd6ba 100644 --- a/src/components/collapse/collapse.js +++ b/src/components/collapse/collapse.js @@ -73,12 +73,14 @@ export default { } }, created() { + this.show = this.visible // Listen for toggle events to open/close us this.listenOnRoot(EVENT_TOGGLE, this.handleToggleEvt) // Listen to other collapses for accordion events this.listenOnRoot(EVENT_ACCORDION, this.handleAccordionEvt) }, mounted() { + this.show = this.visible if (this.isNav && inBrowser) { // Set up handlers this.setWindowEvents(true) @@ -90,7 +92,8 @@ export default { }, updated() { // Emit a private event every time this component updates - // to ensure the toggle button is in sync with the collapse's state + // to ensure the toggle button is in sync with the collapse's state. + // It is emitted regardless if the visible state changes. this.$root.$emit(EVENT_STATE_SYNC, this.id, this.show) }, deactivated() /* istanbul ignore next */ { From 002ff1fe4a84f01c96abcca4465cf69ff3e8ddb0 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 15:07:47 -0300 Subject: [PATCH 10/13] Update navbar-toggle.spec.js --- src/components/navbar/navbar-toggle.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/navbar/navbar-toggle.spec.js b/src/components/navbar/navbar-toggle.spec.js index 980a388420b..a5d9c038d92 100644 --- a/src/components/navbar/navbar-toggle.spec.js +++ b/src/components/navbar/navbar-toggle.spec.js @@ -74,7 +74,7 @@ describe('navbar-toggle', () => { wrapper.vm.$root.$off('bv::toggle::collapse', onRootClick) }) - it('sets area-expanded when receives root emit for target', async () => { + it('sets aria-expanded when receives root emit for target', async () => { const wrapper = mount(NavbarToggle, { propsData: { target: 'target' From 92b4aec34892e545ee0473a9271e36404cd91a60 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 15:08:22 -0300 Subject: [PATCH 11/13] Update toggle.js --- src/directives/toggle/toggle.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/directives/toggle/toggle.js b/src/directives/toggle/toggle.js index 8b5e7866513..6f79f5c85ef 100644 --- a/src/directives/toggle/toggle.js +++ b/src/directives/toggle/toggle.js @@ -18,7 +18,8 @@ const EVENT_TOGGLE = 'bv::toggle::collapse' const 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 +// 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. const EVENT_STATE_SYNC = 'bv::collapse::sync::state' // Reset and remove a property from the provided element From e1c0e467b9c541d4184d6803924c9bcd04fc43e8 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 15:10:05 -0300 Subject: [PATCH 12/13] Update navbar-toggle.js --- src/components/navbar/navbar-toggle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/navbar/navbar-toggle.js b/src/components/navbar/navbar-toggle.js index 10615a9eb15..f8e121ad1ac 100644 --- a/src/components/navbar/navbar-toggle.js +++ b/src/components/navbar/navbar-toggle.js @@ -4,7 +4,7 @@ import { getComponentConfig } from '../../utils/config' const NAME = 'BNavbarToggle' // Events we emit on $root -const EVENT_TOGGLE 'bv::toggle::collapse' +const EVENT_TOGGLE = 'bv::toggle::collapse' // Events we listen to on $root const EVENT_STATE = 'bv::collapse::state' From 911b545809a0118031127f07c67157fda6d50b4a Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 5 Apr 2019 15:14:24 -0300 Subject: [PATCH 13/13] 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 6f79f5c85ef..d971c1f748b 100644 --- a/src/directives/toggle/toggle.js +++ b/src/directives/toggle/toggle.js @@ -9,7 +9,7 @@ const listenTypes = { click: true } const BV_TOGGLE = '__BV_toggle__' const BV_TOGGLE_STATE = '__BV_toggle_STATE__' const BV_TOGGLE_CONTROLS = '__BV_toggle_CONTROLS__' -const BV_TOGGLE_TARGETS = '__BV_toggle_CONTROLS__' +const BV_TOGGLE_TARGETS = '__BV_toggle_TARGETS__' // Emitted control event for collapse (emitted to collapse) const EVENT_TOGGLE = 'bv::toggle::collapse'