diff --git a/.eslintrc b/.eslintrc.js similarity index 84% rename from .eslintrc rename to .eslintrc.js index 33105a65..7cc1609c 100644 --- a/.eslintrc +++ b/.eslintrc.js @@ -1,9 +1,11 @@ -{ +module.exports = { "env": { "es6": true, "browser": true }, + "parser": "babel-eslint", + "parserOptions": { "ecmaVersion": 7, "ecmaFeatures": { @@ -11,6 +13,7 @@ }, "sourceType": "module" }, + "settings": { "react": { "createClass": "createReactClass", @@ -19,10 +22,15 @@ }, "propWrapperFunctions": [ "forbidExtraProps" ] }, - "extends": ["eslint:recommended", "plugin:react/recommended"], + + "extends": ["eslint:recommended", "plugin:react/recommended", "prettier"], + + "plugins": ["prettier"], + "globals": { "process": true }, + "rules": { "quotes": [0], "comma-dangle": [2, "only-multiline"], @@ -34,6 +42,7 @@ "arrow-parens": [0], "space-before-function-paren": [0], "jsx-a11y/no-static-element-interactions": [0], + "prettier/prettier": "error", "react/no-find-dom-node": [0], "react/jsx-closing-bracket-location": [0], "react/require-default-props": 0 diff --git a/package.json b/package.json index da8b4420..de858454 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "scripts": { "start": "./node_modules/.bin/webpack-dev-server --inline --host 127.0.0.1 --content-base examples/", "test": "cross-env NODE_ENV=test karma start", - "lint": "eslint src/ spec/" + "lint": "eslint src/ specs/" }, "authors": [ "Ryan Florence" @@ -33,7 +33,9 @@ "coveralls": "^2.13.1", "cross-env": "^5.0.1", "eslint": "^4.8.0", + "eslint-config-prettier": "^2.6.0", "eslint-plugin-jsx-a11y": "^6.0.2", + "eslint-plugin-prettier": "^2.3.1", "eslint-plugin-react": "^7.4.0", "gitbook-cli": "^2.3.0", "istanbul-instrumenter-loader": "^3.0.0", @@ -47,6 +49,7 @@ "karma-webpack": "^2.0.4", "mocha": "3.5.3", "npm-run-all": "^4.1.1", + "prettier": "^1.7.4", "react": "^16.0.0", "react-dom": "^16.0.0", "react-router": "^4.2.0", diff --git a/specs/Modal.events.spec.js b/specs/Modal.events.spec.js index 7d31aa66..c12d7016 100644 --- a/specs/Modal.events.spec.js +++ b/specs/Modal.events.spec.js @@ -1,44 +1,48 @@ /* eslint-env mocha */ -import 'should'; -import sinon from 'sinon'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import TestUtils from 'react-dom/test-utils'; -import Modal from '../src/components/Modal.js'; +import "should"; +import sinon from "sinon"; import { - moverlay, mcontent, - clickAt, mouseDownAt, mouseUpAt, escKeyDown, tabKeyDown, - renderModal, emptyDOM -} from './helper'; + moverlay, + mcontent, + clickAt, + mouseDownAt, + mouseUpAt, + escKeyDown, + tabKeyDown, + renderModal, + emptyDOM +} from "./helper"; export default () => { - afterEach('Unmount modal', emptyDOM); + afterEach("Unmount modal", emptyDOM); - it('should trigger the onAfterOpen callback', () => { + it("should trigger the onAfterOpen callback", () => { const afterOpenCallback = sinon.spy(); renderModal({ isOpen: true, onAfterOpen: afterOpenCallback }); afterOpenCallback.called.should.be.ok(); }); - it('keeps focus inside the modal when child has no tabbable elements', () => { + it("keeps focus inside the modal when child has no tabbable elements", () => { let tabPrevented = false; - const modal = renderModal({ isOpen: true }, 'hello'); + const modal = renderModal({ isOpen: true }, "hello"); const content = mcontent(modal); document.activeElement.should.be.eql(content); tabKeyDown(content, { - preventDefault() { tabPrevented = true; } + preventDefault() { + tabPrevented = true; + } }); tabPrevented.should.be.eql(true); }); - it('handles case when child has no tabbable elements', () => { - const modal = renderModal({ isOpen: true }, 'hello'); + it("handles case when child has no tabbable elements", () => { + const modal = renderModal({ isOpen: true }, "hello"); const content = mcontent(modal); tabKeyDown(content); document.activeElement.should.be.eql(content); }); - it('should close on Esc key event', () => { + it("should close on Esc key event", () => { const requestCloseCallback = sinon.spy(); const modal = renderModal({ isOpen: true, @@ -52,8 +56,8 @@ export default () => { ev.should.be.ok(); }); - describe('shouldCloseOnoverlayClick', () => { - it('when false, click on overlay should not close', () => { + describe("shouldCloseOnoverlayClick", () => { + it("when false, click on overlay should not close", () => { const requestCloseCallback = sinon.spy(); const modal = renderModal({ isOpen: true, @@ -64,7 +68,7 @@ export default () => { requestCloseCallback.called.should.not.be.ok(); }); - it('when true, click on overlay must close', () => { + it("when true, click on overlay must close", () => { const requestCloseCallback = sinon.spy(); const modal = renderModal({ isOpen: true, @@ -75,7 +79,7 @@ export default () => { requestCloseCallback.called.should.be.ok(); }); - it('overlay mouse down and content mouse up, should not close', () => { + it("overlay mouse down and content mouse up, should not close", () => { const requestCloseCallback = sinon.spy(); const modal = renderModal({ isOpen: true, @@ -87,7 +91,7 @@ export default () => { requestCloseCallback.called.should.not.be.ok(); }); - it('content mouse down and overlay mouse up, should not close', () => { + it("content mouse down and overlay mouse up, should not close", () => { const requestCloseCallback = sinon.spy(); const modal = renderModal({ isOpen: true, @@ -100,20 +104,20 @@ export default () => { }); }); - it('should not stop event propagation', () => { + it("should not stop event propagation", () => { let hasPropagated = false; const modal = renderModal({ isOpen: true, shouldCloseOnOverlayClick: true }); - window.addEventListener('click', () => { + window.addEventListener("click", () => { hasPropagated = true; }); - moverlay(modal).dispatchEvent(new MouseEvent('click', { bubbles: true })); + moverlay(modal).dispatchEvent(new MouseEvent("click", { bubbles: true })); hasPropagated.should.be.ok(); }); - it('verify event passing on overlay click', () => { + it("verify event passing on overlay click", () => { const requestCloseCallback = sinon.spy(); const modal = renderModal({ isOpen: true, @@ -123,7 +127,7 @@ export default () => { // click the overlay clickAt(moverlay(modal), { // Used to test that this was the event received - fakeData: 'ABC' + fakeData: "ABC" }); requestCloseCallback.called.should.be.ok(); // Check if event is passed to onRequestClose callback. diff --git a/specs/Modal.spec.js b/specs/Modal.spec.js index e8b637f1..5da1371a 100644 --- a/specs/Modal.spec.js +++ b/specs/Modal.spec.js @@ -1,44 +1,47 @@ /* eslint-env mocha */ -import 'should'; -import should from 'should'; -import React, { Component } from 'react'; -import ReactDOM from 'react-dom'; -import TestUtils from 'react-dom/test-utils'; -import Modal from 'react-modal'; -import * as ariaAppHider from 'react-modal/helpers/ariaAppHider'; +import "should"; +import should from "should"; +import React, { Component } from "react"; +import ReactDOM from "react-dom"; +import Modal from "react-modal"; +import * as ariaAppHider from "react-modal/helpers/ariaAppHider"; import { isBodyWithReactModalOpenClass, contentAttribute, - mcontent, moverlay, + mcontent, + moverlay, escKeyDown, - renderModal, unmountModal, emptyDOM -} from './helper'; + renderModal, + unmountModal, + emptyDOM +} from "./helper"; export default () => { - afterEach('check if test cleaned up rendered modals', emptyDOM); + afterEach("check if test cleaned up rendered modals", emptyDOM); - it('scopes tab navigation to the modal'); - it('focuses the last focused element when tabbing in from browser chrome'); - it('renders children [tested indirectly]'); + it("scopes tab navigation to the modal"); + it("focuses the last focused element when tabbing in from browser chrome"); + it("renders children [tested indirectly]"); - it('can be open initially', () => { - const modal = renderModal({ isOpen: true }, 'hello'); + it("can be open initially", () => { + const modal = renderModal({ isOpen: true }, "hello"); mcontent(modal).should.be.ok(); }); - it('can be closed initially', () => { - const modal = renderModal({}, 'hello'); + it("can be closed initially", () => { + const modal = renderModal({}, "hello"); should(ReactDOM.findDOMNode(mcontent(modal))).not.be.ok(); }); - it('doesn\'t render the portal if modal is closed', () => { - const modal = renderModal({}, 'hello'); + it("doesn't render the portal if modal is closed", () => { + const modal = renderModal({}, "hello"); should(ReactDOM.findDOMNode(modal.portal)).not.be.ok(); }); - it('has default props', () => { - const node = document.createElement('div'); - Modal.setAppElement(document.createElement('div')); + it("has default props", () => { + const node = document.createElement("div"); + Modal.setAppElement(document.createElement("div")); + // eslint-disable-next-line react/no-render-return-value const modal = ReactDOM.render(, node); const props = modal.props; props.isOpen.should.not.be.ok(); @@ -48,21 +51,19 @@ export default () => { props.shouldCloseOnOverlayClick.should.be.ok(); ReactDOM.unmountComponentAtNode(node); ariaAppHider.resetForTesting(); - Modal.setAppElement(document.body); // restore default + Modal.setAppElement(document.body); // restore default }); - it('accepts appElement as a prop', () => { - const el = document.createElement('div'); - const node = document.createElement('div'); - ReactDOM.render(( - - ), node); - el.getAttribute('aria-hidden').should.be.eql('true'); + it("accepts appElement as a prop", () => { + const el = document.createElement("div"); + const node = document.createElement("div"); + ReactDOM.render(, node); + el.getAttribute("aria-hidden").should.be.eql("true"); ReactDOM.unmountComponentAtNode(node); }); - it('renders into the body, not in context', () => { - const node = document.createElement('div'); + it("renders into the body, not in context", () => { + const node = document.createElement("div"); class App extends Component { render() { return ( @@ -76,47 +77,48 @@ export default () => { } Modal.setAppElement(node); ReactDOM.render(, node); - document.body.querySelector( - '.ReactModalPortal' - ).parentNode.should.be.eql( - document.body - ); + document.body + .querySelector(".ReactModalPortal") + .parentNode.should.be.eql(document.body); ReactDOM.unmountComponentAtNode(node); }); - it ('default parentSelector should be document.body.', () => { + it("default parentSelector should be document.body.", () => { const modal = renderModal({ isOpen: true }); modal.props.parentSelector().should.be.eql(document.body); }); - it('renders the modal content with a dialog aria role when provided ', () => { - const child = 'I am a child of Modal, and he has sent me here...'; - const modal = renderModal({ isOpen: true, role: 'dialog' }, child); - contentAttribute(modal, 'role').should.be.eql('dialog'); + it("renders the modal content with a dialog aria role when provided ", () => { + const child = "I am a child of Modal, and he has sent me here..."; + const modal = renderModal({ isOpen: true, role: "dialog" }, child); + contentAttribute(modal, "role").should.be.eql("dialog"); }); - it('sets aria-label based on the contentLabel prop', () => { - const child = 'I am a child of Modal, and he has sent me here...'; - const modal = renderModal({ - isOpen: true, - contentLabel: 'Special Modal' - }, child); + it("sets aria-label based on the contentLabel prop", () => { + const child = "I am a child of Modal, and he has sent me here..."; + const modal = renderModal( + { + isOpen: true, + contentLabel: "Special Modal" + }, + child + ); - contentAttribute(modal, 'aria-label').should.be.eql('Special Modal'); + contentAttribute(modal, "aria-label").should.be.eql("Special Modal"); }); - it('removes the portal node', () => { - const modal = renderModal({ isOpen: true }, 'hello'); + it("removes the portal node", () => { + renderModal({ isOpen: true }, "hello"); unmountModal(); - should(document.querySelector('.ReactModalPortal')).not.be.ok(); + should(document.querySelector(".ReactModalPortal")).not.be.ok(); }); - it('removes the portal node after closeTimeoutMS', done => { + it("removes the portal node after closeTimeoutMS", done => { const closeTimeoutMS = 100; - renderModal({ isOpen: true, closeTimeoutMS }, 'hello'); + renderModal({ isOpen: true, closeTimeoutMS }, "hello"); function checkDOM(count) { - const portal = document.querySelectorAll('.ReactModalPortal'); + const portal = document.querySelectorAll(".ReactModalPortal"); portal.length.should.be.eql(count); } @@ -132,132 +134,145 @@ export default () => { }, closeTimeoutMS); }); - it('focuses the modal content by default', () => { + it("focuses the modal content by default", () => { const modal = renderModal({ isOpen: true }, null); document.activeElement.should.be.eql(mcontent(modal)); }); - it('does not focus the modal content when shouldFocusAfterRender is false', () => { - const modal = renderModal({ isOpen: true, shouldFocusAfterRender: false }, null); + it("does not focus the modal content when shouldFocusAfterRender is false", () => { + const modal = renderModal( + { isOpen: true, shouldFocusAfterRender: false }, + null + ); document.activeElement.should.not.be.eql(mcontent(modal)); }); - it('give back focus to previous element or modal.', done => { - function cleanup () { + it("give back focus to previous element or modal.", done => { + function cleanup() { unmountModal(); done(); } - const modalA = renderModal({ - isOpen: true, - className: 'modal-a', - onRequestClose: cleanup - }, null); + const modalA = renderModal( + { + isOpen: true, + className: "modal-a", + onRequestClose: cleanup + }, + null + ); const modalContent = mcontent(modalA); document.activeElement.should.be.eql(modalContent); - const modalB = renderModal({ - isOpen: true, - className: 'modal-b', - onRequestClose() { - const modalContent = mcontent(modalB); - document.activeElement.should.be.eql(mcontent(modalA)); - escKeyDown(modalContent); - document.activeElement.should.be.eql(modalContent); - } - }, null); + const modalB = renderModal( + { + isOpen: true, + className: "modal-b", + onRequestClose() { + const modalContent = mcontent(modalB); + document.activeElement.should.be.eql(mcontent(modalA)); + escKeyDown(modalContent); + document.activeElement.should.be.eql(modalContent); + } + }, + null + ); escKeyDown(modalContent); }); - xit('does not steel focus when a descendent is already focused', () => { + xit("does not steel focus when a descendent is already focused", () => { let content; const input = ( - { el && el.focus(); content = el; }} /> + { + el && el.focus(); + content = el; + }} + /> ); renderModal({ isOpen: true }, input, () => { document.activeElement.should.be.eql(content); }); }); - it('supports portalClassName', () => { + it("supports portalClassName", () => { const modal = renderModal({ isOpen: true, - portalClassName: 'myPortalClass' + portalClassName: "myPortalClass" }); - modal.node.className.includes('myPortalClass').should.be.ok(); + modal.node.className.includes("myPortalClass").should.be.ok(); }); - it('supports custom className', () => { - const modal = renderModal({ isOpen: true, className: 'myClass' }); - mcontent(modal).className.includes('myClass').should.be.ok(); + it("supports custom className", () => { + const modal = renderModal({ isOpen: true, className: "myClass" }); + mcontent(modal) + .className.includes("myClass") + .should.be.ok(); }); - it('supports overlayClassName', () => { + it("supports overlayClassName", () => { const modal = renderModal({ isOpen: true, - overlayClassName: 'myOverlayClass' + overlayClassName: "myOverlayClass" }); - moverlay(modal).className.includes('myOverlayClass').should.be.ok(); + moverlay(modal) + .className.includes("myOverlayClass") + .should.be.ok(); }); - it('overrides content classes with custom object className', () => { + it("overrides content classes with custom object className", () => { const modal = renderModal({ isOpen: true, className: { - base: 'myClass', - afterOpen: 'myClass_after-open', - beforeClose: 'myClass_before-close' + base: "myClass", + afterOpen: "myClass_after-open", + beforeClose: "myClass_before-close" } }); - mcontent(modal).className.should.be.eql( - 'myClass myClass_after-open' - ); + mcontent(modal).className.should.be.eql("myClass myClass_after-open"); unmountModal(); }); - it('overrides overlay classes with custom object overlayClassName', () => { + it("overrides overlay classes with custom object overlayClassName", () => { const modal = renderModal({ isOpen: true, overlayClassName: { - base: 'myOverlayClass', - afterOpen: 'myOverlayClass_after-open', - beforeClose: 'myOverlayClass_before-close' + base: "myOverlayClass", + afterOpen: "myOverlayClass_after-open", + beforeClose: "myOverlayClass_before-close" } }); moverlay(modal).className.should.be.eql( - 'myOverlayClass myOverlayClass_after-open' + "myOverlayClass myOverlayClass_after-open" ); unmountModal(); }); - it('supports overriding react modal open class in document.body.', () => { - const modal = renderModal({ - isOpen: true, - bodyOpenClassName: 'custom-modal-open' - }); - (document.body.className.indexOf('custom-modal-open') > -1).should.be.ok(); + it("supports overriding react modal open class in document.body.", () => { + renderModal({ isOpen: true, bodyOpenClassName: "custom-modal-open" }); + (document.body.className.indexOf("custom-modal-open") > -1).should.be.ok(); }); - it('don\'t append class to document.body if modal is not open', () => { + it("don't append class to document.body if modal is not open", () => { renderModal({ isOpen: false }); isBodyWithReactModalOpenClass().should.not.be.ok(); unmountModal(); }); - it('append class to document.body if modal is open', () => { + it("append class to document.body if modal is open", () => { renderModal({ isOpen: true }); isBodyWithReactModalOpenClass().should.be.ok(); unmountModal(); }); - it('removes class from document.body when unmounted without closing', () => { + it("removes class from document.body when unmounted without closing", () => { renderModal({ isOpen: true }); unmountModal(); isBodyWithReactModalOpenClass().should.not.be.ok(); }); - it('remove class from document.body when no modals opened', () => { + it("remove class from document.body when no modals opened", () => { renderModal({ isOpen: true }); renderModal({ isOpen: true }); isBodyWithReactModalOpenClass().should.be.ok(); @@ -267,93 +282,94 @@ export default () => { isBodyWithReactModalOpenClass().should.not.be.ok(); }); - it('supports adding/removing multiple document.body classes', () => { + it("supports adding/removing multiple document.body classes", () => { renderModal({ isOpen: true, - bodyOpenClassName: 'A B C' + bodyOpenClassName: "A B C" }); - document.body.classList.contains('A', 'B', 'C').should.be.ok(); + document.body.classList.contains("A", "B", "C").should.be.ok(); unmountModal(); - document.body.classList.contains('A', 'B', 'C').should.not.be.ok(); + document.body.classList.contains("A", "B", "C").should.not.be.ok(); }); - it('does not remove shared classes if more than one modal is open', () => { + it("does not remove shared classes if more than one modal is open", () => { renderModal({ isOpen: true, - bodyOpenClassName: 'A' + bodyOpenClassName: "A" }); renderModal({ isOpen: true, - bodyOpenClassName: 'A B' + bodyOpenClassName: "A B" }); - isBodyWithReactModalOpenClass('A B').should.be.ok(); + isBodyWithReactModalOpenClass("A B").should.be.ok(); unmountModal(); - isBodyWithReactModalOpenClass('A B').should.not.be.ok(); - isBodyWithReactModalOpenClass('A').should.be.ok(); + isBodyWithReactModalOpenClass("A B").should.not.be.ok(); + isBodyWithReactModalOpenClass("A").should.be.ok(); unmountModal(); - isBodyWithReactModalOpenClass('A').should.not.be.ok(); + isBodyWithReactModalOpenClass("A").should.not.be.ok(); }); - it('should not add classes to document.body for unopened modals', () => { + it("should not add classes to document.body for unopened modals", () => { renderModal({ isOpen: true }); isBodyWithReactModalOpenClass().should.be.ok(); - renderModal({ isOpen: false, bodyOpenClassName: 'testBodyClass' }); - isBodyWithReactModalOpenClass('testBodyClass').should.not.be.ok(); + renderModal({ isOpen: false, bodyOpenClassName: "testBodyClass" }); + isBodyWithReactModalOpenClass("testBodyClass").should.not.be.ok(); }); - it('should not remove classes from document.body when rendering unopened modal', () => { + it("should not remove classes from document.body when rendering unopened modal", () => { renderModal({ isOpen: true }); isBodyWithReactModalOpenClass().should.be.ok(); - renderModal({ isOpen: false, bodyOpenClassName: 'testBodyClass' }); + renderModal({ isOpen: false, bodyOpenClassName: "testBodyClass" }); renderModal({ isOpen: false }); - isBodyWithReactModalOpenClass('testBodyClass').should.not.be.ok(); + isBodyWithReactModalOpenClass("testBodyClass").should.not.be.ok(); isBodyWithReactModalOpenClass().should.be.ok(); renderModal({ isOpen: false }); renderModal({ isOpen: false }); isBodyWithReactModalOpenClass().should.be.ok(); }); - it('additional aria attributes', () => { - const modal = renderModal({ isOpen: true, aria: { labelledby: "a" }}, 'hello'); - mcontent(modal).getAttribute('aria-labelledby').should.be.eql("a"); + it("additional aria attributes", () => { + const modal = renderModal( + { isOpen: true, aria: { labelledby: "a" } }, + "hello" + ); + mcontent(modal) + .getAttribute("aria-labelledby") + .should.be.eql("a"); unmountModal(); }); - it('adding/removing aria-hidden without an appElement will try to fallback to document.body', () => { + it("adding/removing aria-hidden without an appElement will try to fallback to document.body", () => { ariaAppHider.documentNotReadyOrSSRTesting(); - const node = document.createElement('div'); - ReactDOM.render(( - - ), node); - document.body.getAttribute('aria-hidden').should.be.eql('true'); + const node = document.createElement("div"); + ReactDOM.render(, node); + document.body.getAttribute("aria-hidden").should.be.eql("true"); ReactDOM.unmountComponentAtNode(node); - should(document.body.getAttribute('aria-hidden')).not.be.ok(); + should(document.body.getAttribute("aria-hidden")).not.be.ok(); }); - it('raise an exception if appElement is a selector and no elements were found.', () => { - should(() => ariaAppHider.setElement('.test')).throw(); + it("raise an exception if appElement is a selector and no elements were found.", () => { + should(() => ariaAppHider.setElement(".test")).throw(); }); - it('removes aria-hidden from appElement when unmounted w/o closing', () => { - const el = document.createElement('div'); - const node = document.createElement('div'); - ReactDOM.render(( - - ), node); - el.getAttribute('aria-hidden').should.be.eql('true'); + it("removes aria-hidden from appElement when unmounted w/o closing", () => { + const el = document.createElement("div"); + const node = document.createElement("div"); + ReactDOM.render(, node); + el.getAttribute("aria-hidden").should.be.eql("true"); ReactDOM.unmountComponentAtNode(node); - should(el.getAttribute('aria-hidden')).not.be.ok(); + should(el.getAttribute("aria-hidden")).not.be.ok(); }); - it('adds --after-open for animations', () => { + it("adds --after-open for animations", () => { const modal = renderModal({ isOpen: true }); const rg = /--after-open/i; rg.test(mcontent(modal).className).should.be.ok(); rg.test(moverlay(modal).className).should.be.ok(); }); - it('adds --before-close for animations', () => { + it("adds --before-close for animations", () => { const closeTimeoutMS = 50; const modal = renderModal({ isOpen: true, @@ -368,7 +384,7 @@ export default () => { modal.portal.closeWithoutTimeout(); }); - it('should not be open after close with time out and reopen it', () => { + it("should not be open after close with time out and reopen it", () => { const modal = renderModal({ isOpen: true, closeTimeoutMS: 2000, @@ -380,26 +396,26 @@ export default () => { modal.portal.state.isOpen.should.not.be.ok(); }); - it('verify default prop of shouldCloseOnOverlayClick', () => { + it("verify default prop of shouldCloseOnOverlayClick", () => { const modal = renderModal({ isOpen: true }); modal.props.shouldCloseOnOverlayClick.should.be.ok(); }); - it('verify prop of shouldCloseOnOverlayClick', () => { + it("verify prop of shouldCloseOnOverlayClick", () => { const modalOpts = { isOpen: true, shouldCloseOnOverlayClick: false }; const modal = renderModal(modalOpts); modal.props.shouldCloseOnOverlayClick.should.not.be.ok(); }); - it('keeps the modal in the DOM until closeTimeoutMS elapses', done => { + it("keeps the modal in the DOM until closeTimeoutMS elapses", done => { const closeTimeoutMS = 100; const modal = renderModal({ isOpen: true, closeTimeoutMS }); modal.portal.closeWithTimeout(); function checkDOM(count) { - const overlay = document.querySelectorAll('.ReactModal__Overlay'); - const content = document.querySelectorAll('.ReactModal__Content'); + const overlay = document.querySelectorAll(".ReactModal__Overlay"); + const content = document.querySelectorAll(".ReactModal__Content"); overlay.length.should.be.eql(count); content.length.should.be.eql(count); } @@ -414,23 +430,25 @@ export default () => { }, closeTimeoutMS); }); - xit('shouldn\'t throw if forcibly unmounted during mounting', () => { + xit("shouldn't throw if forcibly unmounted during mounting", () => { /* eslint-disable camelcase, react/prop-types */ class Wrapper extends Component { - constructor (props) { + constructor(props) { super(props); this.state = { error: false }; } - unstable_handleError () { + unstable_handleError() { this.setState({ error: true }); } - render () { - return this.state.error ? null :
{ this.props.children }
; + render() { + return this.state.error ? null :
{this.props.children}
; } } /* eslint-enable camelcase, react/prop-types */ - const Throw = () => { throw new Error('reason'); }; + const Throw = () => { + throw new Error("reason"); + }; const TestCase = () => ( @@ -438,17 +456,18 @@ export default () => { ); - const currentDiv = document.createElement('div'); + const currentDiv = document.createElement("div"); document.body.appendChild(currentDiv); + // eslint-disable-next-line react/no-render-return-value const mount = () => ReactDOM.render(, currentDiv); mount.should.not.throw(); document.body.removeChild(currentDiv); }); - it('verify that portalClassName is refreshed on component update', () => { - const node = document.createElement('div'); + it("verify that portalClassName is refreshed on component update", () => { + const node = document.createElement("div"); let modal = null; class App extends Component { @@ -458,7 +477,7 @@ export default () => { } componentDidMount() { - modal.node.className.should.be.eql('myPortalClass'); + modal.node.className.should.be.eql("myPortalClass"); this.setState({ testHasChanged: true @@ -466,19 +485,24 @@ export default () => { } componentDidUpdate() { - modal.node.className.should.be.eql('myPortalClass-modifier'); + modal.node.className.should.be.eql("myPortalClass-modifier"); } render() { - const portalClassName = this.state.testHasChanged === true ? - 'myPortalClass-modifier' : 'myPortalClass'; + const portalClassName = + this.state.testHasChanged === true + ? "myPortalClass-modifier" + : "myPortalClass"; return (
{ modal = modalComponent; }} + ref={modalComponent => { + modal = modalComponent; + }} isOpen - portalClassName={portalClassName}> + portalClassName={portalClassName} + > Test
diff --git a/specs/Modal.style.spec.js b/specs/Modal.style.spec.js index 59a8ddf5..2b17355f 100644 --- a/specs/Modal.style.spec.js +++ b/specs/Modal.style.spec.js @@ -1,62 +1,53 @@ /* eslint-env mocha */ -import 'should'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import TestUtils from 'react-dom/test-utils'; -import Modal from 'react-modal'; -import * as ariaAppHider from 'react-modal/helpers/ariaAppHider'; -import { - mcontent, moverlay, - renderModal, emptyDOM -} from './helper'; +import "should"; +import Modal from "react-modal"; +import { mcontent, moverlay, renderModal, emptyDOM } from "./helper"; export default () => { - afterEach('Unmount modal', emptyDOM); + afterEach("Unmount modal", emptyDOM); - it('overrides the default styles when a custom classname is used', () => { - const modal = renderModal({ isOpen: true, className: 'myClass' }); - mcontent(modal).style.top.should.be.eql(''); + it("overrides the default styles when a custom classname is used", () => { + const modal = renderModal({ isOpen: true, className: "myClass" }); + mcontent(modal).style.top.should.be.eql(""); }); - it('overrides the default styles when a custom overlayClassName is used', - () => { - const modal = renderModal({ - isOpen: true, - overlayClassName: 'myOverlayClass' - }); - moverlay(modal).style.backgroundColor.should.be.eql(''); - } - ); + it("overrides the default styles when a custom overlayClassName is used", () => { + const modal = renderModal({ + isOpen: true, + overlayClassName: "myOverlayClass" + }); + moverlay(modal).style.backgroundColor.should.be.eql(""); + }); - it('supports adding style to the modal contents', () => { - const style = { content: { width: '20px' } }; + it("supports adding style to the modal contents", () => { + const style = { content: { width: "20px" } }; const modal = renderModal({ isOpen: true, style }); - mcontent(modal).style.width.should.be.eql('20px'); + mcontent(modal).style.width.should.be.eql("20px"); }); - it('supports overriding style on the modal contents', () => { - const style = { content: { position: 'static' } }; + it("supports overriding style on the modal contents", () => { + const style = { content: { position: "static" } }; const modal = renderModal({ isOpen: true, style }); - mcontent(modal).style.position.should.be.eql('static'); + mcontent(modal).style.position.should.be.eql("static"); }); - it('supports adding style on the modal overlay', () => { - const style = { overlay: { width: '75px' } }; + it("supports adding style on the modal overlay", () => { + const style = { overlay: { width: "75px" } }; const modal = renderModal({ isOpen: true, style }); - moverlay(modal).style.width.should.be.eql('75px'); + moverlay(modal).style.width.should.be.eql("75px"); }); - it('supports overriding style on the modal overlay', () => { - const style = { overlay: { position: 'static' } }; + it("supports overriding style on the modal overlay", () => { + const style = { overlay: { position: "static" } }; const modal = renderModal({ isOpen: true, style }); - moverlay(modal).style.position.should.be.eql('static'); + moverlay(modal).style.position.should.be.eql("static"); }); - it('supports overriding the default styles', () => { + it("supports overriding the default styles", () => { const previousStyle = Modal.defaultStyles.content.position; // Just in case the default style is already relative, // check that we can change it - const newStyle = previousStyle === 'relative' ? 'static' : 'relative'; + const newStyle = previousStyle === "relative" ? "static" : "relative"; Modal.defaultStyles.content.position = newStyle; const modal = renderModal({ isOpen: true }); modal.portal.content.style.position.should.be.eql(newStyle); diff --git a/specs/helper.js b/specs/helper.js index 0af99a26..e840ae97 100644 --- a/specs/helper.js +++ b/specs/helper.js @@ -1,7 +1,7 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import Modal, { bodyOpenClassName } from '../src/components/Modal'; -import TestUtils from 'react-dom/test-utils'; +import React from "react"; +import ReactDOM from "react-dom"; +import Modal, { bodyOpenClassName } from "../src/components/Modal"; +import TestUtils from "react-dom/test-utils"; const divStack = []; @@ -10,7 +10,7 @@ const divStack = []; */ if (!String.prototype.includes) { String.prototype.includes = function(search, start) { - if (typeof start !== 'number') { + if (typeof start !== "number") { start = 0; } @@ -51,43 +51,51 @@ const getModalAttribute = component => (instance, attr) => * @param {React} A react instance. * @return {DOMElement} */ -const modalComponent = component => instance => - instance.portal[component]; +const modalComponent = component => instance => instance.portal[component]; /** * Returns the modal content. * @param {Modal} modal Modal instance. * @return {DOMElement} */ -export const mcontent = modalComponent('content'); +export const mcontent = modalComponent("content"); /** * Returns the modal overlay. * @param {Modal} modal Modal instance. * @return {DOMElement} */ -export const moverlay = modalComponent('overlay'); +export const moverlay = modalComponent("overlay"); /** * Return an attribute of modal content. * @param {Modal} modal Modal instance. * @return {String} */ -export const contentAttribute = getModalAttribute('content'); +export const contentAttribute = getModalAttribute("content"); /** * Return an attribute of modal overlay. * @param {Modal} modal Modal instance. * @return {String} */ -export const overlayAttribute = getModalAttribute('overlay'); +export const overlayAttribute = getModalAttribute("overlay"); const Simulate = TestUtils.Simulate; const dispatchMockEvent = eventCtor => (key, code) => (element, opts) => - eventCtor(element, Object.assign({}, { - key: key, keyCode: code, which: code - }, opts)); + eventCtor( + element, + Object.assign( + {}, + { + key: key, + keyCode: code, + which: code + }, + opts + ) + ); const dispatchMockKeyDownEvent = dispatchMockEvent(Simulate.keyDown); @@ -114,13 +122,16 @@ export const mouseDownAt = Simulate.mouseDown; export const renderModal = function(props, children, callback) { props.ariaHideApp = false; - const currentDiv = document.createElement('div'); + const currentDiv = document.createElement("div"); divStack.push(currentDiv); document.body.appendChild(currentDiv); + // eslint-disable-next-line react/no-render-return-value return ReactDOM.render( - {children} - , currentDiv, callback); + {children}, + currentDiv, + callback + ); }; export const unmountModal = function() { diff --git a/specs/index.js b/specs/index.js index 9fdd1f47..f640547c 100644 --- a/specs/index.js +++ b/specs/index.js @@ -1,9 +1,9 @@ /* eslint-env mocha */ -import ModalState from './Modal.spec'; -import ModalEvents from './Modal.events.spec'; -import ModalStyle from './Modal.style.spec'; +import ModalState from "./Modal.spec"; +import ModalEvents from "./Modal.events.spec"; +import ModalStyle from "./Modal.style.spec"; -describe('State', ModalState); -describe('Style', ModalStyle); -describe('Events', ModalEvents); +describe("State", ModalState); +describe("Style", ModalStyle); +describe("Events", ModalEvents); diff --git a/src/components/Modal.js b/src/components/Modal.js index 8fbfdf44..104d83a0 100644 --- a/src/components/Modal.js +++ b/src/components/Modal.js @@ -1,19 +1,17 @@ -import React, { Component } from 'react'; -import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -import ModalPortal from './ModalPortal'; -import * as ariaAppHider from '../helpers/ariaAppHider'; -import SafeHTMLElement, { - canUseDOM -} from '../helpers/safeHTMLElement'; - -export const portalClassName = 'ReactModalPortal'; -export const bodyOpenClassName = 'ReactModal__Body--open'; +import React, { Component } from "react"; +import ReactDOM from "react-dom"; +import PropTypes from "prop-types"; +import ModalPortal from "./ModalPortal"; +import * as ariaAppHider from "../helpers/ariaAppHider"; +import SafeHTMLElement, { canUseDOM } from "../helpers/safeHTMLElement"; + +export const portalClassName = "ReactModalPortal"; +export const bodyOpenClassName = "ReactModal__Body--open"; const isReact16 = ReactDOM.createPortal !== undefined; -const createPortal = isReact16 ? - ReactDOM.createPortal : - ReactDOM.unstable_renderSubtreeIntoContainer; +const createPortal = isReact16 + ? ReactDOM.createPortal + : ReactDOM.unstable_renderSubtreeIntoContainer; function getParentElement(parentSelector) { return parentSelector(); @@ -33,14 +31,8 @@ export default class Modal extends Component { }), portalClassName: PropTypes.string, bodyOpenClassName: PropTypes.string, - className: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.object - ]), - overlayClassName: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.object - ]), + className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + overlayClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), appElement: PropTypes.instanceOf(SafeHTMLElement), onAfterOpen: PropTypes.func, onRequestClose: PropTypes.func, @@ -63,31 +55,33 @@ export default class Modal extends Component { closeTimeoutMS: 0, shouldFocusAfterRender: true, shouldCloseOnOverlayClick: true, - parentSelector() { return document.body; } + parentSelector() { + return document.body; + } }; static defaultStyles = { overlay: { - position: 'fixed', + position: "fixed", top: 0, left: 0, right: 0, bottom: 0, - backgroundColor: 'rgba(255, 255, 255, 0.75)' + backgroundColor: "rgba(255, 255, 255, 0.75)" }, content: { - position: 'absolute', - top: '40px', - left: '40px', - right: '40px', - bottom: '40px', - border: '1px solid #ccc', - background: '#fff', - overflow: 'auto', - WebkitOverflowScrolling: 'touch', - borderRadius: '4px', - outline: 'none', - padding: '20px' + position: "absolute", + top: "40px", + left: "40px", + right: "40px", + bottom: "40px", + border: "1px solid #ccc", + background: "#fff", + overflow: "auto", + WebkitOverflowScrolling: "touch", + borderRadius: "4px", + outline: "none", + padding: "20px" } }; @@ -95,14 +89,14 @@ export default class Modal extends Component { if (!canUseDOM) return; if (!isReact16) { - this.node = document.createElement('div'); + this.node = document.createElement("div"); } this.node.className = this.props.portalClassName; const parent = getParentElement(this.props.parentSelector); parent.appendChild(this.node); - (!isReact16) && this.renderPortal(this.props); + !isReact16 && this.renderPortal(this.props); } componentWillReceiveProps(newProps) { @@ -119,7 +113,7 @@ export default class Modal extends Component { newParent.appendChild(this.node); } - (!isReact16) && this.renderPortal(newProps); + !isReact16 && this.renderPortal(newProps); } componentWillUpdate(newProps) { @@ -134,9 +128,10 @@ export default class Modal extends Component { const state = this.portal.state; const now = Date.now(); - const closesAt = state.isOpen && this.props.closeTimeoutMS - && (state.closesAt - || now + this.props.closeTimeoutMS); + const closesAt = + state.isOpen && + this.props.closeTimeoutMS && + (state.closesAt || now + this.props.closeTimeoutMS); if (closesAt) { if (!state.beforeClose) { @@ -150,19 +145,23 @@ export default class Modal extends Component { } removePortal = () => { - (!isReact16) && ReactDOM.unmountComponentAtNode(this.node); + !isReact16 && ReactDOM.unmountComponentAtNode(this.node); const parent = getParentElement(this.props.parentSelector); parent.removeChild(this.node); - } + }; - portalRef = ref => { this.portal = ref; } + portalRef = ref => { + this.portal = ref; + }; renderPortal = props => { - const portal = createPortal(this, ( - - ), this.node); + const portal = createPortal( + this, + , + this.node + ); this.portalRef(portal); - } + }; render() { if (!canUseDOM || !isReact16) { @@ -170,13 +169,15 @@ export default class Modal extends Component { } if (!this.node && isReact16) { - this.node = document.createElement('div'); + this.node = document.createElement("div"); } return createPortal( - , + , this.node ); } diff --git a/src/components/ModalPortal.js b/src/components/ModalPortal.js index 269fc34b..5aeb7b5c 100644 --- a/src/components/ModalPortal.js +++ b/src/components/ModalPortal.js @@ -1,16 +1,16 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import * as focusManager from '../helpers/focusManager'; -import scopeTab from '../helpers/scopeTab'; -import * as ariaAppHider from '../helpers/ariaAppHider'; -import * as refCount from '../helpers/refCount'; -import * as bodyClassList from '../helpers/bodyClassList'; -import SafeHTMLElement from '../helpers/safeHTMLElement'; +import React, { Component } from "react"; +import { PropTypes } from "prop-types"; +import * as focusManager from "../helpers/focusManager"; +import scopeTab from "../helpers/scopeTab"; +import * as ariaAppHider from "../helpers/ariaAppHider"; +import * as refCount from "../helpers/refCount"; +import * as bodyClassList from "../helpers/bodyClassList"; +import SafeHTMLElement from "../helpers/safeHTMLElement"; // so that our CSS is statically analyzable const CLASS_NAMES = { - overlay: 'ReactModal__Overlay', - content: 'ReactModal__Content' + overlay: "ReactModal__Overlay", + content: "ReactModal__Content" }; const TAB_KEY = 9; @@ -34,14 +34,8 @@ export default class ModalPortal extends Component { content: PropTypes.object, overlay: PropTypes.object }), - className: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.object - ]), - overlayClassName: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.object - ]), + className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + overlayClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), bodyOpenClassName: PropTypes.string, ariaHideApp: PropTypes.bool, appElement: PropTypes.instanceOf(SafeHTMLElement), @@ -82,7 +76,7 @@ export default class ModalPortal extends Component { // eslint-disable-next-line no-console console.warn( 'React-Modal: "bodyOpenClassName" prop has been modified. ' + - 'This may cause unexpected behavior when multiple modals are open.' + "This may cause unexpected behavior when multiple modals are open." ); } } @@ -107,17 +101,17 @@ export default class ModalPortal extends Component { clearTimeout(this.closeTimer); } - setFocusAfterRender = (focus) => { + setFocusAfterRender = focus => { this.focusAfterRender = this.props.shouldFocusAfterRender && focus; - } + }; - setOverlayRef = (overlay) => { + setOverlayRef = overlay => { this.overlay = overlay; - } + }; - setContentRef = (content) => { + setContentRef = content => { this.content = content; - } + }; beforeOpen() { const { appElement, ariaHideApp, bodyOpenClassName } = this.props; @@ -142,7 +136,7 @@ export default class ModalPortal extends Component { afterClose = () => { focusManager.returnFocus(); focusManager.teardownScopedFocus(); - } + }; open = () => { this.beforeOpen(); @@ -160,7 +154,7 @@ export default class ModalPortal extends Component { } }); } - } + }; close = () => { this.beforeClose(); @@ -169,11 +163,11 @@ export default class ModalPortal extends Component { } else { this.closeWithoutTimeout(); } - } + }; // Don't steal focus from inner elements focusContent = () => - (this.content && !this.contentHasFocus()) && this.content.focus(); + this.content && !this.contentHasFocus() && this.content.focus(); closeWithTimeout = () => { const closesAt = Date.now() + this.props.closeTimeoutMS; @@ -183,16 +177,19 @@ export default class ModalPortal extends Component { this.state.closesAt - Date.now() ); }); - } + }; closeWithoutTimeout = () => { - this.setState({ - beforeClose: false, - isOpen: false, - afterOpen: false, - closesAt: null - }, this.afterClose); - } + this.setState( + { + beforeClose: false, + isOpen: false, + afterOpen: false, + closesAt: null + }, + this.afterClose + ); + }; handleKeyDown = event => { if (event.keyCode === TAB_KEY) { @@ -202,7 +199,7 @@ export default class ModalPortal extends Component { event.preventDefault(); this.requestClose(event); } - } + }; handleOverlayOnClick = event => { if (this.shouldClose === null) { @@ -218,50 +215,51 @@ export default class ModalPortal extends Component { } this.shouldClose = null; this.moveFromContentToOverlay = null; - } + }; handleOverlayOnMouseUp = () => { if (this.moveFromContentToOverlay === null) { this.shouldClose = false; } - } + }; handleContentOnMouseUp = () => { this.shouldClose = false; - } + }; handleOverlayOnMouseDown = () => { this.moveFromContentToOverlay = false; - } + }; handleContentOnClick = () => { this.shouldClose = false; - } + }; handleContentOnMouseDown = () => { this.shouldClose = false; this.moveFromContentToOverlay = false; - } + }; requestClose = event => this.ownerHandlesClose() && this.props.onRequestClose(event); - ownerHandlesClose = () => - this.props.onRequestClose; + ownerHandlesClose = () => this.props.onRequestClose; - shouldBeClosed = () => - !this.state.isOpen && !this.state.beforeClose; + shouldBeClosed = () => !this.state.isOpen && !this.state.beforeClose; contentHasFocus = () => document.activeElement === this.content || this.content.contains(document.activeElement); buildClassName = (which, additional) => { - const classNames = (typeof additional === 'object') ? additional : { - base: CLASS_NAMES[which], - afterOpen: `${CLASS_NAMES[which]}--after-open`, - beforeClose: `${CLASS_NAMES[which]}--before-close` - }; + const classNames = + typeof additional === "object" + ? additional + : { + base: CLASS_NAMES[which], + afterOpen: `${CLASS_NAMES[which]}--after-open`, + beforeClose: `${CLASS_NAMES[which]}--before-close` + }; let className = classNames.base; if (this.state.afterOpen) { className = `${className} ${classNames.afterOpen}`; @@ -269,14 +267,16 @@ export default class ModalPortal extends Component { if (this.state.beforeClose) { className = `${className} ${classNames.beforeClose}`; } - return (typeof additional === 'string' && additional) ? - `${className} ${additional}` : className; - } + return typeof additional === "string" && additional + ? `${className} ${additional}` + : className; + }; - ariaAttributes = items => Object.keys(items).reduce((acc, name) => { - acc[`aria-${name}`] = items[name]; - return acc; - }, {}); + ariaAttributes = items => + Object.keys(items).reduce((acc, name) => { + acc[`aria-${name}`] = items[name]; + return acc; + }, {}); render() { const { className, overlayClassName, defaultStyles } = this.props; @@ -286,15 +286,16 @@ export default class ModalPortal extends Component { return this.shouldBeClosed() ? null : (
+ onMouseUp={this.handleOverlayOnMouseUp} + >
+ {...this.ariaAttributes(this.props.aria || {})} + > {this.props.children}
diff --git a/src/helpers/ariaAppHider.js b/src/helpers/ariaAppHider.js index 135bc8c7..f93b896a 100644 --- a/src/helpers/ariaAppHider.js +++ b/src/helpers/ariaAppHider.js @@ -10,10 +10,10 @@ export function assertNodeList(nodeList, selector) { export function setElement(element) { let useElement = element; - if (typeof useElement === 'string') { + if (typeof useElement === "string") { const el = document.querySelectorAll(useElement); assertNodeList(el, useElement); - useElement = 'length' in el ? el[0] : el; + useElement = "length" in el ? el[0] : el; } globalElement = useElement || globalElement; return globalElement; @@ -41,12 +41,12 @@ export function validateElement(appElement) { export function hide(appElement) { validateElement(appElement); - (appElement || globalElement).setAttribute('aria-hidden', 'true'); + (appElement || globalElement).setAttribute("aria-hidden", "true"); } export function show(appElement) { validateElement(appElement); - (appElement || globalElement).removeAttribute('aria-hidden'); + (appElement || globalElement).removeAttribute("aria-hidden"); } export function documentNotReadyOrSSRTesting() { diff --git a/src/helpers/bodyClassList.js b/src/helpers/bodyClassList.js index eea672a2..19b98628 100644 --- a/src/helpers/bodyClassList.js +++ b/src/helpers/bodyClassList.js @@ -1,19 +1,19 @@ -import * as refCount from './refCount'; +import * as refCount from "./refCount"; -export function add (bodyClass) { +export function add(bodyClass) { // Increment class(es) on refCount tracker and add class(es) to body bodyClass - .split(' ') + .split(" ") .map(refCount.add) .forEach(className => document.body.classList.add(className)); } -export function remove (bodyClass) { +export function remove(bodyClass) { const classListMap = refCount.get(); // Decrement class(es) from the refCount tracker // and remove unused class(es) from body bodyClass - .split(' ') + .split(" ") .map(refCount.remove) .filter(className => classListMap[className] === 0) .forEach(className => document.body.classList.remove(className)); diff --git a/src/helpers/focusManager.js b/src/helpers/focusManager.js index f4ff69fc..d3cecdc8 100644 --- a/src/helpers/focusManager.js +++ b/src/helpers/focusManager.js @@ -1,4 +1,4 @@ -import findTabbable from '../helpers/tabbable'; +import findTabbable from "../helpers/tabbable"; const focusLaterElements = []; let modalElement = null; @@ -23,7 +23,7 @@ export function handleFocus() { if (modalElement.contains(document.activeElement)) { return; } - const el = (findTabbable(modalElement)[0] || modalElement); + const el = findTabbable(modalElement)[0] || modalElement; el.focus(); }, 0); } @@ -41,11 +41,13 @@ export function returnFocus() { toFocus.focus(); return; } catch (e) { - console.warn([ - 'You tried to return focus to', - toFocus, - 'but it is not in the DOM anymore' - ].join(" ")); + console.warn( + [ + "You tried to return focus to", + toFocus, + "but it is not in the DOM anymore" + ].join(" ") + ); } } /* eslint-enable no-console */ @@ -54,11 +56,11 @@ export function setupScopedFocus(element) { modalElement = element; if (window.addEventListener) { - window.addEventListener('blur', handleBlur, false); - document.addEventListener('focus', handleFocus, true); + window.addEventListener("blur", handleBlur, false); + document.addEventListener("focus", handleFocus, true); } else { - window.attachEvent('onBlur', handleBlur); - document.attachEvent('onFocus', handleFocus); + window.attachEvent("onBlur", handleBlur); + document.attachEvent("onFocus", handleFocus); } } @@ -66,10 +68,10 @@ export function teardownScopedFocus() { modalElement = null; if (window.addEventListener) { - window.removeEventListener('blur', handleBlur); - document.removeEventListener('focus', handleFocus); + window.removeEventListener("blur", handleBlur); + document.removeEventListener("focus", handleFocus); } else { - window.detachEvent('onBlur', handleBlur); - document.detachEvent('onFocus', handleFocus); + window.detachEvent("onBlur", handleBlur); + document.detachEvent("onFocus", handleFocus); } } diff --git a/src/helpers/refCount.js b/src/helpers/refCount.js index fe6ade16..033c3192 100644 --- a/src/helpers/refCount.js +++ b/src/helpers/refCount.js @@ -21,6 +21,8 @@ export function remove(bodyClass) { } export function totalCount() { - return Object.keys(classListMap) - .reduce((acc, curr) => acc + classListMap[curr], 0); + return Object.keys(classListMap).reduce( + (acc, curr) => acc + classListMap[curr], + 0 + ); } diff --git a/src/helpers/safeHTMLElement.js b/src/helpers/safeHTMLElement.js index 3798072d..5f650887 100644 --- a/src/helpers/safeHTMLElement.js +++ b/src/helpers/safeHTMLElement.js @@ -1,4 +1,4 @@ -import ExecutionEnvironment from 'exenv'; +import ExecutionEnvironment from "exenv"; const EE = ExecutionEnvironment; diff --git a/src/helpers/scopeTab.js b/src/helpers/scopeTab.js index 9e5f180f..53a86fc0 100644 --- a/src/helpers/scopeTab.js +++ b/src/helpers/scopeTab.js @@ -1,4 +1,4 @@ -import findTabbable from './tabbable'; +import findTabbable from "./tabbable"; export default function scopeTab(node, event) { const tabbable = findTabbable(node); @@ -7,11 +7,10 @@ export default function scopeTab(node, event) { return; } const finalTabbable = tabbable[event.shiftKey ? 0 : tabbable.length - 1]; - const leavingFinalTabbable = ( + const leavingFinalTabbable = finalTabbable === document.activeElement || - // handle immediate shift+tab after opening with mouse - node === document.activeElement - ); + // handle immediate shift+tab after opening with mouse + node === document.activeElement; if (!leavingFinalTabbable) return; event.preventDefault(); const target = tabbable[event.shiftKey ? tabbable.length - 1 : 0]; diff --git a/src/helpers/tabbable.js b/src/helpers/tabbable.js index 905d17b4..dc3c50b4 100644 --- a/src/helpers/tabbable.js +++ b/src/helpers/tabbable.js @@ -13,8 +13,9 @@ const tabbableNode = /input|select|textarea|button|object/; function hidden(el) { - return (el.offsetWidth <= 0 && el.offsetHeight <= 0) || - el.style.display === 'none'; + return ( + (el.offsetWidth <= 0 && el.offsetHeight <= 0) || el.style.display === "none" + ); } function visible(element) { @@ -29,20 +30,19 @@ function visible(element) { function focusable(element, isTabIndexNotNaN) { const nodeName = element.nodeName.toLowerCase(); - const res = ((tabbableNode.test(nodeName)) && !element.disabled) || - (nodeName === "a" ? element.href || isTabIndexNotNaN : isTabIndexNotNaN); + const res = + (tabbableNode.test(nodeName) && !element.disabled) || + (nodeName === "a" ? element.href || isTabIndexNotNaN : isTabIndexNotNaN); return res && visible(element); } function tabbable(element) { - let tabIndex = element.getAttribute('tabindex'); + let tabIndex = element.getAttribute("tabindex"); if (tabIndex === null) tabIndex = undefined; const isTabIndexNaN = isNaN(tabIndex); return (isTabIndexNaN || tabIndex >= 0) && focusable(element, !isTabIndexNaN); } export default function findTabbableDescendants(element) { - return [].slice.call( - element.querySelectorAll('*'), 0 - ).filter(tabbable); + return [].slice.call(element.querySelectorAll("*"), 0).filter(tabbable); } diff --git a/src/index.js b/src/index.js index 8ecf0994..0235bbfb 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,3 @@ -import Modal from './components/Modal'; +import Modal from "./components/Modal"; export default Modal;