Skip to content

Commit 84017aa

Browse files
Nitin-100Nitin Chaudhary
andauthored
Fix SHIFT+F10 keyboard shortcut for context menu in TextInput (#15646)
Co-authored-by: Nitin Chaudhary <nitchaudhary@microsoft.com>
1 parent 1d7bc89 commit 84017aa

11 files changed

Lines changed: 188 additions & 19 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 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/ComponentView.idl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ namespace Microsoft.ReactNative
106106
DOC_STRING("Used to handle key up events when this component is focused, or if a child component did not handle the key up")
107107
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.KeyRoutedEventArgs> KeyUp;
108108
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.CharacterReceivedRoutedEventArgs> CharacterReceived;
109+
DOC_STRING("Used to handle context menu key events (SHIFT+F10 or Context Menu key) when this component is focused")
110+
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.ContextMenuKeyEventArgs> ContextMenuKey;
109111
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.PointerRoutedEventArgs> PointerPressed;
110112
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.PointerRoutedEventArgs> PointerReleased;
111113
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.PointerRoutedEventArgs> PointerMoved;

vnext/Microsoft.ReactNative/Composition.Input.idl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ namespace Microsoft.ReactNative.Composition.Input
3434
KeyboardSource KeyboardSource { get; };
3535
};
3636

37+
DOC_STRING("Event arguments for context menu key events (SHIFT+F10 or Context Menu key)")
38+
interface ContextMenuKeyEventArgs requires RoutedEventArgs
39+
{
40+
DOC_STRING("Gets or sets whether the event was handled. Set to true to prevent default behavior.")
41+
Boolean Handled { get; set; };
42+
};
43+
3744
interface IPointerPointTransform
3845
{
3946
IPointerPointTransform Inverse { get; };

vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,16 @@ void ComponentView::CharacterReceived(winrt::event_token const &token) noexcept
469469
m_characterReceivedEvent.remove(token);
470470
}
471471

472+
winrt::event_token ComponentView::ContextMenuKey(
473+
winrt::Windows::Foundation::EventHandler<
474+
winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs> const &handler) noexcept {
475+
return m_contextMenuKeyEvent.add(handler);
476+
}
477+
478+
void ComponentView::ContextMenuKey(winrt::event_token const &token) noexcept {
479+
m_contextMenuKeyEvent.remove(token);
480+
}
481+
472482
winrt::event_token ComponentView::PointerPressed(
473483
winrt::Windows::Foundation::EventHandler<
474484
winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs> const &handler) noexcept {
@@ -609,6 +619,14 @@ void ComponentView::OnCharacterReceived(
609619
}
610620
}
611621

622+
void ComponentView::OnContextMenuKey(
623+
const winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs &args) noexcept {
624+
m_contextMenuKeyEvent(*this, args);
625+
if (m_parent && !args.Handled()) {
626+
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(m_parent)->OnContextMenuKey(args);
627+
}
628+
}
629+
612630
bool ComponentView::focusable() const noexcept {
613631
return false;
614632
}

vnext/Microsoft.ReactNative/Fabric/ComponentView.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ struct ComponentView
167167
winrt::Windows::Foundation::EventHandler<
168168
winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs> const &handler) noexcept;
169169
void CharacterReceived(winrt::event_token const &token) noexcept;
170+
winrt::event_token ContextMenuKey(
171+
winrt::Windows::Foundation::EventHandler<
172+
winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs> const &handler) noexcept;
173+
void ContextMenuKey(winrt::event_token const &token) noexcept;
170174
winrt::event_token PointerPressed(
171175
winrt::Windows::Foundation::EventHandler<
172176
winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs> const &handler) noexcept;
@@ -253,6 +257,8 @@ struct ComponentView
253257
virtual void OnKeyUp(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept;
254258
virtual void OnCharacterReceived(
255259
const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs &args) noexcept;
260+
virtual void OnContextMenuKey(
261+
const winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs &args) noexcept;
256262

257263
protected:
258264
winrt::com_ptr<winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder> m_builder;
@@ -277,6 +283,9 @@ struct ComponentView
277283
winrt::event<winrt::Windows::Foundation::EventHandler<
278284
winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs>>
279285
m_characterReceivedEvent;
286+
winrt::event<winrt::Windows::Foundation::EventHandler<
287+
winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs>>
288+
m_contextMenuKeyEvent;
280289
winrt::event<winrt::Windows::Foundation::EventHandler<
281290
winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs>>
282291
m_pointerPressedEvent;

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,18 @@ winrt::Microsoft::ReactNative::Composition::Input::KeyboardSource CharacterRecei
136136
return m_source;
137137
}
138138

