From 4ce68d74f135a66991c2b297c8a580eddb473595 Mon Sep 17 00:00:00 2001 From: DarkSky <25152247+darkskygit@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:28:05 +0800 Subject: [PATCH] fix(editor): chat cannot scroll on windows (#14677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #14529 fix #14612 replace #14614 #14657 #### PR Dependency Tree * **PR #14677** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) ## Summary by CodeRabbit * **Tests** * Added test coverage for scroll position tracking and pinned scroll behavior in AI chat * Added test suite verifying scroll-to-end and scroll-to-position functionality * **New Features** * Introduced configurable scrollable option for text rendering in AI chat components, allowing control over scroll behavior --- .../ai-chat-content/ai-chat-content.spec.ts | 41 +++++++++++++++ .../ai-chat-messages/ai-chat-messages.spec.ts | 50 +++++++++++++++++++ .../ai-message-content/rich-text.ts | 1 + .../blocksuite/ai/components/text-renderer.ts | 7 ++- 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 packages/frontend/core/src/blocksuite/ai/components/ai-chat-content/ai-chat-content.spec.ts create mode 100644 packages/frontend/core/src/blocksuite/ai/components/ai-chat-messages/ai-chat-messages.spec.ts diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-content/ai-chat-content.spec.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-content/ai-chat-content.spec.ts new file mode 100644 index 0000000000..47c3b2db30 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-content/ai-chat-content.spec.ts @@ -0,0 +1,41 @@ +/** + * @vitest-environment happy-dom + */ +import { describe, expect, test, vi } from 'vitest'; + +import { AIChatContent } from './ai-chat-content'; + +describe('AIChatContent pinned scroll tracking', () => { + test('records scroll position from the chat messages host', async () => { + let scrollEndHandler: (() => void) | undefined; + + const chatMessages = { + scrollTop: 256, + updateComplete: Promise.resolve(), + addEventListener: vi.fn((event: string, handler: EventListener) => { + if (event === 'scrollend') { + scrollEndHandler = handler as () => void; + } + }), + }; + + const content = { + chatMessagesRef: { value: chatMessages }, + _scrollListenersInitialized: false, + lastScrollTop: undefined, + } as unknown as AIChatContent; + + (AIChatContent.prototype as any)._initializeScrollListeners.call(content); + await chatMessages.updateComplete; + await Promise.resolve(); + + expect(chatMessages.addEventListener).toHaveBeenCalledWith( + 'scrollend', + expect.any(Function) + ); + + scrollEndHandler?.(); + + expect((content as any).lastScrollTop).toBe(256); + }); +}); diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-messages/ai-chat-messages.spec.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-messages/ai-chat-messages.spec.ts new file mode 100644 index 0000000000..cbc4700776 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-messages/ai-chat-messages.spec.ts @@ -0,0 +1,50 @@ +/** + * @vitest-environment happy-dom + */ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +import { AIChatMessages } from './ai-chat-messages'; + +describe('AIChatMessages scrolling', () => { + beforeEach(() => { + vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => { + cb(0); + return 1; + }); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + test('scrollToEnd scrolls the host element', () => { + const scrollTo = vi.fn(); + const element = { + scrollTo, + } as unknown as AIChatMessages; + + Object.defineProperty(element, 'scrollHeight', { + configurable: true, + value: 480, + }); + + AIChatMessages.prototype.scrollToEnd.call(element); + + expect(scrollTo).toHaveBeenCalledWith({ + top: 480, + behavior: 'smooth', + }); + }); + + test('scrollToPos scrolls the host element', () => { + const scrollTo = vi.fn(); + const element = { + scrollTo, + } as unknown as AIChatMessages; + + AIChatMessages.prototype.scrollToPos.call(element, 128); + + expect(scrollTo).toHaveBeenCalledWith({ top: 128 }); + }); +}); diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-message-content/rich-text.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-message-content/rich-text.ts index 11197a6017..0a8b31c0ba 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-message-content/rich-text.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-message-content/rich-text.ts @@ -32,6 +32,7 @@ export class ChatContentRichText extends WithDisposable(ShadowlessElement) { extensions: this.extensions, affineFeatureFlagService: this.affineFeatureFlagService, theme: this.theme, + scrollable: false, })(text, this.state)}`; } } diff --git a/packages/frontend/core/src/blocksuite/ai/components/text-renderer.ts b/packages/frontend/core/src/blocksuite/ai/components/text-renderer.ts index ccf2a6e464..c67cd94c00 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/text-renderer.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/text-renderer.ts @@ -85,6 +85,7 @@ export type TextRendererOptions = { testId?: string; affineFeatureFlagService?: FeatureFlagService; theme?: Signal; + scrollable?: boolean; }; // todo: refactor it for more general purpose usage instead of AI only? @@ -140,9 +141,12 @@ export class TextRenderer extends SignalWatcher( } .text-renderer-container { + padding: 0; + } + + .text-renderer-container.scrollable { overflow-y: auto; overflow-x: hidden; - padding: 0; overscroll-behavior-y: none; } .text-renderer-container.show-scrollbar::-webkit-scrollbar { @@ -325,6 +329,7 @@ export class TextRenderer extends SignalWatcher( const classes = classMap({ 'text-renderer-container': true, 'custom-heading': !!customHeading, + scrollable: this.options.scrollable !== false, }); const theme = this.options.theme?.value; return html`