ant-design-vue/components/vc-virtual-list/hooks/useScrollTo.tsx

116 lines
3.3 KiB
Vue
Raw Normal View History

2021-06-26 01:35:40 +00:00
import type { Data } from '../../_util/type';
import type { ComputedRef, Ref } from 'vue';
import type { RafFrame } from '../../_util/raf';
2020-09-28 11:14:00 +00:00
import raf from '../../_util/raf';
2021-06-26 01:35:40 +00:00
import type { GetKey } from '../interface';
2020-09-28 11:14:00 +00:00
export default function useScrollTo(
2020-10-01 09:20:10 +00:00
containerRef: Ref<Element | undefined>,
2021-06-22 07:33:11 +00:00
mergedData: ComputedRef<any[]>,
heights: Ref<Data>,
2020-09-29 07:16:56 +00:00
props,
2020-10-01 09:20:10 +00:00
getKey: GetKey,
collectHeight: () => void,
syncScrollTop: (newTop: number) => void,
2020-11-04 10:59:08 +00:00
triggerFlash: () => void,
2020-09-28 11:14:00 +00:00
) {
let scroll: RafFrame = null;
2020-09-28 11:14:00 +00:00
2020-11-04 10:59:08 +00:00
return (arg?: any) => {
// When not argument provided, we think dev may want to show the scrollbar
if (arg === null || arg === undefined) {
triggerFlash();
return;
}
// Normal scroll logic
2020-10-01 09:20:10 +00:00
raf.cancel(scroll!);
2021-06-22 07:33:11 +00:00
const data = mergedData.value;
2020-09-29 07:16:56 +00:00
const itemHeight = props.itemHeight;
2020-09-28 11:14:00 +00:00
if (typeof arg === 'number') {
syncScrollTop(arg);
} else if (arg && typeof arg === 'object') {
2020-10-01 09:20:10 +00:00
let index: number;
2020-09-28 11:14:00 +00:00
const { align } = arg;
if ('index' in arg) {
({ index } = arg);
} else {
2020-10-01 09:20:10 +00:00
index = data.findIndex((item: object) => getKey(item) === arg.key);
2020-09-28 11:14:00 +00:00
}
const { offset = 0 } = arg;
// We will retry 3 times in case dynamic height shaking
2020-10-01 09:20:10 +00:00
const syncScroll = (times: number, targetAlign?: 'top' | 'bottom') => {
if (times < 0 || !containerRef.value) return;
2020-09-28 11:14:00 +00:00
2020-10-01 09:20:10 +00:00
const height = containerRef.value.clientHeight;
2020-09-28 11:14:00 +00:00
let needCollectHeight = false;
let newTargetAlign = targetAlign;
// Go to next frame if height not exist
if (height) {
const mergedAlign = targetAlign || align;
// Get top & bottom
let stackTop = 0;
let itemTop = 0;
let itemBottom = 0;
2021-06-22 07:33:11 +00:00
const maxLen = Math.min(data.length, index);
for (let i = 0; i <= maxLen; i += 1) {
2020-09-28 11:14:00 +00:00
const key = getKey(data[i]);
itemTop = stackTop;
const cacheHeight = heights.value[key!];
2020-09-28 11:14:00 +00:00
itemBottom = itemTop + (cacheHeight === undefined ? itemHeight : cacheHeight);
stackTop = itemBottom;
if (i === index && cacheHeight === undefined) {
needCollectHeight = true;
}
}
// Scroll to
2020-10-01 09:20:10 +00:00
let targetTop: number | null = null;
2020-09-28 11:14:00 +00:00
switch (mergedAlign) {
case 'top':
targetTop = itemTop - offset;
break;
case 'bottom':
targetTop = itemBottom - height + offset;
break;
default: {
2020-10-01 09:20:10 +00:00
const { scrollTop } = containerRef.value;
2020-09-28 11:14:00 +00:00
const scrollBottom = scrollTop + height;
if (itemTop < scrollTop) {
newTargetAlign = 'top';
} else if (itemBottom > scrollBottom) {
newTargetAlign = 'bottom';
}
}
}
2020-10-01 09:20:10 +00:00
if (targetTop !== null && targetTop !== containerRef.value.scrollTop) {
2020-09-28 11:14:00 +00:00
syncScrollTop(targetTop);
}
}
// We will retry since element may not sync height as it described
scroll = raf(() => {
if (needCollectHeight) {
collectHeight();
}
syncScroll(times - 1, newTargetAlign);
});
};
syncScroll(3);
}
};
}