139+
ContextMenuKeyEventArgs::ContextMenuKeyEventArgs(facebook::react::Tag tag) : m_tag(tag) {}
140+
141+
int32_t ContextMenuKeyEventArgs::OriginalSource() noexcept {
142+
return m_tag;
143+
}
144+
bool ContextMenuKeyEventArgs::Handled() noexcept {
145+
return m_handled;
146+
}
147+
void ContextMenuKeyEventArgs::Handled(bool value) noexcept {
148+
m_handled = value;
149+
}
150+
139151
Pointer::Pointer(winrt::Microsoft::ReactNative::Composition::Input::PointerDeviceType type, uint32_t id)
140152
: m_type(type), m_id(id) {}
141153

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,21 @@ struct CharacterReceivedRoutedEventArgs
7878
const winrt::Microsoft::ReactNative::Composition::Input::KeyboardSource m_source;
7979
};
8080

81+
struct ContextMenuKeyEventArgs : winrt::implements<
82+
ContextMenuKeyEventArgs,
83+
winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs,
84+
winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs> {
85+
ContextMenuKeyEventArgs(facebook::react::Tag tag);
86+
87+
int32_t OriginalSource() noexcept;
88+
bool Handled() noexcept;
89+
void Handled(bool value) noexcept;
90+
91+
private:
92+
facebook::react::Tag m_tag{-1};
93+
bool m_handled{false};
94+
};
95+
8196
struct Pointer : PointerT<Pointer> {
8297
Pointer(winrt::Microsoft::ReactNative::Composition::Input::PointerDeviceType type, uint32_t id);
8398

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

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,32 @@ 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+
auto tag =
336+
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)
337+
->Tag();
338+
auto contextMenuArgs = winrt::make<
339+
winrt::Microsoft::ReactNative::Composition::Input::implementation::ContextMenuKeyEventArgs>(tag);
340+
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)
341+
->OnContextMenuKey(contextMenuArgs);
342+
if (contextMenuArgs.Handled()) {
343+
args.Handled(true);
344+
}
345+
}
346+
}
347+
}
348+
});
323349
}
324350
}
325351

@@ -336,6 +362,7 @@ CompositionEventHandler::~CompositionEventHandler() {
336362
keyboardSource.KeyDown(m_keyDownToken);
337363
keyboardSource.KeyUp(m_keyUpToken);
338364
keyboardSource.CharacterReceived(m_characterReceivedToken);
365+
keyboardSource.ContextMenuKey(m_contextMenuKeyToken);
339366
}
340367
}
341368

