react-native-tvos 0.76.5-0 → 0.76.7-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 (62) 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/Components/Pressable/Pressable.d.ts +1 -1
  5. package/Libraries/Components/TV/TVFocusGuideView.js +0 -1
  6. package/Libraries/Components/View/ViewPropTypes.d.ts +4 -2
  7. package/Libraries/Core/ReactNativeVersion.js +1 -1
  8. package/Libraries/Image/RCTImageLoader.mm +9 -1
  9. package/Libraries/Pressability/Pressability.js +2 -2
  10. package/Libraries/Text/TextInput/RCTBaseTextInputView.mm +1 -1
  11. package/Libraries/Utilities/Appearance.js +3 -1
  12. package/React/Base/RCTConvert.mm +3 -1
  13. package/React/Base/RCTVersion.m +1 -1
  14. package/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm +2 -5
  15. package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +85 -31
  16. package/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +4 -0
  17. package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +17 -11
  18. package/React/Views/RCTTVView.m +5 -2
  19. package/React/Views/ScrollView/RCTScrollView.m +63 -26
  20. package/ReactAndroid/api/ReactAndroid.api +2 -0
  21. package/ReactAndroid/cmake-utils/ReactNative-application.cmake +18 -3
  22. package/ReactAndroid/gradle.properties +2 -2
  23. package/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.java +15 -8
  24. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +1 -7
  25. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +1 -11
  26. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +1 -3
  27. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +1 -3
  28. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +1 -12
  29. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +1 -3
  30. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java +1 -1
  31. package/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java +16 -2
  32. package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +1 -0
  33. package/ReactAndroid/src/main/jni/react/fabric/Binding.cpp +17 -19
  34. package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +1 -15
  35. package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +1 -4
  36. package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
  37. package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +1 -5
  38. package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +1 -6
  39. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +47 -65
  40. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +2 -4
  41. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +1 -5
  42. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +1 -2
  43. package/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm +9 -0
  44. package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +1 -6
  45. package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +1 -3
  46. package/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp +5 -0
  47. package/ReactCommon/react/renderer/attributedstring/TextAttributes.h +2 -0
  48. package/ReactCommon/react/renderer/attributedstring/conversions.h +5 -0
  49. package/ReactCommon/react/renderer/components/text/BaseTextProps.cpp +12 -0
  50. package/ReactCommon/react/renderer/textlayoutmanager/TextMeasureCache.h +2 -3
  51. package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +7 -3
  52. package/gradle/libs.versions.toml +1 -1
  53. package/package.json +8 -8
  54. package/scripts/cocoapods/utils.rb +6 -6
  55. package/scripts/codegen/generate-artifacts-executor.js +6 -6
  56. package/sdks/hermesc/linux64-bin/hermesc +0 -0
  57. package/sdks/hermesc/osx-bin/hermes +0 -0
  58. package/sdks/hermesc/osx-bin/hermesc +0 -0
  59. package/sdks/hermesc/win64-bin/hermesc.exe +0 -0
  60. package/src/private/featureflags/ReactNativeFeatureFlags.js +1 -6
  61. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +1 -2
  62. package/types/public/ReactNativeTVTypes.d.ts +9 -7
