From 6bf12562547c8f08f2c6efb4d95196c128b4e707 Mon Sep 17 00:00:00 2001 From: Roc Wong Date: Sun, 13 Feb 2022 01:38:17 +1300 Subject: [PATCH] feat: support non-window scroll parent regarding the scrollTo prop --- src/Grid.vue | 8 ++++---- src/pipeline.ts | 38 +++++++++++++++++++++++--------------- src/utilites.ts | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/src/Grid.vue b/src/Grid.vue index db59ca9..aa3b25f 100644 --- a/src/Grid.vue +++ b/src/Grid.vue @@ -47,7 +47,7 @@ import { fromWindowScroll, useObservable, } from "./utilites"; -import { PageProvider, pipeline } from "./pipeline"; +import { PageProvider, pipeline, ScrollAction } from "./pipeline"; import { once } from "ramda"; import { VueInstance } from "@vueuse/core"; @@ -95,7 +95,7 @@ export default defineComponent({ const { buffer$, // the items in the current scanning window contentHeight$, // the height of the whole list - windowScrollTo$, // the value sent to window.scrollTo() + scrollAction$, // the value sent to window.scrollTo() } = pipeline({ // streams of prop length$: fromProp(props, "length"), @@ -113,8 +113,8 @@ export default defineComponent({ onUpdated( once(() => { - windowScrollTo$.subscribe((next) => { - window.scrollTo({ top: next, behavior: "smooth" }); + scrollAction$.subscribe(([el, top]: ScrollAction) => { + el.scrollTo({ top, behavior: "smooth" }); }); }) ); diff --git a/src/pipeline.ts b/src/pipeline.ts index ace1132..989e3f9 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -15,7 +15,6 @@ import { shareReplay, switchMap, take, - withLatestFrom, } from "rxjs"; import { __, @@ -35,6 +34,7 @@ import { without, zip, } from "ramda"; +import { getVerticalScrollParent } from "./utilites"; export function computeHeightAboveWindowOf(el: Element): number { const top = el.getBoundingClientRect().top; @@ -228,10 +228,12 @@ interface PipelineInput { scrollTo$: Observable; } +export type ScrollAction = [Element, number]; + interface PipelineOutput { buffer$: Observable; contentHeight$: Observable; - windowScrollTo$: Observable; + scrollAction$: Observable; } export function pipeline({ @@ -262,20 +264,26 @@ export function pipeline({ // endregion // region: scroll to a given item by index - const windowScrollTo$ = scrollTo$.pipe( + const scrollAction$: Observable = scrollTo$.pipe( filter(complement(isNil)), - switchMap((scrollTo) => - combineLatest([of(scrollTo), resizeMeasurement$, rootResize$]).pipe( - take(1) - ) + switchMap>( + (scrollTo) => + combineLatest([of(scrollTo), resizeMeasurement$, rootResize$]).pipe( + take(1) + ) ), - map( - ([scrollTo, { columns, itemHeightWithGap }, rootEl]) => - // The offset within the grid - Math.floor(scrollTo / columns) * itemHeightWithGap + - // The offset of grid root to the document - (rootEl.getBoundingClientRect().top + - document.documentElement.scrollTop) + map<[number, ResizeMeasurement, Element], ScrollAction>( + ([scrollTo, { columns, itemHeightWithGap }, rootEl]) => { + const verticalScrollEl = getVerticalScrollParent(rootEl); + + const scrollTop = + // The offset within the grid container + Math.floor((scrollTo - 1) / columns) * itemHeightWithGap + + // Offset to the offsetParent + (rootEl instanceof HTMLElement ? rootEl.offsetTop : 0); + + return [verticalScrollEl, scrollTop]; + } ) ); // endregion @@ -319,5 +327,5 @@ export function pipeline({ ).pipe(scan(accumulateBuffer, [])); // endregion - return { buffer$, contentHeight$, windowScrollTo$ }; + return { buffer$, contentHeight$, scrollAction$: scrollAction$ }; } diff --git a/src/utilites.ts b/src/utilites.ts index 953d121..5323c7c 100644 --- a/src/utilites.ts +++ b/src/utilites.ts @@ -58,3 +58,35 @@ export function useObservable(observable: Observable): Readonly> { return valueRef as Readonly>; } + +export function getVerticalScrollParent( + element: Element, + includeHidden: boolean = false +): Element { + const style = getComputedStyle(element); + const excludeStaticParent = style.position === "absolute"; + const overflowRegex = includeHidden + ? /(auto|scroll|hidden)/ + : /(auto|scroll)/; + + if (style.position === "fixed") { + return document.body; + } + + for ( + let parent: Element | null = element; + (parent = parent.parentElement); + + ) { + const parentStyle = getComputedStyle(parent); + + if (excludeStaticParent && parentStyle.position === "static") { + continue; + } + + if (overflowRegex.test(parentStyle.overflow + parentStyle.overflowY)) + return parent; + } + + return document.scrollingElement || document.documentElement; +}