mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2024-11-21 01:13:16 +08:00
feat(data-table): supports horizontal virtual scrolling
This commit is contained in:
parent
2e453472d8
commit
17b96c9027
@ -84,7 +84,7 @@
|
|||||||
"treemate": "^0.3.11",
|
"treemate": "^0.3.11",
|
||||||
"vdirs": "^0.1.8",
|
"vdirs": "^0.1.8",
|
||||||
"vooks": "^0.2.12",
|
"vooks": "^0.2.12",
|
||||||
"vueuc": "^0.4.58"
|
"vueuc": "^0.4.63"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^2.22.0",
|
"@antfu/eslint-config": "^2.22.0",
|
||||||
|
@ -49,6 +49,7 @@ render-header
|
|||||||
custom-style.vue
|
custom-style.vue
|
||||||
ajax-usage
|
ajax-usage
|
||||||
virtual.vue
|
virtual.vue
|
||||||
|
virtual-x.vue
|
||||||
custom-filter-menu.vue
|
custom-filter-menu.vue
|
||||||
tree.vue
|
tree.vue
|
||||||
flex-height.vue
|
flex-height.vue
|
||||||
|
82
src/data-table/demos/enUS/virtual-x.demo.vue
Normal file
82
src/data-table/demos/enUS/virtual-x.demo.vue
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<markdown>
|
||||||
|
# Large data (rows & cols)
|
||||||
|
|
||||||
|
If you have a large amount of row and column data, such as thousands of rows and hundreds of columns, `naive-ui` provides horizontal + vertical virtual scrolling functionality.
|
||||||
|
|
||||||
|
Due to the inherent complexity of horizontal virtual scrolling, the corresponding configuration can be quite complex, with most of the following content being necessary:
|
||||||
|
|
||||||
|
1. Configure `virtual-scroll` to enable vertical virtual scrolling.
|
||||||
|
2. Configure `virtual-scroll-x` to enable horizontal virtual scrolling:
|
||||||
|
- Each column needs to have a `width` property configured.
|
||||||
|
- Configure the `scroll-x` property, setting it to the total width of all columns.
|
||||||
|
- Configure the `min-row-height` property, setting it to the minimum height of each row, where all rows must be larger than this value.
|
||||||
|
- Configure the `height-for-row` property, which is used to set the height of each row (since only a portion of the cells in each row are always visible, this cannot be automatically calculated). If not configured, the height of each row will be set to `min-row-height`.
|
||||||
|
3. If needed, configure `virtual-scroll-header`. By default, the header will still be fully rendered to maintain compatibility. You can enable virtual rendering for the header with this configuration:
|
||||||
|
- Configure the `header-height` property, setting it to the height of the header.
|
||||||
|
|
||||||
|
The example below corresponds to a table with 1000 rows * 1000 columns.
|
||||||
|
|
||||||
|
`naive-ui`'s table can easily support table data in the millions. You won't find this kind of functionality in many free component libraries.
|
||||||
|
</markdown>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
|
||||||
|
interface RowData {
|
||||||
|
key: number
|
||||||
|
name: string
|
||||||
|
age: number
|
||||||
|
address: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: DataTableColumns<RowData> = []
|
||||||
|
|
||||||
|
let scrollX = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < 1000; ++i) {
|
||||||
|
scrollX += 100
|
||||||
|
columns.push({
|
||||||
|
title: `Col ${i}`,
|
||||||
|
width: 100,
|
||||||
|
key: i,
|
||||||
|
fixed: i <= 1 ? 'left' : i > 997 ? 'right' : undefined,
|
||||||
|
render(_, rowIndex) {
|
||||||
|
return `${i}-${rowIndex}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
setup() {
|
||||||
|
const data: RowData[] = Array.from({ length: 1000 }).map((_, index) => ({
|
||||||
|
key: index,
|
||||||
|
name: `Edward King ${index}`,
|
||||||
|
age: 32,
|
||||||
|
address: `London, Park Lane no. ${index}`
|
||||||
|
}))
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
scrollX,
|
||||||
|
minRowHeight: 48,
|
||||||
|
heightForRow: () => 48
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="data"
|
||||||
|
:max-height="250"
|
||||||
|
virtual-scroll
|
||||||
|
virtual-scroll-x
|
||||||
|
:scroll-x="scrollX"
|
||||||
|
:min-row-height="48"
|
||||||
|
:height-for-row="heightForRow"
|
||||||
|
virtual-scroll-header
|
||||||
|
:header-height="48"
|
||||||
|
/>
|
||||||
|
</template>
|
@ -51,6 +51,7 @@ render-header
|
|||||||
custom-style.vue
|
custom-style.vue
|
||||||
ajax-usage
|
ajax-usage
|
||||||
virtual.vue
|
virtual.vue
|
||||||
|
virtual-x.vue
|
||||||
custom-filter-menu.vue
|
custom-filter-menu.vue
|
||||||
tree.vue
|
tree.vue
|
||||||
flex-height.vue
|
flex-height.vue
|
||||||
|
82
src/data-table/demos/zhCN/virtual-x.demo.vue
Normal file
82
src/data-table/demos/zhCN/virtual-x.demo.vue
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<markdown>
|
||||||
|
# 大量数据(行和列)
|
||||||
|
|
||||||
|
如果你有大量行数据和列数据,例如几千行 + 几百列,`naive-ui` 提供了横向 + 纵向虚拟滚动的功能。
|
||||||
|
|
||||||
|
因为横向虚拟滚动的天然的复杂性,对应的配置也会较为复杂,以下多数内容都是必须的:
|
||||||
|
|
||||||
|
1. 配置 `virtual-scroll` 打开纵向虚拟滚动
|
||||||
|
2. 配置 `virtual-scroll-x` 打开横向虚拟滚动
|
||||||
|
- 每一个列都需要配置 `width` 属性
|
||||||
|
- 配置 `scroll-x` 属性,设为所有列的总宽度
|
||||||
|
- 配置 `min-row-height` 属性,设为每一列的最小高度,所有的列必须比这个值更大
|
||||||
|
- 配置 `height-for-row` 属性,用于配置每一行的高度(因为每一行永远只有一部分格子是可见的,因此无法自动求出),如果不配置,每一行的高度会被设为 `min-row-height`
|
||||||
|
3. 如有需要,配置 `virtual-scroll-header`,默认情况下,表头依然会全量渲染以保持兼容性,你可以通过此配置来打开表头的虚拟渲染
|
||||||
|
- 配置 `header-height` 属性,设为表头的高度
|
||||||
|
|
||||||
|
下面的例子对应了一个 1000 行 * 1000 列的表格。
|
||||||
|
|
||||||
|
`naive-ui` 的表格可以轻松的支持千万级的表格数据,你在不收钱的组件库不容易找得到这样的功能。
|
||||||
|
</markdown>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
|
||||||
|
interface RowData {
|
||||||
|
key: number
|
||||||
|
name: string
|
||||||
|
age: number
|
||||||
|
address: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: DataTableColumns<RowData> = []
|
||||||
|
|
||||||
|
let scrollX = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < 1000; ++i) {
|
||||||
|
scrollX += 100
|
||||||
|
columns.push({
|
||||||
|
title: `Col ${i}`,
|
||||||
|
width: 100,
|
||||||
|
key: i,
|
||||||
|
fixed: i <= 1 ? 'left' : i > 997 ? 'right' : undefined,
|
||||||
|
render(_, rowIndex) {
|
||||||
|
return `${i}-${rowIndex}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
setup() {
|
||||||
|
const data: RowData[] = Array.from({ length: 1000 }).map((_, index) => ({
|
||||||
|
key: index,
|
||||||
|
name: `Edward King ${index}`,
|
||||||
|
age: 32,
|
||||||
|
address: `London, Park Lane no. ${index}`
|
||||||
|
}))
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
scrollX,
|
||||||
|
minRowHeight: 48,
|
||||||
|
heightForRow: () => 48
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="data"
|
||||||
|
:max-height="250"
|
||||||
|
virtual-scroll
|
||||||
|
virtual-scroll-x
|
||||||
|
:scroll-x="scrollX"
|
||||||
|
:min-row-height="48"
|
||||||
|
:height-for-row="heightForRow"
|
||||||
|
virtual-scroll-header
|
||||||
|
:header-height="48"
|
||||||
|
/>
|
||||||
|
</template>
|
@ -244,6 +244,11 @@ export default defineComponent({
|
|||||||
renderExpandRef,
|
renderExpandRef,
|
||||||
summaryRef: toRef(props, 'summary'),
|
summaryRef: toRef(props, 'summary'),
|
||||||
virtualScrollRef: toRef(props, 'virtualScroll'),
|
virtualScrollRef: toRef(props, 'virtualScroll'),
|
||||||
|
virtualScrollXRef: toRef(props, 'virtualScrollX'),
|
||||||
|
heightForRowRef: toRef(props, 'heightForRow'),
|
||||||
|
minRowHeightRef: toRef(props, 'minRowHeight'),
|
||||||
|
virtualScrollHeaderRef: toRef(props, 'virtualScrollHeader'),
|
||||||
|
headerHeightRef: toRef(props, 'headerHeight'),
|
||||||
rowPropsRef: toRef(props, 'rowProps'),
|
rowPropsRef: toRef(props, 'rowProps'),
|
||||||
stripedRef: toRef(props, 'striped'),
|
stripedRef: toRef(props, 'striped'),
|
||||||
checkOptionsRef: computed(() => {
|
checkOptionsRef: computed(() => {
|
||||||
|
@ -20,6 +20,7 @@ export default defineComponent({
|
|||||||
maxHeightRef,
|
maxHeightRef,
|
||||||
minHeightRef,
|
minHeightRef,
|
||||||
flexHeightRef,
|
flexHeightRef,
|
||||||
|
virtualScrollHeaderRef,
|
||||||
syncScrollState
|
syncScrollState
|
||||||
} = inject(dataTableInjectionKey)!
|
} = inject(dataTableInjectionKey)!
|
||||||
|
|
||||||
@ -47,7 +48,12 @@ export default defineComponent({
|
|||||||
function getHeaderElement(): HTMLElement | null {
|
function getHeaderElement(): HTMLElement | null {
|
||||||
const { value } = headerInstRef
|
const { value } = headerInstRef
|
||||||
if (value) {
|
if (value) {
|
||||||
return value.$el
|
if (virtualScrollHeaderRef.value) {
|
||||||
|
return value.virtualListRef?.listElRef || null
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return value.$el
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
|
import type { CSSProperties, PropType, VNode, VNodeChild } from 'vue'
|
||||||
import {
|
import {
|
||||||
type CSSProperties,
|
|
||||||
Fragment,
|
Fragment,
|
||||||
type PropType,
|
|
||||||
type VNode,
|
|
||||||
computed,
|
computed,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
h,
|
h,
|
||||||
@ -12,7 +10,8 @@ import {
|
|||||||
watchEffect
|
watchEffect
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { pxfy, repeat } from 'seemly'
|
import { pxfy, repeat } from 'seemly'
|
||||||
import { VResizeObserver, VirtualList, type VirtualListInst } from 'vueuc'
|
import { VResizeObserver, VirtualList } from 'vueuc'
|
||||||
|
import type { VirtualListInst } from 'vueuc'
|
||||||
import type { CNode } from 'css-render'
|
import type { CNode } from 'css-render'
|
||||||
import { useMemo } from 'vooks'
|
import { useMemo } from 'vooks'
|
||||||
import { cssrAnchorMetaName } from '../../../_mixins/common'
|
import { cssrAnchorMetaName } from '../../../_mixins/common'
|
||||||
@ -170,6 +169,9 @@ export default defineComponent({
|
|||||||
summaryRef,
|
summaryRef,
|
||||||
mergedSortStateRef,
|
mergedSortStateRef,
|
||||||
virtualScrollRef,
|
virtualScrollRef,
|
||||||
|
virtualScrollXRef,
|
||||||
|
heightForRowRef,
|
||||||
|
minRowHeightRef,
|
||||||
componentId,
|
componentId,
|
||||||
mergedTableLayoutRef,
|
mergedTableLayoutRef,
|
||||||
childTriggerColIndexRef,
|
childTriggerColIndexRef,
|
||||||
@ -499,6 +501,9 @@ export default defineComponent({
|
|||||||
hoverKey: hoverKeyRef,
|
hoverKey: hoverKeyRef,
|
||||||
mergedSortState: mergedSortStateRef,
|
mergedSortState: mergedSortStateRef,
|
||||||
virtualScroll: virtualScrollRef,
|
virtualScroll: virtualScrollRef,
|
||||||
|
virtualScrollX: virtualScrollXRef,
|
||||||
|
heightForRow: heightForRowRef,
|
||||||
|
minRowHeight: minRowHeightRef,
|
||||||
mergedTableLayout: mergedTableLayoutRef,
|
mergedTableLayout: mergedTableLayoutRef,
|
||||||
childTriggerColIndex: childTriggerColIndexRef,
|
childTriggerColIndex: childTriggerColIndexRef,
|
||||||
indent: indentRef,
|
indent: indentRef,
|
||||||
@ -597,7 +602,10 @@ export default defineComponent({
|
|||||||
summary,
|
summary,
|
||||||
handleCheckboxUpdateChecked,
|
handleCheckboxUpdateChecked,
|
||||||
handleRadioUpdateChecked,
|
handleRadioUpdateChecked,
|
||||||
handleUpdateExpanded
|
handleUpdateExpanded,
|
||||||
|
heightForRow,
|
||||||
|
minRowHeight,
|
||||||
|
virtualScrollX
|
||||||
} = this
|
} = this
|
||||||
const { length: colCount } = cols
|
const { length: colCount } = cols
|
||||||
|
|
||||||
@ -683,11 +691,40 @@ export default defineComponent({
|
|||||||
const bodyWidthPx
|
const bodyWidthPx
|
||||||
= bodyWidth === null ? undefined : `${bodyWidth}px`
|
= bodyWidth === null ? undefined : `${bodyWidth}px`
|
||||||
|
|
||||||
const renderRow = (
|
const CellComponent = (this.virtualScrollX ? 'div' : 'td') as 'td'
|
||||||
rowInfo: RowRenderInfo,
|
let leftFixedColsCount = 0
|
||||||
displayedRowIndex: number,
|
let rightFixedColsCount = 0
|
||||||
|
if (virtualScrollX) {
|
||||||
|
cols.forEach((col) => {
|
||||||
|
if (col.column.fixed === 'left') {
|
||||||
|
leftFixedColsCount++
|
||||||
|
}
|
||||||
|
else if (col.column.fixed === 'right') {
|
||||||
|
rightFixedColsCount++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderRow = ({
|
||||||
|
// Normal
|
||||||
|
rowInfo,
|
||||||
|
displayedRowIndex,
|
||||||
|
isVirtual,
|
||||||
|
// Virtual X
|
||||||
|
isVirtualX,
|
||||||
|
startColIndex,
|
||||||
|
endColIndex,
|
||||||
|
getLeft
|
||||||
|
}: {
|
||||||
|
rowInfo: RowRenderInfo
|
||||||
|
displayedRowIndex: number
|
||||||
isVirtual: boolean
|
isVirtual: boolean
|
||||||
): VNode => {
|
// for horizontal virtual list
|
||||||
|
isVirtualX: boolean
|
||||||
|
startColIndex: number
|
||||||
|
endColIndex: number
|
||||||
|
getLeft: (index: number) => number
|
||||||
|
}): VNode => {
|
||||||
const { index: actualRowIndex } = rowInfo
|
const { index: actualRowIndex } = rowInfo
|
||||||
if ('isExpandedRow' in rowInfo) {
|
if ('isExpandedRow' in rowInfo) {
|
||||||
const {
|
const {
|
||||||
@ -735,6 +772,244 @@ export default defineComponent({
|
|||||||
= typeof rowClassName === 'string'
|
= typeof rowClassName === 'string'
|
||||||
? rowClassName
|
? rowClassName
|
||||||
: createRowClassName(rowData, actualRowIndex, rowClassName)
|
: createRowClassName(rowData, actualRowIndex, rowClassName)
|
||||||
|
const iteratedCols = isVirtualX
|
||||||
|
? cols.filter((col, index) => {
|
||||||
|
if (startColIndex <= index && index <= endColIndex)
|
||||||
|
return true
|
||||||
|
if (col.column.fixed) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
: cols
|
||||||
|
const virtualXRowHeight = isVirtualX
|
||||||
|
? pxfy(
|
||||||
|
heightForRow?.(rowData, actualRowIndex)
|
||||||
|
|| minRowHeight
|
||||||
|
|| 28
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
const cells = iteratedCols.map((col) => {
|
||||||
|
const colIndex = col.index
|
||||||
|
if (displayedRowIndex in cordToPass) {
|
||||||
|
const cordOfRowToPass = cordToPass[displayedRowIndex]
|
||||||
|
const indexInCordOfRowToPass
|
||||||
|
= cordOfRowToPass.indexOf(colIndex)
|
||||||
|
if (~indexInCordOfRowToPass) {
|
||||||
|
cordOfRowToPass.splice(indexInCordOfRowToPass, 1)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Simplify row calculation
|
||||||
|
const { column } = col
|
||||||
|
const colKey = getColKey(col)
|
||||||
|
const { rowSpan, colSpan } = column
|
||||||
|
const mergedColSpan = isSummary
|
||||||
|
? rowInfo.tmNode.rawNode[colKey]?.colSpan || 1 // optional for #1276
|
||||||
|
: colSpan
|
||||||
|
? colSpan(rowData, actualRowIndex)
|
||||||
|
: 1
|
||||||
|
const mergedRowSpan = isSummary
|
||||||
|
? rowInfo.tmNode.rawNode[colKey]?.rowSpan || 1 // optional for #1276
|
||||||
|
: rowSpan
|
||||||
|
? rowSpan(rowData, actualRowIndex)
|
||||||
|
: 1
|
||||||
|
const isLastCol = colIndex + mergedColSpan === colCount
|
||||||
|
const isLastRow = displayedRowIndex + mergedRowSpan === rowCount
|
||||||
|
const isCrossRowTd = mergedRowSpan > 1
|
||||||
|
if (isCrossRowTd) {
|
||||||
|
cordKey[displayedRowIndex] = {
|
||||||
|
[colIndex]: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mergedColSpan > 1 || isCrossRowTd) {
|
||||||
|
for (
|
||||||
|
let i = displayedRowIndex;
|
||||||
|
i < displayedRowIndex + mergedRowSpan;
|
||||||
|
++i
|
||||||
|
) {
|
||||||
|
if (isCrossRowTd) {
|
||||||
|
cordKey[displayedRowIndex][colIndex].push(
|
||||||
|
rowIndexToKey[i]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for (let j = colIndex; j < colIndex + mergedColSpan; ++j) {
|
||||||
|
if (i === displayedRowIndex && j === colIndex) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!(i in cordToPass)) {
|
||||||
|
cordToPass[i] = [j]
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cordToPass[i].push(j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const hoverKey = isCrossRowTd ? this.hoverKey : null
|
||||||
|
const { cellProps } = column
|
||||||
|
const resolvedCellProps = cellProps?.(rowData, actualRowIndex)
|
||||||
|
const indentOffsetStyle = {
|
||||||
|
'--indent-offset': '' as string | number
|
||||||
|
}
|
||||||
|
const FinalCellComponent = column.fixed ? 'td' : CellComponent
|
||||||
|
return (
|
||||||
|
<FinalCellComponent
|
||||||
|
{...resolvedCellProps}
|
||||||
|
key={colKey}
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
textAlign: column.align || undefined,
|
||||||
|
width: pxfy(column.width)
|
||||||
|
},
|
||||||
|
isVirtualX && {
|
||||||
|
height: virtualXRowHeight
|
||||||
|
},
|
||||||
|
isVirtualX && !column.fixed
|
||||||
|
? {
|
||||||
|
position: 'absolute',
|
||||||
|
left: pxfy(getLeft(colIndex)),
|
||||||
|
top: 0,
|
||||||
|
bottom: 0
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
left: pxfy(fixedColumnLeftMap[colKey]?.start),
|
||||||
|
right: pxfy(fixedColumnRightMap[colKey]?.start)
|
||||||
|
},
|
||||||
|
indentOffsetStyle as CSSProperties,
|
||||||
|
resolvedCellProps?.style || ''
|
||||||
|
]}
|
||||||
|
colspan={mergedColSpan}
|
||||||
|
rowspan={isVirtual ? undefined : mergedRowSpan}
|
||||||
|
data-col-key={colKey}
|
||||||
|
class={[
|
||||||
|
`${mergedClsPrefix}-data-table-td`,
|
||||||
|
column.className,
|
||||||
|
resolvedCellProps?.class,
|
||||||
|
isSummary && `${mergedClsPrefix}-data-table-td--summary`,
|
||||||
|
hoverKey !== null
|
||||||
|
&& cordKey[displayedRowIndex][colIndex].includes(
|
||||||
|
hoverKey
|
||||||
|
)
|
||||||
|
&& `${mergedClsPrefix}-data-table-td--hover`,
|
||||||
|
isColumnSorting(column, mergedSortState)
|
||||||
|
&& `${mergedClsPrefix}-data-table-td--sorting`,
|
||||||
|
column.fixed
|
||||||
|
&& `${mergedClsPrefix}-data-table-td--fixed-${column.fixed}`,
|
||||||
|
column.align
|
||||||
|
&& `${mergedClsPrefix}-data-table-td--${column.align}-align`,
|
||||||
|
column.type === 'selection'
|
||||||
|
&& `${mergedClsPrefix}-data-table-td--selection`,
|
||||||
|
column.type === 'expand'
|
||||||
|
&& `${mergedClsPrefix}-data-table-td--expand`,
|
||||||
|
isLastCol && `${mergedClsPrefix}-data-table-td--last-col`,
|
||||||
|
isLastRow && `${mergedClsPrefix}-data-table-td--last-row`
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{hasChildren && colIndex === childTriggerColIndex
|
||||||
|
? [
|
||||||
|
repeat(
|
||||||
|
(indentOffsetStyle['--indent-offset'] = isSummary
|
||||||
|
? 0
|
||||||
|
: rowInfo.tmNode.level),
|
||||||
|
<div
|
||||||
|
class={`${mergedClsPrefix}-data-table-indent`}
|
||||||
|
style={indentStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
isSummary || rowInfo.tmNode.isLeaf ? (
|
||||||
|
<div
|
||||||
|
class={`${mergedClsPrefix}-data-table-expand-placeholder`}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ExpandTrigger
|
||||||
|
class={`${mergedClsPrefix}-data-table-expand-trigger`}
|
||||||
|
clsPrefix={mergedClsPrefix}
|
||||||
|
expanded={expanded}
|
||||||
|
rowData={rowData}
|
||||||
|
renderExpandIcon={this.renderExpandIcon}
|
||||||
|
loading={loadingKeySet.has(rowInfo.key)}
|
||||||
|
onClick={() => {
|
||||||
|
handleUpdateExpanded(rowKey, rowInfo.tmNode)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
]
|
||||||
|
: null}
|
||||||
|
{column.type === 'selection' ? (
|
||||||
|
!isSummary ? (
|
||||||
|
column.multiple === false ? (
|
||||||
|
<RenderSafeRadio
|
||||||
|
key={currentPage}
|
||||||
|
rowKey={rowKey}
|
||||||
|
disabled={rowInfo.tmNode.disabled}
|
||||||
|
onUpdateChecked={() => {
|
||||||
|
handleRadioUpdateChecked(rowInfo.tmNode)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<RenderSafeCheckbox
|
||||||
|
key={currentPage}
|
||||||
|
rowKey={rowKey}
|
||||||
|
disabled={rowInfo.tmNode.disabled}
|
||||||
|
onUpdateChecked={(checked: boolean, e) => {
|
||||||
|
handleCheckboxUpdateChecked(
|
||||||
|
rowInfo.tmNode,
|
||||||
|
checked,
|
||||||
|
e.shiftKey
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : null
|
||||||
|
) : column.type === 'expand' ? (
|
||||||
|
!isSummary ? (
|
||||||
|
!column.expandable || column.expandable?.(rowData) ? (
|
||||||
|
<ExpandTrigger
|
||||||
|
clsPrefix={mergedClsPrefix}
|
||||||
|
rowData={rowData}
|
||||||
|
expanded={expanded}
|
||||||
|
renderExpandIcon={this.renderExpandIcon}
|
||||||
|
onClick={() => {
|
||||||
|
handleUpdateExpanded(rowKey, null)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
) : null
|
||||||
|
) : (
|
||||||
|
<Cell
|
||||||
|
clsPrefix={mergedClsPrefix}
|
||||||
|
index={actualRowIndex}
|
||||||
|
row={rowData}
|
||||||
|
column={column}
|
||||||
|
isSummary={isSummary}
|
||||||
|
mergedTheme={mergedTheme}
|
||||||
|
renderCell={this.renderCell}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FinalCellComponent>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isVirtualX) {
|
||||||
|
if (leftFixedColsCount && rightFixedColsCount) {
|
||||||
|
cells.splice(
|
||||||
|
leftFixedColsCount,
|
||||||
|
0,
|
||||||
|
<td
|
||||||
|
colspan={
|
||||||
|
cols.length - leftFixedColsCount - rightFixedColsCount
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
pointerEvents: 'none',
|
||||||
|
visibility: 'hidden',
|
||||||
|
height: 0
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const row = (
|
const row = (
|
||||||
<tr
|
<tr
|
||||||
onMouseenter={() => {
|
onMouseenter={() => {
|
||||||
@ -746,211 +1021,15 @@ export default defineComponent({
|
|||||||
isSummary && `${mergedClsPrefix}-data-table-tr--summary`,
|
isSummary && `${mergedClsPrefix}-data-table-tr--summary`,
|
||||||
striped && `${mergedClsPrefix}-data-table-tr--striped`,
|
striped && `${mergedClsPrefix}-data-table-tr--striped`,
|
||||||
expanded && `${mergedClsPrefix}-data-table-tr--expanded`,
|
expanded && `${mergedClsPrefix}-data-table-tr--expanded`,
|
||||||
mergedRowClassName
|
mergedRowClassName,
|
||||||
|
props?.class
|
||||||
]}
|
]}
|
||||||
|
style={props?.style}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{cols.map((col, colIndex) => {
|
{cells}
|
||||||
if (displayedRowIndex in cordToPass) {
|
|
||||||
const cordOfRowToPass = cordToPass[displayedRowIndex]
|
|
||||||
const indexInCordOfRowToPass
|
|
||||||
= cordOfRowToPass.indexOf(colIndex)
|
|
||||||
if (~indexInCordOfRowToPass) {
|
|
||||||
cordOfRowToPass.splice(indexInCordOfRowToPass, 1)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Simplify row calculation
|
|
||||||
const { column } = col
|
|
||||||
const colKey = getColKey(col)
|
|
||||||
const { rowSpan, colSpan } = column
|
|
||||||
const mergedColSpan = isSummary
|
|
||||||
? rowInfo.tmNode.rawNode[colKey]?.colSpan || 1 // optional for #1276
|
|
||||||
: colSpan
|
|
||||||
? colSpan(rowData, actualRowIndex)
|
|
||||||
: 1
|
|
||||||
const mergedRowSpan = isSummary
|
|
||||||
? rowInfo.tmNode.rawNode[colKey]?.rowSpan || 1 // optional for #1276
|
|
||||||
: rowSpan
|
|
||||||
? rowSpan(rowData, actualRowIndex)
|
|
||||||
: 1
|
|
||||||
const isLastCol = colIndex + mergedColSpan === colCount
|
|
||||||
const isLastRow
|
|
||||||
= displayedRowIndex + mergedRowSpan === rowCount
|
|
||||||
const isCrossRowTd = mergedRowSpan > 1
|
|
||||||
if (isCrossRowTd) {
|
|
||||||
cordKey[displayedRowIndex] = {
|
|
||||||
[colIndex]: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mergedColSpan > 1 || isCrossRowTd) {
|
|
||||||
for (
|
|
||||||
let i = displayedRowIndex;
|
|
||||||
i < displayedRowIndex + mergedRowSpan;
|
|
||||||
++i
|
|
||||||
) {
|
|
||||||
if (isCrossRowTd) {
|
|
||||||
cordKey[displayedRowIndex][colIndex].push(
|
|
||||||
rowIndexToKey[i]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
for (
|
|
||||||
let j = colIndex;
|
|
||||||
j < colIndex + mergedColSpan;
|
|
||||||
++j
|
|
||||||
) {
|
|
||||||
if (i === displayedRowIndex && j === colIndex) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (!(i in cordToPass)) {
|
|
||||||
cordToPass[i] = [j]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cordToPass[i].push(j)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const hoverKey = isCrossRowTd ? this.hoverKey : null
|
|
||||||
const { cellProps } = column
|
|
||||||
const resolvedCellProps = cellProps?.(
|
|
||||||
rowData,
|
|
||||||
actualRowIndex
|
|
||||||
)
|
|
||||||
const indentOffsetStyle = {
|
|
||||||
'--indent-offset': '' as string | number
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<td
|
|
||||||
{...resolvedCellProps}
|
|
||||||
key={colKey}
|
|
||||||
style={[
|
|
||||||
{
|
|
||||||
textAlign: column.align || undefined,
|
|
||||||
left: pxfy(fixedColumnLeftMap[colKey]?.start),
|
|
||||||
right: pxfy(fixedColumnRightMap[colKey]?.start)
|
|
||||||
},
|
|
||||||
indentOffsetStyle as CSSProperties,
|
|
||||||
resolvedCellProps?.style || ''
|
|
||||||
]}
|
|
||||||
colspan={mergedColSpan}
|
|
||||||
rowspan={isVirtual ? undefined : mergedRowSpan}
|
|
||||||
data-col-key={colKey}
|
|
||||||
class={[
|
|
||||||
`${mergedClsPrefix}-data-table-td`,
|
|
||||||
column.className,
|
|
||||||
resolvedCellProps?.class,
|
|
||||||
isSummary
|
|
||||||
&& `${mergedClsPrefix}-data-table-td--summary`,
|
|
||||||
hoverKey !== null
|
|
||||||
&& cordKey[displayedRowIndex][colIndex].includes(
|
|
||||||
hoverKey
|
|
||||||
)
|
|
||||||
&& `${mergedClsPrefix}-data-table-td--hover`,
|
|
||||||
isColumnSorting(column, mergedSortState)
|
|
||||||
&& `${mergedClsPrefix}-data-table-td--sorting`,
|
|
||||||
column.fixed
|
|
||||||
&& `${mergedClsPrefix}-data-table-td--fixed-${column.fixed}`,
|
|
||||||
column.align
|
|
||||||
&& `${mergedClsPrefix}-data-table-td--${column.align}-align`,
|
|
||||||
column.type === 'selection'
|
|
||||||
&& `${mergedClsPrefix}-data-table-td--selection`,
|
|
||||||
column.type === 'expand'
|
|
||||||
&& `${mergedClsPrefix}-data-table-td--expand`,
|
|
||||||
isLastCol
|
|
||||||
&& `${mergedClsPrefix}-data-table-td--last-col`,
|
|
||||||
isLastRow
|
|
||||||
&& `${mergedClsPrefix}-data-table-td--last-row`
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{hasChildren && colIndex === childTriggerColIndex
|
|
||||||
? [
|
|
||||||
repeat(
|
|
||||||
(indentOffsetStyle['--indent-offset']
|
|
||||||
= isSummary ? 0 : rowInfo.tmNode.level),
|
|
||||||
<div
|
|
||||||
class={`${mergedClsPrefix}-data-table-indent`}
|
|
||||||
style={indentStyle}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
isSummary || rowInfo.tmNode.isLeaf ? (
|
|
||||||
<div
|
|
||||||
class={`${mergedClsPrefix}-data-table-expand-placeholder`}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ExpandTrigger
|
|
||||||
class={`${mergedClsPrefix}-data-table-expand-trigger`}
|
|
||||||
clsPrefix={mergedClsPrefix}
|
|
||||||
expanded={expanded}
|
|
||||||
rowData={rowData}
|
|
||||||
renderExpandIcon={this.renderExpandIcon}
|
|
||||||
loading={loadingKeySet.has(rowInfo.key)}
|
|
||||||
onClick={() => {
|
|
||||||
handleUpdateExpanded(rowKey, rowInfo.tmNode)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
]
|
|
||||||
: null}
|
|
||||||
{column.type === 'selection' ? (
|
|
||||||
!isSummary ? (
|
|
||||||
column.multiple === false ? (
|
|
||||||
<RenderSafeRadio
|
|
||||||
key={currentPage}
|
|
||||||
rowKey={rowKey}
|
|
||||||
disabled={rowInfo.tmNode.disabled}
|
|
||||||
onUpdateChecked={() => {
|
|
||||||
handleRadioUpdateChecked(rowInfo.tmNode)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<RenderSafeCheckbox
|
|
||||||
key={currentPage}
|
|
||||||
rowKey={rowKey}
|
|
||||||
disabled={rowInfo.tmNode.disabled}
|
|
||||||
onUpdateChecked={(checked: boolean, e) => {
|
|
||||||
handleCheckboxUpdateChecked(
|
|
||||||
rowInfo.tmNode,
|
|
||||||
checked,
|
|
||||||
e.shiftKey
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
) : null
|
|
||||||
) : column.type === 'expand' ? (
|
|
||||||
!isSummary ? (
|
|
||||||
!column.expandable
|
|
||||||
|| column.expandable?.(rowData) ? (
|
|
||||||
<ExpandTrigger
|
|
||||||
clsPrefix={mergedClsPrefix}
|
|
||||||
rowData={rowData}
|
|
||||||
expanded={expanded}
|
|
||||||
renderExpandIcon={this.renderExpandIcon}
|
|
||||||
onClick={() => {
|
|
||||||
handleUpdateExpanded(rowKey, null)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null
|
|
||||||
) : null
|
|
||||||
) : (
|
|
||||||
<Cell
|
|
||||||
clsPrefix={mergedClsPrefix}
|
|
||||||
index={actualRowIndex}
|
|
||||||
row={rowData}
|
|
||||||
column={column}
|
|
||||||
isSummary={isSummary}
|
|
||||||
mergedTheme={mergedTheme}
|
|
||||||
renderCell={this.renderCell}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
|
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -975,7 +1054,17 @@ export default defineComponent({
|
|||||||
class={`${mergedClsPrefix}-data-table-tbody`}
|
class={`${mergedClsPrefix}-data-table-tbody`}
|
||||||
>
|
>
|
||||||
{displayedData.map((rowInfo, displayedRowIndex) => {
|
{displayedData.map((rowInfo, displayedRowIndex) => {
|
||||||
return renderRow(rowInfo, displayedRowIndex, false)
|
return renderRow({
|
||||||
|
rowInfo,
|
||||||
|
displayedRowIndex,
|
||||||
|
isVirtual: false,
|
||||||
|
isVirtualX: false,
|
||||||
|
startColIndex: -1,
|
||||||
|
endColIndex: -1,
|
||||||
|
getLeft(_index) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
})
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
) : null}
|
) : null}
|
||||||
@ -987,7 +1076,7 @@ export default defineComponent({
|
|||||||
<VirtualList
|
<VirtualList
|
||||||
ref="virtualListRef"
|
ref="virtualListRef"
|
||||||
items={displayedData}
|
items={displayedData}
|
||||||
itemSize={28}
|
itemSize={this.minRowHeight || 28}
|
||||||
visibleItemsTag={VirtualListItemWrapper}
|
visibleItemsTag={VirtualListItemWrapper}
|
||||||
visibleItemsProps={{
|
visibleItemsProps={{
|
||||||
clsPrefix: mergedClsPrefix,
|
clsPrefix: mergedClsPrefix,
|
||||||
@ -999,16 +1088,54 @@ export default defineComponent({
|
|||||||
onResize={this.handleVirtualListResize}
|
onResize={this.handleVirtualListResize}
|
||||||
onScroll={this.handleVirtualListScroll}
|
onScroll={this.handleVirtualListScroll}
|
||||||
itemsStyle={contentStyle}
|
itemsStyle={contentStyle}
|
||||||
itemResizable
|
itemResizable={!virtualScrollX}
|
||||||
|
columns={cols}
|
||||||
|
renderItemWithCols={
|
||||||
|
virtualScrollX
|
||||||
|
? ({
|
||||||
|
itemIndex,
|
||||||
|
item,
|
||||||
|
startColIndex,
|
||||||
|
endColIndex,
|
||||||
|
getLeft
|
||||||
|
}) => {
|
||||||
|
return renderRow({
|
||||||
|
displayedRowIndex: itemIndex,
|
||||||
|
isVirtual: true,
|
||||||
|
isVirtualX: true,
|
||||||
|
rowInfo: item as RowRenderInfo,
|
||||||
|
startColIndex,
|
||||||
|
endColIndex,
|
||||||
|
getLeft
|
||||||
|
})
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
default: ({
|
default: ({
|
||||||
item,
|
item,
|
||||||
index
|
index,
|
||||||
|
renderedItemWithCols
|
||||||
}: {
|
}: {
|
||||||
item: RowRenderInfo
|
item: RowRenderInfo
|
||||||
index: number
|
index: number
|
||||||
}) => renderRow(item, index, true)
|
renderedItemWithCols: VNodeChild
|
||||||
|
}) => {
|
||||||
|
if (renderedItemWithCols)
|
||||||
|
return renderedItemWithCols
|
||||||
|
return renderRow({
|
||||||
|
rowInfo: item,
|
||||||
|
displayedRowIndex: index,
|
||||||
|
isVirtual: true,
|
||||||
|
isVirtualX: false,
|
||||||
|
startColIndex: 0,
|
||||||
|
endColIndex: 0,
|
||||||
|
getLeft(_index) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
</VirtualList>
|
</VirtualList>
|
||||||
)
|
)
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
import {
|
import { Fragment, defineComponent, h, inject, ref } from 'vue'
|
||||||
Fragment,
|
import type { PropType, VNode, VNodeChild } from 'vue'
|
||||||
type VNode,
|
|
||||||
type VNodeChild,
|
|
||||||
defineComponent,
|
|
||||||
h,
|
|
||||||
inject,
|
|
||||||
ref
|
|
||||||
} from 'vue'
|
|
||||||
import { happensIn, pxfy } from 'seemly'
|
import { happensIn, pxfy } from 'seemly'
|
||||||
|
import type { VirtualListInst } from 'vueuc'
|
||||||
|
import { VVirtualList } from 'vueuc'
|
||||||
import { formatLength } from '../../../_utils'
|
import { formatLength } from '../../../_utils'
|
||||||
import { NCheckbox } from '../../../checkbox'
|
import { NCheckbox } from '../../../checkbox'
|
||||||
import { NEllipsis } from '../../../ellipsis'
|
import { NEllipsis } from '../../../ellipsis'
|
||||||
@ -30,6 +25,7 @@ import {
|
|||||||
type TableExpandColumn,
|
type TableExpandColumn,
|
||||||
dataTableInjectionKey
|
dataTableInjectionKey
|
||||||
} from '../interface'
|
} from '../interface'
|
||||||
|
import type { ColItem, RowItem } from '../use-group-header'
|
||||||
import SelectionMenu from './SelectionMenu'
|
import SelectionMenu from './SelectionMenu'
|
||||||
|
|
||||||
function renderTitle(
|
function renderTitle(
|
||||||
@ -40,6 +36,42 @@ function renderTitle(
|
|||||||
: column.title
|
: column.title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VirtualListItemWrapper = defineComponent({
|
||||||
|
props: {
|
||||||
|
clsPrefix: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
cols: {
|
||||||
|
type: Array as PropType<ColItem[]>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
width: String
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const { clsPrefix, id, cols, width } = this
|
||||||
|
return (
|
||||||
|
<table
|
||||||
|
style={{ tableLayout: 'fixed', width }}
|
||||||
|
class={`${clsPrefix}-data-table-table`}
|
||||||
|
>
|
||||||
|
<colgroup>
|
||||||
|
{cols.map(col => (
|
||||||
|
<col key={col.key} style={col.style}></col>
|
||||||
|
))}
|
||||||
|
</colgroup>
|
||||||
|
<thead data-n-id={id} class={`${clsPrefix}-data-table-thead`}>
|
||||||
|
{this.$slots}
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'DataTableHeader',
|
name: 'DataTableHeader',
|
||||||
props: {
|
props: {
|
||||||
@ -65,6 +97,8 @@ export default defineComponent({
|
|||||||
componentId,
|
componentId,
|
||||||
mergedTableLayoutRef,
|
mergedTableLayoutRef,
|
||||||
headerCheckboxDisabledRef,
|
headerCheckboxDisabledRef,
|
||||||
|
virtualScrollHeaderRef,
|
||||||
|
headerHeightRef,
|
||||||
onUnstableColumnResize,
|
onUnstableColumnResize,
|
||||||
doUpdateResizableWidth,
|
doUpdateResizableWidth,
|
||||||
handleTableHeaderScroll,
|
handleTableHeaderScroll,
|
||||||
@ -72,6 +106,7 @@ export default defineComponent({
|
|||||||
doUncheckAll,
|
doUncheckAll,
|
||||||
doCheckAll
|
doCheckAll
|
||||||
} = inject(dataTableInjectionKey)!
|
} = inject(dataTableInjectionKey)!
|
||||||
|
const virtualListRef = ref<VirtualListInst | null>()
|
||||||
const cellElsRef = ref<Record<ColumnKey, HTMLTableCellElement>>({})
|
const cellElsRef = ref<Record<ColumnKey, HTMLTableCellElement>>({})
|
||||||
function getCellActualWidth(key: ColumnKey): number | undefined {
|
function getCellActualWidth(key: ColumnKey): number | undefined {
|
||||||
const element = cellElsRef.value[key]
|
const element = cellElsRef.value[key]
|
||||||
@ -147,6 +182,9 @@ export default defineComponent({
|
|||||||
checkOptions: checkOptionsRef,
|
checkOptions: checkOptionsRef,
|
||||||
mergedTableLayout: mergedTableLayoutRef,
|
mergedTableLayout: mergedTableLayoutRef,
|
||||||
headerCheckboxDisabled: headerCheckboxDisabledRef,
|
headerCheckboxDisabled: headerCheckboxDisabledRef,
|
||||||
|
headerHeight: headerHeightRef,
|
||||||
|
virtualScrollHeader: virtualScrollHeaderRef,
|
||||||
|
virtualListRef,
|
||||||
handleCheckboxUpdateChecked,
|
handleCheckboxUpdateChecked,
|
||||||
handleColHeaderClick,
|
handleColHeaderClick,
|
||||||
handleTableHeaderScroll,
|
handleTableHeaderScroll,
|
||||||
@ -172,12 +210,233 @@ export default defineComponent({
|
|||||||
mergedTableLayout,
|
mergedTableLayout,
|
||||||
headerCheckboxDisabled,
|
headerCheckboxDisabled,
|
||||||
mergedSortState,
|
mergedSortState,
|
||||||
|
virtualScrollHeader,
|
||||||
handleColHeaderClick,
|
handleColHeaderClick,
|
||||||
handleCheckboxUpdateChecked,
|
handleCheckboxUpdateChecked,
|
||||||
handleColumnResizeStart,
|
handleColumnResizeStart,
|
||||||
handleColumnResize
|
handleColumnResize
|
||||||
} = this
|
} = this
|
||||||
let hasEllipsis = false
|
let hasEllipsis = false
|
||||||
|
|
||||||
|
const renderRow = (
|
||||||
|
row: RowItem[],
|
||||||
|
getLeft: ((index: number) => number) | null,
|
||||||
|
headerHeightPx: string | undefined
|
||||||
|
) =>
|
||||||
|
row.map(({ column, colIndex, colSpan, rowSpan, isLast }) => {
|
||||||
|
const key = getColKey(column)
|
||||||
|
const { ellipsis } = column
|
||||||
|
if (!hasEllipsis && ellipsis)
|
||||||
|
hasEllipsis = true
|
||||||
|
const createColumnVNode = (): VNode | null => {
|
||||||
|
if (column.type === 'selection') {
|
||||||
|
return column.multiple !== false ? (
|
||||||
|
<>
|
||||||
|
<NCheckbox
|
||||||
|
key={currentPage}
|
||||||
|
privateInsideTable
|
||||||
|
checked={allRowsChecked}
|
||||||
|
indeterminate={someRowsChecked}
|
||||||
|
disabled={headerCheckboxDisabled}
|
||||||
|
onUpdateChecked={handleCheckboxUpdateChecked}
|
||||||
|
/>
|
||||||
|
{checkOptions ? (
|
||||||
|
<SelectionMenu clsPrefix={mergedClsPrefix} />
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class={`${mergedClsPrefix}-data-table-th__title-wrapper`}>
|
||||||
|
<div class={`${mergedClsPrefix}-data-table-th__title`}>
|
||||||
|
{ellipsis === true || (ellipsis && !ellipsis.tooltip) ? (
|
||||||
|
<div class={`${mergedClsPrefix}-data-table-th__ellipsis`}>
|
||||||
|
{renderTitle(column)}
|
||||||
|
</div>
|
||||||
|
) : ellipsis && typeof ellipsis === 'object' ? (
|
||||||
|
<NEllipsis
|
||||||
|
{...ellipsis}
|
||||||
|
theme={mergedTheme.peers.Ellipsis}
|
||||||
|
themeOverrides={mergedTheme.peerOverrides.Ellipsis}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
default: () => renderTitle(column)
|
||||||
|
}}
|
||||||
|
</NEllipsis>
|
||||||
|
) : (
|
||||||
|
renderTitle(column)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isColumnSortable(column) ? (
|
||||||
|
<SortButton column={column as TableBaseColumn} />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{isColumnFilterable(column) ? (
|
||||||
|
<FilterButton
|
||||||
|
column={column as TableBaseColumn}
|
||||||
|
options={column.filterOptions}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{isColumnResizable(column) ? (
|
||||||
|
<ResizeButton
|
||||||
|
onResizeStart={() => {
|
||||||
|
handleColumnResizeStart(column as TableBaseColumn)
|
||||||
|
}}
|
||||||
|
onResize={(displacementX) => {
|
||||||
|
handleColumnResize(column as TableBaseColumn, displacementX)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const leftFixed = key in fixedColumnLeftMap
|
||||||
|
const rightFixed = key in fixedColumnRightMap
|
||||||
|
const CellComponent = (getLeft && !column.fixed ? 'div' : 'th') as 'th'
|
||||||
|
return (
|
||||||
|
<CellComponent
|
||||||
|
ref={el => (cellElsRef[key] = el as HTMLTableCellElement)}
|
||||||
|
key={key}
|
||||||
|
style={[
|
||||||
|
getLeft && !column.fixed
|
||||||
|
? {
|
||||||
|
position: 'absolute',
|
||||||
|
left: pxfy(getLeft(colIndex)),
|
||||||
|
top: 0,
|
||||||
|
bottom: 0
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
left: pxfy(fixedColumnLeftMap[key]?.start),
|
||||||
|
right: pxfy(fixedColumnRightMap[key]?.start)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: pxfy(column.width),
|
||||||
|
textAlign: column.titleAlign || column.align,
|
||||||
|
height: headerHeightPx
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
colspan={colSpan}
|
||||||
|
rowspan={rowSpan}
|
||||||
|
data-col-key={key}
|
||||||
|
class={[
|
||||||
|
`${mergedClsPrefix}-data-table-th`,
|
||||||
|
(leftFixed || rightFixed)
|
||||||
|
&& `${mergedClsPrefix}-data-table-th--fixed-${
|
||||||
|
leftFixed ? 'left' : 'right'
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
[`${mergedClsPrefix}-data-table-th--sorting`]: isColumnSorting(
|
||||||
|
column,
|
||||||
|
mergedSortState
|
||||||
|
),
|
||||||
|
[`${mergedClsPrefix}-data-table-th--filterable`]:
|
||||||
|
isColumnFilterable(column),
|
||||||
|
[`${mergedClsPrefix}-data-table-th--sortable`]:
|
||||||
|
isColumnSortable(column),
|
||||||
|
[`${mergedClsPrefix}-data-table-th--selection`]:
|
||||||
|
column.type === 'selection',
|
||||||
|
[`${mergedClsPrefix}-data-table-th--last`]: isLast
|
||||||
|
},
|
||||||
|
column.className
|
||||||
|
]}
|
||||||
|
onClick={
|
||||||
|
column.type !== 'selection'
|
||||||
|
&& column.type !== 'expand'
|
||||||
|
&& !('children' in column)
|
||||||
|
? (e) => {
|
||||||
|
handleColHeaderClick(e, column)
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{createColumnVNode()}
|
||||||
|
</CellComponent>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (virtualScrollHeader) {
|
||||||
|
const { headerHeight } = this
|
||||||
|
|
||||||
|
let leftFixedColsCount = 0
|
||||||
|
let rightFixedColsCount = 0
|
||||||
|
|
||||||
|
cols.forEach((col) => {
|
||||||
|
if (col.column.fixed === 'left') {
|
||||||
|
leftFixedColsCount++
|
||||||
|
}
|
||||||
|
else if (col.column.fixed === 'right') {
|
||||||
|
rightFixedColsCount++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VVirtualList
|
||||||
|
ref="virtualListRef"
|
||||||
|
class={`${mergedClsPrefix}-data-table-base-table-header`}
|
||||||
|
style={{ height: pxfy(headerHeight) }}
|
||||||
|
onScroll={this.handleTableHeaderScroll}
|
||||||
|
columns={cols}
|
||||||
|
itemSize={headerHeight || 28}
|
||||||
|
showScrollbar={false}
|
||||||
|
items={[{}]}
|
||||||
|
itemResizable={false}
|
||||||
|
visibleItemsTag={VirtualListItemWrapper}
|
||||||
|
visibleItemsProps={{
|
||||||
|
clsPrefix: mergedClsPrefix,
|
||||||
|
id: componentId,
|
||||||
|
cols,
|
||||||
|
width: formatLength(this.scrollX)
|
||||||
|
}}
|
||||||
|
renderItemWithCols={({ startColIndex, endColIndex, getLeft }) => {
|
||||||
|
const row = cols
|
||||||
|
.map<RowItem>((col, index) => {
|
||||||
|
return {
|
||||||
|
column: col.column,
|
||||||
|
isLast: index === cols.length - 1,
|
||||||
|
colIndex: col.index,
|
||||||
|
colSpan: 1,
|
||||||
|
rowSpan: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(({ column }, index) => {
|
||||||
|
if (startColIndex <= index && index <= endColIndex) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (column.fixed) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
const cells = renderRow(row, getLeft, pxfy(headerHeight))
|
||||||
|
|
||||||
|
cells.splice(
|
||||||
|
leftFixedColsCount,
|
||||||
|
0,
|
||||||
|
<th
|
||||||
|
colspan={cols.length - leftFixedColsCount - rightFixedColsCount}
|
||||||
|
style={{
|
||||||
|
pointerEvents: 'none',
|
||||||
|
visibility: 'hidden',
|
||||||
|
height: 0
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
return <tr style={{ position: 'relative' }}>{cells}</tr>
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
default: ({
|
||||||
|
renderedItemWithCols
|
||||||
|
}: {
|
||||||
|
renderedItemWithCols: VNodeChild
|
||||||
|
}) => renderedItemWithCols
|
||||||
|
}}
|
||||||
|
</VVirtualList>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const theadVNode = (
|
const theadVNode = (
|
||||||
<thead
|
<thead
|
||||||
class={`${mergedClsPrefix}-data-table-thead`}
|
class={`${mergedClsPrefix}-data-table-thead`}
|
||||||
@ -186,131 +445,7 @@ export default defineComponent({
|
|||||||
{rows.map((row) => {
|
{rows.map((row) => {
|
||||||
return (
|
return (
|
||||||
<tr class={`${mergedClsPrefix}-data-table-tr`}>
|
<tr class={`${mergedClsPrefix}-data-table-tr`}>
|
||||||
{row.map(({ column, colSpan, rowSpan, isLast }) => {
|
{renderRow(row, null, undefined)}
|
||||||
const key = getColKey(column)
|
|
||||||
const { ellipsis } = column
|
|
||||||
if (!hasEllipsis && ellipsis)
|
|
||||||
hasEllipsis = true
|
|
||||||
const createColumnVNode = (): VNode | null => {
|
|
||||||
if (column.type === 'selection') {
|
|
||||||
return column.multiple !== false ? (
|
|
||||||
<>
|
|
||||||
<NCheckbox
|
|
||||||
key={currentPage}
|
|
||||||
privateInsideTable
|
|
||||||
checked={allRowsChecked}
|
|
||||||
indeterminate={someRowsChecked}
|
|
||||||
disabled={headerCheckboxDisabled}
|
|
||||||
onUpdateChecked={handleCheckboxUpdateChecked}
|
|
||||||
/>
|
|
||||||
{checkOptions ? (
|
|
||||||
<SelectionMenu clsPrefix={mergedClsPrefix} />
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
class={`${mergedClsPrefix}-data-table-th__title-wrapper`}
|
|
||||||
>
|
|
||||||
<div class={`${mergedClsPrefix}-data-table-th__title`}>
|
|
||||||
{ellipsis === true
|
|
||||||
|| (ellipsis && !ellipsis.tooltip) ? (
|
|
||||||
<div
|
|
||||||
class={`${mergedClsPrefix}-data-table-th__ellipsis`}
|
|
||||||
>
|
|
||||||
{renderTitle(column)}
|
|
||||||
</div>
|
|
||||||
) : ellipsis && typeof ellipsis === 'object' ? (
|
|
||||||
<NEllipsis
|
|
||||||
{...ellipsis}
|
|
||||||
theme={mergedTheme.peers.Ellipsis}
|
|
||||||
themeOverrides={
|
|
||||||
mergedTheme.peerOverrides.Ellipsis
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
default: () => renderTitle(column)
|
|
||||||
}}
|
|
||||||
</NEllipsis>
|
|
||||||
) : (
|
|
||||||
renderTitle(column)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{isColumnSortable(column) ? (
|
|
||||||
<SortButton column={column as TableBaseColumn} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
{isColumnFilterable(column) ? (
|
|
||||||
<FilterButton
|
|
||||||
column={column as TableBaseColumn}
|
|
||||||
options={column.filterOptions}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{isColumnResizable(column) ? (
|
|
||||||
<ResizeButton
|
|
||||||
onResizeStart={() => {
|
|
||||||
handleColumnResizeStart(column as TableBaseColumn)
|
|
||||||
}}
|
|
||||||
onResize={(displacementX) => {
|
|
||||||
handleColumnResize(
|
|
||||||
column as TableBaseColumn,
|
|
||||||
displacementX
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const leftFixed = key in fixedColumnLeftMap
|
|
||||||
const rightFixed = key in fixedColumnRightMap
|
|
||||||
return (
|
|
||||||
<th
|
|
||||||
ref={el => (cellElsRef[key] = el as HTMLTableCellElement)}
|
|
||||||
key={key}
|
|
||||||
style={{
|
|
||||||
textAlign: column.titleAlign || column.align,
|
|
||||||
left: pxfy(fixedColumnLeftMap[key]?.start),
|
|
||||||
right: pxfy(fixedColumnRightMap[key]?.start)
|
|
||||||
}}
|
|
||||||
colspan={colSpan}
|
|
||||||
rowspan={rowSpan}
|
|
||||||
data-col-key={key}
|
|
||||||
class={[
|
|
||||||
`${mergedClsPrefix}-data-table-th`,
|
|
||||||
(leftFixed || rightFixed)
|
|
||||||
&& `${mergedClsPrefix}-data-table-th--fixed-${
|
|
||||||
leftFixed ? 'left' : 'right'
|
|
||||||
}`,
|
|
||||||
{
|
|
||||||
[`${mergedClsPrefix}-data-table-th--sorting`]:
|
|
||||||
isColumnSorting(column, mergedSortState),
|
|
||||||
[`${mergedClsPrefix}-data-table-th--filterable`]:
|
|
||||||
isColumnFilterable(column),
|
|
||||||
[`${mergedClsPrefix}-data-table-th--sortable`]:
|
|
||||||
isColumnSortable(column),
|
|
||||||
[`${mergedClsPrefix}-data-table-th--selection`]:
|
|
||||||
column.type === 'selection',
|
|
||||||
[`${mergedClsPrefix}-data-table-th--last`]: isLast
|
|
||||||
},
|
|
||||||
column.className
|
|
||||||
]}
|
|
||||||
onClick={
|
|
||||||
column.type !== 'selection'
|
|
||||||
&& column.type !== 'expand'
|
|
||||||
&& !('children' in column)
|
|
||||||
? (e) => {
|
|
||||||
handleColHeaderClick(e, column)
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{createColumnVNode()}
|
|
||||||
</th>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@ -326,7 +461,6 @@ export default defineComponent({
|
|||||||
onScroll={handleTableHeaderScroll}
|
onScroll={handleTableHeaderScroll}
|
||||||
>
|
>
|
||||||
<table
|
<table
|
||||||
ref="body"
|
|
||||||
class={`${mergedClsPrefix}-data-table-table`}
|
class={`${mergedClsPrefix}-data-table-table`}
|
||||||
style={{
|
style={{
|
||||||
minWidth: formatLength(scrollX),
|
minWidth: formatLength(scrollX),
|
||||||
|
@ -8,6 +8,7 @@ import type {
|
|||||||
Slots,
|
Slots,
|
||||||
VNodeChild
|
VNodeChild
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
|
import type { VirtualListInst } from 'vueuc'
|
||||||
import type { ScrollTo, ScrollbarProps } from '../../scrollbar/src/Scrollbar'
|
import type { ScrollTo, ScrollbarProps } from '../../scrollbar/src/Scrollbar'
|
||||||
import type { EllipsisProps } from '../../ellipsis/src/Ellipsis'
|
import type { EllipsisProps } from '../../ellipsis/src/Ellipsis'
|
||||||
import type { ExtractPublicPropTypes, MaybeArray } from '../../_utils'
|
import type { ExtractPublicPropTypes, MaybeArray } from '../../_utils'
|
||||||
@ -90,6 +91,11 @@ export const dataTableProps = {
|
|||||||
expandedRowKeys: Array as PropType<RowKey[]>,
|
expandedRowKeys: Array as PropType<RowKey[]>,
|
||||||
stickyExpandedRows: Boolean,
|
stickyExpandedRows: Boolean,
|
||||||
virtualScroll: Boolean,
|
virtualScroll: Boolean,
|
||||||
|
virtualScrollX: Boolean,
|
||||||
|
virtualScrollHeader: Boolean,
|
||||||
|
headerHeight: Number,
|
||||||
|
heightForRow: Function as PropType<DataTableHeightForRow>,
|
||||||
|
minRowHeight: Number,
|
||||||
tableLayout: {
|
tableLayout: {
|
||||||
type: String as PropType<'auto' | 'fixed'>,
|
type: String as PropType<'auto' | 'fixed'>,
|
||||||
default: 'auto'
|
default: 'auto'
|
||||||
@ -231,6 +237,11 @@ export interface CommonColumnInfo<T = InternalRowData> {
|
|||||||
cellProps?: (rowData: T, rowIndex: number) => HTMLAttributes
|
cellProps?: (rowData: T, rowIndex: number) => HTMLAttributes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DataTableHeightForRow<T = RowData> = (
|
||||||
|
rowData: T,
|
||||||
|
rowIndex: number
|
||||||
|
) => number
|
||||||
|
|
||||||
export type TableColumnTitle =
|
export type TableColumnTitle =
|
||||||
| string
|
| string
|
||||||
| ((column: TableBaseColumn) => VNodeChild)
|
| ((column: TableBaseColumn) => VNodeChild)
|
||||||
@ -384,6 +395,11 @@ export interface DataTableInjection {
|
|||||||
summaryRef: Ref<undefined | CreateSummary>
|
summaryRef: Ref<undefined | CreateSummary>
|
||||||
rawPaginatedDataRef: Ref<InternalRowData[]>
|
rawPaginatedDataRef: Ref<InternalRowData[]>
|
||||||
virtualScrollRef: Ref<boolean>
|
virtualScrollRef: Ref<boolean>
|
||||||
|
virtualScrollXRef: Ref<boolean>
|
||||||
|
minRowHeightRef: Ref<number | undefined>
|
||||||
|
heightForRowRef: Ref<DataTableHeightForRow | undefined>
|
||||||
|
virtualScrollHeaderRef: Ref<boolean>
|
||||||
|
headerHeightRef: Ref<number | undefined>
|
||||||
bodyWidthRef: Ref<number | null>
|
bodyWidthRef: Ref<number | null>
|
||||||
mergedTableLayoutRef: Ref<'auto' | 'fixed'>
|
mergedTableLayoutRef: Ref<'auto' | 'fixed'>
|
||||||
maxHeightRef: Ref<string | number | undefined>
|
maxHeightRef: Ref<string | number | undefined>
|
||||||
@ -502,6 +518,7 @@ export interface MainTableBodyRef {
|
|||||||
|
|
||||||
export interface MainTableHeaderRef {
|
export interface MainTableHeaderRef {
|
||||||
$el: HTMLElement | null
|
$el: HTMLElement | null
|
||||||
|
virtualListRef: Ref<VirtualListInst | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OnFilterMenuChange = <
|
export type OnFilterMenuChange = <
|
||||||
|
@ -184,6 +184,7 @@ export default c([
|
|||||||
background-color: var(--n-merged-th-color);
|
background-color: var(--n-merged-th-color);
|
||||||
`),
|
`),
|
||||||
cB('data-table-tr', `
|
cB('data-table-tr', `
|
||||||
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
transition: background-color .3s var(--n-bezier);
|
transition: background-color .3s var(--n-bezier);
|
||||||
|
@ -15,12 +15,18 @@ export interface RowItem {
|
|||||||
colSpan: number
|
colSpan: number
|
||||||
rowSpan: number
|
rowSpan: number
|
||||||
column: TableColumn
|
column: TableColumn
|
||||||
|
colIndex: number
|
||||||
isLast: boolean
|
isLast: boolean
|
||||||
}
|
}
|
||||||
export interface ColItem {
|
export interface ColItem {
|
||||||
key: string | number
|
key: string | number
|
||||||
style: CSSProperties
|
style: CSSProperties
|
||||||
column: TableSelectionColumn | TableExpandColumn | TableBaseColumn
|
column: TableSelectionColumn | TableExpandColumn | TableBaseColumn
|
||||||
|
index: number
|
||||||
|
/**
|
||||||
|
* The width property is only applied to horizontally virtual scroll table
|
||||||
|
*/
|
||||||
|
width: number
|
||||||
}
|
}
|
||||||
|
|
||||||
type RowItemMap = WeakMap<TableColumn, RowItem>
|
type RowItemMap = WeakMap<TableColumn, RowItem>
|
||||||
@ -49,7 +55,7 @@ function getRowsAndCols(
|
|||||||
rows[currentDepth] = []
|
rows[currentDepth] = []
|
||||||
maxDepth = currentDepth
|
maxDepth = currentDepth
|
||||||
}
|
}
|
||||||
for (const column of columns) {
|
columns.forEach((column, index) => {
|
||||||
if ('children' in column) {
|
if ('children' in column) {
|
||||||
ensureMaxDepth(column.children, currentDepth + 1)
|
ensureMaxDepth(column.children, currentDepth + 1)
|
||||||
}
|
}
|
||||||
@ -61,7 +67,10 @@ function getRowsAndCols(
|
|||||||
column,
|
column,
|
||||||
key !== undefined ? formatLength(getResizableWidth(key)) : undefined
|
key !== undefined ? formatLength(getResizableWidth(key)) : undefined
|
||||||
),
|
),
|
||||||
column
|
column,
|
||||||
|
index,
|
||||||
|
// The width property is only applied to horizontally virtual scroll table
|
||||||
|
width: column.width === undefined ? 128 : Number(column.width)
|
||||||
})
|
})
|
||||||
totalRowSpan += 1
|
totalRowSpan += 1
|
||||||
if (!hasEllipsis) {
|
if (!hasEllipsis) {
|
||||||
@ -69,7 +78,7 @@ function getRowsAndCols(
|
|||||||
}
|
}
|
||||||
dataRelatedCols.push(column)
|
dataRelatedCols.push(column)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
ensureMaxDepth(columns, 0)
|
ensureMaxDepth(columns, 0)
|
||||||
let currentLeafIndex = 0
|
let currentLeafIndex = 0
|
||||||
@ -82,6 +91,7 @@ function getRowsAndCols(
|
|||||||
const cachedCurrentLeafIndex = currentLeafIndex
|
const cachedCurrentLeafIndex = currentLeafIndex
|
||||||
const rowItem: RowItem = {
|
const rowItem: RowItem = {
|
||||||
column,
|
column,
|
||||||
|
colIndex: currentLeafIndex,
|
||||||
colSpan: 0,
|
colSpan: 0,
|
||||||
rowSpan: 1,
|
rowSpan: 1,
|
||||||
isLast: false
|
isLast: false
|
||||||
@ -112,6 +122,7 @@ function getRowsAndCols(
|
|||||||
const rowItem: RowItem = {
|
const rowItem: RowItem = {
|
||||||
column,
|
column,
|
||||||
colSpan,
|
colSpan,
|
||||||
|
colIndex: currentLeafIndex,
|
||||||
rowSpan: maxDepth - currentDepth + 1,
|
rowSpan: maxDepth - currentDepth + 1,
|
||||||
isLast
|
isLast
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user