Skip to content

Commit 0c6d966

Browse files
committedFeb 21, 2018
[added] htmlOpenClassName will follow the same rules like...
bodyOpenClassName.
1 parent 088e68e commit 0c6d966

File tree

7 files changed

+183
-109
lines changed

7 files changed

+183
-109
lines changed
 

Diff for: ‎docs/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import ReactModal from 'react-modal';
7171
bodyOpenClassName="ReactModal__Body--open"
7272
/*
7373
String className to be applied to the document.html (must be a constant string).
74+
This attribute is `null` by default.
7475
See the `Styles` section for more details.
7576
*/
7677
htmlOpenClassName="ReactModal__Html--open"

Diff for: ‎docs/styles/classes.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ any styles applied using these default classes will not override the default
3838
styles as they would if specified using the `className` or `overlayClassName`
3939
props.
4040

41-
#### For the document body
41+
#### For the document.body and html tag
4242

4343
You can override the default class that is added to `document.body` when the
4444
modal is open by defining a property `bodyOpenClassName`.
@@ -61,6 +61,21 @@ non-default `bodyOpenClassName`), you could use the following CSS:
6161
}
6262
```
6363

64+
You can define a class to be added to the html tag, using the `htmlOpenClassName`
65+
attribute, which can be helpeful to stop the page to scroll to the top when open
66+
a modal.
67+
68+
This attribute follows the same rules as `bodyOpenClassName`, it must be a *constant string*;
69+
70+
Here is an example that can help preventing this behavior:
71+
72+
```CSS
73+
.ReactModal__Body--open,
74+
.ReactModal__Html--open {
75+
overflow: hidden;
76+
}
77+
```
78+
6479
#### For the entire portal
6580

6681
To specify a class to be applied to the entire portal, you may use the

Diff for: ‎specs/Modal.spec.js

+25-64
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as ariaAppHider from "react-modal/helpers/ariaAppHider";
77
import {
88
isBodyWithReactModalOpenClass,
99
isHtmlWithReactModalOpenClass,
10+
htmlClassList,
1011
contentAttribute,
1112
mcontent,
1213
moverlay,
@@ -254,46 +255,45 @@ export default () => {
254255
(document.body.className.indexOf("custom-modal-open") > -1).should.be.ok();
255256
});
256257

257-
it("supports overriding react modal open class in html.", () => {
258+
it("supports setting react modal open class in <html />.", () => {
258259
renderModal({ isOpen: true, htmlOpenClassName: "custom-modal-open" });
259-
(
260-
document
261-
.getElementsByTagName("html")[0]
262-
.className.indexOf("custom-modal-open") > -1
263-
).should.be.ok();
260+
isHtmlWithReactModalOpenClass("custom-modal-open").should.be.ok();
264261
});
265262

266263
// eslint-disable-next-line max-len
267-
it("don't append class to document.body and html if modal is not open", () => {
264+
it("don't append class to document.body if modal is closed.", () => {
268265
renderModal({ isOpen: false });
269266
isBodyWithReactModalOpenClass().should.not.be.ok();
267+
});
268+
269+
it("don't append class to <html /> if modal is closed.", () => {
270+
renderModal({ isOpen: false, htmlOpenClassName: "custom-modal-open" });
270271
isHtmlWithReactModalOpenClass().should.not.be.ok();
271-
unmountModal();
272272
});
273273

274-
it("append class to document.body and html if modal is open", () => {
274+
it("append class to document.body if modal is open.", () => {
275275
renderModal({ isOpen: true });
276276
isBodyWithReactModalOpenClass().should.be.ok();
277-
isHtmlWithReactModalOpenClass().should.be.ok();
278-
unmountModal();
277+
});
278+
279+
it("don't append class to <html /> if not defined.", () => {
280+
renderModal({ isOpen: true });
281+
htmlClassList().should.be.empty();
279282
});
280283

281284
// eslint-disable-next-line max-len
282-
it("removes class from document.body and html when unmounted without closing", () => {
285+
it("removes class from document.body when unmounted without closing", () => {
283286
renderModal({ isOpen: true });
284287
unmountModal();
285288
isBodyWithReactModalOpenClass().should.not.be.ok();
286-
isHtmlWithReactModalOpenClass().should.not.be.ok();
287289
});
288290

289-
it("remove class from document.body and html when no modals opened", () => {
291+
it("remove class from document.body when no modals opened", () => {
290292
renderModal({ isOpen: true });
291293
renderModal({ isOpen: true });
292294
isBodyWithReactModalOpenClass().should.be.ok();
293-
isHtmlWithReactModalOpenClass().should.be.ok();
294295
unmountModal();
295296
isBodyWithReactModalOpenClass().should.be.ok();
296-
isHtmlWithReactModalOpenClass().should.be.ok();
297297
unmountModal();
298298
isBodyWithReactModalOpenClass().should.not.be.ok();
299299
isHtmlWithReactModalOpenClass().should.not.be.ok();
@@ -346,57 +346,18 @@ export default () => {
346346
isBodyWithReactModalOpenClass().should.be.ok();
347347
});
348348

349-
it("supports adding/removing multiple html classes", () => {
350-
renderModal({
351-
isOpen: true,
352-
htmlOpenClassName: "A B C"
353-
});
354-
document
355-
.getElementsByTagName("html")[0]
356-
.classList.contains("A", "B", "C")
357-
.should.be.ok();
358-
unmountModal();
359-
document
360-
.getElementsByTagName("html")[0]
361-
.classList.contains("A", "B", "C")
362-
.should.not.be.ok();
363-
});
364-
365-
it("does not remove shared classes if more than one modal is open", () => {
366-
renderModal({
367-
isOpen: true,
368-
htmlOpenClassName: "A"
369-
});
370-
renderModal({
349+
it("should not remove classes from <html /> if modal is closed", () => {
350+
const modalA = renderModal({ isOpen: false });
351+
isHtmlWithReactModalOpenClass().should.not.be.ok();
352+
const modalB = renderModal({
371353
isOpen: true,
372-
htmlOpenClassName: "A B"
354+
htmlOpenClassName: "testHtmlClass"
373355
});
374-
375-
isHtmlWithReactModalOpenClass("A B").should.be.ok();
376-
unmountModal();
377-
isHtmlWithReactModalOpenClass("A B").should.not.be.ok();
378-
isHtmlWithReactModalOpenClass("A").should.be.ok();
379-
unmountModal();
380-
isHtmlWithReactModalOpenClass("A").should.not.be.ok();
381-
});
382-
383-
it("should not add classes to html for unopened modals", () => {
384-
renderModal({ isOpen: true });
385-
isHtmlWithReactModalOpenClass().should.be.ok();
386-
renderModal({ isOpen: false, htmlOpenClassName: "testHtmlClass" });
387-
isHtmlWithReactModalOpenClass("testHtmlClass").should.not.be.ok();
388-
});
389-
390-
it("should not remove classes from html if modal is closed", () => {
391-
renderModal({ isOpen: true });
392-
isHtmlWithReactModalOpenClass().should.be.ok();
393-
renderModal({ isOpen: false, htmlOpenClassName: "testHtmlClass" });
394-
renderModal({ isOpen: false });
395-
isHtmlWithReactModalOpenClass("testHtmlClass").should.not.be.ok();
396-
isHtmlWithReactModalOpenClass().should.be.ok();
397-
renderModal({ isOpen: false });
356+
modalA.portal.close();
357+
isHtmlWithReactModalOpenClass("testHtmlClass").should.be.ok();
358+
modalB.portal.close();
359+
isHtmlWithReactModalOpenClass().should.not.be.ok();
398360
renderModal({ isOpen: false });
399-
isHtmlWithReactModalOpenClass().should.be.ok();
400361
});
401362

402363
it("additional aria attributes", () => {

Diff for: ‎specs/helper.js

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import React from "react";
22
import ReactDOM from "react-dom";
3-
import Modal, {
4-
bodyOpenClassName,
5-
htmlOpenClassName
6-
} from "../src/components/Modal";
3+
import Modal, { bodyOpenClassName } from "../src/components/Modal";
74
import TestUtils from "react-dom/test-utils";
85

96
const divStack = [];
@@ -25,6 +22,12 @@ if (!String.prototype.includes) {
2522
};
2623
}
2724

25+
/**
26+
* Return the class list object from `document.body`.
27+
* @return {Array}
28+
*/
29+
export const documentBodyClassList = () => document.body.classList;
30+
2831
/**
2932
* Check if the document.body contains the react modal
3033
* open class.
@@ -33,13 +36,20 @@ if (!String.prototype.includes) {
3336
export const isBodyWithReactModalOpenClass = (bodyClass = bodyOpenClassName) =>
3437
document.body.className.includes(bodyClass);
3538

39+
/**
40+
* Return the class list object from <html />.
41+
* @return {Array}
42+
*/
43+
export const htmlClassList = () =>
44+
document.getElementsByTagName("html")[0].classList;
45+
3646
/**
3747
* Check if the html contains the react modal
3848
* open class.
3949
* @return {Boolean}
4050
*/
41-
export const isHtmlWithReactModalOpenClass = (htmlClass = htmlOpenClassName) =>
42-
document.getElementsByTagName("html")[0].className.includes(htmlClass);
51+
export const isHtmlWithReactModalOpenClass = htmlClass =>
52+
htmlClassList().contains(htmlClass);
4353

4454
/**
4555
* Returns a rendered dom element by class.

Diff for: ‎src/components/Modal.js

-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import SafeHTMLElement, { canUseDOM } from "../helpers/safeHTMLElement";
77

88
export const portalClassName = "ReactModalPortal";
99
export const bodyOpenClassName = "ReactModal__Body--open";
10-
export const htmlOpenClassName = "ReactModal__Html--open";
1110

1211
const isReact16 = ReactDOM.createPortal !== undefined;
1312
const createPortal = isReact16
@@ -71,7 +70,6 @@ export default class Modal extends Component {
7170
isOpen: false,
7271
portalClassName,
7372
bodyOpenClassName,
74-
htmlOpenClassName,
7573
ariaHideApp: true,
7674
closeTimeoutMS: 0,
7775
shouldFocusAfterRender: true,

Diff for: ‎src/components/ModalPortal.js

+27-14
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ 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 bodyClassList from "../helpers/bodyClassList";
6+
import * as classList from "../helpers/classList";
77
import SafeHTMLElement from "../helpers/safeHTMLElement";
88

99
// so that our CSS is statically analyzable
@@ -132,28 +132,41 @@ export default class ModalPortal extends Component {
132132
const {
133133
appElement,
134134
ariaHideApp,
135-
bodyOpenClassName,
136-
htmlOpenClassName
135+
htmlOpenClassName,
136+
bodyOpenClassName
137137
} = this.props;
138-
// Add body and html class
139-
bodyClassList.add(document.body, bodyOpenClassName);
140-
classList.add(document.getElementsByTagName("html")[0], htmlOpenClassName);
141-
// Add aria-hidden to appElement
138+
139+
// Add classes.
140+
classList.add(document.body, bodyOpenClassName);
141+
142+
htmlOpenClassName &&
143+
classList.add(
144+
document.getElementsByTagName("html")[0],
145+
htmlOpenClassName
146+
);
147+
142148
if (ariaHideApp) {
143149
ariaHiddenInstances += 1;
144150
ariaAppHider.hide(appElement);
145151
}
146152
}
147153

148154
afterClose = () => {
149-
const { appElement, ariaHideApp } = this.props;
155+
const {
156+
appElement,
157+
ariaHideApp,
158+
htmlOpenClassName,
159+
bodyOpenClassName
160+
} = this.props;
150161

151-
// Remove body class
152-
bodyClassList.remove(this.props.bodyOpenClassName);
153-
classList.remove(
154-
document.getElementsByTagName("html")[0],
155-
this.props.htmlOpenClassName
156-
);
162+
// Remove classes.
163+
classList.remove(document.body, bodyOpenClassName);
164+
165+
htmlOpenClassName &&
166+
classList.remove(
167+
document.getElementsByTagName("html")[0],
168+
htmlOpenClassName
169+
);
157170

158171
// Reset aria-hidden attribute if all modals have been removed
159172
if (ariaHideApp && ariaHiddenInstances > 0) {

0 commit comments

Comments
 (0)
Please sign in to comment.