diff --git a/packages/components/virtual-list/index.ts b/packages/components/virtual-list/index.ts index 9091cf80d9..292ae2d055 100644 --- a/packages/components/virtual-list/index.ts +++ b/packages/components/virtual-list/index.ts @@ -3,3 +3,4 @@ export { default as DynamicSizeList } from './src/components/dynamic-size-list' export { default as FixedSizeGrid } from './src/components/fixed-size-grid' export { default as DynamicSizeGrid } from './src/components/dynamic-size-grid' export * from './src/types' +export * from './src/props' diff --git a/packages/components/virtual-list/src/builders/buildGrid.ts b/packages/components/virtual-list/src/builders/buildGrid.ts index f5d89370a6..8268b696a5 100644 --- a/packages/components/virtual-list/src/builders/buildGrid.ts +++ b/packages/components/virtual-list/src/builders/buildGrid.ts @@ -8,17 +8,18 @@ import { onUpdated, resolveDynamicComponent, h, + unref, } from 'vue' import { hasOwn } from '@vue/shared' -import memo from 'memoize-one' -import { isNumber, isString, $ } from '@element-plus/utils/util' +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 { virtualizedGridProps } from '../props' import { getScrollDir, getRTLOffsetType, isRTL } from '../utils' import { - DefaultGridProps, AUTO_ALIGNMENT, BACKWARD, FORWARD, @@ -30,8 +31,9 @@ import { RTL_OFFSET_POS_ASC, } from '../defaults' -import type { VNode, CSSProperties } from 'vue' +import type { CSSProperties, Slot, VNode, VNodeChild } from 'vue' import type { GridConstructorProps, Alignment } from '../types' +import type { VirtualizedGridProps } from '../props' const createGrid = ({ name, @@ -49,20 +51,20 @@ const createGrid = ({ initCache, validateProps, -}: GridConstructorProps) => { +}: GridConstructorProps) => { return defineComponent({ name: name ?? 'ElVirtualList', - props: DefaultGridProps, + props: virtualizedGridProps, emits: [ITEM_RENDER_EVT, SCROLL_EVT], setup(props, { emit, expose }) { validateProps(props) - const instance = getCurrentInstance() + const instance = getCurrentInstance()! const cache = ref(initCache(props, instance)) // refs // here windowRef and innerRef can be type of HTMLElement // or user defined component type, depends on the type passed // by user - const windowRef = ref(null) + const windowRef = ref() // innerRef is the actual container element which contains all the elements const innerRef = ref(null) const states = ref({ @@ -74,10 +76,12 @@ const createGrid = ({ yAxisScrollDir: FORWARD, }) + const getItemStyleCache = useCache() + // computed const columnsToRender = computed(() => { const { totalColumn, totalRow, columnCache } = props - const { isScrolling, xAxisScrollDir, scrollLeft } = $(states) + const { isScrolling, xAxisScrollDir, scrollLeft } = unref(states) if (totalColumn === 0 || totalRow === 0) { return [0, 0, 0, 0] @@ -86,13 +90,13 @@ const createGrid = ({ const startIndex = getColumnStartIndexForOffset( props, scrollLeft, - $(cache) + unref(cache) ) const stopIndex = getColumnStopIndexForStartIndex( props, startIndex, scrollLeft, - $(cache) + unref(cache) ) const cacheBackward = @@ -106,7 +110,7 @@ const createGrid = ({ return [ Math.max(0, startIndex - cacheBackward), - Math.max(0, Math.min(totalColumn - 1, stopIndex + cacheForward)), + Math.max(0, Math.min(totalColumn! - 1, stopIndex + cacheForward)), startIndex, stopIndex, ] @@ -114,18 +118,22 @@ const createGrid = ({ const rowsToRender = computed(() => { const { totalColumn, totalRow, rowCache } = props - const { isScrolling, yAxisScrollDir, scrollTop } = $(states) + const { isScrolling, yAxisScrollDir, scrollTop } = unref(states) if (totalColumn === 0 || totalRow === 0) { return [0, 0, 0, 0] } - const startIndex = getRowStartIndexForOffset(props, scrollTop, $(cache)) + const startIndex = getRowStartIndexForOffset( + props, + scrollTop, + unref(cache) + ) const stopIndex = getRowStopIndexForStartIndex( props, startIndex, scrollTop, - $(cache) + unref(cache) ) const cacheBackward = @@ -137,17 +145,17 @@ const createGrid = ({ return [ Math.max(0, startIndex - cacheBackward), - Math.max(0, Math.min(totalRow - 1, stopIndex + cacheForward)), + Math.max(0, Math.min(totalRow! - 1, stopIndex + cacheForward)), startIndex, stopIndex, ] }) const estimatedTotalHeight = computed(() => - getEstimatedTotalHeight(props, $(cache)) + getEstimatedTotalHeight(props, unref(cache)) ) const estimatedTotalWidth = computed(() => - getEstimatedTotalWidth(props, $(cache)) + getEstimatedTotalWidth(props, unref(cache)) ) const windowStyle = computed(() => [ @@ -161,17 +169,17 @@ const createGrid = ({ direction: props.direction, 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(() => { - const width = `${$(estimatedTotalWidth)}px` - const height = `${$(estimatedTotalHeight)}px` + const width = `${unref(estimatedTotalWidth)}px` + const height = `${unref(estimatedTotalHeight)}px` return { height, - pointerEvents: $(states).isScrolling ? 'none' : undefined, + pointerEvents: unref(states).isScrolling ? 'none' : undefined, width, } }) @@ -180,15 +188,15 @@ const createGrid = ({ const emitEvents = () => { const { totalColumn, totalRow } = props - if (totalColumn > 0 && totalRow > 0) { + if (totalColumn! > 0 && totalRow! > 0) { const [ columnCacheStart, columnCacheEnd, columnVisibleStart, columnVisibleEnd, - ] = $(columnsToRender) + ] = unref(columnsToRender) const [rowCacheStart, rowCacheEnd, rowVisibleStart, rowVisibleEnd] = - $(rowsToRender) + unref(rowsToRender) // emit the render item event with // [xAxisInvisibleStart, xAxisInvisibleEnd, xAxisVisibleStart, xAxisVisibleEnd] // [yAxisInvisibleStart, yAxisInvisibleEnd, yAxisVisibleStart, yAxisVisibleEnd] @@ -211,7 +219,7 @@ const createGrid = ({ updateRequested, xAxisScrollDir, yAxisScrollDir, - } = $(states) + } = unref(states) emit( SCROLL_EVT, xAxisScrollDir, @@ -232,7 +240,7 @@ const createGrid = ({ scrollWidth, } = e.currentTarget as HTMLElement - const _states = $(states) + const _states = unref(states) if ( _states.scrollTop === scrollTop && _states.scrollLeft === scrollLeft @@ -271,13 +279,10 @@ const createGrid = ({ emitEvents() } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const getItemStyleCache = memo((_: any, __: any, ___: any) => ({})) - const scrollTo = ({ scrollLeft, scrollTop }) => { scrollLeft = Math.max(scrollLeft, 0) scrollTop = Math.max(scrollTop, 0) - const _states = $(states) + const _states = unref(states) if ( scrollTop === _states.scrollTop && scrollLeft === _states.scrollLeft @@ -302,12 +307,12 @@ const createGrid = ({ columnIdx = 0, alignment: Alignment = AUTO_ALIGNMENT ) => { - const _states = $(states) - columnIdx = Math.max(0, Math.min(columnIdx, props.totalColumn - 1)) - rowIndex = Math.max(0, Math.min(rowIndex, props.totalRow - 1)) + const _states = unref(states) + columnIdx = Math.max(0, Math.min(columnIdx, props.totalColumn! - 1)) + rowIndex = Math.max(0, Math.min(rowIndex, props.totalRow! - 1)) const scrollBarWidth = getScrollBarWidth() - const _cache = $(cache) + const _cache = unref(cache) const estimatedHeight = getEstimatedTotalHeight(props, _cache) const estimatedWidth = getEstimatedTotalWidth(props, _cache) @@ -318,7 +323,7 @@ const createGrid = ({ alignment, _states.scrollLeft, _cache, - estimatedWidth > props.width ? scrollBarWidth : 0 + estimatedWidth > props.width! ? scrollBarWidth : 0 ), scrollTop: getRowOffset( props, @@ -326,7 +331,7 @@ const createGrid = ({ alignment, _states.scrollTop, _cache, - estimatedHeight > props.height ? scrollBarWidth : 0 + estimatedHeight > props.height! ? scrollBarWidth : 0 ), }) } @@ -337,7 +342,7 @@ const createGrid = ({ ): CSSProperties => { const { columnWidth, direction, rowHeight } = props - const itemStyleCache = getItemStyleCache( + const itemStyleCache = getItemStyleCache.value( clearCache && columnWidth, clearCache && rowHeight, clearCache && direction @@ -349,8 +354,8 @@ const createGrid = ({ if (hasOwn(itemStyleCache, key)) { return itemStyleCache[key] } else { - const [, left] = getColumnPosition(props, columnIndex, $(cache)) - const _cache = $(cache) + const [, left] = getColumnPosition(props, columnIndex, unref(cache)) + const _cache = unref(cache) const rtl = isRTL(direction) const [height, top] = getRowPosition(props, rowIndex, _cache) @@ -376,7 +381,7 @@ const createGrid = ({ states.value.isScrolling = false nextTick(() => { - getItemStyleCache(-1, null, null) + getItemStyleCache.value(-1, null, null) }) } @@ -385,8 +390,8 @@ const createGrid = ({ // for SSR if (isServer) return const { initScrollLeft, initScrollTop } = props - const windowElement = $(windowRef) - if (windowElement !== null) { + const windowElement = unref(windowRef) + if (windowElement) { if (isNumber(initScrollLeft)) { windowElement.scrollLeft = initScrollLeft } @@ -399,11 +404,11 @@ const createGrid = ({ onUpdated(() => { const { direction } = props - const { scrollLeft, scrollTop, updateRequested } = $(states) + const { scrollLeft, scrollTop, updateRequested } = unref(states) - if (updateRequested && $(windowRef) !== null) { - const windowElement = $(windowRef) + const windowElement = unref(windowRef) + if (updateRequested && windowElement) { if (direction === RTL) { switch (getRTLOffsetType()) { case RTL_OFFSET_NAG: { @@ -480,12 +485,12 @@ const createGrid = ({ const Container = resolveDynamicComponent(containerElement) const Inner = resolveDynamicComponent(innerElement) - const children = [] + 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?.({ + ($slots.default as Slot)?.({ columnIndex: column, data, key: column, diff --git a/packages/components/virtual-list/src/builders/buildList.ts b/packages/components/virtual-list/src/builders/buildList.ts index 0d366c4616..7a7320c606 100644 --- a/packages/components/virtual-list/src/builders/buildList.ts +++ b/packages/components/virtual-list/src/builders/buildList.ts @@ -8,18 +8,19 @@ import { onUpdated, resolveDynamicComponent, h, + unref, } from 'vue' import { hasOwn } from '@vue/shared' -import memo from 'memoize-one' -import { isNumber, isString, $ } from '@element-plus/utils/util' +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 Scrollbar from '../components/scrollbar' import { getScrollDir, isHorizontal, getRTLOffsetType } from '../utils' +import { virtualizedListProps } from '../props' import { - DefaultListProps, AUTO_ALIGNMENT, BACKWARD, FORWARD, @@ -31,8 +32,9 @@ import { RTL_OFFSET_POS_DESC, } from '../defaults' -import type { VNode, CSSProperties } from 'vue' +import type { VNode, CSSProperties, Slot, VNodeChild } from 'vue' import type { ListConstructorProps, Alignment } from '../types' +import type { VirtualizedListProps } from '../props' const createList = ({ name, @@ -45,21 +47,23 @@ const createList = ({ initCache, clearCache, validateProps, -}: ListConstructorProps) => { +}: ListConstructorProps) => { return defineComponent({ name: name ?? 'ElVirtualList', - props: DefaultListProps, + props: virtualizedListProps, emits: [ITEM_RENDER_EVT, SCROLL_EVT], setup(props, { emit, expose }) { validateProps(props) - const instance = getCurrentInstance() + const instance = getCurrentInstance()! const dynamicSizeCache = ref(initCache(props, instance)) + + const getItemStyleCache = useCache() // refs // here windowRef and innerRef can be type of HTMLElement // or user defined component type, depends on the type passed // by user - const windowRef = ref(null) - const innerRef = ref(null) + const windowRef = ref() + const innerRef = ref() const scrollbarRef = ref(null) const states = ref({ @@ -75,7 +79,7 @@ const createList = ({ // computed const itemsToRender = computed(() => { const { total, cache } = props - const { isScrolling, scrollDir, scrollOffset } = $(states) + const { isScrolling, scrollDir, scrollOffset } = unref(states) if (total === 0) { return [0, 0, 0, 0] @@ -84,13 +88,13 @@ const createList = ({ const startIndex = getStartIndexForOffset( props, scrollOffset, - $(dynamicSizeCache) + unref(dynamicSizeCache) ) const stopIndex = getStopIndexForStartIndex( props, startIndex, scrollOffset, - $(dynamicSizeCache) + unref(dynamicSizeCache) ) const cacheBackward = @@ -100,14 +104,14 @@ const createList = ({ return [ Math.max(0, startIndex - cacheBackward), - Math.max(0, Math.min(total - 1, stopIndex + cacheForward)), + Math.max(0, Math.min(total! - 1, stopIndex + cacheForward)), startIndex, stopIndex, ] }) const estimatedTotalSize = computed(() => - getEstimatedTotalSize(props, $(dynamicSizeCache)) + getEstimatedTotalSize(props, unref(dynamicSizeCache)) ) const _isHorizontal = computed(() => isHorizontal(props.layout)) @@ -123,16 +127,16 @@ const createList = ({ direction: props.direction, 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(() => { - const size = $(estimatedTotalSize) - const horizontal = $(_isHorizontal) + const size = unref(estimatedTotalSize) + const horizontal = unref(_isHorizontal) return { height: horizontal ? '100%' : `${size}px`, - pointerEvents: $(states).isScrolling ? 'none' : undefined, + pointerEvents: unref(states).isScrolling ? 'none' : undefined, width: horizontal ? `${size}px` : '100%', } }) @@ -151,7 +155,11 @@ const createList = ({ layout: computed(() => props.layout), }, (offset) => { - scrollbarRef.value.onMouseUp?.() + ;( + scrollbarRef.value as any as { + onMouseUp: () => void + } + ).onMouseUp?.() scrollTo( Math.min( states.value.scrollOffset + offset, @@ -164,20 +172,20 @@ const createList = ({ const emitEvents = () => { const { total } = props - if (total > 0) { + if (total! > 0) { const [cacheStart, cacheEnd, visibleStart, visibleEnd] = - $(itemsToRender) + unref(itemsToRender) emit(ITEM_RENDER_EVT, cacheStart, cacheEnd, visibleStart, visibleEnd) } - const { scrollDir, scrollOffset, updateRequested } = $(states) + const { scrollDir, scrollOffset, updateRequested } = unref(states) emit(SCROLL_EVT, scrollDir, scrollOffset, updateRequested) } const scrollVertically = (e: Event) => { const { clientHeight, scrollHeight, scrollTop } = e.currentTarget as HTMLElement - const _states = $(states) + const _states = unref(states) if (_states.scrollOffset === scrollTop) { return } @@ -201,7 +209,7 @@ const createList = ({ const scrollHorizontally = (e: Event) => { const { clientWidth, scrollLeft, scrollWidth } = e.currentTarget as HTMLElement - const _states = $(states) + const _states = unref(states) if (_states.scrollOffset === scrollLeft) { return @@ -245,7 +253,7 @@ const createList = ({ } const onScroll = (e: Event) => { - $(_isHorizontal) ? scrollHorizontally(e) : scrollVertically(e) + unref(_isHorizontal) ? scrollHorizontally(e) : scrollVertically(e) emitEvents() } @@ -262,20 +270,17 @@ const createList = ({ ) } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const getItemStyleCache = memo((_: any, __: any, ___: any) => ({})) - const scrollTo = (offset: number) => { offset = Math.max(offset, 0) - if (offset === $(states).scrollOffset) { + if (offset === unref(states).scrollOffset) { return } states.value = { - ...$(states), + ...unref(states), scrollOffset: offset, - scrollDir: getScrollDir($(states).scrollOffset, offset), + scrollDir: getScrollDir(unref(states).scrollOffset, offset), updateRequested: true, } @@ -286,18 +291,24 @@ const createList = ({ idx: number, alignment: Alignment = AUTO_ALIGNMENT ) => { - const { scrollOffset } = $(states) + const { scrollOffset } = unref(states) - idx = Math.max(0, Math.min(idx, props.total - 1)) + idx = Math.max(0, Math.min(idx, props.total! - 1)) scrollTo( - getOffset(props, idx, alignment, scrollOffset, $(dynamicSizeCache)) + getOffset( + props, + idx, + alignment, + scrollOffset, + unref(dynamicSizeCache) + ) ) } const getItemStyle = (idx: number) => { const { direction, itemSize, layout } = props - const itemStyleCache = getItemStyleCache( + const itemStyleCache = getItemStyleCache.value( clearCache && itemSize, clearCache && layout, clearCache && direction @@ -307,9 +318,9 @@ const createList = ({ if (hasOwn(itemStyleCache, String(idx))) { style = itemStyleCache[idx] } else { - const offset = getItemOffset(props, idx, $(dynamicSizeCache)) - const size = getItemSize(props, idx, $(dynamicSizeCache)) - const horizontal = $(_isHorizontal) + const offset = getItemOffset(props, idx, unref(dynamicSizeCache)) + const size = getItemSize(props, idx, unref(dynamicSizeCache)) + const horizontal = unref(_isHorizontal) const isRtl = direction === RTL const offsetHorizontal = horizontal ? offset : 0 @@ -334,7 +345,7 @@ const createList = ({ states.value.isScrolling = false nextTick(() => { - getItemStyleCache(-1, null, null) + getItemStyleCache.value(-1, null, null) }) } @@ -349,9 +360,9 @@ const createList = ({ onMounted(() => { if (isServer) return const { initScrollOffset } = props - const windowElement = $(windowRef) - if (isNumber(initScrollOffset) && windowElement !== null) { - if ($(_isHorizontal)) { + const windowElement = unref(windowRef) + if (isNumber(initScrollOffset) && windowElement) { + if (unref(_isHorizontal)) { windowElement.scrollLeft = initScrollOffset } else { windowElement.scrollTop = initScrollOffset @@ -363,11 +374,10 @@ const createList = ({ onUpdated(() => { const { direction, layout } = props - const { scrollOffset, updateRequested } = $(states) - - if (updateRequested && $(windowRef) !== null) { - const windowElement = $(windowRef) + const { scrollOffset, updateRequested } = unref(states) + const windowElement = unref(windowRef) + if (updateRequested && windowElement) { if (layout === HORIZONTAL) { if (direction === RTL) { // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. @@ -456,12 +466,12 @@ const createList = ({ const Container = resolveDynamicComponent(containerElement) const Inner = resolveDynamicComponent(innerElement) - const children = [] + const children = [] as VNodeChild[] if (total > 0) { for (let i = start; i <= end; i++) { children.push( - $slots.default?.({ + ($slots.default as Slot)?.({ data, key: i, index: i, 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 87d75d0428..6ea44699a7 100644 --- a/packages/components/virtual-list/src/components/dynamic-size-list.ts +++ b/packages/components/virtual-list/src/components/dynamic-size-list.ts @@ -11,12 +11,11 @@ import { SMART_ALIGNMENT, START_ALIGNMENT, } from '../defaults' -import type { DefaultListProps } from '../defaults' +import type { VirtualizedListProps } from '../props' import type { ListCache, ListItem, ItemSize } from '../types' -import type { ExtractPropTypes } from 'vue' -type Props = ExtractPropTypes +type Props = VirtualizedListProps const SCOPE = 'ElDynamicSizeList' const getItemFromCache = ( diff --git a/packages/components/virtual-list/src/components/scrollbar.ts b/packages/components/virtual-list/src/components/scrollbar.ts index 25e087b585..e4940310ba 100644 --- a/packages/components/virtual-list/src/components/scrollbar.ts +++ b/packages/components/virtual-list/src/components/scrollbar.ts @@ -8,38 +8,33 @@ import { watch, h, withModifiers, + unref, } from 'vue' import { BAR_MAP } from '@element-plus/components/scrollbar' import { on, off } from '@element-plus/utils/dom' import { rAF, cAF } from '@element-plus/utils/raf' import isServer from '@element-plus/utils/isServer' -import { - DefaultScrollBarProps, - SCROLLBAR_MIN_SIZE, - HORIZONTAL, - ScrollbarDirKey, -} from '../defaults' +import { SCROLLBAR_MIN_SIZE, HORIZONTAL, ScrollbarDirKey } from '../defaults' +import { virtualizedScrollbarProps } from '../props' import { renderThumbStyle } from '../utils' import type { CSSProperties } from 'vue' -type SyntheticMouseEvent = TouchEvent | MouseEvent - const ScrollBar = defineComponent({ name: 'ElVirtualScrollBar', - props: DefaultScrollBarProps, + props: virtualizedScrollbarProps, emits: ['scroll', 'start-move', 'stop-move'], setup(props, { emit }) { const GAP = 4 // top 2 + bottom 2 | left 2 + right 2 // DOM refs - const trackRef = ref(null) - const thumbRef = ref(null) + const trackRef = ref() + const thumbRef = ref() // local variables let frameHandle: null | number = null - let onselectstartStore = null + let onselectstartStore: null | typeof document.onselectstart = null // data const state = reactive({ @@ -49,10 +44,9 @@ const ScrollBar = defineComponent({ const bar = computed(() => BAR_MAP[props.layout]) - const trackSize = computed(() => props.clientSize - GAP) + const trackSize = computed(() => props.clientSize! - GAP) const trackStyle = computed(() => ({ - display: props.visible ? null : 'none', position: 'absolute', width: HORIZONTAL === props.layout ? `${trackSize.value}px` : '6px', height: HORIZONTAL === props.layout ? '6px' : `${trackSize.value}px`, @@ -60,21 +54,24 @@ const ScrollBar = defineComponent({ right: '2px', bottom: '2px', borderRadius: '4px', + ...(props.visible ? {} : { display: 'none' }), })) const thumbSize = computed(() => { - if (props.ratio >= 100) { + const ratio = props.ratio! + const clientSize = props.clientSize! + if (ratio >= 100) { return Number.POSITIVE_INFINITY } - if (props.ratio >= 50) { - return (props.ratio * props.clientSize) / 100 + if (ratio >= 50) { + return (ratio * clientSize) / 100 } - const SCROLLBAR_MAX_SIZE = props.clientSize / 3 + const SCROLLBAR_MAX_SIZE = clientSize / 3 return Math.floor( Math.min( - Math.max(props.ratio * props.clientSize, SCROLLBAR_MIN_SIZE), + Math.max(ratio * clientSize, SCROLLBAR_MIN_SIZE), SCROLLBAR_MAX_SIZE ) ) @@ -104,14 +101,16 @@ const ScrollBar = defineComponent({ }) const totalSteps = computed(() => - Math.floor(props.clientSize - thumbSize.value - GAP) + Math.floor(props.clientSize! - thumbSize.value - GAP) ) const attachEvents = () => { on(window, 'mousemove', onMouseMove) on(window, 'mouseup', onMouseUp) - const thumbEl = thumbRef.value + const thumbEl = unref(thumbRef) + + if (!thumbEl) return onselectstartStore = document.onselectstart document.onselectstart = () => false @@ -127,20 +126,25 @@ const ScrollBar = defineComponent({ document.onselectstart = onselectstartStore onselectstartStore = null - const thumbEl = thumbRef.value + const thumbEl = unref(thumbRef) + if (!thumbEl) return + off(thumbEl, 'touchmove', onMouseMove) off(thumbEl, 'touchend', onMouseUp) } - const onThumbMouseDown = (e: SyntheticMouseEvent) => { + const onThumbMouseDown = (e: Event) => { e.stopImmediatePropagation() - if (e.ctrlKey || [1, 2].includes((e as MouseEvent).button)) { + if ( + (e as KeyboardEvent).ctrlKey || + [1, 2].includes((e as MouseEvent).button) + ) { return } state.isDragging = true state[bar.value.axis] = - e.currentTarget[bar.value.offset] - + e.currentTarget![bar.value.offset] - (e[bar.value.client] - (e.currentTarget as HTMLElement).getBoundingClientRect()[ bar.value.direction @@ -157,14 +161,15 @@ const ScrollBar = defineComponent({ detachEvents() } - const onMouseMove = (e: SyntheticMouseEvent) => { + const onMouseMove = (e: Event) => { const { isDragging } = state if (!isDragging) return + if (!thumbRef.value || !trackRef.value) return const prevPage = state[bar.value.axis] if (!prevPage) return - cAF(frameHandle) + cAF(frameHandle!) // using the current track's offset top/left - the current pointer's clientY/clientX // to get the relative position of the pointer to the track. const offset = @@ -212,7 +217,7 @@ const ScrollBar = defineComponent({ (e.target as HTMLElement).getBoundingClientRect()[bar.value.direction] - e[bar.value.client] ) - const thumbHalf = thumbRef.value[bar.value.offset] / 2 + const thumbHalf = thumbRef.value![bar.value.offset] / 2 const distance = offset - thumbHalf state.traveled = Math.max(0, Math.min(distance, totalSteps.value)) @@ -235,19 +240,19 @@ const ScrollBar = defineComponent({ * formula 2: * traveled = (v * clientSize) / (clientSize / totalSteps) --> (v * clientSize) * (totalSteps / clientSize) --> v * totalSteps */ - state.traveled = Math.ceil(v * totalSteps.value) + state.traveled = Math.ceil(v! * totalSteps.value) } ) onMounted(() => { if (isServer) return - on(trackRef.value, 'touchstart', onScrollbarTouchStart) - on(thumbRef.value, 'touchstart', onThumbMouseDown) + on(trackRef.value!, 'touchstart', onScrollbarTouchStart) + on(thumbRef.value!, 'touchstart', onThumbMouseDown) }) onBeforeUnmount(() => { - off(trackRef.value, 'touchstart', onScrollbarTouchStart) + off(trackRef.value!, 'touchstart', onScrollbarTouchStart) detachEvents() }) @@ -269,7 +274,7 @@ const ScrollBar = defineComponent({ style: thumbStyle.value, onMousedown: onThumbMouseDown, }, - null + [] ) ) } diff --git a/packages/components/virtual-list/src/defaults.ts b/packages/components/virtual-list/src/defaults.ts index 404131ab57..a6a1874a22 100644 --- a/packages/components/virtual-list/src/defaults.ts +++ b/packages/components/virtual-list/src/defaults.ts @@ -1,9 +1,3 @@ -import { isNumber } from '@element-plus/utils/util' - -import type { PropType } from 'vue' -import type { Direction, LayoutDirection, ItemSize } from './types' -import type { StyleValue } from '@element-plus/utils/types' - export const DEFAULT_DYNAMIC_LIST_ITEM_SIZE = 50 export const ITEM_RENDER_EVT = 'item-rendered' @@ -28,114 +22,6 @@ export const RTL_OFFSET_NAG = 'negative' export const RTL_OFFSET_POS_ASC = 'positive-ascending' export const RTL_OFFSET_POS_DESC = 'positive-descending' -export const DefaultListProps = { - cache: { - type: Number as PropType, - default: 2, - }, - className: { - type: String as PropType, - default: '', - }, - containerElement: { - type: [String, Object], - default: 'div', - }, - data: { - type: [Array] as PropType, - default: () => [], - }, - // even though we can use Enums here but due to the issue for some - // intelligence plugin distinguishes string enums to strings - // we had some code that uses enums but the plugins were quite - // about that and reporting issues because of it. - direction: { - type: String as PropType, - default: 'ltr', - validator: (val: Direction) => { - return val === LTR || val === RTL - }, - }, - estimatedItemSize: { - type: [Number] as PropType, - }, - height: { - type: [String, Number] as PropType, - required: true, - }, - layout: { - type: String as PropType, - default: VERTICAL, - }, - initScrollOffset: { - type: Number, - default: 0, - }, - innerElement: { - type: [String, Object], - default: 'div', - }, - total: { - type: Number as PropType, - required: true, - }, - itemSize: { - type: [Number, Function] as PropType, - required: true, - }, - style: { - type: [Object, String, Array] as PropType, - default: () => ({}), - }, - useIsScrolling: { - type: Boolean, - default: false, - }, - width: { - type: [Number, String] as PropType, - required: true, - }, -} - -export const DefaultGridProps = { - className: DefaultListProps.className, - columnCache: DefaultListProps.cache, - columnWidth: DefaultListProps.itemSize, - containerElement: DefaultListProps.containerElement, - data: DefaultListProps.data, - direction: DefaultListProps.direction, - estimatedColumnWidth: DefaultListProps.estimatedItemSize, - estimatedRowHeight: DefaultListProps.estimatedItemSize, - height: { - ...DefaultListProps.height, - validator: (val: number) => isNumber(val), - }, - initScrollLeft: DefaultListProps.initScrollOffset, - initScrollTop: DefaultListProps.initScrollOffset, - innerElement: DefaultListProps.innerElement, - rowCache: DefaultListProps.cache, - rowHeight: DefaultListProps.itemSize, - style: DefaultListProps.style, - useIsScrolling: DefaultListProps.useIsScrolling, - width: { - ...DefaultListProps.width, - validator: (val: number) => { - return isNumber(val) - }, - }, - totalColumn: DefaultListProps.total, - totalRow: DefaultListProps.total, -} - -export const DefaultScrollBarProps = { - layout: DefaultListProps.layout, - total: Number, - ratio: Number, - clientSize: Number, - scrollFrom: Number, - visible: Boolean, -} - export const PageKey = { [HORIZONTAL]: 'pageX', [VERTICAL]: 'pageY', diff --git a/packages/components/virtual-list/src/hooks/useCache.ts b/packages/components/virtual-list/src/hooks/useCache.ts new file mode 100644 index 0000000000..2ecc73556c --- /dev/null +++ b/packages/components/virtual-list/src/hooks/useCache.ts @@ -0,0 +1,19 @@ +import { computed, getCurrentInstance } from 'vue' +import memo from 'lodash/memoize' +import memoOne from 'memoize-one' + +import type { VirtualizedProps } from '../props' + +export const useCache = () => { + const vm = getCurrentInstance()! + + const props = vm.proxy!.$props as VirtualizedProps + + return computed(() => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _getItemStyleCache = (_: any, __: any, ___: any) => ({}) + return props.perfMode + ? memo(_getItemStyleCache) + : memoOne(_getItemStyleCache) + }) +} diff --git a/packages/components/virtual-list/src/props.ts b/packages/components/virtual-list/src/props.ts new file mode 100644 index 0000000000..030bc92549 --- /dev/null +++ b/packages/components/virtual-list/src/props.ts @@ -0,0 +1,160 @@ +import { isNumber } from '@element-plus/utils/util' +import { LTR, RTL, VERTICAL } from './defaults' + +import type { ExtractPropTypes, PropType } from 'vue' +import type { StyleValue } from '@element-plus/utils/types' +import type { Direction, LayoutDirection, ItemSize } from './types' + +const itemSize = { + type: [Number, Function] as PropType, + required: true, +} + +const estimatedItemSize = { + type: [Number] as PropType, +} + +const cache = { + type: Number as PropType, + default: 2, +} + +const direction = { + type: String as PropType, + default: 'ltr', + validator: (val: Direction) => { + return val === LTR || val === RTL + }, +} + +const initScrollOffset = { + type: Number, + default: 0, +} + +const total = { + type: Number as PropType, + required: true, +} + +const layout = { + type: String as PropType, + default: VERTICAL, +} + +export const virtualizedProps = { + className: { + type: String as PropType, + default: '', + }, + + containerElement: { + type: [String, Object], + default: 'div', + }, + + data: { + type: [Array] as PropType, + default: () => [], + }, + + /** + * @description controls the horizontal direction. + */ + direction, + + height: { + type: [String, Number] as PropType, + required: true, + validator: isNumber, + }, + + innerElement: { + type: [String, Object], + default: 'div', + }, + + style: { + type: [Object, String, Array] as PropType, + }, + + useIsScrolling: { + type: Boolean, + default: false, + }, + + width: { + type: [Number, String] as PropType, + required: true, + validator: isNumber, + }, + perfMode: { + type: Boolean, + default: true, + }, +} + +export const virtualizedListProps = { + /** + * @description describes how many items should be pre rendered to the head + * and the tail of the window + */ + cache, + + estimatedItemSize, + + /** + * @description controls the list's orientation + */ + layout, + + initScrollOffset, + + /** + * @description describes the total number of the list. + */ + total, + + itemSize, + ...virtualizedProps, +} + +export const virtualizedGridProps = { + columnCache: cache, + columnWidth: itemSize, + estimatedColumnWidth: estimatedItemSize, + estimatedRowHeight: estimatedItemSize, + initScrollLeft: initScrollOffset, + initScrollTop: initScrollOffset, + rowCache: cache, + rowHeight: itemSize, + totalColumn: total, + totalRow: total, + ...virtualizedProps, +} + +export const virtualizedScrollbarProps = { + layout, + total, + ratio: { + type: Number, + required: true, + }, + clientSize: { + type: Number, + required: true, + }, + scrollFrom: { + type: Number, + required: true, + }, + visible: Boolean, +} + +export type VirtualizedProps = ExtractPropTypes +export type VirtualizedListProps = ExtractPropTypes +export type VirtualizedGridProps = ExtractPropTypes + +export type VirtualizedScrollbarProps = ExtractPropTypes< + typeof virtualizedScrollbarProps +>