Skip to content

Commit

Permalink
Refactor to use count instead of instances
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Fuller committed Jun 24, 2017
1 parent 8f30171 commit a78049b
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 65 deletions.
15 changes: 1 addition & 14 deletions src/components/Modal.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import ExecutionEnvironment from 'exenv';
import ModalPortal from './ModalPortal';
import * as ariaAppHider from '../helpers/ariaAppHider';
import * as refCount from '../helpers/refCount';
import { AppElement, SafeHTMLElement } from '../helpers/appElement';

export const portalClassName = 'ReactModalPortal';
export const bodyOpenClassName = 'ReactModal__Body--open';

const EE = ExecutionEnvironment;
const renderSubtreeIntoContainer = ReactDOM.unstable_renderSubtreeIntoContainer;

const SafeHTMLElement = EE.canUseDOM ? window.HTMLElement : {};
const AppElement = EE.canUseDOM ? document.body : { appendChild() {} };

function getParentElement(parentSelector) {
return parentSelector();
}
Expand Down Expand Up @@ -133,8 +128,6 @@ export default class Modal extends Component {
componentWillUnmount() {
if (!this.node) return;

refCount.remove(this);

const state = this.portal.state;
const now = Date.now();
const closesAt = state.isOpen && this.props.closeTimeoutMS
Expand All @@ -159,12 +152,6 @@ export default class Modal extends Component {
}

renderPortal = props => {
if (props.isOpen) {
refCount.add(this);
} else {
refCount.remove(this);
}

this.portal = renderSubtreeIntoContainer(this, (
<ModalPortal defaultStyles={Modal.defaultStyles} {...props} />
), this.node);
Expand Down
28 changes: 28 additions & 0 deletions src/components/ModalPortal.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import React, { Component } from 'react';
import { PropTypes } from 'prop-types';
import * as focusManager from '../helpers/focusManager';
import scopeTab from '../helpers/scopeTab';
import * as ariaAppHider from '../helpers/ariaAppHider';
import * as refCount from '../helpers/refCount';
import { SafeHTMLElement } from '../helpers/appElement';

// so that our CSS is statically analyzable
const CLASS_NAMES = {
Expand Down Expand Up @@ -38,6 +41,9 @@ export default class ModalPortal extends Component {
PropTypes.string,
PropTypes.object
]),
bodyOpenClassName: PropTypes.string,
ariaHideApp: PropTypes.bool,
appElement: PropTypes.instanceOf(SafeHTMLElement),
onAfterOpen: PropTypes.func,
onRequestClose: PropTypes.func,
closeTimeoutMS: PropTypes.number,
Expand All @@ -62,6 +68,7 @@ export default class ModalPortal extends Component {
// Focus needs to be set when mounting and already open
if (this.props.isOpen) {
this.setFocusAfterRender(true);
this.addModalInstance();
this.open();
}
}
Expand All @@ -70,8 +77,10 @@ export default class ModalPortal extends Component {
// Focus only needs to be set once when the modal is being opened
if (!this.props.isOpen && newProps.isOpen) {
this.setFocusAfterRender(true);
this.addModalInstance();
this.open();
} else if (this.props.isOpen && !newProps.isOpen) {
this.removeModalInstance();
this.close();
}
}
Expand All @@ -84,6 +93,7 @@ export default class ModalPortal extends Component {
}

componentWillUnmount() {
this.removeModalInstance();
clearTimeout(this.closeTimer);
}

Expand All @@ -99,6 +109,24 @@ export default class ModalPortal extends Component {
this.content = content;
}

addModalInstance() {
const { appElement, ariaHideApp, bodyOpenClassName } = this.props;
refCount.add(bodyOpenClassName);
// Add aria-hidden to appELement
if (ariaHideApp) {
ariaAppHider.hide(appElement);
}
}

removeModalInstance() {
const { appElement, ariaHideApp, bodyOpenClassName } = this.props;
refCount.remove(bodyOpenClassName);
// Reset aria-hidden attribute if all modals have been removed
if (ariaHideApp && refCount.totalCount() < 1) {
ariaAppHider.show(appElement);
}
}

afterClose = () => {
focusManager.returnFocus();
focusManager.teardownScopedFocus();
Expand Down
6 changes: 6 additions & 0 deletions src/helpers/appElement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import ExecutionEnvironment from 'exenv';

const EE = ExecutionEnvironment;

export const SafeHTMLElement = EE.canUseDOM ? window.HTMLElement : {};
export const AppElement = EE.canUseDOM ? document.body : { appendChild() {} };
67 changes: 16 additions & 51 deletions src/helpers/refCount.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,28 @@
import elementClass from 'element-class';

import * as ariaAppHider from './ariaAppHider';

const modals = {};

function totalCount() {
// Find all modal types and filter out empty arrays
const values = Object.keys(modals)
.map(key => modals[key])
.filter(el => el.length) || [];
return values.length;
}

export function add(element) {
const { props } = element;
const identifier = props.id || props.contentLabel || props;
const bodyClassName = props.bodyOpenClassName;

export function add(bodyClass) {
// Set variable and default if none
let modalReference = modals[bodyClassName];
if (!modalReference) {
modals[bodyClassName] = [];
modalReference = modals[bodyClassName];
}

// Add reference to modal for specified key if it does not exist already
if (modalReference.indexOf(identifier) === -1) {
modalReference.push(identifier);
if (!modals[bodyClass]) {
modals[bodyClass] = 0;
}
modals[bodyClass] += 1;
elementClass(document.body).add(bodyClass);
}

if (modalReference.length > 0) {
elementClass(document.body).add(bodyClassName);
export function remove(bodyClass) {
if (modals[bodyClass]) {
modals[bodyClass] -= 1;
}

// Add aria-hidden to appELement
if (props.ariaHideApp) {
ariaAppHider.hide(props.appElement);
// Remove class if no more modals are open
if (modals[bodyClass] === 0) {
elementClass(document.body).remove(bodyClass);
}
}

export function remove(element) {
const { props } = element;
const identifier = props.id || props.contentLabel || props;
const bodyClassName = props.bodyOpenClassName;
const modalReference = modals[bodyClassName];

// Remove className if no more references
if (modalReference) {
const index = modalReference.indexOf(identifier);
if (index !== -1) {
modalReference.splice(index, 1);
}
if (modalReference.length === 0) {
elementClass(document.body).remove(bodyClassName);
}
}

// Reset aria-hidden attribute if all modals have been removed
if (props.ariaHideApp && totalCount() < 1) {
ariaAppHider.show(props.appElement);
}
export function totalCount() {
const count = Object.keys(modals)
.reduce((acc, curr) => acc + modals[curr], 0);
return count;
}

0 comments on commit a78049b

Please sign in to comment.