import { CdkScrollable } from "@angular/cdk/scrolling"; import { Signal, inject, signal } from "@angular/core"; import { toObservable, toSignal } from "@angular/core/rxjs-interop"; import { map, startWith, switchMap } from "rxjs"; export type ScrollState = { /** `true` when the scrollbar is not at the top-most position */ top: boolean; /** `true` when the scrollbar is not at the bottom-most position */ bottom: boolean; }; /** * Check if a `CdkScrollable` instance has been scrolled * @param scrollable The instance to check, defaults to the one provided by the current injector * @returns {Signal} */ export const hasScrolledFrom = (scrollable?: Signal): Signal => { const _scrollable = scrollable ?? signal(inject(CdkScrollable)); const scrollable$ = toObservable(_scrollable); const scrollState$ = scrollable$.pipe( switchMap((_scrollable) => _scrollable.elementScrolled().pipe( startWith(null), map(() => ({ top: _scrollable.measureScrollOffset("top") > 0, // Using 1 as the threshold here because `_scrollable.measureScrollOffset("bottom")` returns '0.5' at the bottom most position in Chrome bottom: _scrollable.measureScrollOffset("bottom") > 1, })), ), ), ); return toSignal(scrollState$, { initialValue: { top: false, bottom: false, }, }); };