diff --git a/CHANGELOG.md b/CHANGELOG.md index cc9544d..997a5e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ 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.9.0] +### Added +- Added smart focusing by direction (left, right, top, down), if you can't use buttons or focusing by key. Use `navigateByDirection` method for it. + + ## [2.8.4] ### Fixed - Fixed useless `logIndex` update. diff --git a/README.md b/README.md index 05ef5df..6159881 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,16 @@ In Native mode this method is ignored (`noop`). setFocus(); // set focus to self setFocus('SOME_COMPONENT'); // set focus to another component if you know its focus key ``` +### `navigateByDirection`: function +Move the focus by direction, if you can't use buttons or focusing by key. + +```jsx +navigateByDirection('left'); // The focus is moved to left +navigateByDirection('right'); // The focus is moved to right +navigateByDirection('up'); // The focus is moved to up +navigateByDirection('down'); // The focus is moved to down +``` + ### `stealFocus`: function This method works exactly like `setFocus`, but it always sets focus to current component no matter which params you pass in. diff --git a/package-lock.json b/package-lock.json index d082c75..566335a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@noriginmedia/react-spatial-navigation", - "version": "2.8.4", + "version": "2.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d290fdc..5e5d557 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@noriginmedia/react-spatial-navigation", - "version": "2.8.4", + "version": "2.9.0", "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 1d3a394..7728392 100644 --- a/src/App.js +++ b/src/App.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import shuffle from 'lodash/shuffle'; +import throttle from 'lodash/throttle'; import {View, Text, StyleSheet, TouchableOpacity, ScrollView} from 'react-native'; import withFocusable from './withFocusable'; import SpatialNavigation from './spatialNavigation'; @@ -393,6 +394,42 @@ Categories.propTypes = { 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 ( ( - + ); export default App; diff --git a/src/spatialNavigation.js b/src/spatialNavigation.js index 8ab2e9c..e05fce8 100644 --- a/src/spatialNavigation.js +++ b/src/spatialNavigation.js @@ -300,6 +300,7 @@ class SpatialNavigation { this.pause = this.pause.bind(this); this.resume = this.resume.bind(this); this.setFocus = this.setFocus.bind(this); + this.navigateByDirection = this.navigateByDirection.bind(this); this.init = this.init.bind(this); this.setKeyMap = this.setKeyMap.bind(this); @@ -384,23 +385,22 @@ class SpatialNavigation { return; } - if (eventType === KEY_ENTER && this.focusKey) { - event.preventDefault(); - event.stopPropagation(); + event.preventDefault(); + event.stopPropagation(); + if (eventType === KEY_ENTER && this.focusKey) { this.onEnterPress(); - } else { - event.preventDefault(); - event.stopPropagation(); - const preventDefaultNavigation = this.onArrowPress(eventType) === false; + return; + } + + const preventDefaultNavigation = this.onArrowPress(eventType) === false; - if (preventDefaultNavigation) { - this.log('keyDownEventListener', 'default navigation prevented'); - this.visualDebugger && this.visualDebugger.clear(); - } else { - this.onKeyEvent(event.keyCode); - } + if (preventDefaultNavigation) { + this.log('keyDownEventListener', 'default navigation prevented'); + this.visualDebugger && this.visualDebugger.clear(); + } else { + this.onKeyEvent(event.keyCode); } }; @@ -467,6 +467,29 @@ class SpatialNavigation { return component && component.onArrowPressHandler && component.onArrowPressHandler(...args); } + /** + * Move focus by direction, if you can't use buttons or focusing by key. + * + * @param {string} direction + * + * @example + * navigateByDirection('right') // The focus is moved to right + */ + navigateByDirection(direction) { + if (this.paused === true) { + return; + } + + const validDirections = [DIRECTION_DOWN, DIRECTION_UP, DIRECTION_LEFT, DIRECTION_RIGHT]; + + if (validDirections.includes(direction)) { + this.log('navigateByDirection', 'direction', direction); + this.smartNavigate(direction); + } else { + this.log('navigateByDirection', `Invalid direction. You passed: \`${direction}\`, but you can use only these: `, validDirections); + } + } + onKeyEvent(keyCode) { this.visualDebugger && this.visualDebugger.clear(); diff --git a/src/withFocusable.js b/src/withFocusable.js index e210e3b..a3d58b4 100644 --- a/src/withFocusable.js +++ b/src/withFocusable.js @@ -39,6 +39,8 @@ const withFocusable = ({ */ setFocus: SpatialNavigation.isNativeMode() ? noop : SpatialNavigation.setFocus.bind(null, realFocusKey), + navigateByDirection: SpatialNavigation.navigateByDirection, + /** * In Native mode this is the only way to mark component as focused. * This method always steals focus onto current component no matter which arguments are passed in.