Skip to content

Commit 7174845

Browse files
acoates-msrnbot
authored andcommitted
[0.83] Focus fixes (#15871)
* Enable imperative focus (#15863) * Fix view.focus function * Change files * snapshots * Only show focus visuals when using keyboard to move focus (#15864) * Only show focus visuals when using keyboard to move focus * Change files * fix change files * Revert "fix change files" This reverts commit f99ebba.
1 parent 9d7aeb0 commit 7174845

15 files changed

Lines changed: 112 additions & 25 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Fix view.focus function",
4+
"packageName": "react-native-windows",
5+
"email": "30809111+acoates-ms@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Only show focus visuals when using keyboard to move focus",
4+
"packageName": "react-native-windows",
5+
"email": "30809111+acoates-ms@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

vnext/Microsoft.ReactNative/ComponentView.idl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ namespace Microsoft.ReactNative
4343
Last,
4444
};
4545

46+
enum FocusState
47+
{
48+
Unfocused = 0,
49+
Pointer,
50+
Keyboard,
51+
Programmatic,
52+
};
53+
4654
[webhosthidden]
4755
[experimental]
4856
interface IComponentState
@@ -99,7 +107,7 @@ namespace Microsoft.ReactNative
99107
LayoutMetrics LayoutMetrics { get; };
100108
IInspectable UserData;
101109

102-
Boolean TryFocus();
110+
Boolean TryFocus(FocusState focusState);
103111

104112
DOC_STRING("Used to handle key down events when this component is focused, or if a child component did not handle the key down")
105113
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.KeyRoutedEventArgs> KeyDown;

vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ void ComponentView::parent(const winrt::Microsoft::ReactNative::ComponentView &p
283283
oldRootView->TrySetFocusedComponent(
284284
oldParent,
285285
winrt::Microsoft::ReactNative::FocusNavigationDirection::None,
286+
winrt::Microsoft::ReactNative::FocusState::Programmatic,
286287
true /*forceNoSelectionIfCannotMove*/);
287288
}
288289
}
@@ -431,9 +432,10 @@ void ComponentView::GotFocus(winrt::event_token const &token) noexcept {
431432
m_gotFocusEvent.remove(token);
432433
}
433434

