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.
- package/Libraries/AppDelegate/React-RCTAppDelegate.podspec +1 -1
- package/Libraries/Components/Pressable/Pressable.d.ts +9 -1
- package/Libraries/Components/Pressable/Pressable.js +4 -16
- package/Libraries/Components/TV/TVViewPropTypes.js +2 -1
- package/Libraries/Components/TextInput/TextInput.d.ts +1 -1
- package/Libraries/Components/Touchable/Touchable.js +0 -43
- package/Libraries/Components/Touchable/TouchableBounce.js +0 -33
- package/Libraries/Components/Touchable/TouchableHighlight.js +12 -47
- package/Libraries/Components/Touchable/TouchableNativeFeedback.js +0 -33
- package/Libraries/Components/Touchable/TouchableOpacity.js +12 -44
- package/Libraries/Components/Touchable/TouchableWithoutFeedback.js +0 -19
- package/Libraries/Components/View/ViewNativeComponent.js +6 -0
- package/Libraries/Components/View/ViewPropTypes.d.ts +12 -1
- package/Libraries/Components/View/ViewPropTypes.js +7 -0
- package/Libraries/Core/ReactNativeVersion.js +1 -1
- package/Libraries/Core/setUpErrorHandling.js +1 -7
- package/Libraries/LogBox/Data/LogBoxData.js +2 -2
- package/Libraries/NativeComponent/BaseViewConfig.android.js +19 -0
- package/Libraries/NativeComponent/BaseViewConfig.ios.js +6 -0
- package/Libraries/NativeComponent/TVViewConfig.js +4 -0
- package/Libraries/Pressability/Pressability.js +45 -28
- package/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +1 -0
- package/Libraries/Types/CoreEventTypes.d.ts +21 -0
- package/Libraries/Types/CoreEventTypes.js +6 -0
- package/README.md +9 -7
- package/React/Base/RCTTVRemoteHandler.m +0 -19
- package/React/Base/RCTTVRemoteSelectHandler.h +27 -0
- package/React/Base/RCTTVRemoteSelectHandler.m +120 -0
- package/React/Base/RCTVersion.m +1 -1
- package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +12 -8
- package/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +47 -3
- package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h +8 -0
- package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +51 -44
- package/React/Views/RCTTVView.h +19 -6
- package/React/Views/RCTTVView.m +63 -55
- package/React/Views/RCTViewManager.m +4 -0
- package/React/Views/ScrollView/RCTScrollView.m +12 -8
- package/ReactAndroid/api/ReactAndroid.api +0 -1
- package/ReactAndroid/cmake-utils/ReactNative-application.cmake +1 -1
- package/ReactAndroid/gradle.properties +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt +2 -0
- package/ReactAndroid/src/main/java/com/facebook/react/modules/core/TimingModule.kt +0 -8
- package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +16 -0
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAccessibilityDelegate.java +20 -0
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/BlurEvent.kt +16 -0
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FocusEvent.kt +16 -0
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/PressInEvent.kt +16 -0
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/PressOutEvent.kt +16 -0
- package/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +11 -3
- package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +212 -4
- package/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +47 -4
- package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
- package/ReactCommon/react/renderer/components/textinput/platform/ios/react/renderer/components/iostextinput/TextInputShadowNode.cpp +3 -2
- package/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.cpp +18 -0
- package/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.h +8 -0
- package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.h +12 -1
- package/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +165 -2
- package/cli.js +1 -1
- package/index.js +0 -4
- package/package.json +8 -8
- package/scripts/codegen/generate-artifacts-executor.js +3 -3
- package/sdks/.hermesversion +1 -1
- package/sdks/hermesc/osx-bin/hermes +0 -0
- package/sdks/hermesc/osx-bin/hermesc +0 -0
- package/sdks/hermesc/win64-bin/hermesc.exe +0 -0
- package/types/modules/Codegen.d.ts +6 -0
- package/types/public/ReactNativeTVTypes.d.ts +2 -2
- package/Libraries/Components/TabBarIOS/RCTTabBarItemNativeComponent.js +0 -99
- package/Libraries/Components/TabBarIOS/RCTTabBarNativeComponent.js +0 -32
- package/Libraries/Components/TabBarIOS/TabBarIOS.ios.js +0 -59
- package/Libraries/Components/TabBarIOS/TabBarIOS.js +0 -52
- package/Libraries/Components/TabBarIOS/TabBarIOSProps.js +0 -52
- package/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js +0 -177
- package/Libraries/Components/TabBarIOS/TabBarItemIOS.js +0 -55
- package/Libraries/Components/Touchable/TVTouchable.js +0 -71
- package/React/Views/RCTTabBar.h +0 -22
- package/React/Views/RCTTabBar.m +0 -237
- package/React/Views/RCTTabBarItem.h +0 -35
- package/React/Views/RCTTabBarItem.m +0 -139
- package/React/Views/RCTTabBarItemManager.h +0 -12
- package/React/Views/RCTTabBarItemManager.m +0 -38
- package/React/Views/RCTTabBarManager.h +0 -12
- 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() =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
597
|
+
if (focusables.size() <= 0) return null;
|
|
582
598
|
|
|
583
599
|
View firstFocusableElement = null;
|
|
584
|
-
|
|
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
|
-
|
|
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
|
-
|
|
574
|
+
public void trapFocusDown(ReactViewGroup view, boolean enabled) {
|
|
532
575
|
view.setTrapFocusDown(enabled);
|
|
533
576
|
}
|
|
534
577
|
|
|
535
578
|
@ReactProp(name = "trapFocusLeft")
|
|
536
|
-
|
|
579
|
+
public void trapFocusLeft(ReactViewGroup view, boolean enabled) {
|
|
537
580
|
view.setTrapFocusLeft(enabled);
|
|
538
581
|
}
|
|
539
582
|
|
|
540
583
|
@ReactProp(name = "trapFocusRight")
|
|
541
|
-
|
|
584
|
+
public void trapFocusRight(ReactViewGroup view, boolean enabled) {
|
|
542
585
|
view.setTrapFocusRight(enabled);
|
|
543
586
|
}
|
|
544
587
|
|
|
@@ -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
|
|
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
|
-
|
|
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
|