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"
}
2 changes: 2 additions & 0 deletions vnext/Microsoft.ReactNative/ComponentView.idl
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ namespace Microsoft.ReactNative
DOC_STRING("Used to handle key up events when this component is focused, or if a child component did not handle the key up")
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.KeyRoutedEventArgs> KeyUp;
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.CharacterReceivedRoutedEventArgs> CharacterReceived;
DOC_STRING("Used to handle context menu key events (SHIFT+F10 or Context Menu key) when this component is focused")
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.ContextMenuKeyEventArgs> ContextMenuKey;
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.PointerRoutedEventArgs> PointerPressed;
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.PointerRoutedEventArgs> PointerReleased;
event Windows.Foundation.EventHandler<Microsoft.ReactNative.Composition.Input.PointerRoutedEventArgs> PointerMoved;
Expand Down
7 changes: 7 additions & 0 deletions vnext/Microsoft.ReactNative/Composition.Input.idl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ namespace Microsoft.ReactNative.Composition.Input
KeyboardSource KeyboardSource { get; };
};

DOC_STRING("Event arguments for context menu key events (SHIFT+F10 or Context Menu key)")
interface ContextMenuKeyEventArgs requires RoutedEventArgs
{
DOC_STRING("Gets or sets whether the event was handled. Set to true to prevent default behavior.")
Boolean Handled { get; set; };
};

interface IPointerPointTransform
{
IPointerPointTransform Inverse { get; };
Expand Down
18 changes: 18 additions & 0 deletions vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,16 @@ void ComponentView::CharacterReceived(winrt::event_token const &token) noexcept
m_characterReceivedEvent.remove(token);
}

winrt::event_token ComponentView::ContextMenuKey(
winrt::Windows::Foundation::EventHandler<
winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs> const &handler) noexcept {
return m_contextMenuKeyEvent.add(handler);
}

void ComponentView::ContextMenuKey(winrt::event_token const &token) noexcept {
m_contextMenuKeyEvent.remove(token);
}

winrt::event_token ComponentView::PointerPressed(
winrt::Windows::Foundation::EventHandler<
winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs> const &handler) noexcept {
Expand Down Expand Up @@ -609,6 +619,14 @@ void ComponentView::OnCharacterReceived(
}
}

void ComponentView::OnContextMenuKey(
const winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs &args) noexcept {
m_contextMenuKeyEvent(*this, args);
if (m_parent && !args.Handled()) {
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(m_parent)->OnContextMenuKey(args);
}
}

bool ComponentView::focusable() const noexcept {
return false;
}
Expand Down
9 changes: 9 additions & 0 deletions vnext/Microsoft.ReactNative/Fabric/ComponentView.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ struct ComponentView
winrt::Windows::Foundation::EventHandler<
winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs> const &handler) noexcept;
void CharacterReceived(winrt::event_token const &token) noexcept;
winrt::event_token ContextMenuKey(
winrt::Windows::Foundation::EventHandler<
winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs> const &handler) noexcept;
void ContextMenuKey(winrt::event_token const &token) noexcept;
winrt::event_token PointerPressed(
winrt::Windows::Foundation::EventHandler<
winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs> const &handler) noexcept;
Expand Down Expand Up @@ -253,6 +257,8 @@ 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 OnContextMenuKey(
const winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs &args) noexcept;

protected:
winrt::com_ptr<winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder> m_builder;
Expand All @@ -277,6 +283,9 @@ struct ComponentView
winrt::event<winrt::Windows::Foundation::EventHandler<
winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs>>
m_characterReceivedEvent;
winrt::event<winrt::Windows::Foundation::EventHandler<
winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs>>
m_contextMenuKeyEvent;
winrt::event<winrt::Windows::Foundation::EventHandler<
winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs>>
m_pointerPressedEvent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,18 @@ winrt::Microsoft::ReactNative::Composition::Input::KeyboardSource CharacterRecei
return m_source;
}

ContextMenuKeyEventArgs::ContextMenuKeyEventArgs(facebook::react::Tag tag) : m_tag(tag) {}

int32_t ContextMenuKeyEventArgs::OriginalSource() noexcept {
return m_tag;
}
bool ContextMenuKeyEventArgs::Handled() noexcept {
return m_handled;
}
void ContextMenuKeyEventArgs::Handled(bool value) noexcept {
m_handled = value;
}

Pointer::Pointer(winrt::Microsoft::ReactNative::Composition::Input::PointerDeviceType type, uint32_t id)
: m_type(type), m_id(id) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ struct CharacterReceivedRoutedEventArgs
const winrt::Microsoft::ReactNative::Composition::Input::KeyboardSource m_source;
};

struct ContextMenuKeyEventArgs : winrt::implements<
ContextMenuKeyEventArgs,
winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs,
winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs> {
ContextMenuKeyEventArgs(facebook::react::Tag tag);

int32_t OriginalSource() noexcept;
bool Handled() noexcept;
void Handled(bool value) noexcept;

private:
facebook::react::Tag m_tag{-1};
bool m_handled{false};
};

struct Pointer : PointerT<Pointer> {
Pointer(winrt::Microsoft::ReactNative::Composition::Input::PointerDeviceType type, uint32_t id);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,32 @@ 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) {
auto tag =
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)
->Tag();
auto contextMenuArgs = winrt::make<
winrt::Microsoft::ReactNative::Composition::Input::implementation::ContextMenuKeyEventArgs>(tag);
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)
->OnContextMenuKey(contextMenuArgs);
if (contextMenuArgs.Handled()) {
args.Handled(true);
}
}
}
}
});
}
}

Expand All @@ -336,6 +362,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 +470,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
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,18 @@ 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) {
// Context menu is hidden - don't mark as handled, let app add custom behavior
} 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 @@ -1879,6 +1880,20 @@ void WindowsTextInputComponentView::updateSpellCheck(bool enable) noexcept {
m_textServices->TxSendMessage(EM_SETLANGOPTIONS, IMF_SPELLCHECKING, enable ? newLangOptions : 0, &lresult));
}

void WindowsTextInputComponentView::OnContextMenuKey(
const winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs &args) noexcept {
// Handle context menu key event (SHIFT+F10 or Context Menu key)
if (!windowsTextInputProps().contextMenuHidden) {
// Use cursor position for keyboard-triggered context menu
POINT cursorPos;
GetCursorPos(&cursorPos);
ShowContextMenu(
winrt::Windows::Foundation::Point{static_cast<float>(cursorPos.x), static_cast<float>(cursorPos.y)});
args.Handled(true);
}
// If contextMenuHidden, don't mark as handled - let app handle it
}

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,8 @@ 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 OnContextMenuKey(
const winrt::Microsoft::ReactNative::Composition::Input::ContextMenuKeyEventArgs &args) noexcept override;
void onMounted() noexcept override;

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