react-native-tvos 0.76.6-0 → 0.76.9-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.
Files changed (84) hide show
  1. package/Libraries/AppDelegate/RCTAppDelegate.mm +0 -5
  2. package/Libraries/AppDelegate/RCTAppSetupUtils.mm +3 -1
  3. package/Libraries/AppDelegate/RCTRootViewFactory.mm +3 -3
  4. package/Libraries/AppDelegate/React-RCTAppDelegate.podspec +1 -0
  5. package/Libraries/Blob/React-RCTBlob.podspec +3 -1
  6. package/Libraries/Components/TV/TVFocusGuideView.js +0 -1
  7. package/Libraries/Components/View/ViewPropTypes.d.ts +4 -2
  8. package/Libraries/Core/ReactNativeVersion.js +1 -1
  9. package/Libraries/Image/RCTImageLoader.mm +9 -1
  10. package/Libraries/Network/FormData.js +11 -3
  11. package/Libraries/Network/RCTDataRequestHandler.mm +17 -3
  12. package/Libraries/Network/RCTFileRequestHandler.mm +17 -3
  13. package/Libraries/Utilities/Appearance.js +3 -1
  14. package/React/Base/RCTVersion.m +1 -1
  15. package/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm +2 -5
  16. package/React/CoreModules/React-CoreModules.podspec +9 -10
  17. package/React/CxxModule/RCTCxxMethod.mm +10 -2
  18. package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +85 -31
  19. package/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +1 -7
  20. package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +5 -2
  21. package/React/Fabric/Surface/RCTFabricSurface.mm +1 -0
  22. package/React/React-RCTFabric.podspec +1 -0
  23. package/React/Views/RCTTVView.m +5 -2
  24. package/React/Views/ScrollView/RCTScrollView.m +63 -26
  25. package/React/third-party.xcconfig +1 -1
  26. package/React-Core.podspec +2 -1
  27. package/ReactAndroid/api/ReactAndroid.api +3 -1
  28. package/ReactAndroid/build.gradle.kts +24 -0
  29. package/ReactAndroid/gradle.properties +2 -2
  30. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java +3 -1
  31. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/HMRClient.java +4 -1
  32. package/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +13 -8
  33. package/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java +15 -8
  34. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java +1 -1
  35. package/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java +16 -2
  36. package/ReactAndroid/src/main/jni/CMakeLists.txt +1 -0
  37. package/ReactAndroid/src/main/jni/third-party/fast_float/CMakeLists.txt +13 -0
  38. package/ReactAndroid/src/main/jni/third-party/folly/CMakeLists.txt +5 -6
  39. package/ReactCommon/React-Fabric.podspec +2 -1
  40. package/ReactCommon/React-FabricComponents.podspec +3 -1
  41. package/ReactCommon/React-FabricImage.podspec +7 -5
  42. package/ReactCommon/ReactCommon.podspec +9 -8
  43. package/ReactCommon/cxxreact/CxxNativeModule.cpp +6 -3
  44. package/ReactCommon/cxxreact/React-cxxreact.podspec +11 -10
  45. package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
  46. package/ReactCommon/hermes/React-hermes.podspec +7 -6
  47. package/ReactCommon/jsi/React-jsi.podspec +4 -3
  48. package/ReactCommon/jsiexecutor/React-jsiexecutor.podspec +7 -6
  49. package/ReactCommon/jsinspector-modern/React-jsinspector.podspec +4 -4
  50. package/ReactCommon/react/nativemodule/core/platform/ios/React-NativeModulesApple.podspec +1 -1
  51. package/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm +9 -0
  52. package/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm +24 -13
  53. package/ReactCommon/react/nativemodule/samples/ReactCommon-Samples.podspec +3 -1
  54. package/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp +5 -0
  55. package/ReactCommon/react/renderer/attributedstring/TextAttributes.h +2 -0
  56. package/ReactCommon/react/renderer/attributedstring/conversions.h +5 -0
  57. package/ReactCommon/react/renderer/components/text/BaseTextProps.cpp +12 -0
  58. package/ReactCommon/react/renderer/debug/React-rendererdebug.podspec +4 -2
  59. package/ReactCommon/react/renderer/graphics/React-graphics.podspec +4 -2
  60. package/ReactCommon/react/renderer/imagemanager/platform/ios/React-ImageManager.podspec +1 -0
  61. package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h +24 -3
  62. package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +6 -46
  63. package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +4 -5
  64. package/ReactCommon/reactperflogger/React-perflogger.podspec +1 -0
  65. package/cli.js +11 -2
  66. package/gradle/libs.versions.toml +4 -3
  67. package/package.json +8 -8
  68. package/react-native.config.js +24 -23
  69. package/scripts/cocoapods/codegen_utils.rb +1 -2
  70. package/scripts/cocoapods/helpers.rb +13 -1
  71. package/scripts/cocoapods/new_architecture.rb +1 -0
  72. package/scripts/cocoapods/utils.rb +2 -0
  73. package/scripts/react_native_pods.rb +14 -0
  74. package/sdks/hermes-engine/hermes-utils.rb +3 -1
  75. package/sdks/hermesc/linux64-bin/hermesc +0 -0
  76. package/sdks/hermesc/osx-bin/hermes +0 -0
  77. package/sdks/hermesc/osx-bin/hermesc +0 -0
  78. package/sdks/hermesc/win64-bin/hermesc.exe +0 -0
  79. package/sdks/hermesc/win64-bin/msvcp140.dll +0 -0
  80. package/sdks/hermesc/win64-bin/vcruntime140.dll +0 -0
  81. package/sdks/hermesc/win64-bin/vcruntime140_1.dll +0 -0
  82. package/third-party-podspecs/RCT-Folly.podspec +19 -11
  83. package/third-party-podspecs/fast_float.podspec +29 -0
  84. package/third-party-podspecs/fmt.podspec +2 -2
