From 9e00878361a6bb40f5b716e58981b3b436803f5c Mon Sep 17 00:00:00 2001 From: dev-seb Date: Tue, 17 Mar 2020 14:51:00 +0100 Subject: [PATCH 1/6] Handle TV devices Add support for : - TVEventHandler events - Platform.isTV - hasTVPreferredFocus property - nextFocusUp, nextFocusRight, nextFocusDown, nextFocusLeft properties - back event from remote control --- .../src/exports/BackHandler/index.js | 7 +- .../src/exports/Platform/index.js | 3 + .../src/exports/TVEventHandler/index.js | 103 +++++++++++++++++- .../src/exports/TextInput/index.js | 7 +- .../src/exports/Touchable/index.js | 50 ++++++++- .../src/exports/TouchableHighlight/index.js | 54 ++++++++- .../src/exports/TouchableOpacity/index.js | 66 +++++++++-- 7 files changed, 271 insertions(+), 19 deletions(-) diff --git a/packages/react-native-web/src/exports/BackHandler/index.js b/packages/react-native-web/src/exports/BackHandler/index.js index b55735295..aaef1f964 100644 --- a/packages/react-native-web/src/exports/BackHandler/index.js +++ b/packages/react-native-web/src/exports/BackHandler/index.js @@ -12,9 +12,12 @@ function emptyFunction() {} const BackHandler = { exitApp: emptyFunction, - addEventListener() { + addEventListener(event, callback) { + document.addEventListener(event, callback); return { - remove: emptyFunction + remove: () => { + document.removeEventListener(event, callback); + } }; }, removeEventListener: emptyFunction diff --git a/packages/react-native-web/src/exports/Platform/index.js b/packages/react-native-web/src/exports/Platform/index.js index d7c8f766f..ba8a581ad 100644 --- a/packages/react-native-web/src/exports/Platform/index.js +++ b/packages/react-native-web/src/exports/Platform/index.js @@ -16,6 +16,9 @@ const Platform = { return true; } return false; + }, + get isTV(): boolean { + return process.env.REACT_APP_IS_TV === "true"; } }; diff --git a/packages/react-native-web/src/exports/TVEventHandler/index.js b/packages/react-native-web/src/exports/TVEventHandler/index.js index ff8b4c563..8fbcdd5bc 100644 --- a/packages/react-native-web/src/exports/TVEventHandler/index.js +++ b/packages/react-native-web/src/exports/TVEventHandler/index.js @@ -1 +1,102 @@ -export default {}; +class TVEventHandler { + + constructor() { + this.component = null; + this.callback = null; + } + + enable(component, callback) { + this.component = component; + this.callback = callback; + document.addEventListener( + 'onHWKeyEvent', + this.onHWKeyEvent.bind(this) + ); + } + + disable() { + document.removeEventListener( + 'onHWKeyEvent', + this.onHWKeyEvent + ); + } + + onHWKeyEvent(event) { + if (this.callback) { + if (event && event.detail) { + const tvEvent = event.detail.tvEvent; + if(tvEvent) { + this.callback(this.component, tvEvent); + } + } + } + } + + static dispatchEvent(tvEvent) { + // Dispatch tvEvent through onHWKeyEvent + const hwKeyEvent = new CustomEvent("onHWKeyEvent", { + detail: {tvEvent: tvEvent}, + }); + document.dispatchEvent(hwKeyEvent); + } + + static getTVEvent(event) { + // create tv event + let tvEvent = { + eventKeyAction: -1, + eventType: '', + tag: '' + }; + // Key Event + if (event.type === 'keydown' || event.type === 'keyup') { + // get event type + switch (event.key) { + case 'Enter': + tvEvent.eventType = 'select'; + break; + case 'ArrowUp': + tvEvent.eventType = 'up'; + break; + case 'ArrowRight': + tvEvent.eventType = 'right'; + break; + case 'ArrowDown': + tvEvent.eventType = 'down'; + break; + case 'ArrowLeft': + tvEvent.eventType = 'left'; + break; + case 'MediaPlayPause': + tvEvent.eventType = 'playPause'; + break; + case 'MediaRewind': + tvEvent.eventType = 'rewind'; + break; + case 'MediaFastForward': + tvEvent.eventType = 'fastForward'; + break; + case 'Menu': + tvEvent.eventType = 'menu'; + break; + } + if (event.type === 'keydown') { + tvEvent.eventKeyAction = 0; + } + else if (event.type === 'keyup') { + tvEvent.eventKeyAction = 1; + } + } + // Focus / Blur event + else if (event.type === 'focus' || event.type === 'blur') { + tvEvent.eventType = event.type; + } + // Get tag from id attribute + if (event.target && event.target.id) { + tvEvent.tag = event.target.id; + } + return tvEvent; + } + +} + +export default TVEventHandler; diff --git a/packages/react-native-web/src/exports/TextInput/index.js b/packages/react-native-web/src/exports/TextInput/index.js index 78d2cec04..75a2a300f 100644 --- a/packages/react-native-web/src/exports/TextInput/index.js +++ b/packages/react-native-web/src/exports/TextInput/index.js @@ -20,6 +20,7 @@ import findNodeHandle from '../findNodeHandle'; import React from 'react'; import StyleSheet from '../StyleSheet'; import TextInputState from '../../modules/TextInputState'; +import Platform from '../Platform'; const isAndroid = canUseDOM && /Android/i.test(navigator && navigator.userAgent); const emptyObject = {}; @@ -241,8 +242,10 @@ class TextInput extends React.Component { }; _handleKeyDown = e => { - // Prevent key events bubbling (see #612) - e.stopPropagation(); + if(!Platform.isTV) { + // Prevent key events bubbling (see #612) + e.stopPropagation(); + } // Backspace, Escape, Tab, Cmd+Enter, and Arrow keys only fire 'keydown' // DOM events diff --git a/packages/react-native-web/src/exports/Touchable/index.js b/packages/react-native-web/src/exports/Touchable/index.js index 1b9954420..06379d050 100644 --- a/packages/react-native-web/src/exports/Touchable/index.js +++ b/packages/react-native-web/src/exports/Touchable/index.js @@ -18,6 +18,8 @@ import Position from './Position'; import React from 'react'; import UIManager from '../UIManager'; import View from '../View'; +import Platform from "../Platform"; +import TVEventHandler from "../TVEventHandler"; type Event = Object; type PressEvent = Object; @@ -380,6 +382,7 @@ const TouchableMixin = { }; this._touchableNode.addEventListener('blur', this._touchableBlurListener); } + }, /** @@ -403,7 +406,8 @@ const TouchableMixin = { */ touchableGetInitialState: function() { return { - touchable: { touchState: undefined, responderID: null } + touchable: { touchState: undefined, responderID: null }, + focused: false }; }, @@ -566,6 +570,7 @@ const TouchableMixin = { * using `Touchable.Mixin.withoutDefaultFocusAndBlur`. */ touchableHandleFocus: function(e: Event) { + this.state.focused = true; this.props.onFocus && this.props.onFocus(e); }, @@ -578,6 +583,7 @@ const TouchableMixin = { * `Touchable.Mixin.withoutDefaultFocusAndBlur`. */ touchableHandleBlur: function(e: Event) { + this.state.focused = false; this.props.onBlur && this.props.onBlur(e); }, @@ -873,6 +879,48 @@ const TouchableMixin = { // delays and longPress) touchableHandleKeyEvent: function(e: Event) { const { type, key } = e; + if(Platform.isTV) { + // Get tvEvent + const tvEvent = TVEventHandler.getTVEvent(e); + // Dispatch 'select' tvEvent to component + if(tvEvent.eventType === 'select') { + this.touchableHandlePress(tvEvent); + } + // Dispatch tvEvent to all listeners + TVEventHandler.dispatchEvent(tvEvent); + // Handle next focus + if(this._touchableNode) { + let nextFocusID = ''; + // Check nextFocus* properties + if(this._touchableNode.hasAttribute("nextFocusUp") && key === 'ArrowUp') { + nextFocusID = this._touchableNode.getAttribute("nextFocusUp"); + } + else if(this._touchableNode.hasAttribute("nextFocusRight") && key === 'ArrowRight') { + nextFocusID = this._touchableNode.getAttribute("nextFocusRight"); + } + else if(this._touchableNode.hasAttribute("nextFocusDown") && key === 'ArrowDown') { + nextFocusID = this._touchableNode.getAttribute("nextFocusDown"); + } + else if(this._touchableNode.hasAttribute("nextFocusLeft") && key === 'ArrowLeft') { + nextFocusID = this._touchableNode.getAttribute("nextFocusLeft"); + } + if(nextFocusID && nextFocusID !== '') { + // Get DOM element + const element = document.getElementById(nextFocusID); + if(element && element.tabIndex >= 0) { + // Force focus + element.focus(); + // Stop event propagation + e.stopPropagation(); + } + } + } + // Trigger Hardware Back Press for Back/Escape event keys + if(type === 'keydown' && (key === 'Back' || key === 'Escape')) { + const hwKeyEvent = new CustomEvent("hardwareBackPress", {}); + document.dispatchEvent(hwKeyEvent); + } + } if (key === 'Enter' || key === ' ') { if (type === 'keydown') { if (!this._isTouchableKeyboardActive) { diff --git a/packages/react-native-web/src/exports/TouchableHighlight/index.js b/packages/react-native-web/src/exports/TouchableHighlight/index.js index 18c7c0f96..c739ca639 100644 --- a/packages/react-native-web/src/exports/TouchableHighlight/index.js +++ b/packages/react-native-web/src/exports/TouchableHighlight/index.js @@ -14,10 +14,14 @@ import type { Props as TouchableWithoutFeedbackProps } from '../TouchableWithout import applyNativeMethods from '../../modules/applyNativeMethods'; import createReactClass from 'create-react-class'; import ensurePositiveDelayProps from '../Touchable/ensurePositiveDelayProps'; +import findNodeHandle from '../../exports/findNodeHandle'; import * as React from 'react'; import StyleSheet from '../StyleSheet'; import Touchable from '../Touchable'; import View from '../View'; +import UIManager from '../UIManager'; +import Platform from '../Platform'; +import TVEventHandler from "../TVEventHandler"; type Event = Object; type PressEvent = Object; @@ -169,6 +173,10 @@ const TouchableHighlight = ((createReactClass({ componentDidMount: function() { this._isMounted = true; ensurePositiveDelayProps(this.props); + // Focus component + if(Platform.isTV && this.props.hasTVPreferredFocus === true) { + UIManager.focus(findNodeHandle(this)); + } }, componentWillUnmount: function() { @@ -187,6 +195,15 @@ const TouchableHighlight = ((createReactClass({ }, */ + /** + * Set focus to current element + */ + setTVPreferredFocus(hasTVPreferredFocus) { + if(Platform.isTV && hasTVPreferredFocus === true) { + UIManager.focus(findNodeHandle(this)); + } + }, + /** * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are * defined on your component. @@ -206,11 +223,37 @@ const TouchableHighlight = ((createReactClass({ }, touchableHandleFocus: function(e: Event) { - this.props.onFocus && this.props.onFocus(e); + if(Platform.isTV) { + this.state.focused = true; + // Keep underlay visible + this._showUnderlay(); + // Get tvEvent + const tvEvent = TVEventHandler.getTVEvent(e); + // Dispatch tvEvent to component + this.props.onFocus && this.props.onFocus(tvEvent); + // Dispatch tvEvent to all listeners + TVEventHandler.dispatchEvent(tvEvent); + } + else { + this.props.onFocus && this.props.onFocus(e); + } }, touchableHandleBlur: function(e: Event) { - this.props.onBlur && this.props.onBlur(e); + if(Platform.isTV) { + this.state.focused = false; + // Hide underlay + this._hideUnderlay(); + // Get tvEvent + const tvEvent = TVEventHandler.getTVEvent(e); + // Dispatch tvEvent to component + this.props.onBlur && this.props.onBlur(tvEvent); + // Dispatch tvEvent to all listeners + TVEventHandler.dispatchEvent(tvEvent); + } + else { + this.props.onBlur && this.props.onBlur(e); + } }, touchableHandlePress: function(e: PressEvent) { @@ -262,6 +305,9 @@ const TouchableHighlight = ((createReactClass({ _hideUnderlay: function() { clearTimeout(this._hideTimeout); this._hideTimeout = null; + if(Platform.isTV && this.state.focused) { + return; + } if (this.props.testOnly_pressed) { return; } @@ -298,7 +344,9 @@ const TouchableHighlight = ((createReactClass({ onKeyDown={this.touchableHandleKeyEvent} //isTVSelectable={true} //tvParallaxProperties={this.props.tvParallaxProperties} - //hasTVPreferredFocus={this.props.hasTVPreferredFocus} + hasTVPreferredFocus={this.props.hasTVPreferredFocus} + onFocus={this.touchableHandleFocus} + onBlur={this.touchableHandleBlur} //nextFocusDown={this.props.nextFocusDown} //nextFocusForward={this.props.nextFocusForward} //nextFocusLeft={this.props.nextFocusLeft} diff --git a/packages/react-native-web/src/exports/TouchableOpacity/index.js b/packages/react-native-web/src/exports/TouchableOpacity/index.js index 3222a3032..0cd4cc59b 100644 --- a/packages/react-native-web/src/exports/TouchableOpacity/index.js +++ b/packages/react-native-web/src/exports/TouchableOpacity/index.js @@ -15,10 +15,14 @@ import type { Props as TouchableWithoutFeedbackProps } from '../TouchableWithout import applyNativeMethods from '../../modules/applyNativeMethods'; import createReactClass from 'create-react-class'; import ensurePositiveDelayProps from '../Touchable/ensurePositiveDelayProps'; +import findNodeHandle from "../findNodeHandle"; import * as React from 'react'; import StyleSheet from '../StyleSheet'; import Touchable from '../Touchable'; import View from '../View'; +import UIManager from "../UIManager"; +import Platform from "../Platform"; +import TVEventHandler from "../TVEventHandler"; const flattenStyle = StyleSheet.flatten; @@ -166,6 +170,15 @@ const TouchableOpacity = ((createReactClass({ }); }, + /** + * Set focus to current element + */ + setTVPreferredFocus(hasTVPreferredFocus) { + if(Platform.isTV && hasTVPreferredFocus === true) { + UIManager.focus(findNodeHandle(this)); + } + }, + /** * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are * defined on your component. @@ -174,28 +187,56 @@ const TouchableOpacity = ((createReactClass({ if (e.dispatchConfig.registrationName === 'onResponderGrant') { this._opacityActive(0); } else { - this._opacityActive(150); + this._opacityInactive(250); + } + if(Platform.isTV) { + const tvEvent = TVEventHandler.getTVEvent(e); + this.props.onPress && this.props.onPress(tvEvent); } this.props.onPressIn && this.props.onPressIn(e); }, touchableHandleActivePressOut: function(e: PressEvent) { this._opacityInactive(250); + if(Platform.isTV) { + const tvEvent = TVEventHandler.getTVEvent(e); + this.props.onPress && this.props.onPress(tvEvent); + } this.props.onPressOut && this.props.onPressOut(e); }, touchableHandleFocus: function(e: Event) { - //if (Platform.isTV) { - // this._opacityActive(150); - //} - this.props.onFocus && this.props.onFocus(e); + if(Platform.isTV) { + this.state.focused = true; + // Keep underlay visible + this._opacityActive(0); + // Get tvEvent + const tvEvent = TVEventHandler.getTVEvent(e); + // Dispatch tvEvent to component + this.props.onFocus && this.props.onFocus(tvEvent); + // Dispatch tvEvent to all listeners + TVEventHandler.dispatchEvent(tvEvent); + } + else { + this.props.onFocus && this.props.onFocus(e); + } }, touchableHandleBlur: function(e: Event) { - //if (Platform.isTV) { - // this._opacityInactive(250); - //} - this.props.onBlur && this.props.onBlur(e); + if(Platform.isTV) { + this.state.focused = false; + // Hide underlay + this._opacityInactive(250); + // Get tvEvent + const tvEvent = TVEventHandler.getTVEvent(e); + // Dispatch tvEvent to component + this.props.onBlur && this.props.onBlur(tvEvent); + // Dispatch tvEvent to all listeners + TVEventHandler.dispatchEvent(tvEvent); + } + else { + this.props.onBlur && this.props.onBlur(e); + } }, touchableHandlePress: function(e: PressEvent) { @@ -231,6 +272,9 @@ const TouchableOpacity = ((createReactClass({ }, _opacityInactive: function(duration: number) { + if(Platform.isTV && this.state.focused) { + return; + } this.setOpacityTo(this._getChildStyleOpacityWithDefault(), duration); }, @@ -260,7 +304,9 @@ const TouchableOpacity = ((createReactClass({ //nextFocusLeft={this.props.nextFocusLeft} //nextFocusRight={this.props.nextFocusRight} //nextFocusUp={this.props.nextFocusUp} - //hasTVPreferredFocus={this.props.hasTVPreferredFocus} + hasTVPreferredFocus={this.props.hasTVPreferredFocus} + onFocus={this.touchableHandleFocus} + onBlur={this.touchableHandleBlur} //tvParallaxProperties={this.props.tvParallaxProperties} onResponderMove={this.touchableHandleResponderMove} //clickable={ From 97b51f9edbede7857930116f12e20d265a056168 Mon Sep 17 00:00:00 2001 From: dev-seb Date: Wed, 18 Mar 2020 13:02:27 +0100 Subject: [PATCH 2/6] Some fixes after running yarn test --- .gitignore | 1 + .../src/exports/BackHandler/index.js | 9 +++- .../src/exports/Platform/index.js | 2 +- .../src/exports/TVEventHandler/index.js | 26 ++++------ .../src/exports/Touchable/index.js | 47 +++++++++---------- .../src/exports/TouchableHighlight/index.js | 28 +++++------ .../src/exports/TouchableOpacity/index.js | 42 ++++++++--------- 7 files changed, 75 insertions(+), 80 deletions(-) diff --git a/.gitignore b/.gitignore index 03cbfc4e3..da0faa9ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ coverage node_modules dist +.idea diff --git a/packages/react-native-web/src/exports/BackHandler/index.js b/packages/react-native-web/src/exports/BackHandler/index.js index aaef1f964..4731fd08c 100644 --- a/packages/react-native-web/src/exports/BackHandler/index.js +++ b/packages/react-native-web/src/exports/BackHandler/index.js @@ -12,7 +12,14 @@ function emptyFunction() {} const BackHandler = { exitApp: emptyFunction, - addEventListener(event, callback) { + /** + * Listen to "hardwareBackPress" event + * + * @param event + * @param callback + * @returns {{remove: remove}} + */ + addEventListener(event: string, callback: Function) { document.addEventListener(event, callback); return { remove: () => { diff --git a/packages/react-native-web/src/exports/Platform/index.js b/packages/react-native-web/src/exports/Platform/index.js index ba8a581ad..95ef638cd 100644 --- a/packages/react-native-web/src/exports/Platform/index.js +++ b/packages/react-native-web/src/exports/Platform/index.js @@ -18,7 +18,7 @@ const Platform = { return false; }, get isTV(): boolean { - return process.env.REACT_APP_IS_TV === "true"; + return process.env.REACT_APP_IS_TV === 'true'; } }; diff --git a/packages/react-native-web/src/exports/TVEventHandler/index.js b/packages/react-native-web/src/exports/TVEventHandler/index.js index 8fbcdd5bc..d67c82983 100644 --- a/packages/react-native-web/src/exports/TVEventHandler/index.js +++ b/packages/react-native-web/src/exports/TVEventHandler/index.js @@ -1,5 +1,4 @@ class TVEventHandler { - constructor() { this.component = null; this.callback = null; @@ -8,24 +7,18 @@ class TVEventHandler { enable(component, callback) { this.component = component; this.callback = callback; - document.addEventListener( - 'onHWKeyEvent', - this.onHWKeyEvent.bind(this) - ); + document.addEventListener('onHWKeyEvent', this.onHWKeyEvent.bind(this)); } disable() { - document.removeEventListener( - 'onHWKeyEvent', - this.onHWKeyEvent - ); + document.removeEventListener('onHWKeyEvent', this.onHWKeyEvent); } onHWKeyEvent(event) { if (this.callback) { if (event && event.detail) { const tvEvent = event.detail.tvEvent; - if(tvEvent) { + if (tvEvent) { this.callback(this.component, tvEvent); } } @@ -34,15 +27,16 @@ class TVEventHandler { static dispatchEvent(tvEvent) { // Dispatch tvEvent through onHWKeyEvent - const hwKeyEvent = new CustomEvent("onHWKeyEvent", { - detail: {tvEvent: tvEvent}, + // eslint-disable-next-line no-undef + const hwKeyEvent = new CustomEvent('onHWKeyEvent', { + detail: { tvEvent: tvEvent } }); document.dispatchEvent(hwKeyEvent); } static getTVEvent(event) { // create tv event - let tvEvent = { + const tvEvent = { eventKeyAction: -1, eventType: '', tag: '' @@ -78,11 +72,12 @@ class TVEventHandler { case 'Menu': tvEvent.eventType = 'menu'; break; + default: + tvEvent.eventType = ''; } if (event.type === 'keydown') { tvEvent.eventKeyAction = 0; - } - else if (event.type === 'keyup') { + } else if (event.type === 'keyup') { tvEvent.eventKeyAction = 1; } } @@ -96,7 +91,6 @@ class TVEventHandler { } return tvEvent; } - } export default TVEventHandler; diff --git a/packages/react-native-web/src/exports/Touchable/index.js b/packages/react-native-web/src/exports/Touchable/index.js index 06379d050..9528586a7 100644 --- a/packages/react-native-web/src/exports/Touchable/index.js +++ b/packages/react-native-web/src/exports/Touchable/index.js @@ -18,8 +18,8 @@ import Position from './Position'; import React from 'react'; import UIManager from '../UIManager'; import View from '../View'; -import Platform from "../Platform"; -import TVEventHandler from "../TVEventHandler"; +import Platform from '../Platform'; +import TVEventHandler from '../TVEventHandler'; type Event = Object; type PressEvent = Object; @@ -382,7 +382,6 @@ const TouchableMixin = { }; this._touchableNode.addEventListener('blur', this._touchableBlurListener); } - }, /** @@ -405,9 +404,9 @@ const TouchableMixin = { * `this.state.touchable`. */ touchableGetInitialState: function() { + this._isFocused = false; return { - touchable: { touchState: undefined, responderID: null }, - focused: false + touchable: { touchState: undefined, responderID: null } }; }, @@ -570,7 +569,7 @@ const TouchableMixin = { * using `Touchable.Mixin.withoutDefaultFocusAndBlur`. */ touchableHandleFocus: function(e: Event) { - this.state.focused = true; + this._isFocused = true; this.props.onFocus && this.props.onFocus(e); }, @@ -583,7 +582,7 @@ const TouchableMixin = { * `Touchable.Mixin.withoutDefaultFocusAndBlur`. */ touchableHandleBlur: function(e: Event) { - this.state.focused = false; + this._isFocused = false; this.props.onBlur && this.props.onBlur(e); }, @@ -879,35 +878,32 @@ const TouchableMixin = { // delays and longPress) touchableHandleKeyEvent: function(e: Event) { const { type, key } = e; - if(Platform.isTV) { + if (Platform.isTV) { // Get tvEvent const tvEvent = TVEventHandler.getTVEvent(e); // Dispatch 'select' tvEvent to component - if(tvEvent.eventType === 'select') { + if (tvEvent.eventType === 'select') { this.touchableHandlePress(tvEvent); } // Dispatch tvEvent to all listeners TVEventHandler.dispatchEvent(tvEvent); // Handle next focus - if(this._touchableNode) { + if (this._touchableNode) { let nextFocusID = ''; // Check nextFocus* properties - if(this._touchableNode.hasAttribute("nextFocusUp") && key === 'ArrowUp') { - nextFocusID = this._touchableNode.getAttribute("nextFocusUp"); - } - else if(this._touchableNode.hasAttribute("nextFocusRight") && key === 'ArrowRight') { - nextFocusID = this._touchableNode.getAttribute("nextFocusRight"); - } - else if(this._touchableNode.hasAttribute("nextFocusDown") && key === 'ArrowDown') { - nextFocusID = this._touchableNode.getAttribute("nextFocusDown"); - } - else if(this._touchableNode.hasAttribute("nextFocusLeft") && key === 'ArrowLeft') { - nextFocusID = this._touchableNode.getAttribute("nextFocusLeft"); + if (this._touchableNode.hasAttribute('nextFocusUp') && key === 'ArrowUp') { + nextFocusID = this._touchableNode.getAttribute('nextFocusUp'); + } else if (this._touchableNode.hasAttribute('nextFocusRight') && key === 'ArrowRight') { + nextFocusID = this._touchableNode.getAttribute('nextFocusRight'); + } else if (this._touchableNode.hasAttribute('nextFocusDown') && key === 'ArrowDown') { + nextFocusID = this._touchableNode.getAttribute('nextFocusDown'); + } else if (this._touchableNode.hasAttribute('nextFocusLeft') && key === 'ArrowLeft') { + nextFocusID = this._touchableNode.getAttribute('nextFocusLeft'); } - if(nextFocusID && nextFocusID !== '') { + if (nextFocusID && nextFocusID !== '') { // Get DOM element const element = document.getElementById(nextFocusID); - if(element && element.tabIndex >= 0) { + if (element && element.tabIndex >= 0) { // Force focus element.focus(); // Stop event propagation @@ -916,8 +912,9 @@ const TouchableMixin = { } } // Trigger Hardware Back Press for Back/Escape event keys - if(type === 'keydown' && (key === 'Back' || key === 'Escape')) { - const hwKeyEvent = new CustomEvent("hardwareBackPress", {}); + if (type === 'keydown' && (key === 'Back' || key === 'Escape')) { + // eslint-disable-next-line no-undef + const hwKeyEvent = new CustomEvent('hardwareBackPress', {}); document.dispatchEvent(hwKeyEvent); } } diff --git a/packages/react-native-web/src/exports/TouchableHighlight/index.js b/packages/react-native-web/src/exports/TouchableHighlight/index.js index c739ca639..a88218447 100644 --- a/packages/react-native-web/src/exports/TouchableHighlight/index.js +++ b/packages/react-native-web/src/exports/TouchableHighlight/index.js @@ -21,7 +21,7 @@ import Touchable from '../Touchable'; import View from '../View'; import UIManager from '../UIManager'; import Platform from '../Platform'; -import TVEventHandler from "../TVEventHandler"; +import TVEventHandler from '../TVEventHandler'; type Event = Object; type PressEvent = Object; @@ -174,7 +174,7 @@ const TouchableHighlight = ((createReactClass({ this._isMounted = true; ensurePositiveDelayProps(this.props); // Focus component - if(Platform.isTV && this.props.hasTVPreferredFocus === true) { + if (Platform.isTV && this.props.hasTVPreferredFocus === true) { UIManager.focus(findNodeHandle(this)); } }, @@ -199,7 +199,7 @@ const TouchableHighlight = ((createReactClass({ * Set focus to current element */ setTVPreferredFocus(hasTVPreferredFocus) { - if(Platform.isTV && hasTVPreferredFocus === true) { + if (Platform.isTV && hasTVPreferredFocus === true) { UIManager.focus(findNodeHandle(this)); } }, @@ -223,8 +223,8 @@ const TouchableHighlight = ((createReactClass({ }, touchableHandleFocus: function(e: Event) { - if(Platform.isTV) { - this.state.focused = true; + if (Platform.isTV) { + this._isFocused = true; // Keep underlay visible this._showUnderlay(); // Get tvEvent @@ -233,15 +233,14 @@ const TouchableHighlight = ((createReactClass({ this.props.onFocus && this.props.onFocus(tvEvent); // Dispatch tvEvent to all listeners TVEventHandler.dispatchEvent(tvEvent); - } - else { + } else { this.props.onFocus && this.props.onFocus(e); } }, touchableHandleBlur: function(e: Event) { - if(Platform.isTV) { - this.state.focused = false; + if (Platform.isTV) { + this._isFocused = false; // Hide underlay this._hideUnderlay(); // Get tvEvent @@ -250,8 +249,7 @@ const TouchableHighlight = ((createReactClass({ this.props.onBlur && this.props.onBlur(tvEvent); // Dispatch tvEvent to all listeners TVEventHandler.dispatchEvent(tvEvent); - } - else { + } else { this.props.onBlur && this.props.onBlur(e); } }, @@ -305,7 +303,7 @@ const TouchableHighlight = ((createReactClass({ _hideUnderlay: function() { clearTimeout(this._hideTimeout); this._hideTimeout = null; - if(Platform.isTV && this.state.focused) { + if (Platform.isTV && this._isFocused) { return; } if (this.props.testOnly_pressed) { @@ -339,14 +337,14 @@ const TouchableHighlight = ((createReactClass({ accessibilityRole={this.props.accessibilityRole} accessibilityState={this.props.accessibilityState} accessible={this.props.accessible !== false} + hasTVPreferredFocus={this.props.hasTVPreferredFocus} hitSlop={this.props.hitSlop} nativeID={this.props.nativeID} - onKeyDown={this.touchableHandleKeyEvent} //isTVSelectable={true} //tvParallaxProperties={this.props.tvParallaxProperties} - hasTVPreferredFocus={this.props.hasTVPreferredFocus} - onFocus={this.touchableHandleFocus} onBlur={this.touchableHandleBlur} + onFocus={this.touchableHandleFocus} + onKeyDown={this.touchableHandleKeyEvent} //nextFocusDown={this.props.nextFocusDown} //nextFocusForward={this.props.nextFocusForward} //nextFocusLeft={this.props.nextFocusLeft} diff --git a/packages/react-native-web/src/exports/TouchableOpacity/index.js b/packages/react-native-web/src/exports/TouchableOpacity/index.js index 0cd4cc59b..9eba5049b 100644 --- a/packages/react-native-web/src/exports/TouchableOpacity/index.js +++ b/packages/react-native-web/src/exports/TouchableOpacity/index.js @@ -15,14 +15,14 @@ import type { Props as TouchableWithoutFeedbackProps } from '../TouchableWithout import applyNativeMethods from '../../modules/applyNativeMethods'; import createReactClass from 'create-react-class'; import ensurePositiveDelayProps from '../Touchable/ensurePositiveDelayProps'; -import findNodeHandle from "../findNodeHandle"; +import findNodeHandle from '../findNodeHandle'; import * as React from 'react'; import StyleSheet from '../StyleSheet'; import Touchable from '../Touchable'; import View from '../View'; -import UIManager from "../UIManager"; -import Platform from "../Platform"; -import TVEventHandler from "../TVEventHandler"; +import UIManager from '../UIManager'; +import Platform from '../Platform'; +import TVEventHandler from '../TVEventHandler'; const flattenStyle = StyleSheet.flatten; @@ -174,7 +174,7 @@ const TouchableOpacity = ((createReactClass({ * Set focus to current element */ setTVPreferredFocus(hasTVPreferredFocus) { - if(Platform.isTV && hasTVPreferredFocus === true) { + if (Platform.isTV && hasTVPreferredFocus === true) { UIManager.focus(findNodeHandle(this)); } }, @@ -189,7 +189,7 @@ const TouchableOpacity = ((createReactClass({ } else { this._opacityInactive(250); } - if(Platform.isTV) { + if (Platform.isTV) { const tvEvent = TVEventHandler.getTVEvent(e); this.props.onPress && this.props.onPress(tvEvent); } @@ -198,7 +198,7 @@ const TouchableOpacity = ((createReactClass({ touchableHandleActivePressOut: function(e: PressEvent) { this._opacityInactive(250); - if(Platform.isTV) { + if (Platform.isTV) { const tvEvent = TVEventHandler.getTVEvent(e); this.props.onPress && this.props.onPress(tvEvent); } @@ -206,8 +206,8 @@ const TouchableOpacity = ((createReactClass({ }, touchableHandleFocus: function(e: Event) { - if(Platform.isTV) { - this.state.focused = true; + if (Platform.isTV) { + this._isFocused = true; // Keep underlay visible this._opacityActive(0); // Get tvEvent @@ -216,15 +216,14 @@ const TouchableOpacity = ((createReactClass({ this.props.onFocus && this.props.onFocus(tvEvent); // Dispatch tvEvent to all listeners TVEventHandler.dispatchEvent(tvEvent); - } - else { + } else { this.props.onFocus && this.props.onFocus(e); } }, touchableHandleBlur: function(e: Event) { - if(Platform.isTV) { - this.state.focused = false; + if (Platform.isTV) { + this._isFocused = false; // Hide underlay this._opacityInactive(250); // Get tvEvent @@ -233,8 +232,7 @@ const TouchableOpacity = ((createReactClass({ this.props.onBlur && this.props.onBlur(tvEvent); // Dispatch tvEvent to all listeners TVEventHandler.dispatchEvent(tvEvent); - } - else { + } else { this.props.onBlur && this.props.onBlur(e); } }, @@ -272,7 +270,7 @@ const TouchableOpacity = ((createReactClass({ }, _opacityInactive: function(duration: number) { - if(Platform.isTV && this.state.focused) { + if (Platform.isTV && this._isFocused) { return; } this.setOpacityTo(this._getChildStyleOpacityWithDefault(), duration); @@ -292,21 +290,21 @@ const TouchableOpacity = ((createReactClass({ accessibilityRole={this.props.accessibilityRole} accessibilityState={this.props.accessibilityState} accessible={this.props.accessible !== false} + hasTVPreferredFocus={this.props.hasTVPreferredFocus} hitSlop={this.props.hitSlop} nativeID={this.props.nativeID} + onBlur={this.touchableHandleBlur} + onFocus={this.touchableHandleFocus} onKeyDown={this.touchableHandleKeyEvent} - onKeyUp={this.touchableHandleKeyEvent} - onLayout={this.props.onLayout} - onResponderGrant={this.touchableHandleResponderGrant} //isTVSelectable={true} //nextFocusDown={this.props.nextFocusDown} //nextFocusForward={this.props.nextFocusForward} //nextFocusLeft={this.props.nextFocusLeft} //nextFocusRight={this.props.nextFocusRight} //nextFocusUp={this.props.nextFocusUp} - hasTVPreferredFocus={this.props.hasTVPreferredFocus} - onFocus={this.touchableHandleFocus} - onBlur={this.touchableHandleBlur} + onKeyUp={this.touchableHandleKeyEvent} + onLayout={this.props.onLayout} + onResponderGrant={this.touchableHandleResponderGrant} //tvParallaxProperties={this.props.tvParallaxProperties} onResponderMove={this.touchableHandleResponderMove} //clickable={ From d396f6907f64ca4c55b834efa73e1f000c54271d Mon Sep 17 00:00:00 2001 From: dev-seb Date: Thu, 19 Mar 2020 10:20:10 +0100 Subject: [PATCH 3/6] Remove test committed by mistake --- packages/react-native-web/src/exports/TouchableOpacity/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-web/src/exports/TouchableOpacity/index.js b/packages/react-native-web/src/exports/TouchableOpacity/index.js index 9eba5049b..7907d5477 100644 --- a/packages/react-native-web/src/exports/TouchableOpacity/index.js +++ b/packages/react-native-web/src/exports/TouchableOpacity/index.js @@ -187,7 +187,7 @@ const TouchableOpacity = ((createReactClass({ if (e.dispatchConfig.registrationName === 'onResponderGrant') { this._opacityActive(0); } else { - this._opacityInactive(250); + this._opacityActive(150); } if (Platform.isTV) { const tvEvent = TVEventHandler.getTVEvent(e); From 67fc47f931265f52ff653b69dcebbd88b2397aec Mon Sep 17 00:00:00 2001 From: dev-seb Date: Thu, 19 Mar 2020 10:28:59 +0100 Subject: [PATCH 4/6] Add onFocus / onBlur event listeners only for TV --- .../src/exports/TouchableHighlight/index.js | 7 +++++-- .../react-native-web/src/exports/TouchableOpacity/index.js | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/react-native-web/src/exports/TouchableHighlight/index.js b/packages/react-native-web/src/exports/TouchableHighlight/index.js index a88218447..8e6936503 100644 --- a/packages/react-native-web/src/exports/TouchableHighlight/index.js +++ b/packages/react-native-web/src/exports/TouchableHighlight/index.js @@ -329,6 +329,9 @@ const TouchableHighlight = ((createReactClass({ render: function() { const child = React.Children.only(this.props.children); + // Add this event listeners only for TVs + const onBlur = Platform.isTV ? { onBlur: this.touchableHandleBlur } : {}; + const onFocus = Platform.isTV ? { onFocus: this.touchableHandleFocus } : {}; return ( Date: Mon, 25 Jan 2021 21:46:29 +0100 Subject: [PATCH 5/6] Update to 0.14.10 version - Enable TV support on TouchableHighlight only and if Platform.isTV is true - Move Key Event handler to useTVEvents module - Support both NodeHandle and nativeID for nextFocus props --- .../src/moduleMap.js | 20 +-- .../src/exports/TVEventHandler/index.js | 5 +- .../src/exports/Touchable/index.js | 42 ------ .../src/exports/TouchableHighlight/index.js | 48 +++++- .../src/exports/TouchableOpacity/index.js | 3 - .../src/modules/useTVEvents/index.js | 138 ++++++++++++++++++ 6 files changed, 196 insertions(+), 60 deletions(-) create mode 100644 packages/react-native-web/src/modules/useTVEvents/index.js diff --git a/packages/babel-plugin-react-native-web/src/moduleMap.js b/packages/babel-plugin-react-native-web/src/moduleMap.js index 6f4d57b70..5f524da38 100644 --- a/packages/babel-plugin-react-native-web/src/moduleMap.js +++ b/packages/babel-plugin-react-native-web/src/moduleMap.js @@ -4,20 +4,18 @@ module.exports = { ActivityIndicator: true, Alert: true, Animated: true, - Appearance: true, AppRegistry: true, AppState: true, + Appearance: true, BackHandler: true, Button: true, CheckBox: true, Clipboard: true, - createElement: true, DeviceEventEmitter: true, DeviceInfo: true, Dimensions: true, DrawerLayoutAndroid: true, Easing: true, - findNodeHandle: true, FlatList: true, I18nManager: true, Image: true, @@ -38,10 +36,8 @@ module.exports = { PixelRatio: true, Platform: true, Pressable: true, - processColor: true, ProgressBar: true, RefreshControl: true, - render: true, SafeAreaView: true, ScrollView: true, SectionList: true, @@ -51,6 +47,7 @@ module.exports = { StyleSheet: true, Switch: true, Systrace: true, + TVEventHandler: true, Text: true, TextInput: true, ToastAndroid: true, @@ -59,13 +56,16 @@ module.exports = { TouchableNativeFeedback: true, TouchableOpacity: true, TouchableWithoutFeedback: true, - TVEventHandler: true, UIManager: true, - unmountComponentAtNode: true, - useColorScheme: true, - useWindowDimensions: true, Vibration: true, View: true, VirtualizedList: true, - YellowBox: true + YellowBox: true, + createElement: true, + findNodeHandle: true, + processColor: true, + render: true, + unmountComponentAtNode: true, + useColorScheme: true, + useWindowDimensions: true }; diff --git a/packages/react-native-web/src/exports/TVEventHandler/index.js b/packages/react-native-web/src/exports/TVEventHandler/index.js index d67c82983..9fed73d81 100644 --- a/packages/react-native-web/src/exports/TVEventHandler/index.js +++ b/packages/react-native-web/src/exports/TVEventHandler/index.js @@ -2,16 +2,19 @@ class TVEventHandler { constructor() { this.component = null; this.callback = null; + this.onHWKeyEvent = this.onHWKeyEvent.bind(this); } enable(component, callback) { this.component = component; this.callback = callback; - document.addEventListener('onHWKeyEvent', this.onHWKeyEvent.bind(this)); + document.addEventListener('onHWKeyEvent', this.onHWKeyEvent); } disable() { document.removeEventListener('onHWKeyEvent', this.onHWKeyEvent); + this.component = null; + this.callback = null; } onHWKeyEvent(event) { diff --git a/packages/react-native-web/src/exports/Touchable/index.js b/packages/react-native-web/src/exports/Touchable/index.js index 9528586a7..96cdc4b96 100644 --- a/packages/react-native-web/src/exports/Touchable/index.js +++ b/packages/react-native-web/src/exports/Touchable/index.js @@ -18,8 +18,6 @@ import Position from './Position'; import React from 'react'; import UIManager from '../UIManager'; import View from '../View'; -import Platform from '../Platform'; -import TVEventHandler from '../TVEventHandler'; type Event = Object; type PressEvent = Object; @@ -878,46 +876,6 @@ const TouchableMixin = { // delays and longPress) touchableHandleKeyEvent: function(e: Event) { const { type, key } = e; - if (Platform.isTV) { - // Get tvEvent - const tvEvent = TVEventHandler.getTVEvent(e); - // Dispatch 'select' tvEvent to component - if (tvEvent.eventType === 'select') { - this.touchableHandlePress(tvEvent); - } - // Dispatch tvEvent to all listeners - TVEventHandler.dispatchEvent(tvEvent); - // Handle next focus - if (this._touchableNode) { - let nextFocusID = ''; - // Check nextFocus* properties - if (this._touchableNode.hasAttribute('nextFocusUp') && key === 'ArrowUp') { - nextFocusID = this._touchableNode.getAttribute('nextFocusUp'); - } else if (this._touchableNode.hasAttribute('nextFocusRight') && key === 'ArrowRight') { - nextFocusID = this._touchableNode.getAttribute('nextFocusRight'); - } else if (this._touchableNode.hasAttribute('nextFocusDown') && key === 'ArrowDown') { - nextFocusID = this._touchableNode.getAttribute('nextFocusDown'); - } else if (this._touchableNode.hasAttribute('nextFocusLeft') && key === 'ArrowLeft') { - nextFocusID = this._touchableNode.getAttribute('nextFocusLeft'); - } - if (nextFocusID && nextFocusID !== '') { - // Get DOM element - const element = document.getElementById(nextFocusID); - if (element && element.tabIndex >= 0) { - // Force focus - element.focus(); - // Stop event propagation - e.stopPropagation(); - } - } - } - // Trigger Hardware Back Press for Back/Escape event keys - if (type === 'keydown' && (key === 'Back' || key === 'Escape')) { - // eslint-disable-next-line no-undef - const hwKeyEvent = new CustomEvent('hardwareBackPress', {}); - document.dispatchEvent(hwKeyEvent); - } - } if (key === 'Enter' || key === ' ') { if (type === 'keydown') { if (!this._isTouchableKeyboardActive) { diff --git a/packages/react-native-web/src/exports/TouchableHighlight/index.js b/packages/react-native-web/src/exports/TouchableHighlight/index.js index 4217cb67c..2297540f3 100644 --- a/packages/react-native-web/src/exports/TouchableHighlight/index.js +++ b/packages/react-native-web/src/exports/TouchableHighlight/index.js @@ -18,11 +18,9 @@ import * as React from 'react'; import { useCallback, useMemo, useState, useRef } from 'react'; import useMergeRefs from '../../modules/useMergeRefs'; import usePressEvents from '../../modules/usePressEvents'; +import useTVEvents from '../../modules/useTVEvents'; import StyleSheet from '../StyleSheet'; import View from '../View'; -import UIManager from '../UIManager'; -import Platform from '../Platform'; -import TVEventHandler from '../TVEventHandler'; type ViewStyle = $PropertyType; @@ -33,7 +31,13 @@ type Props = $ReadOnly<{| onShowUnderlay?: ?() => void, style?: ViewStyle, testOnly_pressed?: ?boolean, - underlayColor?: ?ColorValue + underlayColor?: ?ColorValue, + hasTVPreferredFocus?: ?boolean, + nextFocusDown?: ?any, + nextFocusForward?: ?any, + nextFocusLeft?: ?any, + nextFocusRight?: ?any, + nextFocusUp?: ?any |}>; type ExtraStyles = $ReadOnly<{| @@ -82,6 +86,14 @@ function TouchableHighlight(props: Props, forwardedRef): React.Node { delayLongPress, disabled, focusable, + hasTVPreferredFocus, + nextFocusDown, + nextFocusForward, + nextFocusLeft, + nextFocusRight, + nextFocusUp, + onFocus, + onBlur, onHideUnderlay, onLongPress, onPress, @@ -163,12 +175,40 @@ function TouchableHighlight(props: Props, forwardedRef): React.Node { const pressEventHandlers = usePressEvents(hostRef, pressConfig); + const tvConfig = useMemo( + () => ({ + hasTVPreferredFocus, + nextFocusDown, + nextFocusForward, + nextFocusLeft, + nextFocusRight, + nextFocusUp, + onPress, + onFocus, + onBlur + }), + [ + hasTVPreferredFocus, + nextFocusDown, + nextFocusForward, + nextFocusLeft, + nextFocusRight, + nextFocusUp, + onPress, + onFocus, + onBlur + ] + ); + + const tvEventHandlers = useTVEvents(hostRef, tvConfig); + const child = React.Children.only(children); return ( ; diff --git a/packages/react-native-web/src/modules/useTVEvents/index.js b/packages/react-native-web/src/modules/useTVEvents/index.js new file mode 100644 index 000000000..1e7827bec --- /dev/null +++ b/packages/react-native-web/src/modules/useTVEvents/index.js @@ -0,0 +1,138 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +import * as React from 'react'; +import { useEffect } from 'react'; +import View from '../../exports/View'; +import UIManager from '../../exports/UIManager'; +import Platform from '../../exports/Platform'; +import TVEventHandler from '../../exports/TVEventHandler'; + +type Event = any; + +export type TVResponderConfig = $ReadOnly<{| + hasTVPreferredFocus?: ?boolean, + nextFocusDown?: ?any, + nextFocusForward?: ?any, + nextFocusLeft?: ?any, + nextFocusRight?: ?any, + nextFocusUp?: ?any, + onPress?: ?(event: Event) => void, + onFocus?: ?(event: Event) => void, + onBlur?: ?(event: Event) => void, +|}>; + +export default function useTVEvents( + hostRef: React.ElementRef, + config: TVResponderConfig +) { + useEffect(() => { + if (Platform.isTV && config.hasTVPreferredFocus) { + if (hostRef.current) { + UIManager.focus(hostRef.current); + } + } + }, [config.hasTVPreferredFocus, hostRef]); + + function onKeyEvent(event: Event) { + const { type, key } = event; + // Get tvEvent + const tvEvent = TVEventHandler.getTVEvent(event); + // Dispatch 'select' tvEvent to component + if (tvEvent.eventType === 'select') { + if (config.onPress) { + config.onPress(tvEvent); + } + } + // Dispatch tvEvent to all listeners + TVEventHandler.dispatchEvent(tvEvent); + // Handle next focus + let nextElement = null; + // Check nextFocus properties set using : nextFocus*={findNodeHandle(ref.current)} + if (config.nextFocusUp && key === 'ArrowUp') { + nextElement = config.nextFocusUp; + } else if (config.nextFocusRight && key === 'ArrowRight') { + nextElement = config.nextFocusRight; + } else if (config.nextFocusDown && key === 'ArrowDown') { + nextElement = config.nextFocusDown; + } else if (config.nextFocusLeft && key === 'ArrowLeft') { + nextElement = config.nextFocusLeft; + } else if (config.nextFocusForward && key === 'ArrowRight') { + nextElement = config.nextFocusForward; + } + if (nextElement) { + // Focus if element is focusable + UIManager.focus(nextElement); + // Stop event propagation + event.stopPropagation(); + } + // Check nextFocus properties set using : ref.current.setNativeProps({nextFocus*: nativeID} + let nextFocusID = ''; + // Check nextFocus* properties + if (hostRef.current.hasAttribute('nextFocusUp') && key === 'ArrowUp') { + nextFocusID = hostRef.current.getAttribute('nextFocusUp'); + } else if (hostRef.current.hasAttribute('nextFocusRight') && key === 'ArrowRight') { + nextFocusID = hostRef.current.getAttribute('nextFocusRight'); + } else if (hostRef.current.hasAttribute('nextFocusDown') && key === 'ArrowDown') { + nextFocusID = hostRef.current.getAttribute('nextFocusDown'); + } else if (hostRef.current.hasAttribute('nextFocusLeft') && key === 'ArrowLeft') { + nextFocusID = hostRef.current.getAttribute('nextFocusLeft'); + } else if (hostRef.current.hasAttribute('nextFocusForward') && key === 'ArrowRight') { + nextFocusID = hostRef.current.getAttribute('nextFocusForward'); + } + if (nextFocusID && nextFocusID !== '') { + // Get DOM element + nextElement = document.getElementById(nextFocusID); + if (nextElement) { + // Focus is element if focusable + UIManager.focus(nextElement); + // Stop event propagation + event.stopPropagation(); + } + } + // Trigger Hardware Back Press for Back/Escape event keys + if (type === 'keydown' && (key === 'Back' || key === 'Escape')) { + // eslint-disable-next-line no-undef + const hwKeyEvent = new CustomEvent('hardwareBackPress', {}); + document.dispatchEvent(hwKeyEvent); + } + } + + const tvEventHandlers = Platform.isTV + ? { + onFocus: (event: Event) => { + // Get tvEvent + const tvEvent = TVEventHandler.getTVEvent(event); + // Dispatch tvEvent to component + if (config.onFocus) { + config.onFocus(tvEvent); + } + // Dispatch tvEvent to all listeners + TVEventHandler.dispatchEvent(tvEvent); + }, + onBlur: (event: Event) => { + // Get tvEvent + const tvEvent = TVEventHandler.getTVEvent(event); + // Dispatch tvEvent to component + if (config.onBlur) { + config.onBlur(tvEvent); + } + // Dispatch tvEvent to all listeners + TVEventHandler.dispatchEvent(tvEvent); + }, + onKeyDown: onKeyEvent, + onKeyUp: onKeyEvent, + } + : {}; + + return tvEventHandlers; +} From b4a82caa0dd2e1e62b15e9bc82f89a6c6252583e Mon Sep 17 00:00:00 2001 From: dev-seb Date: Mon, 25 Jan 2021 22:13:31 +0100 Subject: [PATCH 6/6] Fix format with prettier --- packages/react-native-web/src/exports/TextInput/index.js | 2 +- packages/react-native-web/src/modules/useTVEvents/index.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native-web/src/exports/TextInput/index.js b/packages/react-native-web/src/exports/TextInput/index.js index 36980e73d..9195a0a50 100644 --- a/packages/react-native-web/src/exports/TextInput/index.js +++ b/packages/react-native-web/src/exports/TextInput/index.js @@ -271,7 +271,7 @@ const TextInput = forwardRef((props, forwardedRef) => { } function handleKeyDown(e) { - if(!Platform.isTV) { + if (!Platform.isTV) { // Prevent key events bubbling (see #612) e.stopPropagation(); } diff --git a/packages/react-native-web/src/modules/useTVEvents/index.js b/packages/react-native-web/src/modules/useTVEvents/index.js index 1e7827bec..f0544b588 100644 --- a/packages/react-native-web/src/modules/useTVEvents/index.js +++ b/packages/react-native-web/src/modules/useTVEvents/index.js @@ -28,7 +28,7 @@ export type TVResponderConfig = $ReadOnly<{| nextFocusUp?: ?any, onPress?: ?(event: Event) => void, onFocus?: ?(event: Event) => void, - onBlur?: ?(event: Event) => void, + onBlur?: ?(event: Event) => void |}>; export default function useTVEvents( @@ -130,7 +130,7 @@ export default function useTVEvents( TVEventHandler.dispatchEvent(tvEvent); }, onKeyDown: onKeyEvent, - onKeyUp: onKeyEvent, + onKeyUp: onKeyEvent } : {};