react-native-tvos 0.76.1-0 → 0.76.2-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Libraries/AppDelegate/React-RCTAppDelegate.podspec +1 -1
- package/Libraries/Components/Pressable/Pressable.d.ts +9 -1
- package/Libraries/Components/Pressable/Pressable.js +4 -16
- package/Libraries/Components/TV/TVViewPropTypes.js +2 -1
- package/Libraries/Components/TextInput/TextInput.d.ts +1 -1
- package/Libraries/Components/Touchable/Touchable.js +0 -43
- package/Libraries/Components/Touchable/TouchableBounce.js +0 -33
- package/Libraries/Components/Touchable/TouchableHighlight.js +12 -47
- package/Libraries/Components/Touchable/TouchableNativeFeedback.js +0 -33
- package/Libraries/Components/Touchable/TouchableOpacity.js +12 -44
- package/Libraries/Components/Touchable/TouchableWithoutFeedback.js +0 -19
- package/Libraries/Components/View/ViewNativeComponent.js +6 -0
- package/Libraries/Components/View/ViewPropTypes.d.ts +12 -1
- package/Libraries/Components/View/ViewPropTypes.js +7 -0
- package/Libraries/Core/ReactNativeVersion.js +1 -1
- package/Libraries/Core/setUpErrorHandling.js +1 -7
- package/Libraries/LogBox/Data/LogBoxData.js +2 -2
- package/Libraries/NativeComponent/BaseViewConfig.android.js +19 -0
- package/Libraries/NativeComponent/BaseViewConfig.ios.js +6 -0
- package/Libraries/NativeComponent/TVViewConfig.js +4 -0
- package/Libraries/Pressability/Pressability.js +45 -28
- package/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +1 -0
- package/Libraries/Types/CoreEventTypes.d.ts +21 -0
- package/Libraries/Types/CoreEventTypes.js +6 -0
- package/README.md +9 -7
- package/React/Base/RCTTVRemoteHandler.m +0 -19
- package/React/Base/RCTTVRemoteSelectHandler.h +27 -0
- package/React/Base/RCTTVRemoteSelectHandler.m +120 -0
- package/React/Base/RCTVersion.m +1 -1
- package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +12 -8
- package/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +47 -3
- package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h +8 -0
- package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +51 -44
- package/React/Views/RCTTVView.h +19 -6
- package/React/Views/RCTTVView.m +63 -55
- package/React/Views/RCTViewManager.m +4 -0
- package/React/Views/ScrollView/RCTScrollView.m +12 -8
- package/ReactAndroid/api/ReactAndroid.api +0 -1
- package/ReactAndroid/cmake-utils/ReactNative-application.cmake +1 -1
- package/ReactAndroid/gradle.properties +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt +2 -0
- package/ReactAndroid/src/main/java/com/facebook/react/modules/core/TimingModule.kt +0 -8
- package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +16 -0
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java +20 -0
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/BlurEvent.kt +16 -0
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FocusEvent.kt +16 -0
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/PressInEvent.kt +16 -0
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/PressOutEvent.kt +16 -0
- package/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +11 -3
- package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +212 -4
- package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +47 -4
- package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
- package/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputShadowNode.cpp +3 -2
- package/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.cpp +18 -0
- package/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.h +8 -0
- package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h +12 -1
- package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +165 -2
- package/cli.js +1 -1
- package/index.js +0 -4
- package/package.json +8 -8
- package/scripts/codegen/generate-artifacts-executor.js +3 -3
- package/sdks/.hermesversion +1 -1
- package/sdks/hermesc/osx-bin/hermes +0 -0
- package/sdks/hermesc/osx-bin/hermesc +0 -0
- package/sdks/hermesc/win64-bin/hermesc.exe +0 -0
- package/types/modules/Codegen.d.ts +6 -0
- package/types/public/ReactNativeTVTypes.d.ts +2 -2
- package/Libraries/Components/TabBarIOS/RCTTabBarItemNativeComponent.js +0 -99
- package/Libraries/Components/TabBarIOS/RCTTabBarNativeComponent.js +0 -32
- package/Libraries/Components/TabBarIOS/TabBarIOS.ios.js +0 -59
- package/Libraries/Components/TabBarIOS/TabBarIOS.js +0 -52
- package/Libraries/Components/TabBarIOS/TabBarIOSProps.js +0 -52
- package/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js +0 -177
- package/Libraries/Components/TabBarIOS/TabBarItemIOS.js +0 -55
- package/Libraries/Components/Touchable/TVTouchable.js +0 -71
- package/React/Views/RCTTabBar.h +0 -22
- package/React/Views/RCTTabBar.m +0 -237
- package/React/Views/RCTTabBarItem.h +0 -35
- package/React/Views/RCTTabBarItem.m +0 -139
- package/React/Views/RCTTabBarItemManager.h +0 -12
- package/React/Views/RCTTabBarItemManager.m +0 -38
- package/React/Views/RCTTabBarManager.h +0 -12
- package/React/Views/RCTTabBarManager.m +0 -81
|
@@ -110,6 +110,18 @@ const bubblingEventTypes = {
|
|
|
110
110
|
bubbled: 'onClick',
|
|
111
111
|
},
|
|
112
112
|
},
|
|
113
|
+
topFocus: {
|
|
114
|
+
phasedRegistrationNames: {
|
|
115
|
+
captured: 'onFocusCapture',
|
|
116
|
+
bubbled: 'onFocus',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
topBlur: {
|
|
120
|
+
phasedRegistrationNames: {
|
|
121
|
+
captured: 'onBlurCapture',
|
|
122
|
+
bubbled: 'onBlur',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
113
125
|
};
|
|
114
126
|
|
|
115
127
|
const directEventTypes = {
|
|
@@ -123,6 +135,13 @@ const directEventTypes = {
|
|
|
123
135
|
registrationName: 'onGestureHandlerStateChange',
|
|
124
136
|
}),
|
|
125
137
|
|
|
138
|
+
topPressIn: {
|
|
139
|
+
registrationName: 'onPressIn',
|
|
140
|
+
},
|
|
141
|
+
topPressOut: {
|
|
142
|
+
registrationName: 'onPressOut',
|
|
143
|
+
},
|
|
144
|
+
|
|
126
145
|
// Direct events from UIManagerModuleConstants.java
|
|
127
146
|
topContentSizeChange: {
|
|
128
147
|
registrationName: 'onContentSizeChange',
|
|
@@ -181,6 +181,12 @@ const directEventTypes = {
|
|
|
181
181
|
onGestureHandlerStateChange: DynamicallyInjectedByGestureHandler({
|
|
182
182
|
registrationName: 'onGestureHandlerStateChange',
|
|
183
183
|
}),
|
|
184
|
+
topPressIn: {
|
|
185
|
+
registrationName: 'onPressIn',
|
|
186
|
+
},
|
|
187
|
+
topPressOut: {
|
|
188
|
+
registrationName: 'onPressOut',
|
|
189
|
+
},
|
|
184
190
|
};
|
|
185
191
|
|
|
186
192
|
const validAttributesForNonEventProps = {
|
|
@@ -16,6 +16,10 @@ export const validAttributesForTVProps = {
|
|
|
16
16
|
nextFocusLeft: true,
|
|
17
17
|
nextFocusRight: true,
|
|
18
18
|
nextFocusUp: true,
|
|
19
|
+
onFocus: true,
|
|
20
|
+
onBlur: true,
|
|
21
|
+
onPressIn: true,
|
|
22
|
+
onPressOut: true,
|
|
19
23
|
trapFocusLeft: true,
|
|
20
24
|
trapFocusRight: true,
|
|
21
25
|
trapFocusDown: true,
|
|
@@ -157,7 +157,8 @@ export type EventHandlers = $ReadOnly<{|
|
|
|
157
157
|
onResponderTerminate: (event: PressEvent) => void,
|
|
158
158
|
onResponderTerminationRequest: () => boolean,
|
|
159
159
|
onStartShouldSetResponder: () => boolean,
|
|
160
|
-
|
|
160
|
+
onPressIn: (event: any) => void,
|
|
161
|
+
onPressOut: (event: any) => void,
|
|
161
162
|
|}>;
|
|
162
163
|
|
|
163
164
|
type TouchState =
|
|
@@ -397,6 +398,7 @@ export default class Pressability {
|
|
|
397
398
|
|}>;
|
|
398
399
|
_touchActivateTime: ?number;
|
|
399
400
|
_touchState: TouchState = 'NOT_RESPONDER';
|
|
401
|
+
_longPressSent: boolean = false;
|
|
400
402
|
|
|
401
403
|
constructor(config: PressabilityConfig) {
|
|
402
404
|
this.configure(config);
|
|
@@ -436,35 +438,50 @@ export default class Pressability {
|
|
|
436
438
|
}
|
|
437
439
|
|
|
438
440
|
_createEventHandlers(): EventHandlers {
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
if (this._config.disabled
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
441
|
+
const tvPressEventHandlers = {
|
|
442
|
+
onPressIn: (evt: any): void => {
|
|
443
|
+
if (this._config.disabled === false) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
this._longPressSent = false;
|
|
448
|
+
|
|
449
|
+
const {onPressIn, onLongPress} = this._config;
|
|
450
|
+
onPressIn && onPressIn(evt);
|
|
451
|
+
|
|
452
|
+
const delayPressIn = normalizeDelay(this._config.delayPressIn);
|
|
453
|
+
const delayLongPress = normalizeDelay(
|
|
454
|
+
this._config.delayLongPress,
|
|
455
|
+
10,
|
|
456
|
+
DEFAULT_LONG_PRESS_DELAY_MS - delayPressIn,
|
|
457
|
+
);
|
|
458
|
+
this._longPressDelayTimeout = setTimeout(() => {
|
|
459
|
+
onLongPress && onLongPress(evt);
|
|
460
|
+
this._longPressSent = true;
|
|
461
|
+
}, delayLongPress + delayPressIn);
|
|
462
|
+
},
|
|
463
|
+
onPressOut: (evt: any): void => {
|
|
464
|
+
if (this._config.disabled === false) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
this._cancelLongPressDelayTimeout();
|
|
468
|
+
const {onPress, onLongPress, onPressOut, android_disableSound} =
|
|
469
|
+
this._config;
|
|
470
|
+
onPressOut && onPressOut(evt);
|
|
471
|
+
|
|
472
|
+
if (onPress != null) {
|
|
473
|
+
const isPressCanceledByLongPress =
|
|
474
|
+
onLongPress != null && this._longPressSent;
|
|
475
|
+
if (!isPressCanceledByLongPress) {
|
|
476
|
+
if (Platform.OS === 'android' && android_disableSound !== true) {
|
|
477
|
+
SoundManager.playTouchSound();
|
|
478
|
+
}
|
|
479
|
+
onPress(evt);
|
|
464
480
|
}
|
|
465
481
|
}
|
|
466
482
|
},
|
|
467
483
|
};
|
|
484
|
+
|
|
468
485
|
const focusEventHandlers = {
|
|
469
486
|
onBlur: (event: BlurEvent): void => {
|
|
470
487
|
const {onBlur} = this._config;
|
|
@@ -645,7 +662,7 @@ export default class Pressability {
|
|
|
645
662
|
};
|
|
646
663
|
}
|
|
647
664
|
return {
|
|
648
|
-
...
|
|
665
|
+
...tvPressEventHandlers,
|
|
649
666
|
...focusEventHandlers,
|
|
650
667
|
...responderEventHandlers,
|
|
651
668
|
...hoverPointerEvents,
|
|
@@ -698,7 +715,7 @@ export default class Pressability {
|
|
|
698
715
|
},
|
|
699
716
|
};
|
|
700
717
|
return {
|
|
701
|
-
...
|
|
718
|
+
...tvPressEventHandlers,
|
|
702
719
|
...focusEventHandlers,
|
|
703
720
|
...responderEventHandlers,
|
|
704
721
|
...mouseEventHandlers,
|
|
@@ -35,6 +35,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
35
35
|
@property (nonatomic, assign, readonly) CGFloat zoomScale;
|
|
36
36
|
@property (nonatomic, assign, readonly) CGPoint contentOffset;
|
|
37
37
|
@property (nonatomic, assign, readonly) UIEdgeInsets contentInset;
|
|
38
|
+
@property (nullable, nonatomic, copy) NSDictionary<NSAttributedStringKey, id> *typingAttributes;
|
|
38
39
|
|
|
39
40
|
// This protocol disallows direct access to `selectedTextRange` property because
|
|
40
41
|
// unwise usage of it can break the `delegate` behavior. So, we always have to
|
|
@@ -246,6 +246,15 @@ export interface GestureResponderEvent
|
|
|
246
246
|
|
|
247
247
|
export interface MouseEvent extends NativeSyntheticEvent<NativeMouseEvent> {}
|
|
248
248
|
|
|
249
|
+
export interface NativeFocusEvent extends TargetedEvent {}
|
|
250
|
+
export interface FocusEvent extends NativeSyntheticEvent<NativeFocusEvent> {}
|
|
251
|
+
|
|
252
|
+
export interface NativeBlurEvent extends TargetedEvent {}
|
|
253
|
+
export interface BlurEvent extends NativeSyntheticEvent<NativeBlurEvent> {}
|
|
254
|
+
|
|
255
|
+
export interface NativePressEvent extends TargetedEvent {}
|
|
256
|
+
export interface PressEvent extends NativeSyntheticEvent<NativePressEvent> {}
|
|
257
|
+
|
|
249
258
|
export interface TargetedEvent {
|
|
250
259
|
target: number;
|
|
251
260
|
}
|
|
@@ -265,6 +274,18 @@ export interface PointerEvents {
|
|
|
265
274
|
onPointerUpCapture?: ((event: PointerEvent) => void) | undefined;
|
|
266
275
|
}
|
|
267
276
|
|
|
277
|
+
export interface PressEvents {
|
|
278
|
+
onPressIn?: ((event: PressEvent) => void) | undefined;
|
|
279
|
+
onPressOut?: ((event: PressEvent) => void) | undefined;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export interface FocusEvents {
|
|
283
|
+
onFocus?: ((event: FocusEvent) => void) | undefined;
|
|
284
|
+
onFocusCapture?: ((event: FocusEvent) => void) | undefined;
|
|
285
|
+
onBlur?: ((event: BlurEvent) => void) | undefined;
|
|
286
|
+
onBlurCapture?: ((event: BlurEvent) => void) | undefined;
|
|
287
|
+
}
|
|
288
|
+
|
|
268
289
|
export interface TVRemoteEvent {
|
|
269
290
|
tag?: number | undefined;
|
|
270
291
|
target?: number | undefined;
|
|
@@ -282,6 +282,12 @@ export type FocusEvent = SyntheticEvent<
|
|
|
282
282
|
|}>,
|
|
283
283
|
>;
|
|
284
284
|
|
|
285
|
+
export type RemotePressEvent = SyntheticEvent<
|
|
286
|
+
$ReadOnly<{|
|
|
287
|
+
target: number,
|
|
288
|
+
|}>,
|
|
289
|
+
>;
|
|
290
|
+
|
|
285
291
|
export type MouseEvent = SyntheticEvent<
|
|
286
292
|
$ReadOnly<{|
|
|
287
293
|
clientX: number,
|
package/README.md
CHANGED
|
@@ -176,19 +176,21 @@ var running_on_apple_tv = Platform.isTVOS;
|
|
|
176
176
|
|
|
177
177
|
- _Common codebase for Android phone and Android TV_: Apps built for Android using this repo will run on both Android phone and Android TV. Most of the changes for TV are specific to handling focus-based navigation on a TV using the D-Pad on the remote control.
|
|
178
178
|
|
|
179
|
-
-
|
|
179
|
+
- _Pressable and Touchable controls_: In RNTV 0.76.1-1 and later, TV controls are supported with fully native events.
|
|
180
|
+
Code has been added to detect focus changes and use existing methods to style the components properly and initiate the proper actions when the view is selected using the TV remote, so `Pressable`, `TouchableHighlight` and `TouchableOpacity` will "just work" on both Apple TV and Android TV. In particular:
|
|
180
181
|
|
|
181
182
|
- `onFocus()` will be executed when the touchable view goes into focus
|
|
182
183
|
- `onBlur()` will be executed when the touchable view goes out of focus
|
|
183
184
|
- `onPress()` will be executed when the touchable view is actually selected by pressing the "select" button on the TV remote (center button on Apple TV remote, or center button on Android TV DPad).
|
|
184
|
-
- `
|
|
185
|
+
- `onPressIn()` will be executed when the TV remote "select" button is pressed down (center button on Apple TV remote, or center button on Android TV DPad)
|
|
186
|
+
- `onPressOut()` will be executed when the TV remote "select" button is released
|
|
187
|
+
- `onLongPress()` will be executed if the "select" button is held down for a length of time (this event is generated in the `Pressability` module, the same as for touchscreen long press events).
|
|
185
188
|
|
|
186
|
-
|
|
189
|
+
`TouchableNativeFeedback` and `TouchableWithoutFeedback` respond to press events, but do not respond to focus and blur events, and are not recommended for TV.
|
|
187
190
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
- The `focus:` pseudo class is also supported via the `onFocus()` and `onBlur()` events.
|
|
191
|
+
Because focus and blur events are now fully native core events, they will respond correctly to capturing and bubbling event handlers in `View` components. A demo of this has been added to the TVEventHandlerExample in RNTester.
|
|
192
|
+
|
|
193
|
+
- _Tailwind styles for Pressable and Touchable controls_: The above events allow RNTV to support the [`focus:` and `active:` pseudo classes for Tailwind styles](https://www.nativewind.dev/v4/core-concepts/states#hover-focus-and-active-).
|
|
192
194
|
|
|
193
195
|
- _TV remote/keyboard input_: Application code that needs to implement custom handling of TV remote events can create an instance of `TVEventHandler` and listen for these events. For a more convenient API, we provide `useTVEventHandler`.
|
|
194
196
|
|
|
@@ -119,11 +119,6 @@ static __volatile BOOL __gestureHandlersCancelTouches = YES;
|
|
|
119
119
|
pressType:UIPressTypePlayPause
|
|
120
120
|
name:RCTTVRemoteEventPlayPause];
|
|
121
121
|
|
|
122
|
-
// Select
|
|
123
|
-
[self addTapGestureRecognizerWithSelector:@selector(selectPressed:)
|
|
124
|
-
pressType:UIPressTypeSelect
|
|
125
|
-
name:RCTTVRemoteEventSelect];
|
|
126
|
-
|
|
127
122
|
// Page Up/Down
|
|
128
123
|
if (@available(tvOS 14.3, *)) {
|
|
129
124
|
[self addTapGestureRecognizerWithSelector:@selector(tappedPageUp:)
|
|
@@ -162,10 +157,6 @@ static __volatile BOOL __gestureHandlersCancelTouches = YES;
|
|
|
162
157
|
pressType:UIPressTypePlayPause
|
|
163
158
|
name:RCTTVRemoteEventLongPlayPause];
|
|
164
159
|
|
|
165
|
-
[self addLongPressGestureRecognizerWithSelector:@selector(longSelectPressed:)
|
|
166
|
-
pressType:UIPressTypeSelect
|
|
167
|
-
name:RCTTVRemoteEventLongSelect];
|
|
168
|
-
|
|
169
160
|
[self addLongPressGestureRecognizerWithSelector:@selector(longUpPressed:)
|
|
170
161
|
pressType:UIPressTypeUpArrow
|
|
171
162
|
name:RCTTVRemoteEventLongUp];
|
|
@@ -355,11 +346,6 @@ static __volatile BOOL __gestureHandlersCancelTouches = YES;
|
|
|
355
346
|
[[NSNotificationCenter defaultCenter] postNavigationPressEventWithType:RCTTVRemoteEventMenu keyAction:r.eventKeyAction tag:nil target:nil];
|
|
356
347
|
}
|
|
357
348
|
|
|
358
|
-
- (void)selectPressed:(UIGestureRecognizer *)r
|
|
359
|
-
{
|
|
360
|
-
[[NSNotificationCenter defaultCenter] postNavigationPressEventWithType:RCTTVRemoteEventSelect keyAction:r.eventKeyAction tag:nil target:nil];
|
|
361
|
-
}
|
|
362
|
-
|
|
363
349
|
- (void)longPlayPausePressed:(UIGestureRecognizer *)r
|
|
364
350
|
{
|
|
365
351
|
[[NSNotificationCenter defaultCenter] postNavigationPressEventWithType:RCTTVRemoteEventLongPlayPause keyAction:r.eventKeyAction tag:nil target:nil];
|
|
@@ -370,11 +356,6 @@ static __volatile BOOL __gestureHandlersCancelTouches = YES;
|
|
|
370
356
|
#endif
|
|
371
357
|
}
|
|
372
358
|
|
|
373
|
-
- (void)longSelectPressed:(UIGestureRecognizer *)r
|
|
374
|
-
{
|
|
375
|
-
[[NSNotificationCenter defaultCenter] postNavigationPressEventWithType:RCTTVRemoteEventLongSelect keyAction:r.eventKeyAction tag:nil target:nil];
|
|
376
|
-
}
|
|
377
|
-
|
|
378
359
|
- (void)longUpPressed:(UIGestureRecognizer *)r
|
|
379
360
|
{
|
|
380
361
|
[[NSNotificationCenter defaultCenter] postNavigationPressEventWithType:RCTTVRemoteEventLongUp keyAction:r.eventKeyAction tag:nil target:nil];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
#import <Foundation/Foundation.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
@protocol RCTTVRemoteSelectHandlerDelegate <NSObject>
|
|
7
|
+
|
|
8
|
+
- (void)animatePressIn;
|
|
9
|
+
- (void)animatePressOut;
|
|
10
|
+
|
|
11
|
+
- (void)emitPressInEvent;
|
|
12
|
+
- (void)emitPressOutEvent;
|
|
13
|
+
|
|
14
|
+
- (void)sendSelectNotification;
|
|
15
|
+
- (void)sendLongSelectBeganNotification;
|
|
16
|
+
- (void)sendLongSelectEndedNotification;
|
|
17
|
+
|
|
18
|
+
@end
|
|
19
|
+
|
|
20
|
+
@interface RCTTVRemoteSelectHandler : NSObject <UIGestureRecognizerDelegate>
|
|
21
|
+
|
|
22
|
+
- (instancetype _Nonnull )initWithView:(UIView<RCTTVRemoteSelectHandlerDelegate> * _Nonnull)view;
|
|
23
|
+
- (instancetype _Nonnull )init __attribute__((unavailable("init not available, use initWithView:")));
|
|
24
|
+
|
|
25
|
+
@end
|
|
26
|
+
|
|
27
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#import "RCTTVRemoteSelectHandler.h"
|
|
2
|
+
|
|
3
|
+
@interface RCTTVRemoteSelectHandler()
|
|
4
|
+
|
|
5
|
+
@property (nonatomic, strong) UILongPressGestureRecognizer * pressRecognizer;
|
|
6
|
+
@property (nonatomic, strong) UILongPressGestureRecognizer * longPressRecognizer;
|
|
7
|
+
|
|
8
|
+
@property (nonatomic, weak) UIView<RCTTVRemoteSelectHandlerDelegate> *view;
|
|
9
|
+
|
|
10
|
+
@end
|
|
11
|
+
|
|
12
|
+
@implementation RCTTVRemoteSelectHandler {
|
|
13
|
+
NSMutableDictionary<NSString *, UIGestureRecognizer *> *_tvRemoteGestureRecognizers;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#pragma mark -
|
|
17
|
+
#pragma mark Public methods
|
|
18
|
+
|
|
19
|
+
- (instancetype)initWithView:(UIView <RCTTVRemoteSelectHandlerDelegate> *)view
|
|
20
|
+
{
|
|
21
|
+
if ((self = [super init])) {
|
|
22
|
+
_view = view;
|
|
23
|
+
[self attachToView];
|
|
24
|
+
}
|
|
25
|
+
return self;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
- (void)dealloc
|
|
29
|
+
{
|
|
30
|
+
[self detachFromView];
|
|
31
|
+
_view = nil;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#pragma mark -
|
|
35
|
+
#pragma UIGestureRecognizerDelegate method
|
|
36
|
+
|
|
37
|
+
// Press recognizer should allow long press recognizer to work (but not the reverse)
|
|
38
|
+
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
|
39
|
+
return gestureRecognizer == _pressRecognizer;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#pragma mark -
|
|
43
|
+
#pragma mark Private methods
|
|
44
|
+
|
|
45
|
+
- (void)attachToView {
|
|
46
|
+
UILongPressGestureRecognizer *pressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlePress:)];
|
|
47
|
+
pressRecognizer.allowedPressTypes = @[ @(UIPressTypeSelect) ];
|
|
48
|
+
pressRecognizer.minimumPressDuration = 0.0;
|
|
49
|
+
pressRecognizer.delegate = self; // Press recognizer allows other recognizers to run
|
|
50
|
+
|
|
51
|
+
[self.view addGestureRecognizer:pressRecognizer];
|
|
52
|
+
self.pressRecognizer = pressRecognizer;
|
|
53
|
+
|
|
54
|
+
UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
|
|
55
|
+
longPressRecognizer.allowedPressTypes = @[ @(UIPressTypeSelect) ];
|
|
56
|
+
longPressRecognizer.minimumPressDuration = 0.5;
|
|
57
|
+
|
|
58
|
+
[self.view addGestureRecognizer:longPressRecognizer];
|
|
59
|
+
self.longPressRecognizer = longPressRecognizer;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
- (void)detachFromView {
|
|
63
|
+
if (_pressRecognizer) {
|
|
64
|
+
[self.view removeGestureRecognizer:_pressRecognizer];
|
|
65
|
+
self.pressRecognizer = nil;
|
|
66
|
+
}
|
|
67
|
+
if (_longPressRecognizer) {
|
|
68
|
+
[self.view removeGestureRecognizer:_longPressRecognizer];
|
|
69
|
+
self.longPressRecognizer = nil;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
- (void)handlePress:(UIGestureRecognizer *)r
|
|
74
|
+
{
|
|
75
|
+
switch (r.state) {
|
|
76
|
+
case UIGestureRecognizerStateBegan:
|
|
77
|
+
[self.view emitPressInEvent];
|
|
78
|
+
[self.view animatePressIn];
|
|
79
|
+
break;
|
|
80
|
+
case UIGestureRecognizerStateCancelled:
|
|
81
|
+
case UIGestureRecognizerStateEnded:
|
|
82
|
+
if (r.enabled) {
|
|
83
|
+
[self.view animatePressOut];
|
|
84
|
+
[self.view emitPressOutEvent];
|
|
85
|
+
[self.view sendSelectNotification];
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
default:
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
When a long press starts, the press recognizer has already started
|
|
95
|
+
and called selectGestureBegan(). We disable the press recognizer
|
|
96
|
+
when the long press starts. At the end of the gesture, we execute the
|
|
97
|
+
code for pressOut (since the press recognizer cannot), and then reenable
|
|
98
|
+
the press recgonizer. This guarantees
|
|
99
|
+
only one pressIn and one pressOut, and only longSelect notifications.
|
|
100
|
+
*/
|
|
101
|
+
- (void)handleLongPress:(UIGestureRecognizer *)r
|
|
102
|
+
{
|
|
103
|
+
switch (r.state) {
|
|
104
|
+
case UIGestureRecognizerStateBegan:
|
|
105
|
+
self.pressRecognizer.enabled = NO;
|
|
106
|
+
[self.view sendLongSelectBeganNotification];
|
|
107
|
+
break;
|
|
108
|
+
case UIGestureRecognizerStateEnded:
|
|
109
|
+
case UIGestureRecognizerStateCancelled:
|
|
110
|
+
[self.view animatePressOut];
|
|
111
|
+
[self.view emitPressOutEvent];
|
|
112
|
+
[self.view sendLongSelectEndedNotification];
|
|
113
|
+
self.pressRecognizer.enabled = YES;
|
|
114
|
+
break;
|
|
115
|
+
default:
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@end
|
package/React/Base/RCTVersion.m
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
#if TARGET_OS_TV
|
|
28
28
|
#import <React/RCTTVRemoteHandler.h>
|
|
29
29
|
#import <React/RCTTVNavigationEventNotification.h>
|
|
30
|
+
#import "React/RCTI18nUtil.h"
|
|
30
31
|
#endif
|
|
31
32
|
|
|
32
33
|
using namespace facebook::react;
|
|
@@ -1100,22 +1101,25 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
|
|
|
1100
1101
|
|
|
1101
1102
|
- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context
|
|
1102
1103
|
{
|
|
1104
|
+
// Determine if the layout is Right-to-Left
|
|
1105
|
+
BOOL isRTL = [[RCTI18nUtil sharedInstance] isRTL];
|
|
1103
1106
|
BOOL isHorizontal = _scrollView.contentSize.width > self.frame.size.width;
|
|
1104
|
-
//
|
|
1107
|
+
// Adjust for horizontal scrolling with RTL support
|
|
1105
1108
|
if (isHorizontal) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
+
BOOL isNavigatingToEnd = (isRTL ? context.focusHeading == UIFocusHeadingLeft : context.focusHeading == UIFocusHeadingRight);
|
|
1110
|
+
BOOL isNavigatingToStart = (isRTL ? context.focusHeading == UIFocusHeadingRight : context.focusHeading == UIFocusHeadingLeft);
|
|
1111
|
+
|
|
1112
|
+
if ((isNavigatingToEnd && self.scrollView.contentOffset.x < self.scrollView.contentSize.width - self.scrollView.visibleSize.width) ||
|
|
1113
|
+
(isNavigatingToStart && self.scrollView.contentOffset.x > 0)) {
|
|
1109
1114
|
return [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem];
|
|
1110
1115
|
}
|
|
1111
1116
|
} else {
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1117
|
+
// Handle vertical scrolling as before
|
|
1118
|
+
if ((context.focusHeading == UIFocusHeadingUp && self.scrollView.contentOffset.y > 0) ||
|
|
1119
|
+
(context.focusHeading == UIFocusHeadingDown && self.scrollView.contentOffset.y < self.scrollView.contentSize.height - self.scrollView.visibleSize.height)) {
|
|
1115
1120
|
return [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem];
|
|
1116
1121
|
}
|
|
1117
1122
|
}
|
|
1118
|
-
|
|
1119
1123
|
return [super shouldUpdateFocusInContext:context];
|
|
1120
1124
|
}
|
|
1121
1125
|
|
|
@@ -61,6 +61,13 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
|
|
61
61
|
*/
|
|
62
62
|
BOOL _comingFromJS;
|
|
63
63
|
BOOL _didMoveToWindow;
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
* Newly initialized default typing attributes contain a no-op NSParagraphStyle and NSShadow. These cause inequality
|
|
67
|
+
* between the AttributedString backing the input and those generated from state. We store these attributes to make
|
|
68
|
+
* later comparison insensitive to them.
|
|
69
|
+
*/
|
|
70
|
+
NSDictionary<NSAttributedStringKey, id> *_originalTypingAttributes;
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
#pragma mark - UIView overrides
|
|
@@ -76,6 +83,7 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
|
|
76
83
|
_ignoreNextTextInputCall = NO;
|
|
77
84
|
_comingFromJS = NO;
|
|
78
85
|
_didMoveToWindow = NO;
|
|
86
|
+
_originalTypingAttributes = [_backedTextInputView.typingAttributes copy];
|
|
79
87
|
|
|
80
88
|
[self addSubview:_backedTextInputView];
|
|
81
89
|
[self initializeReturnKeyType];
|
|
@@ -84,6 +92,20 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
|
|
84
92
|
return self;
|
|
85
93
|
}
|
|
86
94
|
|
|
95
|
+
- (void)updateEventEmitter:(const EventEmitter::Shared &)eventEmitter
|
|
96
|
+
{
|
|
97
|
+
[super updateEventEmitter:eventEmitter];
|
|
98
|
+
|
|
99
|
+
NSMutableDictionary<NSAttributedStringKey, id> *defaultAttributes =
|
|
100
|
+
[_backedTextInputView.defaultTextAttributes mutableCopy];
|
|
101
|
+
|
|
102
|
+
RCTWeakEventEmitterWrapper *eventEmitterWrapper = [RCTWeakEventEmitterWrapper new];
|
|
103
|
+
eventEmitterWrapper.eventEmitter = _eventEmitter;
|
|
104
|
+
defaultAttributes[RCTAttributedStringEventEmitterKey] = eventEmitterWrapper;
|
|
105
|
+
|
|
106
|
+
_backedTextInputView.defaultTextAttributes = defaultAttributes;
|
|
107
|
+
}
|
|
108
|
+
|
|
87
109
|
- (void)didMoveToWindow
|
|
88
110
|
{
|
|
89
111
|
[super didMoveToWindow];
|
|
@@ -236,8 +258,11 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
|
|
236
258
|
}
|
|
237
259
|
|
|
238
260
|
if (newTextInputProps.textAttributes != oldTextInputProps.textAttributes) {
|
|
239
|
-
|
|
261
|
+
NSMutableDictionary<NSAttributedStringKey, id> *defaultAttributes =
|
|
240
262
|
RCTNSTextAttributesFromTextAttributes(newTextInputProps.getEffectiveTextAttributes(RCTFontSizeMultiplier()));
|
|
263
|
+
defaultAttributes[RCTAttributedStringEventEmitterKey] =
|
|
264
|
+
_backedTextInputView.defaultTextAttributes[RCTAttributedStringEventEmitterKey];
|
|
265
|
+
_backedTextInputView.defaultTextAttributes = defaultAttributes;
|
|
241
266
|
}
|
|
242
267
|
|
|
243
268
|
if (newTextInputProps.selectionColor != oldTextInputProps.selectionColor) {
|
|
@@ -418,6 +443,7 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
|
|
418
443
|
|
|
419
444
|
- (void)textInputDidChangeSelection
|
|
420
445
|
{
|
|
446
|
+
[self _updateTypingAttributes];
|
|
421
447
|
if (_comingFromJS) {
|
|
422
448
|
return;
|
|
423
449
|
}
|
|
@@ -680,9 +706,26 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
|
|
680
706
|
[_backedTextInputView scrollRangeToVisible:NSMakeRange(offsetStart, 0)];
|
|
681
707
|
}
|
|
682
708
|
[self _restoreTextSelection];
|
|
709
|
+
[self _updateTypingAttributes];
|
|
683
710
|
_lastStringStateWasUpdatedWith = attributedString;
|
|
684
711
|
}
|
|
685
712
|
|
|
713
|
+
// Ensure that newly typed text will inherit any custom attributes. We follow the logic of RN Android, where attributes
|
|
714
|
+
// to the left of the cursor are copied into new text, unless we are at the start of the field, in which case we will
|
|
715
|
+
// copy the attributes from text to the right. This allows consistency between backed input and new AttributedText
|
|
716
|
+
// https://github.com/facebook/react-native/blob/3102a58df38d96f3dacef0530e4dbb399037fcd2/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/SetSpanOperation.kt#L30
|
|
717
|
+
- (void)_updateTypingAttributes
|
|
718
|
+
{
|
|
719
|
+
if (_backedTextInputView.attributedText.length > 0) {
|
|
720
|
+
NSUInteger offsetStart = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
|
|
721
|
+
toPosition:_backedTextInputView.selectedTextRange.start];
|
|
722
|
+
|
|
723
|
+
NSUInteger samplePoint = offsetStart == 0 ? 0 : offsetStart - 1;
|
|
724
|
+
_backedTextInputView.typingAttributes = [_backedTextInputView.attributedText attributesAtIndex:samplePoint
|
|
725
|
+
effectiveRange:NULL];
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
686
729
|
- (void)_setMultiline:(BOOL)multiline
|
|
687
730
|
{
|
|
688
731
|
[_backedTextInputView removeFromSuperview];
|
|
@@ -738,9 +781,10 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
|
|
738
781
|
_backedTextInputView.markedTextRange || _backedTextInputView.isSecureTextEntry || fontHasBeenUpdatedBySystem;
|
|
739
782
|
|
|
740
783
|
if (shouldFallbackToBareTextComparison) {
|
|
741
|
-
return
|
|
784
|
+
return [newText.string isEqualToString:oldText.string];
|
|
742
785
|
} else {
|
|
743
|
-
return (
|
|
786
|
+
return RCTIsAttributedStringEffectivelySame(
|
|
787
|
+
newText, oldText, _originalTypingAttributes, static_cast<const TextInputProps &>(*_props).textAttributes);
|
|
744
788
|
}
|
|
745
789
|
}
|
|
746
790
|
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
#import <React/RCTConstants.h>
|
|
12
12
|
#import <React/RCTTouchableComponentViewProtocol.h>
|
|
13
13
|
#import <React/UIView+ComponentViewProtocol.h>
|
|
14
|
+
#if TARGET_OS_TV
|
|
15
|
+
#import <React/RCTTVRemoteSelectHandler.h>
|
|
16
|
+
#endif
|
|
14
17
|
#import <react/renderer/components/view/ViewEventEmitter.h>
|
|
15
18
|
#import <react/renderer/components/view/ViewProps.h>
|
|
16
19
|
#import <react/renderer/core/EventEmitter.h>
|
|
@@ -25,7 +28,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
25
28
|
/**
|
|
26
29
|
* UIView class for <View> component.
|
|
27
30
|
*/
|
|
31
|
+
#if TARGET_OS_TV
|
|
32
|
+
@interface RCTViewComponentView : UIView <RCTComponentViewProtocol, RCTTouchableComponentViewProtocol, RCTTVRemoteSelectHandlerDelegate> {
|
|
33
|
+
#else
|
|
28
34
|
@interface RCTViewComponentView : UIView <RCTComponentViewProtocol, RCTTouchableComponentViewProtocol> {
|
|
35
|
+
#endif
|
|
29
36
|
@protected
|
|
30
37
|
facebook::react::LayoutMetrics _layoutMetrics;
|
|
31
38
|
facebook::react::SharedViewProps _props;
|
|
@@ -72,6 +79,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
72
79
|
@property(nonatomic, nullable) UIFocusGuide *focusGuideDown;
|
|
73
80
|
@property(nonatomic, nullable) UIFocusGuide *focusGuideLeft;
|
|
74
81
|
@property(nonatomic, nullable) UIFocusGuide *focusGuideRight;
|
|
82
|
+
@property(nonatomic, nullable, strong) RCTTVRemoteSelectHandler *tvRemoteSelectHandler;
|
|
75
83
|
#endif
|
|
76
84
|
|
|
77
85
|
/**
|