Skip to content

Commit 525ebd2

Browse files
author
Nitin Chaudhary
committed
Fix SHIFT+F10 keyboard shortcut for context menu in TextInput
1 parent baa67da commit 525ebd2

File tree

7 files changed

+155
-15
lines changed

7 files changed

+155
-15
lines changed
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 SHIFT+F10 keyboard shortcut for context menu in TextInput",
4+
"packageName": "react-native-windows",
5+
"email": "nitchaudhary@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,13 @@ void ComponentView::OnCharacterReceived(
609609
}
610610
}
611611

612+
void ComponentView::OnContextMenu(
613+
const winrt::Windows::Foundation::Point & /*position*/,
614+
bool /*isKeyboardTriggered*/) noexcept {
615+
// Default implementation does nothing - derived classes can override to handle context menu
616+
// This is called when WM_CONTEXTMENU is received (SHIFT+F10 or Context Menu key)
617+
}
618+
612619
bool ComponentView::focusable() const noexcept {
613620
return false;
614621
}

vnext/Microsoft.ReactNative/Fabric/ComponentView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ struct ComponentView
253253
virtual void OnKeyUp(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept;
254254
virtual void OnCharacterReceived(
255255
const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs &args) noexcept;
256+
virtual void OnContextMenu(const winrt::Windows::Foundation::Point &position, bool isKeyboardTriggered) noexcept;
256257

257258
protected:
258259
winrt::com_ptr<winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder> m_builder;

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

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,28 @@ void CompositionEventHandler::Initialize() noexcept {
320320
}
321321
}
322322
});
323+
324+
m_contextMenuKeyToken =
325+
keyboardSource.ContextMenuKey([wkThis = weak_from_this()](
326+
winrt::Microsoft::UI::Input::InputKeyboardSource const & /*source*/,
327+
winrt::Microsoft::UI::Input::ContextMenuKeyEventArgs const &args) {
328+
if (auto strongThis = wkThis.lock()) {
329+
if (auto strongRootView = strongThis->m_wkRootView.get()) {
330+
if (strongThis->SurfaceId() == -1)
331+
return;
332+
333+
auto focusedComponent = strongThis->RootComponentView().GetFocusedComponent();
334+
if (focusedComponent) {
335+
// For keyboard-triggered context menu, pass (0,0) as position
336+
// The component will determine the appropriate position (e.g., caret location for TextInput)
337+
winrt::Windows::Foundation::Point position{0, 0};
338+
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)
339+
->OnContextMenu(position, true /*isKeyboardTriggered*/);
340+
args.Handled(true);
341+
}
342+
}
343+
}
344+
});
323345
}
324346
}
325347

@@ -336,6 +358,7 @@ CompositionEventHandler::~CompositionEventHandler() {
336358
keyboardSource.KeyDown(m_keyDownToken);
337359
keyboardSource.KeyUp(m_keyUpToken);
338360
keyboardSource.CharacterReceived(m_characterReceivedToken);
361+
keyboardSource.ContextMenuKey(m_contextMenuKeyToken);
339362
}
340363
}
341364

@@ -443,6 +466,54 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
443466
}
444467
return 0;
445468
}
469+
case WM_RBUTTONDOWN: {
470+
if (auto strongRootView = m_wkRootView.get()) {
471+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
472+
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
473+
onPointerPressed(pp, GetKeyModifiers(wParam));
474+
}
475+
return 0;
476+
}
477+
case WM_RBUTTONUP: {
478+
if (auto strongRootView = m_wkRootView.get()) {
479+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
480+
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
481+
onPointerReleased(pp, GetKeyModifiers(wParam));
482+
}
483+
return 0;
484+
}
485+
case WM_MBUTTONDOWN: {
486+
if (auto strongRootView = m_wkRootView.get()) {
487+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
488+
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
489+
onPointerPressed(pp, GetKeyModifiers(wParam));
490+
}
491+
return 0;
492+
}
493+
case WM_MBUTTONUP: {
494+
if (auto strongRootView = m_wkRootView.get()) {
495+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
496+
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
497+
onPointerReleased(pp, GetKeyModifiers(wParam));
498+
}
499+
return 0;
500+
}
501+
case WM_XBUTTONDOWN: {
502+
if (auto strongRootView = m_wkRootView.get()) {
503+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
504+
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
505+
onPointerPressed(pp, GetKeyModifiers(wParam));
506+
}
507+
return 0;
508+
}
509+
case WM_XBUTTONUP: {
510+
if (auto strongRootView = m_wkRootView.get()) {
511+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
512+
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
513+
onPointerReleased(pp, GetKeyModifiers(wParam));
514+
}
515+
return 0;
516+
}
446517
case WM_POINTERUP: {
447518
if (auto strongRootView = m_wkRootView.get()) {
448519
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
@@ -526,6 +597,29 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
526597
UpdateCursor();
527598
return 1;
528599
}
600+
case WM_CONTEXTMENU: {
601+
// WM_CONTEXTMENU is generated by Windows when SHIFT+F10 or the Context Menu key is pressed
602+
// lParam contains the screen coordinates, or -1 if triggered via keyboard
603+
if (auto strongRootView = m_wkRootView.get()) {
604+
auto focusedComponent = RootComponentView().GetFocusedComponent();
605+
if (focusedComponent) {
606+
// lParam == -1 indicates keyboard-triggered context menu (SHIFT+F10 or VK_APPS)
607+
bool isKeyboardTriggered = (lParam == -1);
608+
winrt::Windows::Foundation::Point position{0, 0};
609+
if (!isKeyboardTriggered) {
610+
// Convert screen coordinates to client coordinates
611+
POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
612+
ScreenToClient(hwnd, &pt);
613+
position = {static_cast<float>(pt.x), static_cast<float>(pt.y)};
614+
}
615+
// Dispatch to the focused component
616+
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)
617+
->OnContextMenu(position, isKeyboardTriggered);
618+
return 1; // Indicate that we handled the message
619+
}
620+
}
621+
return 0;
622+
}
529623
}
530624

