Files
element-plus/packages/components/table-v2/src/table-grid.tsx

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
}>