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 + ? +

+ +
+ Another focusable thing +
+
+ + : false; + + const parentTrap = this.state.activeParentTrap + ? +
+ +
+ Another focusable thing +
+ + {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',