react-native-tvos 0.76.1-0 → 0.76.2-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/Libraries/AppDelegate/React-RCTAppDelegate.podspec +1 -1
  2. package/Libraries/Components/Pressable/Pressable.d.ts +9 -1
  3. package/Libraries/Components/Pressable/Pressable.js +4 -16
  4. package/Libraries/Components/TV/TVViewPropTypes.js +2 -1
  5. package/Libraries/Components/TextInput/TextInput.d.ts +1 -1
  6. package/Libraries/Components/Touchable/Touchable.js +0 -43
  7. package/Libraries/Components/Touchable/TouchableBounce.js +0 -33
  8. package/Libraries/Components/Touchable/TouchableHighlight.js +12 -47
  9. package/Libraries/Components/Touchable/TouchableNativeFeedback.js +0 -33
  10. package/Libraries/Components/Touchable/TouchableOpacity.js +12 -44
  11. package/Libraries/Components/Touchable/TouchableWithoutFeedback.js +0 -19
  12. package/Libraries/Components/View/ViewNativeComponent.js +6 -0
  13. package/Libraries/Components/View/ViewPropTypes.d.ts +12 -1
  14. package/Libraries/Components/View/ViewPropTypes.js +7 -0
  15. package/Libraries/Core/ReactNativeVersion.js +1 -1
  16. package/Libraries/Core/setUpErrorHandling.js +1 -7
  17. package/Libraries/LogBox/Data/LogBoxData.js +2 -2
  18. package/Libraries/NativeComponent/BaseViewConfig.android.js +19 -0
  19. package/Libraries/NativeComponent/BaseViewConfig.ios.js +6 -0
  20. package/Libraries/NativeComponent/TVViewConfig.js +4 -0
  21. package/Libraries/Pressability/Pressability.js +45 -28
  22. package/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +1 -0
  23. package/Libraries/Types/CoreEventTypes.d.ts +21 -0
  24. package/Libraries/Types/CoreEventTypes.js +6 -0
  25. package/README.md +9 -7
  26. package/React/Base/RCTTVRemoteHandler.m +0 -19
  27. package/React/Base/RCTTVRemoteSelectHandler.h +27 -0
  28. package/React/Base/RCTTVRemoteSelectHandler.m +120 -0
  29. package/React/Base/RCTVersion.m +1 -1
  30. package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +12 -8
  31. package/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +47 -3
  32. package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h +8 -0
  33. package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +51 -44
  34. package/React/Views/RCTTVView.h +19 -6
  35. package/React/Views/RCTTVView.m +63 -55
  36. package/React/Views/RCTViewManager.m +4 -0
  37. package/React/Views/ScrollView/RCTScrollView.m +12 -8
  38. package/ReactAndroid/api/ReactAndroid.api +0 -1
  39. package/ReactAndroid/cmake-utils/ReactNative-application.cmake +1 -1
  40. package/ReactAndroid/gradle.properties +1 -1
  41. package/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt +2 -0
  42. package/ReactAndroid/src/main/java/com/facebook/react/modules/core/TimingModule.kt +0 -8
  43. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java +1 -1
  44. package/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +16 -0
  45. package/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java +20 -0
  46. package/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/BlurEvent.kt +16 -0
  47. package/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FocusEvent.kt +16 -0
  48. package/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/PressInEvent.kt +16 -0
  49. package/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/PressOutEvent.kt +16 -0
  50. package/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +11 -3
  51. package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +212 -4
  52. package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +47 -4
  53. package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
  54. package/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputShadowNode.cpp +3 -2
  55. package/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.cpp +18 -0
  56. package/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.h +8 -0
  57. package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h +12 -1
  58. package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +165 -2
  59. package/cli.js +1 -1
  60. package/index.js +0 -4
  61. package/package.json +8 -8
  62. package/scripts/codegen/generate-artifacts-executor.js +3 -3
  63. package/sdks/.hermesversion +1 -1
  64. package/sdks/hermesc/osx-bin/hermes +0 -0
  65. package/sdks/hermesc/osx-bin/hermesc +0 -0
  66. package/sdks/hermesc/win64-bin/hermesc.exe +0 -0
  67. package/types/modules/Codegen.d.ts +6 -0
  68. package/types/public/ReactNativeTVTypes.d.ts +2 -2
  69. package/Libraries/Components/TabBarIOS/RCTTabBarItemNativeComponent.js +0 -99
  70. package/Libraries/Components/TabBarIOS/RCTTabBarNativeComponent.js +0 -32
  71. package/Libraries/Components/TabBarIOS/TabBarIOS.ios.js +0 -59
  72. package/Libraries/Components/TabBarIOS/TabBarIOS.js +0 -52
  73. package/Libraries/Components/TabBarIOS/TabBarIOSProps.js +0 -52
  74. package/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js +0 -177
  75. package/Libraries/Components/TabBarIOS/TabBarItemIOS.js +0 -55
  76. package/Libraries/Components/Touchable/TVTouchable.js +0 -71
  77. package/React/Views/RCTTabBar.h +0 -22
  78. package/React/Views/RCTTabBar.m +0 -237
  79. package/React/Views/RCTTabBarItem.h +0 -35
  80. package/React/Views/RCTTabBarItem.m +0 -139
  81. package/React/Views/RCTTabBarItemManager.h +0 -12
  82. package/React/Views/RCTTabBarItemManager.m +0 -38
  83. package/React/Views/RCTTabBarManager.h +0 -12
  84. package/React/Views/RCTTabBarManager.m +0 -81
