Skip to content

Commit 10898c3

Browse files
Nitin-100Nitin Chaudhary
andauthored
ContextMenu implementation in Fabric as per Parity to Paper (#15339)
* Change files * Implement context menu support for Fabric TextInput with contextMenuHidden property * Fix beachball change file - add meaningful comment * Fix context menu Paste option to check clipboard content - Added clipboard content check using IsClipboardFormatAvailable(CF_UNICODETEXT) - Paste menu item now only enabled when clipboard has text AND field is editable - Fixes bug where Cut/Copy/Paste showed even when text field was empty - Matches XAML TextBox automatic clipboard state checking behavior Menu item logic now matches Paper: - Cut: requires selection AND editable - Copy: requires selection only - Paste: requires editable AND clipboard has text content - Select All: requires non-empty text AND editable * Fix context menu to show on button release (WM_RBUTTONUP) instead of press --------- Co-authored-by: Nitin Chaudhary <[email protected]>
1 parent 0fe72b7 commit 10898c3

File tree

3 files changed

+68
-10
lines changed

3 files changed

+68
-10
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"comment": "Implement context menu support for Fabric TextInput with Cut, Copy, Paste, and Select All options",
3+
"type": "prerelease",
4+
"packageName": "react-native-windows",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

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

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -697,10 +697,17 @@ void WindowsTextInputComponentView::OnPointerPressed(
697697
}
698698

699699
if (m_textServices && msg) {
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);
700+
if (msg == WM_RBUTTONUP && !windowsTextInputProps().contextMenuHidden) {
701+
ShowContextMenu(position);
702+
args.Handled(true);
703+
} else if (msg == WM_RBUTTONUP && windowsTextInputProps().contextMenuHidden) {
704+
args.Handled(true);
705+
} else {
706+
LRESULT lresult;
707+
DrawBlock db(*this);
708+
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
709+
args.Handled(hr != S_FALSE);
710+
}
704711
}
705712

706713
// Emits the OnPressIn event
@@ -844,8 +851,8 @@ void WindowsTextInputComponentView::OnPointerWheelChanged(
844851
}
845852
void WindowsTextInputComponentView::OnKeyDown(
846853
const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept {
847-
// Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with WinUI
848-
// behavior We do forward Ctrl+Tab to the textinput.
854+
// Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with
855+
// WinUI behavior We do forward Ctrl+Tab to the textinput.
849856
if (args.Key() != winrt::Windows::System::VirtualKey::Tab ||
850857
(args.KeyboardSource().GetKeyState(winrt::Windows::System::VirtualKey::Control) &
851858
winrt::Microsoft::UI::Input::VirtualKeyStates::Down) == winrt::Microsoft::UI::Input::VirtualKeyStates::Down) {
@@ -872,8 +879,8 @@ void WindowsTextInputComponentView::OnKeyDown(
872879

873880
void WindowsTextInputComponentView::OnKeyUp(
874881
const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept {
875-
// Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with WinUI
876-
// behavior We do forward Ctrl+Tab to the textinput.
882+
// Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with
883+
// WinUI behavior We do forward Ctrl+Tab to the textinput.
877884
if (args.Key() != winrt::Windows::System::VirtualKey::Tab ||
878885
(args.KeyboardSource().GetKeyState(winrt::Windows::System::VirtualKey::Control) &
879886
winrt::Microsoft::UI::Input::VirtualKeyStates::Down) == winrt::Microsoft::UI::Input::VirtualKeyStates::Down) {
@@ -943,8 +950,8 @@ bool WindowsTextInputComponentView::ShouldSubmit(
943950

944951
void WindowsTextInputComponentView::OnCharacterReceived(
945952
const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs &args) noexcept {
946-
// Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with WinUI
947-
// behavior We do forward Ctrl+Tab to the textinput.
953+
// Do not forward tab keys into the TextInput, since we want that to do the tab loop instead. This aligns with
954+
// WinUI behavior We do forward Ctrl+Tab to the textinput.
948955
if ((args.KeyCode() == '\t') &&
949956
((args.KeyboardSource().GetKeyState(winrt::Windows::System::VirtualKey::Control) &
950957
winrt::Microsoft::UI::Input::VirtualKeyStates::Down) != winrt::Microsoft::UI::Input::VirtualKeyStates::Down)) {
@@ -1827,4 +1834,47 @@ void WindowsTextInputComponentView::updateSpellCheck(bool enable) noexcept {
18271834
winrt::check_hresult(
18281835
m_textServices->TxSendMessage(EM_SETLANGOPTIONS, IMF_SPELLCHECKING, enable ? newLangOptions : 0, &lresult));
18291836
}
1837+
1838+
void WindowsTextInputComponentView::ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept {
1839+
HMENU menu = CreatePopupMenu();
1840+
if (!menu)
1841+
return;
1842+
1843+
CHARRANGE selection;
1844+
LRESULT res;
1845+
m_textServices->TxSendMessage(EM_EXGETSEL, 0, reinterpret_cast<LPARAM>(&selection), &res);
1846+
1847+
bool hasSelection = selection.cpMin != selection.cpMax;
1848+
bool isEmpty = GetTextFromRichEdit().empty();
1849+
bool isReadOnly = windowsTextInputProps().editable == false;
1850+
bool canPaste = !isReadOnly && IsClipboardFormatAvailable(CF_UNICODETEXT);
1851+
1852+
AppendMenuW(menu, MF_STRING | (hasSelection && !isReadOnly ? 0 : MF_GRAYED), 1, L"Cut");
1853+
AppendMenuW(menu, MF_STRING | (hasSelection ? 0 : MF_GRAYED), 2, L"Copy");
1854+
AppendMenuW(menu, MF_STRING | (canPaste ? 0 : MF_GRAYED), 3, L"Paste");
1855+
AppendMenuW(menu, MF_STRING | (!isEmpty && !isReadOnly ? 0 : MF_GRAYED), 4, L"Select All");
1856+
1857+
POINT cursorPos;
1858+
GetCursorPos(&cursorPos);
1859+
1860+
HWND hwnd = GetActiveWindow();
1861+
1862+
int cmd = TrackPopupMenu(
1863+
menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_NONOTIFY, cursorPos.x, cursorPos.y, 0, hwnd, NULL);
1864+
1865+
if (cmd == 1) { // Cut
1866+
m_textServices->TxSendMessage(WM_CUT, 0, 0, &res);
1867+
OnTextUpdated();
1868+
} else if (cmd == 2) { // Copy
1869+
m_textServices->TxSendMessage(WM_COPY, 0, 0, &res);
1870+
} else if (cmd == 3) { // Paste
1871+
m_textServices->TxSendMessage(WM_PASTE, 0, 0, &res);
1872+
OnTextUpdated();
1873+
} else if (cmd == 4) { // Select All
1874+
m_textServices->TxSendMessage(EM_SETSEL, 0, -1, &res);
1875+
}
1876+
1877+
DestroyMenu(menu);
1878+
}
1879+
18301880
} // namespace winrt::Microsoft::ReactNative::Composition::implementation

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ struct WindowsTextInputComponentView
118118
void updateLetterSpacing(float letterSpacing) noexcept;
119119
void updateAutoCorrect(bool value) noexcept;
120120
void updateSpellCheck(bool value) noexcept;
121+
void ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept;
121122

122123
winrt::Windows::UI::Composition::CompositionSurfaceBrush m_brush{nullptr};
123124
winrt::Microsoft::ReactNative::Composition::Experimental::ICaretVisual m_caretVisual{nullptr};

0 commit comments

Comments
 (0)