From d9ebc6372dd4add1f995c336436b6cd31395bdb3 Mon Sep 17 00:00:00 2001
From: Navneet Kambo <72474613+nakambo@users.noreply.github.com>
Date: Wed, 29 Jan 2025 15:10:06 -0800
Subject: [PATCH] Fix TextInput focus when inside a FocusZone (#3849)
* Fix TextInput focus when inside a FocusZone
When a TextInput is not inside a FocusZone, it properly participates in the key view loop in that Tab\Shift+Tab will place focus on the inner `RCTUITextView`, instead of the outer `RCTBaseTextInputView` subclass. This is due to [overriding](https://github.com/microsoft/react-native-macos/blob/b7033b3513d98b20a08ec20abfe2b9bb712c68d4/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm#L1147C1-L1150C2) the `canBecomeKeyView` in that base class.
However, the FocusZone control does not look at that property -- instead it only considers `canBecomeFirstResponder`, as seen in #2329. Although it seems like the logical next step to use `canBecomeKeyView` in the FocusZone, that was previously done with #2267 but later reverted in #2322 since it broke things downstream. Instead, we'll make a compromise and leak some of the text input implementation details into the focus zone -- if we're considering focusing a subclass of `RCTBaseTextInputView`, we'll instead use the containing `backedTextInputView`.
Testing:
* TI inside FZ is now properly focused (using the new case added to the FZ page)
* Verified all other cases on the FZ page
* Verified in the downstream scenario that prompted this investigation
Notes:
* I noticed that Shift+Ctrl+Tab does not move focus out of the containing FZ, but that has been determined to be a pre-existing issue, and thus isn't being addressed in this change.
---
.../FocusZone/FocusZoneTest.tsx | 19 ++++++++++++++++++-
...-d859c102-4cb7-4d68-8107-d1bbfeb76759.json | 7 +++++++
...-3f2249ed-41c7-43f3-8b5e-5955e1e06001.json | 7 +++++++
.../components/FocusZone/macos/RCTFocusZone.m | 10 +++++++++-
4 files changed, 41 insertions(+), 2 deletions(-)
create mode 100644 change/@fluentui-react-native-focus-zone-d859c102-4cb7-4d68-8107-d1bbfeb76759.json
create mode 100644 change/@fluentui-react-native-tester-3f2249ed-41c7-43f3-8b5e-5955e1e06001.json
diff --git a/apps/fluent-tester/src/TestComponents/FocusZone/FocusZoneTest.tsx b/apps/fluent-tester/src/TestComponents/FocusZone/FocusZoneTest.tsx
index dd0f1b10e0..c384b9da2b 100644
--- a/apps/fluent-tester/src/TestComponents/FocusZone/FocusZoneTest.tsx
+++ b/apps/fluent-tester/src/TestComponents/FocusZone/FocusZoneTest.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { View, ScrollView, Pressable } from 'react-native';
+import { View, ScrollView, Pressable, TextInput } from 'react-native';
import type { FocusZoneDirection, FocusZoneTabNavigation } from '@fluentui/react-native';
import { FocusZone, MenuButton, Text, useOnPressWithFocus } from '@fluentui/react-native';
@@ -207,6 +207,19 @@ const FocusZoneGrid: React.FunctionComponent = () => {
);
};
+const FocusZoneTextInput: React.FunctionComponent = () => {
+ return (
+
+ <>
+ FocusZone Grid
+
+
+
+ >
+
+ );
+};
+
const focusZoneSections: TestSection[] = [
{
name: 'Common FocusZone Usage',
@@ -245,6 +258,10 @@ const focusZoneSections: TestSection[] = [
name: 'FocusZone Grid',
component: FocusZoneGrid,
},
+ {
+ name: 'FocusZone with TextInput',
+ component: FocusZoneTextInput,
+ },
];
const e2eSections: TestSection[] = [
diff --git a/change/@fluentui-react-native-focus-zone-d859c102-4cb7-4d68-8107-d1bbfeb76759.json b/change/@fluentui-react-native-focus-zone-d859c102-4cb7-4d68-8107-d1bbfeb76759.json
new file mode 100644
index 0000000000..dc64e88d49
--- /dev/null
+++ b/change/@fluentui-react-native-focus-zone-d859c102-4cb7-4d68-8107-d1bbfeb76759.json
@@ -0,0 +1,7 @@
+{
+ "type": "patch",
+ "comment": "Fix TextInput focus when inside a FocusZone",
+ "packageName": "@fluentui-react-native/focus-zone",
+ "email": "nakambo@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/change/@fluentui-react-native-tester-3f2249ed-41c7-43f3-8b5e-5955e1e06001.json b/change/@fluentui-react-native-tester-3f2249ed-41c7-43f3-8b5e-5955e1e06001.json
new file mode 100644
index 0000000000..217e201c8a
--- /dev/null
+++ b/change/@fluentui-react-native-tester-3f2249ed-41c7-43f3-8b5e-5955e1e06001.json
@@ -0,0 +1,7 @@
+{
+ "type": "patch",
+ "comment": "Add TextInput inside FocusZone case",
+ "packageName": "@fluentui-react-native/tester",
+ "email": "nakambo@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/components/FocusZone/macos/RCTFocusZone.m b/packages/components/FocusZone/macos/RCTFocusZone.m
index 45ec1c1382..5aea248535 100644
--- a/packages/components/FocusZone/macos/RCTFocusZone.m
+++ b/packages/components/FocusZone/macos/RCTFocusZone.m
@@ -1,4 +1,5 @@
#import "KeyCodes.h"
+#import
#import "RCTFocusZone.h"
#import "RCTi18nUtil.h"
@@ -61,7 +62,14 @@ static inline CGFloat GetMinDistanceBetweenRectVerticesAndPoint(NSRect rect, NSP
for (NSView *view in [parentView subviews]) {
if ([view acceptsFirstResponder]) {
- return view;
+ if ([view isKindOfClass:[RCTBaseTextInputView class]]) {
+ RCTUIView *backedTextInputView = [(RCTBaseTextInputView *)view backedTextInputView];
+ if ([backedTextInputView acceptsFirstResponder]) {
+ return backedTextInputView;
+ }
+ } else {
+ return view;
+ }
}
NSView *match = GetFirstFocusableViewWithin(view);