mirror of
https://github.com/element-plus/element-plus.git
synced 2025-02-17 11:49:41 +08:00
feat(components): [virtual-table] compsables (#7341)
- Split `use-table` into separate files for better readability
This commit is contained in:
parent
5a52e61a3a
commit
6d9e56a106
5
packages/components/table-v2/src/composables/index.ts
Normal file
5
packages/components/table-v2/src/composables/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './use-columns'
|
||||
export * from './use-scrollbar'
|
||||
export * from './use-row'
|
||||
export * from './use-data'
|
||||
export * from './use-styles'
|
@ -1,40 +1,18 @@
|
||||
import { computed, unref } from 'vue'
|
||||
import { placeholderSign } from './private'
|
||||
import { isObject } from '@element-plus/utils'
|
||||
import { SortOrder, oppositeOrderMap } from '../constants'
|
||||
import { placeholderSign } from '../private'
|
||||
import { calcColumnStyle } from './utils'
|
||||
|
||||
import type { CSSProperties, Ref } from 'vue'
|
||||
import type { Column, Columns, KeyType } from './types'
|
||||
import type { TableV2Props } from '../table'
|
||||
import type { AnyColumns, Column, KeyType } from '../types'
|
||||
|
||||
type AnyColumn = Columns<any>
|
||||
|
||||
const calcColumnStyle = (
|
||||
column: Column<any>,
|
||||
fixedColumn: boolean
|
||||
): CSSProperties => {
|
||||
const flex = {
|
||||
flexGrow: 0,
|
||||
flexShrink: 0,
|
||||
}
|
||||
|
||||
if (column.fixed) {
|
||||
flex.flexShrink = 1
|
||||
}
|
||||
|
||||
const style = {
|
||||
...(column.style ?? {}),
|
||||
...flex,
|
||||
flexBasis: 'auto',
|
||||
width: column.width,
|
||||
}
|
||||
|
||||
if (!fixedColumn) {
|
||||
if (column.maxWidth) style.maxWidth = column.maxWidth
|
||||
if (column.minWidth) style.maxWidth = column.minWidth
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
|
||||
function useColumns(columns: Ref<AnyColumn>, fixed: Ref<boolean>) {
|
||||
function useColumns(
|
||||
props: TableV2Props,
|
||||
columns: Ref<AnyColumns>,
|
||||
fixed: Ref<boolean>
|
||||
) {
|
||||
const visibleColumns = computed(() => {
|
||||
return unref(columns).filter((column) => !column.hidden)
|
||||
})
|
||||
@ -45,7 +23,7 @@ function useColumns(columns: Ref<AnyColumn>, fixed: Ref<boolean>) {
|
||||
)
|
||||
)
|
||||
|
||||
const fixedColumnOnRight = computed(() =>
|
||||
const fixedColumnsOnRight = computed(() =>
|
||||
unref(visibleColumns).filter((column) => column.fixed === 'right')
|
||||
)
|
||||
|
||||
@ -54,7 +32,7 @@ function useColumns(columns: Ref<AnyColumn>, fixed: Ref<boolean>) {
|
||||
)
|
||||
|
||||
const mainColumns = computed(() => {
|
||||
const ret: AnyColumn = []
|
||||
const ret: AnyColumns = []
|
||||
|
||||
unref(fixedColumnsOnLeft).forEach((column) => {
|
||||
ret.push({
|
||||
@ -67,7 +45,7 @@ function useColumns(columns: Ref<AnyColumn>, fixed: Ref<boolean>) {
|
||||
ret.push(column)
|
||||
})
|
||||
|
||||
unref(fixedColumnOnRight).forEach((column) => {
|
||||
unref(fixedColumnsOnRight).forEach((column) => {
|
||||
ret.push({
|
||||
...column,
|
||||
placeholderSign,
|
||||
@ -78,7 +56,7 @@ function useColumns(columns: Ref<AnyColumn>, fixed: Ref<boolean>) {
|
||||
})
|
||||
|
||||
const hasFixedColumns = computed(() => {
|
||||
return unref(fixedColumnsOnLeft).length || unref(fixedColumnOnRight).length
|
||||
return unref(fixedColumnsOnLeft).length || unref(fixedColumnsOnRight).length
|
||||
})
|
||||
|
||||
const columnsStyles = computed(() => {
|
||||
@ -112,12 +90,28 @@ function useColumns(columns: Ref<AnyColumn>, fixed: Ref<boolean>) {
|
||||
column.width = width
|
||||
}
|
||||
|
||||
function onColumnSorted(e: MouseEvent) {
|
||||
const { key } = (e.currentTarget as HTMLElement).dataset
|
||||
if (!key) return
|
||||
const { sortState, sortBy } = props
|
||||
|
||||
let order = SortOrder.ASC
|
||||
|
||||
if (isObject(sortState)) {
|
||||
order = oppositeOrderMap[sortState[key]]
|
||||
} else {
|
||||
order = oppositeOrderMap[sortBy.order]
|
||||
}
|
||||
|
||||
props.onColumnSort?.({ column: getColumn(key)!, key, order })
|
||||
}
|
||||
|
||||
return {
|
||||
columns,
|
||||
columnsStyles,
|
||||
columnsTotalWidth,
|
||||
fixedColumnsOnLeft,
|
||||
fixedColumnOnRight,
|
||||
fixedColumnsOnRight,
|
||||
hasFixedColumns,
|
||||
mainColumns,
|
||||
normalColumns,
|
||||
@ -126,7 +120,9 @@ function useColumns(columns: Ref<AnyColumn>, fixed: Ref<boolean>) {
|
||||
getColumn,
|
||||
getColumnStyle,
|
||||
updateColumnWidth,
|
||||
onColumnSorted,
|
||||
}
|
||||
}
|
||||
|
||||
export { useColumns }
|
||||
export type UseColumnsReturn = ReturnType<typeof useColumns>
|
71
packages/components/table-v2/src/composables/use-data.ts
Normal file
71
packages/components/table-v2/src/composables/use-data.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { computed, ref, unref, watch } from 'vue'
|
||||
|
||||
import type { TableV2Props } from '../table'
|
||||
import type { KeyType } from '../types'
|
||||
import type { UseRowReturn } from './use-row'
|
||||
|
||||
type UseDataProps = {
|
||||
expandedRowKeys: UseRowReturn['expandedRowKeys']
|
||||
lastRenderedRowIndex: UseRowReturn['lastRenderedRowIndex']
|
||||
resetAfterIndex: UseRowReturn['resetAfterIndex']
|
||||
}
|
||||
|
||||
export const useData = (
|
||||
props: TableV2Props,
|
||||
{ expandedRowKeys, lastRenderedRowIndex, resetAfterIndex }: UseDataProps
|
||||
) => {
|
||||
const depthMap = ref<Record<KeyType, number>>({})
|
||||
|
||||
const flattenedData = computed(() => {
|
||||
const depths: Record<KeyType, number> = {}
|
||||
const { data, rowKey } = props
|
||||
|
||||
const _expandedRowKeys = unref(expandedRowKeys)
|
||||
|
||||
if (!_expandedRowKeys || !_expandedRowKeys.length) return data
|
||||
|
||||
const array: any[] = []
|
||||
const keysSet = new Set()
|
||||
_expandedRowKeys.forEach((x) => keysSet.add(x))
|
||||
|
||||
let copy: any[] = data.slice()
|
||||
copy.forEach((x) => (depths[x[rowKey]] = 0))
|
||||
while (copy.length > 0) {
|
||||
const item = copy.shift()!
|
||||
|
||||
array.push(item)
|
||||
if (
|
||||
keysSet.has(item[rowKey]) &&
|
||||
Array.isArray(item.children) &&
|
||||
item.children.length > 0
|
||||
) {
|
||||
copy = [...item.children, ...copy]
|
||||
item.children.forEach(
|
||||
(child: any) => (depths[child[rowKey]] = depths[item[rowKey]] + 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
depthMap.value = depths
|
||||
return array
|
||||
})
|
||||
|
||||
const data = computed(() => {
|
||||
const { data, expandColumnKey } = props
|
||||
return expandColumnKey ? unref(flattenedData) : data
|
||||
})
|
||||
|
||||
watch(data, (val, prev) => {
|
||||
if (val !== prev) {
|
||||
lastRenderedRowIndex.value = -1
|
||||
resetAfterIndex(0, true)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
data,
|
||||
depthMap,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseDataReturn = ReturnType<typeof useData>
|
170
packages/components/table-v2/src/composables/use-row.ts
Normal file
170
packages/components/table-v2/src/composables/use-row.ts
Normal file
@ -0,0 +1,170 @@
|
||||
import {
|
||||
computed,
|
||||
getCurrentInstance,
|
||||
ref,
|
||||
shallowRef,
|
||||
unref,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import { debounce } from 'lodash-unified'
|
||||
import { isNumber } from '@element-plus/utils'
|
||||
import { FixedDir } from '../constants'
|
||||
|
||||
import type { Ref } from 'vue'
|
||||
import type { TableV2Props } from '../table'
|
||||
import type {
|
||||
RowExpandParams,
|
||||
RowHeightChangedParams,
|
||||
RowHoverParams,
|
||||
} from '../row'
|
||||
import type { FixedDirection, KeyType } from '../types'
|
||||
import type { onRowRenderedParams } from '../grid'
|
||||
import type { TableGridInstance } from '../table-grid'
|
||||
|
||||
type Heights = Record<KeyType, number>
|
||||
type GridInstanceRef = Ref<TableGridInstance | undefined>
|
||||
|
||||
type UseRowProps = {
|
||||
mainTableRef: GridInstanceRef
|
||||
leftTableRef: GridInstanceRef
|
||||
rightTableRef: GridInstanceRef
|
||||
|
||||
onMaybeEndReached: () => void
|
||||
}
|
||||
|
||||
export const useRow = (
|
||||
props: TableV2Props,
|
||||
{ mainTableRef, leftTableRef, rightTableRef, onMaybeEndReached }: UseRowProps
|
||||
) => {
|
||||
const vm = getCurrentInstance()!
|
||||
const { emit } = vm
|
||||
const isResetting = shallowRef(false)
|
||||
const hoveringRowKey = shallowRef<KeyType | null>(null)
|
||||
const expandedRowKeys = ref<KeyType[]>(props.defaultExpandedRowKeys || [])
|
||||
const lastRenderedRowIndex = ref(-1)
|
||||
const resetIndex = shallowRef<number | null>(null)
|
||||
const rowHeights = ref<Heights>({})
|
||||
const pendingRowHeights = ref<Heights>({})
|
||||
const leftTableHeights = shallowRef<Heights>({})
|
||||
const mainTableHeights = shallowRef<Heights>({})
|
||||
const rightTableHeights = shallowRef<Heights>({})
|
||||
const isDynamic = computed(() => isNumber(props.estimatedRowHeight))
|
||||
|
||||
function onRowsRendered(params: onRowRenderedParams) {
|
||||
props.onRowRendered?.(params)
|
||||
|
||||
if (params.rowCacheEnd > unref(lastRenderedRowIndex)) {
|
||||
lastRenderedRowIndex.value = params.rowCacheEnd
|
||||
}
|
||||
}
|
||||
|
||||
function onRowHovered({ hovered, rowKey }: RowHoverParams<any>) {
|
||||
hoveringRowKey.value = hovered ? rowKey : null
|
||||
}
|
||||
|
||||
function onRowExpanded({
|
||||
expanded,
|
||||
rowData,
|
||||
rowIndex,
|
||||
rowKey,
|
||||
}: RowExpandParams<any>) {
|
||||
const _expandedRowKeys = [...unref(expandedRowKeys)]
|
||||
const currentKeyIndex = _expandedRowKeys.indexOf(rowKey)
|
||||
if (expanded) {
|
||||
if (currentKeyIndex === -1) _expandedRowKeys.push(rowKey)
|
||||
} else {
|
||||
if (currentKeyIndex > -1) _expandedRowKeys.splice(currentKeyIndex, 1)
|
||||
}
|
||||
expandedRowKeys.value = _expandedRowKeys
|
||||
|
||||
emit('update:expandedRowKeys', _expandedRowKeys)
|
||||
props.onRowExpand?.({
|
||||
expanded,
|
||||
rowData,
|
||||
rowIndex,
|
||||
rowKey,
|
||||
})
|
||||
// If this is not controlled, then use this to notify changes
|
||||
props.onExpandedRowsChange?.(_expandedRowKeys)
|
||||
}
|
||||
|
||||
const flushingRowHeights = debounce(() => {
|
||||
isResetting.value = true
|
||||
rowHeights.value = { ...unref(rowHeights), ...unref(pendingRowHeights) }
|
||||
resetAfterIndex(unref(resetIndex)!, false)
|
||||
pendingRowHeights.value = {}
|
||||
// force update
|
||||
resetIndex.value = null
|
||||
mainTableRef.value?.forceUpdate()
|
||||
leftTableRef.value?.forceUpdate()
|
||||
rightTableRef.value?.forceUpdate()
|
||||
vm.proxy?.$forceUpdate()
|
||||
isResetting.value = false
|
||||
}, 0)
|
||||
|
||||
function resetAfterIndex(index: number, forceUpdate = false) {
|
||||
if (!unref(isDynamic)) return
|
||||
;[mainTableRef, leftTableRef, rightTableRef].forEach((tableRef) => {
|
||||
const table = unref(tableRef)
|
||||
if (table) table.resetAfterRowIndex(index, forceUpdate)
|
||||
})
|
||||
}
|
||||
|
||||
function resetHeights(rowKey: KeyType, height: number, rowIdx: number) {
|
||||
const resetIdx = unref(resetIndex)
|
||||
if (resetIdx === null) {
|
||||
resetIndex.value = rowIdx
|
||||
} else {
|
||||
if (resetIdx > rowIdx) {
|
||||
resetIndex.value = rowIdx
|
||||
}
|
||||
}
|
||||
|
||||
pendingRowHeights.value[rowKey] = height
|
||||
}
|
||||
|
||||
function onRowHeightChange(
|
||||
{ rowKey, height, rowIndex }: RowHeightChangedParams,
|
||||
fixedDir: FixedDirection
|
||||
) {
|
||||
if (!fixedDir) {
|
||||
mainTableHeights.value[rowKey] = height
|
||||
} else {
|
||||
if (fixedDir === FixedDir.RIGHT) {
|
||||
rightTableHeights.value[rowKey] = height
|
||||
} else {
|
||||
leftTableHeights.value[rowKey] = height
|
||||
}
|
||||
}
|
||||
|
||||
const maximumHeight = Math.max(
|
||||
...[leftTableHeights, rightTableHeights, mainTableHeights].map(
|
||||
(records) => records.value[rowKey] || 0
|
||||
)
|
||||
)
|
||||
|
||||
if (unref(rowHeights)[rowKey] !== maximumHeight) {
|
||||
resetHeights(rowKey, maximumHeight, rowIndex)
|
||||
flushingRowHeights()
|
||||
}
|
||||
}
|
||||
// when rendered row changes, maybe reaching the bottom
|
||||
watch(lastRenderedRowIndex, () => onMaybeEndReached())
|
||||
|
||||
return {
|
||||
hoveringRowKey,
|
||||
expandedRowKeys,
|
||||
lastRenderedRowIndex,
|
||||
isDynamic,
|
||||
isResetting,
|
||||
rowHeights,
|
||||
|
||||
resetAfterIndex,
|
||||
onRowExpanded,
|
||||
onRowHovered,
|
||||
onRowsRendered,
|
||||
onRowHeightChange,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseRowReturn = ReturnType<typeof useRow>
|
@ -0,0 +1,83 @@
|
||||
import { ref, unref, watch } from 'vue'
|
||||
|
||||
import type { Ref } from 'vue'
|
||||
import type { TableV2Props } from '../table'
|
||||
import type { TableGridInstance } from '../table-grid'
|
||||
|
||||
export type ScrollPos = { scrollLeft: number; scrollTop: number }
|
||||
type GridInstanceRef = Ref<TableGridInstance | undefined>
|
||||
|
||||
type UseScrollBarProps = {
|
||||
mainTableRef: GridInstanceRef
|
||||
leftTableRef: GridInstanceRef
|
||||
rightTableRef: GridInstanceRef
|
||||
|
||||
onMaybeEndReached: () => void
|
||||
}
|
||||
|
||||
export const useScrollbar = (
|
||||
props: TableV2Props,
|
||||
{
|
||||
mainTableRef,
|
||||
leftTableRef,
|
||||
rightTableRef,
|
||||
onMaybeEndReached,
|
||||
}: UseScrollBarProps
|
||||
) => {
|
||||
const scrollPos = ref<ScrollPos>({ scrollLeft: 0, scrollTop: 0 })
|
||||
|
||||
function doScroll(params: ScrollPos) {
|
||||
const { scrollTop } = params
|
||||
|
||||
mainTableRef.value?.scrollTo(params)
|
||||
leftTableRef.value?.scrollToTop(scrollTop)
|
||||
rightTableRef.value?.scrollToTop(scrollTop)
|
||||
}
|
||||
|
||||
// methods
|
||||
function scrollTo(params: ScrollPos) {
|
||||
scrollPos.value = params
|
||||
|
||||
doScroll(params)
|
||||
}
|
||||
|
||||
function scrollToTop(scrollTop: number) {
|
||||
scrollPos.value.scrollTop = scrollTop
|
||||
|
||||
doScroll(unref(scrollPos))
|
||||
}
|
||||
|
||||
function scrollToLeft(scrollLeft: number) {
|
||||
scrollPos.value.scrollLeft = scrollLeft
|
||||
|
||||
mainTableRef.value?.scrollTo?.(unref(scrollPos))
|
||||
}
|
||||
|
||||
function onScroll(params: ScrollPos) {
|
||||
scrollTo(params)
|
||||
props.onScroll?.(params)
|
||||
}
|
||||
|
||||
function onVerticalScroll({ scrollTop }: ScrollPos) {
|
||||
const { scrollTop: currentScrollTop } = unref(scrollPos)
|
||||
if (scrollTop !== currentScrollTop) scrollToTop(scrollTop)
|
||||
}
|
||||
|
||||
// When scrollTop changes, maybe reaching the bottom
|
||||
watch(
|
||||
() => unref(scrollPos).scrollTop,
|
||||
(cur, prev) => {
|
||||
if (cur > prev) onMaybeEndReached()
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
scrollPos,
|
||||
|
||||
scrollTo,
|
||||
scrollToLeft,
|
||||
scrollToTop,
|
||||
onScroll,
|
||||
onVerticalScroll,
|
||||
}
|
||||
}
|
127
packages/components/table-v2/src/composables/use-styles.ts
Normal file
127
packages/components/table-v2/src/composables/use-styles.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { computed, unref } from 'vue'
|
||||
import { addUnit, isNumber } from '@element-plus/utils'
|
||||
import { enforceUnit, sum } from '../utils'
|
||||
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type { TableV2Props } from '../table'
|
||||
import type { UseColumnsReturn } from './use-columns'
|
||||
import type { UseDataReturn } from './use-data'
|
||||
|
||||
type UseStyleProps = {
|
||||
columnsTotalWidth: UseColumnsReturn['columnsTotalWidth']
|
||||
data: UseDataReturn['data']
|
||||
fixedColumnsOnLeft: UseColumnsReturn['fixedColumnsOnLeft']
|
||||
fixedColumnsOnRight: UseColumnsReturn['fixedColumnsOnRight']
|
||||
}
|
||||
|
||||
export const useStyles = (
|
||||
props: TableV2Props,
|
||||
{
|
||||
columnsTotalWidth,
|
||||
data,
|
||||
fixedColumnsOnLeft,
|
||||
fixedColumnsOnRight,
|
||||
}: UseStyleProps
|
||||
) => {
|
||||
const bodyWidth = computed(() => {
|
||||
const { fixed, width, vScrollbarSize } = props
|
||||
const ret = width - vScrollbarSize
|
||||
return fixed ? Math.max(Math.round(unref(columnsTotalWidth)), ret) : ret
|
||||
})
|
||||
|
||||
const headerWidth = computed(
|
||||
() => unref(bodyWidth) + (props.fixed ? props.vScrollbarSize : 0)
|
||||
)
|
||||
|
||||
const mainTableHeight = computed(() => {
|
||||
const { height = 0, maxHeight = 0, footerHeight, hScrollbarSize } = props
|
||||
|
||||
if (maxHeight > 0) {
|
||||
const _fixedRowsHeight = unref(fixedRowsHeight)
|
||||
const _rowsHeight = unref(rowsHeight)
|
||||
const _headerHeight = unref(headerHeight)
|
||||
const total =
|
||||
_headerHeight + _fixedRowsHeight + _rowsHeight + hScrollbarSize
|
||||
|
||||
return Math.min(total, maxHeight - footerHeight)
|
||||
}
|
||||
|
||||
return height - footerHeight
|
||||
})
|
||||
|
||||
const rowsHeight = computed(() => {
|
||||
const { rowHeight, estimatedRowHeight } = props
|
||||
const _data = unref(data)
|
||||
if (isNumber(estimatedRowHeight)) {
|
||||
return _data.length * estimatedRowHeight
|
||||
}
|
||||
|
||||
return _data.length * rowHeight
|
||||
})
|
||||
|
||||
const fixedTableHeight = computed(() => {
|
||||
const { maxHeight } = props
|
||||
const tableHeight = unref(mainTableHeight)
|
||||
if (isNumber(maxHeight) && maxHeight > 0) return tableHeight
|
||||
|
||||
const totalHeight =
|
||||
unref(rowsHeight) + unref(headerHeight) + unref(fixedRowsHeight)
|
||||
|
||||
return Math.min(tableHeight, totalHeight)
|
||||
})
|
||||
|
||||
const mapColumn = (column: TableV2Props['columns'][number]) => column.width
|
||||
|
||||
const leftTableWidth = computed(() =>
|
||||
sum(unref(fixedColumnsOnLeft).map(mapColumn))
|
||||
)
|
||||
|
||||
const rightTableWidth = computed(() =>
|
||||
sum(unref(fixedColumnsOnRight).map(mapColumn))
|
||||
)
|
||||
|
||||
const headerHeight = computed(() => sum(props.headerHeight))
|
||||
|
||||
const fixedRowsHeight = computed(() => {
|
||||
return (props.fixedData?.length || 0) * props.rowHeight
|
||||
})
|
||||
|
||||
const windowHeight = computed(() => {
|
||||
return unref(mainTableHeight) - unref(headerHeight) - unref(fixedRowsHeight)
|
||||
})
|
||||
|
||||
const rootStyle = computed<CSSProperties>(() => {
|
||||
const { style = {}, height, width } = props
|
||||
return enforceUnit({
|
||||
...style,
|
||||
height,
|
||||
width,
|
||||
})
|
||||
})
|
||||
|
||||
const footerHeight = computed(() =>
|
||||
enforceUnit({ height: props.footerHeight })
|
||||
)
|
||||
|
||||
const emptyStyle = computed<CSSProperties>(() => ({
|
||||
top: addUnit(unref(headerHeight)),
|
||||
bottom: addUnit(props.footerHeight),
|
||||
width: addUnit(props.width),
|
||||
}))
|
||||
|
||||
return {
|
||||
bodyWidth,
|
||||
fixedTableHeight,
|
||||
mainTableHeight,
|
||||
leftTableWidth,
|
||||
rightTableWidth,
|
||||
headerWidth,
|
||||
rowsHeight,
|
||||
windowHeight,
|
||||
footerHeight,
|
||||
emptyStyle,
|
||||
rootStyle,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseStyleReturn = ReturnType<typeof useStyles>
|
30
packages/components/table-v2/src/composables/utils.ts
Normal file
30
packages/components/table-v2/src/composables/utils.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type { AnyColumns } from '../types'
|
||||
|
||||
export const calcColumnStyle = (
|
||||
column: AnyColumns[number],
|
||||
fixedColumn: boolean
|
||||
): CSSProperties => {
|
||||
const flex = {
|
||||
flexGrow: 0,
|
||||
flexShrink: 0,
|
||||
}
|
||||
|
||||
if (column.fixed) {
|
||||
flex.flexShrink = 1
|
||||
}
|
||||
|
||||
const style = {
|
||||
...(column.style ?? {}),
|
||||
...flex,
|
||||
flexBasis: 'auto',
|
||||
width: column.width,
|
||||
}
|
||||
|
||||
if (!fixedColumn) {
|
||||
if (column.maxWidth) style.maxWidth = column.maxWidth
|
||||
if (column.minWidth) style.maxWidth = column.minWidth
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
@ -32,7 +32,7 @@ const TableV2 = defineComponent({
|
||||
const {
|
||||
columnsStyles,
|
||||
fixedColumnsOnLeft,
|
||||
fixedColumnOnRight,
|
||||
fixedColumnsOnRight,
|
||||
mainColumns,
|
||||
mainTableHeight,
|
||||
fixedTableHeight,
|
||||
@ -159,7 +159,7 @@ const TableV2 = defineComponent({
|
||||
const rightTableProps = {
|
||||
cache,
|
||||
class: ns.e('right'),
|
||||
columns: unref(fixedColumnOnRight),
|
||||
columns: unref(fixedColumnsOnRight),
|
||||
data: _data,
|
||||
estimatedRowHeight,
|
||||
rightTableRef,
|
||||
|
@ -89,6 +89,7 @@ export type Column<T = any> = {
|
||||
}
|
||||
|
||||
export type Columns<T> = Column<T>[]
|
||||
export type AnyColumns = Columns<any>
|
||||
|
||||
export type SortBy = {
|
||||
key: KeyType
|
||||
|
@ -1,120 +1,97 @@
|
||||
import { computed, ref, shallowRef, toRef, unref, watch } from 'vue'
|
||||
import { isArray } from '@element-plus/utils'
|
||||
import {
|
||||
computed,
|
||||
getCurrentInstance,
|
||||
ref,
|
||||
shallowRef,
|
||||
toRef,
|
||||
unref,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import { debounce } from 'lodash-unified'
|
||||
import { addUnit, isArray, isNumber, isObject } from '@element-plus/utils'
|
||||
import { enforceUnit, sum } from './utils'
|
||||
import { useColumns } from './use-columns'
|
||||
import { FixedDir, SortOrder, oppositeOrderMap } from './constants'
|
||||
useColumns,
|
||||
useData,
|
||||
useRow,
|
||||
useScrollbar,
|
||||
useStyles,
|
||||
} from './composables'
|
||||
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type { FixedDirection, KeyType } from './types'
|
||||
import type { TableV2Props } from './table'
|
||||
import type { onRowRenderedParams } from './grid'
|
||||
import type {
|
||||
RowExpandParams,
|
||||
RowHeightChangedParams,
|
||||
RowHoverParams,
|
||||
} from './row'
|
||||
// component
|
||||
import type { TableGridInstance } from './table-grid'
|
||||
|
||||
type Nullable<T> = T | null
|
||||
type ScrollPos = { scrollLeft: number; scrollTop: number }
|
||||
type Heights = Record<KeyType, number>
|
||||
|
||||
function useTable(props: TableV2Props) {
|
||||
const vm = getCurrentInstance()!
|
||||
const { emit } = vm
|
||||
|
||||
const mainTableRef = ref<TableGridInstance>()
|
||||
const leftTableRef = ref<TableGridInstance>()
|
||||
const rightTableRef = ref<TableGridInstance>()
|
||||
const {
|
||||
columns,
|
||||
columnsStyles,
|
||||
columnsTotalWidth,
|
||||
fixedColumnsOnLeft,
|
||||
fixedColumnOnRight,
|
||||
fixedColumnsOnRight,
|
||||
hasFixedColumns,
|
||||
mainColumns,
|
||||
getColumn,
|
||||
} = useColumns(toRef(props, 'columns'), toRef(props, 'fixed'))
|
||||
|
||||
onColumnSorted,
|
||||
} = useColumns(props, toRef(props, 'columns'), toRef(props, 'fixed'))
|
||||
|
||||
const {
|
||||
scrollTo,
|
||||
scrollToLeft,
|
||||
scrollToTop,
|
||||
onScroll,
|
||||
onVerticalScroll,
|
||||
scrollPos,
|
||||
} = useScrollbar(props, {
|
||||
mainTableRef,
|
||||
leftTableRef,
|
||||
rightTableRef,
|
||||
|
||||
onMaybeEndReached,
|
||||
})
|
||||
|
||||
const {
|
||||
expandedRowKeys,
|
||||
hoveringRowKey,
|
||||
lastRenderedRowIndex,
|
||||
isDynamic,
|
||||
isResetting,
|
||||
rowHeights,
|
||||
|
||||
resetAfterIndex,
|
||||
onRowExpanded,
|
||||
onRowHeightChange,
|
||||
onRowHovered,
|
||||
onRowsRendered,
|
||||
} = useRow(props, {
|
||||
mainTableRef,
|
||||
leftTableRef,
|
||||
rightTableRef,
|
||||
|
||||
onMaybeEndReached,
|
||||
})
|
||||
|
||||
const { data, depthMap } = useData(props, {
|
||||
expandedRowKeys,
|
||||
lastRenderedRowIndex,
|
||||
resetAfterIndex,
|
||||
})
|
||||
|
||||
const {
|
||||
bodyWidth,
|
||||
fixedTableHeight,
|
||||
mainTableHeight,
|
||||
leftTableWidth,
|
||||
rightTableWidth,
|
||||
headerWidth,
|
||||
rowsHeight,
|
||||
windowHeight,
|
||||
footerHeight,
|
||||
emptyStyle,
|
||||
rootStyle,
|
||||
} = useStyles(props, {
|
||||
columnsTotalWidth,
|
||||
data,
|
||||
fixedColumnsOnLeft,
|
||||
fixedColumnsOnRight,
|
||||
})
|
||||
// state
|
||||
const expandedRowKeys = ref<KeyType[]>(props.defaultExpandedRowKeys || [])
|
||||
const depthMap = ref<Record<KeyType, number>>({})
|
||||
const hoveringRowKey = shallowRef<Nullable<KeyType>>(null)
|
||||
// const resizingKey = shallowRef<Nullable<KeyType>>(null)
|
||||
// const resizingWidth = shallowRef(0)
|
||||
const resetIndex = shallowRef<Nullable<number>>(null)
|
||||
const isResetting = shallowRef(false)
|
||||
const isScrolling = shallowRef(false)
|
||||
|
||||
// DOM/Component refs
|
||||
const containerRef = ref()
|
||||
const mainTableRef = ref<TableGridInstance>()
|
||||
const leftTableRef = ref<TableGridInstance>()
|
||||
const rightTableRef = ref<TableGridInstance>()
|
||||
|
||||
const scrollPos = ref<ScrollPos>({ scrollLeft: 0, scrollTop: 0 })
|
||||
const lastRenderedRowIndex = ref(-1)
|
||||
const rowHeights = ref<Heights>({})
|
||||
const pendingRowHeights = ref<Heights>({})
|
||||
const leftTableHeights = shallowRef<Heights>({})
|
||||
const mainTableHeights = shallowRef<Heights>({})
|
||||
const rightTableHeights = shallowRef<Heights>({})
|
||||
|
||||
const rowsHeight = computed(() => {
|
||||
const { rowHeight, estimatedRowHeight } = props
|
||||
const _data = unref(data)
|
||||
if (isNumber(estimatedRowHeight)) {
|
||||
return _data.length * estimatedRowHeight
|
||||
}
|
||||
|
||||
return _data.length * rowHeight
|
||||
})
|
||||
|
||||
const flattenedData = computed(() => {
|
||||
const depths: Record<KeyType, number> = {}
|
||||
const { data, rowKey } = props
|
||||
|
||||
const _expandedRowKeys = unref(expandedRowKeys)
|
||||
|
||||
if (!_expandedRowKeys || !_expandedRowKeys.length) return data
|
||||
|
||||
const array: any[] = []
|
||||
const keysSet = new Set()
|
||||
_expandedRowKeys.forEach((x) => keysSet.add(x))
|
||||
|
||||
let copy: any[] = data.slice()
|
||||
copy.forEach((x) => (depths[x[rowKey]] = 0))
|
||||
while (copy.length > 0) {
|
||||
const item = copy.shift()!
|
||||
|
||||
array.push(item)
|
||||
if (
|
||||
keysSet.has(item[rowKey]) &&
|
||||
Array.isArray(item.children) &&
|
||||
item.children.length > 0
|
||||
) {
|
||||
copy = [...item.children, ...copy]
|
||||
item.children.forEach(
|
||||
(child: any) => (depths[child[rowKey]] = depths[item[rowKey]] + 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
depthMap.value = depths
|
||||
return array
|
||||
})
|
||||
|
||||
const data = computed(() => {
|
||||
const { data, expandColumnKey } = props
|
||||
return expandColumnKey ? unref(flattenedData) : data
|
||||
})
|
||||
|
||||
const showEmpty = computed(() => {
|
||||
const noData = unref(data).length === 0
|
||||
@ -124,109 +101,14 @@ function useTable(props: TableV2Props) {
|
||||
: noData
|
||||
})
|
||||
|
||||
const isDynamic = computed(() => isNumber(props.estimatedRowHeight))
|
||||
function getRowHeight(rowIndex: number) {
|
||||
const { estimatedRowHeight, rowHeight, rowKey } = props
|
||||
|
||||
const bodyWidth = computed(() => {
|
||||
const { fixed, width, vScrollbarSize } = props
|
||||
const ret = width - vScrollbarSize
|
||||
return fixed ? Math.max(Math.round(unref(columnsTotalWidth)), ret) : ret
|
||||
})
|
||||
if (!estimatedRowHeight) return rowHeight
|
||||
|
||||
const rootStyle = computed<CSSProperties>(() => {
|
||||
const { style = {}, height, width } = props
|
||||
return enforceUnit({
|
||||
...style,
|
||||
height,
|
||||
width,
|
||||
})
|
||||
})
|
||||
|
||||
const headerWidth = computed(
|
||||
() => unref(bodyWidth) + (props.fixed ? props.vScrollbarSize : 0)
|
||||
)
|
||||
|
||||
const mainTableHeight = computed(() => {
|
||||
const { height = 0, maxHeight = 0, footerHeight, hScrollbarSize } = props
|
||||
|
||||
if (maxHeight > 0) {
|
||||
const _fixedRowsHeight = unref(fixedRowsHeight)
|
||||
const _rowsHeight = unref(rowsHeight)
|
||||
const _headerHeight = unref(headerHeight)
|
||||
const total =
|
||||
_headerHeight + _fixedRowsHeight + _rowsHeight + hScrollbarSize
|
||||
|
||||
return Math.min(total, maxHeight - footerHeight)
|
||||
}
|
||||
|
||||
return height - footerHeight
|
||||
})
|
||||
|
||||
const footerHeight = computed(() =>
|
||||
enforceUnit({ height: props.footerHeight })
|
||||
)
|
||||
|
||||
const emptyStyle = computed<CSSProperties>(() => ({
|
||||
top: addUnit(unref(headerHeight)),
|
||||
bottom: addUnit(props.footerHeight),
|
||||
width: addUnit(props.width),
|
||||
}))
|
||||
|
||||
const fixedTableHeight = computed(() => {
|
||||
const { maxHeight } = props
|
||||
const tableHeight = unref(mainTableHeight)
|
||||
if (isNumber(maxHeight) && maxHeight > 0) return tableHeight
|
||||
|
||||
const totalHeight =
|
||||
unref(rowsHeight) + unref(headerHeight) + unref(fixedRowsHeight)
|
||||
|
||||
return Math.min(tableHeight, totalHeight)
|
||||
})
|
||||
|
||||
const mapColumn = (column: TableV2Props['columns'][number]) => column.width
|
||||
|
||||
const leftTableWidth = computed(() =>
|
||||
sum(unref(fixedColumnsOnLeft).map(mapColumn))
|
||||
)
|
||||
|
||||
const rightTableWidth = computed(() =>
|
||||
sum(unref(fixedColumnOnRight).map(mapColumn))
|
||||
)
|
||||
|
||||
const headerHeight = computed(() => sum(props.headerHeight))
|
||||
|
||||
const fixedRowsHeight = computed(() => {
|
||||
return (props.fixedData?.length || 0) * props.rowHeight
|
||||
})
|
||||
|
||||
const windowHeight = computed(() => {
|
||||
return unref(mainTableHeight) - unref(headerHeight) - unref(fixedRowsHeight)
|
||||
})
|
||||
|
||||
function doScroll(params: ScrollPos) {
|
||||
const { scrollTop } = params
|
||||
|
||||
mainTableRef.value?.scrollTo(params)
|
||||
leftTableRef.value?.scrollToTop(scrollTop)
|
||||
rightTableRef.value?.scrollToTop(scrollTop)
|
||||
}
|
||||
|
||||
// methods
|
||||
function scrollTo(params: ScrollPos) {
|
||||
scrollPos.value = params
|
||||
|
||||
doScroll(params)
|
||||
}
|
||||
|
||||
function scrollToTop(scrollTop: number) {
|
||||
scrollPos.value.scrollTop = scrollTop
|
||||
|
||||
doScroll(unref(scrollPos))
|
||||
}
|
||||
|
||||
function scrollToLeft(scrollLeft: number) {
|
||||
scrollPos.value.scrollLeft = scrollLeft
|
||||
|
||||
mainTableRef.value?.scrollTo?.(unref(scrollPos))
|
||||
return (
|
||||
unref(rowHeights)[unref(data)[rowIndex][rowKey]] || estimatedRowHeight
|
||||
)
|
||||
}
|
||||
|
||||
function onMaybeEndReached() {
|
||||
@ -249,162 +131,8 @@ function useTable(props: TableV2Props) {
|
||||
}
|
||||
}
|
||||
|
||||
function getRowHeight(rowIndex: number) {
|
||||
const { estimatedRowHeight, rowHeight, rowKey } = props
|
||||
|
||||
if (!estimatedRowHeight) return rowHeight
|
||||
|
||||
return (
|
||||
unref(rowHeights)[unref(data)[rowIndex][rowKey]] || estimatedRowHeight
|
||||
)
|
||||
}
|
||||
|
||||
const flushingRowHeights = debounce(() => {
|
||||
// console.log('update')
|
||||
isResetting.value = true
|
||||
// console.log(JSON.stringify(unref(pendingRowHeights)))
|
||||
rowHeights.value = { ...unref(rowHeights), ...unref(pendingRowHeights) }
|
||||
resetAfterIndex(unref(resetIndex)!, false)
|
||||
pendingRowHeights.value = {}
|
||||
// force update
|
||||
resetIndex.value = null
|
||||
mainTableRef.value?.forceUpdate()
|
||||
leftTableRef.value?.forceUpdate()
|
||||
rightTableRef.value?.forceUpdate()
|
||||
vm.proxy?.$forceUpdate()
|
||||
isResetting.value = false
|
||||
}, 0)
|
||||
|
||||
function resetAfterIndex(index: number, forceUpdate = false) {
|
||||
if (!unref(isDynamic)) return
|
||||
;[mainTableRef, leftTableRef, rightTableRef].forEach((tableRef) => {
|
||||
const table = unref(tableRef)
|
||||
if (table) table.resetAfterRowIndex(index, forceUpdate)
|
||||
})
|
||||
}
|
||||
// events
|
||||
|
||||
function onScroll(params: ScrollPos) {
|
||||
scrollTo(params)
|
||||
props.onScroll?.(params)
|
||||
}
|
||||
|
||||
function onVerticalScroll({ scrollTop }: ScrollPos) {
|
||||
const { scrollTop: currentScrollTop } = unref(scrollPos)
|
||||
if (scrollTop !== currentScrollTop) scrollToTop(scrollTop)
|
||||
}
|
||||
|
||||
function onRowsRendered(params: onRowRenderedParams) {
|
||||
props.onRowRendered?.(params)
|
||||
|
||||
if (params.rowCacheEnd > unref(lastRenderedRowIndex)) {
|
||||
lastRenderedRowIndex.value = params.rowCacheEnd
|
||||
}
|
||||
}
|
||||
|
||||
function onRowHovered({ hovered, rowKey }: RowHoverParams<any>) {
|
||||
hoveringRowKey.value = hovered ? rowKey : null
|
||||
}
|
||||
|
||||
function onRowExpanded({
|
||||
expanded,
|
||||
rowData,
|
||||
rowIndex,
|
||||
rowKey,
|
||||
}: RowExpandParams<any>) {
|
||||
const _expandedRowKeys = [...unref(expandedRowKeys)]
|
||||
const currentKeyIndex = _expandedRowKeys.indexOf(rowKey)
|
||||
if (expanded) {
|
||||
if (currentKeyIndex === -1) _expandedRowKeys.push(rowKey)
|
||||
} else {
|
||||
if (currentKeyIndex > -1) _expandedRowKeys.splice(currentKeyIndex, 1)
|
||||
}
|
||||
expandedRowKeys.value = _expandedRowKeys
|
||||
|
||||
emit('update:expandedRowKeys', _expandedRowKeys)
|
||||
props.onRowExpand?.({
|
||||
expanded,
|
||||
rowData,
|
||||
rowIndex,
|
||||
rowKey,
|
||||
})
|
||||
// If this is not controlled, then use this to notify changes
|
||||
props.onExpandedRowsChange?.(_expandedRowKeys)
|
||||
}
|
||||
|
||||
function onColumnSorted(e: MouseEvent) {
|
||||
const { key } = (e.currentTarget as HTMLElement).dataset
|
||||
if (!key) return
|
||||
const { sortState, sortBy } = props
|
||||
|
||||
let order = SortOrder.ASC
|
||||
|
||||
if (isObject(sortState)) {
|
||||
order = oppositeOrderMap[sortState[key]]
|
||||
} else {
|
||||
order = oppositeOrderMap[sortBy.order]
|
||||
}
|
||||
|
||||
props.onColumnSort?.({ column: getColumn(key)!, key, order })
|
||||
}
|
||||
|
||||
function resetHeights(rowKey: KeyType, height: number, rowIdx: number) {
|
||||
const resetIdx = unref(resetIndex)
|
||||
if (resetIdx === null) {
|
||||
resetIndex.value = rowIdx
|
||||
} else {
|
||||
if (resetIdx > rowIdx) {
|
||||
resetIndex.value = rowIdx
|
||||
}
|
||||
}
|
||||
|
||||
pendingRowHeights.value[rowKey] = height
|
||||
}
|
||||
|
||||
function onRowHeightChange(
|
||||
{ rowKey, height, rowIndex }: RowHeightChangedParams,
|
||||
fixedDir: FixedDirection
|
||||
) {
|
||||
if (!fixedDir) {
|
||||
mainTableHeights.value[rowKey] = height
|
||||
} else {
|
||||
if (fixedDir === FixedDir.RIGHT) {
|
||||
rightTableHeights.value[rowKey] = height
|
||||
} else {
|
||||
leftTableHeights.value[rowKey] = height
|
||||
}
|
||||
}
|
||||
|
||||
const maximumHeight = Math.max(
|
||||
...[leftTableHeights, rightTableHeights, mainTableHeights].map(
|
||||
(records) => records.value[rowKey] || 0
|
||||
)
|
||||
)
|
||||
|
||||
if (unref(rowHeights)[rowKey] !== maximumHeight) {
|
||||
resetHeights(rowKey, maximumHeight, rowIndex)
|
||||
flushingRowHeights()
|
||||
}
|
||||
}
|
||||
|
||||
// When scrollTop changes, maybe reaching the bottom
|
||||
watch(
|
||||
() => unref(scrollPos).scrollTop,
|
||||
(cur, prev) => {
|
||||
if (cur > prev) onMaybeEndReached()
|
||||
}
|
||||
)
|
||||
|
||||
watch(data, (val, prev) => {
|
||||
if (val !== prev) {
|
||||
lastRenderedRowIndex.value = -1
|
||||
resetAfterIndex(0, true)
|
||||
}
|
||||
})
|
||||
|
||||
// when rendered row changes, maybe reaching the bottom
|
||||
watch(lastRenderedRowIndex, () => onMaybeEndReached())
|
||||
|
||||
watch(
|
||||
() => props.expandedRowKeys,
|
||||
(val) => (expandedRowKeys.value = val),
|
||||
@ -433,7 +161,7 @@ function useTable(props: TableV2Props) {
|
||||
expandedRowKeys,
|
||||
depthMap,
|
||||
fixedColumnsOnLeft,
|
||||
fixedColumnOnRight,
|
||||
fixedColumnsOnRight,
|
||||
mainColumns,
|
||||
// metadata
|
||||
bodyWidth,
|
||||
@ -450,9 +178,6 @@ function useTable(props: TableV2Props) {
|
||||
|
||||
// methods
|
||||
getRowHeight,
|
||||
scrollTo,
|
||||
scrollToLeft,
|
||||
scrollToTop,
|
||||
|
||||
// event handlers
|
||||
onColumnSorted,
|
||||
@ -460,6 +185,10 @@ function useTable(props: TableV2Props) {
|
||||
onRowExpanded,
|
||||
onRowsRendered,
|
||||
onRowHeightChange,
|
||||
// use scrollbars
|
||||
scrollTo,
|
||||
scrollToLeft,
|
||||
scrollToTop,
|
||||
onScroll,
|
||||
onVerticalScroll,
|
||||
}
|
||||
|
@ -204,7 +204,9 @@ export type GridItemRenderedEvtParams = {
|
||||
|
||||
export type GridScrollOptions = { scrollLeft?: number; scrollTop?: number }
|
||||
|
||||
export type GridItemKeyGetter = <T>(args: {
|
||||
export type GridItemKeyGetter = <
|
||||
T extends { [key: string | number]: any }
|
||||
>(args: {
|
||||
columnIndex: number
|
||||
data: T
|
||||
rowIndex: number
|
||||
|
Loading…
Reference in New Issue
Block a user