@@ -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
@@ -20,7 +20,7 @@ import {
20
20
  import {View} from '../View/View';
21
21
  import {AccessibilityProps} from '../View/ViewAccessibility';
22
22
  import {ViewProps} from '../View/ViewPropTypes';
23
- import {TVParallaxProperties} from '../../../types/public/ReactNativeTVTypes';
23
+ import {TVParallaxProperties} from '../../../types/index';
24
24
 
25
25
  export interface PressableStateCallbackType {
26
26
  readonly pressed: boolean;
@@ -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: 5,
19
+ patch: 7,
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];
@@ -440,7 +440,7 @@ export default class Pressability {
440
440
  _createEventHandlers(): EventHandlers {
441
441
  const tvPressEventHandlers = {
442
442
  onPressIn: (evt: any): void => {
443
- if (this._config.disabled === false) {
443
+ if (this._config.disabled === true) {
444
444
  return;
445
445
  }
446
446
 
@@ -461,7 +461,7 @@ export default class Pressability {
461
461
  }, delayLongPress + delayPressIn);
462
462
  },
463
463
  onPressOut: (evt: any): void => {
464
- if (this._config.disabled === false) {
464
+ if (this._config.disabled === true) {
465
465
  return;
466
466
  }
467
467
  this._cancelLongPressDelayTimeout();
@@ -461,7 +461,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame)
461
461
  _maxLength.integerValue - (NSInteger)backedTextInputView.attributedText.string.length + (NSInteger)range.length,
462
462
  0);
463
463
 
464
- if (text.length > _maxLength.integerValue) {
464
+ if (text.length > allowedLength) {
465
465
  // If we typed/pasted more than one character, limit the text inputted.
466
466
  if (text.length > 1) {
467
467
  if (allowedLength > 0) {
@@ -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
 
@@ -528,17 +528,19 @@ RCT_ENUM_CONVERTER(
528
528
  }),
529
529
  NSNotFound,
530
530
  unsignedIntegerValue)
531
+ #endif
531
532
  RCT_ENUM_CONVERTER(
532
533
  UIModalPresentationStyle,
533
534
  (@{
534
535
  @"fullScreen" : @(UIModalPresentationFullScreen),
536
+ #if !TARGET_OS_TV
535
537
  @"pageSheet" : @(UIModalPresentationPageSheet),
536
538
  @"formSheet" : @(UIModalPresentationFormSheet),
539
+ #endif
537
540
  @"overFullScreen" : @(UIModalPresentationOverFullScreen),
538
541
  }),
539
542
  UIModalPresentationFullScreen,
540
543
  integerValue)
541
- #endif
542
544
 
543
545
  RCT_ENUM_CONVERTER(
544
546
  UIViewContentMode,
@@ -23,7 +23,7 @@ NSDictionary* RCTGetReactNativeVersion(void)
23
23
  __rnVersion = @{
24
24
  RCTVersionMajor: @(0),
25
25
  RCTVersionMinor: @(76),
26
- RCTVersionPatch: @(5),
26
+ RCTVersionPatch: @(7),
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)
@@ -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,9 +99,11 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
99
99
  NSMutableDictionary<NSAttributedStringKey, id> *defaultAttributes =
100
100
  [_backedTextInputView.defaultTextAttributes mutableCopy];
101
101
 
102
+ #if !TARGET_OS_MACCATALYST
102
103
  RCTWeakEventEmitterWrapper *eventEmitterWrapper = [RCTWeakEventEmitterWrapper new];
103
104
  eventEmitterWrapper.eventEmitter = _eventEmitter;
104
105
  defaultAttributes[RCTAttributedStringEventEmitterKey] = eventEmitterWrapper;
106
+ #endif
105
107
 
106
108
  _backedTextInputView.defaultTextAttributes = defaultAttributes;
107
109
  }
@@ -261,8 +263,10 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
261
263
  if (newTextInputProps.textAttributes != oldTextInputProps.textAttributes) {
262
264
  NSMutableDictionary<NSAttributedStringKey, id> *defaultAttributes =
263
265
  RCTNSTextAttributesFromTextAttributes(newTextInputProps.getEffectiveTextAttributes(RCTFontSizeMultiplier()));
266
+ #if !TARGET_OS_MACCATALYST
264
267
  defaultAttributes[RCTAttributedStringEventEmitterKey] =
265
268
  _backedTextInputView.defaultTextAttributes[RCTAttributedStringEventEmitterKey];
269
+ #endif
266
270
  _backedTextInputView.defaultTextAttributes = defaultAttributes;
267
271
  }
268
272
 
@@ -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];
@@ -1656,15 +1659,10 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
1656
1659
  }
1657
1660
 
1658
1661
  // clipping
1662
+ self.currentContainerView.layer.mask = nil;
1659
1663
  if (self.currentContainerView.clipsToBounds) {
1660
1664
  BOOL clipToPaddingBox = ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox();
1661
- if (clipToPaddingBox) {
1662
- CALayer *maskLayer = [self createMaskLayer:RCTCGRectFromRect(_layoutMetrics.getPaddingFrame())
1663
- cornerInsets:RCTGetCornerInsets(
1664
- RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
1665
- RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths))];
1666
- self.currentContainerView.layer.mask = maskLayer;
1667
- } else {
1665
+ if (!clipToPaddingBox) {
1668
1666
  if (borderMetrics.borderRadii.isUniform()) {
1669
1667
  self.currentContainerView.layer.cornerRadius = borderMetrics.borderRadii.topLeft.horizontal;
1670
1668
  } else {
@@ -1686,9 +1684,17 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
1686
1684
  subview.layer.mask = [self createMaskLayer:subview.bounds cornerInsets:cornerInsets];
1687
1685
  }
1688
1686
  }
1687
+ } else if (
1688
+ !borderMetrics.borderWidths.isUniform() || borderMetrics.borderWidths.left != 0 ||
1689
+ !borderMetrics.borderRadii.isUniform()) {
1690
+ CALayer *maskLayer = [self createMaskLayer:RCTCGRectFromRect(_layoutMetrics.getPaddingFrame())
1691
+ cornerInsets:RCTGetCornerInsets(
1692
+ RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
1693
+ RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths))];
1694
+ self.currentContainerView.layer.mask = maskLayer;
1695
+ } else {
1696
+ self.currentContainerView.layer.cornerRadius = borderMetrics.borderRadii.topLeft.horizontal;
1689
1697
  }
1690
- } else {
1691
- self.currentContainerView.layer.mask = nil;
1692
1698
  }
1693
1699
  }
1694
1700
 
@@ -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){
@@ -1025,19 +1025,23 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop)
1025
1025
  [self sendBlurNotification];
