Skip to content
This repository was archived by the owner on Nov 5, 2023. It is now read-only.

Commit d3f0db6

Browse files
authored
feat: support non-window scroll parent regarding the scrollTo prop (#183)
1 parent 5ee62b9 commit d3f0db6

File tree

3 files changed

+59
-19
lines changed

3 files changed

+59
-19
lines changed

src/Grid.vue

+4-4
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import {
4747
fromWindowScroll,
4848
useObservable,
4949
} from "./utilites";
50-
import { PageProvider, pipeline } from "./pipeline";
50+
import { PageProvider, pipeline, ScrollAction } from "./pipeline";
5151
import { once } from "ramda";
5252
import { VueInstance } from "@vueuse/core";
5353
@@ -95,7 +95,7 @@ export default defineComponent({
9595
const {
9696
buffer$, // the items in the current scanning window
9797
contentHeight$, // the height of the whole list
98-
windowScrollTo$, // the value sent to window.scrollTo()
98+
scrollAction$, // the value sent to window.scrollTo()
9999
} = pipeline({
100100
// streams of prop
101101
length$: fromProp(props, "length"),
@@ -113,8 +113,8 @@ export default defineComponent({
113113
114114
onUpdated(
115115
once(() => {
116-
windowScrollTo$.subscribe((next) => {
117-
window.scrollTo({ top: next, behavior: "smooth" });
116+
scrollAction$.subscribe(([el, top]: ScrollAction) => {
117+
el.scrollTo({ top, behavior: "smooth" });
118118
});
119119
})
120120
);

src/pipeline.ts

+23-15
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
shareReplay,
1616
switchMap,
1717
take,
18-
withLatestFrom,
1918
} from "rxjs";
2019
import {
2120
__,
@@ -35,6 +34,7 @@ import {
3534
without,
3635
zip,
3736
} from "ramda";
37+
import { getVerticalScrollParent } from "./utilites";
3838

3939
export function computeHeightAboveWindowOf(el: Element): number {
4040
const top = el.getBoundingClientRect().top;
@@ -228,10 +228,12 @@ interface PipelineInput {
228228
scrollTo$: Observable<number | undefined>;
229229
}
230230

231+
export type ScrollAction = [Element, number];
232+
231233
interface PipelineOutput {
232234
buffer$: Observable<InternalItem[]>;
233235
contentHeight$: Observable<number>;
234-
windowScrollTo$: Observable<number>;
236+
scrollAction$: Observable<ScrollAction>;
235237
}
236238

237239
export function pipeline({
@@ -262,20 +264,26 @@ export function pipeline({
262264
// endregion
263265

264266
// region: scroll to a given item by index
265-
const windowScrollTo$ = scrollTo$.pipe(
267+
const scrollAction$: Observable<ScrollAction> = scrollTo$.pipe(
266268
filter(complement(isNil)),
267-
switchMap((scrollTo) =>
268-
combineLatest([of(scrollTo), resizeMeasurement$, rootResize$]).pipe(
269-
take(1)
270-
)
269+
switchMap<number, Observable<[number, ResizeMeasurement, Element]>>(
270+
(scrollTo) =>
271+
combineLatest([of(scrollTo), resizeMeasurement$, rootResize$]).pipe(
272+
take(1)
273+
)
271274
),
272-
map(
273-
([scrollTo, { columns, itemHeightWithGap }, rootEl]) =>
274-
// The offset within the grid
275-
Math.floor(scrollTo / columns) * itemHeightWithGap +
276-
// The offset of grid root to the document
277-
(rootEl.getBoundingClientRect().top +
278-
document.documentElement.scrollTop)
275+
map<[number, ResizeMeasurement, Element], ScrollAction>(
276+
([scrollTo, { columns, itemHeightWithGap }, rootEl]) => {
277+
const verticalScrollEl = getVerticalScrollParent(rootEl);
278+
279+
const scrollTop =
280+
// The offset within the grid container
281+
Math.floor((scrollTo - 1) / columns) * itemHeightWithGap +
282+
// Offset to the offsetParent
283+
(rootEl instanceof HTMLElement ? rootEl.offsetTop : 0);
284+
285+
return [verticalScrollEl, scrollTop];
286+
}
279287
)
280288
);
281289
// endregion
@@ -319,5 +327,5 @@ export function pipeline({
319327
).pipe(scan(accumulateBuffer, []));
320328
// endregion
321329

322-
return { buffer$, contentHeight$, windowScrollTo$ };
330+
return { buffer$, contentHeight$, scrollAction$: scrollAction$ };
323331
}

src/utilites.ts

+32
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,35 @@ export function useObservable<H>(observable: Observable<H>): Readonly<Ref<H>> {
5858

5959
return valueRef as Readonly<Ref<H>>;
6060
}
61+
62+
export function getVerticalScrollParent(
63+
element: Element,
64+
includeHidden: boolean = false
65+
): Element {
66+
const style = getComputedStyle(element);
67+
const excludeStaticParent = style.position === "absolute";
68+
const overflowRegex = includeHidden
69+
? /(auto|scroll|hidden)/
70+
: /(auto|scroll)/;
71+
72+
if (style.position === "fixed") {
73+
return document.body;
74+
}
75+
76+
for (
77+
let parent: Element | null = element;
78+
(parent = parent.parentElement);
79+
80+
) {
81+
const parentStyle = getComputedStyle(parent);
82+
83+
if (excludeStaticParent && parentStyle.position === "static") {
84+
continue;
85+
}
86+
87+
if (overflowRegex.test(parentStyle.overflow + parentStyle.overflowY))
88+
return parent;
89+
}
90+
91+
return document.scrollingElement || document.documentElement;
92+
}

0 commit comments

Comments
 (0)