Skip to content

Commit 827796d

Browse files
Benjamin Paduladiasbruno
Benjamin Padula
authored andcommitted
[fixed] Ensure after-open css transitions work in Safari 14 & Mobile Safari
1 parent 76df16b commit 827796d

File tree

3 files changed

+88
-56
lines changed

3 files changed

+88
-56
lines changed

Diff for: specs/Modal.events.spec.js

+23-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-env mocha */
22
import React from "react";
3+
import ReactDOM from "react-dom";
34
import "should";
45
import sinon from "sinon";
56
import Modal from "react-modal";
@@ -11,24 +12,37 @@ import {
1112
mouseUpAt,
1213
escKeyDown,
1314
tabKeyDown,
14-
withModal
15+
withModal,
16+
withElementCollector,
17+
createHTMLElement
1518
} from "./helper";
1619

1720
export default () => {
1821
it("should trigger the onAfterOpen callback", () => {
1922
const afterOpenCallback = sinon.spy();
20-
const props = { isOpen: true, onAfterOpen: afterOpenCallback };
21-
withModal(props, null, () => {});
22-
afterOpenCallback.called.should.be.ok();
23+
withElementCollector(() => {
24+
const props = { isOpen: true, onAfterOpen: afterOpenCallback };
25+
const node = createHTMLElement("div");
26+
ReactDOM.render(<Modal {...props} />, node);
27+
requestAnimationFrame(() => {
28+
afterOpenCallback.called.should.be.ok();
29+
ReactDOM.unmountComponentAtNode(node);
30+
});
31+
});
2332
});
2433

2534
it("should call onAfterOpen with overlay and content references", () => {
2635
const afterOpenCallback = sinon.spy();
27-
const props = { isOpen: true, onAfterOpen: afterOpenCallback };
28-
withModal(props, null, modal => {
29-
sinon.assert.calledWith(afterOpenCallback, {
30-
overlayEl: modal.portal.overlay,
31-
contentEl: modal.portal.content
36+
withElementCollector(() => {
37+
const props = { isOpen: true, onAfterOpen: afterOpenCallback };
38+
const node = createHTMLElement("div");
39+
const modal = ReactDOM.render(<Modal {...props} />, node);
40+
requestAnimationFrame(() => {
41+
sinon.assert.calledWith(afterOpenCallback, {
42+
overlayEl: modal.portal.overlay,
43+
contentEl: modal.portal.content
44+
});
45+
ReactDOM.unmountComponentAtNode(node);
3246
});
3347
});
3448
});

Diff for: specs/Modal.spec.js

+55-39
Original file line numberDiff line numberDiff line change
@@ -348,32 +348,42 @@ export default () => {
348348
});
349349

350350
it("overrides content classes with custom object className", () => {
351-
const props = {
352-
isOpen: true,
353-
className: {
354-
base: "myClass",
355-
afterOpen: "myClass_after-open",
356-
beforeClose: "myClass_before-close"
357-
}
358-
};
359-
withModal(props, null, modal => {
360-
mcontent(modal).className.should.be.eql("myClass myClass_after-open");
351+
withElementCollector(() => {
352+
const props = {
353+
isOpen: true,
354+
className: {
355+
base: "myClass",
356+
afterOpen: "myClass_after-open",
357+
beforeClose: "myClass_before-close"
358+
}
359+
};
360+
const node = createHTMLElement("div");
361+
const modal = ReactDOM.render(<Modal {...props} />, node);
362+
requestAnimationFrame(() => {
363+
mcontent(modal).className.should.be.eql("myClass myClass_after-open");
364+
ReactDOM.unmountComponentAtNode(node);
365+
});
361366
});
362367
});
363368

364369
it("overrides overlay classes with custom object overlayClassName", () => {
365-
const props = {
366-
isOpen: true,
367-
overlayClassName: {
368-
base: "myOverlayClass",
369-
afterOpen: "myOverlayClass_after-open",
370-
beforeClose: "myOverlayClass_before-close"
371-
}
372-
};
373-
withModal(props, null, modal => {
374-
moverlay(modal).className.should.be.eql(
375-
"myOverlayClass myOverlayClass_after-open"
376-
);
370+
withElementCollector(() => {
371+
const props = {
372+
isOpen: true,
373+
overlayClassName: {
374+
base: "myOverlayClass",
375+
afterOpen: "myOverlayClass_after-open",
376+
beforeClose: "myOverlayClass_before-close"
377+
}
378+
};
379+
const node = createHTMLElement("div");
380+
const modal = ReactDOM.render(<Modal {...props} />, node);
381+
requestAnimationFrame(() => {
382+
moverlay(modal).className.should.be.eql(
383+
"myOverlayClass myOverlayClass_after-open"
384+
);
385+
ReactDOM.unmountComponentAtNode(node);
386+
});
377387
});
378388
});
379389

@@ -668,11 +678,18 @@ export default () => {
668678
});
669679

670680
it("adds --after-open for animations", () => {
671-
const props = { isOpen: true };
672-
withModal(props, null, modal => {
681+
withElementCollector(() => {
673682
const rg = /--after-open/i;
674-
rg.test(mcontent(modal).className).should.be.ok();
675-
rg.test(moverlay(modal).className).should.be.ok();
683+
const props = { isOpen: true };
684+
const node = createHTMLElement("div");
685+
const modal = ReactDOM.render(<Modal {...props} />, node);
686+
requestAnimationFrame(() => {
687+
const contentName = modal.portal.content.className;
688+
const overlayName = modal.portal.overlay.className;
689+
rg.test(contentName).should.be.ok();
690+
rg.test(overlayName).should.be.ok();
691+
ReactDOM.unmountComponentAtNode(node);
692+
});
676693
});
677694
});
678695

@@ -722,25 +739,24 @@ export default () => {
722739
});
723740

724741
it("keeps the modal in the DOM until closeTimeoutMS elapses", done => {
725-
const closeTimeoutMS = 100;
742+
function checkDOM(count) {
743+
const overlay = document.querySelectorAll(".ReactModal__Overlay");
744+
const content = document.querySelectorAll(".ReactModal__Content");
745+
overlay.length.should.be.eql(count);
746+
content.length.should.be.eql(count);
747+
}
748+
withElementCollector(() => {
749+
const closeTimeoutMS = 100;
750+
const props = { isOpen: true, closeTimeoutMS };
751+
const node = createHTMLElement("div");
752+
const modal = ReactDOM.render(<Modal {...props} />, node);
726753

727-
const props = { isOpen: true, closeTimeoutMS };
728-
withModal(props, null, modal => {
729754
modal.portal.closeWithTimeout();
730-
731-
function checkDOM(count) {
732-
const overlay = document.querySelectorAll(".ReactModal__Overlay");
733-
const content = document.querySelectorAll(".ReactModal__Content");
734-
overlay.length.should.be.eql(count);
735-
content.length.should.be.eql(count);
736-
}
737-
738-
// content is still mounted after modal is gone
739755
checkDOM(1);
740756

741757
setTimeout(() => {
742-
// content is unmounted after specified timeout
743758
checkDOM(0);
759+
ReactDOM.unmountComponentAtNode(node);
744760
done();
745761
}, closeTimeoutMS);
746762
});

Diff for: src/components/ModalPortal.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -222,14 +222,16 @@ export default class ModalPortal extends Component {
222222
}
223223

224224
this.setState({ isOpen: true }, () => {
225-
this.setState({ afterOpen: true });
226-
227-
if (this.props.isOpen && this.props.onAfterOpen) {
228-
this.props.onAfterOpen({
229-
overlayEl: this.overlay,
230-
contentEl: this.content
231-
});
232-
}
225+
requestAnimationFrame(() => {
226+
this.setState({ afterOpen: true });
227+
228+
if (this.props.isOpen && this.props.onAfterOpen) {
229+
this.props.onAfterOpen({
230+
overlayEl: this.overlay,
231+
contentEl: this.content
232+
});
233+
}
234+
});
233235
});
234236
}
235237
};

0 commit comments

Comments
 (0)