Skip to content

Commit 6c4d4ad

Browse files
klootsdiasbruno
authored andcommittedFeb 2, 2018
[fixed] management of aria-hidden attribute decoupled from the management of the body open class
1 parent 93b2c05 commit 6c4d4ad

File tree

5 files changed

+137
-45
lines changed

5 files changed

+137
-45
lines changed
 

Diff for: ‎specs/Modal.spec.js

+99-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from "./helper";
1717

1818
export default () => {
19-
afterEach("check if test cleaned up rendered modals", emptyDOM);
19+
afterEach("cleaned up all rendered modals", emptyDOM);
2020

2121
it("scopes tab navigation to the modal");
2222
it("focuses the last focused element when tabbing in from browser chrome");
@@ -352,6 +352,104 @@ export default () => {
352352
should(el.getAttribute("aria-hidden")).not.be.ok();
353353
});
354354

355+
// eslint-disable-next-line max-len
356+
it("removes aria-hidden when closed and another modal with ariaHideApp set to false is open", () => {
357+
const rootNode = document.createElement("div");
358+
document.body.appendChild(rootNode);
359+
360+
const appElement = document.createElement("div");
361+
document.body.appendChild(appElement);
362+
363+
Modal.setAppElement(appElement);
364+
365+
const initialState = (
366+
<div>
367+
<Modal isOpen={true} ariaHideApp={false} id="test-1-modal-1" />
368+
<Modal isOpen={true} ariaHideApp={true} id="test-1-modal-2" />
369+
</div>
370+
);
371+
372+
ReactDOM.render(initialState, rootNode);
373+
appElement.getAttribute("aria-hidden").should.be.eql("true");
374+
375+
const updatedState = (
376+
<div>
377+
<Modal isOpen={true} ariaHideApp={false} id="test-1-modal-1" />
378+
<Modal isOpen={false} ariaHideApp={true} id="test-1-modal-2" />
379+
</div>
380+
);
381+
382+
ReactDOM.render(updatedState, rootNode);
383+
should(appElement.getAttribute("aria-hidden")).not.be.ok();
384+
385+
ReactDOM.unmountComponentAtNode(rootNode);
386+
});
387+
388+
// eslint-disable-next-line max-len
389+
it("maintains aria-hidden when closed and another modal with ariaHideApp set to true is open", () => {
390+
const rootNode = document.createElement("div");
391+
document.body.appendChild(rootNode);
392+
393+
const appElement = document.createElement("div");
394+
document.body.appendChild(appElement);
395+
396+
Modal.setAppElement(appElement);
397+
398+
const initialState = (
399+
<div>
400+
<Modal isOpen={true} ariaHideApp={true} id="test-1-modal-1" />
401+
<Modal isOpen={true} ariaHideApp={true} id="test-1-modal-2" />
402+
</div>
403+
);
404+
405+
ReactDOM.render(initialState, rootNode);
406+
appElement.getAttribute("aria-hidden").should.be.eql("true");
407+
408+
const updatedState = (
409+
<div>
410+
<Modal isOpen={true} ariaHideApp={true} id="test-1-modal-1" />
411+
<Modal isOpen={false} ariaHideApp={true} id="test-1-modal-2" />
412+
</div>
413+
);
414+
415+
ReactDOM.render(updatedState, rootNode);
416+
appElement.getAttribute("aria-hidden").should.be.eql("true");
417+
418+
ReactDOM.unmountComponentAtNode(rootNode);
419+
});
420+
421+
// eslint-disable-next-line max-len
422+
it("removes aria-hidden when unmounted without close and second modal with ariaHideApp=false is open", () => {
423+
const appElement = document.createElement("div");
424+
document.body.appendChild(appElement);
425+
Modal.setAppElement(appElement);
426+
427+
renderModal({ isOpen: true, ariaHideApp: false, id: "test-2-modal-1" });
428+
should(appElement.getAttribute("aria-hidden")).not.be.ok();
429+
430+
renderModal({ isOpen: true, ariaHideApp: true, id: "test-2-modal-2" });
431+
appElement.getAttribute("aria-hidden").should.be.eql("true");
432+
433+
unmountModal();
434+
should(appElement.getAttribute("aria-hidden")).not.be.ok();
435+
});
436+
437+
// eslint-disable-next-line max-len
438+
it("maintains aria-hidden when unmounted without close and second modal with ariaHideApp=true is open", () => {
439+
const appElement = document.createElement("div");
440+
document.body.appendChild(appElement);
441+
Modal.setAppElement(appElement);
442+
443+
renderModal({ isOpen: true, ariaHideApp: true, id: "test-3-modal-1" });
444+
appElement.getAttribute("aria-hidden").should.be.eql("true");
445+
446+
renderModal({ isOpen: true, ariaHideApp: true, id: "test-3-modal-2" });
447+
appElement.getAttribute("aria-hidden").should.be.eql("true");
448+
449+
unmountModal();
450+
appElement.getAttribute("aria-hidden").should.be.eql("true");
451+
});
452+
355453
it("adds --after-open for animations", () => {
356454
const modal = renderModal({ isOpen: true });
357455
const rg = /--after-open/i;

Diff for: ‎specs/helper.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,15 @@ export const mouseUpAt = Simulate.mouseUp;
121121
export const mouseDownAt = Simulate.mouseDown;
122122

123123
export const renderModal = function(props, children, callback) {
124-
props.ariaHideApp = false;
124+
const modalProps = { ariaHideApp: false, ...props };
125+
125126
const currentDiv = document.createElement("div");
126127
divStack.push(currentDiv);
127128
document.body.appendChild(currentDiv);
128129

129130
// eslint-disable-next-line react/no-render-return-value
130131
return ReactDOM.render(
131-
<Modal {...props}>{children}</Modal>,
132+
<Modal {...modalProps}>{children}</Modal>,
132133
currentDiv,
133134
callback
134135
);

Diff for: ‎src/components/ModalPortal.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import PropTypes from "prop-types";
33
import * as focusManager from "../helpers/focusManager";
44
import scopeTab from "../helpers/scopeTab";
55
import * as ariaAppHider from "../helpers/ariaAppHider";
6-
import * as refCount from "../helpers/refCount";
76
import * as bodyClassList from "../helpers/bodyClassList";
87
import SafeHTMLElement from "../helpers/safeHTMLElement";
98

@@ -16,6 +15,8 @@ const CLASS_NAMES = {
1615
const TAB_KEY = 9;
1716
const ESC_KEY = 27;
1817

18+
let ariaHiddenInstances = 0;
19+
1920
export default class ModalPortal extends Component {
2021
static defaultProps = {
2122
style: {
@@ -121,6 +122,7 @@ export default class ModalPortal extends Component {
121122
bodyClassList.add(bodyOpenClassName);
122123
// Add aria-hidden to appElement
123124
if (ariaHideApp) {
125+
ariaHiddenInstances += 1;
124126
ariaAppHider.hide(appElement);
125127
}
126128
}
@@ -132,8 +134,12 @@ export default class ModalPortal extends Component {
132134
bodyClassList.remove(this.props.bodyOpenClassName);
133135

134136
// Reset aria-hidden attribute if all modals have been removed
135-
if (ariaHideApp && refCount.totalCount() < 1) {
136-
ariaAppHider.show(appElement);
137+
if (ariaHideApp && ariaHiddenInstances > 0) {
138+
ariaHiddenInstances -= 1;
139+
140+
if (ariaHiddenInstances === 0) {
141+
ariaAppHider.show(appElement);
142+
}
137143
}
138144

139145
if (this.props.shouldFocusAfterRender) {

Diff for: ‎src/helpers/bodyClassList.js

+26-11
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
1-
import * as refCount from "./refCount";
1+
const classListMap = {};
22

3-
export function add(bodyClass) {
4-
// Increment class(es) on refCount tracker and add class(es) to body
3+
const addClassToMap = className => {
4+
// Set variable and default if none
5+
if (!classListMap[className]) {
6+
classListMap[className] = 0;
7+
}
8+
classListMap[className] += 1;
9+
return className;
10+
};
11+
12+
const removeClassFromMap = className => {
13+
if (classListMap[className]) {
14+
classListMap[className] -= 1;
15+
}
16+
return className;
17+
};
18+
19+
const add = bodyClass => {
520
bodyClass
621
.split(" ")
7-
.map(refCount.add)
22+
.map(addClassToMap)
823
.forEach(className => document.body.classList.add(className));
9-
}
24+
};
1025

11-
export function remove(bodyClass) {
12-
const classListMap = refCount.get();
13-
// Decrement class(es) from the refCount tracker
14-
// and remove unused class(es) from body
26+
const remove = bodyClass => {
27+
// Remove unused class(es) from body
1528
bodyClass
1629
.split(" ")
17-
.map(refCount.remove)
30+
.map(removeClassFromMap)
1831
.filter(className => classListMap[className] === 0)
1932
.forEach(className => document.body.classList.remove(className));
20-
}
33+
};
34+
35+
export { add, remove };

Diff for: ‎src/helpers/refCount.js

-28
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.