From 8e2417018566fde165ab10e51403f3d13a58500d Mon Sep 17 00:00:00 2001 From: Jay Merrifield Date: Fri, 13 Jan 2017 01:02:37 -0500 Subject: [PATCH] fix(Portal) portal should take focus when open and restore when closed --- src/addons/Portal/Portal.js | 11 ++++++- test/specs/addons/Portal/Portal-test.js | 43 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/addons/Portal/Portal.js b/src/addons/Portal/Portal.js index 78f224af05..5b1d415d1a 100644 --- a/src/addons/Portal/Portal.js +++ b/src/addons/Portal/Portal.js @@ -341,7 +341,7 @@ class Portal extends Component { if (!this.state.open) return debug('renderPortal()') - const { children, className } = this.props + const { children, className, closeOnTriggerBlur } = this.props this.mountPortal() @@ -363,6 +363,14 @@ class Portal extends Component { ) this.portal = this.node.firstElementChild + // don't take focus away from portals that close on blur + if (!closeOnTriggerBlur) { + this.previousActiveElement = document.activeElement + this.portal.setAttribute('tabindex', '-1') + this.portal.style.outline = 'none' + // wait a tick for things like popups which need to calculate where the popup shows up + setTimeout(() => this.portal && this.portal.focus()) + } this.portal.addEventListener('mouseleave', this.handlePortalMouseLeave) this.portal.addEventListener('mouseenter', this.handlePortalMouseEnter) @@ -397,6 +405,7 @@ class Portal extends Component { ReactDOM.unmountComponentAtNode(this.node) this.node.parentNode.removeChild(this.node) + if (this.previousActiveElement) this.previousActiveElement.focus() this.portal.removeEventListener('mouseleave', this.handlePortalMouseLeave) this.portal.removeEventListener('mouseenter', this.handlePortalMouseEnter) diff --git a/test/specs/addons/Portal/Portal-test.js b/test/specs/addons/Portal/Portal-test.js index 8c3d6bcb3c..d2923854ce 100644 --- a/test/specs/addons/Portal/Portal-test.js +++ b/test/specs/addons/Portal/Portal-test.js @@ -509,4 +509,47 @@ describe('Portal', () => { document.body.childElementCount.should.equal(0) }) }) + + describe('focus', () => { + it('should take focus when mounted', (done) => { + attachTo = document.createElement('div') + document.body.appendChild(attachTo) + const opts = { attachTo } + const portal = wrapperMount(

Hi

, opts) + setTimeout(() => { + const portalNode = portal.node.node.firstElementChild + expect(document.activeElement).to.equal(portalNode) + expect(portalNode.getAttribute('tabindex')).to.equal('-1') + expect(portalNode.style.outline).to.equal('none') + done() + }) + }) + it('should not take focus when mounted on portals that closeOnTriggerBlur', (done) => { + attachTo = document.createElement('div') + document.body.appendChild(attachTo) + const opts = { attachTo } + const portal = wrapperMount(

Hi

, opts) + setTimeout(() => { + const portalNode = portal.node.node.firstElementChild + expect(document.activeElement).to.not.equal(portalNode) + expect(portalNode.getAttribute('tabindex')).to.not.equal('-1') + expect(portalNode.style.outline).to.not.equal('none') + done() + }) + }) + it('should restore focus when unmounted', (done) => { + const activeElement = document.activeElement + attachTo = document.createElement('div') + document.body.appendChild(attachTo) + const opts = { attachTo } + const portal = wrapperMount(

Hi

, opts) + setTimeout(() => { + portal.setProps({ + open: false, + }) + expect(document.activeElement).to.equal(activeElement) + done() + }) + }) + }) })