@@ -0,0 +1,16 @@
1
+ package com.facebook.react.uimanager.events
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.WritableMap
5
+
6
+ public class PressOutEvent(surfaceId: Int, viewId: Int) :
7
+ Event<PressOutEvent>(surfaceId, viewId) {
8
+
9
+ override fun getEventName(): String = EVENT_NAME
10
+
11
+ override fun getEventData(): WritableMap = Arguments.createMap()
12
+
13
+ private companion object {
14
+ private const val EVENT_NAME: String = "topPressOut"
15
+ }
16
+ }
@@ -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
@@ -22,16 +22,21 @@ import android.graphics.RectF;
22
22
  import android.graphics.drawable.Drawable;
23
23
  import android.graphics.drawable.LayerDrawable;
24
24
  import android.os.Build;
25
+ import android.os.Bundle;
25
26
  import android.view.FocusFinder;
27
+ import android.view.KeyEvent;
26
28
  import android.view.MotionEvent;
27
29
  import android.view.View;
28
30
  import android.view.ViewGroup;
29
31
  import android.view.ViewParent;
30
32
  import android.view.ViewStructure;
33
+ import android.view.accessibility.AccessibilityNodeInfo;
31
34
  import android.view.animation.Animation;
32
35
 
33
36
  import androidx.annotation.NonNull;
34
37
  import androidx.annotation.Nullable;
38
+ import androidx.core.view.ViewCompat;
39
+
35
40
  import com.facebook.common.logging.FLog;
36
41
  import com.facebook.infer.annotation.Assertions;
37
42
  import com.facebook.react.R;
@@ -53,6 +58,7 @@ import com.facebook.react.uimanager.LengthPercentageType;
53
58
  import com.facebook.react.uimanager.MeasureSpecAssertions;
54
59
  import com.facebook.react.uimanager.PixelUtil;
55
60
  import com.facebook.react.uimanager.PointerEvents;
61
+ import com.facebook.react.uimanager.ReactAccessibilityDelegate;
56
62
  import com.facebook.react.uimanager.ReactClippingProhibitedView;
57
63
  import com.facebook.react.uimanager.ReactClippingViewGroup;
58
64
  import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
@@ -61,10 +67,16 @@ import com.facebook.react.uimanager.ReactPointerEventsView;
61
67
  import com.facebook.react.uimanager.ReactZIndexedViewGroup;
62
68
  import com.facebook.react.uimanager.RootView;
63
69
  import com.facebook.react.uimanager.RootViewUtil;
