mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
315 lines
7.9 KiB
TypeScript
315 lines
7.9 KiB
TypeScript
import { computed, defineComponent, inject, ref, unref } from 'vue'
|
|
import {
|
|
DynamicSizeGrid,
|
|
FixedSizeGrid,
|
|
} from '@element-plus/components/virtual-list'
|
|
import { isNumber, isObject } from '@element-plus/utils'
|
|
import { Header } from './components'
|
|
import { TableV2InjectionKey } from './tokens'
|
|
import { tableV2GridProps } from './grid'
|
|
import { sum } from './utils'
|
|
|
|
import type { UnwrapRef } from 'vue'
|
|
import type {
|
|
DynamicSizeGridInstance,
|
|
GridDefaultSlotParams,
|
|
GridItemKeyGetter,
|
|
GridItemRenderedEvtParams,
|
|
GridScrollOptions,
|
|
ResetAfterIndex,
|
|
Alignment as ScrollStrategy,
|
|
} from '@element-plus/components/virtual-list'
|
|
import type { TableV2HeaderInstance } from './components'
|
|
import type { TableV2GridProps } from './grid'
|
|
|
|
const COMPONENT_NAME = 'ElTableV2Grid'
|
|
|
|
const useTableGrid = (props: TableV2GridProps) => {
|
|
const headerRef = ref<TableV2HeaderInstance>()
|
|
const bodyRef = ref<DynamicSizeGridInstance>()
|
|
|
|
const totalHeight = computed(() => {
|
|
const { data, rowHeight, estimatedRowHeight } = props
|
|
|
|
if (estimatedRowHeight) {
|
|
return
|
|
}
|
|
|
|
return data.length * (rowHeight as number)
|
|
})
|
|
|
|
const fixedRowHeight = computed(() => {
|
|
const { fixedData, rowHeight } = props
|
|
|
|
return (fixedData?.length || 0) * (rowHeight as number)
|
|
})
|
|
|
|
const headerHeight = computed(() => sum(props.headerHeight))
|
|
|
|
const gridHeight = computed(() => {
|
|
const { height } = props
|
|
return Math.max(0, height - unref(headerHeight) - unref(fixedRowHeight))
|
|
})
|
|
|
|
const hasHeader = computed(() => {
|
|
return unref(headerHeight) + unref(fixedRowHeight) > 0
|
|
})
|
|
|
|
const itemKey: GridItemKeyGetter = ({ data, rowIndex }) =>
|
|
data[rowIndex][props.rowKey]
|
|
|
|
function onItemRendered({
|
|
rowCacheStart,
|
|
rowCacheEnd,
|
|
rowVisibleStart,
|
|
rowVisibleEnd,
|
|
}: GridItemRenderedEvtParams) {
|
|
props.onRowsRendered?.({
|
|
rowCacheStart,
|
|
rowCacheEnd,
|
|
rowVisibleStart,
|
|
rowVisibleEnd,
|
|
})
|
|
}
|
|
|
|
function resetAfterRowIndex(index: number, forceUpdate: boolean) {
|
|
bodyRef.value?.resetAfterRowIndex(index, forceUpdate)
|
|
}
|
|
|
|
function scrollTo(x: number, y: number): void
|
|
function scrollTo(options: GridScrollOptions): void
|
|
function scrollTo(leftOrOptions: number | GridScrollOptions, top?: number) {
|
|
const header$ = unref(headerRef)
|
|
const body$ = unref(bodyRef)
|
|
|
|
if (!header$ || !body$) return
|
|
|
|
if (isObject(leftOrOptions)) {
|
|
header$.scrollToLeft(leftOrOptions.scrollLeft)
|
|
body$.scrollTo(leftOrOptions)
|
|
} else {
|
|
header$.scrollToLeft(leftOrOptions)
|
|
body$.scrollTo({
|
|
scrollLeft: leftOrOptions,
|
|
scrollTop: top,
|
|
})
|
|
}
|
|
}
|
|
|
|
function scrollToTop(scrollTop: number) {
|
|
unref(bodyRef)?.scrollTo({
|
|
scrollTop,
|
|
})
|
|
}
|
|
|
|
function scrollToRow(row: number, strategy: ScrollStrategy) {
|
|
unref(bodyRef)?.scrollToItem(row, 1, strategy)
|
|
}
|
|
|
|
function forceUpdate() {
|
|
unref(bodyRef)?.$forceUpdate()
|
|
unref(headerRef)?.$forceUpdate()
|
|
}
|
|
|
|
return {
|
|
bodyRef,
|
|
forceUpdate,
|
|
fixedRowHeight,
|
|
gridHeight,
|
|
hasHeader,
|
|
headerHeight,
|
|
headerRef,
|
|
totalHeight,
|
|
|
|
itemKey,
|
|
onItemRendered,
|
|
resetAfterRowIndex,
|
|
scrollTo,
|
|
scrollToTop,
|
|
scrollToRow,
|
|
}
|
|
}
|
|
|
|
const TableGrid = defineComponent({
|
|
name: COMPONENT_NAME,
|
|
props: tableV2GridProps,
|
|
setup(props, { slots, expose }) {
|
|
const { ns } = inject(TableV2InjectionKey)!
|
|
|
|
const {
|
|
bodyRef,
|
|
fixedRowHeight,
|
|
gridHeight,
|
|
hasHeader,
|
|
headerRef,
|
|
headerHeight,
|
|
totalHeight,
|
|
|
|
forceUpdate,
|
|
itemKey,
|
|
onItemRendered,
|
|
resetAfterRowIndex,
|
|
scrollTo,
|
|
scrollToTop,
|
|
scrollToRow,
|
|
} = useTableGrid(props)
|
|
|
|
expose({
|
|
forceUpdate,
|
|
/**
|
|
* @description fetch total height
|
|
*/
|
|
totalHeight,
|
|
/**
|
|
* @description scroll to a position
|
|
*/
|
|
scrollTo,
|
|
/**
|
|
* @description scroll vertically to position y
|
|
*/
|
|
scrollToTop,
|
|
/**
|
|
* @description scroll to a given row
|
|
* @params row {Number} which row to scroll to
|
|
* @params strategy {ScrollStrategy} use what strategy to scroll to
|
|
*/
|
|
scrollToRow,
|
|
/**
|
|
* @description reset rendered state after row index
|
|
*/
|
|
resetAfterRowIndex,
|
|
})
|
|
|
|
const getColumnWidth = () => props.bodyWidth
|
|
|
|
return () => {
|
|
const {
|
|
cache,
|
|
columns,
|
|
data,
|
|
fixedData,
|
|
useIsScrolling,
|
|
scrollbarAlwaysOn,
|
|
scrollbarEndGap,
|
|
scrollbarStartGap,
|
|
style,
|
|
rowHeight,
|
|
bodyWidth,
|
|
estimatedRowHeight,
|
|
headerWidth,
|
|
height,
|
|
width,
|
|
|
|
getRowHeight,
|
|
onScroll,
|
|
} = props
|
|
|
|
const isDynamicRowEnabled = isNumber(estimatedRowHeight)
|
|
const Grid = isDynamicRowEnabled ? DynamicSizeGrid : FixedSizeGrid
|
|
const _headerHeight = unref(headerHeight)
|
|
|
|
return (
|
|
<div role="table" class={[ns.e('table'), props.class]} style={style}>
|
|
<Grid
|
|
ref={bodyRef}
|
|
// special attrs
|
|
data={data}
|
|
useIsScrolling={useIsScrolling}
|
|
itemKey={itemKey}
|
|
// column attrs
|
|
columnCache={0}
|
|
columnWidth={isDynamicRowEnabled ? getColumnWidth : bodyWidth}
|
|
totalColumn={1}
|
|
// row attrs
|
|
totalRow={data.length}
|
|
rowCache={cache}
|
|
rowHeight={isDynamicRowEnabled ? getRowHeight : rowHeight}
|
|
// DOM attrs
|
|
width={width}
|
|
height={unref(gridHeight)}
|
|
class={ns.e('body')}
|
|
role="rowgroup"
|
|
scrollbarStartGap={scrollbarStartGap}
|
|
scrollbarEndGap={scrollbarEndGap}
|
|
scrollbarAlwaysOn={scrollbarAlwaysOn}
|
|
// handlers
|
|
onScroll={onScroll}
|
|
onItemRendered={onItemRendered}
|
|
perfMode={false}
|
|
>
|
|
{{
|
|
default: (params: GridDefaultSlotParams) => {
|
|
const rowData = data[params.rowIndex]
|
|
return slots.row?.({
|
|
...params,
|
|
columns,
|
|
rowData,
|
|
})
|
|
},
|
|
}}
|
|
</Grid>
|
|
{unref(hasHeader) && (
|
|
<Header
|
|
ref={headerRef}
|
|
class={ns.e('header-wrapper')}
|
|
columns={columns}
|
|
headerData={data}
|
|
headerHeight={props.headerHeight}
|
|
fixedHeaderData={fixedData}
|
|
rowWidth={headerWidth}
|
|
rowHeight={rowHeight}
|
|
width={width}
|
|
height={Math.min(_headerHeight + unref(fixedRowHeight), height)}
|
|
>
|
|
{{
|
|
dynamic: slots.header,
|
|
fixed: slots.row,
|
|
}}
|
|
</Header>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
},
|
|
})
|
|
|
|
export default TableGrid
|
|
|
|
export type TableGridRowSlotParams = {
|
|
columns: TableV2GridProps['columns']
|
|
rowData: any
|
|
} & GridDefaultSlotParams
|
|
|
|
export type TableGridInstance = InstanceType<typeof TableGrid> &
|
|
UnwrapRef<{
|
|
forceUpdate: () => void
|
|
/**
|
|
* @description fetch total height
|
|
*/
|
|
totalHeight: number
|
|
|
|
/**
|
|
* @description scrollTo a position
|
|
* @param { number | ScrollToOptions } arg1
|
|
* @param { number } arg2
|
|
*/
|
|
scrollTo(leftOrOptions: number | GridScrollOptions, top?: number): void
|
|
|
|
/**
|
|
* @description scroll vertically to position y
|
|
*/
|
|
scrollToTop(scrollTop: number): void
|
|
/**
|
|
* @description scroll to a given row
|
|
* @params row {Number} which row to scroll to
|
|
* @params @optional strategy {ScrollStrategy} use what strategy to scroll to
|
|
*/
|
|
scrollToRow(row: number, strategy: ScrollStrategy): void
|
|
/**
|
|
* @description reset rendered state after row index
|
|
* @param { number } rowIndex
|
|
* @param { boolean } forceUpdate
|
|
*/
|
|
resetAfterRowIndex: ResetAfterIndex
|
|
}>
|