From 5f857a168778be90399ca0f2cce08695aa7e2286 Mon Sep 17 00:00:00 2001 From: flamboh Date: Thu, 24 Sep 2026 04:03:12 +0000 Subject: [PATCH] fix(web): composer prompt text no longer jumps when resting or expanding --- .../components/ComposerPromptEditorTiptap.tsx | 1 + apps/web/src/components/chat/ChatComposer.tsx | 37 +++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/apps/web/src/components/ComposerPromptEditorTiptap.tsx b/apps/web/src/components/ComposerPromptEditorTiptap.tsx index 50305ffdbef0..5c7f761f387b 100644 --- a/apps/web/src/components/ComposerPromptEditorTiptap.tsx +++ b/apps/web/src/components/ComposerPromptEditorTiptap.tsx @@ -1241,6 +1241,7 @@ function ComposerPromptEditorTiptapInner(props: ComposerPromptEditorProps) {
('[data-testid="composer-editor"]') ?? prompt; + const lineHeight = Number.parseFloat(getComputedStyle(lineSource).lineHeight); + return rect.top + (Number.isFinite(lineHeight) ? lineHeight / 2 : rect.height / 2); +} + function useComposerRestingTransition( isCollapsed: boolean, isResting: boolean, @@ -469,15 +476,12 @@ function useComposerRestingTransition( Array.from(element.querySelectorAll(selector)).find( (candidate) => candidate.getClientRects().length > 0, ) ?? null; - const prompt = visibleTransitionElement( - '[data-testid="composer-editor"], [data-chat-composer-transition-prompt="true"]', - ); + const prompt = visibleTransitionElement('[data-chat-composer-transition-prompt="true"]'); const action = visibleTransitionElement('[data-chat-composer-transition-actions="true"]'); const footer = element.querySelector('[data-chat-composer-footer="true"]'); const interruptedAnimation = animationRef.current; - const interruptedPromptTop = interruptedAnimation - ? (prompt?.getBoundingClientRect().top ?? null) - : null; + const interruptedPromptTop = + interruptedAnimation && prompt ? composerPromptLineCenter(prompt) : null; const interruptedActionTop = interruptedAnimation ? (action?.getBoundingClientRect().top ?? null) : null; @@ -520,7 +524,7 @@ function useComposerRestingTransition( onOverlayHeightChange(overlayHeight); } const nextPromptRect = prompt?.getBoundingClientRect() ?? null; - const nextPromptTop = nextPromptRect?.top ?? null; + const nextPromptTop = prompt ? composerPromptLineCenter(prompt) : null; const nextActionTop = action?.getBoundingClientRect().top ?? null; const previousHeight = interruptedHeight ?? previousHeightRef.current; const targetChanged = @@ -602,9 +606,10 @@ function useComposerRestingTransition( const animateContentPosition = ( content: HTMLElement | null, previousTop: number | null, + measureTop: (content: HTMLElement) => number, ) => { if (!content || previousTop === null) return; - const offset = previousTop - content.getBoundingClientRect().top; + const offset = Math.round(previousTop - measureTop(content)); if (Math.abs(offset) < 0.5) return; contentAnimations.push( content.animate( @@ -616,8 +621,12 @@ function useComposerRestingTransition( ), ); }; - animateContentPosition(prompt, previousPromptTop); - animateContentPosition(action, previousActionTop); + animateContentPosition(prompt, previousPromptTop, composerPromptLineCenter); + animateContentPosition( + action, + previousActionTop, + (content) => content.getBoundingClientRect().top, + ); contentAnimationsRef.current = contentAnimations; if (stateChanged) { @@ -798,16 +807,14 @@ function useComposerRestingTransition( Array.from(element.querySelectorAll(selector)).find( (candidate) => candidate.getClientRects().length > 0, ) ?? null; - const promptRect = visibleTransitionElement( - '[data-testid="composer-editor"], [data-chat-composer-transition-prompt="true"]', - )?.getBoundingClientRect(); + const prompt = visibleTransitionElement('[data-chat-composer-transition-prompt="true"]'); const actionTop = visibleTransitionElement( '[data-chat-composer-transition-actions="true"]', )?.getBoundingClientRect().top; previousHeightRef.current = elementRect.height; previousContentOffsetsRef.current = { - promptFromTop: promptRect === undefined ? null : promptRect.top - elementRect.top, - promptHeight: promptRect?.height ?? null, + promptFromTop: prompt ? composerPromptLineCenter(prompt) - elementRect.top : null, + promptHeight: prompt?.getBoundingClientRect().height ?? null, actionFromBottom: actionTop === undefined ? null : elementRect.bottom - actionTop, }; });