diff --git a/specs/Modal.spec.js b/specs/Modal.spec.js
index 47c43410..7765d967 100644
--- a/specs/Modal.spec.js
+++ b/specs/Modal.spec.js
@@ -16,7 +16,7 @@ import {
} from "./helper";
export default () => {
- afterEach("check if test cleaned up rendered modals", emptyDOM);
+ afterEach("cleaned up all rendered modals", emptyDOM);
it("scopes tab navigation to the modal");
it("focuses the last focused element when tabbing in from browser chrome");
@@ -352,6 +352,104 @@ export default () => {
should(el.getAttribute("aria-hidden")).not.be.ok();
});
+ // eslint-disable-next-line max-len
+ it("removes aria-hidden when closed and another modal with ariaHideApp set to false is open", () => {
+ const rootNode = document.createElement("div");
+ document.body.appendChild(rootNode);
+
+ const appElement = document.createElement("div");
+ document.body.appendChild(appElement);
+
+ Modal.setAppElement(appElement);
+
+ const initialState = (
+
+
+
+
+ );
+
+ ReactDOM.render(initialState, rootNode);
+ appElement.getAttribute("aria-hidden").should.be.eql("true");
+
+ const updatedState = (
+
+
+
+
+ );
+
+ ReactDOM.render(updatedState, rootNode);
+ should(appElement.getAttribute("aria-hidden")).not.be.ok();
+
+ ReactDOM.unmountComponentAtNode(rootNode);
+ });
+
+ // eslint-disable-next-line max-len
+ it("maintains aria-hidden when closed and another modal with ariaHideApp set to true is open", () => {
+ const rootNode = document.createElement("div");
+ document.body.appendChild(rootNode);
+
+ const appElement = document.createElement("div");
+ document.body.appendChild(appElement);
+
+ Modal.setAppElement(appElement);
+
+ const initialState = (
+
+
+
+
+ );
+
+ ReactDOM.render(initialState, rootNode);
+ appElement.getAttribute("aria-hidden").should.be.eql("true");
+
+ const updatedState = (
+
+
+
+
+ );
+
+ ReactDOM.render(updatedState, rootNode);
+ appElement.getAttribute("aria-hidden").should.be.eql("true");
+
+ ReactDOM.unmountComponentAtNode(rootNode);
+ });
+
+ // eslint-disable-next-line max-len
+ it("removes aria-hidden when unmounted without close and second modal with ariaHideApp=false is open", () => {
+ const appElement = document.createElement("div");
+ document.body.appendChild(appElement);
+ Modal.setAppElement(appElement);
+
+ renderModal({ isOpen: true, ariaHideApp: false, id: "test-2-modal-1" });
+ should(appElement.getAttribute("aria-hidden")).not.be.ok();
+
+ renderModal({ isOpen: true, ariaHideApp: true, id: "test-2-modal-2" });
+ appElement.getAttribute("aria-hidden").should.be.eql("true");
+
+ unmountModal();
+ should(appElement.getAttribute("aria-hidden")).not.be.ok();
+ });
+
+ // eslint-disable-next-line max-len
+ it("maintains aria-hidden when unmounted without close and second modal with ariaHideApp=true is open", () => {
+ const appElement = document.createElement("div");
+ document.body.appendChild(appElement);
+ Modal.setAppElement(appElement);
+
+ renderModal({ isOpen: true, ariaHideApp: true, id: "test-3-modal-1" });
+ appElement.getAttribute("aria-hidden").should.be.eql("true");
+
+ renderModal({ isOpen: true, ariaHideApp: true, id: "test-3-modal-2" });
+ appElement.getAttribute("aria-hidden").should.be.eql("true");
+
+ unmountModal();
+ appElement.getAttribute("aria-hidden").should.be.eql("true");
+ });
+
it("adds --after-open for animations", () => {
const modal = renderModal({ isOpen: true });
const rg = /--after-open/i;
diff --git a/specs/helper.js b/specs/helper.js
index e840ae97..1cf57516 100644
--- a/specs/helper.js
+++ b/specs/helper.js
@@ -121,14 +121,15 @@ export const mouseUpAt = Simulate.mouseUp;
export const mouseDownAt = Simulate.mouseDown;
export const renderModal = function(props, children, callback) {
- props.ariaHideApp = false;
+ const modalProps = { ariaHideApp: false, ...props };
+
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},
+ {children},
currentDiv,
callback
);
diff --git a/src/components/ModalPortal.js b/src/components/ModalPortal.js
index 6f48c513..ea46d945 100644
--- a/src/components/ModalPortal.js
+++ b/src/components/ModalPortal.js
@@ -3,7 +3,6 @@ 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";
@@ -16,6 +15,8 @@ const CLASS_NAMES = {
const TAB_KEY = 9;
const ESC_KEY = 27;
+let ariaHiddenInstances = 0;
+
export default class ModalPortal extends Component {
static defaultProps = {
style: {
@@ -121,6 +122,7 @@ export default class ModalPortal extends Component {
bodyClassList.add(bodyOpenClassName);
// Add aria-hidden to appElement
if (ariaHideApp) {
+ ariaHiddenInstances += 1;
ariaAppHider.hide(appElement);
}
}
@@ -132,8 +134,12 @@ export default class ModalPortal extends Component {
bodyClassList.remove(this.props.bodyOpenClassName);
// Reset aria-hidden attribute if all modals have been removed
- if (ariaHideApp && refCount.totalCount() < 1) {
- ariaAppHider.show(appElement);
+ if (ariaHideApp && ariaHiddenInstances > 0) {
+ ariaHiddenInstances -= 1;
+
+ if (ariaHiddenInstances === 0) {
+ ariaAppHider.show(appElement);
+ }
}
if (this.props.shouldFocusAfterRender) {
diff --git a/src/helpers/bodyClassList.js b/src/helpers/bodyClassList.js
index 19b98628..dab78df6 100644
--- a/src/helpers/bodyClassList.js
+++ b/src/helpers/bodyClassList.js
@@ -1,20 +1,35 @@
-import * as refCount from "./refCount";
+const classListMap = {};
-export function add(bodyClass) {
- // Increment class(es) on refCount tracker and add class(es) to body
+const addClassToMap = className => {
+ // Set variable and default if none
+ if (!classListMap[className]) {
+ classListMap[className] = 0;
+ }
+ classListMap[className] += 1;
+ return className;
+};
+
+const removeClassFromMap = className => {
+ if (classListMap[className]) {
+ classListMap[className] -= 1;
+ }
+ return className;
+};
+
+const add = bodyClass => {
bodyClass
.split(" ")
- .map(refCount.add)
+ .map(addClassToMap)
.forEach(className => document.body.classList.add(className));
-}
+};
-export function remove(bodyClass) {
- const classListMap = refCount.get();
- // Decrement class(es) from the refCount tracker
- // and remove unused class(es) from body
+const remove = bodyClass => {
+ // Remove unused class(es) from body
bodyClass
.split(" ")
- .map(refCount.remove)
+ .map(removeClassFromMap)
.filter(className => classListMap[className] === 0)
.forEach(className => document.body.classList.remove(className));
-}
+};
+
+export { add, remove };
diff --git a/src/helpers/refCount.js b/src/helpers/refCount.js
deleted file mode 100644
index 033c3192..00000000
--- a/src/helpers/refCount.js
+++ /dev/null
@@ -1,28 +0,0 @@
-const classListMap = {};
-
-export function get() {
- return classListMap;
-}
-
-export function add(bodyClass) {
- // Set variable and default if none
- if (!classListMap[bodyClass]) {
- classListMap[bodyClass] = 0;
- }
- classListMap[bodyClass] += 1;
- return bodyClass;
-}
-
-export function remove(bodyClass) {
- if (classListMap[bodyClass]) {
- classListMap[bodyClass] -= 1;
- }
- return bodyClass;
-}
-
-export function totalCount() {
- return Object.keys(classListMap).reduce(
- (acc, curr) => acc + classListMap[curr],
- 0
- );
-}