react-native-tvos 0.73.4-0rc1 → 0.73.6-1
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/Components/TV/TVFocusGuideView.js +23 -5
- package/Libraries/Core/ReactNativeVersion.js +2 -2
- package/Libraries/LogBox/Data/parseLogBoxLog.js +1 -1
- package/Libraries/Text/TextInput/RCTBaseTextInputShadowView.mm +48 -0
- package/README.md +24 -14
- package/React/Base/RCTVersion.m +2 -2
- package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +246 -0
- package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +39 -16
- package/React/React-RCTFabric.podspec +3 -2
- package/React/Views/RCTTVView.m +21 -2
- package/ReactAndroid/gradle.properties +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java +2 -2
- package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +4 -0
- package/ReactCommon/cxxreact/ReactNativeVersion.h +2 -2
- package/package.json +7 -7
- package/scripts/cocoapods/new_architecture.rb +5 -1
- package/scripts/cocoapods/utils.rb +21 -0
- package/scripts/codegen/generate-artifacts-executor.js +18 -11
- package/scripts/react_native_pods.rb +1 -0
- package/scripts/xcode/with-environment.sh +1 -1
- package/sdks/.hermesversion +1 -1
- package/sdks/hermes-engine/hermes-utils.rb +4 -3
- package/sdks/hermesc/linux64-bin/hermesc +0 -0
- 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/sdks/hermesc/win64-bin/msvcp140.dll +0 -0
- package/sdks/hermesc/win64-bin/vcruntime140.dll +0 -0
- package/sdks/hermesc/win64-bin/vcruntime140_1.dll +0 -0
- package/template/package.json +1 -1
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
* @format
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const React = require('react');
|
|
12
|
-
const ReactNative = require('react-native');
|
|
13
|
-
|
|
14
|
-
import {Commands} from '../View/ViewNativeComponent';
|
|
15
|
-
import type {ViewProps} from '../View/ViewPropTypes';
|
|
16
11
|
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
|
|
12
|
+
import type {ViewProps} from '../View/ViewPropTypes';
|
|
13
|
+
|
|
17
14
|
import setAndForwardRef from '../../Utilities/setAndForwardRef';
|
|
15
|
+
import {Commands} from '../View/ViewNativeComponent';
|
|
16
|
+
|
|
17
|
+
const React = require('react');
|
|
18
|
+
const ReactNative = require('react-native');
|
|
18
19
|
|
|
19
20
|
const {View} = ReactNative;
|
|
20
21
|
|
|
@@ -44,6 +45,11 @@ type TVFocusGuideViewProps = $ReadOnly<{
|
|
|
44
45
|
trapFocusDown?: boolean,
|
|
45
46
|
trapFocusLeft?: boolean,
|
|
46
47
|
trapFocusRight?: boolean,
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* When set to false, this view and all its subviews will be NOT focusable.
|
|
51
|
+
*/
|
|
52
|
+
focusable?: boolean | undefined,
|
|
47
53
|
}>;
|
|
48
54
|
|
|
49
55
|
export type TVFocusGuideViewImperativeMethods = $ReadOnly<{
|
|
@@ -57,6 +63,8 @@ function TVFocusGuideView(
|
|
|
57
63
|
enabled = true,
|
|
58
64
|
safePadding,
|
|
59
65
|
destinations: destinationsProp,
|
|
66
|
+
autoFocus,
|
|
67
|
+
focusable,
|
|
60
68
|
...props
|
|
61
69
|
}: TVFocusGuideViewProps,
|
|
62
70
|
forwardedRef,
|
|
@@ -106,6 +114,11 @@ function TVFocusGuideView(
|
|
|
106
114
|
const enabledStyle = {display: enabled ? 'flex' : 'none'};
|
|
107
115
|
const style = [styles.container, props.style, enabledStyle];
|
|
108
116
|
|
|
117
|
+
// If there are no destinations and the autoFocus is false the the default value of focusable should be false
|
|
118
|
+
// It is then properly handled by the native code
|
|
119
|
+
const tvOSSelectable =
|
|
120
|
+
destinationsProp || autoFocus ? focusable !== false : false;
|
|
121
|
+
|
|
109
122
|
return (
|
|
110
123
|
// $FlowFixMe[prop-missing]
|
|
111
124
|
<ReactNative.View
|
|
@@ -113,6 +126,11 @@ function TVFocusGuideView(
|
|
|
113
126
|
style={style}
|
|
114
127
|
ref={_setNativeRef}
|
|
115
128
|
collapsable={false}
|
|
129
|
+
autoFocus={autoFocus}
|
|
130
|
+
// tvOS only prop
|
|
131
|
+
isTVSelectable={tvOSSelectable}
|
|
132
|
+
// Android TV only prop
|
|
133
|
+
tvFocusable={focusable}
|
|
116
134
|
/>
|
|
117
135
|
);
|
|
118
136
|
}
|
|
@@ -192,7 +192,7 @@ export function parseComponentStack(message: string): ComponentStack {
|
|
|
192
192
|
if (!s) {
|
|
193
193
|
return null;
|
|
194
194
|
}
|
|
195
|
-
const match = s.match(/(.*) \(at (.*\.js):([\d]+)\)/);
|
|
195
|
+
const match = s.match(/(.*) \(at (.*\.(?:js|jsx|ts|tsx)):([\d]+)\)/);
|
|
196
196
|
if (!match) {
|
|
197
197
|
return null;
|
|
198
198
|
}
|
|
@@ -158,6 +158,8 @@
|
|
|
158
158
|
[attributedText insertAttributedString:propertyAttributedText atIndex:0];
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
[self postprocessAttributedText:attributedText];
|
|
162
|
+
|
|
161
163
|
NSAttributedString *newAttributedText;
|
|
162
164
|
if (![_previousAttributedText isEqualToAttributedString:attributedText]) {
|
|
163
165
|
// We have to follow `set prop` pattern:
|
|
@@ -191,6 +193,52 @@
|
|
|
191
193
|
}];
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
- (void)postprocessAttributedText:(NSMutableAttributedString *)attributedText
|
|
197
|
+
{
|
|
198
|
+
__block CGFloat maximumLineHeight = 0;
|
|
199
|
+
|
|
200
|
+
[attributedText enumerateAttribute:NSParagraphStyleAttributeName
|
|
201
|
+
inRange:NSMakeRange(0, attributedText.length)
|
|
202
|
+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
|
203
|
+
usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) {
|
|
204
|
+
if (!paragraphStyle) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight);
|
|
209
|
+
}];
|
|
210
|
+
|
|
211
|
+
if (maximumLineHeight == 0) {
|
|
212
|
+
// `lineHeight` was not specified, nothing to do.
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
__block CGFloat maximumFontLineHeight = 0;
|
|
217
|
+
|
|
218
|
+
[attributedText enumerateAttribute:NSFontAttributeName
|
|
219
|
+
inRange:NSMakeRange(0, attributedText.length)
|
|
220
|
+
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
|
|
221
|
+
usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) {
|
|
222
|
+
if (!font) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (maximumFontLineHeight <= font.lineHeight) {
|
|
227
|
+
maximumFontLineHeight = font.lineHeight;
|
|
228
|
+
}
|
|
229
|
+
}];
|
|
230
|
+
|
|
231
|
+
if (maximumLineHeight < maximumFontLineHeight) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
CGFloat baseLineOffset = maximumLineHeight / 2.0 - maximumFontLineHeight / 2.0;
|
|
236
|
+
|
|
237
|
+
[attributedText addAttribute:NSBaselineOffsetAttributeName
|
|
238
|
+
value:@(baseLineOffset)
|
|
239
|
+
range:NSMakeRange(0, attributedText.length)];
|
|
240
|
+
}
|
|
241
|
+
|
|
194
242
|
#pragma mark -
|
|
195
243
|
|
|
196
244
|
- (NSAttributedString *)measurableAttributedText
|
package/README.md
CHANGED
|
@@ -59,12 +59,12 @@ yarn create react-native-app TestApp --template https://github.com/react-native-
|
|
|
59
59
|
cd TestApp
|
|
60
60
|
# Now build and start the app in the tvOS Simulator - this will only work on a macOS machine.
|
|
61
61
|
# This command can also be run via "yarn tvos".
|
|
62
|
-
npx expo run:ios --scheme
|
|
62
|
+
npx expo run:ios --scheme TestApp-tvOS --device "Apple TV"
|
|
63
63
|
# You can also build and start the app on an iOS phone simulator.
|
|
64
64
|
# This command can also be run via "yarn ios".
|
|
65
65
|
npx expo run:ios
|
|
66
66
|
# or specify a simulator:
|
|
67
|
-
npx expo run:ios --scheme
|
|
67
|
+
npx expo run:ios --scheme TestApp --device "iPhone 15"
|
|
68
68
|
# This command builds and starts the app in an Android TV emulator or a phone emulator (needs to be created in advance).
|
|
69
69
|
# This command can also be run via "yarn android".
|
|
70
70
|
npx expo run:android --device tv_api_31
|
|
@@ -204,15 +204,25 @@ class Game2048 extends React.Component {
|
|
|
204
204
|
|
|
205
205
|
- _TVFocusGuideView_: This component provides support for Apple's `UIFocusGuide` API and is implemented in the same way for Android TV, to help ensure that focusable controls can be navigated to, even if they are not directly in line with other controls. An example is provided in `RNTester` that shows two different ways of using this component.
|
|
206
206
|
|
|
207
|
-
| Prop | Value | Description |
|
|
208
|
-
|---|---|---|
|
|
209
|
-
| destinations | any[]? | Array of `Component`s to register as destinations of the FocusGuideView |
|
|
210
|
-
| autoFocus | boolean? | If true, `TVFocusGuide` will automatically manage focus for you. It will redirect the focus to the first focusable child on the first visit. It also remembers the last focused child and redirects the focus to it on the subsequent visits. `destinations` prop takes precedence over this prop when used together. |
|
|
211
|
-
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
207
|
+
| Prop | Value | Description |
|
|
208
|
+
|---|---|---|
|
|
209
|
+
| destinations | any[]? | Array of `Component`s to register as destinations of the FocusGuideView |
|
|
210
|
+
| autoFocus | boolean? | If true, `TVFocusGuide` will automatically manage focus for you. It will redirect the focus to the first focusable child on the first visit. It also remembers the last focused child and redirects the focus to it on the subsequent visits. `destinations` prop takes precedence over this prop when used together. |
|
|
211
|
+
| focusable | boolean? | When set to false, this view and all its subviews will be NOT focusable. |
|
|
212
|
+
| trapFocus* (Up, Down, Left, Right) | Prevents focus escaping from the container for the given directions. |
|
|
213
|
+
|
|
214
|
+
More information on the focus handling improvements above can be found in [this article](https://medium.com/xite-engineering/revolutionizing-focus-management-in-tv-applications-with-react-native-10ba69bd90).
|
|
215
|
+
|
|
216
|
+
- _Next Focus Direction_: the props `nextFocus*` on `View` should work as expected on iOS too (previously android only). One caveat is that if there is no focusable in the `nextFocusable*` direction next to the starting view, iOS doesn't check if we want to override the destination.
|
|
217
|
+
|
|
218
|
+
- _TVTextScrollView_: On Apple TV, a ScrollView will not scroll unless there are focusable items inside it or above/below it. This component wraps ScrollView and uses tvOS-specific native code to allow scrolling using swipe gestures from the remote control.
|
|
219
|
+
|
|
220
|
+
- VirtualizedList: We extend `VirtualizedList` to make virtualization work well with focus management in mind. All of the improvements that we made are automatically available to all the VirtualizedList based components such as `FlatList`.
|
|
221
|
+
- Defaults
|
|
222
|
+
- VirtualizeList contents are automatically wrapped with a `TVFocusGuideView` with `trapFocus*` properties enabled depending on the orientation of the list. This default makes sure that focus doesn't leave the list accidentally due to a virtualization issue etc. until reaching the beginning or the end of the list.
|
|
223
|
+
|
|
224
|
+
New Props:
|
|
225
|
+
|
|
226
|
+
| Prop | Value | Description |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| additionalRenderRegions | {first: number; last: number;}[]? | Array of `RenderRegions` that allows you to define regions in the list that are not subject to virtualization, ensuring they are always rendered. This is particularly useful for preventing blank areas in critical parts of the list. These regions are rendered lazily after the initial render and are specified as an array of objects, each with `first` and `last` indices marking the beginning and end of the non-virtualized region based on index. See the [feature proposal](https://github.com/react-native-tvos/react-native-tvos/discussions/663) for more context. |
|
package/React/Base/RCTVersion.m
CHANGED
|
@@ -23,8 +23,8 @@ NSDictionary* RCTGetReactNativeVersion(void)
|
|
|
23
23
|
__rnVersion = @{
|
|
24
24
|
RCTVersionMajor: @(0),
|
|
25
25
|
RCTVersionMinor: @(73),
|
|
26
|
-
RCTVersionPatch: @(
|
|
27
|
-
RCTVersionPrerelease: @"
|
|
26
|
+
RCTVersionPatch: @(6),
|
|
27
|
+
RCTVersionPrerelease: @"1",
|
|
28
28
|
};
|
|
29
29
|
});
|
|
30
30
|
return __rnVersion;
|
|
@@ -24,9 +24,14 @@
|
|
|
24
24
|
#import "RCTEnhancedScrollView.h"
|
|
25
25
|
#import "RCTFabricComponentsPlugins.h"
|
|
26
26
|
|
|
27
|
+
#if TARGET_OS_TV
|
|
28
|
+
#import "RCTTVRemoteHandler.h"
|
|
29
|
+
#endif
|
|
30
|
+
|
|
27
31
|
using namespace facebook::react;
|
|
28
32
|
|
|
29
33
|
static const CGFloat kClippingLeeway = 44.0;
|
|
34
|
+
static const float TV_DEFAULT_SWIPE_DURATION = 0.3;
|
|
30
35
|
|
|
31
36
|
static UIScrollViewKeyboardDismissMode RCTUIKeyboardDismissModeFromProps(const ScrollViewProps &props)
|
|
32
37
|
{
|
|
@@ -105,6 +110,9 @@ static void RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrol
|
|
|
105
110
|
|
|
106
111
|
CGRect _prevFirstVisibleFrame;
|
|
107
112
|
__weak UIView *_firstVisibleView;
|
|
113
|
+
|
|
114
|
+
BOOL _blockFirstTouch;
|
|
115
|
+
NSMutableDictionary *_tvRemoteGestureRecognizers;
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
+ (RCTScrollViewComponentView *_Nullable)findScrollViewComponentViewForView:(UIView *)view
|
|
@@ -139,6 +147,8 @@ static void RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrol
|
|
|
139
147
|
} else {
|
|
140
148
|
_scrollEventThrottle = INFINITY;
|
|
141
149
|
}
|
|
150
|
+
|
|
151
|
+
_tvRemoteGestureRecognizers = [NSMutableDictionary new];
|
|
142
152
|
}
|
|
143
153
|
|
|
144
154
|
return self;
|
|
@@ -792,6 +802,242 @@ static void RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrol
|
|
|
792
802
|
}
|
|
793
803
|
}
|
|
794
804
|
|
|
805
|
+
#pragma mark Apple TV swipe and focus handling
|
|
806
|
+
|
|
807
|
+
#if TARGET_OS_TV
|
|
808
|
+
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context
|
|
809
|
+
withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
|
|
810
|
+
{
|
|
811
|
+
if (context.previouslyFocusedView == context.nextFocusedView || !_props->isTVSelectable) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
if (context.nextFocusedView == self) {
|
|
815
|
+
[self becomeFirstResponder];
|
|
816
|
+
[self addSwipeGestureRecognizers];
|
|
817
|
+
[self sendFocusNotification];
|
|
818
|
+
// if we enter the scroll view from different view then block first touch event since it is the event that triggered the focus
|
|
819
|
+
_blockFirstTouch = (unsigned long)context.focusHeading != 0;
|
|
820
|
+
[self addArrowsListeners];
|
|
821
|
+
} else if (context.previouslyFocusedView == self) {
|
|
822
|
+
[self removeArrowsListeners];
|
|
823
|
+
[self sendBlurNotification];
|
|
824
|
+
[self removeSwipeGestureRecognizers];
|
|
825
|
+
[self resignFirstResponder];
|
|
826
|
+
// if we leave the scroll view and go up, then scroll to top; if going down,
|
|
827
|
+
// scroll to bottom
|
|
828
|
+
// Similarly for left and right
|
|
829
|
+
RCTEnhancedScrollView *scrollView = (RCTEnhancedScrollView *)_scrollView;
|
|
830
|
+
if (context.focusHeading == UIFocusHeadingUp && scrollView.snapToStart) {
|
|
831
|
+
[self swipeVerticalScrollToOffset:0.0];
|
|
832
|
+
} else if(context.focusHeading == UIFocusHeadingDown && scrollView.snapToEnd) {
|
|
833
|
+
[self swipeVerticalScrollToOffset:scrollView.contentSize.height];
|
|
834
|
+
} else if(context.focusHeading == UIFocusHeadingLeft && scrollView.snapToStart) {
|
|
835
|
+
[self swipeHorizontalScrollToOffset:0.0];
|
|
836
|
+
} else if(context.focusHeading == UIFocusHeadingRight && scrollView.snapToEnd) {
|
|
837
|
+
[self swipeHorizontalScrollToOffset:scrollView.contentSize.width];
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
- (void)addArrowsListeners
|
|
843
|
+
{
|
|
844
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
845
|
+
selector:@selector(handleTVNavigationEventNotification:)
|
|
846
|
+
name:@"RCTTVNavigationEventNotification"
|
|
847
|
+
object:nil];
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
- (void)removeArrowsListeners
|
|
851
|
+
{
|
|
852
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self
|
|
853
|
+
name:@"RCTTVNavigationEventNotification"
|
|
854
|
+
object:nil];
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
- (void)handleTVNavigationEventNotification:(NSNotification *)notif
|
|
858
|
+
{
|
|
859
|
+
NSArray *supportedEvents = [NSArray arrayWithObjects:@"up", @"down", @"left", @"right", nil];
|
|
860
|
+
|
|
861
|
+
if (notif.object == nil || notif.object[@"eventType"] == nil || ![supportedEvents containsObject:notif.object[@"eventType"]] ) {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if (_blockFirstTouch) {
|
|
866
|
+
_blockFirstTouch = NO;
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
BOOL isHorizontal = _scrollView.contentSize.width > self.frame.size.width;
|
|
871
|
+
if (!isHorizontal) {
|
|
872
|
+
if ([notif.object[@"eventType"] isEqual: @"down"]) {
|
|
873
|
+
[self swipedDown];
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if ([notif.object[@"eventType"] isEqual: @"up"]) {
|
|
878
|
+
[self swipedUp];
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if ([notif.object[@"eventType"] isEqual: @"left"]) {
|
|
884
|
+
[self swipedLeft];
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if ([notif.object[@"eventType"] isEqual: @"right"]) {
|
|
889
|
+
[self swipedRight];
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context
|
|
895
|
+
{
|
|
896
|
+
BOOL isHorizontal = _scrollView.contentSize.width > self.frame.size.width;
|
|
897
|
+
// Keep focus inside the scroll view till the end of the content
|
|
898
|
+
if (isHorizontal) {
|
|
899
|
+
if ((context.focusHeading == UIFocusHeadingLeft && self.scrollView.contentOffset.x > 0)
|
|
900
|
+
|| (context.focusHeading == UIFocusHeadingRight && self.scrollView.contentOffset.x < self.scrollView.contentSize.width - self.scrollView.visibleSize.width)
|
|
901
|
+
) {
|
|
902
|
+
return [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem];
|
|
903
|
+
}
|
|
904
|
+
} else {
|
|
905
|
+
if ((context.focusHeading == UIFocusHeadingUp && self.scrollView.contentOffset.y > 0)
|
|
906
|
+
|| (context.focusHeading == UIFocusHeadingDown && self.scrollView.contentOffset.y < self.scrollView.contentSize.height - self.scrollView.visibleSize.height)
|
|
907
|
+
) {
|
|
908
|
+
return [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem];
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
return [super shouldUpdateFocusInContext:context];
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
- (void)sendFocusNotification
|
|
916
|
+
{
|
|
917
|
+
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTTVNavigationEventNotification"
|
|
918
|
+
object:@{ @"eventType": @"focus", @"tag": @([self tag]) }];
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
- (void)sendBlurNotification
|
|
922
|
+
{
|
|
923
|
+
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTTVNavigationEventNotification"
|
|
924
|
+
object:@{ @"eventType": @"blur", @"tag": @([self tag]) }];
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
- (NSInteger)swipeVerticalInterval
|
|
928
|
+
{
|
|
929
|
+
RCTEnhancedScrollView *scrollView = (RCTEnhancedScrollView *)_scrollView;
|
|
930
|
+
if (scrollView.snapToInterval) {
|
|
931
|
+
return scrollView.snapToInterval;
|
|
932
|
+
}
|
|
933
|
+
return scrollView.visibleSize.height / 2;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
- (NSInteger)swipeHorizontalInterval
|
|
937
|
+
{
|
|
938
|
+
RCTEnhancedScrollView *scrollView = (RCTEnhancedScrollView *)_scrollView;
|
|
939
|
+
if (scrollView.snapToInterval) {
|
|
940
|
+
return scrollView.snapToInterval;
|
|
941
|
+
}
|
|
942
|
+
return scrollView.visibleSize.width / 2;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
- (NSTimeInterval)swipeDuration
|
|
946
|
+
{
|
|
947
|
+
auto pressDuration = _props->tvParallaxProperties.pressDuration;
|
|
948
|
+
auto duration = pressDuration.has_value() ? pressDuration.value() : TV_DEFAULT_SWIPE_DURATION;
|
|
949
|
+
if (duration == 0.0) {
|
|
950
|
+
duration = TV_DEFAULT_SWIPE_DURATION;
|
|
951
|
+
}
|
|
952
|
+
return duration;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
- (void)swipeVerticalScrollToOffset:(CGFloat)yOffset
|
|
956
|
+
{
|
|
957
|
+
_blockFirstTouch = NO;
|
|
958
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
959
|
+
CGFloat limitedOffset = yOffset;
|
|
960
|
+
limitedOffset = MAX(limitedOffset, 0.0);
|
|
961
|
+
limitedOffset = MIN(limitedOffset, self.scrollView.contentSize.height - self.scrollView.visibleSize.height);
|
|
962
|
+
[UIView animateWithDuration:[self swipeDuration] animations:^{
|
|
963
|
+
self.scrollView.contentOffset =
|
|
964
|
+
CGPointMake(self.scrollView.contentOffset.x, limitedOffset);
|
|
965
|
+
}];
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
- (void)swipeHorizontalScrollToOffset:(CGFloat)xOffset
|
|
970
|
+
{
|
|
971
|
+
_blockFirstTouch = NO;
|
|
972
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
973
|
+
CGFloat limitedOffset = xOffset;
|
|
974
|
+
limitedOffset = MAX(limitedOffset, 0.0);
|
|
975
|
+
limitedOffset = MIN(limitedOffset, self.scrollView.contentSize.width - self.scrollView.visibleSize.width);
|
|
976
|
+
[UIView animateWithDuration:[self swipeDuration] animations:^{
|
|
977
|
+
self.scrollView.contentOffset =
|
|
978
|
+
CGPointMake(limitedOffset, self.scrollView.contentOffset.y);
|
|
979
|
+
}];
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
- (void)swipedUp
|
|
984
|
+
{
|
|
985
|
+
CGFloat newOffset = self.scrollView.contentOffset.y - [self swipeVerticalInterval];
|
|
986
|
+
NSLog(@"Swiped up to %f", newOffset);
|
|
987
|
+
[self swipeVerticalScrollToOffset:newOffset];
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
- (void)swipedDown
|
|
991
|
+
{
|
|
992
|
+
CGFloat newOffset = self.scrollView.contentOffset.y + [self swipeVerticalInterval];
|
|
993
|
+
NSLog(@"Swiped down to %f", newOffset);
|
|
994
|
+
[self swipeVerticalScrollToOffset:newOffset];
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
- (void)swipedLeft
|
|
998
|
+
{
|
|
999
|
+
CGFloat newOffset = self.scrollView.contentOffset.x - [self swipeHorizontalInterval];
|
|
1000
|
+
NSLog(@"Swiped left to %f", newOffset);
|
|
1001
|
+
[self swipeHorizontalScrollToOffset:newOffset];
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
- (void)swipedRight
|
|
1005
|
+
{
|
|
1006
|
+
CGFloat newOffset = self.scrollView.contentOffset.x + [self swipeHorizontalInterval];
|
|
1007
|
+
NSLog(@"Swiped right to %f", newOffset);
|
|
1008
|
+
[self swipeHorizontalScrollToOffset:newOffset];
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
- (void)addSwipeGestureRecognizers
|
|
1012
|
+
{
|
|
1013
|
+
[self addSwipeGestureRecognizerWithSelector:@selector(swipedUp) direction:UISwipeGestureRecognizerDirectionUp name:RCTTVRemoteEventSwipeUp];
|
|
1014
|
+
[self addSwipeGestureRecognizerWithSelector:@selector(swipedDown) direction:UISwipeGestureRecognizerDirectionDown name:RCTTVRemoteEventSwipeDown];
|
|
1015
|
+
[self addSwipeGestureRecognizerWithSelector:@selector(swipedLeft) direction:UISwipeGestureRecognizerDirectionLeft name:RCTTVRemoteEventSwipeLeft];
|
|
1016
|
+
[self addSwipeGestureRecognizerWithSelector:@selector(swipedRight) direction:UISwipeGestureRecognizerDirectionRight name:RCTTVRemoteEventSwipeRight];
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
- (void)addSwipeGestureRecognizerWithSelector:(nonnull SEL)selector
|
|
1020
|
+
direction:(UISwipeGestureRecognizerDirection)direction
|
|
1021
|
+
name:(NSString *)name
|
|
1022
|
+
{
|
|
1023
|
+
UISwipeGestureRecognizer *recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:selector];
|
|
1024
|
+
recognizer.direction = direction;
|
|
1025
|
+
|
|
1026
|
+
_tvRemoteGestureRecognizers[name] = recognizer;
|
|
1027
|
+
[self.scrollView addGestureRecognizer:recognizer];
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
- (void)removeSwipeGestureRecognizers
|
|
1031
|
+
{
|
|
1032
|
+
NSArray *names = [self->_tvRemoteGestureRecognizers allKeys];
|
|
1033
|
+
for (NSString *name in names) {
|
|
1034
|
+
UIGestureRecognizer *r = self->_tvRemoteGestureRecognizers[name];
|
|
1035
|
+
[self.scrollView removeGestureRecognizer:r];
|
|
1036
|
+
[self->_tvRemoteGestureRecognizers removeObjectForKey:name];
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
#endif // TARGET_OS_TV
|
|
1040
|
+
|
|
795
1041
|
@end
|
|
796
1042
|
|
|
797
1043
|
Class<RCTComponentViewProtocol> RCTScrollViewCls(void)
|
|
@@ -45,7 +45,7 @@ using namespace facebook::react;
|
|
|
45
45
|
|
|
46
46
|
@implementation RCTViewComponentView {
|
|
47
47
|
UIColor *_backgroundColor;
|
|
48
|
-
CALayer *_borderLayer;
|
|
48
|
+
__weak CALayer *_borderLayer;
|
|
49
49
|
BOOL _needsInvalidateLayer;
|
|
50
50
|
BOOL _isJSResponder;
|
|
51
51
|
BOOL _removeClippedSubviews;
|
|
@@ -415,13 +415,29 @@ using namespace facebook::react;
|
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
|
|
418
|
+
- (BOOL)isTVFocusGuide
|
|
419
|
+
{
|
|
420
|
+
#if TARGET_OS_TV
|
|
421
|
+
return self.focusGuide != nil;
|
|
422
|
+
#endif
|
|
423
|
+
|
|
424
|
+
return NO;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
|
|
418
428
|
- (BOOL)isUserInteractionEnabled
|
|
419
429
|
{
|
|
420
|
-
|
|
430
|
+
if ([self isTVFocusGuide]) {
|
|
431
|
+
return _props->isTVSelectable;
|
|
432
|
+
}
|
|
433
|
+
return YES;
|
|
421
434
|
}
|
|
422
435
|
|
|
423
436
|
- (BOOL)canBecomeFocused
|
|
424
437
|
{
|
|
438
|
+
if ([self isTVFocusGuide]) {
|
|
439
|
+
return NO;
|
|
440
|
+
}
|
|
425
441
|
return _props->isTVSelectable;
|
|
426
442
|
}
|
|
427
443
|
|
|
@@ -433,6 +449,9 @@ using namespace facebook::react;
|
|
|
433
449
|
//
|
|
434
450
|
- (void)enableDirectionalFocusGuides
|
|
435
451
|
{
|
|
452
|
+
if (!self.isFocused) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
436
455
|
if (self->_nextFocusUp != nil) {
|
|
437
456
|
if (self.focusGuideUp == nil) {
|
|
438
457
|
self.focusGuideUp = [UIFocusGuide new];
|
|
@@ -545,7 +564,7 @@ using namespace facebook::react;
|
|
|
545
564
|
[self handleFocusGuide];
|
|
546
565
|
}
|
|
547
566
|
|
|
548
|
-
if (context.nextFocusedView == self && self.isUserInteractionEnabled ) {
|
|
567
|
+
if (context.nextFocusedView == self && self.isUserInteractionEnabled && ![self isTVFocusGuide]) {
|
|
549
568
|
[self becomeFirstResponder];
|
|
550
569
|
[self enableDirectionalFocusGuides];
|
|
551
570
|
[coordinator addCoordinatedAnimations:^(void){
|
|
@@ -895,7 +914,7 @@ using namespace facebook::react;
|
|
|
895
914
|
#if TARGET_OS_TV
|
|
896
915
|
// `isTVSelectable`
|
|
897
916
|
if (oldViewProps.isTVSelectable != newViewProps.isTVSelectable) {
|
|
898
|
-
if (newViewProps.isTVSelectable) {
|
|
917
|
+
if (newViewProps.isTVSelectable && ![self isTVFocusGuide]) {
|
|
899
918
|
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
|
|
900
919
|
action:@selector(handleSelect:)];
|
|
901
920
|
recognizer.allowedPressTypes = @[ @(UIPressTypeSelect) ];
|
|
@@ -942,6 +961,7 @@ using namespace facebook::react;
|
|
|
942
961
|
if (newViewProps.nextFocusUp.has_value()) {
|
|
943
962
|
UIView *rootView = [self containingRootView];
|
|
944
963
|
_nextFocusUp = [rootView viewWithTag:newViewProps.nextFocusUp.value()];
|
|
964
|
+
[self enableDirectionalFocusGuides];
|
|
945
965
|
} else {
|
|
946
966
|
_nextFocusUp = nil;
|
|
947
967
|
}
|
|
@@ -951,6 +971,7 @@ using namespace facebook::react;
|
|
|
951
971
|
if (newViewProps.nextFocusDown.has_value()) {
|
|
952
972
|
UIView *rootView = [self containingRootView];
|
|
953
973
|
_nextFocusDown = [rootView viewWithTag:newViewProps.nextFocusDown.value()];
|
|
974
|
+
[self enableDirectionalFocusGuides];
|
|
954
975
|
} else {
|
|
955
976
|
_nextFocusDown = nil;
|
|
956
977
|
}
|
|
@@ -960,6 +981,7 @@ using namespace facebook::react;
|
|
|
960
981
|
if (newViewProps.nextFocusLeft.has_value()) {
|
|
961
982
|
UIView *rootView = [self containingRootView];
|
|
962
983
|
_nextFocusLeft = [rootView viewWithTag:newViewProps.nextFocusLeft.value()];
|
|
984
|
+
[self enableDirectionalFocusGuides];
|
|
963
985
|
} else {
|
|
964
986
|
_nextFocusLeft = nil;
|
|
965
987
|
}
|
|
@@ -969,6 +991,7 @@ using namespace facebook::react;
|
|
|
969
991
|
if (newViewProps.nextFocusRight.has_value()) {
|
|
970
992
|
UIView *rootView = [self containingRootView];
|
|
971
993
|
_nextFocusRight = [rootView viewWithTag:newViewProps.nextFocusRight.value()];
|
|
994
|
+
[self enableDirectionalFocusGuides];
|
|
972
995
|
} else {
|
|
973
996
|
_nextFocusRight = nil;
|
|
974
997
|
}
|
|
@@ -1013,9 +1036,7 @@ using namespace facebook::react;
|
|
|
1013
1036
|
_layoutMetrics = layoutMetrics;
|
|
1014
1037
|
_needsInvalidateLayer = YES;
|
|
1015
1038
|
|
|
1016
|
-
|
|
1017
|
-
_borderLayer.frame = self.layer.bounds;
|
|
1018
|
-
}
|
|
1039
|
+
_borderLayer.frame = self.layer.bounds;
|
|
1019
1040
|
|
|
1020
1041
|
if (_contentView) {
|
|
1021
1042
|
_contentView.frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
|
|
@@ -1232,10 +1253,7 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
|
|
|
1232
1253
|
|
|
1233
1254
|
if (useCoreAnimationBorderRendering) {
|
|
1234
1255
|
layer.mask = nil;
|
|
1235
|
-
|
|
1236
|
-
[_borderLayer removeFromSuperlayer];
|
|
1237
|
-
_borderLayer = nil;
|
|
1238
|
-
}
|
|
1256
|
+
[_borderLayer removeFromSuperlayer];
|
|
1239
1257
|
|
|
1240
1258
|
layer.borderWidth = (CGFloat)borderMetrics.borderWidths.left;
|
|
1241
1259
|
CGColorRef borderColor = RCTCreateCGColorRefFromSharedColor(borderMetrics.borderColors.left);
|
|
@@ -1248,11 +1266,12 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
|
|
|
1248
1266
|
layer.backgroundColor = _backgroundColor.CGColor;
|
|
1249
1267
|
} else {
|
|
1250
1268
|
if (!_borderLayer) {
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
[layer addSublayer:
|
|
1269
|
+
CALayer *borderLayer = [CALayer new];
|
|
1270
|
+
borderLayer.zPosition = -1024.0f;
|
|
1271
|
+
borderLayer.frame = layer.bounds;
|
|
1272
|
+
borderLayer.magnificationFilter = kCAFilterNearest;
|
|
1273
|
+
[layer addSublayer:borderLayer];
|
|
1274
|
+
_borderLayer = borderLayer;
|
|
1256
1275
|
}
|
|
1257
1276
|
|
|
1258
1277
|
layer.backgroundColor = nil;
|
|
@@ -1293,6 +1312,10 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
|
|
|
1293
1312
|
}
|
|
1294
1313
|
}
|
|
1295
1314
|
|
|
1315
|
+
// If mutations are applied inside of Animation block, it may cause _borderLayer to be animated.
|
|
1316
|
+
// To stop that, imperatively remove all animations from _borderLayer.
|
|
1317
|
+
[_borderLayer removeAllAnimations];
|
|
1318
|
+
|
|
1296
1319
|
// Stage 2.5. Custom Clipping Mask
|
|
1297
1320
|
CAShapeLayer *maskLayer = nil;
|
|
1298
1321
|
CGFloat cornerRadius = 0;
|
|
@@ -20,6 +20,7 @@ folly_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_C
|
|
|
20
20
|
folly_compiler_flags = folly_flags + ' ' + '-Wno-comma -Wno-shorten-64-to-32'
|
|
21
21
|
folly_version = '2022.05.16.00'
|
|
22
22
|
boost_compiler_flags = '-Wno-documentation'
|
|
23
|
+
new_arch_flags = ENV['RCT_NEW_ARCH_ENABLED'] == '1' ? ' -DRCT_NEW_ARCH_ENABLED=1' : ''
|
|
23
24
|
|
|
24
25
|
header_search_paths = [
|
|
25
26
|
"\"$(PODS_TARGET_SRCROOT)/ReactCommon\"",
|
|
@@ -56,13 +57,13 @@ Pod::Spec.new do |s|
|
|
|
56
57
|
"Fabric/**/RCTSurfacePointerHandler.mm"
|
|
57
58
|
s.ios.exclude_files = "Fabric/**/RCTSurfaceTouchHandlerTV.mm",
|
|
58
59
|
"Fabric/**/RCTSurfacePointerHandlerTV.mm"
|
|
59
|
-
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
|
|
60
|
+
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags + new_arch_flags
|
|
60
61
|
s.header_dir = header_dir
|
|
61
62
|
s.module_name = module_name
|
|
62
63
|
s.framework = ["JavaScriptCore", "MobileCoreServices"]
|
|
63
64
|
s.pod_target_xcconfig = {
|
|
64
65
|
"HEADER_SEARCH_PATHS" => header_search_paths,
|
|
65
|
-
"OTHER_CFLAGS" => "$(inherited) -DRN_FABRIC_ENABLED" + " " + folly_flags,
|
|
66
|
+
"OTHER_CFLAGS" => "$(inherited) -DRN_FABRIC_ENABLED" + " " + folly_flags + new_arch_flags,
|
|
66
67
|
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20"
|
|
67
68
|
}.merge!(ENV['USE_FRAMEWORKS'] != nil ? {
|
|
68
69
|
"PUBLIC_HEADERS_FOLDER_PATH" => "#{module_name}.framework/Headers/#{header_dir}"
|
package/React/Views/RCTTVView.m
CHANGED
|
@@ -75,7 +75,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
|
|
|
75
75
|
- (void)setIsTVSelectable:(BOOL)isTVSelectable
|
|
76
76
|
{
|
|
77
77
|
self->_isTVSelectable = isTVSelectable;
|
|
78
|
-
if (isTVSelectable) {
|
|
78
|
+
if (isTVSelectable && ![self isTVFocusGuide]) {
|
|
79
79
|
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
|
|
80
80
|
action:@selector(handleSelect:)];
|
|
81
81
|
recognizer.allowedPressTypes = @[ @(UIPressTypeSelect) ];
|
|
@@ -138,13 +138,25 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
- (BOOL)isTVFocusGuide
|
|
142
|
+
{
|
|
143
|
+
return self.focusGuide != nil;
|
|
144
|
+
}
|
|
145
|
+
|
|
141
146
|
- (BOOL)isUserInteractionEnabled
|
|
142
147
|
{
|
|
148
|
+
if ([self isTVFocusGuide]) {
|
|
149
|
+
return (self.isTVSelectable);
|
|
150
|
+
}
|
|
143
151
|
return YES;
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
- (BOOL)canBecomeFocused
|
|
147
155
|
{
|
|
156
|
+
if ([self isTVFocusGuide]) {
|
|
157
|
+
return NO;
|
|
158
|
+
}
|
|
159
|
+
|
|
148
160
|
return (self.isTVSelectable);
|
|
149
161
|
}
|
|
150
162
|
|
|
@@ -311,7 +323,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
|
|
|
311
323
|
[self handleFocusGuide];
|
|
312
324
|
}
|
|
313
325
|
|
|
314
|
-
if (context.nextFocusedView == self && self.isTVSelectable ) {
|
|
326
|
+
if (context.nextFocusedView == self && ![self isTVFocusGuide] && self.isTVSelectable ) {
|
|
315
327
|
[self becomeFirstResponder];
|
|
316
328
|
[self enableDirectionalFocusGuides];
|
|
317
329
|
[coordinator addCoordinatedAnimations:^(void){
|
|
@@ -336,6 +348,9 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
|
|
|
336
348
|
//
|
|
337
349
|
- (void)enableDirectionalFocusGuides
|
|
338
350
|
{
|
|
351
|
+
if (!self.isFocused) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
339
354
|
if (self->_nextFocusUp != nil) {
|
|
340
355
|
if (self.focusGuideUp == nil) {
|
|
341
356
|
self.focusGuideUp = [UIFocusGuide new];
|
|
@@ -455,18 +470,22 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
|
|
|
455
470
|
|
|
456
471
|
- (void)setNextFocusUp:(NSNumber *)nextFocusUp {
|
|
457
472
|
self->_nextFocusUp = [self getViewById: nextFocusUp];
|
|
473
|
+
[self enableDirectionalFocusGuides];
|
|
458
474
|
}
|
|
459
475
|
|
|
460
476
|
- (void)setNextFocusDown:(NSNumber *)nextFocusDown {
|
|
461
477
|
self->_nextFocusDown = [self getViewById: nextFocusDown];
|
|
478
|
+
[self enableDirectionalFocusGuides];
|
|
462
479
|
}
|
|
463
480
|
|
|
464
481
|
- (void)setNextFocusLeft:(NSNumber *)nextFocusLeft {
|
|
465
482
|
self->_nextFocusLeft = [self getViewById: nextFocusLeft];
|
|
483
|
+
[self enableDirectionalFocusGuides];
|
|
466
484
|
}
|
|
467
485
|
|
|
468
486
|
- (void)setNextFocusRight:(NSNumber *)nextFocusRight {
|
|
469
487
|
self->_nextFocusRight = [self getViewById: nextFocusRight];
|
|
488
|
+
[self enableDirectionalFocusGuides];
|
|
470
489
|
}
|
|
471
490
|
|
|
472
491
|
- (void)setPreferredFocus:(BOOL)hasTVPreferredFocus
|
|
@@ -1216,7 +1216,7 @@ public class ReactViewGroup extends ViewGroup
|
|
|
1216
1216
|
* `mRecoverFocus` flag indicates a temporary focus recovery mode it's in which
|
|
1217
1217
|
* requires full access to children focusable elements.
|
|
1218
1218
|
*/
|
|
1219
|
-
if (isTVFocusGuide() && !mRecoverFocus) {
|
|
1219
|
+
if (isTVFocusGuide() && !mRecoverFocus && this.getDescendantFocusability() != ViewGroup.FOCUS_BLOCK_DESCENDANTS) {
|
|
1220
1220
|
View focusedChild = getFocusedChildOfFocusGuide();
|
|
1221
1221
|
|
|
1222
1222
|
/*
|
|
@@ -12,6 +12,7 @@ import android.graphics.Rect;
|
|
|
12
12
|
import android.os.Build;
|
|
13
13
|
import android.util.Log;
|
|
14
14
|
import android.view.View;
|
|
15
|
+
import android.view.ViewGroup;
|
|
15
16
|
import androidx.annotation.NonNull;
|
|
16
17
|
import androidx.annotation.Nullable;
|
|
17
18
|
import com.facebook.common.logging.FLog;
|
|
@@ -306,6 +307,9 @@ public class ReactViewManager extends ReactClippingViewManager<ReactViewGroup> {
|
|
|
306
307
|
setFocusable(view, focusable);
|
|
307
308
|
if (!focusable) {
|
|
308
309
|
view.setFocusable(false);
|
|
310
|
+
view.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
|
|
311
|
+
} else {
|
|
312
|
+
view.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
|
|
309
313
|
}
|
|
310
314
|
}
|
|
311
315
|
|
|
@@ -17,8 +17,8 @@ namespace facebook::react {
|
|
|
17
17
|
constexpr struct {
|
|
18
18
|
int32_t Major = 0;
|
|
19
19
|
int32_t Minor = 73;
|
|
20
|
-
int32_t Patch =
|
|
21
|
-
std::string_view Prerelease = "
|
|
20
|
+
int32_t Patch = 6;
|
|
21
|
+
std::string_view Prerelease = "1";
|
|
22
22
|
} ReactNativeVersion;
|
|
23
23
|
|
|
24
24
|
} // namespace facebook::react
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-tvos",
|
|
3
|
-
"version": "0.73.
|
|
3
|
+
"version": "0.73.6-1",
|
|
4
4
|
"description": "A framework for building native apps using React",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -94,16 +94,16 @@
|
|
|
94
94
|
},
|
|
95
95
|
"dependencies": {
|
|
96
96
|
"@jest/create-cache-key-function": "^29.6.3",
|
|
97
|
-
"@react-native-community/cli": "12.3.
|
|
98
|
-
"@react-native-community/cli-platform-android": "12.3.
|
|
99
|
-
"@react-native-community/cli-platform-ios": "12.3.
|
|
97
|
+
"@react-native-community/cli": "12.3.6",
|
|
98
|
+
"@react-native-community/cli-platform-android": "12.3.6",
|
|
99
|
+
"@react-native-community/cli-platform-ios": "12.3.6",
|
|
100
100
|
"@react-native/assets-registry": "0.73.1",
|
|
101
|
-
"@react-native/community-cli-plugin": "0.73.
|
|
101
|
+
"@react-native/community-cli-plugin": "0.73.17",
|
|
102
102
|
"@react-native/codegen": "0.73.3",
|
|
103
103
|
"@react-native/gradle-plugin": "0.73.4",
|
|
104
104
|
"@react-native/js-polyfills": "0.73.1",
|
|
105
105
|
"@react-native/normalize-colors": "0.73.2",
|
|
106
|
-
"@react-native-tvos/virtualized-lists": "0.73.
|
|
106
|
+
"@react-native-tvos/virtualized-lists": "0.73.6-1",
|
|
107
107
|
"abort-controller": "^3.0.0",
|
|
108
108
|
"anser": "^1.4.9",
|
|
109
109
|
"ansi-regex": "^5.0.0",
|
|
@@ -151,6 +151,6 @@
|
|
|
151
151
|
]
|
|
152
152
|
},
|
|
153
153
|
"devDependencies": {
|
|
154
|
-
"react-native-core": "npm:react-native@0.73.
|
|
154
|
+
"react-native-core": "npm:react-native@0.73.6"
|
|
155
155
|
}
|
|
156
156
|
}
|
|
@@ -104,6 +104,10 @@ class NewArchitectureHelper
|
|
|
104
104
|
current_config = hash["pod_target_xcconfig"] != nil ? hash["pod_target_xcconfig"] : {}
|
|
105
105
|
current_headers = current_config["HEADER_SEARCH_PATHS"] != nil ? current_config["HEADER_SEARCH_PATHS"] : ""
|
|
106
106
|
|
|
107
|
+
flags_to_add = new_arch_enabled ?
|
|
108
|
+
"#{@@folly_compiler_flags} -DRCT_NEW_ARCH_ENABLED=1" :
|
|
109
|
+
"#{@@folly_compiler_flags}"
|
|
110
|
+
|
|
107
111
|
header_search_paths = ["\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Private/Yoga\""]
|
|
108
112
|
if ENV['USE_FRAMEWORKS']
|
|
109
113
|
header_search_paths << "\"$(PODS_ROOT)/DoubleConversion\""
|
|
@@ -123,7 +127,7 @@ class NewArchitectureHelper
|
|
|
123
127
|
}
|
|
124
128
|
end
|
|
125
129
|
header_search_paths_string = header_search_paths.join(" ")
|
|
126
|
-
spec.compiler_flags = compiler_flags.empty? ?
|
|
130
|
+
spec.compiler_flags = compiler_flags.empty? ? "$(inherited) #{flags_to_add}" : "$(inherited) #{compiler_flags} #{flags_to_add}"
|
|
127
131
|
current_config["HEADER_SEARCH_PATHS"] = current_headers.empty? ?
|
|
128
132
|
header_search_paths_string :
|
|
129
133
|
"#{current_headers} #{header_search_paths_string}"
|
|
@@ -86,6 +86,27 @@ class ReactNativePodsUtils
|
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
+
def self.fix_flipper_for_xcode_15_3(installer)
|
|
90
|
+
installer.pods_project.targets.each do |target|
|
|
91
|
+
if target.name == 'Flipper'
|
|
92
|
+
file_path = 'Pods/Flipper/xplat/Flipper/FlipperTransportTypes.h'
|
|
93
|
+
if !File.exist?(file_path)
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
contents = File.read(file_path)
|
|
98
|
+
if contents.include?('#include <functional>')
|
|
99
|
+
return
|
|
100
|
+
end
|
|
101
|
+
mod_content = contents.gsub("#pragma once", "#pragma once\n#include <functional>")
|
|
102
|
+
File.chmod(0755, file_path)
|
|
103
|
+
File.open(file_path, 'w') do |file|
|
|
104
|
+
file.puts(mod_content)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
89
110
|
def self.set_use_hermes_build_setting(installer, hermes_enabled)
|
|
90
111
|
Pod::UI.puts("Setting USE_HERMES build settings")
|
|
91
112
|
projects = self.extract_projects(installer)
|
|
@@ -32,7 +32,13 @@ const REACT_NATIVE_PACKAGE_ROOT_FOLDER = path.join(__dirname, '..', '..');
|
|
|
32
32
|
|
|
33
33
|
const CODEGEN_DEPENDENCY_NAME = '@react-native/codegen';
|
|
34
34
|
const CODEGEN_REPO_PATH = `${REACT_NATIVE_REPOSITORY_ROOT}/packages/react-native-codegen`;
|
|
35
|
-
|
|
35
|
+
// This is a change for 0.73-stable only since this piece of code was replaced:
|
|
36
|
+
// https://github.com/facebook/react-native/commit/9071a3a0b0e11ad711927651bcb2412f553b6fe9
|
|
37
|
+
const CODEGEN_NPM_PATH = path.dirname(
|
|
38
|
+
require.resolve(path.join(CODEGEN_DEPENDENCY_NAME, 'package.json'), {
|
|
39
|
+
paths: [REACT_NATIVE_PACKAGE_ROOT_FOLDER],
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
36
42
|
const CORE_LIBRARIES_WITH_OUTPUT_FOLDER = {
|
|
37
43
|
rncore: path.join(REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'ReactCommon'),
|
|
38
44
|
FBReactNativeSpec: null,
|
|
@@ -189,33 +195,34 @@ function handleThirdPartyLibraries(
|
|
|
189
195
|
codegenConfigKey,
|
|
190
196
|
) {
|
|
191
197
|
// Determine which of these are codegen-enabled libraries
|
|
192
|
-
const configDir =
|
|
193
|
-
baseCodegenConfigFileDir ||
|
|
194
|
-
path.join(REACT_NATIVE_PACKAGE_ROOT_FOLDER, '..');
|
|
198
|
+
const configDir = baseCodegenConfigFileDir || process.cwd();
|
|
195
199
|
console.log(
|
|
196
200
|
`\n\n[Codegen] >>>>> Searching for codegen-enabled libraries in ${configDir}`,
|
|
197
201
|
);
|
|
198
202
|
|
|
199
203
|
// Handle third-party libraries
|
|
204
|
+
const resolveOptions = {paths: [configDir]};
|
|
200
205
|
Object.keys(dependencies).forEach(dependency => {
|
|
201
206
|
if (dependency === REACT_NATIVE_DEPENDENCY_NAME) {
|
|
202
207
|
// react-native should already be added.
|
|
203
208
|
return;
|
|
204
209
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const configFilePath = require.resolve(
|
|
213
|
+
`${dependency}/${codegenConfigFilename}`,
|
|
214
|
+
resolveOptions,
|
|
215
|
+
);
|
|
211
216
|
const configFile = JSON.parse(fs.readFileSync(configFilePath));
|
|
212
217
|
extractLibrariesFromJSON(
|
|
213
218
|
configFile,
|
|
214
219
|
libraries,
|
|
215
220
|
codegenConfigKey,
|
|
216
221
|
dependency,
|
|
217
|
-
|
|
222
|
+
path.dirname(configFilePath),
|
|
218
223
|
);
|
|
224
|
+
} catch (_) {
|
|
225
|
+
// ignore
|
|
219
226
|
}
|
|
220
227
|
});
|
|
221
228
|
}
|
|
@@ -313,6 +313,7 @@ def react_native_post_install(
|
|
|
313
313
|
ReactNativePodsUtils.apply_flags_for_fabric(installer, fabric_enabled: fabric_enabled)
|
|
314
314
|
ReactNativePodsUtils.apply_xcode_15_patch(installer)
|
|
315
315
|
ReactNativePodsUtils.updateOSDeploymentTarget(installer)
|
|
316
|
+
ReactNativePodsUtils.fix_flipper_for_xcode_15_3(installer)
|
|
316
317
|
|
|
317
318
|
NewArchitectureHelper.set_clang_cxx_language_standard_if_needed(installer)
|
|
318
319
|
NewArchitectureHelper.modify_flags_for_new_architecture(installer, NewArchitectureHelper.new_arch_enabled)
|
package/sdks/.hermesversion
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
hermes-2024-01-
|
|
1
|
+
hermes-2024-03-01-RNv0.73.5-5e3ba8a5ff73efc46f4570492176b0605de77ed6
|
|
@@ -7,6 +7,7 @@ require 'net/http'
|
|
|
7
7
|
require 'rexml/document'
|
|
8
8
|
|
|
9
9
|
HERMES_GITHUB_URL = "https://github.com/facebook/hermes.git"
|
|
10
|
+
ENV_BUILD_FROM_SOURCE = "RCT_BUILD_HERMES_FROM_SOURCE"
|
|
10
11
|
|
|
11
12
|
module HermesEngineSourceType
|
|
12
13
|
LOCAL_PREBUILT_TARBALL = :local_prebuilt_tarball
|
|
@@ -30,7 +31,7 @@ end
|
|
|
30
31
|
# - To use a specific tarball, install the dependencies with:
|
|
31
32
|
# `HERMES_ENGINE_TARBALL_PATH=<path_to_tarball> bundle exec pod install`
|
|
32
33
|
# - To force a build from source, install the dependencies with:
|
|
33
|
-
# `
|
|
34
|
+
# `RCT_BUILD_HERMES_FROM_SOURCE=true bundle exec pod install`
|
|
34
35
|
# If none of the two are provided, Cocoapods will check whether there is a tarball for the current version
|
|
35
36
|
# (either release or nightly). If not, it will fall back to building from source (the latest commit on main).
|
|
36
37
|
#
|
|
@@ -85,11 +86,11 @@ def hermes_commit_envvar_defined()
|
|
|
85
86
|
end
|
|
86
87
|
|
|
87
88
|
def force_build_from_tag(react_native_path)
|
|
88
|
-
return ENV[
|
|
89
|
+
return ENV[ENV_BUILD_FROM_SOURCE] === 'true' && File.exist?(hermestag_file(react_native_path))
|
|
89
90
|
end
|
|
90
91
|
|
|
91
92
|
def force_build_from_main(react_native_path)
|
|
92
|
-
return ENV[
|
|
93
|
+
return ENV[ENV_BUILD_FROM_SOURCE] === 'true' && !File.exist?(hermestag_file(react_native_path))
|
|
93
94
|
end
|
|
94
95
|
|
|
95
96
|
def release_artifact_exists(version)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/template/package.json
CHANGED