From 3bbd42587ec7b728693965b8b673577ac3625575 Mon Sep 17 00:00:00 2001 From: HearingSmile <41669762+bigcoder84@users.noreply.github.com> Date: Sat, 13 Jun 2026 05:28:42 +0800 Subject: [PATCH] fix: stabilize question custom textarea resizing (#1614) * fix: stabilize question custom textarea resizing * fix: preserve custom question answer state --------- Co-authored-by: Bohdan Triapitsyn --- .../ui/src/components/chat/QuestionCard.tsx | 119 +++++++++++++----- .../__tests__/questionTextareaSizing.test.ts | 18 +++ .../components/chat/questionTextareaSizing.ts | 18 +++ 3 files changed, 123 insertions(+), 32 deletions(-) create mode 100644 packages/ui/src/components/chat/__tests__/questionTextareaSizing.test.ts create mode 100644 packages/ui/src/components/chat/questionTextareaSizing.ts diff --git a/packages/ui/src/components/chat/QuestionCard.tsx b/packages/ui/src/components/chat/QuestionCard.tsx index 93b13e77..01d5a5d0 100644 --- a/packages/ui/src/components/chat/QuestionCard.tsx +++ b/packages/ui/src/components/chat/QuestionCard.tsx @@ -14,6 +14,7 @@ import { useSessions } from '@/sync/sync-context'; import * as sessionActions from '@/sync/session-actions'; import { useI18n } from '@/lib/i18n'; import { serializeQuestionAsJson, serializeQuestionAsMarkdown } from './questionSerializers'; +import { QUESTION_CUSTOM_TEXTAREA_MIN_HEIGHT, getQuestionCustomTextareaHeight } from './questionTextareaSizing'; interface QuestionCardProps { question: QuestionRequest; @@ -22,6 +23,70 @@ interface QuestionCardProps { type TabKey = string; const SUMMARY_TAB = 'summary'; +interface CustomAnswerTextareaProps { + value: string; + placeholder: string; + disabled: boolean; + onValueChange: (value: string) => void; + onKeyDown: (event: React.KeyboardEvent) => void; +} + +const CustomAnswerTextarea = React.memo(function CustomAnswerTextarea({ + value, + placeholder, + disabled, + onValueChange, + onKeyDown, +}: CustomAnswerTextareaProps) { + const textareaRef = React.useRef(null); + const [localValue, setLocalValue] = React.useState(value); + const [height, setHeight] = React.useState(QUESTION_CUSTOM_TEXTAREA_MIN_HEIGHT); + const [isScrollable, setIsScrollable] = React.useState(false); + + React.useEffect(() => { + setLocalValue(value); + }, [value]); + + React.useLayoutEffect(() => { + const textarea = textareaRef.current; + if (!textarea) return; + + const nextHeight = getQuestionCustomTextareaHeight({ + scrollHeight: textarea.scrollHeight, + currentHeight: height, + }); + const nextScrollable = textarea.scrollHeight > (nextHeight ?? height); + if (isScrollable !== nextScrollable) { + setIsScrollable(nextScrollable); + } + if (nextHeight !== null) { + setHeight(nextHeight); + } + }, [height, isScrollable, localValue]); + + return ( +