Skip to content

Commit b1c28c9

Browse files
dev-sebSébastien Biziou
authored andcommitted
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
1 parent cd71d27 commit b1c28c9

File tree

6 files changed

+196
-60
lines changed

6 files changed

+196
-60
lines changed

packages/babel-plugin-react-native-web/src/moduleMap.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,18 @@ module.exports = {
44
ActivityIndicator: true,
55
Alert: true,
66
Animated: true,
7-
Appearance: true,
87
AppRegistry: true,
98
AppState: true,
9+
Appearance: true,
1010
BackHandler: true,
1111
Button: true,
1212
CheckBox: true,
1313
Clipboard: true,
14-
createElement: true,
1514
DeviceEventEmitter: true,
1615
DeviceInfo: true,
1716
Dimensions: true,
1817
DrawerLayoutAndroid: true,
1918
Easing: true,
20-
findNodeHandle: true,
2119
FlatList: true,
2220
I18nManager: true,
2321
Image: true,
@@ -38,10 +36,8 @@ module.exports = {
3836
PixelRatio: true,
3937
Platform: true,
4038
Pressable: true,
41-
processColor: true,
4239
ProgressBar: true,
4340
RefreshControl: true,
44-
render: true,
4541
SafeAreaView: true,
4642
ScrollView: true,
4743
SectionList: true,
@@ -51,6 +47,7 @@ module.exports = {
5147
StyleSheet: true,
5248
Switch: true,
5349
Systrace: true,
50+
TVEventHandler: true,
5451
Text: true,
5552
TextInput: true,
5653
ToastAndroid: true,
@@ -59,13 +56,16 @@ module.exports = {
5956
TouchableNativeFeedback: true,
6057
TouchableOpacity: true,
6158
TouchableWithoutFeedback: true,
62-
TVEventHandler: true,
6359
UIManager: true,
64-
unmountComponentAtNode: true,
65-
useColorScheme: true,
66-
useWindowDimensions: true,
6760
Vibration: true,
6861
View: true,
6962
VirtualizedList: true,
70-
YellowBox: true
63+
YellowBox: true,
64+
createElement: true,
65+
findNodeHandle: true,
66+
processColor: true,
67+
render: true,
68+
unmountComponentAtNode: true,
69+
useColorScheme: true,
70+
useWindowDimensions: true
7171
};

packages/react-native-web/src/exports/TVEventHandler/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ class TVEventHandler {
22
constructor() {
33
this.component = null;
44
this.callback = null;
5+
this.onHWKeyEvent = this.onHWKeyEvent.bind(this);
56
}
67

78
enable(component, callback) {
89
this.component = component;
910
this.callback = callback;
10-
document.addEventListener('onHWKeyEvent', this.onHWKeyEvent.bind(this));
11+
document.addEventListener('onHWKeyEvent', this.onHWKeyEvent);
1112
}
1213

1314
disable() {
1415
document.removeEventListener('onHWKeyEvent', this.onHWKeyEvent);
16+
this.component = null;
17+
this.callback = null;
1518
}
1619

1720
onHWKeyEvent(event) {

packages/react-native-web/src/exports/Touchable/index.js

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ import Position from './Position';
1818
import React from 'react';
1919
import UIManager from '../UIManager';
2020
import View from '../View';
21-
import Platform from '../Platform';
22-
import TVEventHandler from '../TVEventHandler';
2321

2422
type Event = Object;
2523
type PressEvent = Object;
@@ -878,46 +876,6 @@ const TouchableMixin = {
878876
// delays and longPress)
879877
touchableHandleKeyEvent: function(e: Event) {
880878
const { type, key } = e;
881-
if (Platform.isTV) {
882-
// Get tvEvent
883-
const tvEvent = TVEventHandler.getTVEvent(e);
884-
// Dispatch 'select' tvEvent to component
885-
if (tvEvent.eventType === 'select') {
886-
this.touchableHandlePress(tvEvent);
887-
}
888-
// Dispatch tvEvent to all listeners
889-
TVEventHandler.dispatchEvent(tvEvent);
890-
// Handle next focus
891-
if (this._touchableNode) {
892-
let nextFocusID = '';
893-
// Check nextFocus* properties
894-
if (this._touchableNode.hasAttribute('nextFocusUp') && key === 'ArrowUp') {
895-
nextFocusID = this._touchableNode.getAttribute('nextFocusUp');
896-
} else if (this._touchableNode.hasAttribute('nextFocusRight') && key === 'ArrowRight') {
897-
nextFocusID = this._touchableNode.getAttribute('nextFocusRight');
898-
} else if (this._touchableNode.hasAttribute('nextFocusDown') && key === 'ArrowDown') {
899-
nextFocusID = this._touchableNode.getAttribute('nextFocusDown');
900-
} else if (this._touchableNode.hasAttribute('nextFocusLeft') && key === 'ArrowLeft') {
901-
nextFocusID = this._touchableNode.getAttribute('nextFocusLeft');
902-
}
903-
if (nextFocusID && nextFocusID !== '') {
904-
// Get DOM element
905-
const element = document.getElementById(nextFocusID);
906-
if (element && element.tabIndex >= 0) {
907-
// Force focus
908-
element.focus();
909-
// Stop event propagation
910-
e.stopPropagation();
911-
}
912-
}
913-
}
914-
// Trigger Hardware Back Press for Back/Escape event keys
915-
if (type === 'keydown' && (key === 'Back' || key === 'Escape')) {
916-
// eslint-disable-next-line no-undef
917-
const hwKeyEvent = new CustomEvent('hardwareBackPress', {});
918-
document.dispatchEvent(hwKeyEvent);
919-
}
920-
}
921879
if (key === 'Enter' || key === ' ') {
922880
if (type === 'keydown') {
923881
if (!this._isTouchableKeyboardActive) {

packages/react-native-web/src/exports/TouchableHighlight/index.js

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,9 @@ import * as React from 'react';
1818
import { useCallback, useMemo, useState, useRef } from 'react';
1919
import useMergeRefs from '../../modules/useMergeRefs';
2020
import usePressEvents from '../../modules/usePressEvents';
21+
import useTVEvents from '../../modules/useTVEvents';
2122
import StyleSheet from '../StyleSheet';
2223
import View from '../View';
23-
import UIManager from '../UIManager';
24-
import Platform from '../Platform';
25-
import TVEventHandler from '../TVEventHandler';
2624

2725
type ViewStyle = $PropertyType<ViewProps, 'style'>;
2826

@@ -33,7 +31,13 @@ type Props = $ReadOnly<{|
3331
onShowUnderlay?: ?() => void,
3432
style?: ViewStyle,
3533
testOnly_pressed?: ?boolean,
36-
underlayColor?: ?ColorValue
34+
underlayColor?: ?ColorValue,
35+
hasTVPreferredFocus?: ?boolean,
36+
nextFocusDown?: ?any,
37+
nextFocusForward?: ?any,
38+
nextFocusLeft?: ?any,
39+
nextFocusRight?: ?any,
40+
nextFocusUp?: ?any
3741
|}>;
3842

3943
type ExtraStyles = $ReadOnly<{|
@@ -82,6 +86,14 @@ function TouchableHighlight(props: Props, forwardedRef): React.Node {
8286
delayLongPress,
8387
disabled,
8488
focusable,
89+
hasTVPreferredFocus,
90+
nextFocusDown,
91+
nextFocusForward,
92+
nextFocusLeft,
93+
nextFocusRight,
94+
nextFocusUp,
95+
onFocus,
96+
onBlur,
8597
onHideUnderlay,
8698
onLongPress,
8799
onPress,
@@ -163,12 +175,40 @@ function TouchableHighlight(props: Props, forwardedRef): React.Node {
163175

164176
const pressEventHandlers = usePressEvents(hostRef, pressConfig);
165177

178+
const tvConfig = useMemo(
179+
() => ({
180+
hasTVPreferredFocus,
181+
nextFocusDown,
182+
nextFocusForward,
183+
nextFocusLeft,
184+
nextFocusRight,
185+
nextFocusUp,
186+
onPress,
187+
onFocus,
188+
onBlur
189+
}),
190+
[
191+
hasTVPreferredFocus,
192+
nextFocusDown,
193+
nextFocusForward,
194+
nextFocusLeft,
195+
nextFocusRight,
196+
nextFocusUp,
197+
onPress,
198+
onFocus,
199+
onBlur
200+
]
201+
);
202+
203+
const tvEventHandlers = useTVEvents(hostRef, tvConfig);
204+
166205
const child = React.Children.only(children);
167206

168207
return (
169208
<View
170209
{...rest}
171210
{...pressEventHandlers}
211+
{...tvEventHandlers}
172212
accessibilityState={{
173213
disabled,
174214
...props.accessibilityState

packages/react-native-web/src/exports/TouchableOpacity/index.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ import useMergeRefs from '../../modules/useMergeRefs';
1919
import usePressEvents from '../../modules/usePressEvents';
2020
import StyleSheet from '../StyleSheet';
2121
import View from '../View';
22-
import UIManager from '../UIManager';
23-
import Platform from '../Platform';
24-
import TVEventHandler from '../TVEventHandler';
2522

2623
type ViewStyle = $PropertyType<ViewProps, 'style'>;
2724

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
'use strict';
12+
13+
import * as React from 'react';
14+
import { useEffect } from 'react';
15+
import View from '../../exports/View';
16+
import UIManager from '../../exports/UIManager';
17+
import Platform from '../../exports/Platform';
18+
import TVEventHandler from '../../exports/TVEventHandler';
19+
20+
type Event = any;
21+
22+
export type TVResponderConfig = $ReadOnly<{|
23+
hasTVPreferredFocus?: ?boolean,
24+
nextFocusDown?: ?any,
25+
nextFocusForward?: ?any,
26+
nextFocusLeft?: ?any,
27+
nextFocusRight?: ?any,
28+
nextFocusUp?: ?any,
29+
onPress?: ?(event: Event) => void,
30+
onFocus?: ?(event: Event) => void,
31+
onBlur?: ?(event: Event) => void,
32+
|}>;
33+
34+
export default function useTVEvents(
35+
hostRef: React.ElementRef<typeof View>,
36+
config: TVResponderConfig
37+
) {
38+
useEffect(() => {
39+
if (Platform.isTV && config.hasTVPreferredFocus) {
40+
if (hostRef.current) {
41+
UIManager.focus(hostRef.current);
42+
}
43+
}
44+
}, [config.hasTVPreferredFocus, hostRef]);
45+
46+
function onKeyEvent(event: Event) {
47+
const { type, key } = event;
48+
// Get tvEvent
49+
const tvEvent = TVEventHandler.getTVEvent(event);
50+
// Dispatch 'select' tvEvent to component
51+
if (tvEvent.eventType === 'select') {
52+
if (config.onPress) {
53+
config.onPress(tvEvent);
54+
}
55+
}
56+
// Dispatch tvEvent to all listeners
57+
TVEventHandler.dispatchEvent(tvEvent);
58+
// Handle next focus
59+
let nextElement = null;
60+
// Check nextFocus properties set using : nextFocus*={findNodeHandle(ref.current)}
61+
if (config.nextFocusUp && key === 'ArrowUp') {
62+
nextElement = config.nextFocusUp;
63+
} else if (config.nextFocusRight && key === 'ArrowRight') {
64+
nextElement = config.nextFocusRight;
65+
} else if (config.nextFocusDown && key === 'ArrowDown') {
66+
nextElement = config.nextFocusDown;
67+
} else if (config.nextFocusLeft && key === 'ArrowLeft') {
68+
nextElement = config.nextFocusLeft;
69+
} else if (config.nextFocusForward && key === 'ArrowRight') {
70+
nextElement = config.nextFocusForward;
71+
}
72+
if (nextElement) {
73+
// Focus if element is focusable
74+
UIManager.focus(nextElement);
75+
// Stop event propagation
76+
event.stopPropagation();
77+
}
78+
// Check nextFocus properties set using : ref.current.setNativeProps({nextFocus*: nativeID}
79+
let nextFocusID = '';
80+
// Check nextFocus* properties
81+
if (hostRef.current.hasAttribute('nextFocusUp') && key === 'ArrowUp') {
82+
nextFocusID = hostRef.current.getAttribute('nextFocusUp');
83+
} else if (hostRef.current.hasAttribute('nextFocusRight') && key === 'ArrowRight') {
84+
nextFocusID = hostRef.current.getAttribute('nextFocusRight');
85+
} else if (hostRef.current.hasAttribute('nextFocusDown') && key === 'ArrowDown') {
86+
nextFocusID = hostRef.current.getAttribute('nextFocusDown');
87+
} else if (hostRef.current.hasAttribute('nextFocusLeft') && key === 'ArrowLeft') {
88+
nextFocusID = hostRef.current.getAttribute('nextFocusLeft');
89+
} else if (hostRef.current.hasAttribute('nextFocusForward') && key === 'ArrowRight') {
90+
nextFocusID = hostRef.current.getAttribute('nextFocusForward');
91+
}
92+
if (nextFocusID && nextFocusID !== '') {
93+
// Get DOM element
94+
nextElement = document.getElementById(nextFocusID);
95+
if (nextElement) {
96+
// Focus is element if focusable
97+
UIManager.focus(nextElement);
98+
// Stop event propagation
99+
event.stopPropagation();
100+
}
101+
}
102+
// Trigger Hardware Back Press for Back/Escape event keys
103+
if (type === 'keydown' && (key === 'Back' || key === 'Escape')) {
104+
// eslint-disable-next-line no-undef
105+
const hwKeyEvent = new CustomEvent('hardwareBackPress', {});
106+
document.dispatchEvent(hwKeyEvent);
107+
}
108+
}
109+
110+
const tvEventHandlers = Platform.isTV
111+
? {
112+
onFocus: (event: Event) => {
113+
// Get tvEvent
114+
const tvEvent = TVEventHandler.getTVEvent(event);
115+
// Dispatch tvEvent to component
116+
if (config.onFocus) {
117+
config.onFocus(tvEvent);
118+
}
119+
// Dispatch tvEvent to all listeners
120+
TVEventHandler.dispatchEvent(tvEvent);
121+
},
122+
onBlur: (event: Event) => {
123+
// Get tvEvent
124+
const tvEvent = TVEventHandler.getTVEvent(event);
125+
// Dispatch tvEvent to component
126+
if (config.onBlur) {
127+
config.onBlur(tvEvent);
128+
}
129+
// Dispatch tvEvent to all listeners
130+
TVEventHandler.dispatchEvent(tvEvent);
131+
},
132+
onKeyDown: onKeyEvent,
133+
onKeyUp: onKeyEvent,
134+
}
135+
: {};
136+
137+
return tvEventHandlers;
138+
}

0 commit comments

Comments
 (0)