UNPKG

@oruga-ui/oruga-next

Version:

UI components for Vue.js and CSS framework agnostic

132 lines (112 loc) 4.75 kB
import { getCurrentScope, type Component, type MaybeRefOrGetter } from "vue"; import { isDefined } from "@/utils/helpers"; import { isClient } from "@/utils/ssr"; import { unrefElement } from "./unrefElement"; import { useDebounce } from "./useDebounce"; import { useEventListener, type EventListenerOptions, type EventTarget, } from "./useEventListener"; /** Call a function when the scoll reaches the end or the start of an element. * This is useful for infinite scroll lists. * @param element - The element to listen for scroll events. * @param options - Options for the infinite scroll. * @param options.onScroll - Function to call on every scroll event. * @param options.onEnd - Function to call when the scroll reaches the end. * @param options.onStart - Function to call when the scroll reaches the start. * @param options.debounce - Debounce time in milliseconds for the function call on scroll events. * @returns A function to call to to manually check the scroll position. */ export function useScrollEvents( element: MaybeRefOrGetter<EventTarget>, options: { onScroll?: () => void; onScrollEnd?: () => void; onScrollStart?: () => void; debounce?: number; }, listenerOptions?: EventListenerOptions, ): () => void { if (!getCurrentScope()) throw new Error( "The 'useScrollEvents' composable should be used inside a current EffectScope.", ); /** debounced checkScroll funciton */ const debouncedCheckScroll = useDebounce( checkScroll, options.debounce ?? 100, ); if (isClient) useEventListener( element, "scroll", debouncedCheckScroll, listenerOptions, ); /** Check if the scroll list inside the dropdown reached the top or it's end. */ function checkScroll(): void { const el = unrefElement(element); if (!el) return; if (options.onScroll) options.onScroll(); const { offsetTop, scrollTop, clientHeight, scrollHeight } = el; const trashhold = offsetTop; if (clientHeight !== scrollHeight) { if ( Math.ceil(scrollTop + clientHeight + trashhold) >= scrollHeight ) { if (options.onScrollEnd) options.onScrollEnd(); } else if (scrollTop <= trashhold) { if (options.onScrollStart) options.onScrollStart(); } } } return debouncedCheckScroll; } /** * Given an element, returns the element who scrolls it. */ export function getScrollingParent(target: HTMLElement): HTMLElement | null { if (target.style.position === "fixed" || !target) return document.documentElement; let isScrollingParent = false; let nextParent = target.parentElement; while (!isScrollingParent && isDefined(nextParent)) { if (nextParent === document.documentElement) break; const { overflow, overflowY } = getComputedStyle(nextParent); const { scrollHeight, clientHeight } = nextParent; // Both rounded by nature isScrollingParent = /(auto|scroll)/.test(`${overflow}${overflowY}`) && scrollHeight > clientHeight; /* ...found it, this one is returned */ if (isScrollingParent) break; /* ...if not check the next one */ nextParent = nextParent.parentElement; } return nextParent; } /** * Ensure a given child element is within the parent's visible scroll area. * If the child is not visible, scroll the parent to the child's position. */ export function scrollElementInView( scrollableParent: MaybeRefOrGetter<HTMLElement | Component | null>, childElement: MaybeRefOrGetter<HTMLElement | Component | null>, ): void { const parent = unrefElement(scrollableParent); const element = unrefElement(childElement); if (!parent || !element) return; // The 'offsetTop' is the distance from the outer border of the element (including margin) // to the top padding edge of the offsetParent, the closest positioned ancestor element. // The 'offsetHeight' is the height of an element, including vertical padding and borders, as an integer. // The 'scrollTop' is the number of pixels by which an element's content is scrolled from its top edge. const { offsetHeight, offsetTop } = element; const { offsetHeight: parentOffsetHeight, scrollTop } = parent; const isAbove = offsetTop < scrollTop; const isBelow = offsetTop + offsetHeight > scrollTop + parentOffsetHeight; if (isAbove) { parent.scrollTo(0, offsetTop); } else if (isBelow) { parent.scrollTo(0, offsetTop - parentOffsetHeight + offsetHeight); } }