Components/Conversation Stream

Conversation Stream

A virtualized conversation scroller that sticks to the bottom and loads history.

VerifiedSince 0.3.9

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: false when you know there is no history; the default undefined means unknown.
  • Keep estimatedItemHeight close to the real average so the scrollbar doesn't jump on first paint.
  • Scroll programmatically through the exposed scrollToBottom and scrollToIndex, not the scroll container.

API Reference

Props

NameTypeDefaultDescription
itemsT[]—The messages. Required.
itemKeyConversationStreamItemKey<T>—Stable key: a field name, or a function returning the key. Required.
estimatedItemHeightnumber96Height assumed for unmeasured items, corrected once measured.
overscannumber4Extra items rendered on each side of the viewport.
loadOlder() => Promise<ConversationStreamLoadResult>—Called near the top; prepend into items, then resolve { hasMore }.
hasMoreInitialbooleanundefinedWhether history may exist before the first loadOlder answers.
streamingbooleanfalseDrives the scroll-to-bottom button's new-content state.

Events

NamePayloadDescription
at-bottom-change(atBottom: boolean)Fires when the at-bottom state changes.
load-error(error: unknown)Fires when loadOlder throws.

Slots

NameScopeDescription
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

NameTypeDescription
scrollToBottom(behavior?: ScrollBehavior) => voidScrolls to the bottom.
scrollToIndex(index: number) => voidScrolls to an index.
tweenToBottom(duration?: number) => Promise<boolean>Glides to the bottom over a fixed duration; resolves false if interrupted.
atBottombooleanWhether the view is at the bottom. Read-only.

Overview

  • The component is generic over T: the element type of items flows to the item slot'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.
  • streaming only 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.
  • hasMoreInitial defaults to an explicit undefined, not false, 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 the item slot.
  • Source: packages/tuffex/packages/components/src/conversation-stream/.