Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Fix SHIFT+F10 keyboard shortcut for context menu in TextInput",
"packageName": "react-native-windows",
"email": "nitchaudhary@microsoft.com",
"dependentChangeType": "patch"
}
7 changes: 7 additions & 0 deletions vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,13 @@ void ComponentView::OnCharacterReceived(
}
}

void ComponentView::OnContextMenu(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't be necessary to inject additional input into the ReactNativeIsland.

The ContentIsland should provide all that we need here.

Either we should be listening directly to the key events, or there is / should be some ContentIsland event that corresponds to WM_CONTEXTMENU.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah!Right!
I've updated the implementation to use InputKeyboardSource.ContextMenuKey event instead of adding a new public API to ReactNativeIsland. The ContextMenuKey event fires when the Menu key (or Shift+F10) is unhandled from KeyDown, which is exactly what we need.

Removed:
-NotifyContextMenu API from ReactNativeIsland
-WM_CONTEXTMENU handler
Added:
-ContextMenuKey event subscription in CompositionEventHandler for ContentIsland hosting
-Kept WM_CONTEXTMENU handling in SendMessage for HWND hosting (CompositionHwndHost)

const winrt::Windows::Foundation::Point & /*position*/,
bool /*isKeyboardTriggered*/) noexcept {
// Default implementation does nothing - derived classes can override to handle context menu
// This is called when WM_CONTEXTMENU is received (SHIFT+F10 or Context Menu key)
}

bool ComponentView::focusable() const noexcept {
return false;
}
Expand Down
1 change: 1 addition & 0 deletions vnext/Microsoft.ReactNative/Fabric/ComponentView.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ struct ComponentView
virtual void OnKeyUp(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept;
virtual void OnCharacterReceived(
const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs &args) noexcept;
virtual void OnContextMenu(const winrt::Windows::Foundation::Point &position, bool isKeyboardTriggered) noexcept;

protected:
winrt::com_ptr<winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder> m_builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,28 @@ void CompositionEventHandler::Initialize() noexcept {
}
}
});

m_contextMenuKeyToken =
keyboardSource.ContextMenuKey([wkThis = weak_from_this()](
winrt::Microsoft::UI::Input::InputKeyboardSource const & /*source*/,
winrt::Microsoft::UI::Input::ContextMenuKeyEventArgs const &args) {
if (auto strongThis = wkThis.lock()) {
if (auto strongRootView = strongThis->m_wkRootView.get()) {
if (strongThis->SurfaceId() == -1)
return;

auto focusedComponent = strongThis->RootComponentView().GetFocusedComponent();
if (focusedComponent) {
// For keyboard-triggered context menu, pass (0,0) as position
// The component will determine the appropriate position (e.g., caret location for TextInput)
winrt::Windows::Foundation::Point position{0, 0};
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)
->OnContextMenu(position, true /*isKeyboardTriggered*/);
args.Handled(true);
}
}
}
});
}
}

Expand All @@ -336,6 +358,7 @@ CompositionEventHandler::~CompositionEventHandler() {
keyboardSource.KeyDown(m_keyDownToken);
keyboardSource.KeyUp(m_keyUpToken);
keyboardSource.CharacterReceived(m_characterReceivedToken);
keyboardSource.ContextMenuKey(m_contextMenuKeyToken);
}
}

Expand Down Expand Up @@ -443,6 +466,54 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
}
return 0;
}
case WM_RBUTTONDOWN: {
if (auto strongRootView = m_wkRootView.get()) {
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
onPointerPressed(pp, GetKeyModifiers(wParam));
}
return 0;
}
case WM_RBUTTONUP: {
if (auto strongRootView = m_wkRootView.get()) {
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
onPointerReleased(pp, GetKeyModifiers(wParam));
}
return 0;
}
case WM_MBUTTONDOWN: {
if (auto strongRootView = m_wkRootView.get()) {
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
onPointerPressed(pp, GetKeyModifiers(wParam));
}
return 0;
}
case WM_MBUTTONUP: {
if (auto strongRootView = m_wkRootView.get()) {
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
onPointerReleased(pp, GetKeyModifiers(wParam));
}
return 0;
}
case WM_XBUTTONDOWN: {
if (auto strongRootView = m_wkRootView.get()) {
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
onPointerPressed(pp, GetKeyModifiers(wParam));
}
return 0;
}
case WM_XBUTTONUP: {
if (auto strongRootView = m_wkRootView.get()) {
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
onPointerReleased(pp, GetKeyModifiers(wParam));
}
return 0;
}
case WM_POINTERUP: {
if (auto strongRootView = m_wkRootView.get()) {
auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
Expand Down Expand Up @@ -526,6 +597,29 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
UpdateCursor();
return 1;
}
case WM_CONTEXTMENU: {
// WM_CONTEXTMENU is generated by Windows when SHIFT+F10 or the Context Menu key is pressed
// lParam contains the screen coordinates, or -1 if triggered via keyboard
if (auto strongRootView = m_wkRootView.get()) {
auto focusedComponent = RootComponentView().GetFocusedComponent();
if (focusedComponent) {
// lParam == -1 indicates keyboard-triggered context menu (SHIFT+F10 or VK_APPS)
bool isKeyboardTriggered = (lParam == -1);
winrt::Windows::Foundation::Point position{0, 0};
if (!isKeyboardTriggered) {
// Convert screen coordinates to client coordinates
POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
ScreenToClient(hwnd, &pt);
position = {static_cast<float>(pt.x), static_cast<float>(pt.y)};
}
// Dispatch to the focused component
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)
->OnContextMenu(position, isKeyboardTriggered);
return 1; // Indicate that we handled the message
}
}
return 0;
}
}