@@ -89,11 +89,6 @@
89
89
  [_window makeKeyAndVisible];
90
90
  }
91
91
 
92
- - (void)applicationDidEnterBackground:(UIApplication *)application
93
- {
94
- // Noop
95
- }
96
-
97
92
  - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
98
93
  {
99
94
  [NSException raise:@"RCTBridgeDelegate::sourceURLForBridge not implemented"
@@ -53,7 +53,9 @@ RCTAppSetupDefaultRootView(RCTBridge *bridge, NSString *moduleName, NSDictionary
53
53
  id<RCTSurfaceProtocol> surface = [[RCTFabricSurface alloc] initWithBridge:bridge
54
54
  moduleName:moduleName
55
55
  initialProperties:initialProperties];
56
- return [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
56
+ UIView *rootView = [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
57
+ [surface start];
58
+ return rootView;
57
59
  }
58
60
  return [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
59
61
  }
@@ -159,9 +159,9 @@ static NSDictionary *updateInitialProps(NSDictionary *initialProps, BOOL isFabri
159
159
 
160
160
  RCTFabricSurface *surface = [self.reactHost createSurfaceWithModuleName:moduleName initialProperties:initProps];
161
161
 
162
- RCTSurfaceHostingProxyRootView *surfaceHostingProxyRootView = [[RCTSurfaceHostingProxyRootView alloc]
163
- initWithSurface:surface
164
- sizeMeasureMode:RCTSurfaceSizeMeasureModeWidthExact | RCTSurfaceSizeMeasureModeHeightExact];
162
+ RCTSurfaceHostingProxyRootView *surfaceHostingProxyRootView =
163
+ [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
164
+
165
165
  #if !TARGET_OS_TV
166
166
  surfaceHostingProxyRootView.backgroundColor = [UIColor systemBackgroundColor];
167
167
  #endif
@@ -32,6 +32,7 @@ header_search_paths = [
32
32
  "$(PODS_ROOT)/Headers/Private/React-Core",
33
33
  "$(PODS_ROOT)/boost",
34
34
  "$(PODS_ROOT)/DoubleConversion",
35
+ "$(PODS_ROOT)/fast_float/include",
35
36
  "$(PODS_ROOT)/fmt/include",
36
37
  "$(PODS_ROOT)/RCT-Folly",
37
38
  "${PODS_ROOT}/Headers/Public/FlipperKit",
@@ -24,6 +24,7 @@ header_search_paths = [
24
24
  "\"$(PODS_ROOT)/RCT-Folly\"",
25
25
  "\"$(PODS_ROOT)/boost\"",
26
26
  "\"$(PODS_ROOT)/DoubleConversion\"",
27
+ "\"$(PODS_ROOT)/fast_float/include\"",
27
28
  "\"$(PODS_ROOT)/fmt/include\"",
28
29
  "\"${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\"",
29
30
  ]
@@ -48,7 +49,8 @@ Pod::Spec.new do |s|
48
49
  }
49
50
 
50
51
  s.dependency "DoubleConversion"
51
- s.dependency "fmt", "9.1.0"
52
+ s.dependency "fast_float"
53
+ s.dependency "fmt"
52
54
  s.dependency "RCT-Folly", folly_version
53
55
  s.dependency "React-jsi"
54
56
  s.dependency "React-Core/RCTBlobHeaders"
@@ -8,7 +8,6 @@
8
8
  * @format
9
9
  */
10
10
 
11
- import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
12
11
  import type {ViewProps} from '../View/ViewPropTypes';
13
12
  import type {ComponentOrHandleType} from './tagForComponentOrHandle';
14
13
 
@@ -17,6 +17,8 @@ import {
17
17
  PointerEvents,
18
18
  FocusEvents,
19
19
  PressEvents,
20
+ NativeFocusEvent,
21
+ NativeBlurEvent,
20
22
  } from '../../Types/CoreEventTypes';
21
23
  import {Touchable} from '../Touchable/Touchable';
22
24
  import {AccessibilityProps} from './ViewAccessibility';
@@ -220,6 +222,6 @@ export interface ViewProps
220
222
  */
221
223
  nativeID?: string | undefined;
222
224
 
223
- readonly onFocus?: BubblingEventHandler<Event> | undefined;
224
- readonly onBlur?: BubblingEventHandler<Event> | undefined;
225
+ readonly onFocus?: BubblingEventHandler<NativeFocusEvent> | undefined;
226
+ readonly onBlur?: BubblingEventHandler<NativeBlurEvent> | undefined;
225
227
  }
@@ -16,7 +16,7 @@ const version: $ReadOnly<{
16
16
  }> = {
17
17
  major: 0,
18
18
  minor: 76,
19
- patch: 6,
19
+ patch: 9,
20
20
  prerelease: '0',
21
21
  };
22
22
 
@@ -477,7 +477,15 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, CGSize size, CGFloat scal
477
477
 
478
478
  // Add missing png extension
479
479
  if (request.URL.fileURL && request.URL.pathExtension.length == 0) {
480
- mutableRequest.URL = [request.URL URLByAppendingPathExtension:@"png"];
480
+ // Check if there exists a file with that url on disk already
481
+ // This should fix issue https://github.com/facebook/react-native/issues/46870
482
+ if ([[NSFileManager defaultManager] fileExistsAtPath:request.URL.path]) {
483
+ mutableRequest.URL = request.URL;
484
+ } else {
485
+ // This is the default behavior in case there is no file on disk with no extension.
486
+ // We assume that the extension is `png`.
487
+ mutableRequest.URL = [request.URL URLByAppendingPathExtension:@"png"];
488
+ }
481
489
  }
482
490
  if (_redirectDelegate != nil) {
483
491
  mutableRequest.URL = [_redirectDelegate redirectAssetsURL:mutableRequest.URL];
@@ -28,6 +28,15 @@ type FormDataPart =
28
28
  ...
29
29
  };
30
30
 
31
+ /**
32
+ * Encode a FormData filename compliant with RFC 2183
33
+ *
34
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#directives
35
+ */
36
+ function encodeFilename(filename: string): string {
37
+ return encodeURIComponent(filename.replace(/\//g, '_'));
38
+ }
39
+
31
40
  /**
32
41
  * Polyfill for XMLHttpRequest2 FormData API, allowing multipart POST requests
33
42
  * with mixed data (string, native files) to be submitted via XMLHttpRequest.
@@ -82,9 +91,8 @@ class FormData {
82
91
  // content type (cf. web Blob interface.)
83
92
  if (typeof value === 'object' && !Array.isArray(value) && value) {
84
93
  if (typeof value.name === 'string') {
85
- headers['content-disposition'] += `; filename="${
86
- value.name
87
- }"; filename*=utf-8''${encodeURI(value.name)}`;
94
+ headers['content-disposition'] +=
95
+ `; filename="${encodeFilename(value.name)}"`;
88
96
  }
89
97
  if (typeof value.type === 'string') {
90
98
  headers['content-type'] = value.type;
@@ -8,6 +8,8 @@
8
8
  #import <React/RCTDataRequestHandler.h>
9
9
  #import <ReactCommon/RCTTurboModule.h>
10
10
 
11
+ #import <mutex>
12
+
11
13
  #import "RCTNetworkPlugins.h"
12
14
 
13
15
  @interface RCTDataRequestHandler () <RCTTurboModule>
@@ -15,14 +17,22 @@
15
17
 
16
18
  @implementation RCTDataRequestHandler {
17
19
  NSOperationQueue *_queue;
20
+ std::mutex _operationHandlerMutexLock;
18
21
  }
19
22
 
20
23
  RCT_EXPORT_MODULE()
21
24
 
22
25
  - (void)invalidate
23
26
  {
24
- [_queue cancelAllOperations];
25
- _queue = nil;
27
+ std::lock_guard<std::mutex> lock(_operationHandlerMutexLock);
28
+ if (_queue) {
29
+ for (NSOperation *operation in _queue.operations) {
30
+ if (!operation.isCancelled && !operation.isFinished) {
31
+ [operation cancel];
32
+ }
33
+ }
34
+ _queue = nil;
35
+ }
26
36
  }
27
37
 
28
38
  - (BOOL)canHandleRequest:(NSURLRequest *)request
@@ -32,6 +42,7 @@ RCT_EXPORT_MODULE()
32
42
 
33
43
  - (NSOperation *)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
34
44
  {
45
+ std::lock_guard<std::mutex> lock(_operationHandlerMutexLock);
35
46
  // Lazy setup
36
47
  if (!_queue) {
37
48
  _queue = [NSOperationQueue new];
@@ -69,7 +80,10 @@ RCT_EXPORT_MODULE()
69
80
 
70
81
  - (void)cancelRequest:(NSOperation *)op
71
82
  {
72
- [op cancel];
83
+ std::lock_guard<std::mutex> lock(_operationHandlerMutexLock);
84
+ if (!op.isCancelled && !op.isFinished) {
85
+ [op cancel];
86
+ }
73
87
  }
74
88
 
75
89
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
@@ -7,6 +7,8 @@
7
7
 
8
8
  #import <React/RCTFileRequestHandler.h>
9
9
 
10
+ #import <mutex>
11
+
10
12
  #import <MobileCoreServices/MobileCoreServices.h>
11
13
 
12
14
  #import <React/RCTUtils.h>
@@ -19,14 +21,22 @@
19
21
 
20
22
  @implementation RCTFileRequestHandler {
21
23
  NSOperationQueue *_fileQueue;
24
+ std::mutex _operationHandlerMutexLock;
22
25
  }
23
26
 
24
27
  RCT_EXPORT_MODULE()
25
28
 
26
29
  - (void)invalidate
27
30
  {
28
- [_fileQueue cancelAllOperations];
29
- _fileQueue = nil;
31
+ std::lock_guard<std::mutex> lock(_operationHandlerMutexLock);
32
+ if (_fileQueue) {
33
+ for (NSOperation *operation in _fileQueue.operations) {
34
+ if (!operation.isCancelled && !operation.isFinished) {
35
+ [operation cancel];
36
+ }
37
+ }
38
+ _fileQueue = nil;
39
+ }
30
40
  }
31
41
 
32
42
  - (BOOL)canHandleRequest:(NSURLRequest *)request
@@ -36,6 +46,7 @@ RCT_EXPORT_MODULE()
36
46
 
37
47
  - (NSOperation *)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
38
48
  {
49
+ std::lock_guard<std::mutex> lock(_operationHandlerMutexLock);
39
50
  // Lazy setup
40
51
  if (!_fileQueue) {
41
52
  _fileQueue = [NSOperationQueue new];
@@ -83,7 +94,10 @@ RCT_EXPORT_MODULE()
83
94
 
84
95
  - (void)cancelRequest:(NSOperation *)op
85
96
  {
86
- [op cancel];
97
+ std::lock_guard<std::mutex> lock(_operationHandlerMutexLock);
98
+ if (!op.isCancelled && !op.isFinished) {
99
+ [op cancel];
100
+ }
87
101
  }
88
102
 
89
103
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
@@ -105,7 +105,9 @@ export function setColorScheme(colorScheme: ?ColorSchemeName): void {
105
105
  const {NativeAppearance} = state;
106
106
  if (NativeAppearance != null) {
107
107
  NativeAppearance.setColorScheme(colorScheme ?? 'unspecified');
108
- state.appearance = {colorScheme};
108
+ state.appearance = {
109
+ colorScheme: toColorScheme(NativeAppearance.getColorScheme()),
110
+ };
109
111
  }
110
112
  }
111
113
 
@@ -23,7 +23,7 @@ NSDictionary* RCTGetReactNativeVersion(void)
23
23
  __rnVersion = @{
24
24
  RCTVersionMajor: @(0),
25
25
  RCTVersionMinor: @(76),
26
- RCTVersionPatch: @(6),
26
+ RCTVersionPatch: @(9),
27
27
  RCTVersionPrerelease: @"0",
28
28
  };
29
29
  });
@@ -53,11 +53,8 @@ static RCTRootViewSizeFlexibility convertToRootViewSizeFlexibility(RCTSurfaceSiz
53
53
 
54
54
  - (instancetype)initWithSurface:(id<RCTSurfaceProtocol>)surface
55
55
  {
56
- if (self = [super initWithSurface:surface
57
- sizeMeasureMode:RCTSurfaceSizeMeasureModeWidthExact | RCTSurfaceSizeMeasureModeHeightExact]) {
58
- [surface start];
59
- }
60
- return self;
56
+ return [super initWithSurface:surface
57
+ sizeMeasureMode:RCTSurfaceSizeMeasureModeWidthExact | RCTSurfaceSizeMeasureModeHeightExact];
61
58
  }
62
59
 
63
60
  RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame)
@@ -20,14 +20,12 @@ folly_config = get_folly_config()
20
20
  folly_compiler_flags = folly_config[:compiler_flags]
21
21
  folly_version = folly_config[:version]
22
22
 
23
- socket_rocket_config = get_socket_rocket_config()
24
- socket_rocket_version = socket_rocket_config[:version]
25
-
26
23
  header_search_paths = [
27
24
  "\"$(PODS_ROOT)/boost\"",
28
25
  "\"$(PODS_TARGET_SRCROOT)/React/CoreModules\"",
29
26
  "\"$(PODS_ROOT)/RCT-Folly\"",
30
27
  "\"$(PODS_ROOT)/DoubleConversion\"",
28
+ "\"$(PODS_ROOT)/fast_float/include\"",
31
29
  "\"$(PODS_ROOT)/fmt/include\"",
32
30
  "\"${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\"",
33
31
  ]
@@ -51,14 +49,15 @@ Pod::Spec.new do |s|
51
49
  }
52
50
  s.framework = "UIKit"
53
51
  s.dependency "DoubleConversion"
54
- s.dependency "fmt", "9.1.0"
55
- s.dependency "RCT-Folly", folly_version
56
- s.dependency "RCTTypeSafety", version
57
- s.dependency "React-Core/CoreModulesHeaders", version
58
- s.dependency "React-RCTImage", version
59
- s.dependency "React-jsi", version
52
+ s.dependency "fast_float"
53
+ s.dependency "fmt"
54
+ s.dependency "RCT-Folly"
55
+ s.dependency "RCTTypeSafety"
56
+ s.dependency "React-Core/CoreModulesHeaders"
57
+ s.dependency "React-RCTImage"
58
+ s.dependency "React-jsi"
60
59
  s.dependency 'React-RCTBlob'
61
- s.dependency "SocketRocket", socket_rocket_version
60
+ s.dependency "SocketRocket"
62
61
  add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
63
62
 
64
63
  add_dependency(s, "ReactCodegen")
@@ -98,14 +98,22 @@ using namespace facebook::react;
98
98
  NSNumber *id2 = arguments[arguments.count - 1];
99
99
 
100
100
  second = ^(std::vector<folly::dynamic> args) {
101
- [bridge enqueueCallback:id2 args:convertFollyDynamicToId(folly::dynamic(args.begin(), args.end()))];
101
+ folly::dynamic obj = folly::dynamic::array;
102
+ for (auto &arg : args) {
103
+ obj.push_back(std::move(arg));
104
+ }
105
+ [bridge enqueueCallback:id2 args:convertFollyDynamicToId(std::move(obj))];
102
106
  };
103
107
  } else {
104
108
  id1 = arguments[arguments.count - 1];
105
109
  }
106
110
 
107
111
  first = ^(std::vector<folly::dynamic> args) {
108
- [bridge enqueueCallback:id1 args:convertFollyDynamicToId(folly::dynamic(args.begin(), args.end()))];
112
+ folly::dynamic obj = folly::dynamic::array;
113
+ for (auto &arg : args) {
114
+ obj.push_back(std::move(arg));
115
+ }
116
+ [bridge enqueueCallback:id1 args:convertFollyDynamicToId(std::move(obj))];
109
117
  };
110
118
  }
111
119
 
@@ -1030,18 +1030,23 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
1030
1030
  [self sendBlurNotification];
1031
1031
  [self removeSwipeGestureRecognizers];
1032
1032
  [self resignFirstResponder];
1033
- // if we leave the scroll view and go up, then scroll to top; if going down,
1034
- // scroll to bottom
1035
- // Similarly for left and right
1033
+ // If scrolling is enabled:
1034
+ // - Scroll to the top when moving up and to the bottom when moving down.
1035
+ // - Similarly, scroll towards leading edge when moving towards leading edge and to the trailing edge when moving towards the trailing edge.
1036
+ BOOL isRTL = [[RCTI18nUtil sharedInstance] isRTL];
1037
+ BOOL isMovingTowardsLeadingEdge = (isRTL ? context.focusHeading == UIFocusHeadingRight : context.focusHeading == UIFocusHeadingLeft);
1038
+ BOOL isMovingTowardsTrailingEdge = (isRTL ? context.focusHeading == UIFocusHeadingLeft : context.focusHeading == UIFocusHeadingRight);
1036
1039
  RCTEnhancedScrollView *scrollView = (RCTEnhancedScrollView *)_scrollView;
1037
- if (context.focusHeading == UIFocusHeadingUp && scrollView.snapToStart) {
1038
- [self swipeVerticalScrollToOffset:0.0];
1039
- } else if(context.focusHeading == UIFocusHeadingDown && scrollView.snapToEnd) {
1040
- [self swipeVerticalScrollToOffset:scrollView.contentSize.height];
1041
- } else if(context.focusHeading == UIFocusHeadingLeft && scrollView.snapToStart) {
1042
- [self swipeHorizontalScrollToOffset:0.0];
1043
- } else if(context.focusHeading == UIFocusHeadingRight && scrollView.snapToEnd) {
1044
- [self swipeHorizontalScrollToOffset:scrollView.contentSize.width];
1040
+ if (scrollView.isScrollEnabled) {
1041
+ if (context.focusHeading == UIFocusHeadingUp && scrollView.snapToStart) {
1042
+ [self scrollToVerticalOffset:0.0];
1043
+ } else if(context.focusHeading == UIFocusHeadingDown && scrollView.snapToEnd) {
1044
+ [self scrollToVerticalOffset:scrollView.contentSize.height];
1045
+ } else if(isMovingTowardsLeadingEdge && scrollView.snapToStart) {
1046
+ [self scrollToHorizontalOffset:0.0];
1047
+ } else if(isMovingTowardsTrailingEdge && scrollView.snapToEnd) {
1048
+ [self scrollToHorizontalOffset:scrollView.contentSize.width];
1049
+ }
1045
1050
  }
1046
1051
  }
1047
1052
  }
@@ -1100,23 +1105,32 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
1100
1105
 
1101
1106
  - (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context
1102
1107
  {
1108
+ // If the previously focused item is this view and scrolling is disabled, defer to the superclass
1109
+ if (context.previouslyFocusedItem == self && !self.scrollView.isScrollEnabled) {
1110
+ return [super shouldUpdateFocusInContext:context];
1111
+ }
1112
+
1103
1113
  // Determine if the layout is Right-to-Left
1104
1114
  BOOL isRTL = [[RCTI18nUtil sharedInstance] isRTL];
1105
1115
  BOOL isHorizontal = _scrollView.contentSize.width > self.frame.size.width;
1106
1116
  // Adjust for horizontal scrolling with RTL support
1107
1117
  if (isHorizontal) {
1108
- BOOL isNavigatingToEnd = (isRTL ? context.focusHeading == UIFocusHeadingLeft : context.focusHeading == UIFocusHeadingRight);
1109
- BOOL isNavigatingToStart = (isRTL ? context.focusHeading == UIFocusHeadingRight : context.focusHeading == UIFocusHeadingLeft);
1118
+ BOOL isMovingTowardsLeadingEdge = (isRTL ? context.focusHeading == UIFocusHeadingRight : context.focusHeading == UIFocusHeadingLeft);
1119
+ BOOL isMovingTowardsTrailingEdge = (isRTL ? context.focusHeading == UIFocusHeadingLeft : context.focusHeading == UIFocusHeadingRight);
1110
1120
 
1111
- if ((isNavigatingToEnd && self.scrollView.contentOffset.x < self.scrollView.contentSize.width - self.scrollView.visibleSize.width) ||
1112
- (isNavigatingToStart && self.scrollView.contentOffset.x > 0)) {
1113
- return [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem];
1121
+ BOOL isScrollingToLeading = (isMovingTowardsLeadingEdge && self.scrollView.contentOffset.x > 0);
1122
+ BOOL isScrollingToTrailing = (isMovingTowardsTrailingEdge && self.scrollView.contentOffset.x < self.scrollView.contentSize.width - MAX(self.scrollView.visibleSize.width, 1));
1123
+
1124
+ if (isScrollingToLeading || isScrollingToTrailing) {
1125
+ return (context.nextFocusedItem && [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem]);
1114
1126
  }
1115
1127
  } else {
1116
1128
  // Handle vertical scrolling as before
1117
- if ((context.focusHeading == UIFocusHeadingUp && self.scrollView.contentOffset.y > 0) ||
1118
- (context.focusHeading == UIFocusHeadingDown && self.scrollView.contentOffset.y < self.scrollView.contentSize.height - self.scrollView.visibleSize.height)) {
1119
- return [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem];
1129
+ BOOL isMovingUp = (context.focusHeading == UIFocusHeadingUp && self.scrollView.contentOffset.y > 0);
1130
+ BOOL isMovingDown = (context.focusHeading == UIFocusHeadingDown && self.scrollView.contentOffset.y < self.scrollView.contentSize.height - MAX(self.scrollView.visibleSize.height, 1));
1131
+
1132
+ if (isMovingUp || isMovingDown) {
1133
+ return (context.nextFocusedItem && [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem]);
1120
1134
  }
1121
1135
  }
1122
1136
  return [super shouldUpdateFocusInContext:context];
@@ -1160,60 +1174,100 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
1160
1174
  return duration;
1161
1175
  }
1162
1176
 
1163
- - (void)swipeVerticalScrollToOffset:(CGFloat)yOffset
1177
+ - (void)scrollToVerticalOffset:(CGFloat)yOffset
1164
1178
  {
1165
1179
  _blockFirstTouch = NO;
1166
1180
  dispatch_async(dispatch_get_main_queue(), ^{
1167
1181
  CGFloat limitedOffset = yOffset;
1182
+
1183
+ // Ensure content size and visible size are non-negative
1184
+ CGFloat contentHeight = MAX(self.scrollView.contentSize.height, 0.0);
1185
+ CGFloat visibleHeight = MAX(self.scrollView.visibleSize.height, 0.0);
1186
+
1187
+ // Compute the maximum offset, ensuring it's non-negative
1188
+ CGFloat maxOffset = MAX(contentHeight - visibleHeight, 0.0);
1189
+
1190
+ // Clamp the offset within valid bounds
1168
1191
  limitedOffset = MAX(limitedOffset, 0.0);
1169
- limitedOffset = MIN(limitedOffset, self.scrollView.contentSize.height - self.scrollView.visibleSize.height);
1192
+ limitedOffset = MIN(limitedOffset, maxOffset);
1193
+
1170
1194
  [UIView animateWithDuration:[self swipeDuration] animations:^{
1171
1195
  self.scrollView.contentOffset =
1172
- CGPointMake(self.scrollView.contentOffset.x, limitedOffset);
1196
+ CGPointMake(self.scrollView.contentOffset.x, limitedOffset);
1173
1197
  }];
1174
1198
  });
1175
1199
  }
1176
1200
 
1177
- - (void)swipeHorizontalScrollToOffset:(CGFloat)xOffset
1201
+ - (void)scrollToHorizontalOffset:(CGFloat)xOffset
1178
1202
  {
1179
1203
  _blockFirstTouch = NO;
1180
1204
  dispatch_async(dispatch_get_main_queue(), ^{
1181
1205
  CGFloat limitedOffset = xOffset;
1206
+
1207
+ // Ensure content size and visible size are non-negative
1208
+ CGFloat contentWidth = MAX(self.scrollView.contentSize.width, 0.0);
1209
+ CGFloat visibleWidth = MAX(self.scrollView.visibleSize.width, 0.0);
1210
+
1211
+ // Compute the maximum offset, ensuring it's non-negative
1212
+ CGFloat maxOffset = MAX(contentWidth - visibleWidth, 0.0);
1213
+
1214
+ // Clamp the offset within valid bounds
1182
1215
  limitedOffset = MAX(limitedOffset, 0.0);
1183
- limitedOffset = MIN(limitedOffset, self.scrollView.contentSize.width - self.scrollView.visibleSize.width);
1216
+ limitedOffset = MIN(limitedOffset, maxOffset);
1217
+
1184
1218
  [UIView animateWithDuration:[self swipeDuration] animations:^{
1185
1219
  self.scrollView.contentOffset =
1186
- CGPointMake(limitedOffset, self.scrollView.contentOffset.y);
1220
+ CGPointMake(limitedOffset, self.scrollView.contentOffset.y);
1187
1221
  }];
1188
1222
  });
1189
1223
  }
1190
1224
 
1191
1225
  - (void)swipedUp
1192
1226
  {
1227
+ if (!self.scrollView.scrollEnabled) {
1228
+ return;
1229
+ }
1230
+
1193
1231
  CGFloat newOffset = self.scrollView.contentOffset.y - [self swipeVerticalInterval];
1194
1232
  // NSLog(@"Swiped up to %f", newOffset);
1195
- [self swipeVerticalScrollToOffset:newOffset];
1233
+ [self scrollToVerticalOffset:newOffset];
1196
1234
  }
1197
1235
 
1198
1236
  - (void)swipedDown
1199
1237
  {
1238
+ if (!self.scrollView.scrollEnabled) {
1239
+ return;
1240
+ }
1241
+
1200
1242
  CGFloat newOffset = self.scrollView.contentOffset.y + [self swipeVerticalInterval];
1201
1243
  // NSLog(@"Swiped down to %f", newOffset);
1202
- [self swipeVerticalScrollToOffset:newOffset];
1244
+ [self scrollToVerticalOffset:newOffset];
1203
1245
  }
1204
1246
 
1205
1247
  - (void)swipedLeft
1206
1248
  {
1207
- CGFloat newOffset = self.scrollView.contentOffset.x - [self swipeHorizontalInterval];
1249
+ if (!self.scrollView.scrollEnabled) {
1250
+ return;
1251
+ }
1252
+
1253
+ BOOL isRTL = [[RCTI18nUtil sharedInstance] isRTL];
1254
+ NSInteger horizontalInterval = [self swipeHorizontalInterval];
1255
+ CGFloat newOffset = self.scrollView.contentOffset.x + (isRTL ? horizontalInterval : -horizontalInterval);
1208
1256
  // NSLog(@"Swiped left to %f", newOffset);
1209
- [self swipeHorizontalScrollToOffset:newOffset];
1257
+ [self scrollToHorizontalOffset:newOffset];
1210
1258
  }
1211
1259
 
1212
1260
  - (void)swipedRight
1213
1261
  {
1214
- CGFloat newOffset = self.scrollView.contentOffset.x + [self swipeHorizontalInterval];
1262
+ if (!self.scrollView.scrollEnabled) {
1263
+ return;
1264
+ }
1265
+
1266
+ BOOL isRTL = [[RCTI18nUtil sharedInstance] isRTL];
1267
+ NSInteger horizontalInterval = [self swipeHorizontalInterval];
1268
+ CGFloat newOffset = self.scrollView.contentOffset.x + (isRTL ? -horizontalInterval : horizontalInterval);
1215
1269
  // NSLog(@"Swiped right to %f", newOffset);
1216
- [self swipeHorizontalScrollToOffset:newOffset];
1270
+ [self scrollToHorizontalOffset:newOffset];
1217
1271
  }
1218
1272
 
1219
1273
  - (void)addSwipeGestureRecognizers
@@ -99,11 +99,7 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
99
99
  NSMutableDictionary<NSAttributedStringKey, id> *defaultAttributes =
100
100
  [_backedTextInputView.defaultTextAttributes mutableCopy];
101
101
 
102
- #if !TARGET_OS_MACCATALYST
103
- RCTWeakEventEmitterWrapper *eventEmitterWrapper = [RCTWeakEventEmitterWrapper new];
104
- eventEmitterWrapper.eventEmitter = _eventEmitter;
105
- defaultAttributes[RCTAttributedStringEventEmitterKey] = eventEmitterWrapper;
106
- #endif
102
+ defaultAttributes[RCTAttributedStringEventEmitterKey] = RCTWrapEventEmitter(_eventEmitter);
107
103
 
108
104
  _backedTextInputView.defaultTextAttributes = defaultAttributes;
109
105
  }
@@ -263,10 +259,8 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
263
259
  if (newTextInputProps.textAttributes != oldTextInputProps.textAttributes) {
264
260
  NSMutableDictionary<NSAttributedStringKey, id> *defaultAttributes =
265
261
  RCTNSTextAttributesFromTextAttributes(newTextInputProps.getEffectiveTextAttributes(RCTFontSizeMultiplier()));
266
- #if !TARGET_OS_MACCATALYST
267
262
  defaultAttributes[RCTAttributedStringEventEmitterKey] =
268
263
  _backedTextInputView.defaultTextAttributes[RCTAttributedStringEventEmitterKey];
269
- #endif
270
264
  _backedTextInputView.defaultTextAttributes = defaultAttributes;
271
265
  }
272
266
 
@@ -582,7 +582,7 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
582
582
  [self handleFocusGuide];
583
583
  }
584
584
 
585
- if (context.nextFocusedView == self && self.isUserInteractionEnabled && ![self isTVFocusGuide]) {
585
+ if (context.nextFocusedView == self) {
586
586
  if(_eventEmitter) _eventEmitter->onFocus();
587
587
 
588
588
  [self becomeFirstResponder];
@@ -591,7 +591,10 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
591
591
  [self addParallaxMotionEffects];
592
592
  [self sendFocusNotification:context];
593
593
  } completion:^(void){}];
594
- } else {
594
+ // Without this check, onBlur would also trigger when `TVFocusGuideView` transfers focus to its children.
595
+ // [self isTVFocusGuide] is false when autofocus and destinations are not used, so we cannot use that.
596
+ // Generally speaking, it would happen for any non-collapsable `View`.
597
+ } else if (context.previouslyFocusedView == self) {
595
598
  if (_eventEmitter) _eventEmitter->onBlur();
596
599
 
597
600
  [self disableDirectionalFocusGuides];
@@ -142,6 +142,7 @@ using namespace facebook::react;
142
142
 
143
143
  if (!_view) {
144
144
  _view = [[RCTSurfaceView alloc] initWithSurface:(RCTSurface *)self];
145
+ [self _updateLayoutContext];
145
146
  _touchHandler = [RCTSurfaceTouchHandler new];
146
147
  [_touchHandler attachToView:_view];
147
148
  }
@@ -26,6 +26,7 @@ header_search_paths = [
26
26
  "\"$(PODS_TARGET_SRCROOT)/ReactCommon\"",
27
27
  "\"$(PODS_ROOT)/boost\"",
28
28
  "\"$(PODS_ROOT)/DoubleConversion\"",
29
+ "\"$(PODS_ROOT)/fast_float/include\"",
29
30
  "\"$(PODS_ROOT)/fmt/include\"",
30
31
  "\"$(PODS_ROOT)/RCT-Folly\"",
31
32
  "\"$(PODS_ROOT)/Headers/Private/React-Core\"",
@@ -303,7 +303,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
303
303
  [self handleFocusGuide];
304
304
  }
305
305
 
306
- if (context.nextFocusedView == self && ![self isTVFocusGuide] && self.isTVSelectable ) {
306
+ if (context.nextFocusedView == self) {
307
307
  if (self.onFocus) self.onFocus(nil);
308
308
  [self becomeFirstResponder];
309
309
  [self enableDirectionalFocusGuides];
@@ -311,7 +311,10 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
311
311
  [self addParallaxMotionEffects];
312
312
  [self sendFocusNotification:context];
313
313
  } completion:^(void){}];
314
- } else {
314
+ // Without this check, onBlur would also trigger when `TVFocusGuideView` transfers focus to its children.
315
+ // [self isTVFocusGuide] is false when autofocus and destinations are not used, so we cannot use that.
316
+ // Generally speaking, it would happen for any non-collapsable `View`.
317
+ } else if (context.previouslyFocusedView == self ) {
315
318
  if (self.onBlur) self.onBlur(nil);
316
319
  [self disableDirectionalFocusGuides];
317
320
  [coordinator addCoordinatedAnimations:^(void){