Fix prompt placeholder overlap during IME input
This commit is contained in:
@@ -49,15 +49,16 @@ export function PlaygroundPromptMentionInput(props: {
|
|||||||
const editableRef = useRef<HTMLDivElement>(null);
|
const editableRef = useRef<HTMLDivElement>(null);
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
const blurTimerRef = useRef<number | undefined>(undefined);
|
const blurTimerRef = useRef<number | undefined>(undefined);
|
||||||
|
const isComposingRef = useRef(false);
|
||||||
const [text, setText] = useState(props.value);
|
const [text, setText] = useState(props.value);
|
||||||
const [focused, setFocused] = useState(false);
|
const [focused, setFocused] = useState(false);
|
||||||
const [isComposing, setIsComposing] = useState(false);
|
const [hasEditableContent, setHasEditableContent] = useState(() => promptTextHasContent(props.value));
|
||||||
const [mentionOpen, setMentionOpen] = useState(false);
|
const [mentionOpen, setMentionOpen] = useState(false);
|
||||||
const [mentionAtIndex, setMentionAtIndex] = useState(-1);
|
const [mentionAtIndex, setMentionAtIndex] = useState(-1);
|
||||||
const [mentionSearch, setMentionSearch] = useState('');
|
const [mentionSearch, setMentionSearch] = useState('');
|
||||||
const [highlightIndex, setHighlightIndex] = useState(0);
|
const [highlightIndex, setHighlightIndex] = useState(0);
|
||||||
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0, placement: 'bottom' as 'bottom' | 'top' });
|
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0, placement: 'bottom' as 'bottom' | 'top' });
|
||||||
const showPlaceholder = text.trim().length === 0;
|
const showPlaceholder = !hasEditableContent;
|
||||||
|
|
||||||
const uploadSignature = useMemo(
|
const uploadSignature = useMemo(
|
||||||
() => props.uploads.map((item) => `${item.id}:${item.kind}:${item.name}:${item.url}`).join('|'),
|
() => props.uploads.map((item) => `${item.id}:${item.kind}:${item.name}:${item.url}`).join('|'),
|
||||||
@@ -78,8 +79,12 @@ export function PlaygroundPromptMentionInput(props: {
|
|||||||
}, [mentionItems, mentionSearch]);
|
}, [mentionItems, mentionSearch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.value === text) return;
|
if (props.value === text) {
|
||||||
|
setHasEditableContent(promptTextHasContent(props.value));
|
||||||
|
return;
|
||||||
|
}
|
||||||
setText(props.value);
|
setText(props.value);
|
||||||
|
setHasEditableContent(promptTextHasContent(props.value));
|
||||||
if (!focused) {
|
if (!focused) {
|
||||||
requestAnimationFrame(() => renderToEditable(props.value));
|
requestAnimationFrame(() => renderToEditable(props.value));
|
||||||
}
|
}
|
||||||
@@ -89,6 +94,7 @@ export function PlaygroundPromptMentionInput(props: {
|
|||||||
const cleaned = removeInvalidPlaygroundResourceTokens(text, props.uploads);
|
const cleaned = removeInvalidPlaygroundResourceTokens(text, props.uploads);
|
||||||
if (cleaned !== text) {
|
if (cleaned !== text) {
|
||||||
setText(cleaned);
|
setText(cleaned);
|
||||||
|
setHasEditableContent(promptTextHasContent(cleaned));
|
||||||
props.onChange(cleaned);
|
props.onChange(cleaned);
|
||||||
requestAnimationFrame(() => renderToEditable(cleaned));
|
requestAnimationFrame(() => renderToEditable(cleaned));
|
||||||
return;
|
return;
|
||||||
@@ -149,6 +155,7 @@ export function PlaygroundPromptMentionInput(props: {
|
|||||||
const editable = editableRef.current;
|
const editable = editableRef.current;
|
||||||
if (!editable) return;
|
if (!editable) return;
|
||||||
editable.innerHTML = textToHtml(nextText, props.uploads);
|
editable.innerHTML = textToHtml(nextText, props.uploads);
|
||||||
|
setHasEditableContent(promptTextHasContent(nextText));
|
||||||
if (typeof caret === 'number') {
|
if (typeof caret === 'number') {
|
||||||
editable.focus();
|
editable.focus();
|
||||||
setCaretOffset(editable, caret);
|
setCaretOffset(editable, caret);
|
||||||
@@ -157,8 +164,10 @@ export function PlaygroundPromptMentionInput(props: {
|
|||||||
|
|
||||||
function commitFromEditable(shouldInspectMention: boolean, event?: InputEvent) {
|
function commitFromEditable(shouldInspectMention: boolean, event?: InputEvent) {
|
||||||
const editable = editableRef.current;
|
const editable = editableRef.current;
|
||||||
if (!editable || props.disabled || isComposing) return;
|
if (!editable || props.disabled) return;
|
||||||
const nextText = serializeEditableToPlainText(editable);
|
const nextText = serializeEditableToPlainText(editable);
|
||||||
|
setHasEditableContent(promptTextHasContent(nextText));
|
||||||
|
if (isComposingRef.current || event?.isComposing) return;
|
||||||
setText(nextText);
|
setText(nextText);
|
||||||
props.onChange(nextText);
|
props.onChange(nextText);
|
||||||
if (!shouldInspectMention) return;
|
if (!shouldInspectMention) return;
|
||||||
@@ -206,6 +215,7 @@ export function PlaygroundPromptMentionInput(props: {
|
|||||||
const nextText = `${before}${token} ${after}`;
|
const nextText = `${before}${token} ${after}`;
|
||||||
const nextCaret = before.length + token.length + 1;
|
const nextCaret = before.length + token.length + 1;
|
||||||
setText(nextText);
|
setText(nextText);
|
||||||
|
setHasEditableContent(promptTextHasContent(nextText));
|
||||||
props.onChange(nextText);
|
props.onChange(nextText);
|
||||||
closeMention();
|
closeMention();
|
||||||
requestAnimationFrame(() => renderToEditable(nextText, nextCaret));
|
requestAnimationFrame(() => renderToEditable(nextText, nextCaret));
|
||||||
@@ -257,10 +267,13 @@ export function PlaygroundPromptMentionInput(props: {
|
|||||||
}, 120);
|
}, 120);
|
||||||
}}
|
}}
|
||||||
onCompositionEnd={() => {
|
onCompositionEnd={() => {
|
||||||
setIsComposing(false);
|
isComposingRef.current = false;
|
||||||
|
setHasEditableContent(editablePromptTextHasContent(editableRef.current));
|
||||||
requestAnimationFrame(() => commitFromEditable(true));
|
requestAnimationFrame(() => commitFromEditable(true));
|
||||||
}}
|
}}
|
||||||
onCompositionStart={() => setIsComposing(true)}
|
onCompositionStart={() => {
|
||||||
|
isComposingRef.current = true;
|
||||||
|
}}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
setFocused(true);
|
setFocused(true);
|
||||||
if (blurTimerRef.current) window.clearTimeout(blurTimerRef.current);
|
if (blurTimerRef.current) window.clearTimeout(blurTimerRef.current);
|
||||||
@@ -313,6 +326,14 @@ export function PlaygroundPromptMentionInput(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function promptTextHasContent(raw: string) {
|
||||||
|
return raw.trim().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function editablePromptTextHasContent(editable: HTMLElement | null) {
|
||||||
|
return editable ? promptTextHasContent(serializeEditableToPlainText(editable)) : false;
|
||||||
|
}
|
||||||
|
|
||||||
function MentionThumb(props: { item: PlaygroundMentionUpload }) {
|
function MentionThumb(props: { item: PlaygroundMentionUpload }) {
|
||||||
if (props.item.kind === 'image') {
|
if (props.item.kind === 'image') {
|
||||||
return <img className="promptMentionThumb" src={props.item.url} alt="" draggable={false} />;
|
return <img className="promptMentionThumb" src={props.item.url} alt="" draggable={false} />;
|
||||||
|
|||||||
Reference in New Issue
Block a user