70
+ import com.facebook.react.uimanager.UIManagerHelper;
64
71
  import com.facebook.react.uimanager.ViewGroupDrawingOrderHelper;
65
72
  import com.facebook.react.uimanager.common.UIManagerType;
66
73
  import com.facebook.react.uimanager.common.ViewUtil;
67
74
  import com.facebook.react.uimanager.drawable.CSSBackgroundDrawable;
75
+ import com.facebook.react.uimanager.events.BlurEvent;
76
+ import com.facebook.react.uimanager.events.EventDispatcher;
77
+ import com.facebook.react.uimanager.events.FocusEvent;
78
+ import com.facebook.react.uimanager.events.PressInEvent;
79
+ import com.facebook.react.uimanager.events.PressOutEvent;
68
80
  import com.facebook.react.uimanager.style.BackgroundImageLayer;
69
81
  import com.facebook.react.uimanager.style.BorderRadiusProp;
70
82
  import com.facebook.react.uimanager.style.BorderStyle;
@@ -98,8 +110,10 @@ public class ReactViewGroup extends ViewGroup
98
110
 
99
111
  private @NonNull int[] focusDestinations = new int[0];
100
112
  private boolean autoFocus = false;
113
+ private boolean isFocusGuideTalkbackAccessibilityDelegateSet = false;
101
114
  private WeakReference<View> lastFocusedElement;
102
115
  private boolean mRecoverFocus = false;
116
+ private boolean originalIsFocusable = false;
103
117
  private boolean trapFocusUp = false;
104
118
  private boolean trapFocusDown = false;
105
119
  private boolean trapFocusLeft = false;
@@ -213,6 +227,8 @@ public class ReactViewGroup extends ViewGroup
213
227
  updateBackgroundDrawable(null);
214
228
 
215
229
  resetPointerEvents();
230
+
231
+ cleanupFocusGuideTalkbackAccessibilityDelegate();
216
232
  }
217
233
 
218
234
  private ViewGroupDrawingOrderHelper getDrawingOrderHelper() {
@@ -559,7 +575,7 @@ public class ReactViewGroup extends ViewGroup
559
575
  return super.getChildVisibleRect(child, r, offset);
560
576
  }
561
577
 
