react-native-tvos 0.76.1-1 → 0.76.3-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 (61) hide show
  1. package/Libraries/AppDelegate/React-RCTAppDelegate.podspec +1 -1
  2. package/Libraries/Components/Pressable/Pressable.d.ts +8 -0
  3. package/Libraries/Components/Pressable/Pressable.js +4 -1
  4. package/Libraries/Core/ReactNativeVersion.js +2 -2
  5. package/Libraries/Core/setUpErrorHandling.js +1 -7
  6. package/Libraries/LogBox/Data/LogBoxData.js +2 -2
  7. package/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +1 -0
  8. package/Libraries/Types/CoreEventTypes.d.ts +3 -3
  9. package/README.md +9 -7
  10. package/React/Base/RCTTVRemoteHandler.m +0 -19
  11. package/React/Base/RCTTVRemoteSelectHandler.h +27 -0
  12. package/React/Base/RCTTVRemoteSelectHandler.m +120 -0
  13. package/React/Base/RCTVersion.m +2 -2
  14. package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +12 -8
  15. package/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +47 -3
  16. package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h +8 -0
  17. package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +35 -42
  18. package/React/Views/RCTTVView.h +9 -14
  19. package/React/Views/RCTTVView.m +34 -46
  20. package/React/Views/RCTView.h +19 -0
  21. package/React/Views/ScrollView/RCTScrollView.m +12 -8
  22. package/ReactAndroid/api/ReactAndroid.api +0 -1
  23. package/ReactAndroid/cmake-utils/ReactNative-application.cmake +3 -4
  24. package/ReactAndroid/gradle.properties +1 -1
  25. package/ReactAndroid/hermes-engine/build.gradle.kts +4 -0
  26. package/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt +2 -0
  27. package/ReactAndroid/src/main/java/com/facebook/react/modules/core/TimingModule.kt +0 -8
  28. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java +2 -2
  29. package/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +11 -3
  30. package/ReactCommon/cxxreact/ReactNativeVersion.h +2 -2
  31. package/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputShadowNode.cpp +3 -2
  32. package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h +12 -1
  33. package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +165 -2
  34. package/cli.js +1 -1
  35. package/index.js +0 -4
  36. package/package.json +8 -8
  37. package/scripts/codegen/generate-artifacts-executor.js +3 -3
  38. package/sdks/.hermesversion +1 -1
  39. package/sdks/hermesc/osx-bin/hermes +0 -0
  40. package/sdks/hermesc/osx-bin/hermesc +0 -0
  41. package/sdks/hermesc/win64-bin/hermesc.exe +0 -0
  42. package/sdks/hermesc/win64-bin/msvcp140.dll +0 -0
  43. package/sdks/hermesc/win64-bin/vcruntime140.dll +0 -0
  44. package/sdks/hermesc/win64-bin/vcruntime140_1.dll +0 -0
  45. package/types/modules/Codegen.d.ts +6 -0
  46. package/types/public/ReactNativeTVTypes.d.ts +1 -1
  47. package/Libraries/Components/TabBarIOS/RCTTabBarItemNativeComponent.js +0 -99
  48. package/Libraries/Components/TabBarIOS/RCTTabBarNativeComponent.js +0 -32
  49. package/Libraries/Components/TabBarIOS/TabBarIOS.ios.js +0 -59
  50. package/Libraries/Components/TabBarIOS/TabBarIOS.js +0 -52
  51. package/Libraries/Components/TabBarIOS/TabBarIOSProps.js +0 -52
  52. package/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js +0 -177
  53. package/Libraries/Components/TabBarIOS/TabBarItemIOS.js +0 -55
  54. package/React/Views/RCTTabBar.h +0 -22
  55. package/React/Views/RCTTabBar.m +0 -237
  56. package/React/Views/RCTTabBarItem.h +0 -35
  57. package/React/Views/RCTTabBarItem.m +0 -139
  58. package/React/Views/RCTTabBarItemManager.h +0 -12
  59. package/React/Views/RCTTabBarItemManager.m +0 -38
  60. package/React/Views/RCTTabBarManager.h +0 -12
  61. package/React/Views/RCTTabBarManager.m +0 -81
@@ -10,9 +10,10 @@
10
10
 
11
11
  #import <React/RCTView.h>
12
12
  #import <React/RCTBridge.h>
13
+ #import <React/RCTTVRemoteSelectHandler.h>
13
14
 
14
15
  // A RCTView with additional properties and methods for user interaction using the Apple TV focus engine.
15
- @interface RCTTVView : RCTView
16
+ @interface RCTTVView : RCTView <RCTTVRemoteSelectHandlerDelegate>
16
17
 
