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
This commit is contained in:
jeremywu 2021-09-26 19:22:56 +08:00 committed by GitHub
parent 9a5a1d5ea6
commit 50a1b6f892
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 307 additions and 153 deletions

View File

@ -1,4 +1,4 @@
import { nextTick } from 'vue' import { nextTick, unref } from 'vue'
import makeMount from '@element-plus/test-utils/make-mount' import makeMount from '@element-plus/test-utils/make-mount'
import makeScroll from '@element-plus/test-utils/make-scroll' import makeScroll from '@element-plus/test-utils/make-scroll'
import setupMock from '../setup-mock' import setupMock from '../setup-mock'
@ -96,12 +96,12 @@ describe('<fixed-size-grid />', () => {
expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(36) expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(36)
const gridRef = wrapper.vm.$refs.gridRef as GridRef const gridRef = wrapper.vm.$refs.gridRef as GridRef
makeScroll(gridRef.windowRef, 'scrollTop', 100) makeScroll(unref(gridRef.windowRef), 'scrollTop', 100)
await nextTick() await nextTick()
// 8 x 5 grid // 8 x 5 grid
expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(40) expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(40)
makeScroll(gridRef.windowRef, 'scrollLeft', 100) makeScroll(unref(gridRef.windowRef), 'scrollLeft', 100)
await nextTick() await nextTick()
expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(64) expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(64)
}) })
@ -112,11 +112,11 @@ describe('<fixed-size-grid />', () => {
expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(36) expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(36)
const gridRef = wrapper.vm.$refs.gridRef as GridRef const gridRef = wrapper.vm.$refs.gridRef as GridRef
makeScroll(gridRef.windowRef, 'scrollTop', 0) makeScroll(unref(gridRef.windowRef), 'scrollTop', 0)
await nextTick() await nextTick()
expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(36) expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(36)
makeScroll(gridRef.windowRef, 'scrollLeft', 0) makeScroll(unref(gridRef.windowRef), 'scrollLeft', 0)
await nextTick() await nextTick()
expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(36) expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(36)
}) })

View File

