Skip to content

[Fabric] Implement the onPressIn property for the fabric implementation of TextInput #14480

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 26, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "Implemented OnPressIn event for textinput",
"packageName": "react-native-windows",
"email": "hmalothu@microsoft.com",
"dependentChangeType": "none"
}
Original file line number Diff line number Diff line change
@@ -80,16 +80,17 @@ class PressInOutEvents extends React.Component<
super(props);
this.state = {text: 'PressIn/PressOut message'};
}

render() {
return (
<View>
<Text>{this.state.text}</Text>
<Text testID="textinput-state-display">{this.state.text}</Text>
<ExampleTextInput
placeholder="Click inside the box to observe events being fired."
style={[styles.singleLineWithHeightTextInput]}
onPressIn={() =>
this.setState({text: 'Holding down the click/touch'})
}
onPressIn={() => {
this.setState({text: 'Holding down the click/touch'});
}}
onPressOut={() => this.setState({text: 'Released click/touch'})}
testID="textinput-press"
/>
Original file line number Diff line number Diff line change
@@ -21,6 +21,21 @@ afterEach(async () => {
await verifyNoErrorLogs();
});

const searchBox = async (input: string) => {
const searchBox = await app.findElementByTestID('example_search');
await app.waitUntil(
async () => {
await searchBox.setValue(input);
return (await searchBox.getText()) === input;
},
{
interval: 1500,
timeout: 5000,
timeoutMsg: `Unable to enter correct search text into test searchbox.`,
},
);
};

describe('TextInput Tests', () => {
test('TextInputs can rewrite characters: Replace Space with Underscore', async () => {
const component = await app.findElementByTestID(
@@ -179,6 +194,34 @@ describe('TextInput Tests', () => {
},
);
});
test('TextInput triggers onPressIn and updates state text', async () => {
// Scroll the example into view
await searchBox('onPressIn');
const component = await app.findElementByTestID('textinput-press');
await component.waitForDisplayed({timeout: 5000});
const dump = await dumpVisualTree('textinput-press');
expect(dump).toMatchSnapshot();

// Trigger onPressIn (click only)
await component.click();
const stateText = await app.findElementByTestID('textinput-state-display');

await app.waitUntil(
async () => {
const currentText = await stateText.getText();
return currentText === 'Holding down the click/touch';
},
{
timeout: 5000,
timeoutMsg: 'State text not updated after onPressIn.',
},
);
// Assertion
expect(await stateText.getText()).toBe('Holding down the click/touch');
// This step helps avoid UI lock by unfocusing the input
const search = await app.findElementByTestID('example_search');
await search.setValue('');
});
test('TextInputs can have attributed text', async () => {
const component = await app.findElementByTestID('text-input');
await component.waitForDisplayed({timeout: 5000});
Original file line number Diff line number Diff line change
@@ -391,6 +391,84 @@ exports[`TextInput Tests Text have cursorColor 1`] = `
}
`;

exports[`TextInput Tests TextInput triggers onPressIn and updates state text 1`] = `
{
"Automation Tree": {
"AutomationId": "textinput-press",
"ControlType": 50004,
"HelpText": "Click inside the box to observe events being fired.",
"IsKeyboardFocusable": true,
"LocalizedControlType": "edit",
"Name": "Click inside the box to observe events being fired.",
"TextRangePattern.GetText": "Click inside the box to observe events being fired.",
},
"Component Tree": {
"Type": "Microsoft.ReactNative.Composition.WindowsTextInputComponentView",
"_Props": {
"TestId": "textinput-press",
},
},
"Visual Tree": {
"Comment": "textinput-press",
"Offset": "0, 0, 0",
"Size": "916, 30",
"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, 255)",
},
"Offset": "0, 0, 0",
"Opacity": 0,
"Size": "0, 0",
"Visual Type": "SpriteVisual",
},
],
},
}
`;

exports[`TextInput Tests TextInputs can autocapitalize: Autocapitalize Characters 1`] = `
{
"Automation Tree": {
Original file line number Diff line number Diff line change
@@ -79941,7 +79941,9 @@ exports[`snapshotAllPages TextInput 37`] = `

exports[`snapshotAllPages TextInput 38`] = `
<View>
<Text>
<Text
testID="textinput-state-display"
>
PressIn/PressOut message
</Text>
<TextInput
8 changes: 8 additions & 0 deletions packages/playground/Samples/textinput.tsx
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ import {
KeyboardAvoidingView,
ScrollView,
TouchableWithoutFeedback,
Alert,
} from 'react-native';

import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
@@ -219,6 +220,13 @@ export default class Bootstrap extends React.Component<{}, any> {
{backgroundColor: 'black', color: 'white', marginBottom: 4},
]}
/>
<TextInput
style={styles.input}
placeholder="OnPressIn..."
onPressIn={event => {
Alert.alert('Pressed!');
}}
/>
<TextInput
placeholder="Single line with selection color"
cursorColor="#00FF00"
Original file line number Diff line number Diff line change
@@ -683,6 +683,24 @@ void WindowsTextInputComponentView::OnPointerPressed(
auto hr = m_textServices->TxSendMessage(msg, static_cast<WPARAM>(wParam), static_cast<LPARAM>(lParam), &lresult);
args.Handled(hr != S_FALSE);
}

// Emits the OnPressIn event
if (m_eventEmitter && !m_comingFromJS) {
auto emitter = std::static_pointer_cast<const facebook::react::WindowsTextInputEventEmitter>(m_eventEmitter);
float offsetX = position.X - m_layoutMetrics.frame.origin.x;
float offsetY = position.Y - m_layoutMetrics.frame.origin.y;
float neutralX = m_layoutMetrics.frame.origin.x;
float neutralY = m_layoutMetrics.frame.origin.y;

facebook::react::PressEvent pressInArgs;
pressInArgs.target = m_tag;
pressInArgs.pagePoint = {position.X, position.Y};
pressInArgs.offsetPoint = {offsetX, offsetY}; //{LocationX,LocationY}
pressInArgs.timestamp = static_cast<double>(pp.Timestamp()) / 1000.0;
pressInArgs.identifier = pp.PointerId();

emitter->onPressIn(pressInArgs);
}
}

void WindowsTextInputComponentView::OnPointerReleased(
Original file line number Diff line number Diff line change
@@ -67,6 +67,22 @@ void WindowsTextInputEventEmitter::onContentSizeChange(OnContentSizeChange event
});
}

void WindowsTextInputEventEmitter::onPressIn(PressEvent event) const {
dispatchEvent("textInputPressIn", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
auto nativeEvent = jsi::Object(runtime);
nativeEvent.setProperty(runtime, "target", static_cast<double>(event.target));
nativeEvent.setProperty(runtime, "pageX", event.pagePoint.x);
nativeEvent.setProperty(runtime, "pageY", event.pagePoint.y);
nativeEvent.setProperty(runtime, "locationX", event.offsetPoint.x);
nativeEvent.setProperty(runtime, "locationY", event.offsetPoint.y);
nativeEvent.setProperty(runtime, "timestamp", event.timestamp);
nativeEvent.setProperty(runtime, "identifier", static_cast<double>(event.identifier));
payload.setProperty(runtime, "nativeEvent", nativeEvent);
return payload;
});
}

void WindowsTextInputEventEmitter::onEndEditing(OnEndEditing event) const {
dispatchEvent("textInputEndEditing", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
Original file line number Diff line number Diff line change
@@ -52,6 +52,7 @@ class WindowsTextInputEventEmitter : public ViewEventEmitter {
void onSubmitEditing(OnSubmitEditing value) const;
void onKeyPress(OnKeyPress value) const;
void onContentSizeChange(OnContentSizeChange value) const;
void onPressIn(PressEvent event) const override;
void onEndEditing(OnEndEditing value) const;
};

Original file line number Diff line number Diff line change
@@ -46,4 +46,22 @@ void HostPlatformViewEventEmitter::onMouseLeave(PointerEvent const &pointerEvent
dispatchEvent("mouseLeave", std::make_shared<PointerEvent>(pointerEvent), RawEvent::Category::ContinuousStart);
}

#pragma mark - Touch Events

void HostPlatformViewEventEmitter::onPressIn(PressEvent event) const {
dispatchEvent("pressIn", [event](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
auto nativeEvent = jsi::Object(runtime);
nativeEvent.setProperty(runtime, "target", static_cast<double>(event.target));
nativeEvent.setProperty(runtime, "pageX", event.pagePoint.x);
nativeEvent.setProperty(runtime, "pageY", event.pagePoint.y);
nativeEvent.setProperty(runtime, "locationX", event.offsetPoint.x);
nativeEvent.setProperty(runtime, "locationY", event.offsetPoint.y);
nativeEvent.setProperty(runtime, "timestamp", event.timestamp);
nativeEvent.setProperty(runtime, "identifier", event.identifier);
payload.setProperty(runtime, "nativeEvent", nativeEvent);
return payload;
});
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
@@ -8,6 +8,14 @@

namespace facebook::react {

struct PressEvent {
Tag target;
Point pagePoint;
Point offsetPoint;
double timestamp;
int identifier;
};

class HostPlatformViewEventEmitter : public BaseViewEventEmitter {
public:
using BaseViewEventEmitter::BaseViewEventEmitter;
@@ -26,6 +34,10 @@ class HostPlatformViewEventEmitter : public BaseViewEventEmitter {

void onMouseEnter(PointerEvent const &pointerEvent) const;
void onMouseLeave(PointerEvent const &pointerEvent) const;

#pragma mark - Touch Events

virtual void onPressIn(PressEvent event) const;
};

} // namespace facebook::react