Conversation Stream
A virtualized conversation scroller that sticks to the bottom and loads history.
Usage
Basic
Loading demo...
Best Practices
- Key by the message's own id, never the index; virtualization depends on stable keys.
- Prepend inside
loadOlder, then resolve{ hasMore }; the component never owns the array. - Pass
hasMoreInitial: falsewhen you know there is no history; the defaultundefinedmeans unknown. - Keep
estimatedItemHeightclose to the real average so the scrollbar doesn't jump on first paint. - Scroll programmatically through the exposed
scrollToBottomandscrollToIndex, not the scroll container.
API Reference
Props
| Name | Type | Default | Description |
|---|---|---|---|
items | T[] | — | The messages. Required. |
itemKey | ConversationStreamItemKey<T> | — | Stable key: a field name, or a function returning the key. Required. |
estimatedItemHeight | number | 96 | Height assumed for unmeasured items, corrected once measured. |
overscan | number | 4 | Extra items rendered on each side of the viewport. |
loadOlder | () => Promise<ConversationStreamLoadResult> | — | Called near the top; prepend into items, then resolve { hasMore }. |
hasMoreInitial | boolean | undefined | Whether history may exist before the first loadOlder answers. |
streaming | boolean | false | Drives the scroll-to-bottom button's new-content state. |
Events
| Name | Payload | Description |
|---|---|---|
at-bottom-change | (atBottom: boolean) | Fires when the at-bottom state changes. |
load-error | (error: unknown) | Fires when loadOlder throws. |
Slots
| Name | Scope | Description |
|---|---|---|
item | { item: T, index: number } | Renders one message. |
empty | — | Shown when items is empty. |
top-loading | — | Shown while older messages load. |
top-error | { retry: () => void } | Shown when loading fails, with a retry callback. |
top-done | — | Shown when there is no more history. |
scroll-to-bottom | { streaming: boolean } | Replaces the scroll-to-bottom button's content. |
Exposed Methods
| Name | Type | Description |
|---|---|---|
scrollToBottom | (behavior?: ScrollBehavior) => void | Scrolls to the bottom. |
scrollToIndex | (index: number) => void | Scrolls to an index. |
tweenToBottom | (duration?: number) => Promise<boolean> | Glides to the bottom over a fixed duration; resolves false if interrupted. |
atBottom | boolean | Whether the view is at the bottom. Read-only. |
Overview
- The component is generic over
T: the element type ofitemsflows to theitemslot's scope without casts. - At the bottom, new content is followed; once the user scrolls up, the view stays put and shows a scroll-to-bottom button.
streamingonly drives the button's new-content state; it doesn't decide whether the view sticks.- While the stream glides to the bottom on its own (after a send, or a programmatic scroll), the button doesn't show.
- A prepend keeps the viewport anchored.
hasMoreInitialdefaults to an explicitundefined, notfalse, so "unknown" stays distinct from "no history".
Technologies
- The virtual window takes
{ start, end }from a position cache (a prefix sum of measured heights, kept by key); a scroll frame that crosses no row boundary keeps the previous window and doesn't re-invoke theitemslot. - Source:
packages/tuffex/packages/components/src/conversation-stream/.