Skip to content
This repository was archived by the owner on May 5, 2023. It is now read-only.

Commit c625b54

Browse files
authored
Added blockNavigationOut prop (#72)
* Added blockNavigationOut prop * Updated changelog * Added blockNavigationOut as a config prop * Updated readme
1 parent 62a0310 commit c625b54

6 files changed

+56
-9
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [2.12.5]
8+
### Added
9+
- Added `blockNavigationOut` to avoid focus out from the selected component.
10+
711
## [2.12.4]
812
### Fixed
913
- Fixed issue where this library didn't work in SSR environments due to references to DOM-only variables

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,13 @@ To determine whether parent component should focus the first available child com
247247
* **true (default)**
248248
* **false**
249249

250+
##### `blockNavigationOut`: boolean
251+
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.
252+
253+
It doesn't block focus set programmatically by `setFocus`.
254+
* **false (default)**
255+
* **true**
256+
250257
## Props that can be applied to HOC
251258
All these props are optional.
252259

@@ -259,6 +266,9 @@ Same as in [config](#config).
259266
### `autoRestoreFocus`: boolean
260267
Same as in [config](#config).
261268

269+
### `blockNavigationOut`: boolean
270+
Same as in [config](#config).
271+
262272
### `focusable`: boolean
263273
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).
264274

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@noriginmedia/react-spatial-navigation",
3-
"version": "2.12.4",
3+
"version": "2.12.5",
44
"description": "HOC-based Spatial Navigation (key navigation) solution for React",
55
"main": "dist/index.js",
66
"files": [

src/App.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ const programs = shuffle([{
119119
}]);
120120

121121
const RETURN_KEY = 8;
122+
const B_KEY = 66;
122123

123124
/* eslint-disable react/prefer-stateless-function */
124125
class MenuItem extends React.PureComponent {
@@ -190,12 +191,31 @@ class Content extends React.PureComponent {
190191
super(props);
191192

192193
this.state = {
193-
currentProgram: null
194+
currentProgram: null,
195+
blockNavigationOut: false
194196
};
195197

198+
this.onPressKey = this.onPressKey.bind(this);
196199
this.onProgramPress = this.onProgramPress.bind(this);
197200
}
198201

202+
componentDidMount() {
203+
window.addEventListener('keydown', this.onPressKey);
204+
}
205+
206+
componentWillUnmount() {
207+
window.removeEventListener('keydown', this.onPressKey);
208+
}
209+
210+
onPressKey(event) {
211+
if (event.keyCode === B_KEY) {
212+
const {blockNavigationOut: blocked} = this.state;
213+
214+
console.warn(`blockNavigationOut: ${!blocked}. Press B to ${blocked ? 'block' : 'unblock '}`);
215+
this.setState((prevState) => ({blockNavigationOut: !prevState.blockNavigationOut}));
216+
}
217+
}
218+
199219
onProgramPress(programProps, {pressedKeys} = {}) {
200220
if (pressedKeys && pressedKeys[KEY_ENTER] > 1) {
201221
return;
@@ -206,13 +226,16 @@ class Content extends React.PureComponent {
206226
}
207227

208228
render() {
229+
const {blockNavigationOut} = this.state;
230+
209231
// console.log('content rendered: ', this.props.realFocusKey);
210232

211233
return (<View style={styles.content}>
212234
<Active program={this.state.currentProgram} />
213235
<CategoriesFocusable
214236
focusKey={'CATEGORIES'}
215237
onProgramPress={this.onProgramPress}
238+
blockNavigationOut={blockNavigationOut}
216239
/>
217240
</View>);
218241
}

src/spatialNavigation.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,9 @@ class SpatialNavigation {
626626

627627
this.saveLastFocusedChildKey(parentComponent, focusKey);
628628

629-
this.smartNavigate(direction, parentFocusKey, details);
629+
if (!parentComponent || !parentComponent.blockNavigationOut) {
630+
this.smartNavigate(direction, parentFocusKey, details);
631+
}
630632
}
631633
}
632634
}
@@ -728,7 +730,8 @@ class SpatialNavigation {
728730
onUpdateHasFocusedChild,
729731
preferredChildFocusKey,
730732
autoRestoreFocus,
731-
focusable
733+
focusable,
734+
blockNavigationOut
732735
}) {
733736
this.focusableComponents[focusKey] = {
734737
focusKey,
@@ -745,6 +748,7 @@ class SpatialNavigation {
745748
lastFocusedChildKey: null,
746749
preferredChildFocusKey,
747750
focusable,
751+
blockNavigationOut,
748752
autoRestoreFocus,
749753
layout: {
750754
x: 0,
@@ -990,7 +994,7 @@ class SpatialNavigation {
990994
});
991995
}
992996

993-
updateFocusable(focusKey, {node, preferredChildFocusKey, focusable}) {
997+
updateFocusable(focusKey, {node, preferredChildFocusKey, focusable, blockNavigationOut}) {
994998
if (this.nativeMode) {
995999
return;
9961000
}
@@ -1000,6 +1004,7 @@ class SpatialNavigation {
10001004
if (component) {
10011005
component.preferredChildFocusKey = preferredChildFocusKey;
10021006
component.focusable = focusable;
1007+
component.blockNavigationOut = blockNavigationOut;
10031008

10041009
if (node) {
10051010
component.node = node;

src/withFocusable.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ const omitProps = (keys) => mapProps((props) => omit(props, keys));
1919
const withFocusable = ({
2020
forgetLastFocusedChild: configForgetLastFocusedChild = false,
2121
trackChildren: configTrackChildren = false,
22-
autoRestoreFocus: configAutoRestoreFocus
22+
autoRestoreFocus: configAutoRestoreFocus,
23+
blockNavigationOut: configBlockNavigationOut = false
2324
} = {}) => compose(
2425
getContext({
2526
/**
@@ -111,7 +112,8 @@ const withFocusable = ({
111112
onUpdateHasFocusedChild,
112113
trackChildren,
113114
focusable = true,
114-
autoRestoreFocus = true
115+
autoRestoreFocus = true,
116+
blockNavigationOut = false
115117
} = this.props;
116118

117119
const node = SpatialNavigation.isNativeMode() ? this : findDOMNode(this);
@@ -129,6 +131,7 @@ const withFocusable = ({
129131
onUpdateHasFocusedChild,
130132
forgetLastFocusedChild: (configForgetLastFocusedChild || forgetLastFocusedChild),
131133
trackChildren: (configTrackChildren || trackChildren),
134+
blockNavigationOut: (configBlockNavigationOut || blockNavigationOut),
132135
autoRestoreFocus: configAutoRestoreFocus !== undefined ? configAutoRestoreFocus : autoRestoreFocus,
133136
focusable
134137
});
@@ -137,15 +140,17 @@ const withFocusable = ({
137140
const {
138141
realFocusKey: focusKey,
139142
preferredChildFocusKey,
140-
focusable = true
143+
focusable = true,
144+
blockNavigationOut = false
141145
} = this.props;
142146

143147
const node = SpatialNavigation.isNativeMode() ? this : findDOMNode(this);
144148

145149
SpatialNavigation.updateFocusable(focusKey, {
146150
node,
147151
preferredChildFocusKey,
148-
focusable
152+
focusable,
153+
blockNavigationOut: (configBlockNavigationOut || blockNavigationOut)
149154
});
150155
},
151156
componentWillUnmount() {

0 commit comments

Comments
 (0)