Skip to content

Commit e1807ce

Browse files
eemelidiasbruno
authored andcommittedApr 2, 2021
[added] support Array, HTMLCollection and NodeList values for appElement
1 parent c9d8e2d commit e1807ce

File tree

6 files changed

+82
-13
lines changed

6 files changed

+82
-13
lines changed
 

Diff for: ‎docs/accessibility/index.md

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ rewritten:
2323
Modal.setAppElement(document.getElementById('root'));
2424
```
2525

26+
Using a selector that matches multiple elements or passing a list of DOM
27+
elements will hide all of the elements.
28+
2629
If you are already applying the `aria-hidden` attribute to your app content
2730
through other means, you can pass the `ariaHideApp={false}` prop to your modal
2831
to avoid getting a warning that your app element is not specified.

Diff for: ‎specs/Modal.spec.js

+40
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ export default () => {
6565
ReactDOM.unmountComponentAtNode(node);
6666
});
6767

68+
it("accepts array of appElement as a prop", () => {
69+
const el1 = document.createElement("div");
70+
const el2 = document.createElement("div");
71+
const node = document.createElement("div");
72+
ReactDOM.render(<Modal isOpen={true} appElement={[el1, el2]} />, node);
73+
el1.getAttribute("aria-hidden").should.be.eql("true");
74+
el2.getAttribute("aria-hidden").should.be.eql("true");
75+
ReactDOM.unmountComponentAtNode(node);
76+
});
77+
6878
it("renders into the body, not in context", () => {
6979
const node = document.createElement("div");
7080
class App extends Component {
@@ -108,6 +118,36 @@ export default () => {
108118
ReactDOM.unmountComponentAtNode(node);
109119
});
110120

121+
// eslint-disable-next-line max-len
122+
it("allow setting appElement of type string matching multiple elements", () => {
123+
const el1 = document.createElement("div");
124+
el1.id = "id1";
125+
document.body.appendChild(el1);
126+
const el2 = document.createElement("div");
127+
el2.id = "id2";
128+
document.body.appendChild(el2);
129+
const node = document.createElement("div");
130+
class App extends Component {
131+
render() {
132+
return (
133+
<div>
134+
<Modal isOpen>
135+
<span>hello</span>
136+
</Modal>
137+
</div>
138+
);
139+
}
140+
}
141+
const appElement = "#id1, #id2";
142+
Modal.setAppElement(appElement);
143+
ReactDOM.render(<App />, node);
144+
el1.getAttribute("aria-hidden").should.be.eql("true");
145+
el2.getAttribute("aria-hidden").should.be.eql("true");
146+
ReactDOM.unmountComponentAtNode(node);
147+
document.body.removeChild(el1);
148+
document.body.removeChild(el2);
149+
});
150+
111151
it("default parentSelector should be document.body.", () => {
112152
const modal = renderModal({ isOpen: true });
113153
modal.props.parentSelector().should.be.eql(document.body);

Diff for: ‎src/components/Modal.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import ReactDOM from "react-dom";
33
import PropTypes from "prop-types";
44
import ModalPortal from "./ModalPortal";
55
import * as ariaAppHider from "../helpers/ariaAppHider";
6-
import SafeHTMLElement, { canUseDOM } from "../helpers/safeHTMLElement";
6+
import SafeHTMLElement, {
7+
SafeNodeList,
8+
SafeHTMLCollection,
9+
canUseDOM
10+
} from "../helpers/safeHTMLElement";
711

812
import { polyfill } from "react-lifecycles-compat";
913

@@ -52,7 +56,12 @@ class Modal extends Component {
5256
beforeClose: PropTypes.string.isRequired
5357
})
5458
]),
55-
appElement: PropTypes.instanceOf(SafeHTMLElement),
59+
appElement: PropTypes.oneOfType([
60+
PropTypes.instanceOf(SafeHTMLElement),
61+
PropTypes.instanceOf(SafeHTMLCollection),
62+
PropTypes.instanceOf(SafeNodeList),
63+
PropTypes.arrayOf(PropTypes.instanceOf(SafeHTMLElement))
64+
]),
5665
onAfterOpen: PropTypes.func,
5766
onRequestClose: PropTypes.func,
5867
closeTimeoutMS: PropTypes.number,

Diff for: ‎src/components/ModalPortal.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import * as focusManager from "../helpers/focusManager";
44
import scopeTab from "../helpers/scopeTab";
55
import * as ariaAppHider from "../helpers/ariaAppHider";
66
import * as classList from "../helpers/classList";
7-
import SafeHTMLElement from "../helpers/safeHTMLElement";
7+
import SafeHTMLElement, {
8+
SafeHTMLCollection,
9+
SafeNodeList
10+
} from "../helpers/safeHTMLElement";
811
import portalOpenInstances from "../helpers/portalOpenInstances";
912
import "../helpers/bodyTrap";
1013

@@ -43,7 +46,12 @@ export default class ModalPortal extends Component {
4346
bodyOpenClassName: PropTypes.string,
4447
htmlOpenClassName: PropTypes.string,
4548
ariaHideApp: PropTypes.bool,
46-
appElement: PropTypes.instanceOf(SafeHTMLElement),
49+
appElement: PropTypes.oneOfType([
50+
PropTypes.instanceOf(SafeHTMLElement),
51+
PropTypes.instanceOf(SafeHTMLCollection),
52+
PropTypes.instanceOf(SafeNodeList),
53+
PropTypes.arrayOf(PropTypes.instanceOf(SafeHTMLElement))
54+
]),
4755
onAfterOpen: PropTypes.func,
4856
onAfterClose: PropTypes.func,
4957
onRequestClose: PropTypes.func,

Diff for: ‎src/helpers/ariaAppHider.js

+14-9
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@ export function setElement(element) {
1616
if (typeof useElement === "string" && canUseDOM) {
1717
const el = document.querySelectorAll(useElement);
1818
assertNodeList(el, useElement);
19-
useElement = "length" in el ? el[0] : el;
19+
useElement = el;
2020
}
2121
globalElement = useElement || globalElement;
2222
return globalElement;
2323
}
2424

2525
export function validateElement(appElement) {
26-
if (!appElement && !globalElement) {
26+
const el = appElement || globalElement;
27+
if (el) {
28+
return Array.isArray(el) ||
29+
el instanceof HTMLCollection ||
30+
el instanceof NodeList
31+
? el
32+
: [el];
33+
} else {
2734
warning(
2835
false,
2936
[
@@ -35,21 +42,19 @@ export function validateElement(appElement) {
3542
].join(" ")
3643
);
3744

38-
return false;
45+
return [];
3946
}
40-
41-
return true;
4247
}
4348

4449
export function hide(appElement) {
45-
if (validateElement(appElement)) {
46-
(appElement || globalElement).setAttribute("aria-hidden", "true");
50+
for (let el of validateElement(appElement)) {
51+
el.setAttribute("aria-hidden", "true");
4752
}
4853
}
4954

5055
export function show(appElement) {
51-
if (validateElement(appElement)) {
52-
(appElement || globalElement).removeAttribute("aria-hidden");
56+
for (let el of validateElement(appElement)) {
57+
el.removeAttribute("aria-hidden");
5358
}
5459
}
5560

Diff for: ‎src/helpers/safeHTMLElement.js

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ const EE = ExecutionEnvironment;
44

55
const SafeHTMLElement = EE.canUseDOM ? window.HTMLElement : {};
66

7+
export const SafeHTMLCollection = EE.canUseDOM ? window.HTMLCollection : {};
8+
9+
export const SafeNodeList = EE.canUseDOM ? window.NodeList : {};
10+
711
export const canUseDOM = EE.canUseDOM;
812

913
export default SafeHTMLElement;

0 commit comments

Comments
 (0)
Please sign in to comment.