Skip to content

Commit 09880af

Browse files
committed
[fix] keep references of modals when available.
keeping a reference of all modals in order to manage when to add/remove classes and aria from elements correctly.
1 parent ebec638 commit 09880af

File tree

3 files changed

+58
-7
lines changed

3 files changed

+58
-7
lines changed

lib/components/Modal.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ExecutionEnvironment from 'exenv';
44
import elementClass from 'element-class';
55
import ModalPortal from './ModalPortal';
66
import * as ariaAppHider from '../helpers/ariaAppHider';
7+
import * as refCount from '../helpers/refCount';
78

89
const renderSubtreeIntoContainer = ReactDOM.unstable_renderSubtreeIntoContainer;
910

@@ -83,12 +84,17 @@ export default class Modal extends Component {
8384
this.node = document.createElement('div');
8485
this.node.className = this.props.portalClassName;
8586

87+
if (this.props.isOpen) refCount.add(this);
88+
8689
const parent = getParentElement(this.props.parentSelector);
8790
parent.appendChild(this.node);
8891
this.renderPortal(this.props);
8992
}
9093

9194
componentWillReceiveProps (newProps) {
95+
if (newProps.isOpen) refCount.add(this);
96+
if (!newProps.isOpen) refCount.remove(this);
97+
9298
const currentParent = getParentElement(this.props.parentSelector);
9399
const newParent = getParentElement(newProps.parentSelector);
94100

@@ -101,18 +107,22 @@ export default class Modal extends Component {
101107
}
102108

103109
componentWillUnmount () {
110+
refCount.remove(this);
111+
104112
if (this.props.ariaHideApp) {
105113
ariaAppHider.show(this.props.appElement);
106114
}
107115

108116
ReactDOM.unmountComponentAtNode(this.node);
109117
const parent = getParentElement(this.props.parentSelector);
110118
parent.removeChild(this.node);
111-
elementClass(document.body).remove('ReactModal__Body--open');
119+
if (refCount.count() === 0) {
120+
elementClass(document.body).remove('ReactModal__Body--open');
121+
}
112122
}
113123

114124
renderPortal (props) {
115-
if (props.isOpen) {
125+
if (props.isOpen || refCount.count() > 0) {
116126
elementClass(document.body).add('ReactModal__Body--open');
117127
} else {
118128
elementClass(document.body).remove('ReactModal__Body--open');

lib/helpers/refCount.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const modals = [];
2+
3+
export function add (element) {
4+
if (modals.indexOf(element) === -1) {
5+
modals.push(element);
6+
}
7+
}
8+
9+
export function remove (element) {
10+
const index = modals.indexOf(element);
11+
if (index === -1) {
12+
return;
13+
}
14+
modals.splice(index, 1);
15+
}
16+
17+
export function count () {
18+
return modals.length;
19+
}

specs/Modal.spec.js

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,17 +112,21 @@ describe('Modal', () => {
112112
});
113113

114114
it('give back focus to previous element or modal.', (done) => {
115+
function cleanup () {
116+
unmountModal();
117+
done();
118+
}
115119
const modal = renderModal({
116120
isOpen: true,
117121
onRequestClose () {
118-
unmountModal();
119-
done();
122+
cleanup();
120123
}
121124
}, null, () => {});
122125

123126
renderModal({
124127
isOpen: true,
125128
onRequestClose () {
129+
unmountModal();
126130
Simulate.keyDown(modal.portal.content, {
127131
// The keyCode is all that matters, so this works
128132
key: 'FakeKeyToTestLater',
@@ -175,6 +179,7 @@ describe('Modal', () => {
175179
preventDefault () { tabPrevented = true; }
176180
});
177181
expect(tabPrevented).toEqual(true);
182+
unmountModal();
178183
});
179184

180185
it('supports portalClassName', () => {
@@ -204,26 +209,31 @@ describe('Modal', () => {
204209
it('overrides the default styles when a custom overlayClassName is used', () => {
205210
const modal = renderModal({ isOpen: true, overlayClassName: 'myOverlayClass' });
206211
expect(modal.portal.overlay.style.backgroundColor).toEqual('');
212+
unmountModal();
207213
});
208214

209215
it('supports adding style to the modal contents', () => {
210216
const modal = renderModal({ isOpen: true, style: { content: { width: '20px' } } });
211217
expect(modal.portal.content.style.width).toEqual('20px');
218+
unmountModal();
212219
});
213220

214221
it('supports overriding style on the modal contents', () => {
215222
const modal = renderModal({ isOpen: true, style: { content: { position: 'static' } } });
216223
expect(modal.portal.content.style.position).toEqual('static');
224+
unmountModal();
217225
});
218226

219227
it('supports adding style on the modal overlay', () => {
220228
const modal = renderModal({ isOpen: true, style: { overlay: { width: '75px' } } });
221229
expect(modal.portal.overlay.style.width).toEqual('75px');
230+
unmountModal();
222231
});
223232

224233
it('supports overriding style on the modal overlay', () => {
225234
const modal = renderModal({ isOpen: true, style: { overlay: { position: 'static' } } });
226235
expect(modal.portal.overlay.style.position).toEqual('static');
236+
unmountModal();
227237
});
228238

229239
it('supports overriding the default styles', () => {
@@ -234,15 +244,27 @@ describe('Modal', () => {
234244
const modal = renderModal({ isOpen: true });
235245
expect(modal.portal.content.style.position).toEqual(newStyle);
236246
Modal.defaultStyles.content.position = previousStyle;
247+
unmountModal();
248+
});
249+
250+
251+
it('should remove class from body when no modals opened', () => {
252+
renderModal({ isOpen: true });
253+
renderModal({ isOpen: true });
254+
expect(document.body.className.indexOf('ReactModal__Body--open') > -1).toBe(true);
255+
unmountModal();
256+
expect(document.body.className.indexOf('ReactModal__Body--open') > -1).toBe(true);
257+
unmountModal();
258+
expect(document.body.className.indexOf('ReactModal__Body--open') === -1).toBe(true);
237259
});
238260

239261
it('adds class to body when open', () => {
240262
renderModal({ isOpen: false });
241263
expect(document.body.className.indexOf('ReactModal__Body--open') !== -1).toEqual(false);
242-
264+
unmountModal();
243265
renderModal({ isOpen: true });
244-
expect(document.body.className.indexOf('ReactModal__Body--open') !== -1).toEqual(true);
245-
266+
expect(document.body.className.indexOf('ReactModal__Body--open') > -1).toEqual(true);
267+
unmountModal();
246268
renderModal({ isOpen: false });
247269
expect(document.body.className.indexOf('ReactModal__Body--open') !== -1).toEqual(false);
248270
unmountModal();

0 commit comments

Comments
 (0)