{"version":3,"file":"index.modern.mjs","sources":["../src/index.tsx"],"sourcesContent":["import * as React from 'react';\nimport { CSSProperties } from 'react';\n\nexport type ScrollableFeedProps = {\n    forceScroll?: boolean;\n    animateScroll?: (element: HTMLElement, offset: number) => void;\n    onScrollComplete?: () => void;\n    changeDetectionFilter?: (previousProps: ScrollableFeedComponentProps, newProps: ScrollableFeedComponentProps) => boolean;\n    viewableDetectionEpsilon?: number;\n    className?: string;\n    onScroll?: (isAtBottom: boolean) => void;\n    debug?: boolean\n}\n\ntype ScrollableFeedComponentProps = React.PropsWithChildren<ScrollableFeedProps>;\n\nclass ScrollableFeed extends React.Component<React.PropsWithChildren<ScrollableFeedProps>> {\n    private readonly wrapperRef: React.RefObject<HTMLDivElement | null>;\n    private readonly bottomRef: React.RefObject<HTMLDivElement | null>;\n\n    constructor(props: ScrollableFeedProps) {\n        super(props);\n        this.bottomRef = React.createRef();\n        this.wrapperRef = React.createRef();\n        this.handleScroll = this.handleScroll.bind(this);\n\n        if (this.props.debug) console.log(\"Component cstr\");\n    }\n\n    static defaultProps: ScrollableFeedProps = {\n        forceScroll: false,\n        animateScroll: (element: HTMLElement, offset: number): void => {\n            if (element.scrollBy) {\n                element.scrollBy({ top: offset });\n            }\n            else {\n                element.scrollTop = offset;\n            }\n        },\n        onScrollComplete: () => {},\n        changeDetectionFilter: () => true,\n        viewableDetectionEpsilon: 2,\n        onScroll: () => {},\n    };\n\n    getSnapshotBeforeUpdate(): boolean {\n        if (this.props.debug) console.log(\"Component \", this.getSnapshotBeforeUpdate.name);\n        if (this.wrapperRef.current && this.bottomRef.current) {\n            const { viewableDetectionEpsilon } = this.props;\n            return ScrollableFeed.isViewable(this.wrapperRef.current, this.bottomRef.current, viewableDetectionEpsilon!); //This argument is passed down to componentDidUpdate as 3rd parameter\n        }\n        return false;\n    }\n\n    componentDidUpdate(previousProps: ScrollableFeedComponentProps, _previousState: any, snapshot: boolean): void {\n        if (this.props.debug) console.log(\"Component \", this.componentDidUpdate.name);\n        const { forceScroll, changeDetectionFilter } = this.props;\n        const isValidChange = changeDetectionFilter!(previousProps, this.props);\n        if (isValidChange && (forceScroll || snapshot) && this.bottomRef.current && this.wrapperRef.current) {\n            this.scrollParentToChild(this.wrapperRef.current, this.bottomRef.current);\n        }\n    }\n\n    componentDidMount(): void {\n        if (this.props.debug) console.log(\"Component \", this.componentDidMount.name);\n        //Scroll to bottom from the start\n        if (this.bottomRef.current && this.wrapperRef.current) {\n            this.scrollParentToChild(this.wrapperRef.current, this.bottomRef.current);\n        }\n    }\n\n    /**\n     * Scrolls a parent element such that the child element will be in view\n     * @param parent\n     * @param child\n     */\n    protected scrollParentToChild(parent: HTMLElement, child: HTMLElement): void {\n        const { viewableDetectionEpsilon } = this.props;\n        if (!ScrollableFeed.isViewable(parent, child, viewableDetectionEpsilon!)) {\n            //Source: https://stackoverflow.com/a/45411081/6316091\n            const parentRect = parent.getBoundingClientRect();\n            const childRect = child.getBoundingClientRect();\n\n            //Scroll by offset relative to parent\n            const scrollOffset = (childRect.top + parent.scrollTop) - parentRect.top;\n            const { animateScroll, onScrollComplete } = this.props;\n            if (animateScroll) {\n                animateScroll(parent, scrollOffset);\n                onScrollComplete!();\n            }\n        }\n    }\n\n    /**\n     * Returns whether a child element is visible within a parent element\n     *\n     * @param parent\n     * @param child\n     * @param epsilon\n     */\n    private static isViewable(parent: HTMLElement, child: HTMLElement, epsilon: number): boolean {\n        epsilon = epsilon || 0;\n\n        //Source: https://stackoverflow.com/a/45411081/6316091\n        const parentRect = parent.getBoundingClientRect();\n        const childRect = child.getBoundingClientRect();\n\n        const childTopIsViewable = (childRect.top >= parentRect.top);\n\n        const childOffsetToParentBottom = parentRect.top + parent.clientHeight - childRect.top;\n        const childBottomIsViewable = childOffsetToParentBottom + epsilon >= 0;\n\n        return childTopIsViewable && childBottomIsViewable;\n    }\n\n    /**\n     * Fires the onScroll event, sending isAtBottom boolean as its first parameter\n     */\n    protected handleScroll(): void {\n        const { viewableDetectionEpsilon, onScroll } = this.props;\n        if (onScroll && this.bottomRef.current && this.wrapperRef.current) {\n            const isAtBottom = ScrollableFeed.isViewable(this.wrapperRef.current, this.bottomRef.current, viewableDetectionEpsilon!);\n            onScroll(isAtBottom);\n        }\n    }\n\n    /**\n     * Scroll to the bottom\n     */\n    public scrollToBottom(): void {\n        if (this.bottomRef.current && this.wrapperRef.current) {\n            this.scrollParentToChild(this.wrapperRef.current, this.bottomRef.current);\n        }\n    }\n\n    render(): React.ReactNode {\n        if (this.props.debug) console.log(\"Component \", this.render.name);\n\n        const style: CSSProperties = {\n            maxHeight: \"inherit\",\n            height: \"inherit\",\n            overflowY: \"auto\",\n        };\n        const { children, className } = this.props;\n        return (\n            <div className={className} style={style} ref={this.wrapperRef} onScroll={this.handleScroll}>\n                {children}\n                <div ref={this.bottomRef}></div>\n            </div>\n        );\n    }\n}\n\nexport default ScrollableFeed;\n"],"names":["ScrollableFeed","React","Component","constructor","props","super","this","wrapperRef","bottomRef","createRef","handleScroll","bind","debug","console","log","getSnapshotBeforeUpdate","name","current","viewableDetectionEpsilon","isViewable","componentDidUpdate","previousProps","_previousState","snapshot","forceScroll","changeDetectionFilter","scrollParentToChild","componentDidMount","parent","child","parentRect","getBoundingClientRect","scrollOffset","top","scrollTop","animateScroll","onScrollComplete","epsilon","childRect","clientHeight","onScroll","scrollToBottom","render","children","className","style","maxHeight","height","overflowY","ref","createElement","defaultProps","element","offset","scrollBy"],"mappings":"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"}