feat(components): [virtual-table] compsables (#7341)

- Split `use-table` into separate files for better readability
This commit is contained in:
JeremyWuuuuu 2022-04-23 20:05:20 +08:00 committed by GitHub
parent 5a52e61a3a
commit 6d9e56a106
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 613 additions and 399 deletions

View 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'

View File

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

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

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

View File

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

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

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

View File

@ -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,

View File

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

View File

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

View File

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