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;
|
blur(): void;
|
||||||
focus(): void;
|
focus(): void;
|
||||||
isFocused(): boolean;
|
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;
|
setSelection?(start: number, end: number): void;
|
||||||
getNativeRef?(): unknown;
|
getNativeRef?(): unknown;
|
||||||
};
|
};
|
||||||
@@ -39,7 +41,18 @@ export const EditingTextInput = forwardRef<EditingTextInputHandle, EditingTextIn
|
|||||||
getText: () => textRef.current,
|
getText: () => textRef.current,
|
||||||
replaceText: (nextText, selection) => {
|
replaceText: (nextText, selection) => {
|
||||||
textRef.current = nextText;
|
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);
|
if (selection) inputRef.current?.setSelection?.(selection.start, selection.end);
|
||||||
},
|
},
|
||||||
getNativeRef: () => inputRef.current?.getNativeRef?.() ?? inputRef.current,
|
getNativeRef: () => inputRef.current?.getNativeRef?.() ?? inputRef.current,
|
||||||
|
|||||||
@@ -27,3 +27,369 @@ index 73466dd..8440ac9 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto textSize = textLayoutManager_
|
auto textSize = textLayoutManager_
|
||||||
|
diff --git a/node_modules/@mattermost/react-native-paste-input/lib/commonjs/PasteInput.js b/node_modules/@mattermost/react-native-paste-input/lib/commonjs/PasteInput.js
|
||||||
|
index 51b9dfa..501badf 100644
|
||||||
|
--- a/node_modules/@mattermost/react-native-paste-input/lib/commonjs/PasteInput.js
|
||||||
|
+++ b/node_modules/@mattermost/react-native-paste-input/lib/commonjs/PasteInput.js
|
||||||
|
@@ -54,17 +54,25 @@ function PasteInputIOSComponent(props, forwardedRef) {
|
||||||
|
|
||||||
|
// Expose the TextInput ref to parent components (same as Android)
|
||||||
|
// TextInput already has all methods from PasteTextInputInstance (clear, isFocused, setSelection)
|
||||||
|
- (0, _react.useImperativeHandle)(forwardedRef, () => {
|
||||||
|
- if (textInputRef.current) {
|
||||||
|
- return textInputRef.current;
|
||||||
|
- }
|
||||||
|
- // Return a no-op implementation if ref is not yet available
|
||||||
|
- return {
|
||||||
|
- clear: () => {},
|
||||||
|
- isFocused: () => false,
|
||||||
|
- setSelection: () => {}
|
||||||
|
- };
|
||||||
|
- }, []);
|
||||||
|
+ (0, _react.useImperativeHandle)(forwardedRef, () => ({
|
||||||
|
+ clear: () => textInputRef.current?.clear?.(),
|
||||||
|
+ focus: () => textInputRef.current?.focus?.(),
|
||||||
|
+ blur: () => textInputRef.current?.blur?.(),
|
||||||
|
+ isFocused: () => textInputRef.current?.isFocused?.() ?? false,
|
||||||
|
+ setSelection: (start, end) => textInputRef.current?.setSelection?.(start, end),
|
||||||
|
+ replaceText: (text, selection) => {
|
||||||
|
+ if (text === '') {
|
||||||
|
+ textInputRef.current?.clear?.();
|
||||||
|
+ } else {
|
||||||
|
+ textInputRef.current?.setNativeProps?.({ text, ...(selection ? { selection } : {}) });
|
||||||
|
+ }
|
||||||
|
+ if (selection) {
|
||||||
|
+ textInputRef.current?.setSelection?.(selection.start, selection.end);
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
+ getNativeRef: () => textInputRef.current,
|
||||||
|
+ setNativeProps: nativeProps => textInputRef.current?.setNativeProps?.(nativeProps)
|
||||||
|
+ }), []);
|
||||||
|
|
||||||
|
// Register/unregister with native module on mount/unmount
|
||||||
|
(0, _react.useEffect)(() => {
|
||||||
|
@@ -125,17 +133,15 @@ function PasteInputAndroidComponent(props, forwardedRef) {
|
||||||
|
const pasteTextInputRef = (0, _react.useRef)(null);
|
||||||
|
|
||||||
|
// Expose the PasteTextInput ref to parent components (same as iOS)
|
||||||
|
- (0, _react.useImperativeHandle)(forwardedRef, () => {
|
||||||
|
- if (pasteTextInputRef.current) {
|
||||||
|
- return pasteTextInputRef.current;
|
||||||
|
- }
|
||||||
|
- // Return a no-op implementation if ref is not yet available
|
||||||
|
- return {
|
||||||
|
- clear: () => {},
|
||||||
|
- isFocused: () => false,
|
||||||
|
- setSelection: () => {}
|
||||||
|
- };
|
||||||
|
- }, []);
|
||||||
|
+ (0, _react.useImperativeHandle)(forwardedRef, () => ({
|
||||||
|
+ clear: () => pasteTextInputRef.current?.clear?.(),
|
||||||
|
+ focus: () => pasteTextInputRef.current?.focus?.(),
|
||||||
|
+ blur: () => pasteTextInputRef.current?.blur?.(),
|
||||||
|
+ isFocused: () => pasteTextInputRef.current?.isFocused?.() ?? false,
|
||||||
|
+ setSelection: (start, end) => pasteTextInputRef.current?.setSelection?.(start, end),
|
||||||
|
+ replaceText: (text, selection) => pasteTextInputRef.current?.replaceText?.(text, selection),
|
||||||
|
+ getNativeRef: () => pasteTextInputRef.current?.getNativeRef?.() ?? pasteTextInputRef.current
|
||||||
|
+ }), []);
|
||||||
|
|
||||||
|
// For Android, PasteTextInput already handles everything internally
|
||||||
|
// Just need to adapt the onPaste callback format
|
||||||
|
diff --git a/node_modules/@mattermost/react-native-paste-input/lib/commonjs/PasteTextInput.js b/node_modules/@mattermost/react-native-paste-input/lib/commonjs/PasteTextInput.js
|
||||||
|
index 5119e65..287216a 100644
|
||||||
|
--- a/node_modules/@mattermost/react-native-paste-input/lib/commonjs/PasteTextInput.js
|
||||||
|
+++ b/node_modules/@mattermost/react-native-paste-input/lib/commonjs/PasteTextInput.js
|
||||||
|
@@ -52,6 +52,7 @@ function InternalTextInput(props) {
|
||||||
|
end: propsSelection.end ?? propsSelection.start
|
||||||
|
}, [propsSelection]);
|
||||||
|
const [mostRecentEventCount, setMostRecentEventCount] = React.useState(0);
|
||||||
|
+ const mostRecentEventCountRef = React.useRef(0);
|
||||||
|
const [lastNativeText, setLastNativeText] = React.useState(props.value);
|
||||||
|
const [lastNativeSelectionState, setLastNativeSelection] = React.useState({
|
||||||
|
selection: {
|
||||||
|
@@ -103,7 +104,12 @@ function InternalTextInput(props) {
|
||||||
|
Object.assign(instance, {
|
||||||
|
clear() {
|
||||||
|
if (inputRef.current != null) {
|
||||||
|
- _PasteTextInputNativeComponent.Commands.setTextAndSelection(inputRef.current, mostRecentEventCount, '', 0, 0);
|
||||||
|
+ _PasteTextInputNativeComponent.Commands.setTextAndSelection(inputRef.current, mostRecentEventCountRef.current, '', 0, 0);
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
+ replaceText(text, selection) {
|
||||||
|
+ if (inputRef.current != null) {
|
||||||
|
+ _PasteTextInputNativeComponent.Commands.setTextAndSelection(inputRef.current, mostRecentEventCountRef.current, text, selection?.start ?? -1, selection?.end ?? -1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isFocused() {
|
||||||
|
@@ -114,12 +120,12 @@ function InternalTextInput(props) {
|
||||||
|
},
|
||||||
|
setSelection(start, end) {
|
||||||
|
if (inputRef.current != null) {
|
||||||
|
- _PasteTextInputNativeComponent.Commands.setTextAndSelection(inputRef.current, mostRecentEventCount, null, start, end);
|
||||||
|
+ _PasteTextInputNativeComponent.Commands.setTextAndSelection(inputRef.current, mostRecentEventCountRef.current, null, start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
- }, [mostRecentEventCount]);
|
||||||
|
+ }, []);
|
||||||
|
const ref = useMergeRefs(setLocalRef, props.forwardedRef);
|
||||||
|
const _onChange = event => {
|
||||||
|
const currentText = event.nativeEvent.text;
|
||||||
|
@@ -128,6 +134,7 @@ function InternalTextInput(props) {
|
||||||
|
if (inputRef.current == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
+ mostRecentEventCountRef.current = event.nativeEvent.eventCount;
|
||||||
|
setLastNativeText(currentText);
|
||||||
|
setMostRecentEventCount(event.nativeEvent.eventCount);
|
||||||
|
};
|
||||||
|
diff --git a/node_modules/@mattermost/react-native-paste-input/lib/module/PasteInput.js b/node_modules/@mattermost/react-native-paste-input/lib/module/PasteInput.js
|
||||||
|
index 75fe015..1d3cd64 100644
|
||||||
|
--- a/node_modules/@mattermost/react-native-paste-input/lib/module/PasteInput.js
|
||||||
|
+++ b/node_modules/@mattermost/react-native-paste-input/lib/module/PasteInput.js
|
||||||
|
@@ -49,17 +49,25 @@ function PasteInputIOSComponent(props, forwardedRef) {
|
||||||
|
|
||||||
|
// Expose the TextInput ref to parent components (same as Android)
|
||||||
|
// TextInput already has all methods from PasteTextInputInstance (clear, isFocused, setSelection)
|
||||||
|
- useImperativeHandle(forwardedRef, () => {
|
||||||
|
- if (textInputRef.current) {
|
||||||
|
- return textInputRef.current;
|
||||||
|
- }
|
||||||
|
- // Return a no-op implementation if ref is not yet available
|
||||||
|
- return {
|
||||||
|
- clear: () => {},
|
||||||
|
- isFocused: () => false,
|
||||||
|
- setSelection: () => {}
|
||||||
|
- };
|
||||||
|
- }, []);
|
||||||
|
+ useImperativeHandle(forwardedRef, () => ({
|
||||||
|
+ clear: () => textInputRef.current?.clear?.(),
|
||||||
|
+ focus: () => textInputRef.current?.focus?.(),
|
||||||
|
+ blur: () => textInputRef.current?.blur?.(),
|
||||||
|
+ isFocused: () => textInputRef.current?.isFocused?.() ?? false,
|
||||||
|
+ setSelection: (start, end) => textInputRef.current?.setSelection?.(start, end),
|
||||||
|
+ replaceText: (text, selection) => {
|
||||||
|
+ if (text === '') {
|
||||||
|
+ textInputRef.current?.clear?.();
|
||||||
|
+ } else {
|
||||||
|
+ textInputRef.current?.setNativeProps?.({ text, ...(selection ? { selection } : {}) });
|
||||||
|
+ }
|
||||||
|
+ if (selection) {
|
||||||
|
+ textInputRef.current?.setSelection?.(selection.start, selection.end);
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
+ getNativeRef: () => textInputRef.current,
|
||||||
|
+ setNativeProps: nativeProps => textInputRef.current?.setNativeProps?.(nativeProps)
|
||||||
|
+ }), []);
|
||||||
|
|
||||||
|
// Register/unregister with native module on mount/unmount
|
||||||
|
useEffect(() => {
|
||||||
|
@@ -120,17 +128,15 @@ function PasteInputAndroidComponent(props, forwardedRef) {
|
||||||
|
const pasteTextInputRef = useRef(null);
|
||||||
|
|
||||||
|
// Expose the PasteTextInput ref to parent components (same as iOS)
|
||||||
|
- useImperativeHandle(forwardedRef, () => {
|
||||||
|
- if (pasteTextInputRef.current) {
|
||||||
|
- return pasteTextInputRef.current;
|
||||||
|
- }
|
||||||
|
- // Return a no-op implementation if ref is not yet available
|
||||||
|
- return {
|
||||||
|
- clear: () => {},
|
||||||
|
- isFocused: () => false,
|
||||||
|
- setSelection: () => {}
|
||||||
|
- };
|
||||||
|
- }, []);
|
||||||
|
+ useImperativeHandle(forwardedRef, () => ({
|
||||||
|
+ clear: () => pasteTextInputRef.current?.clear?.(),
|
||||||
|
+ focus: () => pasteTextInputRef.current?.focus?.(),
|
||||||
|
+ blur: () => pasteTextInputRef.current?.blur?.(),
|
||||||
|
+ isFocused: () => pasteTextInputRef.current?.isFocused?.() ?? false,
|
||||||
|
+ setSelection: (start, end) => pasteTextInputRef.current?.setSelection?.(start, end),
|
||||||
|
+ replaceText: (text, selection) => pasteTextInputRef.current?.replaceText?.(text, selection),
|
||||||
|
+ getNativeRef: () => pasteTextInputRef.current?.getNativeRef?.() ?? pasteTextInputRef.current
|
||||||
|
+ }), []);
|
||||||
|
|
||||||
|
// For Android, PasteTextInput already handles everything internally
|
||||||
|
// Just need to adapt the onPaste callback format
|
||||||
|
diff --git a/node_modules/@mattermost/react-native-paste-input/lib/module/PasteTextInput.js b/node_modules/@mattermost/react-native-paste-input/lib/module/PasteTextInput.js
|
||||||
|
index 39bd607..f2b1d2f 100644
|
||||||
|
--- a/node_modules/@mattermost/react-native-paste-input/lib/module/PasteTextInput.js
|
||||||
|
+++ b/node_modules/@mattermost/react-native-paste-input/lib/module/PasteTextInput.js
|
||||||
|
@@ -46,6 +46,7 @@ function InternalTextInput(props) {
|
||||||
|
end: propsSelection.end ?? propsSelection.start
|
||||||
|
}, [propsSelection]);
|
||||||
|
const [mostRecentEventCount, setMostRecentEventCount] = React.useState(0);
|
||||||
|
+ const mostRecentEventCountRef = React.useRef(0);
|
||||||
|
const [lastNativeText, setLastNativeText] = React.useState(props.value);
|
||||||
|
const [lastNativeSelectionState, setLastNativeSelection] = React.useState({
|
||||||
|
selection: {
|
||||||
|
@@ -97,7 +98,12 @@ function InternalTextInput(props) {
|
||||||
|
Object.assign(instance, {
|
||||||
|
clear() {
|
||||||
|
if (inputRef.current != null) {
|
||||||
|
- Commands.setTextAndSelection(inputRef.current, mostRecentEventCount, '', 0, 0);
|
||||||
|
+ Commands.setTextAndSelection(inputRef.current, mostRecentEventCountRef.current, '', 0, 0);
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
+ replaceText(text, selection) {
|
||||||
|
+ if (inputRef.current != null) {
|
||||||
|
+ Commands.setTextAndSelection(inputRef.current, mostRecentEventCountRef.current, text, selection?.start ?? -1, selection?.end ?? -1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isFocused() {
|
||||||
|
@@ -108,12 +114,12 @@ function InternalTextInput(props) {
|
||||||
|
},
|
||||||
|
setSelection(start, end) {
|
||||||
|
if (inputRef.current != null) {
|
||||||
|
- Commands.setTextAndSelection(inputRef.current, mostRecentEventCount, null, start, end);
|
||||||
|
+ Commands.setTextAndSelection(inputRef.current, mostRecentEventCountRef.current, null, start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
- }, [mostRecentEventCount]);
|
||||||
|
+ }, []);
|
||||||
|
const ref = useMergeRefs(setLocalRef, props.forwardedRef);
|
||||||
|
const _onChange = event => {
|
||||||
|
const currentText = event.nativeEvent.text;
|
||||||
|
@@ -122,6 +128,7 @@ function InternalTextInput(props) {
|
||||||
|
if (inputRef.current == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
+ mostRecentEventCountRef.current = event.nativeEvent.eventCount;
|
||||||
|
setLastNativeText(currentText);
|
||||||
|
setMostRecentEventCount(event.nativeEvent.eventCount);
|
||||||
|
};
|
||||||
|
diff --git a/node_modules/@mattermost/react-native-paste-input/src/PasteInput.tsx b/node_modules/@mattermost/react-native-paste-input/src/PasteInput.tsx
|
||||||
|
index 12befab..0d23ca5 100644
|
||||||
|
--- a/node_modules/@mattermost/react-native-paste-input/src/PasteInput.tsx
|
||||||
|
+++ b/node_modules/@mattermost/react-native-paste-input/src/PasteInput.tsx
|
||||||
|
@@ -64,17 +64,25 @@ function PasteInputIOSComponent(
|
||||||
|
|
||||||
|
// Expose the TextInput ref to parent components (same as Android)
|
||||||
|
// TextInput already has all methods from PasteTextInputInstance (clear, isFocused, setSelection)
|
||||||
|
- useImperativeHandle(forwardedRef, () => {
|
||||||
|
- if (textInputRef.current) {
|
||||||
|
- return textInputRef.current as unknown as PasteTextInputInstance;
|
||||||
|
- }
|
||||||
|
- // Return a no-op implementation if ref is not yet available
|
||||||
|
- return {
|
||||||
|
- clear: () => {},
|
||||||
|
- isFocused: () => false,
|
||||||
|
- setSelection: () => {},
|
||||||
|
- } as unknown as PasteTextInputInstance;
|
||||||
|
- }, []);
|
||||||
|
+ useImperativeHandle(forwardedRef, () => ({
|
||||||
|
+ clear: () => textInputRef.current?.clear?.(),
|
||||||
|
+ focus: () => textInputRef.current?.focus?.(),
|
||||||
|
+ blur: () => textInputRef.current?.blur?.(),
|
||||||
|
+ isFocused: () => textInputRef.current?.isFocused?.() ?? false,
|
||||||
|
+ setSelection: (start: number, end: number) => textInputRef.current?.setSelection?.(start, end),
|
||||||
|
+ replaceText: (text: string, selection?: { start: number; end: number }) => {
|
||||||
|
+ if (text === '') {
|
||||||
|
+ textInputRef.current?.clear?.();
|
||||||
|
+ } else {
|
||||||
|
+ textInputRef.current?.setNativeProps?.({ text, ...(selection ? { selection } : {}) });
|
||||||
|
+ }
|
||||||
|
+ if (selection) {
|
||||||
|
+ textInputRef.current?.setSelection?.(selection.start, selection.end);
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
+ getNativeRef: () => textInputRef.current,
|
||||||
|
+ setNativeProps: (nativeProps: any) => textInputRef.current?.setNativeProps?.(nativeProps),
|
||||||
|
+ } as unknown as PasteTextInputInstance), []);
|
||||||
|
|
||||||
|
// Register/unregister with native module on mount/unmount
|
||||||
|
useEffect(() => {
|
||||||
|
@@ -143,17 +151,15 @@ function PasteInputAndroidComponent(
|
||||||
|
const pasteTextInputRef = useRef<PasteTextInputInstance>(null);
|
||||||
|
|
||||||
|
// Expose the PasteTextInput ref to parent components (same as iOS)
|
||||||
|
- useImperativeHandle(forwardedRef, () => {
|
||||||
|
- if (pasteTextInputRef.current) {
|
||||||
|
- return pasteTextInputRef.current;
|
||||||
|
- }
|
||||||
|
- // Return a no-op implementation if ref is not yet available
|
||||||
|
- return {
|
||||||
|
- clear: () => {},
|
||||||
|
- isFocused: () => false,
|
||||||
|
- setSelection: () => {},
|
||||||
|
- } as unknown as PasteTextInputInstance;
|
||||||
|
- }, []);
|
||||||
|
+ useImperativeHandle(forwardedRef, () => ({
|
||||||
|
+ clear: () => pasteTextInputRef.current?.clear?.(),
|
||||||
|
+ focus: () => pasteTextInputRef.current?.focus?.(),
|
||||||
|
+ blur: () => pasteTextInputRef.current?.blur?.(),
|
||||||
|
+ isFocused: () => pasteTextInputRef.current?.isFocused?.() ?? false,
|
||||||
|
+ setSelection: (start: number, end: number) => pasteTextInputRef.current?.setSelection?.(start, end),
|
||||||
|
+ replaceText: (text: string, selection?: { start: number; end: number }) => (pasteTextInputRef.current as any)?.replaceText?.(text, selection),
|
||||||
|
+ getNativeRef: () => pasteTextInputRef.current?.getNativeRef?.() ?? pasteTextInputRef.current,
|
||||||
|
+ } as unknown as PasteTextInputInstance), []);
|
||||||
|
|
||||||
|
// For Android, PasteTextInput already handles everything internally
|
||||||
|
// Just need to adapt the onPaste callback format
|
||||||
|
diff --git a/node_modules/@mattermost/react-native-paste-input/src/PasteTextInput.tsx b/node_modules/@mattermost/react-native-paste-input/src/PasteTextInput.tsx
|
||||||
|
index dcc4a2b..1156458 100644
|
||||||
|
--- a/node_modules/@mattermost/react-native-paste-input/src/PasteTextInput.tsx
|
||||||
|
+++ b/node_modules/@mattermost/react-native-paste-input/src/PasteTextInput.tsx
|
||||||
|
@@ -84,6 +84,7 @@ function InternalTextInput(props: PasteInputProps): React.ReactNode {
|
||||||
|
|
||||||
|
const [mostRecentEventCount, setMostRecentEventCount] =
|
||||||
|
React.useState<number>(0);
|
||||||
|
+ const mostRecentEventCountRef = React.useRef<number>(0);
|
||||||
|
const [lastNativeText, setLastNativeText] = React.useState<
|
||||||
|
string | undefined
|
||||||
|
>(props.value);
|
||||||
|
@@ -181,13 +182,24 @@ function InternalTextInput(props: PasteInputProps): React.ReactNode {
|
||||||
|
if (inputRef.current != null) {
|
||||||
|
Commands.setTextAndSelection(
|
||||||
|
inputRef.current,
|
||||||
|
- mostRecentEventCount,
|
||||||
|
+ mostRecentEventCountRef.current,
|
||||||
|
'',
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
+ replaceText(text: string, selection?: { start: number; end: number }): void {
|
||||||
|
+ if (inputRef.current != null) {
|
||||||
|
+ Commands.setTextAndSelection(
|
||||||
|
+ inputRef.current,
|
||||||
|
+ mostRecentEventCountRef.current,
|
||||||
|
+ text,
|
||||||
|
+ selection?.start ?? -1,
|
||||||
|
+ selection?.end ?? -1
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
isFocused(): boolean {
|
||||||
|
return (
|
||||||
|
TextInputState.currentlyFocusedInput() ===
|
||||||
|
@@ -203,7 +215,7 @@ function InternalTextInput(props: PasteInputProps): React.ReactNode {
|
||||||
|
if (inputRef.current != null) {
|
||||||
|
Commands.setTextAndSelection(
|
||||||
|
inputRef.current,
|
||||||
|
- mostRecentEventCount,
|
||||||
|
+ mostRecentEventCountRef.current,
|
||||||
|
null,
|
||||||
|
start,
|
||||||
|
end
|
||||||
|
@@ -213,7 +225,7 @@ function InternalTextInput(props: PasteInputProps): React.ReactNode {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
- [mostRecentEventCount]
|
||||||
|
+ []
|
||||||
|
);
|
||||||
|
|
||||||
|
const ref = useMergeRefs<PasteTextInputInstance>(
|
||||||
|
@@ -232,6 +244,7 @@ function InternalTextInput(props: PasteInputProps): React.ReactNode {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ mostRecentEventCountRef.current = event.nativeEvent.eventCount;
|
||||||
|
setLastNativeText(currentText);
|
||||||
|
setMostRecentEventCount(event.nativeEvent.eventCount);
|
||||||
|
};
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ const patchedPackages = [
|
|||||||
nodeModulesPath: "node_modules/react-native-gesture-handler",
|
nodeModulesPath: "node_modules/react-native-gesture-handler",
|
||||||
patchPrefix: "react-native-gesture-handler+",
|
patchPrefix: "react-native-gesture-handler+",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
nodeModulesPath: "node_modules/@mattermost/react-native-paste-input",
|
||||||
|
patchPrefix: "@mattermost+react-native-paste-input+",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
nodeModulesPath: "packages/server/node_modules/@opencode-ai/sdk",
|
nodeModulesPath: "packages/server/node_modules/@opencode-ai/sdk",
|
||||||
patchPrefix: "@opencode-ai+sdk+",
|
patchPrefix: "@opencode-ai+sdk+",
|
||||||
|
|||||||
Reference in New Issue
Block a user