mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
fix(webview-ui): robustly center edit area when editing long historical messages after topic switch by recentering within Virtuoso scroller and deferring scroll
This commit is contained in:
parent
15932f901d
commit
9059766ac6
1 changed files with 62 additions and 19 deletions
|
|
@ -166,13 +166,29 @@ export const ChatRowContent = ({
|
|||
|
||||
// Handle edit button click
|
||||
const handleEditClick = useCallback(() => {
|
||||
// Pre-scroll the bubble container into view so the textarea can mount fully
|
||||
// Pre-scroll the bubble container into view so the textarea can mount fully.
|
||||
// Use center to give Virtuoso more room to render around the target.
|
||||
try {
|
||||
editAreaRef.current?.scrollIntoView({
|
||||
behavior: "auto",
|
||||
block: "nearest",
|
||||
inline: "nearest",
|
||||
})
|
||||
const el = editAreaRef.current
|
||||
if (el) {
|
||||
el.scrollIntoView({
|
||||
behavior: "auto",
|
||||
block: "center",
|
||||
inline: "nearest",
|
||||
})
|
||||
// Safety re-center on the next frame in case virtualization/layout shifts after state updates.
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
el.scrollIntoView({
|
||||
behavior: "auto",
|
||||
block: "center",
|
||||
inline: "nearest",
|
||||
})
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
|
@ -195,7 +211,7 @@ export const ChatRowContent = ({
|
|||
}, [isEditing, message.text, message.images, mode])
|
||||
|
||||
// Ensure the edit textarea is focused and scrolled into view when entering edit mode.
|
||||
// Uses a short delay and a few animation frames to allow virtualization reflow before scrolling.
|
||||
// Uses a short delay and repeated frames to allow virtualization reflow before scrolling.
|
||||
useEffect(() => {
|
||||
if (!isEditing) return
|
||||
|
||||
|
|
@ -205,8 +221,7 @@ export const ChatRowContent = ({
|
|||
const getScrollContainer = (el: HTMLElement | null): HTMLElement | null => {
|
||||
if (!el) return null
|
||||
// ChatView sets the Virtuoso scroller with class "scrollable"
|
||||
const scroller = el.closest(".scrollable") as HTMLElement | null
|
||||
return scroller
|
||||
return el.closest(".scrollable") as HTMLElement | null
|
||||
}
|
||||
|
||||
const isFullyVisible = (el: HTMLElement): boolean => {
|
||||
|
|
@ -220,6 +235,22 @@ export const ChatRowContent = ({
|
|||
return topVisible && bottomVisible
|
||||
}
|
||||
|
||||
const centerInScroller = (el: HTMLElement) => {
|
||||
const scroller = getScrollContainer(el)
|
||||
if (!scroller) {
|
||||
// Fallback to element's own scroll logic
|
||||
el.scrollIntoView({ behavior: "auto", block: "center", inline: "nearest" })
|
||||
return
|
||||
}
|
||||
const rect = el.getBoundingClientRect()
|
||||
const containerRect = scroller.getBoundingClientRect()
|
||||
const elCenter = rect.top + rect.height / 2
|
||||
const containerCenter = containerRect.top + containerRect.height / 2
|
||||
const delta = elCenter - containerCenter
|
||||
// Adjust scrollTop by the delta between element center and container center
|
||||
scroller.scrollTop += delta
|
||||
}
|
||||
|
||||
const focusTextarea = () => {
|
||||
if (editTextAreaRef.current) {
|
||||
try {
|
||||
|
|
@ -231,23 +262,35 @@ export const ChatRowContent = ({
|
|||
}
|
||||
|
||||
let attempts = 0
|
||||
const maxAttempts = 10
|
||||
const maxAttempts = 20 // a bit more robust across topic switches
|
||||
|
||||
const step = () => {
|
||||
if (cancelled) return
|
||||
attempts += 1
|
||||
|
||||
const targetEl = (editTextAreaRef.current as HTMLTextAreaElement | null) ?? editAreaRef.current
|
||||
const targetEl =
|
||||
(editTextAreaRef.current as HTMLTextAreaElement | null) ?? (editAreaRef.current as HTMLElement | null)
|
||||
|
||||
// Focus first so caret is visible; preventScroll avoids double-jump
|
||||
focusTextarea()
|
||||
|
||||
if (targetEl && !isFullyVisible(targetEl)) {
|
||||
targetEl.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
inline: "nearest",
|
||||
})
|
||||
if (targetEl) {
|
||||
if (!isFullyVisible(targetEl)) {
|
||||
// Prefer centering within the Virtuoso scroller; fallback to native scrollIntoView
|
||||
try {
|
||||
centerInScroller(targetEl)
|
||||
} catch {
|
||||
try {
|
||||
targetEl.scrollIntoView({
|
||||
behavior: "auto",
|
||||
block: "center",
|
||||
inline: "nearest",
|
||||
})
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Continue for a few frames to account for virtualization/layout reflows
|
||||
|
|
@ -256,12 +299,12 @@ export const ChatRowContent = ({
|
|||
}
|
||||
}
|
||||
|
||||
// Defer until after the textarea has mounted
|
||||
// Defer until after the textarea has mounted and Virtuoso has had a moment to lay out
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
if (!cancelled) {
|
||||
step()
|
||||
}
|
||||
}, 50)
|
||||
}, 80)
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue