diff --git a/apps/client/src/common/components/input/auto-textarea/AutoTextarea.tsx b/apps/client/src/common/components/input/auto-textarea/AutoTextarea.tsx index e0f91039c..1c52a8126 100644 --- a/apps/client/src/common/components/input/auto-textarea/AutoTextarea.tsx +++ b/apps/client/src/common/components/input/auto-textarea/AutoTextarea.tsx @@ -1,6 +1,6 @@ // @ts-expect-error no types from library import autosize from 'autosize/dist/autosize'; -import { RefObject, useEffect } from 'react'; +import { FocusEvent, RefObject, useCallback, useEffect } from 'react'; import { timeSync } from '../../../devtools/cuesheet-metrics/usePerfMark'; // PERF-METRICS import Textarea, { type TextareaProps } from '../textarea/Textarea'; @@ -10,18 +10,41 @@ interface AutoTextAreaProps extends TextareaProps { } /** - * A textarea that automatically resizes based on its content + * A textarea that automatically resizes based on its content. + * + * `autosize()` forces a synchronous reflow (reads scrollHeight) and installs a MutationObserver. + * Doing that on mount makes it expensive when many of these are mounted at once (e.g. virtualised + * table rows during scroll), so we only attach autosize while the field is focused for editing and + * keep it in sync with the value during that time. */ -export function AutoTextarea({ value, inputref, ...textAreaProps }: AutoTextAreaProps) { - // when the value changes, we use the ref to reapply autosize +export function AutoTextarea({ value, inputref, onFocus, onBlur, ...textAreaProps }: AutoTextAreaProps) { + const handleFocus = useCallback( + (event: FocusEvent) => { + timeSync('cell.autosize', () => autosize(inputref.current)); // PERF-METRICS + onFocus?.(event); + }, + [inputref, onFocus], + ); + + const handleBlur = useCallback( + (event: FocusEvent) => { + if (inputref.current) { + autosize.destroy(inputref.current); + } + onBlur?.(event); + }, + [inputref, onBlur], + ); + + // while focused, keep the height in sync as the value changes useEffect(() => { const node = inputref.current; - timeSync('cell.autosize', () => autosize(inputref.current)); // PERF-METRICS - - return () => { - autosize.destroy(node); - }; + if (node && document.activeElement === node) { + autosize(node); + } }, [inputref, value]); - return