|
1 | 1 | 'use client' |
2 | 2 |
|
3 | | -import { useCallback, useEffect, useLayoutEffect, useMemo } from 'react' |
| 3 | +import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react' |
4 | 4 | import { cn } from '@sim/emcn' |
5 | 5 | import { ContextMentionIcon } from '@/app/workspace/[workspaceId]/home/components/context-mention-icon' |
6 | 6 | import { |
@@ -71,14 +71,25 @@ export function PromptEditor({ |
71 | 71 | onArrowUpOnEmpty, |
72 | 72 | }: PromptEditorProps) { |
73 | 73 | const { textareaRef, value } = editor |
| 74 | + const scrollerRef = useRef<HTMLDivElement>(null) |
74 | 75 |
|
| 76 | + /** |
| 77 | + * Autosize: grow the textarea to its full content height; the scroller caps |
| 78 | + * the visible height and scrolls textarea + overlay together natively. The |
| 79 | + * scroller's box is locked while the textarea collapses to `auto` for |
| 80 | + * measurement — the scrollHeight read forces a layout at the collapsed |
| 81 | + * height, and without the lock that transient layout grows the chat scroll |
| 82 | + * container, letting the browser clamp a bottom-pinned transcript upward by |
| 83 | + * the input's grown height on every multi-line edit. |
| 84 | + */ |
75 | 85 | useLayoutEffect(() => { |
76 | 86 | const textarea = textareaRef.current |
77 | 87 | if (!textarea) return |
78 | | - // Grow the textarea to its full content height; the scroller caps the |
79 | | - // visible height and scrolls textarea + overlay together natively. |
| 88 | + const scroller = scrollerRef.current |
| 89 | + if (scroller) scroller.style.height = `${scroller.offsetHeight}px` |
80 | 90 | textarea.style.height = 'auto' |
81 | 91 | textarea.style.height = `${textarea.scrollHeight}px` |
| 92 | + if (scroller) scroller.style.height = '' |
82 | 93 | }, [value, textareaRef]) |
83 | 94 |
|
84 | 95 | useEffect(() => { |
@@ -171,7 +182,11 @@ export function PromptEditor({ |
171 | 182 | }, [value, editor.contexts]) |
172 | 183 |
|
173 | 184 | return ( |
174 | | - <div className={cn(SCROLLER_CLASSES, 'cursor-text', className)} onClick={handleSurfaceClick}> |
| 185 | + <div |
| 186 | + ref={scrollerRef} |
| 187 | + className={cn(SCROLLER_CLASSES, 'cursor-text', className)} |
| 188 | + onClick={handleSurfaceClick} |
| 189 | + > |
175 | 190 | {/* Sizer for textarea + overlay: the textarea grows to full content |
176 | 191 | height and the overlay fills it via `inset-0`, so both are flow |
177 | 192 | children of the same scroller and co-scroll natively. */} |
|
0 commit comments