531625
return 0;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
175175
winrt::event_token m_keyDownToken;
176176
winrt::event_token m_keyUpToken;
177177
winrt::event_token m_characterReceivedToken;
178+
winrt::event_token m_contextMenuKeyToken;
178179
};
179180

180181
} // namespace Microsoft::ReactNative

vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -696,17 +696,10 @@ void WindowsTextInputComponentView::OnPointerPressed(
696696
}
697697

698698
if (m_textServices && msg) {
699-
if (msg == WM_RBUTTONUP && !windowsTextInputProps().contextMenuHidden) {
700-
ShowContextMenu(position);
701-
args.Handled(true);
702-
} else if (msg == WM_RBUTTONUP && windowsTextInputProps().contextMenuHidden) {
703-
args.Handled(true);
704-
} else {
705-
LRESULT lresult;
706-
DrawBlock db(*this);
707-
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
708-
args.Handled(hr != S_FALSE);
709-
}
699+
LRESULT lresult;
700+
DrawBlock db(*this);
701+
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
702+
args.Handled(hr != S_FALSE);
710703
}
711704

712705
// Emits the OnPressIn event
@@ -768,10 +761,19 @@ void WindowsTextInputComponentView::OnPointerReleased(
768761
}
769762

770763
if (m_textServices && msg) {
771-
LRESULT lresult;
772-
DrawBlock db(*this);
773-
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
774-
args.Handled(hr != S_FALSE);
764+
// Show context menu on right button release (standard Windows behavior)
765+
if (msg == WM_RBUTTONUP && !windowsTextInputProps().contextMenuHidden) {
766+
ShowContextMenu(position);
767+
args.Handled(true);
768+
} else if (msg == WM_RBUTTONUP && windowsTextInputProps().contextMenuHidden) {
769+
// Context menu is hidden, just mark as handled
770+
args.Handled(true);
771+
} else {
772+
LRESULT lresult;
773+
DrawBlock db(*this);
774+
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
775+
args.Handled(hr != S_FALSE);
776+
}
775777
}
776778

777779
// Emits the OnPressOut event
@@ -850,6 +852,24 @@ void WindowsTextInputComponentView::OnPointerWheelChanged(
850852
}
851853
void WindowsTextInputComponentView::OnKeyDown(
852854
const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept {
855+
// Handle context menu keyboard shortcuts
856+
// VK_APPS (Application/Context Menu key) is handled here
857+
// SHIFT+F10 is handled via WM_CONTEXTMENU for HWND hosting (CompositionHwndHost)
858+
// For Island hosting (ReactNativeAppBuilder), SHIFT+F10 requires InputPreTranslateKeyboardSource - TODO
859+
bool isShiftDown =
860+
(args.KeyboardSource().GetKeyState(winrt::Windows::System::VirtualKey::Shift) &
861+
winrt::Microsoft::UI::Input::VirtualKeyStates::Down) == winrt::Microsoft::UI::Input::VirtualKeyStates::Down;
862+
bool isShiftF10 = args.Key() == winrt::Windows::System::VirtualKey::F10 && isShiftDown;
863+
bool isContextMenuKey = args.Key() == winrt::Windows::System::VirtualKey::Application;
864+
865+
if ((isShiftF10 || isContextMenuKey) && !windowsTextInputProps().contextMenuHidden) {
866+
// Use caret position for context menu when triggered via keyboard
867+
winrt::Windows::Foundation::Point caretPosition{0, 0};
868+
ShowContextMenu(caretPosition);
869+
args.Handled(true);
870+
return;
871+
}
872+
853873
// Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with
854874
// WinUI behavior We do forward Ctrl+Tab to the textinput.
855875
if (args.Key() != winrt::Windows::System::VirtualKey::Tab ||
@@ -1879,6 +1899,15 @@ void WindowsTextInputComponentView::updateSpellCheck(bool enable) noexcept {
18791899
m_textServices->TxSendMessage(EM_SETLANGOPTIONS, IMF_SPELLCHECKING, enable ? newLangOptions : 0, &lresult));
18801900
}
18811901

1902+
void WindowsTextInputComponentView::OnContextMenu(
1903+
const winrt::Windows::Foundation::Point &position,
1904+
bool isKeyboardTriggered) noexcept {
1905+
// Handle WM_CONTEXTMENU message (generated by Windows for SHIFT+F10 or Context Menu key)
1906+
if (!windowsTextInputProps().contextMenuHidden) {
1907+
ShowContextMenu(position);
1908+
}
1909+
}
1910+
18821911
void WindowsTextInputComponentView::ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept {
18831912
HMENU menu = CreatePopupMenu();
18841913
if (!menu)

vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ struct WindowsTextInputComponentView
6767
void OnKeyUp(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept override;
6868
void OnCharacterReceived(const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs
6969
&args) noexcept override;
70+
void OnContextMenu(const winrt::Windows::Foundation::Point &position, bool isKeyboardTriggered) noexcept override;
7071
void onMounted() noexcept override;
7172

7273
std::optional<std::string> getAccessiblityValue() noexcept override;

0 commit comments

Comments
 (0)