Some checks failed
Build Android APK / build-android-apk (push) Has been cancelled
Deploy Web Apps / deploy (push) Has been cancelled
Queue Release Build / prepare (push) Has been cancelled
Queue Release Build / build-linux (push) Has been cancelled
Queue Release Build / build-windows (push) Has been cancelled
Queue Release Build / finalize (push) Has been cancelled
60 lines
2.3 KiB
TypeScript
60 lines
2.3 KiB
TypeScript
/** Behaviour the message list should apply when new messages arrive. */
|
|
export type AutoScrollBehavior = 'smooth' | 'instant' | 'none';
|
|
|
|
export interface AutoScrollDecisionInput {
|
|
/** Whether the message count grew since the last render. */
|
|
readonly newMessages: boolean;
|
|
/** Forced jump for the local user's own just-sent message. */
|
|
readonly forceLocalSend: boolean;
|
|
/** Pixels from the bottom of the scroll container. */
|
|
readonly distanceFromBottom: number;
|
|
/**
|
|
* True while the conversation is still settling after a channel/server
|
|
* switch (initial scroll + async embed/image height changes). During this
|
|
* window we jump instantly so we never animate to the wrong position.
|
|
*/
|
|
readonly withinInitialGrace: boolean;
|
|
/** Distance threshold under which the list is considered stuck to bottom. */
|
|
readonly stickyThreshold?: number;
|
|
}
|
|
|
|
/**
|
|
* Decides how the message list should react when the message count changes.
|
|
*
|
|
* - No new messages -> `none`.
|
|
* - Local user's own send -> `instant` (never animate your own message).
|
|
* - Near bottom during the post-switch settle window -> `instant` (so loading
|
|
* a channel always lands at the bottom without a mid-chat smooth animation).
|
|
* - Near bottom afterwards -> `smooth` (live messages animate into view).
|
|
* - Far from bottom -> `none` (show the new-messages indicator instead).
|
|
*/
|
|
export function resolveAutoScrollBehavior(input: AutoScrollDecisionInput): AutoScrollBehavior {
|
|
if (!input.newMessages)
|
|
return 'none';
|
|
|
|
if (input.forceLocalSend)
|
|
return 'instant';
|
|
|
|
const threshold = input.stickyThreshold ?? 300;
|
|
|
|
if (input.distanceFromBottom > threshold)
|
|
return 'none';
|
|
|
|
return input.withinInitialGrace ? 'instant' : 'smooth';
|
|
}
|
|
|
|
/** Default pixel distance under which the list is considered stuck to bottom. */
|
|
export const STICKY_BOTTOM_THRESHOLD = 300;
|
|
|
|
/**
|
|
* Whether the scroll position is close enough to the bottom that the list
|
|
* should keep auto-pinning to the latest message. Negative distances (rubber
|
|
* band / overscroll) count as stuck.
|
|
*
|
|
* This is the predicate the message list uses to decide whether a content
|
|
* height change (late image/embed/plugin render) should re-pin to bottom.
|
|
*/
|
|
export function isStuckToBottom(distanceFromBottom: number, threshold: number = STICKY_BOTTOM_THRESHOLD): boolean {
|
|
return distanceFromBottom <= threshold;
|
|
}
|