From 823165c166d3ac6321eed6f312086b4937b7f02e Mon Sep 17 00:00:00 2001 From: danranvm Date: Thu, 29 Sep 2022 19:16:10 +0800 Subject: [PATCH] fix(comp:table): the scroll body is incorrect, when virtual enabled --- package.json | 2 +- .../cdk/scroll/src/virtual/VirtualScroll.tsx | 2 +- .../scroll/src/virtual/contents/Holder.tsx | 11 +- packages/components/table/docs/Api.zh.md | 2 +- packages/components/table/src/Table.tsx | 2 +- .../table/src/composables/useScroll.ts | 149 ++++++++++++------ .../src/composables/useScrollOnChange.ts | 31 ++-- .../components/table/src/main/MainTable.tsx | 3 +- .../components/transfer/src/list/List.tsx | 2 +- packages/components/tree/src/Tree.tsx | 2 +- 10 files changed, 121 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index 21235190c..abd70f8ed 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "tslib": "^2.4.0", "typescript": "^4.7.4", "unplugin-vue-components": "^0.22.4", - "vite": "^3.1.0", + "vite": "3.1.3", "vitest": "^0.21.1", "vue": "^3.2.39", "yaml-front-matter": "^4.1.1" diff --git a/packages/cdk/scroll/src/virtual/VirtualScroll.tsx b/packages/cdk/scroll/src/virtual/VirtualScroll.tsx index 6abdb8be1..1977dae33 100644 --- a/packages/cdk/scroll/src/virtual/VirtualScroll.tsx +++ b/packages/cdk/scroll/src/virtual/VirtualScroll.tsx @@ -70,7 +70,7 @@ export default defineComponent({ }) const scrollTo = useScrollTo(props, holderRef, getKey, heights, collectHeights, syncScrollTop) - expose({ scrollTo }) + expose({ scrollTo, holderRef }) const mergedData = computed(() => props.dataSource.slice(startIndex.value, endIndex.value + 1)) watch(mergedData, data => callEmit(props.onScrolledChange, startIndex.value, endIndex.value, data)) diff --git a/packages/cdk/scroll/src/virtual/contents/Holder.tsx b/packages/cdk/scroll/src/virtual/contents/Holder.tsx index 625d765ef..9d5e21536 100644 --- a/packages/cdk/scroll/src/virtual/contents/Holder.tsx +++ b/packages/cdk/scroll/src/virtual/contents/Holder.tsx @@ -10,6 +10,7 @@ import { type CSSProperties, computed, defineComponent, inject, onBeforeUnmount, import { isString, throttle } from 'lodash-es' import { offResize, onResize } from '@idux/cdk/resize' +import { convertCssPixel } from '@idux/cdk/utils' import { virtualScrollToken } from '../token' @@ -28,7 +29,7 @@ export default defineComponent({ const style = computed(() => { const { height, fullHeight } = props - if (!height) { + if (!height || height <= 0) { return undefined } @@ -36,13 +37,7 @@ export default defineComponent({ return { height } } - /* eslint-disable indent */ - return height <= 0 - ? undefined - : { - [fullHeight ? 'height' : 'maxHeight']: height + 'px', - } - /* eslint-enable indent */ + return { [fullHeight ? 'height' : 'maxHeight']: convertCssPixel(height) } }) const fillerStyle = computed(() => { diff --git a/packages/components/table/docs/Api.zh.md b/packages/components/table/docs/Api.zh.md index 4fc4daf9e..01a1bfcc5 100644 --- a/packages/components/table/docs/Api.zh.md +++ b/packages/components/table/docs/Api.zh.md @@ -179,7 +179,7 @@ export type TablePaginationPosition = 'topStart' | 'top' | 'topEnd' | 'bottomSta | 名称 | 说明 | 参数类型 | 备注 | | --- | --- | --- | --- | -| `scrollTo` | 滚动到指定位置 | `(option?: number \| VirtualScrollToOptions) => void` | 仅 `virtual` 模式下可用 | +| `scrollTo` | 滚动到指定高度或位置 | `(option?: number \| VirtualScrollToOptions) => void` | `VirtualScrollToOptions` 参数仅 `virtual` 模式下可用 | ### IxTableColumn diff --git a/packages/components/table/src/Table.tsx b/packages/components/table/src/Table.tsx index a33d33523..dec799eef 100644 --- a/packages/components/table/src/Table.tsx +++ b/packages/components/table/src/Table.tsx @@ -71,7 +71,7 @@ export default defineComponent({ ) const selectableContext = useSelectable(props, locale, columnsContext.flattedColumns, dataContext) - useScrollOnChange(props, config, scrollContext.scrollBodyRef, mergedPagination, activeSorters, activeFilters) + useScrollOnChange(props, config, mergedPagination, activeSorters, activeFilters, scrollContext.scrollTo) const context = { props, diff --git a/packages/components/table/src/composables/useScroll.ts b/packages/components/table/src/composables/useScroll.ts index 92a030af1..250a544ee 100644 --- a/packages/components/table/src/composables/useScroll.ts +++ b/packages/components/table/src/composables/useScroll.ts @@ -5,10 +5,10 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import { type ComputedRef, type Ref, computed, onBeforeUnmount, ref } from 'vue' +import { type ComputedRef, type Ref, computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { useResizeObserver } from '@idux/cdk/resize' -import { type VirtualScrollInstance, type VirtualScrollToFn, getScrollBarSize } from '@idux/cdk/scroll' +import { type VirtualScrollInstance, type VirtualScrollToFn, getScrollBarSize, scrollToTop } from '@idux/cdk/scroll' import { Logger, convertCssPixel, convertElement } from '@idux/cdk/utils' import { type TableProps } from '../types' @@ -19,8 +19,25 @@ export function useScroll( mergedAutoHeight: ComputedRef, { setStickyScrollLeft }: StickyContext, ): ScrollContext { - const { scrollHeadRef, scrollBodyRef, scrollContentRef, scrollFootRef, handleScroll, pingedStart, pingedEnd } = - useScrollRef(setStickyScrollLeft) + const virtualScrollRef = ref() + const scrollHeadRef = ref() + const scrollBodyRef = ref() + const scrollContentRef = ref() + const scrollFootRef = ref() + + watch(virtualScrollRef, instance => { + if (instance) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + scrollBodyRef.value = (instance as any).holderRef.value + } + }) + + const { handleScroll, pingedStart, pingedEnd } = useScrollRef( + scrollHeadRef, + scrollBodyRef, + scrollFootRef, + setStickyScrollLeft, + ) __DEV__ && props.scroll?.x && @@ -30,33 +47,34 @@ export function useScroll( props.scroll?.y && Logger.warn('components/table', '`scroll.y` was deprecated, please use `scroll.height` instead') - const scrollWithAutoHeight = ref(mergedAutoHeight.value) - const calcScrollWithAutoHeight = () => { - const bodyEl = convertElement(scrollBodyRef.value) - if (!mergedAutoHeight.value || !bodyEl) { - scrollWithAutoHeight.value = false - } else { - scrollWithAutoHeight.value = props.virtual || bodyEl.scrollHeight > bodyEl.clientHeight - } - } - useResizeObserver(scrollBodyRef, calcScrollWithAutoHeight) - useResizeObserver(scrollContentRef, calcScrollWithAutoHeight) - + const scrollWithAutoHeight = useScrollWithAutoHeight(props, mergedAutoHeight, scrollBodyRef, scrollContentRef) const scrollWidth = computed(() => convertCssPixel(props.scroll?.width || props.scroll?.x)) - const scrollHeight = computed( - () => convertCssPixel(props.scroll?.height || props.scroll?.y) || (scrollWithAutoHeight.value ? 'auto' : ''), - ) + const scrollHeight = computed(() => { + let height = convertCssPixel(props.scroll?.height || props.scroll?.y) + if (!height && mergedAutoHeight.value && (props.virtual || scrollWithAutoHeight.value)) { + height = 'auto' + } + return height + }) const scrollBarSize = computed(() => getScrollBarSize(convertElement(scrollBodyRef))) const scrollBarSizeOnFixedHolder = computed(() => (scrollHeight.value ? scrollBarSize.value : 0)) const scrollTo: VirtualScrollToFn = options => { if (props.virtual) { - return (scrollBodyRef.value as unknown as VirtualScrollInstance)?.scrollTo(options) + virtualScrollRef.value?.scrollTo(options) + } else { + if (typeof options === 'number') { + scrollToTop({ target: convertElement(scrollBodyRef), top: options, duration: 200 }) + } else { + __DEV__ && + Logger.warn('components/table', 'the scrollTo argument must be a number, when virtual is not enabled.') + } } } return { + virtualScrollRef, scrollHeadRef, scrollBodyRef, scrollContentRef, @@ -73,9 +91,10 @@ export function useScroll( } export interface ScrollContext { + virtualScrollRef: Ref scrollHeadRef: Ref scrollBodyRef: Ref - scrollContentRef: Ref + scrollContentRef: Ref scrollFootRef: Ref handleScroll: (evt?: Event, scrollLeft?: number) => void scrollTo: VirtualScrollToFn @@ -92,26 +111,15 @@ export interface ScrollOptions { scrollLeft?: number } -function useScrollRef(setStickyScrollLeft: (value: number) => void) { - const scrollHeadRef = ref() - const scrollBodyRef = ref() - const scrollContentRef = ref() - const scrollFootRef = ref() - - const changeStickyScrollLeft = (scrollLeft: number) => { - const scrollBodyElement = convertElement(scrollBodyRef) - if (!scrollBodyElement) { - return - } - const { clientWidth, scrollWidth } = scrollBodyElement - setStickyScrollLeft((scrollLeft / scrollWidth) * clientWidth || 0) - } - +function useScrollRef( + scrollHeadRef: Ref, + scrollBodyRef: Ref, + scrollFootRef: Ref, + setStickyScrollLeft: (value: number) => void, +) { const pingedStart = ref(false) const pingedEnd = ref(false) - const lockedScrollTargetRef = ref() - let timeout: number | undefined const clearTimer = () => { @@ -121,6 +129,10 @@ function useScrollRef(setStickyScrollLeft: (value: number) => void) { } } + onBeforeUnmount(() => clearTimer()) + + const lockedScrollTargetRef = ref() + const lockScrollTarget = (target: HTMLElement | undefined) => { lockedScrollTargetRef.value = target clearTimer() @@ -131,8 +143,6 @@ function useScrollRef(setStickyScrollLeft: (value: number) => void) { }, 100) } - onBeforeUnmount(() => clearTimer()) - const forceScroll = (scrollLeft: number, target: HTMLElement | undefined) => { if (!target) { return @@ -142,6 +152,15 @@ function useScrollRef(setStickyScrollLeft: (value: number) => void) { } } + const changeStickyScrollLeft = (scrollLeft: number) => { + const scrollBodyElement = convertElement(scrollBodyRef) + if (!scrollBodyElement) { + return + } + const { clientWidth, scrollWidth } = scrollBodyElement + setStickyScrollLeft((scrollLeft / scrollWidth) * clientWidth || 0) + } + const handleScroll = (evt?: Event, scrollLeft?: number) => { const currentTarget = evt?.currentTarget as HTMLElement | undefined const mergedScrollLeft = scrollLeft ?? currentTarget!.scrollLeft @@ -162,13 +181,47 @@ function useScrollRef(setStickyScrollLeft: (value: number) => void) { } } - return { - scrollHeadRef, - scrollBodyRef, - scrollContentRef, - scrollFootRef, - handleScroll, - pingedStart, - pingedEnd, + return { handleScroll, pingedStart, pingedEnd } +} + +// 针对非虚拟滚动场景,需要判断 scrollHeight 和 clientHeight +function useScrollWithAutoHeight( + props: TableProps, + mergedAutoHeight: ComputedRef, + scrollBodyRef: Ref, + scrollContentRef: Ref, +) { + const scrollWithAutoHeight = ref(true) + + const calcScrollWithAutoHeight = () => { + const bodyEl = convertElement(scrollBodyRef.value) + if (!bodyEl) { + scrollWithAutoHeight.value = false + } else { + scrollWithAutoHeight.value = bodyEl.scrollHeight > bodyEl.clientHeight + } } + + let stopResizeObservers: Array<() => void> = [] + const stopHandler = () => stopResizeObservers.forEach(stop => stop()) + + onMounted(() => { + watch( + [mergedAutoHeight, () => props.virtual], + ([autoHeight, virtual]) => { + stopHandler() + if (autoHeight && !virtual) { + stopResizeObservers = [ + useResizeObserver(scrollBodyRef, calcScrollWithAutoHeight).stop, + useResizeObserver(scrollContentRef, calcScrollWithAutoHeight).stop, + ] + } + }, + { immediate: true }, + ) + }) + + onBeforeUnmount(() => stopHandler()) + + return scrollWithAutoHeight } diff --git a/packages/components/table/src/composables/useScrollOnChange.ts b/packages/components/table/src/composables/useScrollOnChange.ts index df006eff3..0e1ee3f81 100644 --- a/packages/components/table/src/composables/useScrollOnChange.ts +++ b/packages/components/table/src/composables/useScrollOnChange.ts @@ -10,40 +10,27 @@ import type { ActiveFilter } from './useFilterable' import type { ActiveSorter } from './useSortable' import type { TableConfig } from '@idux/components/config' -import { type ComputedRef, type Ref, type WatchStopHandle, computed, watch } from 'vue' +import { type ComputedRef, type WatchStopHandle, computed, watch } from 'vue' -import { type VirtualScrollInstance, scrollToTop } from '@idux/cdk/scroll' +import { type VirtualScrollToFn } from '@idux/cdk/scroll' export function useScrollOnChange( props: TableProps, config: TableConfig, - scrollBodyRef: Ref, mergedPagination: ComputedRef, activeSorters: ComputedRef, activeFilters: ComputedRef, + scrollTo: VirtualScrollToFn, ): void { const mergedScrollToTopOnChange = computed(() => props.scrollToTopOnChange ?? config.scrollToTopOnChange) - let stopOnChangeWatch: WatchStopHandle | undefined - const startOnChangeWatch = () => { - stopOnChangeWatch = watch( + const startOnChangeWatch = () => + watch( [() => mergedPagination.value?.pageIndex, () => mergedPagination.value?.pageSize, activeSorters, activeFilters], - () => { - if (!scrollBodyRef.value) { - return - } - - if (props.virtual) { - ;(scrollBodyRef.value as VirtualScrollInstance).scrollTo(0) - } else { - scrollToTop({ - target: scrollBodyRef.value as HTMLElement, - top: 0, - }) - } - }, + () => scrollTo(0), ) - } + + let stopOnChangeWatch: WatchStopHandle | undefined watch( mergedScrollToTopOnChange, @@ -51,7 +38,7 @@ export function useScrollOnChange( stopOnChangeWatch?.() if (scrollToTopOnChange) { - startOnChangeWatch() + stopOnChangeWatch = startOnChangeWatch() } }, { immediate: true }, diff --git a/packages/components/table/src/main/MainTable.tsx b/packages/components/table/src/main/MainTable.tsx index 8298a9188..24a2b90d4 100644 --- a/packages/components/table/src/main/MainTable.tsx +++ b/packages/components/table/src/main/MainTable.tsx @@ -50,6 +50,7 @@ export default defineComponent({ flattedData, isSticky, mergedSticky, + virtualScrollRef, scrollBodyRef, scrollContentRef, handleScroll, @@ -184,7 +185,7 @@ export default defineComponent({ children.push( virtualScrollRef.value?.scrollTo(...params), + scrollTo: option => virtualScrollRef.value?.scrollTo(option), } expose(transferListApi) diff --git a/packages/components/tree/src/Tree.tsx b/packages/components/tree/src/Tree.tsx index 01ebf2fc8..5281ac9a0 100644 --- a/packages/components/tree/src/Tree.tsx +++ b/packages/components/tree/src/Tree.tsx @@ -142,7 +142,7 @@ export default defineComponent({ inputRef?.value?.blur() } const scrollTo = (option?: number | VirtualScrollToOptions) => { - virtualScrollRef?.value?.scrollTo(option) + virtualScrollRef.value?.scrollTo(option) } const getNode = (key: VKey) => {