fix(frontend): 多行输入时保持光标可见
adjustTextareaSize 重算高度会打乱 scrollTop,导致多行输入时光标被滚出视野。 新增 keepEditorSelectionVisible:高度重设后,聚焦态用编辑器 coordsAtPos 算出 光标位置并滚回可视区(上下留 lineHeight*0.3 padding),非聚焦态恢复原 scrollTop。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9be775d346
commit
e34e2c965c
@ -1528,6 +1528,9 @@ const adjustTextareaSize = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const target = root;
|
const target = root;
|
||||||
|
const previousScrollTop = target.scrollTop;
|
||||||
|
const selectionShouldStayVisible =
|
||||||
|
document.activeElement === target || target.contains(document.activeElement);
|
||||||
target.style.height = 'auto';
|
target.style.height = 'auto';
|
||||||
const computedStyle = window.getComputedStyle(target);
|
const computedStyle = window.getComputedStyle(target);
|
||||||
const lineHeight = parseFloat(computedStyle.lineHeight || '20') || 20;
|
const lineHeight = parseFloat(computedStyle.lineHeight || '20') || 20;
|
||||||
@ -1537,11 +1540,58 @@ const adjustTextareaSize = () => {
|
|||||||
const multiline = targetHeight > lineHeight * 1.4;
|
const multiline = targetHeight > lineHeight * 1.4;
|
||||||
applyLineMetrics(lines, multiline);
|
applyLineMetrics(lines, multiline);
|
||||||
target.style.height = `${targetHeight}px`;
|
target.style.height = `${targetHeight}px`;
|
||||||
|
keepEditorSelectionVisible(target, {
|
||||||
|
previousScrollTop,
|
||||||
|
selectionShouldStayVisible,
|
||||||
|
padding: Math.max(4, lineHeight * 0.3)
|
||||||
|
});
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
emitComposerHeight();
|
emitComposerHeight();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const keepEditorSelectionVisible = (
|
||||||
|
target: HTMLElement,
|
||||||
|
options: { previousScrollTop: number; selectionShouldStayVisible: boolean; padding: number }
|
||||||
|
) => {
|
||||||
|
const maxScrollTop = Math.max(0, target.scrollHeight - target.clientHeight);
|
||||||
|
if (maxScrollTop <= 0) {
|
||||||
|
target.scrollTop = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.selectionShouldStayVisible) {
|
||||||
|
target.scrollTop = Math.min(maxScrollTop, Math.max(0, options.previousScrollTop));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const instance = editor.value;
|
||||||
|
if (!instance) {
|
||||||
|
target.scrollTop = Math.min(maxScrollTop, Math.max(0, options.previousScrollTop));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const caretRect = instance.view.coordsAtPos(instance.state.selection.head);
|
||||||
|
const targetRect = target.getBoundingClientRect();
|
||||||
|
const padding = options.padding;
|
||||||
|
const currentMaxScrollTop = Math.max(0, target.scrollHeight - target.clientHeight);
|
||||||
|
let nextScrollTop = target.scrollTop;
|
||||||
|
|
||||||
|
if (caretRect.bottom > targetRect.bottom - padding) {
|
||||||
|
nextScrollTop += caretRect.bottom - targetRect.bottom + padding;
|
||||||
|
} else if (caretRect.top < targetRect.top + padding) {
|
||||||
|
nextScrollTop -= targetRect.top + padding - caretRect.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.scrollTop = Math.min(currentMaxScrollTop, Math.max(0, nextScrollTop));
|
||||||
|
} catch {
|
||||||
|
target.scrollTop = Math.min(maxScrollTop, Math.max(0, options.previousScrollTop));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onInput = (event: Event) => {
|
const onInput = (event: Event) => {
|
||||||
const target = event.target as HTMLTextAreaElement;
|
const target = event.target as HTMLTextAreaElement;
|
||||||
if (!props.isConnected || props.inputLocked) {
|
if (!props.isConnected || props.inputLocked) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user