sdxc

Type to search, or start from one of these:

[ @sdxc/ui/behaviors ]

ScrollFollowModel

Headless scroll-follow state for a conversational message viewport.

import { ScrollFollowModel } from "@sdxc/ui/behaviors";

Usage

Auto-follow state for a message viewport: the pinned live edge, the anchored turn, the visible messages, and the reachable scroll edges. Every measurement arrives through a setter, so transitions stay DOM-free.

Signature

new ScrollFollowModel(options?: ScrollFollowModel.Options)

Parameters

ParameterTypeDescription
options?ScrollFollowModel.OptionsInitial pinned state, anchor turn, visible messages, and edge reachability. All are optional and default to a freshly pinned model anchored to nothing, with no visible messages and both edges unreachable.

Properties

PropertyTypeDescription
pinnedbooleanWhether the viewport is auto-following the live edge, so arriving messages scroll the reader down. Updated through setPinned.
anchorTurnIdstring | nullId of the turn the viewport is anchored to, or null before one has been measured. Read back to hold the reader's position while older history prepends above this turn.
visibleMessageIdsReadonlySet<string>Ids of the messages currently visible in the viewport, as last reported through setMessageVisible.
startReachablebooleanWhether the viewport can still be scrolled toward its start edge.
endReachablebooleanWhether the viewport can still be scrolled toward its end edge.
pendingScrollRequestScrollFollowModel.ScrollRequest | nullThe scroll intent recorded by scrollToEnd, scrollToStart, or scrollToMessage that is still unfulfilled, or null once consumeScrollRequest has cleared it.

Methods

MethodDescription
setPinned(pinned: boolean): voidRecords whether the viewport is auto-following the live edge, as observed while the reader scrolls. A no-op, dispatching nothing, when pinned already matches the current value.
setAnchorTurnId(id: string | null): voidRecords which turn the viewport is anchored to, once the caller has measured which turn sits nearest the anchor edge. A no-op, dispatching nothing, when id already matches the current anchor.
setMessageVisible(id: string, visible: boolean): voidRecords whether a single message is visible, one entry at a time to match an IntersectionObserver callback. A no-op, dispatching nothing, when visible already matches membership in visibleMessageIds.
isMessageVisible(id: string): booleanReports whether a message is visible, per the last report given to setMessageVisible. The read side of the visibility API, so a consumer reads visibility here after each "change".
setReachableEdges(edges: ScrollFollowModel.ReachableEdges): voidRecords which scrollable edges the viewport can reach. Takes both edges at once because one scroll or resize measurement yields both. A no-op, dispatching nothing, when neither edge's reachability changes.
scrollToEnd(): voidRecords an intent to scroll to the live edge of the conversation. Always dispatches "change", even while a request is pending, so a repeated "jump to latest" click still reaches the viewport.
scrollToStart(): voidRecords an intent to scroll to the start of the conversation. Always dispatches "change", even when a request is already pending.
scrollToMessage(id: string, options?: ScrollFollowModel.ScrollToMessageOptions): voidRecords an intent to scroll to a specific message. Always dispatches "change", even when a request is already pending.
consumeScrollRequest(): ScrollFollowModel.ScrollRequest | nullReads and clears the pending scroll request in one step, so an intent is fulfilled exactly once. Clearing stays silent — subscribers already reacted when the intent was recorded.

Examples

let model = new ScrollFollowModel();
model.addEventListener("change", () => update());
model.setReachableEdges({ start: true, end: false });
model.scrollToEnd();
model.consumeScrollRequest(); // { type: "end" }

The events, constants and types its module publishes, each imported from @sdxc/ui/behaviors too.

Interface

ScrollFollowModel.ScrollToMessageOptions

Options accepted by ScrollFollowModel.scrollToMessage.

MemberTypeDescription
align?AlignWhere the message lands in the viewport. Defaults to "start".
smooth?booleanWhether the viewport animates on its way to the message. Defaults to true.
Interface

ScrollFollowModel.ReachableEdges

Reachability of the two ends of the scrollable region, as last measured.

MemberTypeDescription
startbooleanWhether the viewport can still be scrolled toward its start edge.
endbooleanWhether the viewport can still be scrolled toward its end edge.
Interface

ScrollFollowModel.Options

Constructor options for ScrollFollowModel.

MemberTypeDescription
pinned?booleanWhether auto-follow starts engaged. Defaults to true.
anchorTurnId?string | nullTurn id the viewport starts anchored to. Defaults to null.
visibleMessageIds?Iterable<string>Message ids visible in the viewport at construction. Defaults to none.
reachableEdges?ReachableEdgesReachability of the start/end edges at construction. Defaults to both unreachable.
Interface

ScrollFollowModel.EventMap

Events dispatched by ScrollFollowModel as its state changes.

MemberTypeDescription
changeEventDispatched after any owned state changes, or a scroll intent is recorded.
Type

ScrollFollowModel.Align

Where a scrolled-to message should land inside the viewport once an intent is fulfilled, matching Element.scrollIntoView's block option.

type ScrollFollowModel.Align = "start" | "center" | "end"
Type

ScrollFollowModel.ScrollRequest

One scroll intent recorded by an intent method (scrollToEnd, scrollToStart, scrollToMessage) and read back by the caller that fulfills it against the real viewport.

type ScrollFollowModel.ScrollRequest = | { type: "end" }
		| { type: "start" }
		| { type: "message"; id: string; align: Align; smooth: boolean }

Written by Sergio Xalambrí. Follow @sergiodxa for new packages, or sponsor the work.