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:
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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 });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -32,6 +32,7 @@ export class ChatContentRichText extends WithDisposable(ShadowlessElement) {
|
|||||||
extensions: this.extensions,
|
extensions: this.extensions,
|
||||||
affineFeatureFlagService: this.affineFeatureFlagService,
|
affineFeatureFlagService: this.affineFeatureFlagService,
|
||||||
theme: this.theme,
|
theme: this.theme,
|
||||||
|
scrollable: false,
|
||||||
})(text, this.state)}`;
|
})(text, this.state)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export type TextRendererOptions = {
|
|||||||
testId?: string;
|
testId?: string;
|
||||||
affineFeatureFlagService?: FeatureFlagService;
|
affineFeatureFlagService?: FeatureFlagService;
|
||||||
theme?: Signal<ColorScheme>;
|
theme?: Signal<ColorScheme>;
|
||||||
|
scrollable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// todo: refactor it for more general purpose usage instead of AI only?
|
// todo: refactor it for more general purpose usage instead of AI only?
|
||||||
@@ -140,9 +141,12 @@ export class TextRenderer extends SignalWatcher(
|
|||||||
}
|
}
|
||||||
|
|
||||||
.text-renderer-container {
|
.text-renderer-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-renderer-container.scrollable {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
padding: 0;
|
|
||||||
overscroll-behavior-y: none;
|
overscroll-behavior-y: none;
|
||||||
}
|
}
|
||||||
.text-renderer-container.show-scrollbar::-webkit-scrollbar {
|
.text-renderer-container.show-scrollbar::-webkit-scrollbar {
|
||||||
@@ -325,6 +329,7 @@ export class TextRenderer extends SignalWatcher(
|
|||||||
const classes = classMap({
|
const classes = classMap({
|
||||||
'text-renderer-container': true,
|
'text-renderer-container': true,
|
||||||
'custom-heading': !!customHeading,
|
'custom-heading': !!customHeading,
|
||||||
|
scrollable: this.options.scrollable !== false,
|
||||||
});
|
});
|
||||||
const theme = this.options.theme?.value;
|
const theme = this.options.theme?.value;
|
||||||
return html`
|
return html`
|
||||||
|
|||||||
Reference in New Issue
Block a user