1026
1026
  [self removeSwipeGestureRecognizers];
1027
1027
  [self resignFirstResponder];
1028
- // if we leave the scroll view and go up, then scroll to top; if going down,
1029
- // scroll to bottom
1030
- // Similarly for left and right
1031
- if (context.focusHeading == UIFocusHeadingUp && self.snapToStart) {
1032
- [self scrollToVerticalOffset:0.0];
1033
- } else if(context.focusHeading == UIFocusHeadingDown && self.snapToEnd) {
1034
- [self scrollToVerticalOffset:self.scrollView.contentSize.height];
1035
- } else if(context.focusHeading == UIFocusHeadingLeft && self.snapToStart) {
1036
- [self scrollToHorizontalOffset:0.0];
1037
- } else if(context.focusHeading == UIFocusHeadingRight && self.snapToEnd) {
1038
- [self scrollToHorizontalOffset:self.scrollView.contentSize.width];
1028
+ // If scrolling is enabled:
1029
+ // - Scroll to the top when moving up and to the bottom when moving down.
1030
+ // - Similarly, scroll towards leading edge when moving towards leading edge and to the trailing edge when moving towards the trailing edge.
1031
+ BOOL isRTL = [[RCTI18nUtil sharedInstance] isRTL];
1032
+ BOOL isMovingTowardsLeadingEdge = (isRTL ? context.focusHeading == UIFocusHeadingRight : context.focusHeading == UIFocusHeadingLeft);
1033
+ BOOL isMovingTowardsTrailingEdge = (isRTL ? context.focusHeading == UIFocusHeadingLeft : context.focusHeading == UIFocusHeadingRight);
1034
+ if (self.scrollView.isScrollEnabled) {
1035
+ if (context.focusHeading == UIFocusHeadingUp && self.snapToStart) {
1036
+ [self scrollToVerticalOffset:0.0];
1037
+ } else if(context.focusHeading == UIFocusHeadingDown && self.snapToEnd) {
1038
+ [self scrollToVerticalOffset:self.scrollView.contentSize.height];
1039
+ } else if(isMovingTowardsLeadingEdge && self.snapToStart) {
1040
+ [self scrollToHorizontalOffset:0.0];
1041
+ } else if(isMovingTowardsLeadingEdge && self.snapToEnd) {
1042
+ [self scrollToHorizontalOffset:self.scrollView.contentSize.width];
1043
+ }
1039
1044
  }
1040
-
1041
1045
  }
