From 4294d0500a8e0af1b59755baa43d0361217f7c20 Mon Sep 17 00:00:00 2001 From: Daniel Ayala <14967941+danielayala94@users.noreply.github.com> Date: Fri, 18 Oct 2024 19:03:10 -0700 Subject: [PATCH] [Fabric] Introducing autocapitalize prop in TextInput - Take 2 (#13343) * New implementation of autocapitalize! * Change files * Fixed bug for sentences scenario * Just keep characters mode for now * Revert "Just keep characters mode for now" This reverts commit 60ca1cebbc49182992a0cdaa5dd75e930610b47a. * Re-apply changes minus packages.json.lock * The original js file was deleted, re-applying changes * Fixed snapshot and lint errors * Fix override mismatch, added comments * Remove stale test check * Minor changes * Update obsolete snapshot --- ...-aa22348e-c6b1-4cfc-9a85-c8b2bc2df484.json | 7 + .../test/TextInputComponentTest.test.ts | 63 ++++++-- .../TextInputComponentTest.test.ts.snap | 152 ------------------ .../WindowsTextInputComponentView.cpp | 32 +++- .../TextInput/WindowsTextInputComponentView.h | 4 + 5 files changed, 95 insertions(+), 163 deletions(-) create mode 100644 change/react-native-windows-aa22348e-c6b1-4cfc-9a85-c8b2bc2df484.json diff --git a/change/react-native-windows-aa22348e-c6b1-4cfc-9a85-c8b2bc2df484.json b/change/react-native-windows-aa22348e-c6b1-4cfc-9a85-c8b2bc2df484.json new file mode 100644 index 00000000000..862f10ed207 --- /dev/null +++ b/change/react-native-windows-aa22348e-c6b1-4cfc-9a85-c8b2bc2df484.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Introduced autocapitalize prop in TextInput", + "packageName": "react-native-windows", + "email": "14967941+danielayala94@users.noreply.github.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 1feb55c9102..9b66707fab3 100644 --- a/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts +++ b/packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts @@ -96,10 +96,11 @@ describe('TextInput Tests', () => { await component.waitForDisplayed({timeout: 5000}); const dump = await dumpVisualTree('capitalize-none'); expect(dump).toMatchSnapshot(); + await app.waitUntil( async () => { - await component.setValue('Hello World'); - return (await component.getText()) === 'Hello World'; + await component.setValue('hello world'); + return (await component.getText()) === 'hello world'; }, { interval: 1500, @@ -107,28 +108,70 @@ describe('TextInput Tests', () => { timeoutMsg: `Unable to enter correct text.`, }, ); - expect(await component.getText()).toBe('Hello World'); + expect(await component.getText()).toBe('hello world'); }); - test('TextInputs can autocapitalize: Autocapitalize Sentences', async () => { + // Comment out once the sentences mode has been implemented. + /*test('TextInputs can autocapitalize: Autocapitalize Sentences', async () => { const component = await app.findElementByTestID('capitalize-sentences'); await component.waitForDisplayed({timeout: 5000}); const dump = await dumpVisualTree('capitalize-sentences'); expect(dump).toMatchSnapshot(); - // Behavior not supported yet. - }); - test('TextInputs can autocapitalize: Autocapitalize Words', async () => { + + // Test behavior when text is set from JS. + // These TextInputs are currently empty. Setting defaultValue prop for them in TextInputSharedExamples.js + // leads to an "override error". This file is expected to be a exact copy of its RN Core parent. + expect(await component.getText()).toBe('initial text is not capitalized'); + + await app.waitUntil( + async () => { + await component.setValue('hey here is a sentence. one more sentence? yeah one more sentence! and a last one.'); + return (await component.getText()) === 'Hey here is a sentence. One more sentence? Yeah one more sentence! And a last one.'; + }, + { + interval: 1500, + timeout: 5000, + timeoutMsg: `Unable to enter correct text.`, + }); + });*/ + // Comment out once the words mode has been implemented. + /*test('TextInputs can autocapitalize: Autocapitalize Words', async () => { const component = await app.findElementByTestID('capitalize-words'); await component.waitForDisplayed({timeout: 5000}); const dump = await dumpVisualTree('capitalize-words'); expect(dump).toMatchSnapshot(); - // Behavior not supported yet. - }); + + // Test behavior when text is set from JS. + // These TextInputs are currently empty. Setting defaultValue prop for them in TextInputSharedExamples.js + // leads to an "override error". This file is expected to be a exact copy of its RN Core parent. + expect(await component.getText()).toBe('initial text is not capitalized'); + + await app.waitUntil( + async () => { + await component.setValue('hi i am autocapitalizing all words.'); + return (await component.getText()) === 'Hi I Am Autocapitalizing All Words.'; + }, + { + interval: 1500, + timeout: 5000, + timeoutMsg: `Unable to enter correct text.`, + }); + });*/ test('TextInputs can autocapitalize: Autocapitalize Characters', async () => { const component = await app.findElementByTestID('capitalize-characters'); await component.waitForDisplayed({timeout: 5000}); const dump = await dumpVisualTree('capitalize-characters'); expect(dump).toMatchSnapshot(); - // Behavior not supported yet. + + await app.waitUntil( + async () => { + await component.setValue('hi i am setting up this whole UPPERCASE sentence.'); + return (await component.getText()) === 'HI I AM SETTING UP THIS WHOLE UPPERCASE SENTENCE.'; + }, + { + interval: 1500, + timeout: 5000, + timeoutMsg: `Unable to enter correct text.`, + }); }); test('TextInputs can have attributed text', async () => { const component = await app.findElementByTestID('text-input'); 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 63a81293046..063014b9942 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 @@ -425,82 +425,6 @@ exports[`TextInput Tests TextInputs can autocapitalize: Autocapitalize Character } `; -exports[`TextInput Tests TextInputs can autocapitalize: Autocapitalize Sentences 1`] = ` -{ - "Automation Tree": { - "AutomationId": "capitalize-sentences", - "ControlType": 50004, - "IsKeyboardFocusable": true, - "LocalizedControlType": "edit", - "ValuePattern.IsReadOnly": false, - }, - "Component Tree": { - "Type": "Microsoft.ReactNative.Composition.WindowsTextInputComponentView", - "_Props": { - "TestId": "capitalize-sentences", - }, - }, - "Visual Tree": { - "Comment": "capitalize-sentences", - "Offset": "0, 0, 0", - "Size": "791, 28", - "Visual Type": "SpriteVisual", - "__Children": [ - { - "Offset": "0, 0, 0", - "Size": "1, 1", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "1, 0, 0", - "Size": "-2, 1", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "-1, 0, 0", - "Size": "1, 1", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "-1, 1, 0", - "Size": "1, -2", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "-1, -1, 0", - "Size": "1, 1", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "1, -1, 0", - "Size": "-2, 1", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "0, -1, 0", - "Size": "1, 1", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "0, 1, 0", - "Size": "1, -2", - "Visual Type": "SpriteVisual", - }, - { - "Brush": { - "Brush Type": "ColorBrush", - "Color": "rgba(0, 0, 0, 228)", - }, - "Offset": "0, 0, 0", - "Opacity": 0, - "Size": "0, 0", - "Visual Type": "SpriteVisual", - }, - ], - }, -} -`; - exports[`TextInput Tests TextInputs can autocapitalize: Autocapitalize Turned Off 1`] = ` { "Automation Tree": { @@ -577,82 +501,6 @@ exports[`TextInput Tests TextInputs can autocapitalize: Autocapitalize Turned Of } `; -exports[`TextInput Tests TextInputs can autocapitalize: Autocapitalize Words 1`] = ` -{ - "Automation Tree": { - "AutomationId": "capitalize-words", - "ControlType": 50004, - "IsKeyboardFocusable": true, - "LocalizedControlType": "edit", - "ValuePattern.IsReadOnly": false, - }, - "Component Tree": { - "Type": "Microsoft.ReactNative.Composition.WindowsTextInputComponentView", - "_Props": { - "TestId": "capitalize-words", - }, - }, - "Visual Tree": { - "Comment": "capitalize-words", - "Offset": "0, 0, 0", - "Size": "791, 29", - "Visual Type": "SpriteVisual", - "__Children": [ - { - "Offset": "0, 0, 0", - "Size": "1, 1", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "1, 0, 0", - "Size": "-2, 1", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "-1, 0, 0", - "Size": "1, 1", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "-1, 1, 0", - "Size": "1, -2", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "-1, -1, 0", - "Size": "1, 1", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "1, -1, 0", - "Size": "-2, 1", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "0, -1, 0", - "Size": "1, 1", - "Visual Type": "SpriteVisual", - }, - { - "Offset": "0, 1, 0", - "Size": "1, -2", - "Visual Type": "SpriteVisual", - }, - { - "Brush": { - "Brush Type": "ColorBrush", - "Color": "rgba(0, 0, 0, 228)", - }, - "Offset": "0, 0, 0", - "Opacity": 0, - "Size": "0, 0", - "Visual Type": "SpriteVisual", - }, - ], - }, -} -`; - exports[`TextInput Tests TextInputs can autocomplete, address country 1`] = ` { "Automation Tree": { diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp index 9e4ca978f87..5be5d39a5f2 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp @@ -870,6 +870,7 @@ void WindowsTextInputComponentView::OnCharacterReceived( emitter->onKeyPress(onKeyPressArgs); WPARAM wParam = static_cast(args.KeyCode()); + LPARAM lParam = 0; lParam = args.KeyStatus().RepeatCount; // bits 0-15 lParam |= args.KeyStatus().ScanCode << 16; // bits 16-23 @@ -1022,6 +1023,10 @@ void WindowsTextInputComponentView::updateProps( m_submitKeyEvents.clear(); } + if (oldTextInputProps.autoCapitalize != newTextInputProps.autoCapitalize) { + autoCapitalizeOnUpdateProps(oldTextInputProps.autoCapitalize, newTextInputProps.autoCapitalize); + } + UpdatePropertyBits(); } @@ -1476,4 +1481,29 @@ winrt::Microsoft::ReactNative::ComponentView WindowsTextInputComponentView::Crea return winrt::make(compContext, tag, reactContext); } -} // namespace winrt::Microsoft::ReactNative::Composition::implementation \ No newline at end of file +// This function assumes that previous and new capitalization types are different. +void WindowsTextInputComponentView::autoCapitalizeOnUpdateProps( + const std::string &previousCapitalizationType, + const std::string &newCapitalizationType) noexcept { + /* + Possible values are: + Characters - All characters. + Words - First letter of each word. + Sentences - First letter of each sentence. + None - Do not autocapitalize anything. + + For now, only characters and none are supported. + */ + + if (previousCapitalizationType == "characters") { + winrt::check_hresult(m_textServices->TxSendMessage( + EM_SETEDITSTYLE, 0 /* disable */, SES_UPPERCASE /* flag affected */, nullptr /* LRESULT */)); + } + + if (newCapitalizationType == "characters") { + winrt::check_hresult(m_textServices->TxSendMessage( + EM_SETEDITSTYLE, SES_UPPERCASE /* enable */, SES_UPPERCASE /* flag affected */, nullptr /* LRESULT */)); + } +} + +} // namespace winrt::Microsoft::ReactNative::Composition::implementation diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h index fb3084663cc..e8919325a05 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h @@ -104,6 +104,10 @@ struct WindowsTextInputComponentView void InternalFinalize() noexcept; void UpdatePropertyBits() noexcept; + void autoCapitalizeOnUpdateProps( + const std::string &previousCapitalizationType, + const std::string &newcapitalizationType) noexcept; + winrt::Windows::UI::Composition::CompositionSurfaceBrush m_brush{nullptr}; winrt::Microsoft::ReactNative::Composition::Experimental::ICaretVisual m_caretVisual{nullptr}; winrt::Microsoft::ReactNative::Composition::Experimental::IDrawingSurfaceBrush m_drawingSurface{nullptr};