diff --git a/change/react-native-windows-8be51422-0198-43a0-bbab-29659d755e48.json b/change/react-native-windows-8be51422-0198-43a0-bbab-29659d755e48.json new file mode 100644 index 00000000000..351e972dd6c --- /dev/null +++ b/change/react-native-windows-8be51422-0198-43a0-bbab-29659d755e48.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "sumbitkeyevents working with shift", + "packageName": "react-native-windows", + "email": "tatianakapos@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts b/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts index eac959d8df8..d55bf30f4c3 100644 --- a/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts +++ b/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts @@ -264,6 +264,28 @@ describe('TextInput Tests', () => { 'textinput-clear-on-submit', ); await component.waitForDisplayed({timeout: 5000}); + await app.waitUntil( + async () => { + await component.setValue('Hello World'); + return (await component.getText()) === 'Hello World'; + }, + { + interval: 1500, + timeout: 5000, + timeoutMsg: `Unable to enter correct text.`, + }, + ); + await app.waitUntil( + async () => { + await component.setValue('\uE007'); + return (await component.getText()) === ''; + }, + { + interval: 1500, + timeout: 5000, + timeoutMsg: `Unable to enter correct text.`, + }, + ); const dump = await dumpVisualTree('textinput-clear-on-submit'); expect(dump).toMatchSnapshot(); }); @@ -283,11 +305,33 @@ describe('TextInput Tests', () => { const dump = await dumpVisualTree('textinput-clear-on-submit-3'); expect(dump).toMatchSnapshot(); }); - test('TextInputs can submit with custom key', async () => { + test('TextInputs can submit with custom key, multilined and submit with enter', async () => { const component = await app.findElementByTestID( 'textinput-clear-on-submit-4', ); await component.waitForDisplayed({timeout: 5000}); + await app.waitUntil( + async () => { + await component.setValue('Hello World'); + return (await component.getText()) === 'Hello World'; + }, + { + interval: 1500, + timeout: 5000, + timeoutMsg: `Unable to enter correct text.`, + }, + ); + await app.waitUntil( + async () => { + await component.setValue('\uE007'); + return (await component.getText()) === ''; + }, + { + interval: 1500, + timeout: 5000, + timeoutMsg: `Unable to enter correct text.`, + }, + ); const dump = await dumpVisualTree('textinput-clear-on-submit-4'); expect(dump).toMatchSnapshot(); }); diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/TextInputComponentTest.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/TextInputComponentTest.test.ts.snap index 6198d4cd7a7..1179a922e86 100644 --- a/packages/e2e-test-app-fabric/test/__snapshots__/TextInputComponentTest.test.ts.snap +++ b/packages/e2e-test-app-fabric/test/__snapshots__/TextInputComponentTest.test.ts.snap @@ -2421,8 +2421,8 @@ exports[`TextInput Tests TextInputs can clear on submit 1`] = ` ], "Opacity": 0, "Size": [ - 0, - 0, + 1, + 19, ], "Visual Type": "SpriteVisual", }, @@ -6071,7 +6071,7 @@ exports[`TextInput Tests TextInputs can set their readOnly prop to true 1`] = ` } `; -exports[`TextInput Tests TextInputs can submit with custom key 1`] = ` +exports[`TextInput Tests TextInputs can submit with custom key, multilined and submit with enter 1`] = ` { "Automation Tree": { "AutomationId": "textinput-clear-on-submit-4", @@ -6096,8 +6096,8 @@ exports[`TextInput Tests TextInputs can submit with custom key 1`] = ` ], "Opacity": 0, "Size": [ - 0, - 0, + 1, + 19, ], "Visual Type": "SpriteVisual", }, diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index b01d707683d..4af9d1fcee8 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -798,6 +798,44 @@ void WindowsTextInputComponentView::OnKeyUp( Super::OnKeyDown(source, args); } +bool WindowsTextInputComponentView::ShouldSubmit( + const winrt::Microsoft::ReactNative::Composition::Input::KeyboardSource &source, + const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs &args) noexcept { + bool shouldSubmit = true; + + if (shouldSubmit) { + if (!m_multiline && m_submitKeyEvents.size() == 0) { + // If no 'submitKeyEvents' are supplied, use the default behavior for single-line TextInput + shouldSubmit = args.KeyCode() == '\r'; + } else if (m_submitKeyEvents.size() > 0) { + auto submitKeyEvent = m_submitKeyEvents.at(0); + // If 'submitKeyEvents' are supplied, use them to determine whether to emit onSubmitEditing' for either + // single-line or multi-line TextInput + if (args.KeyCode() == '\r') { + bool shiftDown = source.GetKeyState(winrt::Windows::System::VirtualKey::Shift) == + winrt::Windows::UI::Core::CoreVirtualKeyStates::Down; + bool ctrlDown = source.GetKeyState(winrt::Windows::System::VirtualKey::Control) == + winrt::Windows::UI::Core::CoreVirtualKeyStates::Down; + bool altDown = source.GetKeyState(winrt::Windows::System::VirtualKey::Control) == + winrt::Windows::UI::Core::CoreVirtualKeyStates::Down; + bool metaDown = source.GetKeyState(winrt::Windows::System::VirtualKey::LeftWindows) == + winrt::Windows::UI::Core::CoreVirtualKeyStates::Down || + source.GetKeyState(winrt::Windows::System::VirtualKey::RightWindows) == + winrt::Windows::UI::Core::CoreVirtualKeyStates::Down; + return (submitKeyEvent.shiftKey && shiftDown) || (submitKeyEvent.ctrlKey && ctrlDown) || + (submitKeyEvent.altKey && altDown) || (submitKeyEvent.metaKey && metaDown) || + (!submitKeyEvent.shiftKey && !submitKeyEvent.altKey && !submitKeyEvent.metaKey && !submitKeyEvent.altKey && + !shiftDown && !ctrlDown && !altDown && !metaDown); + } else { + shouldSubmit = false; + } + } else { + shouldSubmit = false; + } + } + return shouldSubmit; +} + void WindowsTextInputComponentView::OnCharacterReceived( const winrt::Microsoft::ReactNative::Composition::Input::KeyboardSource &source, const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs &args) noexcept { @@ -809,6 +847,24 @@ void WindowsTextInputComponentView::OnCharacterReceived( return; } + // Logic for submit events + if (ShouldSubmit(source, args)) { + // call onSubmitEditing event + if (m_eventEmitter && !m_comingFromJS) { + auto emitter = std::static_pointer_cast(m_eventEmitter); + facebook::react::WindowsTextInputEventEmitter::OnSubmitEditing onSubmitEditingArgs; + onSubmitEditingArgs.text = GetTextFromRichEdit(); + onSubmitEditingArgs.eventCount = ++m_nativeEventCount; + emitter->onSubmitEditing(onSubmitEditingArgs); + } + + if (m_clearTextOnSubmit) { + // clear text from RichEdit + m_textServices->TxSetText(L""); + } + return; + } + WPARAM wParam = static_cast(args.KeyCode()); LPARAM lParam = 0; lParam = args.KeyStatus().RepeatCount; // bits 0-15 @@ -926,6 +982,7 @@ void WindowsTextInputComponentView::updateProps( } if (oldTextInputProps.multiline != newTextInputProps.multiline) { + m_multiline = newTextInputProps.multiline; propBitsMask |= TXTBIT_MULTILINE | TXTBIT_WORDWRAP; if (newTextInputProps.multiline) { propBits |= TXTBIT_MULTILINE | TXTBIT_WORDWRAP; @@ -952,6 +1009,16 @@ void WindowsTextInputComponentView::updateProps( updateCursorColor(newTextInputProps.cursorColor, newTextInputProps.textAttributes.foregroundColor); } + if (oldTextInputProps.clearTextOnSubmit != newTextInputProps.clearTextOnSubmit) { + m_clearTextOnSubmit = newTextInputProps.clearTextOnSubmit; + } + + if ((!newTextInputProps.submitKeyEvents.empty())) { + m_submitKeyEvents = newTextInputProps.submitKeyEvents; + } else { + m_submitKeyEvents.clear(); + } + /* if (oldTextInputProps.textAttributes.foregroundColor != newTextInputProps.textAttributes.foregroundColor) { if (newTextInputProps.textAttributes.foregroundColor) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h index 92ef15c77a3..ac2bd0ce86c 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h @@ -109,6 +109,9 @@ struct WindowsTextInputComponentView : WindowsTextInputComponentViewT m_submitKeyEvents; }; } // namespace winrt::Microsoft::ReactNative::Composition::implementation diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputEventEmitter.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputEventEmitter.cpp index 016a0aa49fa..628e8fe4b79 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputEventEmitter.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputEventEmitter.cpp @@ -28,4 +28,14 @@ void WindowsTextInputEventEmitter::onSelectionChange(const OnSelectionChange &ev }); } +void WindowsTextInputEventEmitter::onSubmitEditing(OnSubmitEditing event) const { + dispatchEvent("textInputSubmitEditing", [event = std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "eventCount", event.eventCount); + payload.setProperty(runtime, "target", event.target); + payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + } // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputEventEmitter.h b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputEventEmitter.h index 6372c8c7c50..e35a4ab47ea 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputEventEmitter.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputEventEmitter.h @@ -26,8 +26,15 @@ class WindowsTextInputEventEmitter : public ViewEventEmitter { Selection selection; }; + struct OnSubmitEditing { + int eventCount; + int target; + std::string text; + }; + void onChange(OnChange value) const; void onSelectionChange(const OnSelectionChange &value) const; + void onSubmitEditing(OnSubmitEditing value) const; }; } // namespace facebook::react diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputProps.h b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputProps.h index 9c9fc0158d7..99580c66e3b 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputProps.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputProps.h @@ -33,10 +33,10 @@ static inline std::string toString(const CompWindowsTextInputSelectionStruct &va } struct CompWindowsTextInputSubmitKeyEventsStruct { - bool altKey; - bool ctrlKey; - bool metaKey; - bool shiftKey; + bool altKey{false}; + bool ctrlKey{false}; + bool metaKey{false}; + bool shiftKey{false}; std::string code; };