1042
1046
  }
1043
1047
 
@@ -1093,22 +1097,31 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop)
1093
1097
 
1094
1098
  - (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context
1095
1099
  {
1100
+ // If the previously focused item is this view and scrolling is disabled, defer to the superclass
1101
+ if (context.previouslyFocusedItem == self && !self.scrollView.isScrollEnabled) {
1102
+ return [super shouldUpdateFocusInContext:context];
1103
+ }
1104
+
1096
1105
  // Determine if the layout is Right-to-Left
1097
1106
  BOOL isRTL = [[RCTI18nUtil sharedInstance] isRTL];
1098
1107
  // Adjust for horizontal scrolling with RTL support
1099
1108
  if ([self isHorizontal:self.scrollView]) {
1100
- BOOL isNavigatingToEnd = (isRTL ? context.focusHeading == UIFocusHeadingLeft : context.focusHeading == UIFocusHeadingRight);
1101
- BOOL isNavigatingToStart = (isRTL ? context.focusHeading == UIFocusHeadingRight : context.focusHeading == UIFocusHeadingLeft);
1109
+ BOOL isMovingTowardsLeadingEdge = (isRTL ? context.focusHeading == UIFocusHeadingRight : context.focusHeading == UIFocusHeadingLeft);
1110
+ BOOL isMovingTowardsTrailingEdge = (isRTL ? context.focusHeading == UIFocusHeadingLeft : context.focusHeading == UIFocusHeadingRight);
1111
+
1112
+ BOOL isScrollingToLeading = (isMovingTowardsLeadingEdge && self.scrollView.contentOffset.x > 0);
1113
+ BOOL isScrollingToTrailing = (isMovingTowardsTrailingEdge && self.scrollView.contentOffset.x < self.scrollView.contentSize.width - MAX(self.scrollView.visibleSize.width, 1));
1102
1114
 
1103
- if ((isNavigatingToEnd && self.scrollView.contentOffset.x < self.scrollView.contentSize.width - self.scrollView.visibleSize.width) ||
1104
- (isNavigatingToStart && self.scrollView.contentOffset.x > 0)) {
1105
- return [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem];
1115
+ if (isScrollingToLeading || isScrollingToTrailing) {
1116
+ return (context.nextFocusedItem && [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem]);
1106
1117
  }
1107
1118
  } else {
1108
1119
  // Handle vertical scrolling as before
1109
- if ((context.focusHeading == UIFocusHeadingUp && self.scrollView.contentOffset.y > 0) ||
1110
- (context.focusHeading == UIFocusHeadingDown && self.scrollView.contentOffset.y < self.scrollView.contentSize.height - self.scrollView.visibleSize.height)) {
1111
- return [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem];
1120
+ BOOL isMovingUp = (context.focusHeading == UIFocusHeadingUp && self.scrollView.contentOffset.y > 0);
1121
+ BOOL isMovingDown = (context.focusHeading == UIFocusHeadingDown && self.scrollView.contentOffset.y < self.scrollView.contentSize.height - MAX(self.scrollView.visibleSize.height, 1));
1122
+
1123
+ if (isMovingUp || isMovingDown) {
1124
+ return (context.nextFocusedItem && [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem]);
1112
1125
  }
1113
1126
  }
1114
1127
  return [super shouldUpdateFocusInContext:context];
@@ -1154,11 +1167,21 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop)
1154
1167
  _blockFirstTouch = NO;
1155
1168
  dispatch_async(dispatch_get_main_queue(), ^{
1156
1169
  CGFloat limitedOffset = yOffset;
1170
+
1171
+ // Ensure content size and visible size are non-negative
1172
+ CGFloat contentHeight = MAX(self.scrollView.contentSize.height, 0.0);
1173
+ CGFloat visibleHeight = MAX(self.scrollView.visibleSize.height, 0.0);
1174
+
1175
+ // Compute the maximum offset, ensuring it's non-negative
1176
+ CGFloat maxOffset = MAX(contentHeight - visibleHeight, 0.0);
1177
+
1178
+ // Clamp the offset within valid bounds
1157
1179
  limitedOffset = MAX(limitedOffset, 0.0);
1158
- limitedOffset = MIN(limitedOffset, self.scrollView.contentSize.height - self.scrollView.visibleSize.height);
1180
+ limitedOffset = MIN(limitedOffset, maxOffset);
1181
+
1159
1182
  [UIView animateWithDuration:[self swipeDuration] animations:^{
1160
1183
  self.scrollView.contentOffset =
1161
- CGPointMake(self.scrollView.contentOffset.x, limitedOffset);
1184
+ CGPointMake(self.scrollView.contentOffset.x, limitedOffset);
1162
1185
  }];
1163
1186
  });
1164
1187
  }
@@ -1168,11 +1191,21 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop)
1168
1191
  _blockFirstTouch = NO;
1169
1192
  dispatch_async(dispatch_get_main_queue(), ^{
1170
1193
  CGFloat limitedOffset = xOffset;
1194
+
1195
+ // Ensure content size and visible size are non-negative
1196
+ CGFloat contentWidth = MAX(self.scrollView.contentSize.width, 0.0);
1197
+ CGFloat visibleWidth = MAX(self.scrollView.visibleSize.width, 0.0);
1198
+
1199
+ // Compute the maximum offset, ensuring it's non-negative
1200
+ CGFloat maxOffset = MAX(contentWidth - visibleWidth, 0.0);
1201
+
1202
+ // Clamp the offset within valid bounds
1171
1203
  limitedOffset = MAX(limitedOffset, 0.0);
1172
- limitedOffset = MIN(limitedOffset, self.scrollView.contentSize.width - self.scrollView.visibleSize.width);
1204
+ limitedOffset = MIN(limitedOffset, maxOffset);
1205
+
1173
1206
  [UIView animateWithDuration:[self swipeDuration] animations:^{
1174
1207
  self.scrollView.contentOffset =
1175
- CGPointMake(limitedOffset, self.scrollView.contentOffset.y);
1208
+ CGPointMake(limitedOffset, self.scrollView.contentOffset.y);
1176
1209
  }];
1177
1210
  });
1178
1211
  }
