fix: @提及弹窗跟随光标位置
parent
0327034874
commit
8badd8f6db
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue