From 50a1b6f8921b0df852d30efc86f4331340b73ebb Mon Sep 17 00:00:00 2001 From: jeremywu <15975785+JeremyWuuuuu@users.noreply.github.com> Date: Sun, 26 Sep 2021 19:22:56 +0800 Subject: [PATCH] feat(components): [el-virtualized-grid] fulfillment (#3612) * feat(components): [el-virtualized-grid] fulfillment - Add scrollabr and scroll handler to v-grid * Address comments and code perfection * Update render function * fix linter * address comments --- .../__tests__/dynamic-size-grid.spec.ts | 10 +- .../__tests__/fixed-size-grid.spec.ts | 14 +- .../virtual-list/__tests__/scrollbar.spec.ts | 2 +- .../builders/{buildGrid.ts => build-grid.ts} | 257 ++++++++++++------ .../builders/{buildList.ts => build-list.ts} | 6 +- .../src/components/dynamic-size-grid.ts | 7 +- .../src/components/dynamic-size-list.ts | 6 +- .../src/components/fixed-size-grid.ts | 9 +- .../src/components/fixed-size-list.ts | 15 +- .../src/hooks/{useCache.ts => use-cache.ts} | 0 .../virtual-list/src/hooks/use-grid-wheel.ts | 61 +++++ .../src/hooks/{useWheel.ts => use-wheel.ts} | 10 +- packages/components/virtual-list/src/props.ts | 22 +- packages/components/virtual-list/src/types.ts | 35 ++- packages/theme-chalk/src/virtual-list.scss | 6 + 15 files changed, 307 insertions(+), 153 deletions(-) rename packages/components/virtual-list/src/builders/{buildGrid.ts => build-grid.ts} (68%) rename packages/components/virtual-list/src/builders/{buildList.ts => build-list.ts} (99%) rename packages/components/virtual-list/src/hooks/{useCache.ts => use-cache.ts} (100%) create mode 100644 packages/components/virtual-list/src/hooks/use-grid-wheel.ts rename packages/components/virtual-list/src/hooks/{useWheel.ts => use-wheel.ts} (87%) diff --git a/packages/components/virtual-list/__tests__/dynamic-size-grid.spec.ts b/packages/components/virtual-list/__tests__/dynamic-size-grid.spec.ts index 8856238806..6193b531a5 100644 --- a/packages/components/virtual-list/__tests__/dynamic-size-grid.spec.ts +++ b/packages/components/virtual-list/__tests__/dynamic-size-grid.spec.ts @@ -1,4 +1,4 @@ -import { nextTick } from 'vue' +import { nextTick, unref } from 'vue' import makeMount from '@element-plus/test-utils/make-mount' import makeScroll from '@element-plus/test-utils/make-scroll' import setupMock from '../setup-mock' @@ -96,12 +96,12 @@ describe('', () => { expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(36) const gridRef = wrapper.vm.$refs.gridRef as GridRef - makeScroll(gridRef.windowRef, 'scrollTop', 100) + makeScroll(unref(gridRef.windowRef), 'scrollTop', 100) await nextTick() // 8 x 5 grid expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(40) - makeScroll(gridRef.windowRef, 'scrollLeft', 100) + makeScroll(unref(gridRef.windowRef), 'scrollLeft', 100) await nextTick() expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(64) }) @@ -112,11 +112,11 @@ describe('', () => { expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(36) const gridRef = wrapper.vm.$refs.gridRef as GridRef - makeScroll(gridRef.windowRef, 'scrollTop', 0) + makeScroll(unref(gridRef.windowRef), 'scrollTop', 0) await nextTick() expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(36) - makeScroll(gridRef.windowRef, 'scrollLeft', 0) + makeScroll(unref(gridRef.windowRef), 'scrollLeft', 0) await nextTick() expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(36) }) diff --git a/packages/components/virtual-list/__tests__/fixed-size-grid.spec.ts b/packages/components/virtual-list/__tests__/fixed-size-grid.spec.ts index ec10983046..099896c61b 100644 --- a/packages/components/virtual-list/__tests__/fixed-size-grid.spec.ts +++ b/packages/components/virtual-list/__tests__/fixed-size-grid.spec.ts @@ -1,4 +1,4 @@ -import { nextTick } from 'vue' +import { nextTick, unref } from 'vue' import makeMount from '@element-plus/test-utils/make-mount' import makeScroll from '@element-plus/test-utils/make-scroll' import setupMock from '../setup-mock' @@ -71,8 +71,8 @@ describe('', () => { expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(24) const gridRef = wrapper.vm.$refs.gridRef as GridRef - expect(gridRef.innerRef.style.height).toBe('2500px') - expect(gridRef.innerRef.style.width).toBe('5000px') + expect(unref(gridRef.innerRef).style.height).toBe('2500px') + expect(unref(gridRef.innerRef).style.width).toBe('5000px') }) it('should render zero row zero column', async () => { @@ -96,11 +96,11 @@ describe('', () => { const gridRef = wrapper.vm.$refs.gridRef as GridRef - makeScroll(gridRef.windowRef, 'scrollTop', 100) + makeScroll(unref(gridRef.windowRef), 'scrollTop', 100) await nextTick() expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(21) - makeScroll(gridRef.windowRef, 'scrollLeft', 100) + makeScroll(unref(gridRef.windowRef), 'scrollLeft', 100) await nextTick() // 5 (backward cache 1 + visible 2 + forward cache 2) // * 7 (backward cache 1 + visible 4 + forward cache 2) @@ -114,11 +114,11 @@ describe('', () => { expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(24) const gridRef = wrapper.vm.$refs.gridRef as GridRef - makeScroll(gridRef.windowRef, 'scrollTop', 0) + makeScroll(unref(gridRef.windowRef), 'scrollTop', 0) await nextTick() expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(24) - makeScroll(gridRef.windowRef, 'scrollLeft', 0) + makeScroll(unref(gridRef.windowRef), 'scrollLeft', 0) await nextTick() expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(24) }) diff --git a/packages/components/virtual-list/__tests__/scrollbar.spec.ts b/packages/components/virtual-list/__tests__/scrollbar.spec.ts index dcc0618107..b7d4608e00 100644 --- a/packages/components/virtual-list/__tests__/scrollbar.spec.ts +++ b/packages/components/virtual-list/__tests__/scrollbar.spec.ts @@ -6,7 +6,7 @@ import { ScrollbarDirKey } from '../src/defaults' describe('virtual scrollbar', () => { async function testInlineStyle(layout = 'vertical') { const wrapper = mount({ - template: ``, + template: ``, components: { Scrollbar, }, diff --git a/packages/components/virtual-list/src/builders/buildGrid.ts b/packages/components/virtual-list/src/builders/build-grid.ts similarity index 68% rename from packages/components/virtual-list/src/builders/buildGrid.ts rename to packages/components/virtual-list/src/builders/build-grid.ts index 8268b696a5..aeff56acdd 100644 --- a/packages/components/virtual-list/src/builders/buildGrid.ts +++ b/packages/components/virtual-list/src/builders/build-grid.ts @@ -16,7 +16,9 @@ import { isNumber, isString } from '@element-plus/utils/util' import isServer from '@element-plus/utils/isServer' import getScrollBarWidth from '@element-plus/utils/scrollbar-width' -import { useCache } from '../hooks/useCache' +import Scrollbar from '../components/scrollbar' +import { useGridWheel } from '../hooks/use-grid-wheel' +import { useCache } from '../hooks/use-cache' import { virtualizedGridProps } from '../props' import { getScrollDir, getRTLOffsetType, isRTL } from '../utils' import { @@ -31,8 +33,9 @@ import { RTL_OFFSET_POS_ASC, } from '../defaults' -import type { CSSProperties, Slot, VNode, VNodeChild } from 'vue' -import type { GridConstructorProps, Alignment } from '../types' +import type { CSSProperties, VNode, VNodeChild } from 'vue' +import type { StyleValue } from '@element-plus/utils/types' +import type { GridConstructorProps, Alignment, ScrollbarExpose } from '../types' import type { VirtualizedGridProps } from '../props' const createGrid = ({ @@ -56,7 +59,7 @@ const createGrid = ({ name: name ?? 'ElVirtualList', props: virtualizedGridProps, emits: [ITEM_RENDER_EVT, SCROLL_EVT], - setup(props, { emit, expose }) { + setup(props, { emit, expose, slots }) { validateProps(props) const instance = getCurrentInstance()! const cache = ref(initCache(props, instance)) @@ -65,6 +68,8 @@ const createGrid = ({ // or user defined component type, depends on the type passed // by user const windowRef = ref() + const hScrollbar = ref() + const vScrollbar = ref() // innerRef is the actual container element which contains all the elements const innerRef = ref(null) const states = ref({ @@ -79,6 +84,8 @@ const createGrid = ({ const getItemStyleCache = useCache() // computed + const parsedHeight = computed(() => parseInt(`${props.height}`, 10)) + const parsedWidth = computed(() => parseInt(`${props.width}`, 10)) const columnsToRender = computed(() => { const { totalColumn, totalRow, columnCache } = props const { isScrolling, xAxisScrollDir, scrollLeft } = unref(states) @@ -158,10 +165,10 @@ const createGrid = ({ getEstimatedTotalWidth(props, unref(cache)) ) - const windowStyle = computed(() => [ + const windowStyle = computed(() => [ { position: 'relative', - overflow: 'auto', + overflow: 'hidden', WebkitOverflowScrolling: 'touch', willChange: 'transform', }, @@ -170,7 +177,7 @@ const createGrid = ({ height: isNumber(props.height) ? `${props.height}px` : props.height, width: isNumber(props.width) ? `${props.width}px` : props.width, }, - props.style, + props.style ?? {}, ]) const innerStyle = computed(() => { @@ -279,7 +286,57 @@ const createGrid = ({ emitEvents() } - const scrollTo = ({ scrollLeft, scrollTop }) => { + const onVerticalScroll = (distance: number, totalSteps: number) => { + const height = unref(parsedHeight) + const offset = + ((estimatedTotalHeight.value - height) / totalSteps) * distance + scrollTo({ + scrollTop: Math.min(estimatedTotalHeight.value - height, offset), + }) + } + + const onHorizontalScroll = (distance: number, totalSteps: number) => { + const width = unref(parsedWidth) + const offset = + ((estimatedTotalWidth.value - width) / totalSteps) * distance + scrollTo({ + scrollLeft: Math.min(estimatedTotalWidth.value - width, offset), + }) + } + + const { onWheel } = useGridWheel( + { + atXStartEdge: computed(() => states.value.scrollLeft <= 0), + atXEndEdge: computed( + () => states.value.scrollLeft >= estimatedTotalWidth.value + ), + atYStartEdge: computed(() => states.value.scrollTop <= 0), + atYEndEdge: computed( + () => states.value.scrollTop >= estimatedTotalHeight.value + ), + }, + (x: number, y: number) => { + hScrollbar.value?.onMouseUp?.() + hScrollbar.value?.onMouseUp?.() + const width = unref(parsedWidth) + const height = unref(parsedHeight) + scrollTo({ + scrollLeft: Math.min( + states.value.scrollLeft + x, + estimatedTotalWidth.value - width + ), + scrollTop: Math.min( + states.value.scrollTop + y, + estimatedTotalHeight.value - height + ), + }) + } + ) + + const scrollTo = ({ + scrollLeft = states.value.scrollLeft, + scrollTop = states.value.scrollTop, + }) => { scrollLeft = Math.max(scrollLeft, 0) scrollTop = Math.max(scrollTop, 0) const _states = unref(states) @@ -434,20 +491,6 @@ const createGrid = ({ } }) - const api = { - windowStyle, - windowRef, - columnsToRender, - innerRef, - innerStyle, - states, - rowsToRender, - getItemStyle, - onScroll, - scrollTo, - scrollToItem, - } - expose({ windowRef, innerRef, @@ -457,77 +500,121 @@ const createGrid = ({ states, }) - return api - }, + // rendering part - render(ctx: any) { - const { - $slots, - className, - containerElement, - columnsToRender, - data, - getItemStyle, - innerElement, - innerStyle, - rowsToRender, - onScroll, - states, - useIsScrolling, - windowStyle, - totalColumn, - totalRow, - } = ctx + const renderScrollbars = () => { + const { totalColumn, totalRow } = props - const [columnStart, columnEnd] = columnsToRender - const [rowStart, rowEnd] = rowsToRender + const width = unref(parsedWidth) + const height = unref(parsedHeight) + const estimatedWidth = unref(estimatedTotalWidth) + const estimatedHeight = unref(estimatedTotalHeight) + const { scrollLeft, scrollTop } = unref(states) + const horizontalScrollbar = h(Scrollbar, { + ref: hScrollbar, + clientSize: width, + layout: 'horizontal', + onScroll: onHorizontalScroll, + ratio: (width * 100) / estimatedWidth, + scrollFrom: scrollLeft / (estimatedWidth - width), + total: totalRow, + visible: true, + }) - const Container = resolveDynamicComponent(containerElement) - const Inner = resolveDynamicComponent(innerElement) + const verticalScrollbar = h(Scrollbar, { + ref: vScrollbar, + clientSize: height, + layout: 'vertical', + onScroll: onVerticalScroll, + ratio: (height * 100) / estimatedHeight, + scrollFrom: scrollTop / (estimatedHeight - height), + total: totalColumn, + visible: true, + }) - const children: VNodeChild[] = [] - if (totalRow > 0 && totalColumn > 0) { - for (let row = rowStart; row <= rowEnd; row++) { - for (let column = columnStart; column <= columnEnd; column++) { - children.push( - ($slots.default as Slot)?.({ - columnIndex: column, - data, - key: column, - isScrolling: useIsScrolling ? states.isScrolling : undefined, - style: getItemStyle(row, column), - rowIndex: row, - }) - ) - } + return { + horizontalScrollbar, + verticalScrollbar, } } - const InnerNode = [ - h( - Inner as VNode, - { - style: innerStyle, - ref: 'innerRef', - }, - !isString(Inner) - ? { - default: () => children, - } - : children - ), - ] + const renderItems = () => { + const [columnStart, columnEnd] = unref(columnsToRender) + const [rowStart, rowEnd] = unref(rowsToRender) + const { data, totalColumn, totalRow, useIsScrolling } = props + const children: VNodeChild[] = [] + if (totalRow > 0 && totalColumn > 0) { + for (let row = rowStart; row <= rowEnd; row++) { + for (let column = columnStart; column <= columnEnd; column++) { + children.push( + slots.default?.({ + columnIndex: column, + data, + key: column, + isScrolling: useIsScrolling + ? unref(states).isScrolling + : undefined, + style: getItemStyle(row, column), + rowIndex: row, + }) + ) + } + } + } + return children + } - return h( - Container as VNode, - { - class: className, - style: windowStyle, - onScroll, - ref: 'windowRef', - }, - !isString(Container) ? { default: () => InnerNode } : InnerNode - ) + const renderInner = () => { + const Inner = resolveDynamicComponent(props.innerElement) as VNode + const children = renderItems() + return [ + h( + Inner, + { + style: unref(innerStyle), + ref: innerRef, + }, + !isString(Inner) + ? { + default: () => children, + } + : children + ), + ] + } + + const renderWindow = () => { + const Container = resolveDynamicComponent( + props.containerElement + ) as VNode + const { horizontalScrollbar, verticalScrollbar } = renderScrollbars() + const Inner = renderInner() + + return h( + 'div', + { + key: 0, + class: 'el-vg__wrapper', + }, + [ + h( + Container, + { + class: props.className, + style: unref(windowStyle), + onScroll, + onWheel, + ref: windowRef, + }, + !isString(Container) ? { default: () => Inner } : Inner + ), + horizontalScrollbar, + verticalScrollbar, + ] + ) + } + + return renderWindow }, }) } diff --git a/packages/components/virtual-list/src/builders/buildList.ts b/packages/components/virtual-list/src/builders/build-list.ts similarity index 99% rename from packages/components/virtual-list/src/builders/buildList.ts rename to packages/components/virtual-list/src/builders/build-list.ts index 7a7320c606..fe0680a0ae 100644 --- a/packages/components/virtual-list/src/builders/buildList.ts +++ b/packages/components/virtual-list/src/builders/build-list.ts @@ -15,8 +15,8 @@ import { hasOwn } from '@vue/shared' import { isNumber, isString } from '@element-plus/utils/util' import isServer from '@element-plus/utils/isServer' -import { useCache } from '../hooks/useCache' -import useWheel from '../hooks/useWheel' +import { useCache } from '../hooks/use-cache' +import useWheel from '../hooks/use-wheel' import Scrollbar from '../components/scrollbar' import { getScrollDir, isHorizontal, getRTLOffsetType } from '../utils' import { virtualizedListProps } from '../props' @@ -64,7 +64,7 @@ const createList = ({ // by user const windowRef = ref() const innerRef = ref() - const scrollbarRef = ref(null) + const scrollbarRef = ref() const states = ref({ isScrolling: false, diff --git a/packages/components/virtual-list/src/components/dynamic-size-grid.ts b/packages/components/virtual-list/src/components/dynamic-size-grid.ts index ecd65fe5d1..3a986d87af 100644 --- a/packages/components/virtual-list/src/components/dynamic-size-grid.ts +++ b/packages/components/virtual-list/src/components/dynamic-size-grid.ts @@ -1,6 +1,6 @@ import { isFunction } from '@vue/shared' import { throwError } from '@element-plus/utils/error' -import createGrid from '../builders/buildGrid' +import createGrid from '../builders/build-grid' import { AUTO_ALIGNMENT, @@ -10,15 +10,14 @@ import { SMART_ALIGNMENT, START_ALIGNMENT, } from '../defaults' -import type { DefaultGridProps } from '../defaults' +import type { VirtualizedGridProps } from '../props' -import type { ExtractPropTypes } from 'vue' import type { Alignment, GridCache, ListItem, ItemSize } from '../types' const { max, min, floor } = Math const SCOPE = 'ElDynamicSizeGrid' -type Props = ExtractPropTypes +type Props = VirtualizedGridProps type CacheItemType = 'column' | 'row' // generates props access key via type diff --git a/packages/components/virtual-list/src/components/dynamic-size-list.ts b/packages/components/virtual-list/src/components/dynamic-size-list.ts index 6ea44699a7..70c69bc39f 100644 --- a/packages/components/virtual-list/src/components/dynamic-size-list.ts +++ b/packages/components/virtual-list/src/components/dynamic-size-list.ts @@ -1,6 +1,6 @@ import { throwError } from '@element-plus/utils/error' -import createList from '../builders/buildList' +import createList from '../builders/build-list' import { isHorizontal } from '../utils' import { @@ -228,10 +228,10 @@ const DynamicSizeList = createList({ cache.clearCacheAfterIndex = (index: number, forceUpdate = true) => { cache.lastVisitedIndex = Math.min(cache.lastVisitedIndex, index - 1) - instance.exposed.getItemStyleCache(-1) + instance.exposed?.getItemStyleCache(-1) if (forceUpdate) { - instance.proxy.$forceUpdate() + instance.proxy?.$forceUpdate() } } diff --git a/packages/components/virtual-list/src/components/fixed-size-grid.ts b/packages/components/virtual-list/src/components/fixed-size-grid.ts index 3cf6817ed3..d99523c35a 100644 --- a/packages/components/virtual-list/src/components/fixed-size-grid.ts +++ b/packages/components/virtual-list/src/components/fixed-size-grid.ts @@ -1,6 +1,6 @@ import { isNumber } from '@element-plus/utils/util' import { throwError } from '@element-plus/utils/error' -import createGrid from '../builders/buildGrid' +import createGrid from '../builders/build-grid' import { AUTO_ALIGNMENT, @@ -198,8 +198,11 @@ const FixedSizeGrid = createGrid({ ) ) }, - - initCache: () => undefined, + /** + * Fixed size grid does not need this cache + * Using any to bypass it, TODO: Using type inference to fix this. + */ + initCache: () => undefined as any, clearCache: true, diff --git a/packages/components/virtual-list/src/components/fixed-size-list.ts b/packages/components/virtual-list/src/components/fixed-size-list.ts index daded57e32..830b1f023d 100644 --- a/packages/components/virtual-list/src/components/fixed-size-list.ts +++ b/packages/components/virtual-list/src/components/fixed-size-list.ts @@ -1,6 +1,6 @@ import { isString } from '@element-plus/utils/util' import { throwError } from '@element-plus/utils/error' -import buildList from '../builders/buildList' +import buildList from '../builders/build-list' import { isHorizontal } from '../utils' import { SMART_ALIGNMENT, @@ -9,11 +9,10 @@ import { END_ALIGNMENT, CENTERED_ALIGNMENT, } from '../defaults' -import type { DefaultListProps } from '../defaults' -import type { ExtractPropTypes } from 'vue' +import type { VirtualizedListProps } from '../props' -type IProps = ExtractPropTypes +type Props = VirtualizedListProps const FixedSizeList = buildList({ name: 'ElFixedSizeList', @@ -92,7 +91,7 @@ const FixedSizeList = buildList({ Math.max(0, Math.min(total - 1, Math.floor(offset / (itemSize as number)))), getStopIndexForStartIndex: ( - { height, total, itemSize, layout, width }: IProps, + { height, total, itemSize, layout, width }: Props, startIndex: number, scrollOffset: number ) => { @@ -112,8 +111,12 @@ const FixedSizeList = buildList({ ) }, + /** + * Fixed size list does not need this cache + * Using any to bypass it, TODO: Using type inference to fix this. + */ initCache() { - return undefined + return undefined as any }, clearCache: true, diff --git a/packages/components/virtual-list/src/hooks/useCache.ts b/packages/components/virtual-list/src/hooks/use-cache.ts similarity index 100% rename from packages/components/virtual-list/src/hooks/useCache.ts rename to packages/components/virtual-list/src/hooks/use-cache.ts diff --git a/packages/components/virtual-list/src/hooks/use-grid-wheel.ts b/packages/components/virtual-list/src/hooks/use-grid-wheel.ts new file mode 100644 index 0000000000..685f754daa --- /dev/null +++ b/packages/components/virtual-list/src/hooks/use-grid-wheel.ts @@ -0,0 +1,61 @@ +import { rAF, cAF } from '@element-plus/utils/raf' +import { isFF } from '../utils' + +import type { ComputedRef } from 'vue' + +interface GridWheelState { + atXStartEdge: ComputedRef + atXEndEdge: ComputedRef + atYStartEdge: ComputedRef + atYEndEdge: ComputedRef +} + +type GridWheelHandler = (x: number, y: number) => void + +export const useGridWheel = ( + { atXEndEdge, atXStartEdge, atYEndEdge, atYStartEdge }: GridWheelState, + onWheelDelta: GridWheelHandler +) => { + let frameHandle: number | null = null + let xOffset = 0 + let yOffset = 0 + + const hasReachedEdge = (x: number, y: number) => { + const xEdgeReached = + (x < 0 && atXStartEdge.value) || (x > 0 && atXEndEdge.value) + const yEdgeReached = + (y < 0 && atYStartEdge.value) || (y > 0 && atYEndEdge.value) + return xEdgeReached && yEdgeReached + } + + const onWheel = (e: WheelEvent) => { + cAF(frameHandle!) + + const x = e.deltaX + const y = e.deltaY + + if ( + hasReachedEdge(xOffset, yOffset) && + hasReachedEdge(xOffset + x, yOffset + y) + ) + return + + xOffset += x + yOffset += y + + if (!isFF) { + e.preventDefault() + } + + frameHandle = rAF(() => { + onWheelDelta(xOffset, yOffset) + xOffset = 0 + yOffset = 0 + }) + } + + return { + hasReachedEdge, + onWheel, + } +} diff --git a/packages/components/virtual-list/src/hooks/useWheel.ts b/packages/components/virtual-list/src/hooks/use-wheel.ts similarity index 87% rename from packages/components/virtual-list/src/hooks/useWheel.ts rename to packages/components/virtual-list/src/hooks/use-wheel.ts index 990503fa9c..6185d9347f 100644 --- a/packages/components/virtual-list/src/hooks/useWheel.ts +++ b/packages/components/virtual-list/src/hooks/use-wheel.ts @@ -10,19 +10,19 @@ const LayoutKeys = { [VERTICAL]: 'deltaY', } -interface IWheelState { +interface ListWheelState { atStartEdge: ComputedRef // exclusive to reachEnd atEndEdge: ComputedRef layout: ComputedRef } -type IWheelHandler = (offset: number) => void +type ListWheelHandler = (offset: number) => void const useWheel = ( - { atEndEdge, atStartEdge, layout }: IWheelState, - onWheelDelta: IWheelHandler + { atEndEdge, atStartEdge, layout }: ListWheelState, + onWheelDelta: ListWheelHandler ) => { - let frameHandle: number | null = null + let frameHandle: number let offset = 0 // let scrollLock = false diff --git a/packages/components/virtual-list/src/props.ts b/packages/components/virtual-list/src/props.ts index 030bc92549..63555c5ca7 100644 --- a/packages/components/virtual-list/src/props.ts +++ b/packages/components/virtual-list/src/props.ts @@ -1,4 +1,4 @@ -import { isNumber } from '@element-plus/utils/util' +import { buildProp } from '@element-plus/utils/props' import { LTR, RTL, VERTICAL } from './defaults' import type { ExtractPropTypes, PropType } from 'vue' @@ -32,10 +32,10 @@ const initScrollOffset = { default: 0, } -const total = { - type: Number as PropType, +const total = buildProp({ + type: Number, required: true, -} +}) const layout = { type: String as PropType, @@ -63,11 +63,10 @@ export const virtualizedProps = { */ direction, - height: { - type: [String, Number] as PropType, + height: buildProp({ + type: [String, Number], required: true, - validator: isNumber, - }, + }), innerElement: { type: [String, Object], @@ -83,11 +82,10 @@ export const virtualizedProps = { default: false, }, - width: { - type: [Number, String] as PropType, + width: buildProp({ + type: [Number, String], required: true, - validator: isNumber, - }, + }), perfMode: { type: Boolean, default: true, diff --git a/packages/components/virtual-list/src/types.ts b/packages/components/virtual-list/src/types.ts index 1366b1cc60..e1ad90b525 100644 --- a/packages/components/virtual-list/src/types.ts +++ b/packages/components/virtual-list/src/types.ts @@ -1,8 +1,4 @@ -import type { - CSSProperties, - ComponentInternalInstance, - ExtractPropTypes, -} from 'vue' +import type { CSSProperties, ComponentInternalInstance, Ref } from 'vue' export type Instance = ComponentInternalInstance @@ -46,7 +42,7 @@ export type GridCache = { export type ScrollDir = 'forwards' | 'backwards' export type ListItemSizer> = ( - props: ExtractPropTypes, + props: T, index: number, cache: ReturnType

) => number @@ -54,10 +50,10 @@ export type ListItemSizer> = ( export type GetEstimatedTotalSize< T, P extends InitCacheFunc -> = (props: ExtractPropTypes, cache: ReturnType

) => number +> = (props: T, cache: ReturnType

) => number export type GetOffset> = ( - props: ExtractPropTypes, + props: T, idx: number, alignment: Alignment, offset: number, @@ -67,24 +63,21 @@ export type GetOffset> = ( export type GetStartIndexForOffset< T, P extends InitCacheFunc -> = (props: ExtractPropTypes, offset: number, cache: ReturnType

) => number +> = (props: T, offset: number, cache: ReturnType

) => number export type GetStopIndexForStartIndex< T, P extends InitCacheFunc > = ( - props: ExtractPropTypes, + props: T, startIndex: number, scrollOffset: number, cache: ReturnType

) => number -export type PropValidator = (props: ExtractPropTypes) => void +export type PropValidator = (props: T) => void -export type InitCacheFunc = ( - props: ExtractPropTypes, - cache: Instance -) => P +export type InitCacheFunc = (props: T, cache: Instance) => P export type InitListCacheFunc = InitCacheFunc export type InitGridCacheFunc = InitCacheFunc @@ -110,8 +103,8 @@ export type ExposesStates = { } export type SharedExposes = { - windowRef: HTMLElement - innerRef: HTMLElement + windowRef: Ref + innerRef: Ref getItemStyleCache: (_: any, __: any, ___: any) => CSSProperties } @@ -139,8 +132,12 @@ export type GridExposes = { ) => void } & SharedExposes +export type ScrollbarExpose = { + onMouseUp: () => void +} + export type GetGridOffset> = ( - props: ExtractPropTypes, + props: T, index: number, alignment: Alignment, offset: number, @@ -149,7 +146,7 @@ export type GetGridOffset> = ( ) => number export type GetPosition> = ( - props: ExtractPropTypes, + props: T, index: number, cache: ReturnType

) => [number, number] diff --git a/packages/theme-chalk/src/virtual-list.scss b/packages/theme-chalk/src/virtual-list.scss index 8247cf962a..65dce2675e 100644 --- a/packages/theme-chalk/src/virtual-list.scss +++ b/packages/theme-chalk/src/virtual-list.scss @@ -6,3 +6,9 @@ position: relative; } } + +@include b(vg) { + @include e(wrapper) { + position: relative; + } +}