@@ -1205,7 +1238,9 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop)
1205
1238
  return;
1206
1239
  }
1207
1240
 
1208
- CGFloat newOffset = self.scrollView.contentOffset.x - [self swipeHorizontalInterval];
1241
+ BOOL isRTL = [[RCTI18nUtil sharedInstance] isRTL];
1242
+ NSInteger horizontalInterval = [self swipeHorizontalInterval];
1243
+ CGFloat newOffset = self.scrollView.contentOffset.x + (isRTL ? horizontalInterval : -horizontalInterval);
1209
1244
  // NSLog(@"Swiped left to %f", newOffset);
1210
1245
  [self scrollToHorizontalOffset:newOffset];
1211
1246
  }
@@ -1216,7 +1251,9 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop)
1216
1251
  return;
1217
1252
  }
1218
1253
 
1219
- CGFloat newOffset = self.scrollView.contentOffset.x + [self swipeHorizontalInterval];
1254
+ BOOL isRTL = [[RCTI18nUtil sharedInstance] isRTL];
1255
+ NSInteger horizontalInterval = [self swipeHorizontalInterval];
1256
+ CGFloat newOffset = self.scrollView.contentOffset.x + (isRTL ? -horizontalInterval : horizontalInterval);
1220
1257
  // NSLog(@"Swiped right to %f", newOffset);
1221
1258
  [self scrollToHorizontalOffset:newOffset];
1222
1259
  }