562
- boolean moveFocusToFirstFocusable(ReactViewGroup viewGroup) {
578
+ private View getFirstFocusableView(ReactViewGroup viewGroup) {
563
579
  ArrayList<View> focusables = new ArrayList<View>(0);
564
580
  /**
565
581
  * `addFocusables` is the method used by `FocusFinder` to determine
@@ -578,10 +594,10 @@ public class ReactViewGroup extends ViewGroup
578
594
  * The other ones on the list can be non-focusable as well.
579
595
  * So, we run a loop till finding the first real focusable element.
580
596
  */
581
- if (focusables.size() <= 0) return false;
597
+ if (focusables.size() <= 0) return null;
582
598
 
583
599
  View firstFocusableElement = null;
584
- Integer index = 0;
600
+ int index = 0;
585
601
  while (firstFocusableElement == null && index < focusables.size()) {
586
602
  View elem = focusables.get(index);
587
603
  if (elem != viewGroup) {
@@ -591,6 +607,12 @@ public class ReactViewGroup extends ViewGroup
591
607
  index++;
592
608
  }
593
609
 
610
+ return firstFocusableElement;
611
+ }
612
+
613
+ boolean moveFocusToFirstFocusable(ReactViewGroup viewGroup) {
614
+ View firstFocusableElement = this.getFirstFocusableView(viewGroup);
615
+
594
616
  if (firstFocusableElement != null) return firstFocusableElement.requestFocus();
595
617
 
596
618
  return false;
@@ -1244,6 +1266,139 @@ public class ReactViewGroup extends ViewGroup
1244
1266
  setAlpha(0);
1245
1267
  }
1246
1268
 
1269
+ public void initializeFocusGuideTalkbackAccessibilityDelegate() {
1270
+ if (!this.isTVFocusGuide()) {
1271
+ return;
1272
+ }
1273
+
1274
+ this.originalIsFocusable = this.isFocusable();
1275
+
1276
+ ReactAccessibilityDelegate viewAccessibilityDelegate = new ReactAccessibilityDelegate(
1277
+ this, originalIsFocusable, this.getImportantForAccessibility()
1278
+ ) {
1279
+ @Override
1280
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
1281
+ if (!(host instanceof ReactViewGroup self)) {
1282
+ return super.performAccessibilityAction(host, action, args);
1283
+ }
1284
+ if (action == AccessibilityNodeInfo.ACTION_FOCUS) {
1285
+ if (self.interceptAccessibilityEvents(action, args)) {
1286
+ return true;
1287
+ }
1288
+ // Handle case when focus guide cannot find any focusable child
1289
+ if (self.isTVFocusGuide() && self.getFirstFocusableView(self) == null) {
1290
+ if (self.getChildCount() > 0) {
1291
+ View child = self.getChildAt(0);
1292
+ ArrayList<View> childFocusables = new ArrayList<>(0);
1293
+ child.addFocusables(childFocusables, FOCUS_DOWN, 0);
1294
+ if (!childFocusables.isEmpty()) {
1295
+ childFocusables.get(0).performAccessibilityAction(action, args);
1296
+ return true;
1297
+ }
1298
+ }
1299
+ }
1300
+ return super.performAccessibilityAction(host, action, args);
1301
+ }
1302
+ if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
1303
+ if (self.interceptAccessibilityEvents(action, args)) {
1304
+ return true;
1305
+ }
1306
+ // Handle case when focus guide cannot find any focusable child
1307
+ if (self.isTVFocusGuide() && self.getFirstFocusableView(self) == null) {
1308
+ if (self.getChildCount() > 0) {
1309
+ View child = self.getChildAt(0);
1310
+ ArrayList<View> childFocusables = new ArrayList<>(0);
1311
+ child.addFocusables(childFocusables, FOCUS_DOWN, 0);
1312
+ if (!childFocusables.isEmpty()) {
1313
+ /*
1314
+ * Instead of forwarding AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS,
1315
+ * let's invoke AccessibilityNodeInfo.ACTION_FOCUS
1316
+ * to trigger focus events on JS side - the AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
1317
+ * will be automatically invoked later
1318
+ */
1319
+ childFocusables.get(0).performAccessibilityAction(
1320
+ AccessibilityNodeInfo.ACTION_FOCUS,
1321
+ args
1322
+ );
1323
+ return true;
1324
+ }
1325
+ }
1326
+ /*
1327
+ * Let's consume event here, otherwise there might be an issue with
1328
+ * FocusGuide receiving focus instead of one of its child views
1329
+ */
1330
+ return true;
1331
+ }
1332
+ return super.performAccessibilityAction(host, action, args);
1333
+ }
1334
+ return super.performAccessibilityAction(host, action, args);
1335
+ }
1336
+ };
1337
+ ViewCompat.setAccessibilityDelegate(this, viewAccessibilityDelegate);
1338
+ this.setFocusable(true);
1339
+ this.setFocusableInTouchMode(true);
1340
+ // To force Talkback to give the a11y event to the FocusGuide
1341
+ // we need to make it look like it has some a11y label to be announced.
1342
+ // Because FocusGuide should always have at least one focusable child view,
1343
+ // which will receive forwarded a11y event from this FocusGuide,
1344
+ // the following fake label will never be announced
1345
+ this.setContentDescription("FocusGuide");
1346
+ this.isFocusGuideTalkbackAccessibilityDelegateSet = true;
1347
+ }
1348
+
1349
+ public void cleanupFocusGuideTalkbackAccessibilityDelegate() {
1350
+ ViewCompat.setAccessibilityDelegate(this, null);
1351
+ this.setFocusable(this.originalIsFocusable);
1352
+ this.setFocusableInTouchMode(this.originalIsFocusable);
1353
+ this.originalIsFocusable = false;
1354
+ this.setContentDescription(null);
1355
+ this.isFocusGuideTalkbackAccessibilityDelegateSet = false;
1356
+ }
1357
+
1358
+ public boolean hasFocusGuideTalkbackAccessibilityDelegate() {
1359
+ return this.isFocusGuideTalkbackAccessibilityDelegateSet;
1360
+ }
1361
+
1362
+ private boolean interceptAccessibilityEvents(int action, Bundle args) {
1363
+ if (!this.isTVFocusGuide()) {
1364
+ return false;
1365
+ }
1366
+
1367
+ // If it's "FocusGuide", we want to intercept
1368
+ // the event and redirect it to either:
1369
+ // 1) first available destination view from "destinations" prop
1370
+ // 2) last focused element saved when "autoFocus" prop is `true`
1371
+ // 3) first focusable child view
1372
+ View destinationView = this.findDestinationView();
1373
+ if (destinationView != null) {
1374
+ try {
1375
+ destinationView.performAccessibilityAction(action, args);
1376
+ return true;
1377
+ } catch (Exception e) {
1378
+ FLog.e(TAG, "Exception when performing accessibility action on destination view - falling back to next case (last focused view): " + e);
1379
+ }
1380
+ }
1381
+ View lastFocusedView = this.lastFocusedElement.get();
1382
+ if (lastFocusedView != null) {
1383
+ try {
1384
+ lastFocusedView.performAccessibilityAction(action, args);
1385
+ return true;
1386
+ } catch (Exception e) {
1387
+ FLog.e(TAG, "Exception when performing accessibility action on last focused view - falling back to next case (first focusable view): " + e);
1388
+ }
1389
+ }
1390
+ View firstFocusableView = this.getFirstFocusableView(this);
1391
+ if (firstFocusableView != null) {
1392
+ try {
1393
+ firstFocusableView.performAccessibilityAction(action, args);
1394
+ return true;
1395
+ } catch (Exception e) {
1396
+ FLog.e(TAG, "Exception when performing accessibility action on first focusable view - focus guide will not handle focus: " + e);
1397
+ }
1398
+ }
1399
+ return false;
1400
+ }
1401
+
1247
1402
  private View findDestinationView() {
1248
1403
  for (int focusDestination : focusDestinations) {
1249
1404
  View childViewWithTag = findViewById(focusDestination);
@@ -1274,7 +1429,7 @@ public class ReactViewGroup extends ViewGroup
1274
1429
  return focusDestinations.length > 0;
1275
1430
  }
1276
1431
 
1277
- private boolean isTVFocusGuide() {
1432
+ boolean isTVFocusGuide() {
1278
1433
  /**
1279
1434
  * We don't count a view as `TVFocusGuide` if it has `trapFocus*` props enabled.
1280
1435
  * The reason is, it's a seperate functionality that has nothing to do with other
@@ -1362,6 +1517,59 @@ public class ReactViewGroup extends ViewGroup
1362
1517
  @Override
1363
1518
  protected void onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) {
1364
1519
  super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
1520
+
1521
+ final EventDispatcher mEventDispatcher =
1522
+ UIManagerHelper.getEventDispatcherForReactTag(
1523
+ (ReactContext) this.getContext(), this.getId());
1524
+
1525
+ if (mEventDispatcher == null) {
1526
+ return;
1527
+ }
1528
+
1529
+ if (gainFocus) {
1530
+ mEventDispatcher.dispatchEvent(
1531
+ new FocusEvent(
1532
+ UIManagerHelper.getSurfaceId(this.getContext()), this.getId()));
1533
+ } else {
1534
+ mEventDispatcher.dispatchEvent(
1535
+ new BlurEvent(
1536
+ UIManagerHelper.getSurfaceId(this.getContext()), this.getId()));
1537
+ }
1538
+ }
1539
+
1540
+ @Override
1541
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
1542
+ if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) && event.getRepeatCount() == 0 && !this.isTVFocusGuide()) {
1543
+ final EventDispatcher mEventDispatcher =
1544
+ UIManagerHelper.getEventDispatcherForReactTag(
1545
+ (ReactContext) this.getContext(), this.getId());
1546
+
1547
+ if (mEventDispatcher == null) {
1548
+ return super.onKeyDown(keyCode, event);
1549
+ }
1550
+
1551
+ mEventDispatcher.dispatchEvent(new PressInEvent(UIManagerHelper.getSurfaceId(this.getContext()), this.getId()));
1552
+ }
1553
+
1554
+
1555
+ return super.onKeyDown(keyCode, event);
1556
+ }
1557
+
1558
+ @Override
1559
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
1560
+ if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) && !this.isTVFocusGuide()) {
1561
+ final EventDispatcher mEventDispatcher =
1562
+ UIManagerHelper.getEventDispatcherForReactTag(
1563
+ (ReactContext) this.getContext(), this.getId());
1564
+
1565
+ if (mEventDispatcher == null) {
1566
+ return super.onKeyUp(keyCode, event);
1567
+ }
1568
+
1569
+ mEventDispatcher.dispatchEvent(new PressOutEvent(UIManagerHelper.getSurfaceId(this.getContext()), this.getId()));
1570
+ }
1571
+
1572
+ return super.onKeyUp(keyCode, event);
1365
1573
  }
1366
1574
 
1367
1575
  @Override
@@ -7,15 +7,18 @@
7
7
 
8
8
  package com.facebook.react.views.view;
9
9
 
10
+ import android.accessibilityservice.AccessibilityServiceInfo;
10
11
  import android.content.Context;
11
12
  import android.content.pm.PackageManager;
12
13
  import android.graphics.Rect;
13
- import android.util.Log;
14
14
  import android.view.View;
15
15
  import androidx.annotation.ColorInt;
16
16
  import android.view.ViewGroup;
17
+ import android.view.accessibility.AccessibilityManager;
18
+
17
19
  import androidx.annotation.NonNull;
18
20
  import androidx.annotation.Nullable;
21
+
19
22
  import com.facebook.common.logging.FLog;
20
23
  import com.facebook.infer.annotation.Nullsafe;
21
24
  import com.facebook.react.bridge.Dynamic;
@@ -424,6 +427,13 @@ public class ReactViewManager extends ReactClippingViewManager<ReactViewGroup> {
424
427
  return new ReactViewGroup(context);
425
428
  }
426
429
 
430
+ @Override
431
+ protected void onAfterUpdateTransaction(@NonNull ReactViewGroup view) {
432
+ super.onAfterUpdateTransaction(view);
433
+
434
+ manageFocusGuideAccessibilityDelegate(view);
435
+ }
436
+
427
437
  @Override
428
438
  public Map<String, Integer> getCommandsMap() {
429
439
  return MapBuilder.of(HOTSPOT_UPDATE_KEY, CMD_HOTSPOT_UPDATE, "setPressed", CMD_SET_PRESSED, "setDestinations", CMD_SET_DESTINATIONS);
@@ -507,6 +517,39 @@ public class ReactViewManager extends ReactClippingViewManager<ReactViewGroup> {
507
517
  fd[i] = destinations.getInt(i);
508
518
  }
509
519
  root.setFocusDestinations(fd);
520
+ this.manageFocusGuideAccessibilityDelegate(root);
521
+ }
522
+
523
+ private void manageFocusGuideAccessibilityDelegate(ReactViewGroup view) {
524
+ AccessibilityManager accessibilityManager = ((AccessibilityManager) view.getContext()
525
+ .getSystemService(Context.ACCESSIBILITY_SERVICE));
526
+ List<AccessibilityServiceInfo> a11yServiceList = accessibilityManager
527
+ .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
528
+ boolean isTalkbackInstalledAndEnabled = false;
529
+ for (int i = 0; i < a11yServiceList.size(); i++) {
530
+ String a11yServiceId = a11yServiceList.get(i).getId();
531
+ if (a11yServiceId != null && a11yServiceId.contains("talkback")) {
532
+ isTalkbackInstalledAndEnabled = true;
533
+ }
534
+ }
535
+ boolean isTVFocusable = view.getDescendantFocusability() != ViewGroup.FOCUS_BLOCK_DESCENDANTS;
536
+ if (!view.hasFocusGuideTalkbackAccessibilityDelegate()) {
537
+ if (view.isTVFocusGuide() && isTVFocusable && isTalkbackInstalledAndEnabled) {
538
+ // Custom accessibility delegate is needed only for Talkback,
539
+ // as it's not handling TV focus guide scenarios as well as e.g. Amazon's VoiceView
540
+ //
541
+ // Delegate should only be set if TVFocusGuideView is focusable
542
+ view.initializeFocusGuideTalkbackAccessibilityDelegate();
543
+ }
544
+ } else {
545
+ if (!view.isTVFocusGuide() || !isTVFocusable || !isTalkbackInstalledAndEnabled) {
546
+ // If this view had delegate set, but is no longer a "focus guide"
547
+ // or is no longer focusable
548
+ // or talkback is no longer enabled
549
+ // then the delegate should be cleared
550
+ view.cleanupFocusGuideTalkbackAccessibilityDelegate();
551
+ }
552
+ }
510
553
  }
511
554
 
512
555
  /**
@@ -528,17 +571,17 @@ public class ReactViewManager extends ReactClippingViewManager<ReactViewGroup> {
528
571
  }
529
572
 
530
573
  @ReactProp(name = "trapFocusDown")
531
- public void trapFocusDown(ReactViewGroup view, boolean enabled) {
574
+ public void trapFocusDown(ReactViewGroup view, boolean enabled) {
532
575
  view.setTrapFocusDown(enabled);
533
576
  }
534
577
 
535
578
  @ReactProp(name = "trapFocusLeft")
536
- public void trapFocusLeft(ReactViewGroup view, boolean enabled) {
579
+ public void trapFocusLeft(ReactViewGroup view, boolean enabled) {
537
580
  view.setTrapFocusLeft(enabled);
538
581
  }
539
582
 
540
583
  @ReactProp(name = "trapFocusRight")
541
- public void trapFocusRight(ReactViewGroup view, boolean enabled) {
584
+ public void trapFocusRight(ReactViewGroup view, boolean enabled) {
542
585
  view.setTrapFocusRight(enabled);
543
586
  }
544
587
 
@@ -17,7 +17,7 @@ namespace facebook::react {
17
17
  constexpr struct {
18
18
  int32_t Major = 0;
19
19
  int32_t Minor = 76;
20
- int32_t Patch = 1;
20
+ int32_t Patch = 2;
21
21
  std::string_view Prerelease = "0";
22
22
  } ReactNativeVersion;
23
23
 
@@ -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
  }
@@ -32,6 +32,24 @@ void BaseViewEventEmitter::onAccessibilityEscape() const {
32
32
  dispatchEvent("accessibilityEscape");
33
33
  }
34
34
 
35
+ #pragma mark - Focus
36
+ void BaseViewEventEmitter::onFocus() const {
37
+ dispatchEvent("focus");
38
+ }
39
+
40
+ void BaseViewEventEmitter::onBlur() const {
41
+ dispatchEvent("blur");
42
+ }
43
+
44
+ #pragma mark - Press
45
+ void BaseViewEventEmitter::onPressIn() const {
46
+ dispatchEvent("pressIn");
47
+ }
48
+
49
+ void BaseViewEventEmitter::onPressOut() const {
50
+ dispatchEvent("pressOut");
51
+ }
52
+
35
53
  #pragma mark - Layout
36
54
 
37
55
  void BaseViewEventEmitter::onLayout(const LayoutMetrics& layoutMetrics) const {
@@ -28,6 +28,14 @@ class BaseViewEventEmitter : public TouchEventEmitter {
28
28
  void onAccessibilityMagicTap() const;
29
29
  void onAccessibilityEscape() const;
30
30
 
31
+ #pragma mark - Focus
32
+ void onFocus() const;
33
+ void onBlur() const;
34
+
35
+ #pragma mark - Press
36
+ void onPressIn() const;
37
+ void onPressOut() const;
38
+
31
39
  #pragma mark - Layout
32
40
 
33
41
  void onLayout(const LayoutMetrics& layoutMetrics) const;
@@ -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