fix: @提及弹窗跟随光标位置

main
sp mac bookpro 2605 2026-06-08 01:40:56 +08:00
parent 0327034874
commit 8badd8f6db
1 changed files with 49 additions and 4 deletions

View File

@ -52,6 +52,50 @@ export default function ChatInput(props: {
const [mentionPos, setMentionPos] = useState<{ top: number; left: number } | null>(null);
const inputRef = useRef<TextAreaRef>(null);
const getCaretPos = (textarea: HTMLTextAreaElement, caretIndex: number) => {
const rect = textarea.getBoundingClientRect();
const cs = window.getComputedStyle(textarea);
const div = document.createElement('div');
div.style.position = 'absolute';
div.style.visibility = 'hidden';
div.style.top = '0';
div.style.left = '-9999px';
div.style.whiteSpace = 'pre-wrap';
div.style.wordWrap = 'break-word';
div.style.overflow = 'hidden';
div.style.boxSizing = cs.boxSizing;
div.style.width = rect.width + 'px';
div.style.fontFamily = cs.fontFamily;
div.style.fontSize = cs.fontSize;
div.style.fontWeight = cs.fontWeight;
div.style.fontStyle = cs.fontStyle;
div.style.letterSpacing = cs.letterSpacing;
div.style.textTransform = cs.textTransform;
div.style.lineHeight = cs.lineHeight;
div.style.padding = cs.padding;
div.style.border = cs.border;
div.style.tabSize = (cs as any).tabSize || '8';
const before = textarea.value.slice(0, caretIndex).replace(/ /g, '\u00a0');
div.textContent = before;
const span = document.createElement('span');
span.textContent = '\u200b';
div.appendChild(span);
document.body.appendChild(div);
const divRect = div.getBoundingClientRect();
const spanRect = span.getBoundingClientRect();
document.body.removeChild(div);
const lineHeight = Number.parseFloat(cs.lineHeight) || Number.parseFloat(cs.fontSize) * 1.2;
return {
top: rect.top + (spanRect.top - divRect.top) - textarea.scrollTop,
left: rect.left + (spanRect.left - divRect.left) - textarea.scrollLeft,
lineHeight
};
};
// 检测 @ 触发提及选择
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value;
@ -82,10 +126,11 @@ export default function ChatInput(props: {
console.log('[@mention] cannot get textarea DOM from inputRef:', inputRef.current);
return;
}
const rect = textarea.getBoundingClientRect();
console.log('[@mention] textarea rect:', rect);
setMentionPos({ top: rect.bottom, left: rect.left + 10 });
console.log('[@mention] show popover:', { top: rect.bottom, left: rect.left + 10, query });
const caret = getCaretPos(textarea, cursorPos);
const top = Math.min(window.innerHeight - 8, caret.top + caret.lineHeight + 8);
const left = Math.min(window.innerWidth - 160, Math.max(8, caret.left));
setMentionPos({ top, left });
console.log('[@mention] show popover:', { top, left, query });
setShowMentionPopover(true);
});
return;