From d92723378d6f860b06f118d3e4d61cd7e29184b7 Mon Sep 17 00:00:00 2001
From: Chandler Prall
Date: Tue, 14 Aug 2018 11:26:20 -0600
Subject: [PATCH] Add code & demo to pause/unpause parents of nested traps
---
demo/index.html | 6 +++
demo/js/demo-nested.js | 86 +++++++++++++++++++++++++++++++++++++++++
demo/js/index.js | 1 +
package.json | 2 +
src/focus-trap-react.js | 32 +++++++++++++++
5 files changed, 127 insertions(+)
create mode 100644 demo/js/demo-nested.js
diff --git a/demo/index.html b/demo/index.html
index ac7abb8b..ccf4f875 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -53,6 +53,12 @@ demo autofocus
+ nested traps
+
+ The trap contains a nested child trap. The parent trap automatically pauses when the child is active.
+
+
+
☜
Return to the repository
diff --git a/demo/js/demo-nested.js b/demo/js/demo-nested.js
new file mode 100644
index 00000000..767383a4
--- /dev/null
+++ b/demo/js/demo-nested.js
@@ -0,0 +1,86 @@
+const React = require('react');
+const ReactDOM = require('react-dom');
+const FocusTrap = require('../../dist/focus-trap-react');
+
+const container = document.getElementById('demo-nested');
+
+class DemoNestedTrap extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ activeParentTrap: false,
+ activeChildTrap: false,
+ };
+
+ this.mountParentTrap = this.mountParentTrap.bind(this);
+ this.unmountParentTrap = this.unmountParentTrap.bind(this);
+ this.mountChildTrap = this.mountChildTrap.bind(this);
+ this.unmountChildTrap = this.unmountChildTrap.bind(this);
+ }
+
+ mountParentTrap() {
+ this.setState({ activeParentTrap: true });
+ }
+
+ unmountParentTrap() {
+ this.setState({ activeParentTrap: false });
+ }
+
+ mountChildTrap() {
+ this.setState({ activeChildTrap: true });
+ }
+
+ unmountChildTrap() {
+ this.setState({ activeChildTrap: false });
+ }
+
+ render() {
+ const childTrap = this.state.activeChildTrap
+ ?
+
+
+
+
+
+ : false;
+
+ const parentTrap = this.state.activeParentTrap
+ ?
+
+
+
+
+ {childTrap}
+
+
+ : false;
+
+ return (
+
+
+
+
+ {parentTrap}
+
+ );
+ }
+}
+
+ReactDOM.render(, container);
diff --git a/demo/js/index.js b/demo/js/index.js
index 155eb2d5..971faecf 100644
--- a/demo/js/index.js
+++ b/demo/js/index.js
@@ -2,3 +2,4 @@ require('./demo-defaults');
require('./demo-ffne');
require('./demo-special-element');
require('./demo-autofocus');
+require('./demo-nested');
diff --git a/package.json b/package.json
index 2b67bd75..b822972a 100644
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
"eslint-plugin-react": "^6.10.3",
"jest": "^23.4.0",
"prettier": "^1.2.2",
+ "prop-types": "^15.0.0",
"react": "^16.0.0",
"react-dom": "^16.0.0"
},
@@ -61,6 +62,7 @@
"focus-trap": "^3.0.0"
},
"peerDependencies": {
+ "prop-types": "^15.0.0",
"react": "0.14.x || ^15.0.0 || ^16.0.0",
"react-dom": "0.14.x || ^15.0.0 || ^16.0.0"
},
diff --git a/src/focus-trap-react.js b/src/focus-trap-react.js
index db17dbaf..ca070b50 100644
--- a/src/focus-trap-react.js
+++ b/src/focus-trap-react.js
@@ -1,3 +1,4 @@
+const PropTypes = require('prop-types');
const React = require('react');
const createFocusTrap = require('focus-trap');
@@ -9,6 +10,13 @@ const checkedProps = [
'_createFocusTrap'
];
+const FocusTrapReactContextType = {
+ _FocusTrapReact: PropTypes.shape({
+ pause: PropTypes.func.isRequired,
+ unpause: PropTypes.func.isRequired,
+ })
+};
+
class FocusTrap extends React.Component {
constructor(props) {
super(props)
@@ -18,6 +26,19 @@ class FocusTrap extends React.Component {
}
}
+ getChildContext() {
+ return {
+ _FocusTrapReact: {
+ pause: () => this.focusTrap.pause(),
+ unpause: () => {
+ if (this.props.paused === false) {
+ this.focusTrap.unpause()
+ }
+ }
+ }
+ };
+ }
+
componentDidMount() {
// We need to hijack the returnFocusOnDeactivate option,
// because React can move focus into the element before we arrived at
@@ -45,6 +66,10 @@ class FocusTrap extends React.Component {
if (this.props.paused) {
this.focusTrap.pause();
}
+
+ // if there is a _FocusTrapReact context from a parent focus trap, pause it
+ const {_FocusTrapReact: {pause} = {}} = this.context;
+ if (pause) pause();
}
componentDidUpdate(prevProps) {
@@ -70,6 +95,10 @@ class FocusTrap extends React.Component {
) {
this.previouslyFocusedElement.focus();
}
+
+ // if there is a _FocusTrapReact context from a parent focus trap, activate it
+ const {_FocusTrapReact: {unpause} = {}} = this.context;
+ if (unpause) unpause();
}
setNode = el => {
@@ -96,6 +125,9 @@ class FocusTrap extends React.Component {
}
}
+FocusTrap.contextTypes = FocusTrapReactContextType;
+FocusTrap.childContextTypes = FocusTrapReactContextType;
+
FocusTrap.defaultProps = {
active: true,
tag: 'div',