diff --git a/change/react-native-windows-60609fec-a316-43d0-b789-deb31e09b2f7.json b/change/react-native-windows-60609fec-a316-43d0-b789-deb31e09b2f7.json new file mode 100644 index 00000000000..03bde7d0c45 --- /dev/null +++ b/change/react-native-windows-60609fec-a316-43d0-b789-deb31e09b2f7.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Emit TextInput onChangeText before onSelectionChange", + "packageName": "react-native-windows", + "email": "erozell@outlook.com", + "dependentChangeType": "patch" +} diff --git a/packages/e2e-test-app/test/LegacyTextInputTest.test.ts b/packages/e2e-test-app/test/LegacyTextInputTest.test.ts index 7b5a3ba0be2..bdfe824da2e 100644 --- a/packages/e2e-test-app/test/LegacyTextInputTest.test.ts +++ b/packages/e2e-test-app/test/LegacyTextInputTest.test.ts @@ -74,6 +74,15 @@ describe('LegacyTextInputTest', () => { expect(await textInput.getText()).toBe('abc\rdef'); }); + + test('TextInput onChange before onSelectionChange', async () => { + const textInput = await textInputField(); + await textInput.setValue('a'); + await assertLogContainsInOrder( + 'onChange text: a', + 'onSelectionChange range: 1,1', + ); + }); }); async function textInputField() { @@ -101,3 +110,30 @@ async function assertLogContains(text: string) { }, ); } + +async function assertLogContainsInOrder(...expectedLines: string[]) { + const textLogComponent = await $('~textinput-log'); + + await browser.waitUntil( + async () => { + const loggedText = await textLogComponent.getText(); + const actualLines = loggedText.split('\n'); + let previousIndex = Number.MAX_VALUE; + for (const line of expectedLines) { + const index = actualLines.findIndex(l => l === line); + if (index === -1 || index > previousIndex) { + return false; + } + + previousIndex = index; + } + + return true; + }, + { + timeoutMsg: `"${await textLogComponent.getText()}" did not contain lines "${expectedLines.join( + ', ', + )}"`, + }, + ); +} diff --git a/vnext/Microsoft.ReactNative/Views/TextInputViewManager.cpp b/vnext/Microsoft.ReactNative/Views/TextInputViewManager.cpp index ba338c8af0c..f13e35d094a 100644 --- a/vnext/Microsoft.ReactNative/Views/TextInputViewManager.cpp +++ b/vnext/Microsoft.ReactNative/Views/TextInputViewManager.cpp @@ -154,7 +154,6 @@ class TextInputShadowNode : public ShadowNodeBase { private: xaml::Controls::TextBox::TextChanging_revoker m_textBoxTextChangingRevoker{}; - xaml::Controls::TextBox::TextChanged_revoker m_textBoxTextChangedRevoker{}; xaml::Controls::TextBox::SelectionChanged_revoker m_textBoxSelectionChangedRevoker{}; xaml::Controls::TextBox::ContextMenuOpening_revoker m_textBoxContextMenuOpeningRevoker{}; @@ -202,35 +201,22 @@ void TextInputShadowNode::registerEvents() { // // TextChanging is used to drop the Javascript response of 'A' and expect // another TextChanged event with correct event count. - if (m_isTextBox) { - m_passwordBoxPasswordChangingRevoker = {}; - m_textBoxTextChangingRevoker = - control.as().TextChanging(winrt::auto_revoke, [=](auto &&, auto &&) { - if (m_initialUpdateComplete) - m_nativeEventCount++; - }); - } else { - m_textBoxTextChangingRevoker = {}; - - if (control.try_as()) { - m_passwordBoxPasswordChangingRevoker = - control.as().PasswordChanging(winrt::auto_revoke, [=](auto &&, auto &&) { - if (m_initialUpdateComplete) - m_nativeEventCount++; - }); - } - } - if (m_isTextBox) { m_passwordBoxPasswordChangedRevoker = {}; + m_passwordBoxPasswordChangingRevoker = {}; auto textBox = control.as(); - m_textBoxTextChangedRevoker = textBox.TextChanged( + m_textBoxTextChangingRevoker = textBox.TextChanging( winrt::auto_revoke, [=](auto &&, auto &&) { dispatchTextInputChangeEvent(textBox.Text()); }); } else { - m_textBoxTextChangedRevoker = {}; + m_textBoxTextChangingRevoker = {}; auto passwordBox = control.as(); - m_passwordBoxPasswordChangedRevoker = passwordBox.PasswordChanged( - winrt::auto_revoke, [=](auto &&, auto &&) { dispatchTextInputChangeEvent(passwordBox.Password()); }); + if (control.try_as()) { + m_passwordBoxPasswordChangingRevoker = passwordBox.PasswordChanging( + winrt::auto_revoke, [=](auto &&, auto &&) { dispatchTextInputChangeEvent(passwordBox.Password()); }); + } else { + m_passwordBoxPasswordChangedRevoker = passwordBox.PasswordChanged( + winrt::auto_revoke, [=](auto &&, auto &&) { dispatchTextInputChangeEvent(passwordBox.Password()); }); + } } if (m_isTextBox) {