434-
bool ComponentView::TryFocus() noexcept {
435+
bool ComponentView::TryFocus(winrt::Microsoft::ReactNative::FocusState focusState) noexcept {
435436
if (auto root = rootComponentView()) {
436-
return root->TrySetFocusedComponent(*get_strong(), winrt::Microsoft::ReactNative::FocusNavigationDirection::None);
437+
return root->TrySetFocusedComponent(
438+
*get_strong(), winrt::Microsoft::ReactNative::FocusNavigationDirection::None, focusState);
437439
}
438440

439441
return false;

vnext/Microsoft.ReactNative/Fabric/ComponentView.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ struct ComponentView
201201

202202
LayoutMetrics LayoutMetrics() const noexcept;
203203

204-
bool TryFocus() noexcept;
204+
bool TryFocus(winrt::Microsoft::ReactNative::FocusState focusState) noexcept;
205205

206206
virtual bool focusable() const noexcept;
207207
virtual facebook::react::SharedViewEventEmitter eventEmitterAtPoint(facebook::react::Point pt) noexcept;

vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,8 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
608608

609609
void CompositionEventHandler::onKeyDown(
610610
const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept {
611+
RootComponentView().UseKeyboardForProgrammaticFocus(true);
612+
611613
if (auto focusedComponent = RootComponentView().GetFocusedComponent()) {
612614
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)->OnKeyDown(args);
613615

@@ -631,7 +633,7 @@ void CompositionEventHandler::onKeyDown(
631633
}
632634

633635
if (!fCtrl && args.Key() == winrt::Windows::System::VirtualKey::Tab) {
634-
if (RootComponentView().TryMoveFocus(!fShift)) {
636+
if (RootComponentView().TryMoveFocus(!fShift, winrt::Microsoft::ReactNative::FocusState::Keyboard)) {
635637
args.Handled(true);
636638
}
637639

@@ -641,6 +643,8 @@ void CompositionEventHandler::onKeyDown(
641643

642644
void CompositionEventHandler::onKeyUp(
643645
const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept {
646+
RootComponentView().UseKeyboardForProgrammaticFocus(true);
647+
644648
if (auto focusedComponent = RootComponentView().GetFocusedComponent()) {
645649
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)->OnKeyUp(args);
646650

@@ -1173,6 +1177,8 @@ void CompositionEventHandler::onPointerPressed(
11731177
winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept {
11741178
namespace Composition = winrt::Microsoft::ReactNative::Composition;
11751179

1180+
RootComponentView().UseKeyboardForProgrammaticFocus(false);
1181+
11761182
// Clears any active text selection when left pointer is pressed
11771183
if (pointerPoint.Properties().PointerUpdateKind() != Composition::Input::PointerUpdateKind::RightButtonPressed) {
11781184
RootComponentView().ClearCurrentTextSelection();
@@ -1269,6 +1275,8 @@ void CompositionEventHandler::onPointerReleased(
12691275
winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept {
12701276
int pointerId = pointerPoint.PointerId();
12711277

1278+
RootComponentView().UseKeyboardForProgrammaticFocus(false);
1279+
12721280
auto activeTouch = std::find_if(m_activeTouches.begin(), m_activeTouches.end(), [pointerId](const auto &pair) {
12731281
return pair.second.touch.identifier == pointerId;
12741282
});

vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,8 @@ void ComponentView::onGotFocus(
373373
const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
374374
if (args.OriginalSource() == Tag()) {
375375
m_eventEmitter->onFocus();
376-
if (viewProps()->enableFocusRing) {
376+
if (viewProps()->enableFocusRing &&
377+
rootComponentView()->focusState() == winrt::Microsoft::ReactNative::FocusState::Keyboard) {
377378
facebook::react::Rect focusRect = m_layoutMetrics.frame;
378379
focusRect.origin.x -= (FOCUS_VISUAL_WIDTH * 2);
379380
focusRect.origin.y -= (FOCUS_VISUAL_WIDTH * 2);
@@ -426,15 +427,20 @@ void ComponentView::HandleCommand(const winrt::Microsoft::ReactNative::HandleCom
426427
auto commandName = args.CommandName();
427428
if (commandName == L"focus") {
428429
if (auto root = rootComponentView()) {
429-
root->TrySetFocusedComponent(*get_strong(), winrt::Microsoft::ReactNative::FocusNavigationDirection::None);
430+
root->TrySetFocusedComponent(
431+
*get_strong(),
432+
winrt::Microsoft::ReactNative::FocusNavigationDirection::None,
433+
winrt::Microsoft::ReactNative::FocusState::Programmatic);
430434
}
431435
return;
432436
}
433437
if (commandName == L"blur") {
434438
if (auto root = rootComponentView()) {
435439
root->TrySetFocusedComponent(
436-
nullptr, winrt::Microsoft::ReactNative::FocusNavigationDirection::None); // Todo store this component as
437-
// previously focused element
440+
nullptr,
441+
winrt::Microsoft::ReactNative::FocusNavigationDirection::None,
442+
winrt::Microsoft::ReactNative::FocusState::Programmatic); // Todo store this component as
443+
// previously focused element
438444
}
439445
return;
440446
}

vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ void ContentIslandComponentView::OnMounted() noexcept {
6464
m_navigationHost.DepartFocusRequested([wkThis = get_weak()](const auto &, const auto &args) {
6565
if (auto strongThis = wkThis.get()) {
6666
const bool next = (args.Request().Reason() != winrt::Microsoft::UI::Input::FocusNavigationReason::Last);
67-
strongThis->rootComponentView()->TryMoveFocus(next);
67+
strongThis->rootComponentView()->TryMoveFocus(next, winrt::Microsoft::ReactNative::FocusState::Programmatic);
6868
args.Result(winrt::Microsoft::UI::Input::FocusNavigationResult::Moved);
6969
}
7070
});

vnext/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,10 @@ void ParagraphComponentView::OnPointerPressed(
622622

623623
// Focuses so we receive onLostFocus when clicking elsewhere
624624
if (auto root = rootComponentView()) {
625-
root->TrySetFocusedComponent(*get_strong(), winrt::Microsoft::ReactNative::FocusNavigationDirection::None);
625+
root->TrySetFocusedComponent(
626+
*get_strong(),
627+
winrt::Microsoft::ReactNative::FocusNavigationDirection::None,
628+
winrt::Microsoft::ReactNative::FocusState::Pointer);
626629
}
627630

628631
args.Handled(true);

vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ winrt::Microsoft::ReactNative::ComponentView RootComponentView::GetFocusedCompon
8282

8383
void RootComponentView::SetFocusedComponent(
8484
const winrt::Microsoft::ReactNative::ComponentView &value,
85-
winrt::Microsoft::ReactNative::FocusNavigationDirection direction) noexcept {
85+
winrt::Microsoft::ReactNative::FocusNavigationDirection direction,
86+
winrt::Microsoft::ReactNative::FocusState focusState) noexcept {
8687
if (m_focusedComponent == value)
8788
return;
8889

@@ -97,11 +98,26 @@ void RootComponentView::SetFocusedComponent(
9798
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(rootView)->TrySetFocus();
9899
}
99100
m_focusedComponent = value;
101+
if (focusState == winrt::Microsoft::ReactNative::FocusState::Programmatic) {
102+
focusState =
103+
(!m_useKeyboardForProgrammaticFocus || m_focusState == winrt::Microsoft::ReactNative::FocusState::Pointer)
104+
? winrt::Microsoft::ReactNative::FocusState::Pointer
105+
: winrt::Microsoft::ReactNative::FocusState::Keyboard;
106+
}
107+
m_focusState = focusState;
100108
auto args = winrt::make<winrt::Microsoft::ReactNative::implementation::GotFocusEventArgs>(value, direction);
101109
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(value)->onGotFocus(args);
102110
}
103111
}
104112

113+
winrt::Microsoft::ReactNative::FocusState RootComponentView::focusState() const noexcept {
114+
return m_focusState;
115+
}
116+
117+
void RootComponentView::UseKeyboardForProgrammaticFocus(bool value) noexcept {
118+
m_useKeyboardForProgrammaticFocus = value;
119+
}
120+
105121
bool RootComponentView::NavigateFocus(const winrt::Microsoft::ReactNative::FocusNavigationRequest &request) noexcept {
106122
if (request.Reason() == winrt::Microsoft::ReactNative::FocusNavigationReason::Restore) {
107123
if (m_focusedComponent)
@@ -116,14 +132,16 @@ bool RootComponentView::NavigateFocus(const winrt::Microsoft::ReactNative::Focus
116132
view,
117133
request.Reason() == winrt::Microsoft::ReactNative::FocusNavigationReason::First
118134
? winrt::Microsoft::ReactNative::FocusNavigationDirection::First
119-
: winrt::Microsoft::ReactNative::FocusNavigationDirection::Last);
135+
: winrt::Microsoft::ReactNative::FocusNavigationDirection::Last,
136+
winrt::Microsoft::ReactNative::FocusState::Programmatic);
120137
}
121138
return view != nullptr;
122139
}
123140

124141
bool RootComponentView::TrySetFocusedComponent(
125142
const winrt::Microsoft::ReactNative::ComponentView &view,
126143
winrt::Microsoft::ReactNative::FocusNavigationDirection direction,
144+
winrt::Microsoft::ReactNative::FocusState focusState,
127145
bool forceNoSelectionIfCannotMove /*= false*/) noexcept {
128146
auto target = view;
129147
auto selfView = winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(target);
@@ -157,23 +175,24 @@ bool RootComponentView::TrySetFocusedComponent(
157175

158176
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(losingFocusArgs.NewFocusedComponent())
159177
->rootComponentView()
160-
->SetFocusedComponent(gettingFocusArgs.NewFocusedComponent(), direction);
178+
->SetFocusedComponent(gettingFocusArgs.NewFocusedComponent(), direction, focusState);
161179
} else {
162-
SetFocusedComponent(nullptr, direction);
180+
SetFocusedComponent(nullptr, direction, focusState);
163181
}
164182

165183
return true;
166184
}
167185

168-
bool RootComponentView::TryMoveFocus(bool next) noexcept {
186+
bool RootComponentView::TryMoveFocus(bool next, winrt::Microsoft::ReactNative::FocusState focusState) noexcept {
169187
if (!m_focusedComponent) {
170188
return NavigateFocus(winrt::Microsoft::ReactNative::FocusNavigationRequest(
171189
next ? winrt::Microsoft::ReactNative::FocusNavigationReason::First
172190
: winrt::Microsoft::ReactNative::FocusNavigationReason::Last));
173191
}
174192

175193
Mso::Functor<bool(const winrt::Microsoft::ReactNative::ComponentView &)> fn =
176-
[currentlyFocused = m_focusedComponent, next](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept {
194+
[currentlyFocused = m_focusedComponent, next, focusState](
195+
const winrt::Microsoft::ReactNative::ComponentView &view) noexcept {
177196
if (view == currentlyFocused)
178197
return false;
179198
auto selfView = winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(view);
@@ -185,7 +204,8 @@ bool RootComponentView::TryMoveFocus(bool next) noexcept {
185204
->TrySetFocusedComponent(
186205
view,
187206
next ? winrt::Microsoft::ReactNative::FocusNavigationDirection::Next
188-
: winrt::Microsoft::ReactNative::FocusNavigationDirection::Previous);
207+
: winrt::Microsoft::ReactNative::FocusNavigationDirection::Previous,
208+
focusState);
189209
};
190210

191211
if (winrt::Microsoft::ReactNative::implementation::walkTree(m_focusedComponent, next, fn)) {
@@ -249,7 +269,10 @@ void RootComponentView::start(const winrt::Microsoft::ReactNative::ReactNativeIs
249269
}
250270

251271
void RootComponentView::stop() noexcept {
252-
SetFocusedComponent(nullptr, winrt::Microsoft::ReactNative::FocusNavigationDirection::None);
272+
SetFocusedComponent(
273+
nullptr,
274+
winrt::Microsoft::ReactNative::FocusNavigationDirection::None,
275+
winrt::Microsoft::ReactNative::FocusState::Programmatic);
253276
if (m_visualAddedToIsland) {
254277
if (auto rootView = m_wkRootView.get()) {
255278
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(rootView)->RemoveRenderedVisual(

0 commit comments

Comments
 (0)