17
18
  /**
18
19
  * TV event handlers
@@ -29,6 +30,10 @@
29
30
  */
30
31
  @property (nonatomic, assign) BOOL hasTVPreferredFocus;
31
32
 
33
+ /**
34
+ * Select and longSelect event handler
35
+ */
36
+ @property (nonatomic, strong) RCTTVRemoteSelectHandler *tvRemoteSelectHandler;
32
37
  /**
33
38
  * Focus direction tags
34
39
  */
@@ -52,18 +57,13 @@
52
57
  @property (nonatomic, assign) BOOL trapFocusLeft;
53
58
  @property (nonatomic, assign) BOOL trapFocusRight;
54
59
 
55
-
56
- /**
57
- * Focus
58
- */
60
+ // These handlers are defined in RCTView
61
+ /*
59
62
  @property (nonatomic, copy) RCTBubblingEventBlock onFocus;
60
63
  @property (nonatomic, copy) RCTBubblingEventBlock onBlur;
61
-
62
- /**
63
- * TV Press Handlers
64
- */
65
64
  @property (nonatomic, copy) RCTDirectEventBlock onPressIn;
66
65
  @property (nonatomic, copy) RCTDirectEventBlock onPressOut;
66
+ */
67
67
 
68
68
  - (instancetype)initWithBridge:(RCTBridge *)bridge;
69
69
 
@@ -77,11 +77,6 @@
77
77
  */
78
78
  - (void)sendBlurNotification:(UIFocusUpdateContext *)context;
79
79
 
80
- /**
81
- * Send Select Notification to listeners
82
- */
83
- - (void)sendSelectNotification:(UIGestureRecognizer *)recognizer;
84
-
85
80
  /**
86
81
  * Adds Parallax Motion Effects if tvParallaxProperty is enabled
87
82
  */
@@ -22,7 +22,6 @@
22
22
 
23
23
  @implementation RCTTVView {
24
24
  __weak RCTBridge *_bridge;
25
- UILongPressGestureRecognizer * _pressRecognizer;
26
25
  BOOL motionEffectsAdded;
27
26
  NSArray* focusDestinations;
28
27
  id<UIFocusItem> previouslyFocusedItem;
@@ -76,65 +75,49 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
76
75
  {
77
76
  self->_isTVSelectable = isTVSelectable;
78
77
  if (isTVSelectable && ![self isTVFocusGuide]) {
79
- UILongPressGestureRecognizer *pressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlePress:)];
80
- pressRecognizer.allowedPressTypes = @[ @(UIPressTypeSelect) ];
81
- pressRecognizer.minimumPressDuration = 0;
82
-
83
- _pressRecognizer = pressRecognizer;
84
-
85
- [self addGestureRecognizer:_pressRecognizer];
78
+ self.tvRemoteSelectHandler = [[RCTTVRemoteSelectHandler alloc] initWithView:self];
86
79
  } else {
87
- if (_pressRecognizer) {
88
- [self removeGestureRecognizer:_pressRecognizer];
89
- }
80
+ self.tvRemoteSelectHandler = nil;
90
81
  }
91
82
  }
92
83
 
93
- - (void)animatePress
84
+ - (void)animatePressIn
94
85
  {
95
86
  if ([self.tvParallaxProperties[@"enabled"] boolValue] == YES) {
96
- float magnification = [self.tvParallaxProperties[@"magnification"] floatValue];
97
87
  float pressMagnification = [self.tvParallaxProperties[@"pressMagnification"] floatValue];
98
-
99
- // Duration of press animation
100
88
  float pressDuration = [self.tvParallaxProperties[@"pressDuration"] floatValue];
101
-
102
- // Delay of press animation
103
- float pressDelay = [self.tvParallaxProperties[@"pressDelay"] floatValue];
104
-
105
- [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:pressDelay]];
106
-
107
89
  [UIView animateWithDuration:(pressDuration/2)
108
90
  animations:^{
109
91
  self.transform = CGAffineTransformMakeScale(pressMagnification, pressMagnification);
110
92
  }
111
- completion:^(__unused BOOL finished1){
112
- [UIView animateWithDuration:(pressDuration/2)
113
- animations:^{
114
- self.transform = CGAffineTransformMakeScale(magnification, magnification);
115
- }
116
- completion:^(__unused BOOL finished2) {
117
- }];
118
- }];
93
+ completion:^(__unused BOOL finished){}];
119
94
  }
120
95
  }
121
96
 