@ -1,4 +1,4 @@
import { nextTick } from 'vue' import { nextTick, unref } from 'vue'
import makeMount from '@element-plus/test-utils/make-mount' import makeMount from '@element-plus/test-utils/make-mount'
import makeScroll from '@element-plus/test-utils/make-scroll' import makeScroll from '@element-plus/test-utils/make-scroll'
import setupMock from '../setup-mock' import setupMock from '../setup-mock'
@ -71,8 +71,8 @@ describe('<fixed-size-grid />', () => {
expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(24) expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(24)
const gridRef = wrapper.vm.$refs.gridRef as GridRef const gridRef = wrapper.vm.$refs.gridRef as GridRef
expect(gridRef.innerRef.style.height).toBe('2500px') expect(unref(gridRef.innerRef).style.height).toBe('2500px')
expect(gridRef.innerRef.style.width).toBe('5000px') expect(unref(gridRef.innerRef).style.width).toBe('5000px')
}) })
it('should render zero row zero column', async () => { it('should render zero row zero column', async () => {
@ -96,11 +96,11 @@ describe('<fixed-size-grid />', () => {
const gridRef = wrapper.vm.$refs.gridRef as GridRef const gridRef = wrapper.vm.$refs.gridRef as GridRef
makeScroll(gridRef.windowRef, 'scrollTop', 100) makeScroll(unref(gridRef.windowRef), 'scrollTop', 100)
await nextTick() await nextTick()
expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(21) expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(21)
makeScroll(gridRef.windowRef, 'scrollLeft', 100) makeScroll(unref(gridRef.windowRef), 'scrollLeft', 100)
await nextTick() await nextTick()
// 5 (backward cache 1 + visible 2 + forward cache 2) // 5 (backward cache 1 + visible 2 + forward cache 2)
// * 7 (backward cache 1 + visible 4 + forward cache 2) // * 7 (backward cache 1 + visible 4 + forward cache 2)
@ -114,11 +114,11 @@ describe('<fixed-size-grid />', () => {
expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(24) expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(24)
const gridRef = wrapper.vm.$refs.gridRef as GridRef const gridRef = wrapper.vm.$refs.gridRef as GridRef
makeScroll(gridRef.windowRef, 'scrollTop', 0) makeScroll(unref(gridRef.windowRef), 'scrollTop', 0)
await nextTick() await nextTick()
expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(24) expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(24)
makeScroll(gridRef.windowRef, 'scrollLeft', 0) makeScroll(unref(gridRef.windowRef), 'scrollLeft', 0)
await nextTick() await nextTick()
expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(24) expect(wrapper.findAll(ITEM_SELECTOR)).toHaveLength(24)
}) })

View File

@ -6,7 +6,7 @@ import { ScrollbarDirKey } from '../src/defaults'
describe('virtual scrollbar', () => { describe('virtual scrollbar', () => {
async function testInlineStyle(layout = 'vertical') { async function testInlineStyle(layout = 'vertical') {
const wrapper = mount({ const wrapper = mount({
template: `<scrollbar visible layout="${layout}"></scrollbar>`, template: `<scrollbar visible layout="${layout}" :total="100" :ratio="25" :client-size="100" :scroll-from="20"></scrollbar>`,
components: { components: {
Scrollbar, Scrollbar,
}, },

View File

@ -16,7 +16,9 @@ import { isNumber, isString } from '@element-plus/utils/util'
import isServer from '@element-plus/utils/isServer' import isServer from '@element-plus/utils/isServer'
import getScrollBarWidth from '@element-plus/utils/scrollbar-width' 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 { virtualizedGridProps } from '../props'
import { getScrollDir, getRTLOffsetType, isRTL } from '../utils' import { getScrollDir, getRTLOffsetType, isRTL } from '../utils'
import { import {
@ -31,8 +33,9 @@ import {
RTL_OFFSET_POS_ASC, RTL_OFFSET_POS_ASC,
} from '../defaults' } from '../defaults'
import type { CSSProperties, Slot, VNode, VNodeChild } from 'vue' import type { CSSProperties, VNode, VNodeChild } from 'vue'
import type { GridConstructorProps, Alignment } from '../types' import type { StyleValue } from '@element-plus/utils/types'
import type { GridConstructorProps, Alignment, ScrollbarExpose } from '../types'
import type { VirtualizedGridProps } from '../props' import type { VirtualizedGridProps } from '../props'
const createGrid = ({ const createGrid = ({
@ -56,7 +59,7 @@ const createGrid = ({
name: name ?? 'ElVirtualList', name: name ?? 'ElVirtualList',
props: virtualizedGridProps, props: virtualizedGridProps,
emits: [ITEM_RENDER_EVT, SCROLL_EVT], emits: [ITEM_RENDER_EVT, SCROLL_EVT],
setup(props, { emit, expose }) { setup(props, { emit, expose, slots }) {
validateProps(props) validateProps(props)
const instance = getCurrentInstance()! const instance = getCurrentInstance()!
const cache = ref(initCache(props, instance)) const cache = ref(initCache(props, instance))
@ -65,6 +68,8 @@ const createGrid = ({
// or user defined component type, depends on the type passed // or user defined component type, depends on the type passed
// by user // by user
const windowRef = ref<HTMLElement>() const windowRef = ref<HTMLElement>()
const hScrollbar = ref<ScrollbarExpose>()
const vScrollbar = ref<ScrollbarExpose>()
// innerRef is the actual container element which contains all the elements // innerRef is the actual container element which contains all the elements
const innerRef = ref(null) const innerRef = ref(null)
const states = ref({ const states = ref({
@ -79,6 +84,8 @@ const createGrid = ({
const getItemStyleCache = useCache() const getItemStyleCache = useCache()
// computed // computed
const parsedHeight = computed(() => parseInt(`${props.height}`, 10))
const parsedWidth = computed(() => parseInt(`${props.width}`, 10))
const columnsToRender = computed(() => { const columnsToRender = computed(() => {
const { totalColumn, totalRow, columnCache } = props const { totalColumn, totalRow, columnCache } = props
const { isScrolling, xAxisScrollDir, scrollLeft } = unref(states) const { isScrolling, xAxisScrollDir, scrollLeft } = unref(states)
@ -158,10 +165,10 @@ const createGrid = ({
getEstimatedTotalWidth(props, unref(cache)) getEstimatedTotalWidth(props, unref(cache))
) )
const windowStyle = computed(() => [ const windowStyle = computed<StyleValue>(() => [
{ {
position: 'relative', position: 'relative',
overflow: 'auto', overflow: 'hidden',
WebkitOverflowScrolling: 'touch', WebkitOverflowScrolling: 'touch',
willChange: 'transform', willChange: 'transform',
}, },
@ -170,7 +177,7 @@ const createGrid = ({
height: isNumber(props.height) ? `${props.height}px` : props.height, height: isNumber(props.height) ? `${props.height}px` : props.height,
width: isNumber(props.width) ? `${props.width}px` : props.width, width: isNumber(props.width) ? `${props.width}px` : props.width,
}, },
props.style, props.style ?? {},
]) ])
const innerStyle = computed(() => { const innerStyle = computed(() => {
@ -279,7 +286,57 @@ const createGrid = ({
emitEvents() 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) scrollLeft = Math.max(scrollLeft, 0)
scrollTop = Math.max(scrollTop, 0) scrollTop = Math.max(scrollTop, 0)
const _states = unref(states) const _states = unref(states)
@ -434,20 +491,6 @@ const createGrid = ({
} }
}) })
const api = {
windowStyle,
windowRef,
columnsToRender,
innerRef,
innerStyle,
states,
rowsToRender,
getItemStyle,
onScroll,
scrollTo,
scrollToItem,
}
expose({ expose({
windowRef, windowRef,
innerRef, innerRef,
@ -457,77 +500,121 @@ const createGrid = ({
states, states,
}) })
return api // rendering part
},
render(ctx: any) { const renderScrollbars = () => {
const { const { totalColumn, totalRow } = props
$slots,
className,
containerElement,
columnsToRender,
data,
getItemStyle,
innerElement,
innerStyle,
rowsToRender,
onScroll,
states,
useIsScrolling,
windowStyle,
totalColumn,
totalRow,
} = ctx
const [columnStart, columnEnd] = columnsToRender const width = unref(parsedWidth)
const [rowStart, rowEnd] = rowsToRender 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 verticalScrollbar = h(Scrollbar, {
const Inner = resolveDynamicComponent(innerElement) ref: vScrollbar,
clientSize: height,
layout: 'vertical',
onScroll: onVerticalScroll,
ratio: (height * 100) / estimatedHeight,
scrollFrom: scrollTop / (estimatedHeight - height),
total: totalColumn,
visible: true,
})
const children: VNodeChild[] = [] return {
if (totalRow > 0 && totalColumn > 0) { horizontalScrollbar,
for (let row = rowStart; row <= rowEnd; row++) { verticalScrollbar,
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,
})
)
}
} }
} }
const InnerNode = [ const renderItems = () => {
h( const [columnStart, columnEnd] = unref(columnsToRender)
Inner as VNode, const [rowStart, rowEnd] = unref(rowsToRender)
{ const { data, totalColumn, totalRow, useIsScrolling } = props
style: innerStyle, const children: VNodeChild[] = []
ref: 'innerRef', if (totalRow > 0 && totalColumn > 0) {
}, for (let row = rowStart; row <= rowEnd; row++) {
!isString(Inner) for (let column = columnStart; column <= columnEnd; column++) {
? { children.push(
default: () => children, slots.default?.({
} columnIndex: column,
: children data,
), key: column,
] isScrolling: useIsScrolling
? unref(states).isScrolling
: undefined,
style: getItemStyle(row, column),
rowIndex: row,
})
)
}
}
}
return children
}
return h( const renderInner = () => {
Container as VNode, const Inner = resolveDynamicComponent(props.innerElement) as VNode
{ const children = renderItems()
class: className, return [
style: windowStyle, h(
onScroll, Inner,
ref: 'windowRef', {
}, style: unref(innerStyle),
!isString(Container) ? { default: () => InnerNode } : InnerNode 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
}, },
}) })
} }

View File

@ -15,8 +15,8 @@ import { hasOwn } from '@vue/shared'
import { isNumber, isString } from '@element-plus/utils/util' import { isNumber, isString } from '@element-plus/utils/util'
import isServer from '@element-plus/utils/isServer' import isServer from '@element-plus/utils/isServer'
import { useCache } from '../hooks/useCache' import { useCache } from '../hooks/use-cache'
import useWheel from '../hooks/useWheel' import useWheel from '../hooks/use-wheel'
import Scrollbar from '../components/scrollbar' import Scrollbar from '../components/scrollbar'
import { getScrollDir, isHorizontal, getRTLOffsetType } from '../utils' import { getScrollDir, isHorizontal, getRTLOffsetType } from '../utils'
import { virtualizedListProps } from '../props' import { virtualizedListProps } from '../props'
@ -64,7 +64,7 @@ const createList = ({
// by user // by user
const windowRef = ref<HTMLElement>() const windowRef = ref<HTMLElement>()
const innerRef = ref<HTMLElement>() const innerRef = ref<HTMLElement>()
const scrollbarRef = ref(null) const scrollbarRef = ref()
const states = ref({ const states = ref({
isScrolling: false, isScrolling: false,

View File

@ -1,6 +1,6 @@
import { isFunction } from '@vue/shared' import { isFunction } from '@vue/shared'
import { throwError } from '@element-plus/utils/error' import { throwError } from '@element-plus/utils/error'
import createGrid from '../builders/buildGrid' import createGrid from '../builders/build-grid'
import { import {
AUTO_ALIGNMENT, AUTO_ALIGNMENT,
@ -10,15 +10,14 @@ import {
SMART_ALIGNMENT, SMART_ALIGNMENT,
START_ALIGNMENT, START_ALIGNMENT,
} from '../defaults' } 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' import type { Alignment, GridCache, ListItem, ItemSize } from '../types'
const { max, min, floor } = Math const { max, min, floor } = Math
const SCOPE = 'ElDynamicSizeGrid' const SCOPE = 'ElDynamicSizeGrid'
type Props = ExtractPropTypes<typeof DefaultGridProps> type Props = VirtualizedGridProps
type CacheItemType = 'column' | 'row' type CacheItemType = 'column' | 'row'
// generates props access key via type // generates props access key via type

View File

@ -1,6 +1,6 @@
import { throwError } from '@element-plus/utils/error' import { throwError } from '@element-plus/utils/error'
import createList from '../builders/buildList' import createList from '../builders/build-list'
import { isHorizontal } from '../utils' import { isHorizontal } from '../utils'
import { import {
@ -228,10 +228,10 @@ const DynamicSizeList = createList({
cache.clearCacheAfterIndex = (index: number, forceUpdate = true) => { cache.clearCacheAfterIndex = (index: number, forceUpdate = true) => {
cache.lastVisitedIndex = Math.min(cache.lastVisitedIndex, index - 1) cache.lastVisitedIndex = Math.min(cache.lastVisitedIndex, index - 1)
instance.exposed.getItemStyleCache(-1) instance.exposed?.getItemStyleCache(-1)
if (forceUpdate) { if (forceUpdate) {
instance.proxy.$forceUpdate() instance.proxy?.$forceUpdate()
} }
} }

View File

@ -1,6 +1,6 @@
import { isNumber } from '@element-plus/utils/util' import { isNumber } from '@element-plus/utils/util'
import { throwError } from '@element-plus/utils/error' import { throwError } from '@element-plus/utils/error'
import createGrid from '../builders/buildGrid' import createGrid from '../builders/build-grid'
import { import {
AUTO_ALIGNMENT, 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, clearCache: true,

View File

@ -1,6 +1,6 @@
import { isString } from '@element-plus/utils/util' import { isString } from '@element-plus/utils/util'
import { throwError } from '@element-plus/utils/error' import { throwError } from '@element-plus/utils/error'
import buildList from '../builders/buildList' import buildList from '../builders/build-list'
import { isHorizontal } from '../utils' import { isHorizontal } from '../utils'
import { import {
SMART_ALIGNMENT, SMART_ALIGNMENT,
@ -9,11 +9,10 @@ import {
END_ALIGNMENT, END_ALIGNMENT,
CENTERED_ALIGNMENT, CENTERED_ALIGNMENT,
} from '../defaults' } from '../defaults'
import type { DefaultListProps } from '../defaults'
import type { ExtractPropTypes } from 'vue' import type { VirtualizedListProps } from '../props'
type IProps = ExtractPropTypes<typeof DefaultListProps> type Props = VirtualizedListProps
const FixedSizeList = buildList({ const FixedSizeList = buildList({
name: 'ElFixedSizeList', name: 'ElFixedSizeList',
@ -92,7 +91,7 @@ const FixedSizeList = buildList({
Math.max(0, Math.min(total - 1, Math.floor(offset / (itemSize as number)))), Math.max(0, Math.min(total - 1, Math.floor(offset / (itemSize as number)))),
getStopIndexForStartIndex: ( getStopIndexForStartIndex: (
{ height, total, itemSize, layout, width }: IProps, { height, total, itemSize, layout, width }: Props,
startIndex: number, startIndex: number,
scrollOffset: 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() { initCache() {
return undefined return undefined as any
}, },
clearCache: true, clearCache: true,

View File

@ -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<boolean>
atXEndEdge: ComputedRef<boolean>
atYStartEdge: ComputedRef<boolean>
atYEndEdge: ComputedRef<boolean>
}
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,
}
}

View File

@ -10,19 +10,19 @@ const LayoutKeys = {
[VERTICAL]: 'deltaY', [VERTICAL]: 'deltaY',
} }
interface IWheelState { interface ListWheelState {
atStartEdge: ComputedRef<boolean> // exclusive to reachEnd atStartEdge: ComputedRef<boolean> // exclusive to reachEnd
atEndEdge: ComputedRef<boolean> atEndEdge: ComputedRef<boolean>
layout: ComputedRef<LayoutDirection> layout: ComputedRef<LayoutDirection>
} }
type IWheelHandler = (offset: number) => void type ListWheelHandler = (offset: number) => void
const useWheel = ( const useWheel = (
{ atEndEdge, atStartEdge, layout }: IWheelState, { atEndEdge, atStartEdge, layout }: ListWheelState,
onWheelDelta: IWheelHandler onWheelDelta: ListWheelHandler
) => { ) => {
let frameHandle: number | null = null let frameHandle: number
let offset = 0 let offset = 0
// let scrollLock = false // let scrollLock = false

View File

@ -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 { LTR, RTL, VERTICAL } from './defaults'
import type { ExtractPropTypes, PropType } from 'vue' import type { ExtractPropTypes, PropType } from 'vue'
@ -32,10 +32,10 @@ const initScrollOffset = {
default: 0, default: 0,
} }
const total = { const total = buildProp({
type: Number as PropType<number>, type: Number,
required: true, required: true,
} })
const layout = { const layout = {
type: String as PropType<LayoutDirection>, type: String as PropType<LayoutDirection>,
@ -63,11 +63,10 @@ export const virtualizedProps = {
*/ */
direction, direction,
height: { height: buildProp({
type: [String, Number] as PropType<string | number>, type: [String, Number],
required: true, required: true,
validator: isNumber, }),
},
innerElement: { innerElement: {
type: [String, Object], type: [String, Object],
@ -83,11 +82,10 @@ export const virtualizedProps = {
default: false, default: false,
}, },
width: { width: buildProp({
type: [Number, String] as PropType<string | number>, type: [Number, String],
required: true, required: true,
validator: isNumber, }),
},
perfMode: { perfMode: {
type: Boolean, type: Boolean,
default: true, default: true,

View File

@ -1,8 +1,4 @@
import type { import type { CSSProperties, ComponentInternalInstance, Ref } from 'vue'
CSSProperties,
ComponentInternalInstance,
ExtractPropTypes,
} from 'vue'
export type Instance = ComponentInternalInstance export type Instance = ComponentInternalInstance
@ -46,7 +42,7 @@ export type GridCache = {
export type ScrollDir = 'forwards' | 'backwards' export type ScrollDir = 'forwards' | 'backwards'
export type ListItemSizer<T, P extends InitListCacheFunc<T>> = ( export type ListItemSizer<T, P extends InitListCacheFunc<T>> = (
props: ExtractPropTypes<T>, props: T,
index: number, index: number,
cache: ReturnType<P> cache: ReturnType<P>
) => number ) => number
@ -54,10 +50,10 @@ export type ListItemSizer<T, P extends InitListCacheFunc<T>> = (
export type GetEstimatedTotalSize< export type GetEstimatedTotalSize<
T, T,
P extends InitCacheFunc<T, GridCache | ListCache> P extends InitCacheFunc<T, GridCache | ListCache>
> = (props: ExtractPropTypes<T>, cache: ReturnType<P>) => number > = (props: T, cache: ReturnType<P>) => number
export type GetOffset<T, P extends InitListCacheFunc<T>> = ( export type GetOffset<T, P extends InitListCacheFunc<T>> = (
props: ExtractPropTypes<T>, props: T,
idx: number, idx: number,
alignment: Alignment, alignment: Alignment,
offset: number, offset: number,
@ -67,24 +63,21 @@ export type GetOffset<T, P extends InitListCacheFunc<T>> = (
export type GetStartIndexForOffset< export type GetStartIndexForOffset<
T, T,
P extends InitCacheFunc<T, GridCache | ListCache> P extends InitCacheFunc<T, GridCache | ListCache>
> = (props: ExtractPropTypes<T>, offset: number, cache: ReturnType<P>) => number > = (props: T, offset: number, cache: ReturnType<P>) => number
export type GetStopIndexForStartIndex< export type GetStopIndexForStartIndex<
T, T,
P extends InitCacheFunc<T, GridCache | ListCache> P extends InitCacheFunc<T, GridCache | ListCache>
> = ( > = (
props: ExtractPropTypes<T>, props: T,
startIndex: number, startIndex: number,
scrollOffset: number, scrollOffset: number,
cache: ReturnType<P> cache: ReturnType<P>
) => number ) => number
export type PropValidator<T> = (props: ExtractPropTypes<T>) => void export type PropValidator<T> = (props: T) => void
export type InitCacheFunc<T, P> = ( export type InitCacheFunc<T, P> = (props: T, cache: Instance) => P
props: ExtractPropTypes<T>,
cache: Instance
) => P
export type InitListCacheFunc<T> = InitCacheFunc<T, ListCache> export type InitListCacheFunc<T> = InitCacheFunc<T, ListCache>
export type InitGridCacheFunc<T> = InitCacheFunc<T, GridCache> export type InitGridCacheFunc<T> = InitCacheFunc<T, GridCache>
@ -110,8 +103,8 @@ export type ExposesStates = {
} }
export type SharedExposes = { export type SharedExposes = {
windowRef: HTMLElement windowRef: Ref<HTMLElement>
innerRef: HTMLElement innerRef: Ref<HTMLElement>
getItemStyleCache: (_: any, __: any, ___: any) => CSSProperties getItemStyleCache: (_: any, __: any, ___: any) => CSSProperties
} }
@ -139,8 +132,12 @@ export type GridExposes = {
) => void ) => void
} & SharedExposes } & SharedExposes
export type ScrollbarExpose = {
onMouseUp: () => void
}
export type GetGridOffset<T, P extends InitGridCacheFunc<T>> = ( export type GetGridOffset<T, P extends InitGridCacheFunc<T>> = (
props: ExtractPropTypes<T>, props: T,
index: number, index: number,
alignment: Alignment, alignment: Alignment,
offset: number, offset: number,
@ -149,7 +146,7 @@ export type GetGridOffset<T, P extends InitGridCacheFunc<T>> = (
) => number ) => number
export type GetPosition<T, P extends InitGridCacheFunc<T>> = ( export type GetPosition<T, P extends InitGridCacheFunc<T>> = (
props: ExtractPropTypes<T>, props: T,
index: number, index: number,
cache: ReturnType<P> cache: ReturnType<P>
) => [number, number] ) => [number, number]

View File

@ -6,3 +6,9 @@
position: relative; position: relative;
} }
} }
@include b(vg) {
@include e(wrapper) {
position: relative;
}
}