Skip to content

Commit

Permalink
[fixed] Focus trap when reentering document (reactjs#742) (reactjs#791)
Browse files Browse the repository at this point in the history
* [added] Focus trap when reentering document (reactjs#742)
  • Loading branch information
Matthew Holloway authored and diasbruno committed Dec 9, 2019
1 parent 98dd5be commit eb20444
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/components/ModalPortal.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import scopeTab from "../helpers/scopeTab";
import * as ariaAppHider from "../helpers/ariaAppHider";
import * as classList from "../helpers/classList";
import SafeHTMLElement from "../helpers/safeHTMLElement";
import portalOpenInstances from "../helpers/portalOpenInstances";
import "../helpers/bodyTrap";

// so that our CSS is statically analyzable
const CLASS_NAMES = {
Expand Down Expand Up @@ -151,6 +153,8 @@ export default class ModalPortal extends Component {
ariaHiddenInstances += 1;
ariaAppHider.hide(appElement);
}

portalOpenInstances.register(this);
}

afterClose = () => {
Expand Down Expand Up @@ -191,6 +195,8 @@ export default class ModalPortal extends Component {
if (this.props.onAfterClose) {
this.props.onAfterClose();
}

portalOpenInstances.deregister(this);
};

open = () => {
Expand Down
52 changes: 52 additions & 0 deletions src/helpers/bodyTrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import portalOpenInstances from "./portalOpenInstances";
// Body focus trap see Issue #742

let before,
after,
instances = [];

function focusContent() {
if (instances.length === 0) {
if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line no-console
console.warn(`React-Modal: Open instances > 0 expected`);
}
return;
}
instances[instances.length - 1].focusContent();
}

function bodyTrap(eventType, openInstances) {
if (!before || !after) {
before = document.createElement("div");
before.setAttribute("data-react-modal-body-trap", "");
before.style.position = "absolute";
before.style.opacity = "0";
before.setAttribute("tabindex", "0");
before.addEventListener("focus", focusContent);
after = before.cloneNode();
after.addEventListener("focus", focusContent);
}

instances = openInstances;

if (instances.length > 0) {
// Add focus trap
if (document.body.firstChild !== before) {
document.body.insertBefore(before, document.body.firstChild);
}
if (document.body.lastChild !== after) {
document.body.appendChild(after);
}
} else {
// Remove focus trap
if (before.parentElement) {
before.parentElement.removeChild(before);
}
if (after.parentElement) {
after.parentElement.removeChild(after);
}
}
}

portalOpenInstances.subscribe(bodyTrap);
55 changes: 55 additions & 0 deletions src/helpers/portalOpenInstances.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Tracks portals that are open and emits events to subscribers

class PortalOpenInstances {
constructor() {
this.openInstances = [];
this.subscribers = [];
}

register = openInstance => {
if (this.openInstances.indexOf(openInstance) !== -1) {
if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line no-console
console.warn(
`React-Modal: Cannot register modal instance that's already open`
);
}
return;
}
this.openInstances.push(openInstance);
this.emit("register");
};

deregister = openInstance => {
const index = this.openInstances.indexOf(openInstance);
if (index === -1) {
if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line no-console
console.warn(
`React-Modal: Unable to deregister ${openInstance} as it was never registered`
);
}
return;
}
this.openInstances.splice(index, 1);
this.emit("deregister");
};

subscribe = callback => {
this.subscribers.push(callback);
};

emit = eventType => {
this.subscribers.forEach(subscriber =>
subscriber(
eventType,
// shallow copy to avoid accidental mutation
this.openInstances.slice()
)
);
};
}

const portalOpenInstances = new PortalOpenInstances();

export default portalOpenInstances;

0 comments on commit eb20444

Please sign in to comment.