diff --git a/CHANGELOG.md b/CHANGELOG.md index d7b2cec..b92e389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ 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.11.0] +### Changed +- `onBecameBlurred` and `onBecameFocused` are always invoked synchonously with focus change and not on componentDidUpdate +### Added +- `setFocus` and `navigateByDirection` accept an details object, this object is passed back on `onBecameBlurred` and `onBecameFocused` callbacks + ## [2.10.0] ### Changed - Changed behaviour of `onBecameFocused`, now it's invoked also in case of stealFocus diff --git a/README.md b/README.md index 9ef3ce3..c58f456 100644 --- a/README.md +++ b/README.md @@ -306,10 +306,10 @@ const onPress = (direction, {prop1, prop2}) => { Callback function that is called when the item becomes focused directly or when any of the children components become focused. For example when you have nested tree of 5 focusable components, this callback will be called on every level of down-tree focus change. Payload: -Component layout object is passed as a first param. All the component props passed back to this callback. Useful to avoid creating callback functions during render. `x` and `y` are relative coordinates to parent DOM (**not the Focusable parent**) element. `left` and `top` are absolute coordinates on the screen. +The first parameter is the component layout object. The second paramter is an object containing all the component props. The third parameter is a details object that was used when triggering the focus change, for example it contains the key event in case of arrow navigation. Useful to avoid creating callback functions during render. `x` and `y` are relative coordinates to parent DOM (**not the Focusable parent**) element. `left` and `top` are absolute coordinates on the screen. ```jsx -const onFocused = ({width, height, x, y, top, left, node}, {prop1, prop2}) => {...}; +const onFocused = ({width, height, x, y, top, left, node}, {prop1, prop2}, {event, other}) => {...}; ... {. Callback function that is called when the item loses focus or when all the children components lose focus. For example when you have nested tree of 5 focusable components, this callback will be called on every level of down-tree focus change. Payload: -Component layout object is passed as a first param. All the component props passed back to this callback. Useful to avoid creating callback functions during render. `x` and `y` are relative coordinates to parent DOM (**not the Focusable parent**) element. `left` and `top` are absolute coordinates on the screen. +The first parameter is the component layout object. The second paramter is an object containing all the component props. The third parameter is a details object that was used when triggering the focus change, for example it contains the key event in case of arrow navigation. Useful to avoid creating callback functions during render. `x` and `y` are relative coordinates to parent DOM (**not the Focusable parent**) element. `left` and `top` are absolute coordinates on the screen. ```jsx -const onBlur = ({width, height, x, y, top, left, node}, {prop1, prop2}) => {...}; +const onBlur = ({width, height, x, y, top, left, node}, {prop1, prop2}, {event, other}) => {...}; ... keyCode === code); + const direction = findKey(this.getKeyMap(), (code) => event.keyCode === code); - this.smartNavigate(direction); + this.smartNavigate(direction, null, {event}); } /** * This function navigates between siblings OR goes up by the Tree * Based on the Direction */ - smartNavigate(direction, fromParentFocusKey) { + smartNavigate(direction, fromParentFocusKey, details) { this.log('smartNavigate', 'direction', direction); this.log('smartNavigate', 'fromParentFocusKey', fromParentFocusKey); this.log('smartNavigate', 'this.focusKey', this.focusKey); @@ -609,13 +610,13 @@ class SpatialNavigation { ); if (nextComponent) { - this.setFocus(nextComponent.focusKey); + this.setFocus(nextComponent.focusKey, null, details); } else { const parentComponent = this.focusableComponents[parentFocusKey]; this.saveLastFocusedChildKey(parentComponent, focusKey); - this.smartNavigate(direction, parentFocusKey); + this.smartNavigate(direction, parentFocusKey, details); } } } @@ -803,24 +804,28 @@ class SpatialNavigation { return null; } - setCurrentFocusedKey(focusKey) { - if (this.isFocusableComponent(this.focusKey) && focusKey !== this.focusKey) { + setCurrentFocusedKey(newFocusKey, details) { + if (this.isFocusableComponent(this.focusKey) && newFocusKey !== this.focusKey) { const oldComponent = this.focusableComponents[this.focusKey]; const parentComponent = this.focusableComponents[oldComponent.parentFocusKey]; this.saveLastFocusedChildKey(parentComponent, this.focusKey); oldComponent.onUpdateFocus(false); + oldComponent.onBecameBlurredHandler(this.getNodeLayoutByFocusKey(this.focusKey), details); } - this.focusKey = focusKey; + this.focusKey = newFocusKey; - const newComponent = this.focusableComponents[this.focusKey]; + if (this.isFocusableComponent(this.focusKey)) { + const newComponent = this.focusableComponents[this.focusKey]; - newComponent && newComponent.onUpdateFocus(true); + newComponent.onUpdateFocus(true); + newComponent.onBecameFocusedHandler(this.getNodeLayoutByFocusKey(this.focusKey), details); + } } - updateParentsHasFocusedChild(focusKey) { + updateParentsHasFocusedChild(focusKey, details) { const parents = []; let currentComponent = this.focusableComponents[focusKey]; @@ -849,14 +854,14 @@ class SpatialNavigation { const parentComponent = this.focusableComponents[parentFocusKey]; parentComponent && parentComponent.trackChildren && parentComponent.onUpdateHasFocusedChild(false); - this.onIntermediateNodeBecameBlurred(parentFocusKey); + this.onIntermediateNodeBecameBlurred(parentFocusKey, details); }); forEach(parentsToAddFlag, (parentFocusKey) => { const parentComponent = this.focusableComponents[parentFocusKey]; parentComponent && parentComponent.trackChildren && parentComponent.onUpdateHasFocusedChild(true); - this.onIntermediateNodeBecameFocused(parentFocusKey); + this.onIntermediateNodeBecameFocused(parentFocusKey, details); }); this.parentsHavingFocusedChild = parents; @@ -904,14 +909,14 @@ class SpatialNavigation { return this.isFocusableComponent(focusKey) && this.focusableComponents[focusKey].focusable; } - onIntermediateNodeBecameFocused(focusKey) { + onIntermediateNodeBecameFocused(focusKey, details) { this.isParticipatingFocusableComponent(focusKey) && - this.focusableComponents[focusKey].onBecameFocusedHandler(this.getNodeLayoutByFocusKey(focusKey)); + this.focusableComponents[focusKey].onBecameFocusedHandler(this.getNodeLayoutByFocusKey(focusKey), details); } - onIntermediateNodeBecameBlurred(focusKey) { + onIntermediateNodeBecameBlurred(focusKey, details) { this.isParticipatingFocusableComponent(focusKey) && - this.focusableComponents[focusKey].onBecameBlurredHandler(this.getNodeLayoutByFocusKey(focusKey)); + this.focusableComponents[focusKey].onBecameBlurredHandler(this.getNodeLayoutByFocusKey(focusKey), details); } pause() { @@ -922,7 +927,7 @@ class SpatialNavigation { this.paused = false; } - setFocus(focusKey, overwriteFocusKey) { + setFocus(focusKey, overwriteFocusKey, details = {}) { if (!this.enabled) { return; } @@ -936,8 +941,8 @@ class SpatialNavigation { this.log('setFocus', 'newFocusKey', newFocusKey); - this.setCurrentFocusedKey(newFocusKey); - this.updateParentsHasFocusedChild(newFocusKey); + this.setCurrentFocusedKey(newFocusKey, details); + this.updateParentsHasFocusedChild(newFocusKey, details); this.updateParentsLastFocusedChild(lastFocusedKey); if (!this.nativeMode) { diff --git a/src/withFocusable.js b/src/withFocusable.js index fc41dad..2da8990 100644 --- a/src/withFocusable.js +++ b/src/withFocusable.js @@ -82,14 +82,14 @@ const withFocusable = ({ onBecameFocusedHandler: ({ onBecameFocused = noop, ...rest - }) => (layout) => { - onBecameFocused(layout, rest); + }) => (layout, details) => { + onBecameFocused(layout, rest, details); }, onBecameBlurredHandler: ({ onBecameBlurred = noop, ...rest - }) => (layout) => { - onBecameBlurred(layout, rest); + }) => (layout, details) => { + onBecameBlurred(layout, rest, details); }, pauseSpatialNavigation: () => SpatialNavigation.pause, resumeSpatialNavigation: () => SpatialNavigation.resume @@ -130,12 +130,9 @@ const withFocusable = ({ focusable }); }, - componentDidUpdate(prevProps) { + componentDidUpdate() { const { - focused, realFocusKey: focusKey, - onBecameFocusedHandler, - onBecameBlurredHandler, preferredChildFocusKey, focusable = true } = this.props; @@ -147,12 +144,6 @@ const withFocusable = ({ preferredChildFocusKey, focusable }); - - if (!prevProps.focused && focused) { - onBecameFocusedHandler(SpatialNavigation.getNodeLayoutByFocusKey(focusKey)); - } else if (prevProps.focused && !focused) { - onBecameBlurredHandler(SpatialNavigation.getNodeLayoutByFocusKey(focusKey)); - } }, componentWillUnmount() { const {realFocusKey: focusKey} = this.props;