From 72b9726431b4397293cf3e22c84b13386bce3470 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 18:40:34 -0300 Subject: [PATCH 01/13] feat(modal): auto return focus to trigger elements using document.activeElement When a modal opens, and a return_focus value is not sent by the trigger events (or if using the v-model), try setting the return focus element as the document.activeElement. --- src/components/modal/modal.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/modal/modal.js b/src/components/modal/modal.js index b56853f0195..5103da1e103 100644 --- a/src/components/modal/modal.js +++ b/src/components/modal/modal.js @@ -363,6 +363,13 @@ export default Vue.extend({ return } this.is_opening = true + if (inBrowser && document.activeElement.focus) { + // Preset the fallback return focus value if it is not set. + // document.activeElement should be the trigger element that was clicked or + // in the case of using the v-model, which ever element has current focus. + // Will be overridden by some commands such as toggle, etc. + this.return_focus = this.return_focus || document.activeElement + } const showEvt = new BvModalEvent('show', { cancelable: true, vueTarget: this, @@ -628,8 +635,9 @@ export default Vue.extend({ el = select(el) } if (el) { + // Possibly could be a component reference el = el.$el || el - if (isVisible(el)) { + if (isVisible(el) && el.focus) { el.focus() } } From 8e5c2ef2eb309da9ba8dc6daf28d8e4fb356b46a Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 18:49:40 -0300 Subject: [PATCH 02/13] Update modal.js --- src/components/modal/modal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/modal/modal.js b/src/components/modal/modal.js index 5103da1e103..084143f0f4d 100644 --- a/src/components/modal/modal.js +++ b/src/components/modal/modal.js @@ -584,7 +584,7 @@ export default Vue.extend({ // Root listener handlers showHandler(id, triggerEl) { if (id === this.id) { - this.return_focus = triggerEl || null + this.return_focus = triggerEl || document.activeElement || null this.show() } }, @@ -629,7 +629,7 @@ export default Vue.extend({ }, returnFocusTo() { // Prefer `returnFocus` prop over event specified `return_focus` value - let el = this.returnFocus || this.return_focus || null + let el = this.returnFocus || this.return_focus || document.activeElement || null if (typeof el === 'string') { // CSS Selector el = select(el) From eedf622160dac64836f9762e279719b857609ede Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 18:52:28 -0300 Subject: [PATCH 03/13] Update modal.js --- src/components/modal/modal.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/modal/modal.js b/src/components/modal/modal.js index 084143f0f4d..bcb588952e5 100644 --- a/src/components/modal/modal.js +++ b/src/components/modal/modal.js @@ -630,10 +630,8 @@ export default Vue.extend({ returnFocusTo() { // Prefer `returnFocus` prop over event specified `return_focus` value let el = this.returnFocus || this.return_focus || document.activeElement || null - if (typeof el === 'string') { - // CSS Selector - el = select(el) - } + // Is el a string CSS Selector? + el = typeof el === 'string' ? select(el) : el if (el) { // Possibly could be a component reference el = el.$el || el From 182fbf2bc78465cf0fc122631304bfe79f42df99 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 19:19:14 -0300 Subject: [PATCH 04/13] Update modal.spec.js --- src/components/modal/modal.spec.js | 198 ++++++++++++++++++++++++++++- 1 file changed, 197 insertions(+), 1 deletion(-) diff --git a/src/components/modal/modal.spec.js b/src/components/modal/modal.spec.js index f025408f2c9..18de414f30b 100644 --- a/src/components/modal/modal.spec.js +++ b/src/components/modal/modal.spec.js @@ -1,7 +1,7 @@ import BModal from './modal' import BvModalEvent from './helpers/bv-modal-event.class' -import { mount, createWrapper } from '@vue/test-utils' +import { mount, createWrapper, createLocalVue as CreateLocalVue } from '@vue/test-utils' // The defautl Z-INDEX for modal backdrop const DEFAULT_ZINDEX = 1040 @@ -896,6 +896,202 @@ describe('modal', () => { // Modal should now be closed expect($modal.element.style.display).toEqual('none') + + wrapper.destroy() + }) + }) + + describe('return focus support', () => { + it('returns focus to document.body when no return focus set and not using v-b-toggle', async () => { + const wrapper = mount(BModal, { + attachToDocument: true, + stubs: { + transition: false + }, + propsData: { + id: 'test', + visible: false + } + }) + + expect(wrapper.isVueInstance()).toBe(true) + + await wrapper.vm.$nextTick() + await waitAF() + await wrapper.vm.$nextTick() + await waitAF() + + const $modal = wrapper.find('div.modal') + expect($modal.exists()).toBe(true) + + expect($modal.element.style.display).toEqual('none') + expect(document.activeElement).toBe(document.body) + + // Try and open modal via .toggle() method + wrapper.vm.toggle() + + await wrapper.vm.$nextTick() + await waitAF() + await wrapper.vm.$nextTick() + await waitAF() + + // Modal should now be open + expect($modal.element.style.display).toEqual('') + expect(document.activeElement).not.toBe(document.body) + expect(wrapper.element.contains(document.activeElement)).toBe(true) + + // Try and close modal via .toggle() + wrapper.vm.toggle() + + await wrapper.vm.$nextTick() + await waitAF() + await wrapper.vm.$nextTick() + await waitAF() + + // Modal should now be closed + expect($modal.element.style.display).toEqual('none') + expect(document.activeElement).toBe(document.body) + + wrapper.destroy() + }) + + it('returns focus to previous active element when return focus not set and not using v-b-toggle', async () => { + const localVue = new CreateLocalVue() + const App = localVue.extend({ + render(h) { + h('div', {} [ + h('button', { attrs: { id: 'trigger', type: 'button' } }, 'trigger'), + h(BModal, { props: { id: 'test', visible: false } }, 'modal content') + ]) + } + }) + const wrapper = mount(App, { + attachToDocument: true, + stubs: { + transition: false + } + }) + + expect(wrapper.isVueInstance()).toBe(true) + + await wrapper.vm.$nextTick() + await waitAF() + await wrapper.vm.$nextTick() + await waitAF() + + const $button = wrapper.find('button#trigger') + expect($button.exists()).toBe(true) + expect($button.is('button')).toBe(true) + + const $modal = wrapper.find('div.modal') + expect($modal.exists()).toBe(true) + + expect($modal.element.style.display).toEqual('none') + expect(document.activeElement).toBe(document.body) + + // Set the active element to the button + $button.element.focus() + expect(document.activeElement).toBe($button.element) + + // Try and open modal via .toggle() method + $modal.vm.toggle() + + await wrapper.vm.$nextTick() + await waitAF() + await wrapper.vm.$nextTick() + await waitAF() + + // Modal should now be open + expect($modal.element.style.display).toEqual('') + expect(document.activeElement).not.toBe(document.body) + expect(document.activeElement).not.toBe($button.element) + expect($modal.element.contains(document.activeElement)).toBe(true) + + // Try and close modal via .toggle() + wrapper.vm.toggle() + + await wrapper.vm.$nextTick() + await waitAF() + await wrapper.vm.$nextTick() + await waitAF() + + // Modal should now be closed + expect($modal.element.style.display).toEqual('none') + expect(document.activeElement).toBe($button.element) + + wrapper.destroy() + }) + + it('returns focus to element specified in toggle() method', async () => { + const localVue = new CreateLocalVue() + const App = localVue.extend({ + render(h) { + h('div', {} [ + h('button', { attrs: { id: 'trigger', type: 'button' } }, 'trigger'), + h('button', { attrs: { id: 'return-to', type: 'button' } }, 'trigger'), + h(BModal, { props: { id: 'test', visible: false } }, 'modal content') + ]) + } + }) + const wrapper = mount(App, { + attachToDocument: true, + stubs: { + transition: false + } + }) + + expect(wrapper.isVueInstance()).toBe(true) + + await wrapper.vm.$nextTick() + await waitAF() + await wrapper.vm.$nextTick() + await waitAF() + + const $button = wrapper.find('button#trigger') + expect($button.exists()).toBe(true) + expect($button.is('button')).toBe(true) + + const $button2 = wrapper.find('button#return-to') + expect($button2.exists()).toBe(true) + expect($button2.is('button')).toBe(true) + + const $modal = wrapper.find('div.modal') + expect($modal.exists()).toBe(true) + + expect($modal.element.style.display).toEqual('none') + expect(document.activeElement).toBe(document.body) + + // Set the active element to the button + $button.element.focus() + expect(document.activeElement).toBe($button.element) + + // Try and open modal via .toggle() method + $modal.vm.toggle('#return-to') + + await wrapper.vm.$nextTick() + await waitAF() + await wrapper.vm.$nextTick() + await waitAF() + + // Modal should now be open + expect($modal.element.style.display).toEqual('') + expect(document.activeElement).not.toBe(document.body) + expect(document.activeElement).not.toBe($button.element) + expect(document.activeElement).not.toBe($button2.element) + expect($modal.element.contains(document.activeElement)).toBe(true) + + // Try and close modal via .toggle() + wrapper.vm.toggle() + + await wrapper.vm.$nextTick() + await waitAF() + await wrapper.vm.$nextTick() + await waitAF() + + // Modal should now be closed + expect($modal.element.style.display).toEqual('none') + expect(document.activeElement).toBe($button2.element) + wrapper.destroy() }) }) From 8c43d71fcabc36deccb8d99535a283c78d437909 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 19:22:24 -0300 Subject: [PATCH 05/13] Update modal.spec.js --- src/components/modal/modal.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/modal/modal.spec.js b/src/components/modal/modal.spec.js index 18de414f30b..a02487fcc3b 100644 --- a/src/components/modal/modal.spec.js +++ b/src/components/modal/modal.spec.js @@ -959,7 +959,7 @@ describe('modal', () => { const localVue = new CreateLocalVue() const App = localVue.extend({ render(h) { - h('div', {} [ + h('div', {}, [ h('button', { attrs: { id: 'trigger', type: 'button' } }, 'trigger'), h(BModal, { props: { id: 'test', visible: false } }, 'modal content') ]) @@ -1026,7 +1026,7 @@ describe('modal', () => { const localVue = new CreateLocalVue() const App = localVue.extend({ render(h) { - h('div', {} [ + h('div', {}, [ h('button', { attrs: { id: 'trigger', type: 'button' } }, 'trigger'), h('button', { attrs: { id: 'return-to', type: 'button' } }, 'trigger'), h(BModal, { props: { id: 'test', visible: false } }, 'modal content') From 81d8e82004f423491e9865b525cff2c5e2395997 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 19:27:42 -0300 Subject: [PATCH 06/13] Update modal.spec.js --- src/components/modal/modal.spec.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/modal/modal.spec.js b/src/components/modal/modal.spec.js index a02487fcc3b..0380524775f 100644 --- a/src/components/modal/modal.spec.js +++ b/src/components/modal/modal.spec.js @@ -960,7 +960,7 @@ describe('modal', () => { const App = localVue.extend({ render(h) { h('div', {}, [ - h('button', { attrs: { id: 'trigger', type: 'button' } }, 'trigger'), + h('button', { class: 'trigger', attrs: { id: 'trigger', type: 'button' } }, 'trigger'), h(BModal, { props: { id: 'test', visible: false } }, 'modal content') ]) } @@ -979,7 +979,7 @@ describe('modal', () => { await wrapper.vm.$nextTick() await waitAF() - const $button = wrapper.find('button#trigger') + const $button = wrapper.find('button.trigger') expect($button.exists()).toBe(true) expect($button.is('button')).toBe(true) @@ -1027,8 +1027,8 @@ describe('modal', () => { const App = localVue.extend({ render(h) { h('div', {}, [ - h('button', { attrs: { id: 'trigger', type: 'button' } }, 'trigger'), - h('button', { attrs: { id: 'return-to', type: 'button' } }, 'trigger'), + h('button', { class: 'trigegr', attrs: { id: 'trigger', type: 'button' } }, 'trigger'), + h('button', { classL 'return-to', attrs: { id: 'return-to', type: 'button' } }, 'trigger'), h(BModal, { props: { id: 'test', visible: false } }, 'modal content') ]) } @@ -1047,11 +1047,11 @@ describe('modal', () => { await wrapper.vm.$nextTick() await waitAF() - const $button = wrapper.find('button#trigger') + const $button = wrapper.find('button.trigger') expect($button.exists()).toBe(true) expect($button.is('button')).toBe(true) - const $button2 = wrapper.find('button#return-to') + const $button2 = wrapper.find('button.return-to') expect($button2.exists()).toBe(true) expect($button2.is('button')).toBe(true) @@ -1066,7 +1066,7 @@ describe('modal', () => { expect(document.activeElement).toBe($button.element) // Try and open modal via .toggle() method - $modal.vm.toggle('#return-to') + $modal.vm.toggle('button.return-to') await wrapper.vm.$nextTick() await waitAF() From a16ebd7cdcc9040c73c1cba2a272c280168ca091 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 19:33:08 -0300 Subject: [PATCH 07/13] Update modal.spec.js --- src/components/modal/modal.spec.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/modal/modal.spec.js b/src/components/modal/modal.spec.js index 0380524775f..e843419b43e 100644 --- a/src/components/modal/modal.spec.js +++ b/src/components/modal/modal.spec.js @@ -1000,6 +1000,7 @@ describe('modal', () => { await waitAF() await wrapper.vm.$nextTick() await waitAF() + await wrapper.vm.$nextTick() // Modal should now be open expect($modal.element.style.display).toEqual('') @@ -1014,6 +1015,7 @@ describe('modal', () => { await waitAF() await wrapper.vm.$nextTick() await waitAF() + await wrapper.vm.$nextTick() // Modal should now be closed expect($modal.element.style.display).toEqual('none') @@ -1028,7 +1030,7 @@ describe('modal', () => { render(h) { h('div', {}, [ h('button', { class: 'trigegr', attrs: { id: 'trigger', type: 'button' } }, 'trigger'), - h('button', { classL 'return-to', attrs: { id: 'return-to', type: 'button' } }, 'trigger'), + h('button', { class: 'return-to', attrs: { id: 'return-to', type: 'button' } }, 'trigger'), h(BModal, { props: { id: 'test', visible: false } }, 'modal content') ]) } @@ -1072,6 +1074,7 @@ describe('modal', () => { await waitAF() await wrapper.vm.$nextTick() await waitAF() + await wrapper.vm.$nextTick() // Modal should now be open expect($modal.element.style.display).toEqual('') @@ -1087,6 +1090,7 @@ describe('modal', () => { await waitAF() await wrapper.vm.$nextTick() await waitAF() + await wrapper.vm.$nextTick() // Modal should now be closed expect($modal.element.style.display).toEqual('none') From 6c483bcfccb579a72fcee5a1cb578edae59df33b Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 19:39:42 -0300 Subject: [PATCH 08/13] Update modal.spec.js --- src/components/modal/modal.spec.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/modal/modal.spec.js b/src/components/modal/modal.spec.js index e843419b43e..4b56d570cbb 100644 --- a/src/components/modal/modal.spec.js +++ b/src/components/modal/modal.spec.js @@ -934,6 +934,8 @@ describe('modal', () => { await waitAF() await wrapper.vm.$nextTick() await waitAF() + await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() // Modal should now be open expect($modal.element.style.display).toEqual('') @@ -947,6 +949,8 @@ describe('modal', () => { await waitAF() await wrapper.vm.$nextTick() await waitAF() + await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() // Modal should now be closed expect($modal.element.style.display).toEqual('none') @@ -978,6 +982,8 @@ describe('modal', () => { await waitAF() await wrapper.vm.$nextTick() await waitAF() + await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() const $button = wrapper.find('button.trigger') expect($button.exists()).toBe(true) @@ -1001,6 +1007,7 @@ describe('modal', () => { await wrapper.vm.$nextTick() await waitAF() await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() // Modal should now be open expect($modal.element.style.display).toEqual('') @@ -1016,6 +1023,7 @@ describe('modal', () => { await wrapper.vm.$nextTick() await waitAF() await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() // Modal should now be closed expect($modal.element.style.display).toEqual('none') @@ -1030,7 +1038,11 @@ describe('modal', () => { render(h) { h('div', {}, [ h('button', { class: 'trigegr', attrs: { id: 'trigger', type: 'button' } }, 'trigger'), - h('button', { class: 'return-to', attrs: { id: 'return-to', type: 'button' } }, 'trigger'), + h( + 'button', + { class: 'return-to', attrs: { id: 'return-to', type: 'button' } }, + 'trigger' + ), h(BModal, { props: { id: 'test', visible: false } }, 'modal content') ]) } @@ -1048,6 +1060,8 @@ describe('modal', () => { await waitAF() await wrapper.vm.$nextTick() await waitAF() + await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() const $button = wrapper.find('button.trigger') expect($button.exists()).toBe(true) @@ -1075,6 +1089,7 @@ describe('modal', () => { await wrapper.vm.$nextTick() await waitAF() await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() // Modal should now be open expect($modal.element.style.display).toEqual('') @@ -1091,6 +1106,7 @@ describe('modal', () => { await wrapper.vm.$nextTick() await waitAF() await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() // Modal should now be closed expect($modal.element.style.display).toEqual('none') From 30c2f80f16ecdfc2d840a7a9f4eb2cb9ca443336 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 19:46:17 -0300 Subject: [PATCH 09/13] Update modal.spec.js --- src/components/modal/modal.spec.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/modal/modal.spec.js b/src/components/modal/modal.spec.js index 4b56d570cbb..50f9bbf0356 100644 --- a/src/components/modal/modal.spec.js +++ b/src/components/modal/modal.spec.js @@ -902,9 +902,15 @@ describe('modal', () => { }) describe('return focus support', () => { + const localVue = new CreateLOcalVue() + it('returns focus to document.body when no return focus set and not using v-b-toggle', async () => { + // JSDOM won't focus the document unless it has a tab index + document.body.tabIndex = 0 + const wrapper = mount(BModal, { attachToDocument: true, + localVue: localVue, stubs: { transition: false }, @@ -960,10 +966,9 @@ describe('modal', () => { }) it('returns focus to previous active element when return focus not set and not using v-b-toggle', async () => { - const localVue = new CreateLocalVue() const App = localVue.extend({ render(h) { - h('div', {}, [ + return h('div', {}, [ h('button', { class: 'trigger', attrs: { id: 'trigger', type: 'button' } }, 'trigger'), h(BModal, { props: { id: 'test', visible: false } }, 'modal content') ]) @@ -971,6 +976,7 @@ describe('modal', () => { }) const wrapper = mount(App, { attachToDocument: true, + localVue: localVue, stubs: { transition: false } @@ -1033,10 +1039,9 @@ describe('modal', () => { }) it('returns focus to element specified in toggle() method', async () => { - const localVue = new CreateLocalVue() const App = localVue.extend({ render(h) { - h('div', {}, [ + return h('div', {}, [ h('button', { class: 'trigegr', attrs: { id: 'trigger', type: 'button' } }, 'trigger'), h( 'button', @@ -1049,6 +1054,7 @@ describe('modal', () => { }) const wrapper = mount(App, { attachToDocument: true, + localVue: localVue, stubs: { transition: false } From c8dffd967d4bef660e0288a3b94db58996140c9e Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 19:49:39 -0300 Subject: [PATCH 10/13] lint --- src/components/modal/modal.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/modal/modal.spec.js b/src/components/modal/modal.spec.js index 50f9bbf0356..0cbda1cd178 100644 --- a/src/components/modal/modal.spec.js +++ b/src/components/modal/modal.spec.js @@ -902,7 +902,7 @@ describe('modal', () => { }) describe('return focus support', () => { - const localVue = new CreateLOcalVue() + const localVue = new CreateLocalVue() it('returns focus to document.body when no return focus set and not using v-b-toggle', async () => { // JSDOM won't focus the document unless it has a tab index From 2bbc417287d90829c9c8e4bf2e1eb412f6c37335 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 19:55:50 -0300 Subject: [PATCH 11/13] Update modal.spec.js --- src/components/modal/modal.spec.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/modal/modal.spec.js b/src/components/modal/modal.spec.js index 0cbda1cd178..b310822c085 100644 --- a/src/components/modal/modal.spec.js +++ b/src/components/modal/modal.spec.js @@ -1006,7 +1006,7 @@ describe('modal', () => { expect(document.activeElement).toBe($button.element) // Try and open modal via .toggle() method - $modal.vm.toggle() + wrapper.find(BModal).vm.toggle() await wrapper.vm.$nextTick() await waitAF() @@ -1022,7 +1022,7 @@ describe('modal', () => { expect($modal.element.contains(document.activeElement)).toBe(true) // Try and close modal via .toggle() - wrapper.vm.toggle() + wrapper.find(BModal).vm.toggle() await wrapper.vm.$nextTick() await waitAF() @@ -1042,7 +1042,7 @@ describe('modal', () => { const App = localVue.extend({ render(h) { return h('div', {}, [ - h('button', { class: 'trigegr', attrs: { id: 'trigger', type: 'button' } }, 'trigger'), + h('button', { class: 'trigger', attrs: { id: 'trigger', type: 'button' } }, 'trigger'), h( 'button', { class: 'return-to', attrs: { id: 'return-to', type: 'button' } }, @@ -1088,7 +1088,7 @@ describe('modal', () => { expect(document.activeElement).toBe($button.element) // Try and open modal via .toggle() method - $modal.vm.toggle('button.return-to') + wrapper.find(BModal).vm.toggle('button.return-to') await wrapper.vm.$nextTick() await waitAF() @@ -1105,7 +1105,7 @@ describe('modal', () => { expect($modal.element.contains(document.activeElement)).toBe(true) // Try and close modal via .toggle() - wrapper.vm.toggle() + wrapper.find(BModal).vm.toggle() await wrapper.vm.$nextTick() await waitAF() From 1b849bee0c9a2e109e48de6392e5c6c8b687d254 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 20:10:27 -0300 Subject: [PATCH 12/13] Update modal.spec.js --- src/components/modal/modal.spec.js | 56 +++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/components/modal/modal.spec.js b/src/components/modal/modal.spec.js index b310822c085..d6318069086 100644 --- a/src/components/modal/modal.spec.js +++ b/src/components/modal/modal.spec.js @@ -901,7 +901,7 @@ describe('modal', () => { }) }) - describe('return focus support', () => { + describe('focus management', () => { const localVue = new CreateLocalVue() it('returns focus to document.body when no return focus set and not using v-b-toggle', async () => { @@ -1120,5 +1120,59 @@ describe('modal', () => { wrapper.destroy() }) + + it('if focus leave modal it reutrns to modal', async () => { + const App = localVue.extend({ + render(h) { + return h('div', {}, [ + h('button', { class: 'trigger', attrs: { id: 'trigger', type: 'button' } }, 'trigger'), + h(BModal, { props: { id: 'test', visible: true } }, 'modal content') + ]) + } + }) + const wrapper = mount(App, { + attachToDocument: true, + localVue: localVue, + stubs: { + transition: false + } + }) + + expect(wrapper.isVueInstance()).toBe(true) + + await wrapper.vm.$nextTick() + await waitAF() + await wrapper.vm.$nextTick() + await waitAF() + await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() + + const $button = wrapper.find('button.trigger') + expect($button.exists()).toBe(true) + expect($button.is('button')).toBe(true) + + const $modal = wrapper.find('div.modal') + expect($modal.exists()).toBe(true) + + expect($modal.element.style.display).toEqual('') + expect(document.activeElement).not.toBe(document.body) + expect(document.activeElement).toBe($modal.element) + + // Try anf set focusin on external button + $button.trigger('focusin') + await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() + expect(document.activeElement).not.toBe($button.element) + expect(document.activeElement).toBe($modal.element) + + // Try anf set focusin on external button + $button.trigger('focus') + await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() + expect(document.activeElement).not.toBe($button.element) + expect(document.activeElement).toBe($modal.element) + + wrapper.destroy() + }) }) }) From be231a0510246fb3eb57925f5f6a31951242160a Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 7 Apr 2019 20:20:51 -0300 Subject: [PATCH 13/13] Update modal.js --- src/components/modal/modal.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/modal/modal.js b/src/components/modal/modal.js index bcb588952e5..650d6d7e6ca 100644 --- a/src/components/modal/modal.js +++ b/src/components/modal/modal.js @@ -613,11 +613,8 @@ export default Vue.extend({ if (inBrowser) { const modal = this.$refs.modal const activeElement = document.activeElement - if (activeElement && contains(modal, activeElement)) { - // If `activeElement` is child of modal or is modal, no need to change focus - return - } - if (modal) { + // If the modal contains the activeElement, we don't do anything + if (modal && !(activeElement && contains(modal, activeElement))) { // Make sure top of modal is showing (if longer than the viewport) // and focus the modal content wrapper this.$nextTick(() => {