Files
paseo/packages/app/src/components/ui/text-input/text-input.web.tsx
T
Mohamed Boudra f3ddf99fe3 fix(app): restore fullscreen composer keyboard motion and clearing (#6902)
* fix(app): keep fullscreen composer in the keyboard dock

* fix(app): preserve composer caret and scope keyboard actions
2026-10-15 06:30:13 +07:00

104 lines
3.7 KiB
TypeScript

import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from "react";
import { TextInput } from "react-native";
import type { EditingTextInputHandle, EditingTextInputProps } from "./types";
interface WebTextInputElement extends TextInput {
value?: string;
setSelectionRange?: (start: number, end: number) => void;
addEventListener(type: "compositionstart" | "compositionend", listener: EventListener): void;
removeEventListener(type: "compositionstart" | "compositionend", listener: EventListener): void;
}
export const EditingTextInput = forwardRef<EditingTextInputHandle, EditingTextInputProps>(
function EditingTextInputWeb(allProps, ref) {
const {
initialValue = "",
onChangeText,
onPasteImages: _,
onPasteError: __,
variant: ___,
value: ____,
defaultValue: _____,
...props
} = allProps as EditingTextInputProps & { value?: unknown; defaultValue?: unknown };
const inputRef = useRef<TextInput | null>(null);
const initialTextRef = useRef(initialValue);
const textRef = useRef(initialTextRef.current);
const isComposingRef = useRef(false);
const onChangeTextRef = useRef(onChangeText);
onChangeTextRef.current = onChangeText;
useEffect(() => {
const input = inputRef.current as WebTextInputElement | null;
if (!input) return;
const startComposition = () => {
isComposingRef.current = true;
};
const endComposition = () => {
isComposingRef.current = false;
const nextText = input.value ?? "";
if (nextText === textRef.current) return;
textRef.current = nextText;
onChangeTextRef.current?.(nextText);
};
input.addEventListener("compositionstart", startComposition);
input.addEventListener("compositionend", endComposition);
return () => {
input.removeEventListener("compositionstart", startComposition);
input.removeEventListener("compositionend", endComposition);
};
}, []);
const handleChangeText = useCallback((nextText: string) => {
if (isComposingRef.current || nextText === textRef.current) return;
textRef.current = nextText;
onChangeTextRef.current?.(nextText);
}, []);
useImperativeHandle(
ref,
() => ({
focus: () => inputRef.current?.focus(),
blur: () => inputRef.current?.blur(),
isFocused: () => document.activeElement === inputRef.current,
getText: () => {
const input = inputRef.current as WebTextInputElement | null;
const nextText = input?.value ?? textRef.current;
textRef.current = nextText;
return nextText;
},
replaceText: (nextText, selection) => {
textRef.current = nextText;
const input = inputRef.current as WebTextInputElement | null;
if (input && "value" in input) input.value = nextText;
if (selection && typeof input?.setSelectionRange === "function") {
input.setSelectionRange(selection.start, selection.end);
}
},
setSelection: (selection) => {
const input = inputRef.current as WebTextInputElement | null;
input?.setSelectionRange?.(selection.start, selection.end);
},
reset: () => {
textRef.current = "";
const input = inputRef.current as WebTextInputElement | null;
if (input && "value" in input) input.value = "";
},
getNativeRef: () => inputRef.current,
}),
[],
);
return (
<TextInput
{...props}
ref={inputRef}
defaultValue={initialTextRef.current}
onChangeText={handleChangeText}
/>
);
},
);