mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-11-30 10:18:02 +08:00
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:
parent
9a5a1d5ea6
commit
50a1b6f892
@ -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('<fixed-size-grid />', () => {
|
||||
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('<fixed-size-grid />', () => {
|
||||
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)
|
||||
})
|
||||
|
@ -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('<fixed-size-grid />', () => {
|
||||
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('<fixed-size-grid />', () => {
|
||||
|
||||
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('<fixed-size-grid />', () => {
|
||||
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)
|
||||
})
|
||||
|
@ -6,7 +6,7 @@ import { ScrollbarDirKey } from '../src/defaults'
|
||||
describe('virtual scrollbar', () => {
|
||||
async function testInlineStyle(layout = 'vertical') {
|
||||
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: {
|
||||
Scrollbar,
|
||||
},
|
||||
|
@ -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<HTMLElement>()
|
||||
const hScrollbar = ref<ScrollbarExpose>()
|
||||
const vScrollbar = ref<ScrollbarExpose>()
|
||||
// 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<StyleValue>(() => [
|
||||
{
|
||||
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
|
||||
},
|
||||
})
|
||||
}
|
@ -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<HTMLElement>()
|
||||
const innerRef = ref<HTMLElement>()
|
||||
const scrollbarRef = ref(null)
|
||||
const scrollbarRef = ref()
|
||||
|
||||
const states = ref({
|
||||
isScrolling: false,
|
@ -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<typeof DefaultGridProps>
|
||||
type Props = VirtualizedGridProps
|
||||
type CacheItemType = 'column' | 'row'
|
||||
|
||||
// generates props access key via type
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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<typeof DefaultListProps>
|
||||
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,
|
||||
|
61
packages/components/virtual-list/src/hooks/use-grid-wheel.ts
Normal file
61
packages/components/virtual-list/src/hooks/use-grid-wheel.ts
Normal 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,
|
||||
}
|
||||
}
|
@ -10,19 +10,19 @@ const LayoutKeys = {
|
||||
[VERTICAL]: 'deltaY',
|
||||
}
|
||||
|
||||
interface IWheelState {
|
||||
interface ListWheelState {
|
||||
atStartEdge: ComputedRef<boolean> // exclusive to reachEnd
|
||||
atEndEdge: ComputedRef<boolean>
|
||||
layout: ComputedRef<LayoutDirection>
|
||||
}
|
||||
|
||||
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
|
@ -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<number>,
|
||||
const total = buildProp({
|
||||
type: Number,
|
||||
required: true,
|
||||
}
|
||||
})
|
||||
|
||||
const layout = {
|
||||
type: String as PropType<LayoutDirection>,
|
||||
@ -63,11 +63,10 @@ export const virtualizedProps = {
|
||||
*/
|
||||
direction,
|
||||
|
||||
height: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
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<string | number>,
|
||||
width: buildProp({
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
validator: isNumber,
|
||||
},
|
||||
}),
|
||||
perfMode: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
@ -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<T, P extends InitListCacheFunc<T>> = (
|
||||
props: ExtractPropTypes<T>,
|
||||
props: T,
|
||||
index: number,
|
||||
cache: ReturnType<P>
|
||||
) => number
|
||||
@ -54,10 +50,10 @@ export type ListItemSizer<T, P extends InitListCacheFunc<T>> = (
|
||||
export type GetEstimatedTotalSize<
|
||||
T,
|
||||
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>> = (
|
||||
props: ExtractPropTypes<T>,
|
||||
props: T,
|
||||
idx: number,
|
||||
alignment: Alignment,
|
||||
offset: number,
|
||||
@ -67,24 +63,21 @@ export type GetOffset<T, P extends InitListCacheFunc<T>> = (
|
||||
export type GetStartIndexForOffset<
|
||||
T,
|
||||
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<
|
||||
T,
|
||||
P extends InitCacheFunc<T, GridCache | ListCache>
|
||||
> = (
|
||||
props: ExtractPropTypes<T>,
|
||||
props: T,
|
||||
startIndex: number,
|
||||
scrollOffset: number,
|
||||
cache: ReturnType<P>
|
||||
) => number
|
||||
|
||||
export type PropValidator<T> = (props: ExtractPropTypes<T>) => void
|
||||
export type PropValidator<T> = (props: T) => void
|
||||
|
||||
export type InitCacheFunc<T, P> = (
|
||||
props: ExtractPropTypes<T>,
|
||||
cache: Instance
|
||||
) => P
|
||||
export type InitCacheFunc<T, P> = (props: T, cache: Instance) => P
|
||||
export type InitListCacheFunc<T> = InitCacheFunc<T, ListCache>
|
||||
export type InitGridCacheFunc<T> = InitCacheFunc<T, GridCache>
|
||||
|
||||
@ -110,8 +103,8 @@ export type ExposesStates = {
|
||||
}
|
||||
|
||||
export type SharedExposes = {
|
||||
windowRef: HTMLElement
|
||||
innerRef: HTMLElement
|
||||
windowRef: Ref<HTMLElement>
|
||||
innerRef: Ref<HTMLElement>
|
||||
getItemStyleCache: (_: any, __: any, ___: any) => CSSProperties
|
||||
}
|
||||
|
||||
@ -139,8 +132,12 @@ export type GridExposes = {
|
||||
) => void
|
||||
} & SharedExposes
|
||||
|
||||
export type ScrollbarExpose = {
|
||||
onMouseUp: () => void
|
||||
}
|
||||
|
||||
export type GetGridOffset<T, P extends InitGridCacheFunc<T>> = (
|
||||
props: ExtractPropTypes<T>,
|
||||
props: T,
|
||||
index: number,
|
||||
alignment: Alignment,
|
||||
offset: number,
|
||||
@ -149,7 +146,7 @@ export type GetGridOffset<T, P extends InitGridCacheFunc<T>> = (
|
||||
) => number
|
||||
|
||||
export type GetPosition<T, P extends InitGridCacheFunc<T>> = (
|
||||
props: ExtractPropTypes<T>,
|
||||
props: T,
|
||||
index: number,
|
||||
cache: ReturnType<P>
|
||||
) => [number, number]
|
||||
|
@ -6,3 +6,9 @@
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
@include b(vg) {
|
||||
@include e(wrapper) {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user