diff --git a/change/react-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json b/change/react-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json new file mode 100644 index 00000000000..e0c0d3e7754 --- /dev/null +++ b/change/react-native-windows-daac39ef-4292-4f66-81ae-708b1c0f6676.json @@ -0,0 +1,7 @@ +{ + "comment": "Fix TextInput clearTextOnSubmit-style behavior regression in Fabric architecture", + "type": "prerelease", + "packageName": "react-native-windows", + "email": "protikbiswas100@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/@react-native-windows/tester/src/js/examples/TextInput/TextInputExample.windows.js b/packages/@react-native-windows/tester/src/js/examples/TextInput/TextInputExample.windows.js index 701e2a2ac59..67b3cae9f21 100644 --- a/packages/@react-native-windows/tester/src/js/examples/TextInput/TextInputExample.windows.js +++ b/packages/@react-native-windows/tester/src/js/examples/TextInput/TextInputExample.windows.js @@ -762,7 +762,7 @@ const examples: Array = [ }, // [Windows { - title: 'Clear text on submit', + title: 'Clear text on submitting', render: function (): React.Node { return ( @@ -799,6 +799,38 @@ const examples: Array = [ ); }, }, + { + title: 'Manual clear text on submit (setValue in onSubmitEditing)', + render: function () { + function ManualClearExample() { + const [value, setValue] = React.useState(''); + const submitValue = () => { + // Simulate the regression scenario: manual setValue('') during onSubmitEditing + if (value !== '') { + setValue(''); + } + }; + return ( + + + Manual clear using setValue('') in onSubmitEditing callback: + + + Current value: "{value}" + + ); + } + return ; + }, + }, { title: 'Stop propagation sample', render: function (): React.Node { 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 2c3f866f0ce..2bda46c9a50 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 @@ -333,7 +333,7 @@ exports[`TextInput Tests Text have cursorColor 1`] = ` "Visual Tree": { "Comment": "textinput-cursorColor", "Offset": "0, 0, 0", - "Size": "916, 32", + "Size": "916, 33", "Visual Type": "SpriteVisual", "__Children": [ { @@ -1153,7 +1153,7 @@ exports[`TextInput Tests TextInputs can be defined as a set using accessibilityP "Visual Tree": { "Comment": "textinput-set", "Offset": "0, 0, 0", - "Size": "916, 94", + "Size": "916, 93", "Visual Type": "SpriteVisual", "__Children": [ { @@ -1222,12 +1222,12 @@ exports[`TextInput Tests TextInputs can be defined as a set using accessibilityP }, { "Offset": "0, 31, 0", - "Size": "916, 32", + "Size": "916, 33", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "916, 32", + "Size": "916, 33", "Visual Type": "SpriteVisual", "__Children": [ { @@ -1286,12 +1286,12 @@ exports[`TextInput Tests TextInputs can be defined as a set using accessibilityP }, { "Offset": "0, 62, 0", - "Size": "916, 33", + "Size": "916, 32", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "916, 33", + "Size": "916, 32", "Visual Type": "SpriteVisual", "__Children": [ { @@ -2757,7 +2757,7 @@ exports[`TextInput Tests TextInputs can have caretHidden 1`] = ` "Visual Tree": { "Comment": "textinput-carethidden", "Offset": "0, 0, 0", - "Size": "916, 32", + "Size": "916, 33", "Visual Type": "SpriteVisual", "__Children": [ { @@ -4333,7 +4333,7 @@ exports[`TextInput Tests TextInputs can have shadows 1`] = ` "Visual Tree": { "Comment": "textinput-shadow", "Offset": "0, 0, 0", - "Size": "916, 33", + "Size": "916, 32", "Visual Type": "SpriteVisual", "__Children": [ { @@ -6041,17 +6041,17 @@ exports[`TextInput Tests TextInputs which have a searchbox role should also supp "Visual Tree": { "Comment": "textinput-searchbox", "Offset": "0, 0, 0", - "Size": "916, 31", + "Size": "916, 32", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "916, 33", + "Size": "916, 32", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "916, 33", + "Size": "916, 32", "Visual Type": "SpriteVisual", "__Children": [ { diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap b/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap index cabc5c16230..dd53f45b53a 100644 --- a/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap +++ b/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap @@ -81312,6 +81312,51 @@ exports[`snapshotAllPages TextInput 43`] = ` `; exports[`snapshotAllPages TextInput 44`] = ` + + + Manual clear using setValue('') in onSubmitEditing callback: + + + + Current value: " + " + + +`; + +exports[`snapshotAllPages TextInput 45`] = ` [ Spell Check Enabled: @@ -81426,7 +81471,7 @@ exports[`snapshotAllPages TextInput 45`] = ` ] `; -exports[`snapshotAllPages TextInput 46`] = ` +exports[`snapshotAllPages TextInput 47`] = ` CaretHidden @@ -81458,7 +81503,7 @@ exports[`snapshotAllPages TextInput 46`] = ` `; -exports[`snapshotAllPages TextInput 47`] = ` +exports[`snapshotAllPages TextInput 48`] = ` Cursorcolor @@ -81490,7 +81535,7 @@ exports[`snapshotAllPages TextInput 47`] = ` `; -exports[`snapshotAllPages TextInput 48`] = ` +exports[`snapshotAllPages TextInput 49`] = ` Shadow @@ -81528,7 +81573,7 @@ exports[`snapshotAllPages TextInput 48`] = ` `; -exports[`snapshotAllPages TextInput 49`] = ` +exports[`snapshotAllPages TextInput 50`] = ` `; -exports[`snapshotAllPages TextInput 50`] = ` +exports[`snapshotAllPages TextInput 51`] = ` text; winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end); - if (eventCount >= m_nativeEventCount) { + + // Standard synchronization check - only accept current or future events + bool isCurrentEvent = eventCount >= m_nativeEventCount; + + // Special case: Allow setValue('') if it's responding to the exact onSubmitEditing event + // This is safe because clearing text doesn't depend on intermediate state + bool isSubmitClearResponse = false; + if (!isCurrentEvent && text.has_value() && winrt::to_string(text.value()).empty()) { + // Only allow if this is responding to the exact submit event we fired + isSubmitClearResponse = (m_lastSubmitEventCount != -1 && eventCount == m_lastSubmitEventCount); + } + + if (isCurrentEvent || isSubmitClearResponse) { m_comingFromJS = true; { if (text.has_value()) { @@ -564,6 +576,11 @@ void WindowsTextInputComponentView::HandleCommand( m_textServices->TxSendMessage(EM_SETSEL, static_cast(begin), static_cast(end), &res)); } + // Clear the submit event count after using it to prevent reuse + if (isSubmitClearResponse) { + m_lastSubmitEventCount = -1; + } + m_comingFromJS = false; } } @@ -959,6 +976,7 @@ void WindowsTextInputComponentView::OnCharacterReceived( facebook::react::WindowsTextInputEventEmitter::OnSubmitEditing onSubmitEditingArgs; onSubmitEditingArgs.text = GetTextFromRichEdit(); onSubmitEditingArgs.eventCount = ++m_nativeEventCount; + m_lastSubmitEventCount = m_nativeEventCount; // Track this submit event emitter->onSubmitEditing(onSubmitEditingArgs); } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h index 9692f5ca351..40b0a68010a 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h @@ -134,6 +134,7 @@ struct WindowsTextInputComponentView float m_fontSizeMultiplier{1.0}; int64_t m_mostRecentEventCount{0}; int m_nativeEventCount{0}; + int m_lastSubmitEventCount{-1}; // Track the event count of the last onSubmitEditing bool m_comingFromJS{false}; bool m_comingFromState{false}; int m_cDrawBlock{0};