fix(app): clear composer text input upon sending on mobile (#3564)
* fix(app): clear mobile composer input on submit
Submitting a prompt on mobile failed to clear the native text input because EditingTextInput.replaceText previously only called setNativeProps({ text }), which is ignored on Fabric and PasteTextInput.
Fix this by:
- Delegating replaceText in EditingTextInputNative to inputRef.current.replaceText when available, clearing via clear() when text is empty, and falling back to setNativeProps.
- Patching @mattermost/react-native-paste-input to expose replaceText(text, selection) backed by native Commands.setTextAndSelection, updating clear() to use live event counts, and preventing useImperativeHandle closures over null refs during initial mount.
- Registering the patch in scripts/postinstall-patches.mjs and adding unit tests for native text input replacement and clearing.
* fix: update nix npm dependency hash
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
// @vitest-environment jsdom
|
||||
import React, { act, createRef } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { EditingTextInput } from "./text-input";
|
||||
import type { EditingTextInputHandle } from "./types";
|
||||
|
||||
let container: HTMLDivElement | null = null;
|
||||
let root: Root | null = null;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (root && container) {
|
||||
act(() => {
|
||||
root?.unmount();
|
||||
});
|
||||
container.remove();
|
||||
}
|
||||
root = null;
|
||||
container = null;
|
||||
});
|
||||
|
||||
function noop() {}
|
||||
|
||||
describe("EditingTextInputNative", () => {
|
||||
it("clears text via clear() when replaceText receives an empty string", () => {
|
||||
const handleRef = createRef<EditingTextInputHandle>();
|
||||
|
||||
act(() => {
|
||||
root?.render(<EditingTextInput ref={handleRef} initialValue="initial" onChangeText={noop} />);
|
||||
});
|
||||
|
||||
expect(handleRef.current?.getText()).toBe("initial");
|
||||
|
||||
act(() => {
|
||||
handleRef.current?.replaceText("");
|
||||
});
|
||||
|
||||
expect(handleRef.current?.getText()).toBe("");
|
||||
});
|
||||
|
||||
it("updates textRef and text when replaceText receives non-empty text", () => {
|
||||
const handleRef = createRef<EditingTextInputHandle>();
|
||||
|
||||
act(() => {
|
||||
root?.render(<EditingTextInput ref={handleRef} initialValue="hello" />);
|
||||
});
|
||||
|
||||
expect(handleRef.current?.getText()).toBe("hello");
|
||||
|
||||
act(() => {
|
||||
handleRef.current?.replaceText("world", { start: 0, end: 5 });
|
||||
});
|
||||
|
||||
expect(handleRef.current?.getText()).toBe("world");
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,9 @@ type NativeInput = (TextInput | PasteTextInputInstance) & {
|
||||
blur(): void;
|
||||
focus(): void;
|
||||
isFocused(): boolean;
|
||||
setNativeProps(props: { text: string; selection?: { start: number; end: number } }): void;
|
||||
clear?(): void;
|
||||
replaceText?(text: string, selection?: { start: number; end: number }): void;
|
||||
setNativeProps?(props: { text?: string; selection?: { start: number; end: number } }): void;
|
||||
setSelection?(start: number, end: number): void;
|
||||
getNativeRef?(): unknown;
|
||||
};
|
||||
@@ -39,7 +41,18 @@ export const EditingTextInput = forwardRef<EditingTextInputHandle, EditingTextIn
|
||||
getText: () => textRef.current,
|
||||
replaceText: (nextText, selection) => {
|
||||
textRef.current = nextText;
|
||||
inputRef.current?.setNativeProps({ text: nextText, ...(selection ? { selection } : {}) });
|
||||
if (inputRef.current?.replaceText) {
|
||||
inputRef.current.replaceText(nextText, selection);
|
||||
return;
|
||||
}
|
||||
if (nextText === "") {
|
||||
inputRef.current?.clear?.();
|
||||
} else {
|
||||
inputRef.current?.setNativeProps?.({
|
||||
text: nextText,
|
||||
...(selection ? { selection } : {}),
|
||||
});
|
||||
}
|
||||
if (selection) inputRef.current?.setSelection?.(selection.start, selection.end);
|
||||
},
|
||||
getNativeRef: () => inputRef.current?.getNativeRef?.() ?? inputRef.current,
|
||||
|
||||
Reference in New Issue
Block a user