return 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
winrt::event_token m_keyDownToken;
winrt::event_token m_keyUpToken;
winrt::event_token m_characterReceivedToken;
winrt::event_token m_contextMenuKeyToken;
};

} // namespace Microsoft::ReactNative
Original file line number Diff line number Diff line change
Expand Up @@ -696,17 +696,10 @@ void WindowsTextInputComponentView::OnPointerPressed(
}

if (m_textServices && msg) {
if (msg == WM_RBUTTONUP && !windowsTextInputProps().contextMenuHidden) {
ShowContextMenu(position);
args.Handled(true);
} else if (msg == WM_RBUTTONUP && windowsTextInputProps().contextMenuHidden) {
args.Handled(true);
} else {
LRESULT lresult;
DrawBlock db(*this);
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
args.Handled(hr != S_FALSE);
}
LRESULT lresult;
DrawBlock db(*this);
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
args.Handled(hr != S_FALSE);
}

// Emits the OnPressIn event
Expand Down Expand Up @@ -768,10 +761,19 @@ void WindowsTextInputComponentView::OnPointerReleased(
}

if (m_textServices && msg) {
LRESULT lresult;
DrawBlock db(*this);
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
args.Handled(hr != S_FALSE);
// Show context menu on right button release (standard Windows behavior)
if (msg == WM_RBUTTONUP && !windowsTextInputProps().contextMenuHidden) {
ShowContextMenu(position);
args.Handled(true);
} else if (msg == WM_RBUTTONUP && windowsTextInputProps().contextMenuHidden) {
// Context menu is hidden, just mark as handled
args.Handled(true);
} else {
LRESULT lresult;
DrawBlock db(*this);
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
args.Handled(hr != S_FALSE);
}
}

// Emits the OnPressOut event
Expand Down Expand Up @@ -850,6 +852,24 @@ void WindowsTextInputComponentView::OnPointerWheelChanged(
}
void WindowsTextInputComponentView::OnKeyDown(
const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept {
// Handle context menu keyboard shortcuts
// VK_APPS (Application/Context Menu key) is handled here
// SHIFT+F10 is handled via WM_CONTEXTMENU for HWND hosting (CompositionHwndHost)
// For Island hosting (ReactNativeAppBuilder), SHIFT+F10 requires InputPreTranslateKeyboardSource - TODO
bool isShiftDown =
(args.KeyboardSource().GetKeyState(winrt::Windows::System::VirtualKey::Shift) &
winrt::Microsoft::UI::Input::VirtualKeyStates::Down) == winrt::Microsoft::UI::Input::VirtualKeyStates::Down;
bool isShiftF10 = args.Key() == winrt::Windows::System::VirtualKey::F10 && isShiftDown;
bool isContextMenuKey = args.Key() == winrt::Windows::System::VirtualKey::Application;

if ((isShiftF10 || isContextMenuKey) && !windowsTextInputProps().contextMenuHidden) {
// Use caret position for context menu when triggered via keyboard
winrt::Windows::Foundation::Point caretPosition{0, 0};
ShowContextMenu(caretPosition);
args.Handled(true);
return;
}

// Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with
// WinUI behavior We do forward Ctrl+Tab to the textinput.
if (args.Key() != winrt::Windows::System::VirtualKey::Tab ||
Expand Down Expand Up @@ -1879,6 +1899,15 @@ void WindowsTextInputComponentView::updateSpellCheck(bool enable) noexcept {
m_textServices->TxSendMessage(EM_SETLANGOPTIONS, IMF_SPELLCHECKING, enable ? newLangOptions : 0, &lresult));
}

void WindowsTextInputComponentView::OnContextMenu(
const winrt::Windows::Foundation::Point &position,
bool isKeyboardTriggered) noexcept {
// Handle WM_CONTEXTMENU message (generated by Windows for SHIFT+F10 or Context Menu key)
if (!windowsTextInputProps().contextMenuHidden) {
ShowContextMenu(position);
}
}

void WindowsTextInputComponentView::ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept {
HMENU menu = CreatePopupMenu();
if (!menu)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ struct WindowsTextInputComponentView
void OnKeyUp(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept override;
void OnCharacterReceived(const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs
&args) noexcept override;
void OnContextMenu(const winrt::Windows::Foundation::Point &position, bool isKeyboardTriggered) noexcept override;
void onMounted() noexcept override;

std::optional<std::string> getAccessiblityValue() noexcept override;
Expand Down
Loading