Comments (6)
nice that worked thanks! but just one small bug, when im on Link5.js and I press right to goto Link6.js then I press down to goto the content on Link6.js then press up to go back to the menu it jumps to Link5.js instead of setting focus to Link6, its like when going from Link5 to Link6 it does not set focus to Link6 like it ignored that action?
return (<View style={[styles.menu2, this.props.hasFocusedChild ? styles.menuFocused2 : null]}>
<TopNavMenuItemFocusable focusKey={'TOPMENU-1'} itemText='Link1' linkTo='' itemId="Link1" onBecameFocused={() => this.props.history.push("/") } />
<TopNavMenuItemFocusable focusKey={'TOPMENU-2'} itemText='Link2' linkTo='Link2' itemId="Link2" onBecameFocused={() => this.props.history.push("/Link2") } />
<TopNavMenuItemFocusable focusKey={'TOPMENU-3'} itemText='Link3' linkTo='Link3' itemId="Link3" onBecameFocused={() => this.props.history.push("/Link3") } />
<TopNavMenuItemFocusable focusKey={'TOPMENU-4'} itemText='Link4' linkTo='Link4' itemId="Link4" onBecameFocused={() => this.props.history.push("/Link4") } />
<TopNavMenuItemFocusable focusKey={'TOPMENU-5'} itemText='Link5' linkTo='Link5' itemId="Link5" onBecameFocused={() => this.props.history.push("/Link5") } />
<TopNavMenuItemFocusable focusKey={'TOPMENU-6'} itemText='Link6' linkTo='Link6' itemId="Link6" onBecameFocused={() => this.props.history.push("/Link6") } />
</View>);
from react-spatial-navigation.
Hi,
From the first look I can see that you call initNavigation from many components, while this should be done once at the root of the app.
Please try to change this and see if it helps.
Also make sure that initial “setFocus” is called when the components are mounted.
from react-spatial-navigation.
Hi
Brilliant thanks! Yes that was the error I think along with having focusable={false} on Link6.js, im posting the working code below if anyone needs it.
I have a followup question regarding focus on items and how to execute actions when focus is obtained on the TouchableOpacity, im currently doing this below but it never executes when the TouchableOpacity item has focus
render() {
// console.log('Menu item rendered: ', this.props.realFocusKey);
return (<TouchableOpacity style={[styles.topNavmenuItem, this.props.focused ? styles.TopNavfocused : null]} onFocus={() => console.log("hasfocus")} ><Link id={this.props.itemId} to={this.props.linkTo} >{this.props.itemText}</Link></TouchableOpacity>);
}
Is there someway of doing this? My aim is to use the TopNav.js menu items to redirect to different pages when each item has focus rather then keypress to achieve the same thing?
Working example of the issue reported in the original post:
App.js
import React, { Component, useState } from 'react';
import './App.css';
import PrivateRoute from './PrivateRoute';
import { HashRouter as Router, Route, Link, Switch } from 'react-router-dom';
import TopNav from './components/TopNav';
import Link1 from './pages/Link1';
import Link2 from './pages/Link2';
import Link3 from './pages/Link3';
import Link4 from './pages/Link4';
import Link5 from './pages/Link5';
import Link6 from './pages/Link6';
import {initNavigation, setKeyMap, withFocusable} from '@noriginmedia/react-spatial-navigation';
initNavigation({
debug: false,
visualDebug: false
})
function App(props) {
const Auth = JSON.parse(localStorage.getItem('authData'));
const isAuthenticated = (Auth && Auth.sessionId != '') ? true : true;
return (
<Router >
<div className="App">
{isAuthenticated && (
<header className="App-header">
<TopNav />
</header>
)}
<div>
<Switch>
<Route path="/" exact component={Link1} />
<Route path="/Link2" component={Link2} />
<Route path="/Link3" component={Link3} />
<Route path="/Link4" component={Link4} />
<Route path="/Link5" component={Link5} />
<Route path="/Link6" component={Link6} />
</Switch>
</div>
</div>
</Router>
);
}
export default App;
TopNav.js
import React from 'react'
import "core-js/stable";
import "regenerator-runtime/runtime";
import PropTypes from 'prop-types';
import {View, Text, StyleSheet, TouchableOpacity, ScrollView} from 'react-native';
import {initNavigation, setKeyMap, withFocusable} from '@noriginmedia/react-spatial-navigation';
import { Link, withRouter } from 'react-router-dom';
const styles = StyleSheet.create({
menu2: {
maxWidth: 900,
flexDirection: 'row'
},
menuFocused2: {
backgroundColor: '#546e84'
},
topNavmenuItem: {
alignItems: 'center',
padding: 14,
//textDecoration: 'none',
backgroundColor: '#333',
flexDirection: 'row'
},
TopNavfocused: {
backgroundColor: '#ccc'
}
});
const KEY_ENTER = 'enter';
const RETURN_KEY = 8;
class TopMenuItem extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
}
render() {
// console.log('Menu item rendered: ', this.props.realFocusKey);
return (<TouchableOpacity style={[styles.topNavmenuItem, this.props.focused ? styles.TopNavfocused : null]}><Link id={this.props.itemId} to={this.props.linkTo} >{this.props.itemText}</Link></TouchableOpacity>);
}
}
TopMenuItem.propTypes = {
focused: PropTypes.bool.isRequired,
// realFocusKey: PropTypes.string.isRequired
};
const TopNavMenuItemFocusable = withFocusable()(TopMenuItem);
class TopNavMenu extends React.PureComponent {
constructor(props) {
super(props);
this.onPressKey = this.onPressKey.bind(this);
}
componentDidMount() {
document.getElementById('Link1').addEventListener('focus', this.handleFocus);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.onPressKey);
}
onPressKey(event) {
if (event.keyCode === RETURN_KEY) {
this.props.setFocus();
}
}
render() {
// console.log('Menu rendered: ', this.props.realFocusKey);
return (<View style={[styles.menu2, this.props.hasFocusedChild ? styles.menuFocused2 : null]}>
<TopNavMenuItemFocusable focusKey={'TOPMENU-1'} itemText='Link1' linkTo='' itemId="Link1" />
<TopNavMenuItemFocusable focusKey={'TOPMENU-2'} itemText='Link2' linkTo='Link2' itemId="Link2" />
<TopNavMenuItemFocusable focusKey={'TOPMENU-3'} itemText='Link3' linkTo='Link3' itemId="Link3" />
<TopNavMenuItemFocusable focusKey={'TOPMENU-4'} itemText='Link4' linkTo='Link4' itemId="Link4" />
<TopNavMenuItemFocusable focusKey={'TOPMENU-5'} itemText='Link5' linkTo='Link5' itemId="Link5" />
<TopNavMenuItemFocusable focusKey={'TOPMENU-6'} itemText='Link6' linkTo='Link6' itemId="Link6" />
</View>);
}
}
TopNavMenu.propTypes = {
hasFocusedChild: PropTypes.bool.isRequired
};
const TopNavMenuFocusable = withFocusable({
trackChildren: true
})(TopNavMenu);
class TopNavSpatial extends React.PureComponent {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.setFocus();
}
render() {
return (<View>
<TopNavMenuFocusable focusable={true} history={this.props.history} />
</View>
);
}
}
TopNavSpatial.propTypes = {
setFocus: PropTypes.func.isRequired,
hasFocusedChild: PropTypes.bool.isRequired,
history: PropTypes.shape({
push: PropTypes.func.isRequired
}).isRequired
};
const TopNav = withFocusable({
trackChildren: true
})(TopNavSpatial);
export default withRouter(TopNav);
Link6.js
/* eslint-disable react/no-multi-comp */
import React from 'react';
import PropTypes from 'prop-types';
import shuffle from 'lodash/shuffle';
import throttle from 'lodash/throttle';
import "core-js/stable";
import "regenerator-runtime/runtime";
import {View, Text, StyleSheet, TouchableOpacity, ScrollView} from 'react-native';
import {setKeyMap, withFocusable} from '@noriginmedia/react-spatial-navigation';
const KEY_ENTER = 'enter';
const styles = StyleSheet.create({
wrapper: {
flex: 1,
maxHeight: 400,
maxWidth: 800,
backgroundColor: '#333333',
flexDirection: 'row'
},
content: {
flex: 1
},
menu: {
maxWidth: 60,
flex: 1,
alignItems: 'center',
justifyContent: 'space-around'
},
menuFocused: {
backgroundColor: '#546e84'
},
menuItem: {
width: 50,
height: 50,
backgroundColor: '#f8f258'
},
activeWrapper: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
activeProgram: {
width: 160,
height: 120
},
activeProgramTitle: {
padding: 20,
color: 'white'
},
programWrapper: {
padding: 10,
alignItems: 'center'
},
program: {
height: 100,
width: 100
},
programTitle: {
color: 'white'
},
categoryWrapper: {
padding: 20
},
categoryTitle: {
color: 'white'
},
categoriesWrapper: {
flex: 1
},
focusedBorder: {
borderWidth: 6,
borderColor: 'red',
backgroundColor: 'white'
}
});
const categories = shuffle([{
title: 'Featured'
}, {
title: 'Cool'
}, {
title: 'Decent'
}]);
const programs = shuffle([{
title: 'Program 1',
color: '#337fdd'
}, {
title: 'Program 2',
color: '#dd4558'
}, {
title: 'Program 3',
color: '#7ddd6a'
}, {
title: 'Program 4',
color: '#dddd4d'
}, {
title: 'Program 5',
color: '#8299dd'
}, {
title: 'Program 6',
color: '#edab83'
}, {
title: 'Program 7',
color: '#60ed9e'
}, {
title: 'Program 8',
color: '#d15fb6'
}, {
title: 'Program 9',
color: '#c0ee33'
}]);
const RETURN_KEY = 8;
/* eslint-disable react/prefer-stateless-function */
class MenuItem extends React.PureComponent {
render() {
// console.log('Menu item rendered: ', this.props.realFocusKey);
return (<TouchableOpacity style={[styles.menuItem, this.props.focused ? styles.focusedBorder : null]} />);
}
}
MenuItem.propTypes = {
focused: PropTypes.bool.isRequired
// realFocusKey: PropTypes.string.isRequired
};
const MenuItemFocusable = withFocusable()(MenuItem);
class Menu extends React.PureComponent {
constructor(props) {
super(props);
this.onPressKey = this.onPressKey.bind(this);
}
componentDidMount() {
window.addEventListener('keydown', this.onPressKey);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.onPressKey);
}
onPressKey(event) {
if (event.keyCode === RETURN_KEY) {
this.props.setFocus();
}
}
render() {
// console.log('Menu rendered: ', this.props.realFocusKey);
return (<View style={[styles.menu, this.props.hasFocusedChild ? styles.menuFocused : null]}>
<MenuItemFocusable focusKey={'MENU-1'} />
<MenuItemFocusable focusKey={'MENU-2'} />
<MenuItemFocusable focusKey={'MENU-3'} />
<MenuItemFocusable focusKey={'MENU-4'} />
<MenuItemFocusable focusKey={'MENU-5'} />
<MenuItemFocusable focusKey={'MENU-6'} />
</View>);
}
}
Menu.propTypes = {
setFocus: PropTypes.func.isRequired,
hasFocusedChild: PropTypes.bool.isRequired
// realFocusKey: PropTypes.string.isRequired
};
const MenuFocusable = withFocusable({
trackChildren: true
})(Menu);
class Content extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
currentProgram: null
};
this.onProgramPress = this.onProgramPress.bind(this);
}
onProgramPress(programProps, {pressedKeys} = {}) {
if (pressedKeys && pressedKeys[KEY_ENTER] > 1) {
return;
}
this.setState({
currentProgram: programProps
});
}
render() {
// console.log('content rendered: ', this.props.realFocusKey);
return (<View style={styles.content}>
<Active program={this.state.currentProgram} />
<CategoriesFocusable
focusKey={'CATEGORIES'}
onProgramPress={this.onProgramPress}
/>
</View>);
}
}
Content.propTypes = {
// realFocusKey: PropTypes.string.isRequired
};
const ContentFocusable = withFocusable()(Content);
class Active extends React.PureComponent {
render() {
const {program} = this.props;
const style = {
backgroundColor: program ? program.color : 'grey'
};
return (<View style={styles.activeWrapper}>
<View style={[style, styles.activeProgram]} />
<Text style={styles.activeProgramTitle}>
{program ? program.title : 'No Program'}
</Text>
</View>);
}
}
Active.propTypes = {
program: PropTypes.shape({
title: PropTypes.string.isRequired,
color: PropTypes.string.isRequired
})
};
Active.defaultProps = {
program: null
};
class Program extends React.PureComponent {
render() {
// console.log('Program rendered: ', this.props.realFocusKey);
const {color, onPress, focused, title} = this.props;
const style = {
backgroundColor: color
};
return (<TouchableOpacity
onPress={onPress}
style={styles.programWrapper}
>
<View style={[style, styles.program, focused ? styles.focusedBorder : null]} />
<Text style={styles.programTitle}>
{title}
</Text>
</TouchableOpacity>);
}
}
Program.propTypes = {
title: PropTypes.string.isRequired,
color: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
focused: PropTypes.bool.isRequired
// realFocusKey: PropTypes.string.isRequired
};
const ProgramFocusable = withFocusable()(Program);
class Category extends React.PureComponent {
constructor(props) {
super(props);
this.scrollRef = null;
this.onProgramFocused = this.onProgramFocused.bind(this);
this.onProgramArrowPress = this.onProgramArrowPress.bind(this);
}
onProgramFocused({x}) {
this.scrollRef.scrollTo({
x
});
}
onProgramArrowPress(direction, {categoryIndex, programIndex}) {
if (direction === 'right' && programIndex === programs.length - 1 && categoryIndex < categories.length - 1) {
this.props.setFocus(`CATEGORY-${categoryIndex + 1}`);
return false;
}
return true;
}
render() {
// console.log('Category rendered: ', this.props.realFocusKey);
return (<View style={styles.categoryWrapper}>
<Text style={styles.categoryTitle}>
{this.props.title}
</Text>
<ScrollView
horizontal
ref={(reference) => {
if (reference) {
this.scrollRef = reference;
}
}}
>
{programs.map((program, index) => ((<ProgramFocusable
{...program}
focusKey={`PROGRAM-${this.props.realFocusKey}-${index}`}
onPress={() => this.props.onProgramPress(program)}
onEnterPress={this.props.onProgramPress}
key={program.title}
onBecameFocused={this.onProgramFocused}
onArrowPress={this.onProgramArrowPress}
programIndex={index}
categoryIndex={this.props.categoryIndex}
/>)))}
</ScrollView>
</View>);
}
}
Category.propTypes = {
title: PropTypes.string.isRequired,
onProgramPress: PropTypes.func.isRequired,
realFocusKey: PropTypes.string.isRequired,
categoryIndex: PropTypes.number.isRequired,
setFocus: PropTypes.func.isRequired
};
const CategoryFocusable = withFocusable()(Category);
class Categories extends React.PureComponent {
constructor(props) {
super(props);
this.scrollRef = null;
this.onCategoryFocused = this.onCategoryFocused.bind(this);
}
onCategoryFocused({y}) {
this.scrollRef.scrollTo({
y
});
}
render() {
// console.log('Categories rendered: ', this.props.realFocusKey);
return (<ScrollView
ref={(reference) => {
if (reference) {
this.scrollRef = reference;
}
}}
style={styles.categoriesWrapper}
>
{categories.map((category, index) => (<CategoryFocusable
focusKey={`CATEGORY-${index}`}
{...category}
onProgramPress={this.props.onProgramPress}
key={category.title}
onBecameFocused={this.onCategoryFocused}
categoryIndex={index}
// preferredChildFocusKey={`PROGRAM-CATEGORY-${index}-${programs.length - 1}`}
/>))}
</ScrollView>);
}
}
Categories.propTypes = {
onProgramPress: PropTypes.func.isRequired,
realFocusKey: PropTypes.string.isRequired
};
const CategoriesFocusable = withFocusable()(Categories);
class Spatial extends React.PureComponent {
constructor(props) {
super(props);
this.onWheel = this.onWheel.bind(this);
this.throttledWheelHandler = throttle(this.throttledWheelHandler.bind(this), 500, {trailing: false});
}
componentDidMount() {
window.addEventListener('wheel', this.onWheel, {passive: false});
}
componentWillUnmount() {
window.removeEventListener('wheel', this.onWheel);
}
onWheel(event) {
event.preventDefault();
this.throttledWheelHandler(event);
}
throttledWheelHandler(event) {
event.preventDefault();
const {deltaY, deltaX} = event;
const {navigateByDirection} = this.props;
if (deltaY > 1) {
navigateByDirection('down');
} else if (deltaY < 0) {
navigateByDirection('up');
} else if (deltaX > 1) {
navigateByDirection('right');
} else if (deltaX < 1) {
navigateByDirection('left');
}
}
render() {
return (<View style={styles.wrapper}>
<MenuFocusable
focusKey={'MENU'}
/>
<ContentFocusable
focusKey={'CONTENT'}
/>
</View>);
}
}
Spatial.propTypes = {
navigateByDirection: PropTypes.func.isRequired
};
const SpatialFocusable = withFocusable()(Spatial);
class Link6Spatial extends React.PureComponent {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.setFocus();
}
render() {
return (<View>
<SpatialFocusable focusable={true} />
</View>);
}
}
Link6Spatial.propTypes = {
setFocus: PropTypes.func.isRequired,
hasFocusedChild: PropTypes.bool.isRequired
};
const Link6 = withFocusable({
trackChildren: true
})(Link6Spatial);
export default Link6;
from react-spatial-navigation.
Glad it worked :)
In case you want to do something when item is focused, you can use onBecameFocused
callback prop that is automatically exposed from the component that is wrapped into withFocusable
:
https://github.com/NoriginMedia/react-spatial-navigation#using-props-on-focusable-components
So for example in your TopNav
component you can listen to onBecameFocusable
on each of the menu items and trigger page navigation.
onFocus
callback on TouchableOpacity is more like a native browser "onfocus" event, which is not the same as the "internal" focus from this library.
from react-spatial-navigation.
Im getting the hang of this :) I removed the setFocus inside Link6.js and that now keeps focus on the menu item Link6 when going from the menu to the content and then back again.
from react-spatial-navigation.
Closing as resolved
from react-spatial-navigation.
Related Issues (20)
- CSS gap:0 breaks navigation HOT 1
- Focus lost when Item Removed HOT 4
- Sometimes navigation doesn't work after go back to previous screen HOT 1
- a vulnerability CVE-2020-15168 is introduced in @noriginmedia/react-spatial-navigation HOT 2
- Focuses the second item in the list, after navigating from the fixed menu HOT 2
- Performance/Crash issue when load multiple rails and hold arrow keys to navigate HOT 1
- [Question]Can I use this package in a ReactJS project? HOT 4
- Focusable items hidden are able to be accessed. HOT 19
- withFocusable usage with forwardRef HOT 9
- Understanding of roadmap for library HOT 5
- more than 100 UI elements in funcational component taking too long to navigate. HOT 1
- 2-Directional Grid-type Items HOT 1
- display none control HOT 2
- Focus gets lost in case of collision HOT 3
- Track Child not recognizing children
- RTL is not supported
- Loss of focus when a component is rendered HOT 2
- Error when Server Side Rendering HOT 1
- Focusable elements not recognised when layout is position absolute HOT 5
- How should I handle focus when opening/closing a pop-up? HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react-spatial-navigation.