fix(editor): chat cannot scroll on windows (#14677)

fix #14529 
fix #14612 
replace #14614 #14657


#### PR Dependency Tree


* **PR #14677** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## 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

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-03-18 13:28:05 +08:00
committed by GitHub
parent fbfcc01d14
commit 4ce68d74f1
4 changed files with 98 additions and 1 deletions

View File

@@ -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);
});
});

View File

@@ -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 });
});
});

View File

@@ -32,6 +32,7 @@ export class ChatContentRichText extends WithDisposable(ShadowlessElement) {
extensions: this.extensions,
affineFeatureFlagService: this.affineFeatureFlagService,
theme: this.theme,
scrollable: false,
})(text, this.state)}`;
}
}

View File

@@ -85,6 +85,7 @@ export type TextRendererOptions = {
testId?: string;
affineFeatureFlagService?: FeatureFlagService;
theme?: Signal<ColorScheme>;
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`