122
- - (void)handlePress:(UIGestureRecognizer *)r
97
+ - (void) animatePressOut
123
98
  {
124
- switch (r.state) {
125
- case UIGestureRecognizerStateBegan:
126
- if (self.onPressIn) self.onPressIn(nil);
127
- break;
128
- case UIGestureRecognizerStateEnded:
129
- case UIGestureRecognizerStateCancelled:
130
- [self animatePress];
131
- if (self.onPressOut) self.onPressOut(nil);
132
- break;
133
- default:
134
- break;
99
+ if ([self.tvParallaxProperties[@"enabled"] boolValue] == YES) {
100
+ float magnification = [self.tvParallaxProperties[@"magnification"] floatValue];
101
+ float pressDuration = [self.tvParallaxProperties[@"pressDuration"] floatValue];
102
+
103
+ [UIView animateWithDuration:(pressDuration/2)
104
+ animations:^{
105
+ self.transform = CGAffineTransformMakeScale(magnification, magnification);
106
+ }
107
+ completion:^(__unused BOOL finished){}];
135
108
  }
136
109
  }
137
110
 
111
+ - (void)emitPressInEvent
112
+ {
113
+ if (self.onPressIn) self.onPressIn(nil);
114
+ }
115
+
116
+ - (void)emitPressOutEvent
117
+ {
118
+ if (self.onPressOut) self.onPressOut(nil);
119
+ }
120
+
138
121
  - (BOOL)isTVFocusGuide
139
122
  {
140
123
  return self.focusGuide != nil;
@@ -441,14 +424,19 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
441
424
  [[NSNotificationCenter defaultCenter] postNavigationBlurEventWithTag:self.reactTag target:self.reactTag];
442
425
  }
443
426
 
444
- - (void)sendSelectNotification:(UIGestureRecognizer *)recognizer
427
+ - (void)sendSelectNotification
445
428
  {
446
429
  [[NSNotificationCenter defaultCenter] postNavigationPressEventWithType:RCTTVRemoteEventSelect keyAction:RCTTVRemoteEventKeyActionUp tag:self.reactTag target:self.reactTag];
447
430
  }
448
431
 
449
- - (void)sendLongSelectNotification:(UIGestureRecognizer *)recognizer
432
+ - (void)sendLongSelectBeganNotification
433
+ {
434
+ [[NSNotificationCenter defaultCenter] postNavigationPressEventWithType:RCTTVRemoteEventLongSelect keyAction:RCTTVRemoteEventKeyActionDown tag:self.reactTag target:self.reactTag];
435
+ }
436
+
437
+ - (void)sendLongSelectEndedNotification
450
438
  {
451
- [[NSNotificationCenter defaultCenter] postNavigationPressEventWithType:RCTTVRemoteEventLongSelect keyAction:recognizer.eventKeyAction tag:self.reactTag target:self.reactTag];
439
+ [[NSNotificationCenter defaultCenter] postNavigationPressEventWithType:RCTTVRemoteEventLongSelect keyAction:RCTTVRemoteEventKeyActionUp tag:self.reactTag target:self.reactTag];
452
440
  }
453
441
 
454
442
  - (RCTTVView *)getViewById:(NSNumber *)viewId {
@@ -468,7 +456,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
468
456
  }
469
457
 
470
458
  - (void)setNextFocusDown:(NSNumber *)nextFocusDown {
471
- if (self.focusGuideDown != nil && nextFocusDown) {
459
+ if (self.focusGuideDown != nil && nextFocusDown == nil) {
472
460
  [[self rootView] removeLayoutGuide:self.focusGuideDown];
473
461
  self.focusGuideDown = nil;
474
462
  } else {
@@ -488,7 +476,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
488
476
  }
489
477
 
490
478
  - (void)setNextFocusRight:(NSNumber *)nextFocusRight {
491
- if (self.focusGuideRight != nil && nextFocusRight) {
479
+ if (self.focusGuideRight != nil && nextFocusRight == nil) {
492
480
  [[self rootView] removeLayoutGuide:self.focusGuideRight];
493
481
  self.focusGuideRight = nil;
494
482
  } else {
@@ -123,6 +123,25 @@ extern const UIAccessibilityTraits SwitchAccessibilityTrait;
123
123
 
124
124
  @property (nonatomic, assign) RCTCursor cursor;
125
125
 
126
+ #if TARGET_OS_TV
127
+ // For Paper, all views on TV might have focus, blur, pressIn, pressOut handlers,
128
+ // so we need to add these properties here and not in RCTTVView,
129
+ // since some views do not inherit from RCTTVView.
130
+
131
+ /**
132
+ * Focus
133
+ */
134
+ @property (nonatomic, copy) RCTBubblingEventBlock onFocus;
135
+ @property (nonatomic, copy) RCTBubblingEventBlock onBlur;
136
+
137
+ /**
138
+ * TV Press Handlers
139
+ */
140
+ @property (nonatomic, copy) RCTDirectEventBlock onPressIn;
141
+ @property (nonatomic, copy) RCTDirectEventBlock onPressOut;
142
+
143
+ #endif // TARGET_OS_TV
144
+
126
145
  /**
127
146
  * (Experimental and unused for Paper) Pointer event handlers.
128
147
  */
@@ -23,6 +23,7 @@
23
23
  #if TARGET_OS_TV
24
24
  #import "RCTTVRemoteHandler.h"
25
25
  #import "RCTTVNavigationEventNotification.h"
26
+ #import "React/RCTI18nUtil.h"
26
27
  #endif
27
28
 
28
29
  #if !TARGET_OS_TV
@@ -1086,21 +1087,24 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop)
1086
1087
 
1087
1088
  - (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context
1088
1089
  {
1089
- // Keep focus inside the scroll view till the end of the content
1090
+ // Determine if the layout is Right-to-Left
1091
+ BOOL isRTL = [[RCTI18nUtil sharedInstance] isRTL];
1092
+ // Adjust for horizontal scrolling with RTL support
1090
1093
  if ([self isHorizontal:self.scrollView]) {
1091
- if ((context.focusHeading == UIFocusHeadingLeft && self.scrollView.contentOffset.x > 0)
1092
- || (context.focusHeading == UIFocusHeadingRight && self.scrollView.contentOffset.x < self.scrollView.contentSize.width - self.scrollView.visibleSize.width)
1093
- ) {
1094
+ BOOL isNavigatingToEnd = (isRTL ? context.focusHeading == UIFocusHeadingLeft : context.focusHeading == UIFocusHeadingRight);
1095
+ BOOL isNavigatingToStart = (isRTL ? context.focusHeading == UIFocusHeadingRight : context.focusHeading == UIFocusHeadingLeft);
1096
+
1097
+ if ((isNavigatingToEnd && self.scrollView.contentOffset.x < self.scrollView.contentSize.width - self.scrollView.visibleSize.width) ||
1098
+ (isNavigatingToStart && self.scrollView.contentOffset.x > 0)) {
1094
1099
  return [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem];
1095
1100
  }
1096
1101
  } else {
1097
- if ((context.focusHeading == UIFocusHeadingUp && self.scrollView.contentOffset.y > 0)
1098
- || (context.focusHeading == UIFocusHeadingDown && self.scrollView.contentOffset.y < self.scrollView.contentSize.height - self.scrollView.visibleSize.height)
1099
- ) {
1102
+ // Handle vertical scrolling as before
1103
+ if ((context.focusHeading == UIFocusHeadingUp && self.scrollView.contentOffset.y > 0) ||
1104
+ (context.focusHeading == UIFocusHeadingDown && self.scrollView.contentOffset.y < self.scrollView.contentSize.height - self.scrollView.visibleSize.height)) {
1100
1105
  return [UIFocusSystem environment:self containsEnvironment:context.nextFocusedItem];
1101
1106
  }
1102
1107
  }
1103
-
1104
1108
  return [super shouldUpdateFocusInContext:context];
1105
1109
  }
1106
1110
 
@@ -3306,7 +3306,6 @@ public final class com/facebook/react/modules/core/TimingModule : com/facebook/f
3306
3306
  public fun createTimer (DDDZ)V
3307
3307
  public fun deleteTimer (D)V
3308
3308
  public fun emitTimeDriftWarning (Ljava/lang/String;)V
3309
- public fun initialize ()V
3310
3309
  public fun invalidate ()V
3311
3310
  public fun setSendIdleEvents (Z)V
3312
3311
  }
@@ -31,12 +31,11 @@ if(CCACHE_FOUND)
31
31
  endif(CCACHE_FOUND)
32
32
 
33
33
  set(BUILD_DIR ${PROJECT_BUILD_DIR})
34
- if(CMAKE_HOST_WIN32)
35
- string(REPLACE "\\" "/" BUILD_DIR ${BUILD_DIR})
36
- endif()
34
+ file(TO_CMAKE_PATH "${BUILD_DIR}" BUILD_DIR)
35
+ file(TO_CMAKE_PATH "${REACT_ANDROID_DIR}" REACT_ANDROID_DIR)
37
36
 
38
37
  file(GLOB input_SRC CONFIGURE_DEPENDS
39
- *.cpp
38
+ ${REACT_ANDROID_DIR}/cmake-utils/default-app-setup/*.cpp
40
39
  ${BUILD_DIR}/generated/autolinking/src/main/jni/*.cpp)
41
40
 
42
41
  add_library(${CMAKE_PROJECT_NAME} SHARED ${input_SRC})
@@ -1,4 +1,4 @@
1
- VERSION_NAME=0.76.1-1
1
+ VERSION_NAME=0.76.3-0
2
2
  react.internal.publishingGroup=io.github.react-native-tvos
3
3
 
4
4
  android.useAndroidX=true
@@ -38,9 +38,13 @@ fun getSDKPath(): String {
38
38
  fun getSDKManagerPath(): String {
39
39
  val metaSdkManagerPath = File("${getSDKPath()}/cmdline-tools/latest/bin/sdkmanager")
40
40
  val ossSdkManagerPath = File("${getSDKPath()}/tools/bin/sdkmanager")
41
+ val windowsMetaSdkManagerPath = File("${getSDKPath()}/cmdline-tools/latest/bin/sdkmanager.bat")
42
+ val windowsOssSdkManagerPath = File("${getSDKPath()}/tools/bin/sdkmanager.bat")
41
43
  return when {
42
44
  metaSdkManagerPath.exists() -> metaSdkManagerPath.absolutePath
45
+ windowsMetaSdkManagerPath.exists() -> windowsMetaSdkManagerPath.absolutePath
43
46
  ossSdkManagerPath.exists() -> ossSdkManagerPath.absolutePath
47
+ windowsOssSdkManagerPath.exists() -> windowsOssSdkManagerPath.absolutePath
44
48
  else -> throw GradleException("Could not find sdkmanager executable.")
45
49
  }
46
50
  }
@@ -65,6 +65,7 @@ public open class JavaTimerManager(
65
65
 
66
66
  init {
67
67
  reactApplicationContext.addLifecycleEventListener(this)
68
+ HeadlessJsTaskContext.getInstance(reactApplicationContext).addTaskEventListener(this)
68
69
  }
69
70
 
70
71
  override fun onHostPause() {
@@ -103,6 +104,7 @@ public open class JavaTimerManager(
103
104
  }
104
105
 
105
106
  public open fun onInstanceDestroy() {
107
+ HeadlessJsTaskContext.getInstance(reactApplicationContext).removeTaskEventListener(this)
106
108
  reactApplicationContext.removeLifecycleEventListener(this)
107
109
  clearFrameCallback()
108
110
  clearChoreographerIdleCallback()
@@ -12,7 +12,6 @@ import com.facebook.react.bridge.ReactApplicationContext
12
12
  import com.facebook.react.bridge.WritableArray
13
13
  import com.facebook.react.common.annotations.VisibleForTesting
14
14
  import com.facebook.react.devsupport.interfaces.DevSupportManager
15
- import com.facebook.react.jstasks.HeadlessJsTaskContext
16
15
  import com.facebook.react.module.annotations.ReactModule
17
16
 
18
17
  /** Native module for JS timer execution. Timers fire on frame boundaries. */
@@ -24,11 +23,6 @@ public class TimingModule(
24
23
  private val javaTimerManager: JavaTimerManager =
25
24
  JavaTimerManager(reactContext, this, ReactChoreographer.getInstance(), devSupportManager)
26
25
 
27
- override fun initialize() {
28
- HeadlessJsTaskContext.getInstance(getReactApplicationContext())
29
- .addTaskEventListener(javaTimerManager)
30
- }
31
-
32
26
  override fun createTimer(
33
27
  callbackIDDouble: Double,
34
28
  durationDouble: Double,
@@ -68,8 +62,6 @@ public class TimingModule(
68
62
  }
69
63
 
70
64
  override fun invalidate() {
71
- val headlessJsTaskContext = HeadlessJsTaskContext.getInstance(getReactApplicationContext())
72
- headlessJsTaskContext.removeTaskEventListener(javaTimerManager)
73
65
  javaTimerManager.onInstanceDestroy()
74
66
  }
75
67
 
@@ -17,6 +17,6 @@ public class ReactNativeVersion {
17
17
  public static final Map<String, Object> VERSION = MapBuilder.<String, Object>of(
18
18
  "major", 0,
19
19
  "minor", 76,
20
- "patch", 1,
21
- "prerelease", "1");
20
+ "patch", 3,
21
+ "prerelease", "0");
22
22
  }
@@ -303,7 +303,14 @@ public class ReactModalHostView(context: ThemedReactContext) :
303
303
  * changed. This has the pleasant side-effect of us not having to preface all Modals with "top:
304
304
  * statusBarHeight", since that margin will be included in the FrameLayout.
305
305
  */
306
- get() = FrameLayout(context).apply { addView(dialogRootViewGroup) }
306
+ get() =
307
+ FrameLayout(context).apply {
308
+ addView(dialogRootViewGroup)
309
+ if (!statusBarTranslucent) {
310
+ // this is needed to prevent content hiding behind systems bars < API 30
311
+ this.fitsSystemWindows = true
312
+ }
313
+ }
307
314
 
308
315
  /**
309
316
  * updateProperties will update the properties that do not require us to recreate the dialog
@@ -401,8 +408,9 @@ public class ReactModalHostView(context: ThemedReactContext) :
401
408
  private var viewHeight = 0
402
409
  private val jSTouchDispatcher: JSTouchDispatcher = JSTouchDispatcher(this)
403
410
  private var jSPointerDispatcher: JSPointerDispatcher? = null
404
- internal val androidHWInputDeviceHelper: ReactAndroidHWInputDeviceHelper
405
- get() = ReactAndroidHWInputDeviceHelper()
411
+ internal val androidHWInputDeviceHelper: ReactAndroidHWInputDeviceHelper by lazy {
412
+ ReactAndroidHWInputDeviceHelper()
413
+ }
406
414
 
407
415
  internal val reactContext: ThemedReactContext
408
416
  get() = context as ThemedReactContext
@@ -17,8 +17,8 @@ namespace facebook::react {
17
17
  constexpr struct {
18
18
  int32_t Major = 0;
19
19
  int32_t Minor = 76;
20
- int32_t Patch = 1;
21
- std::string_view Prerelease = "1";
20
+ int32_t Patch = 3;
21
+ std::string_view Prerelease = "0";
22
22
  } ReactNativeVersion;
23
23
 
24
24
  } // namespace facebook::react
@@ -83,7 +83,7 @@ AttributedString TextInputShadowNode::getAttributedString(
83
83
  .string = getConcreteProps().text,
84
84
  .textAttributes = textAttributes,
85
85
  // TODO: Is this really meant to be by value?
86
- .parentShadowView = ShadowView{}});
86
+ .parentShadowView = ShadowView(*this)});
87
87
 
88
88
  auto attachments = Attachments{};
89
89
  BaseTextShadowNode::buildAttributedString(
@@ -110,7 +110,8 @@ void TextInputShadowNode::updateStateIfNeeded(
110
110
  (!state.layoutManager || state.layoutManager == textLayoutManager_) &&
111
111
  "`StateData` refers to a different `TextLayoutManager`");
112
112
 
113
- if (state.reactTreeAttributedString == reactTreeAttributedString &&
113
+ if (state.reactTreeAttributedString.isContentEqual(
114
+ reactTreeAttributedString) &&
114
115
  state.layoutManager == textLayoutManager_) {
115
116
  return;
116
117
  }
@@ -22,7 +22,7 @@ NSString *const RCTTextAttributesAccessibilityRoleAttributeName = @"Accessibilit
22
22
  /*
23
23
  * Creates `NSTextAttributes` from given `facebook::react::TextAttributes`
24
24
  */
25
- NSDictionary<NSAttributedStringKey, id> *RCTNSTextAttributesFromTextAttributes(
25
+ NSMutableDictionary<NSAttributedStringKey, id> *RCTNSTextAttributesFromTextAttributes(
26
26
  const facebook::react::TextAttributes &textAttributes);
27
27
 
28
28
  /*
@@ -41,6 +41,17 @@ NSString *RCTNSStringFromStringApplyingTextTransform(NSString *string, facebook:
41
41
 
42
42
  void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText);
43
43
 
44
+ /*
45
+ * Whether two `NSAttributedString` lead to the same underlying displayed text, even if they are not strictly equal.
46
+ * I.e. is one string substitutable for the other when backing a control (which may have some ignorable attributes
47
+ * provided).
48
+ */
49
+ BOOL RCTIsAttributedStringEffectivelySame(
50
+ NSAttributedString *text1,
51
+ NSAttributedString *text2,
52
+ NSDictionary<NSAttributedStringKey, id> *insensitiveAttributes,
53
+ const facebook::react::TextAttributes &baseTextAttributes);
54
+
44
55
  @interface RCTWeakEventEmitterWrapper : NSObject
45
56
  @property (nonatomic, assign) facebook::react::SharedEventEmitter eventEmitter;
46
57
  @end
@@ -35,6 +35,24 @@ using namespace facebook::react;
35
35
  _weakEventEmitter.reset();
36
36
  }
37
37
 
38
+ - (BOOL)isEqual:(id)object
39
+ {
40
+ // We consider the underlying EventEmitter as the identity
41
+ if (![object isKindOfClass:[self class]]) {
42
+ return NO;
43
+ }
44
+
45
+ auto thisEventEmitter = [self eventEmitter];
46
+ auto otherEventEmitter = [((RCTWeakEventEmitterWrapper *)object) eventEmitter];
47
+ return thisEventEmitter == otherEventEmitter;
48
+ }
49
+
50
+ - (NSUInteger)hash
51
+ {
52
+ // We consider the underlying EventEmitter as the identity
53
+ return (NSUInteger)_weakEventEmitter.lock().get();
54
+ }
55
+
38
56
  @end
39
57
 
40
58
  inline static UIFontWeight RCTUIFontWeightFromInteger(NSInteger fontWeight)
@@ -182,7 +200,8 @@ inline static UIColor *RCTEffectiveBackgroundColorFromTextAttributes(const TextA
182
200
  return effectiveBackgroundColor ?: [UIColor clearColor];
183
201
  }
184
202
 
185
- NSDictionary<NSAttributedStringKey, id> *RCTNSTextAttributesFromTextAttributes(const TextAttributes &textAttributes)
203
+ NSMutableDictionary<NSAttributedStringKey, id> *RCTNSTextAttributesFromTextAttributes(
204
+ const TextAttributes &textAttributes)
186
205
  {
187
206
  NSMutableDictionary<NSAttributedStringKey, id> *attributes = [NSMutableDictionary dictionaryWithCapacity:10];
188
207
 
@@ -306,7 +325,7 @@ NSDictionary<NSAttributedStringKey, id> *RCTNSTextAttributesFromTextAttributes(c
306
325
  attributes[RCTTextAttributesAccessibilityRoleAttributeName] = [NSString stringWithUTF8String:roleStr.c_str()];
307
326
  }
308
327
 
309
- return [attributes copy];
328
+ return attributes;
310
329
  }
311
330
 
312
331
  void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText)
@@ -470,3 +489,147 @@ NSString *RCTNSStringFromStringApplyingTextTransform(NSString *string, TextTrans
470
489
  return string;
471
490
  }
472
491
  }
492
+
493
+ static BOOL RCTIsParagraphStyleEffectivelySame(
494
+ NSParagraphStyle *style1,
495
+ NSParagraphStyle *style2,
496
+ const TextAttributes &baseTextAttributes)
497
+ {
498
+ if (style1 == nil || style2 == nil) {
499
+ return style1 == nil && style2 == nil;
500
+ }
501
+
502
+ // The NSParagraphStyle included as part of typingAttributes may eventually resolve "natural" directions to
503
+ // physical direction, so we should compare resolved directions
504
+ auto naturalAlignment =
505
+ baseTextAttributes.layoutDirection.value_or(LayoutDirection::LeftToRight) == LayoutDirection::LeftToRight
506
+ ? NSTextAlignmentLeft
507
+ : NSTextAlignmentRight;
508
+
509
+ NSWritingDirection naturalBaseWritingDirection = baseTextAttributes.baseWritingDirection.has_value()
510
+ ? RCTNSWritingDirectionFromWritingDirection(baseTextAttributes.baseWritingDirection.value())
511
+ : [NSParagraphStyle defaultWritingDirectionForLanguage:nil];
512
+
513
+ if (style1.alignment == NSTextAlignmentNatural || style1.baseWritingDirection == NSWritingDirectionNatural) {
514
+ NSMutableParagraphStyle *mutableStyle1 = [style1 mutableCopy];
515
+ style1 = mutableStyle1;
516
+
517
+ if (mutableStyle1.alignment == NSTextAlignmentNatural) {
518
+ mutableStyle1.alignment = naturalAlignment;
519
+ }
520
+
521
+ if (mutableStyle1.baseWritingDirection == NSWritingDirectionNatural) {
522
+ mutableStyle1.baseWritingDirection = naturalBaseWritingDirection;
523
+ }
524
+ }
525
+
526
+ if (style2.alignment == NSTextAlignmentNatural || style2.baseWritingDirection == NSWritingDirectionNatural) {
527
+ NSMutableParagraphStyle *mutableStyle2 = [style2 mutableCopy];
528
+ style2 = mutableStyle2;
529
+
530
+ if (mutableStyle2.alignment == NSTextAlignmentNatural) {
531
+ mutableStyle2.alignment = naturalAlignment;
532
+ }
533
+
534
+ if (mutableStyle2.baseWritingDirection == NSWritingDirectionNatural) {
535
+ mutableStyle2.baseWritingDirection = naturalBaseWritingDirection;
536
+ }
537
+ }
538
+
539
+ return [style1 isEqual:style2];
540
+ }
541
+
542
+ static BOOL RCTIsAttributeEffectivelySame(
543
+ NSAttributedStringKey attributeKey,
544
+ NSDictionary<NSAttributedStringKey, id> *attributes1,
545
+ NSDictionary<NSAttributedStringKey, id> *attributes2,
546
+ NSDictionary<NSAttributedStringKey, id> *insensitiveAttributes,
547
+ const TextAttributes &baseTextAttributes)
548
+ {
549
+ id attribute1 = attributes1[attributeKey] ?: insensitiveAttributes[attributeKey];
550
+ id attribute2 = attributes2[attributeKey] ?: insensitiveAttributes[attributeKey];
551
+
552
+ // Normalize attributes which can inexact but still effectively the same
553
+ if ([attributeKey isEqualToString:NSParagraphStyleAttributeName]) {
554
+ return RCTIsParagraphStyleEffectivelySame(attribute1, attribute2, baseTextAttributes);
555
+ }
556
+
557
+ // Otherwise rely on built-in comparison
558
+ return [attribute1 isEqual:attribute2];
559
+ }
560
+
561
+ BOOL RCTIsAttributedStringEffectivelySame(
562
+ NSAttributedString *text1,
563
+ NSAttributedString *text2,
564
+ NSDictionary<NSAttributedStringKey, id> *insensitiveAttributes,
565
+ const TextAttributes &baseTextAttributes)
566
+ {
567
+ if (![text1.string isEqualToString:text2.string]) {
568
+ return NO;
569
+ }
570
+
571
+ // We check that for every fragment in the old string
572
+ // 1. The new string's fragment overlapping the first spans the same characters
573
+ // 2. The attributes of each matching fragment are the same, ignoring those which match insensitive attibutes
574
+ __block BOOL areAttributesSame = YES;
575
+ [text1 enumerateAttributesInRange:NSMakeRange(0, text1.length)
576
+ options:0
577
+ usingBlock:^(
578
+ NSDictionary<NSAttributedStringKey, id> *text1Attributes,
579
+ NSRange text1Range,
580
+ BOOL *text1Stop) {
581
+ [text2 enumerateAttributesInRange:text1Range
582
+ options:0
583
+ usingBlock:^(
584
+ NSDictionary<NSAttributedStringKey, id> *text2Attributes,
585
+ NSRange text2Range,
586
+ BOOL *text2Stop) {
587
+ if (!NSEqualRanges(text1Range, text2Range)) {
588
+ areAttributesSame = NO;
589
+ *text1Stop = YES;
590
+ *text2Stop = YES;
591
+ return;
592
+ }
593
+
594
+ // Compare every attribute in text1 to the corresponding attribute
595
+ // in text2, or the set of insensitive attributes if not present
596
+ for (NSAttributedStringKey key in text1Attributes) {
597
+ if (!RCTIsAttributeEffectivelySame(
598
+ key,
599
+ text1Attributes,
600
+ text2Attributes,
601
+ insensitiveAttributes,
602
+ baseTextAttributes)) {
603
+ areAttributesSame = NO;
604
+ *text1Stop = YES;
605
+ *text2Stop = YES;
606
+ return;
607
+ }
608
+ }
609
+
610
+ for (NSAttributedStringKey key in text2Attributes) {
611
+ // We have already compared this attribute if it is present in
612
+ // both
613
+ if (text1Attributes[key] != nil) {
614
+ continue;
615
+ }
616
+
617
+ // But we still need to compare attributes if it is only present
618
+ // in text 2, to compare against insensitive attributes
619
+ if (!RCTIsAttributeEffectivelySame(
620
+ key,
621
+ text1Attributes,
622
+ text2Attributes,
623
+ insensitiveAttributes,
624
+ baseTextAttributes)) {
625
+ areAttributesSame = NO;
626
+ *text1Stop = YES;
627
+ *text2Stop = YES;
628
+ return;
629
+ }
630
+ }
631
+ }];
632
+ }];
633
+
634
+ return areAttributesSame;
635
+ }
package/cli.js CHANGED
@@ -204,7 +204,7 @@ async function main() {
204
204
 
205
205
  const proc = spawn(
206
206
  'npx',
207
- ['@react-native-community/cli', ...process.argv.slice(2)],
207
+ ['@react-native-community/cli@latest', ...process.argv.slice(2)],
208
208
  {
209
209
  stdio: 'inherit',
210
210
  },