@@ -443,6 +470,54 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
443470
}
444471
return 0;
445472
}
473+
case WM_RBUTTONDOWN: {
474+
if (auto strongRootView = m_wkRootView.get()) {
475+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
476+
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
477+
onPointerPressed(pp, GetKeyModifiers(wParam));
478+
}
479+
return 0;
480+
}
481+
case WM_RBUTTONUP: {
482+
if (auto strongRootView = m_wkRootView.get()) {
483+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
484+
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
485+
onPointerReleased(pp, GetKeyModifiers(wParam));
486+
}
487+
return 0;
488+
}
489+
case WM_MBUTTONDOWN: {
490+
if (auto strongRootView = m_wkRootView.get()) {
491+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
492+
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
493+
onPointerPressed(pp, GetKeyModifiers(wParam));
494+
}
495+
return 0;
496+
}
497+
case WM_MBUTTONUP: {
498+
if (auto strongRootView = m_wkRootView.get()) {
499+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
500+
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
501+
onPointerReleased(pp, GetKeyModifiers(wParam));
502+
}
503+
return 0;
504+
}
505+
case WM_XBUTTONDOWN: {
506+
if (auto strongRootView = m_wkRootView.get()) {
507+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
508+
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
509+
onPointerPressed(pp, GetKeyModifiers(wParam));
510+
}
511+
return 0;
512+
}
513+
case WM_XBUTTONUP: {
514+
if (auto strongRootView = m_wkRootView.get()) {
515+
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
516+
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
517+
onPointerReleased(pp, GetKeyModifiers(wParam));
518+
}
519+
return 0;
520+
}
446521
case WM_POINTERUP: {
447522
if (auto strongRootView = m_wkRootView.get()) {
448523
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(

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: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ struct CompTextHost : public winrt::implements<CompTextHost, ITextHost> {
186186

187187
auto pt = m_outer->getClientOffset();
188188
m_outer->m_caretVisual.Position({x - pt.x, y - pt.y});
189+
m_outer->m_caretPosition = {x, y};
189190
return true;
190191
}
191192

@@ -696,17 +697,10 @@ void WindowsTextInputComponentView::OnPointerPressed(
696697
}
697698

698699
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-
}
700+
LRESULT lresult;
701+
DrawBlock db(*this);
702+
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
703+
args.Handled(hr != S_FALSE);
710704
}
711705

712706
// Emits the OnPressIn event
@@ -768,10 +762,18 @@ void WindowsTextInputComponentView::OnPointerReleased(
768762
}
769763

770764
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);
765+
// Show context menu on right button release (standard Windows behavior)
766+
if (msg == WM_RBUTTONUP && !windowsTextInputProps().contextMenuHidden) {
767+
ShowContextMenu(LocalToScreen(position));
768+
args.Handled(true);
769+
} else if (msg == WM_RBUTTONUP) {
770+
// Context menu is hidden - don't mark as handled, let app add custom behavior
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
@@ -1879,6 +1881,21 @@ void WindowsTextInputComponentView::updateSpellCheck(bool enable) noexcept {
18791881
m_textServices->TxSendMessage(EM_SETLANGOPTIONS, IMF_SPELLCHECKING, enable ? newLangOptions : 0, &lresult));
18801882
}
18811883

1884+
void WindowsTextInputComponentView::OnContextMenuKey(
1885+
const winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs &args) noexcept {
1886+
// Handle context menu key event (SHIFT+F10 or Context Menu key)
1887+
if (!windowsTextInputProps().contextMenuHidden) {
1888+
// m_caretPosition is stored from TxSetCaretPos in RichEdit client rect space (physical pixels).
1889+
// LocalToScreen expects logical (DIP) coordinates, so divide by pointScaleFactor.
1890+
auto screenPt = LocalToScreen(winrt::Windows::Foundation::Point{
1891+
static_cast<float>(m_caretPosition.x) / m_layoutMetrics.pointScaleFactor,
1892+
static_cast<float>(m_caretPosition.y) / m_layoutMetrics.pointScaleFactor});
1893+
ShowContextMenu(screenPt);
1894+
args.Handled(true);
1895+
}
1896+
// If contextMenuHidden, don't mark as handled - let app handle it
1897+
}
1898+
18821899
void WindowsTextInputComponentView::ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept {
18831900
HMENU menu = CreatePopupMenu();
18841901
if (!menu)
@@ -1898,13 +1915,16 @@ void WindowsTextInputComponentView::ShowContextMenu(const winrt::Windows::Founda
18981915
AppendMenuW(menu, MF_STRING | (canPaste ? 0 : MF_GRAYED), 3, L"Paste");
18991916
AppendMenuW(menu, MF_STRING | (!isEmpty && !isReadOnly ? 0 : MF_GRAYED), 4, L"Select All");
19001917

1901-
POINT cursorPos;
1902-
GetCursorPos(&cursorPos);
1903-
19041918
HWND hwnd = GetActiveWindow();
19051919

19061920
int cmd = TrackPopupMenu(
1907-
menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_NONOTIFY, cursorPos.x, cursorPos.y, 0, hwnd, NULL);
1921+
menu,
1922+
TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_NONOTIFY,
1923+
static_cast<int>(position.X),
1924+
static_cast<int>(position.Y),
1925+
0,
1926+
hwnd,
1927+
NULL);
19081928

19091929
if (cmd == 1) { // Cut
19101930
m_textServices->TxSendMessage(WM_CUT, 0, 0, &res);

0 commit comments

Comments
 (0)