[ @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
| Parameter | Type | Description |
|---|---|---|
options? | ScrollFollowModel.Options | Initial 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
| Property | Type | Description |
|---|---|---|
pinned | boolean | Whether the viewport is auto-following the live edge, so arriving messages scroll the reader down. Updated through setPinned. |
anchorTurnId | string | null | Id 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. |
visibleMessageIds | ReadonlySet<string> | Ids of the messages currently visible in the viewport, as last reported through setMessageVisible. |
startReachable | boolean | Whether the viewport can still be scrolled toward its start edge. |
endReachable | boolean | Whether the viewport can still be scrolled toward its end edge. |
pendingScrollRequest | ScrollFollowModel.ScrollRequest | null | The scroll intent recorded by scrollToEnd, scrollToStart, or scrollToMessage that is still unfulfilled, or null once consumeScrollRequest has cleared it. |
Methods
| Method | Description |
|---|---|
setPinned(pinned: boolean): void | Records 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): void | Records 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): void | Records 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): boolean | Reports 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): void | Records 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(): void | Records 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(): void | Records 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): void | Records an intent to scroll to a specific message. Always dispatches "change", even when a request is already pending. |
consumeScrollRequest(): ScrollFollowModel.ScrollRequest | null | Reads 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" }Used with it
The events, constants and types its module publishes, each imported from @sdxc/ui/behaviors too.
ScrollFollowModel.ScrollToMessageOptions
Options accepted by ScrollFollowModel.scrollToMessage.
| Member | Type | Description |
|---|---|---|
align? | Align | Where the message lands in the viewport. Defaults to "start". |
smooth? | boolean | Whether the viewport animates on its way to the message. Defaults to true. |
ScrollFollowModel.ReachableEdges
Reachability of the two ends of the scrollable region, as last measured.
| Member | Type | Description |
|---|---|---|
start | boolean | Whether the viewport can still be scrolled toward its start edge. |
end | boolean | Whether the viewport can still be scrolled toward its end edge. |
ScrollFollowModel.Options
Constructor options for ScrollFollowModel.
| Member | Type | Description |
|---|---|---|
pinned? | boolean | Whether auto-follow starts engaged. Defaults to true. |
anchorTurnId? | string | null | Turn id the viewport starts anchored to. Defaults to null. |
visibleMessageIds? | Iterable<string> | Message ids visible in the viewport at construction. Defaults to none. |
reachableEdges? | ReachableEdges | Reachability of the start/end edges at construction. Defaults to both unreachable. |
ScrollFollowModel.EventMap
Events dispatched by ScrollFollowModel as its state changes.
| Member | Type | Description |
|---|---|---|
change | Event | Dispatched after any owned state changes, or a scroll intent is recorded. |
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"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 }