diff --git a/CHANGELOG.md b/CHANGELOG.md index 155e8a6..54ed58e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.12.5] +### Added +- Added `blockNavigationOut` to avoid focus out from the selected component. + ## [2.12.4] ### Fixed - Fixed issue where this library didn't work in SSR environments due to references to DOM-only variables diff --git a/README.md b/README.md index aca576c..0a32df2 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,13 @@ To determine whether parent component should focus the first available child com * **true (default)** * **false** +##### `blockNavigationOut`: boolean +Disable the navigation out from the selected component. It can be useful when a user opens a popup (or screen) and you don't want to allow the user to focus other components outside this area. + +It doesn't block focus set programmatically by `setFocus`. +* **false (default)** +* **true** + ## Props that can be applied to HOC All these props are optional. @@ -259,6 +266,9 @@ Same as in [config](#config). ### `autoRestoreFocus`: boolean Same as in [config](#config). +### `blockNavigationOut`: boolean +Same as in [config](#config). + ### `focusable`: boolean Determine whether this component should be focusable (in other words, whether it's *currently* participating in the spatial navigation tree). This allows a focusable component to be ignored as a navigation target despite being mounted (e.g. due to being off-screen, hidden, or temporarily disabled). diff --git a/package.json b/package.json index a757448..4e729b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@noriginmedia/react-spatial-navigation", - "version": "2.12.4", + "version": "2.12.5", "description": "HOC-based Spatial Navigation (key navigation) solution for React", "main": "dist/index.js", "files": [ diff --git a/src/App.js b/src/App.js index 0051b6e..d5c3667 100644 --- a/src/App.js +++ b/src/App.js @@ -119,6 +119,7 @@ const programs = shuffle([{ }]); const RETURN_KEY = 8; +const B_KEY = 66; /* eslint-disable react/prefer-stateless-function */ class MenuItem extends React.PureComponent { @@ -190,12 +191,31 @@ class Content extends React.PureComponent { super(props); this.state = { - currentProgram: null + currentProgram: null, + blockNavigationOut: false }; + this.onPressKey = this.onPressKey.bind(this); this.onProgramPress = this.onProgramPress.bind(this); } + componentDidMount() { + window.addEventListener('keydown', this.onPressKey); + } + + componentWillUnmount() { + window.removeEventListener('keydown', this.onPressKey); + } + + onPressKey(event) { + if (event.keyCode === B_KEY) { + const {blockNavigationOut: blocked} = this.state; + + console.warn(`blockNavigationOut: ${!blocked}. Press B to ${blocked ? 'block' : 'unblock '}`); + this.setState((prevState) => ({blockNavigationOut: !prevState.blockNavigationOut})); + } + } + onProgramPress(programProps, {pressedKeys} = {}) { if (pressedKeys && pressedKeys[KEY_ENTER] > 1) { return; @@ -206,6 +226,8 @@ class Content extends React.PureComponent { } render() { + const {blockNavigationOut} = this.state; + // console.log('content rendered: ', this.props.realFocusKey); return ( @@ -213,6 +235,7 @@ class Content extends React.PureComponent { ); } diff --git a/src/spatialNavigation.js b/src/spatialNavigation.js index ade9b7d..e7ea191 100644 --- a/src/spatialNavigation.js +++ b/src/spatialNavigation.js @@ -626,7 +626,9 @@ class SpatialNavigation { this.saveLastFocusedChildKey(parentComponent, focusKey); - this.smartNavigate(direction, parentFocusKey, details); + if (!parentComponent || !parentComponent.blockNavigationOut) { + this.smartNavigate(direction, parentFocusKey, details); + } } } } @@ -728,7 +730,8 @@ class SpatialNavigation { onUpdateHasFocusedChild, preferredChildFocusKey, autoRestoreFocus, - focusable + focusable, + blockNavigationOut }) { this.focusableComponents[focusKey] = { focusKey, @@ -745,6 +748,7 @@ class SpatialNavigation { lastFocusedChildKey: null, preferredChildFocusKey, focusable, + blockNavigationOut, autoRestoreFocus, layout: { x: 0, @@ -990,7 +994,7 @@ class SpatialNavigation { }); } - updateFocusable(focusKey, {node, preferredChildFocusKey, focusable}) { + updateFocusable(focusKey, {node, preferredChildFocusKey, focusable, blockNavigationOut}) { if (this.nativeMode) { return; } @@ -1000,6 +1004,7 @@ class SpatialNavigation { if (component) { component.preferredChildFocusKey = preferredChildFocusKey; component.focusable = focusable; + component.blockNavigationOut = blockNavigationOut; if (node) { component.node = node; diff --git a/src/withFocusable.js b/src/withFocusable.js index 9eabe4d..bc93f93 100644 --- a/src/withFocusable.js +++ b/src/withFocusable.js @@ -19,7 +19,8 @@ const omitProps = (keys) => mapProps((props) => omit(props, keys)); const withFocusable = ({ forgetLastFocusedChild: configForgetLastFocusedChild = false, trackChildren: configTrackChildren = false, - autoRestoreFocus: configAutoRestoreFocus + autoRestoreFocus: configAutoRestoreFocus, + blockNavigationOut: configBlockNavigationOut = false } = {}) => compose( getContext({ /** @@ -111,7 +112,8 @@ const withFocusable = ({ onUpdateHasFocusedChild, trackChildren, focusable = true, - autoRestoreFocus = true + autoRestoreFocus = true, + blockNavigationOut = false } = this.props; const node = SpatialNavigation.isNativeMode() ? this : findDOMNode(this); @@ -129,6 +131,7 @@ const withFocusable = ({ onUpdateHasFocusedChild, forgetLastFocusedChild: (configForgetLastFocusedChild || forgetLastFocusedChild), trackChildren: (configTrackChildren || trackChildren), + blockNavigationOut: (configBlockNavigationOut || blockNavigationOut), autoRestoreFocus: configAutoRestoreFocus !== undefined ? configAutoRestoreFocus : autoRestoreFocus, focusable }); @@ -137,7 +140,8 @@ const withFocusable = ({ const { realFocusKey: focusKey, preferredChildFocusKey, - focusable = true + focusable = true, + blockNavigationOut = false } = this.props; const node = SpatialNavigation.isNativeMode() ? this : findDOMNode(this); @@ -145,7 +149,8 @@ const withFocusable = ({ SpatialNavigation.updateFocusable(focusKey, { node, preferredChildFocusKey, - focusable + focusable, + blockNavigationOut: (configBlockNavigationOut || blockNavigationOut) }); }, componentWillUnmount() {