diff --git a/packages/components/table-v2/src/grid.ts b/packages/components/table-v2/src/grid.ts index 1bd3eda3de..3ffdda5169 100644 --- a/packages/components/table-v2/src/grid.ts +++ b/packages/components/table-v2/src/grid.ts @@ -42,7 +42,6 @@ export const tableV2GridProps = buildProps({ * Special attributes */ cache: virtualizedListProps.cache, - rowKey: tableV2RowProps.rowKey, useIsScrolling: Boolean, /** diff --git a/packages/components/table-v2/src/renderers/cell.tsx b/packages/components/table-v2/src/renderers/cell.tsx new file mode 100644 index 0000000000..47d5f7ac44 --- /dev/null +++ b/packages/components/table-v2/src/renderers/cell.tsx @@ -0,0 +1,127 @@ +import { get } from 'lodash-unified' +import { isFunction, isObject } from '@element-plus/utils' +import TableCell from '../table-cell' +import ExpandIcon from '../expand-icon' +import { Alignment } from '../constants' +import { placeholderSign } from '../private' +import { enforceUnit, tryCall } from '../utils' + +import type { FunctionalComponent, UnwrapNestedRefs, VNode } from 'vue' +import type { TableV2RowCellRenderParam } from '../table-row' +import type { UseNamespaceReturn } from '@element-plus/hooks' +import type { UseTableReturn } from '../use-table' +import type { TableV2Props } from '../table' + +type CellRendererProps = TableV2RowCellRenderParam & + Pick< + TableV2Props, + 'cellProps' | 'expandColumnKey' | 'indentSize' | 'iconSize' | 'rowKey' + > & + UnwrapNestedRefs< + Pick + > & { + ns: UseNamespaceReturn + } + +const CellRenderer: FunctionalComponent = ( + { + // renderer props + columns, + column, + columnIndex, + depth, + expandIconProps, + isScrolling, + rowData, + rowIndex, + // from use-table + columnsStyles, + expandedRowKeys, + ns, + // derived props + expandColumnKey, + indentSize, + iconSize, + rowKey, + }, + { slots } +) => { + const cellStyle = enforceUnit(columnsStyles[column.key]) + + if (column.placeholderSign === placeholderSign) { + return
+ } + const { dataKey, dataGetter } = column + + const CellComponent = slots.cell || ((props) => ) + const cellData = isFunction(dataGetter) + ? dataGetter({ columns, column, columnIndex, rowData, rowIndex }) + : get(rowData, dataKey ?? '') + + const cellProps = { + class: ns.e('cell-text'), + columns, + column, + columnIndex, + cellData, + isScrolling, + rowData, + rowIndex, + } + + const Cell = CellComponent(cellProps) + + const kls = [ + ns.e('row-cell'), + column.align === Alignment.CENTER && ns.is('align-center'), + column.align === Alignment.RIGHT && ns.is('align-right'), + ] + + const expandable = rowIndex >= 0 && column.key === expandColumnKey + const expanded = rowIndex >= 0 && expandedRowKeys.includes(rowData[rowKey]) + + let IconOrPlaceholder: VNode | undefined + const iconStyle = `margin-inline-start: ${depth * indentSize}px;` + if (expandable) { + if (isObject(expandIconProps)) { + IconOrPlaceholder = ( + + ) + } else { + IconOrPlaceholder = ( +
+ ) + } + } + + return ( +
+ {IconOrPlaceholder} + {Cell} +
+ ) +} + +export default CellRenderer diff --git a/packages/components/table-v2/src/renderers/header-cell.tsx b/packages/components/table-v2/src/renderers/header-cell.tsx new file mode 100644 index 0000000000..20b0ba5e42 --- /dev/null +++ b/packages/components/table-v2/src/renderers/header-cell.tsx @@ -0,0 +1,112 @@ +import HeaderCell from '../table-header-cell' +import ColumnResizer from '../table-column-resizer' +import SortIcon from '../sort-icon' +import { Alignment, SortOrder, oppositeOrderMap } from '../constants' +import { placeholderSign } from '../private' +import { tryCall } from '../utils' + +import type { FunctionalComponent, UnwrapNestedRefs } from 'vue' +import type { UseNamespaceReturn } from '@element-plus/hooks' +import type { TableV2HeaderRowCellRendererParams } from '../table-header-row' +import type { UseTableReturn } from '../use-table' +import type { TableV2Props } from '../table' +import type { TableV2HeaderCell } from '../header-cell' + +type HeaderCellRendererProps = TableV2HeaderRowCellRendererParams & + UnwrapNestedRefs< + Pick< + UseTableReturn, + | 'columnsStyles' + | 'resizingKey' + | 'onColumnSorted' + | 'onColumnResized' + | 'onColumnResizeEnd' + | 'onColumnResizeStart' + > + > & + Pick & { + ns: UseNamespaceReturn + } + +const HeaderCellRenderer: FunctionalComponent = ( + props +) => { + const { + column, + ns, + resizingKey, + columnsStyles, + onColumnResizeEnd, + onColumnResizeStart, + onColumnResized, + onColumnSorted, + } = props + + if (column.placeholderSign === placeholderSign) { + return + } + + const { headerCellRenderer, headerClass, sortable, resizable } = column + + /** + * render Cell children + */ + const cellRenderer = + headerCellRenderer || + ((props: TableV2HeaderCell) => ) + + const Cell = cellRenderer({ + ...props, + class: ns.e('header-cell-text'), + }) + + /** + * Render cell container and sort indicator + */ + const { sortBy, sortState, headerCellProps } = props + + const cellKls = [ + ns.e('header-cell'), + ...tryCall(headerClass, props, ''), + column.align === Alignment.CENTER && ns.is('align-center'), + column.align === Alignment.RIGHT && ns.is('align-right'), + sortable && ns.is('sortable'), + column.key === resizingKey && ns.is('resizing'), + ] + + let sorting: boolean, sortOrder: SortOrder + if (sortState) { + const order = sortState[column.key] + sorting = Boolean(oppositeOrderMap[order]) + sortOrder = sorting ? order : SortOrder.ASC + } else { + sorting = column.key === sortBy.key + sortOrder = sorting ? sortBy.order : SortOrder.ASC + } + + const cellProps = { + ...tryCall(headerCellProps, props), + onClick: column.sortable ? onColumnSorted : undefined, + class: cellKls, + style: columnsStyles[column.key], + ['data-key']: column.key, + } + + return ( +
+ {Cell} + {sortable && } + {resizable && ( + + )} +
+ ) +} + +export default HeaderCellRenderer diff --git a/packages/components/table-v2/src/renderers/header.tsx b/packages/components/table-v2/src/renderers/header.tsx new file mode 100644 index 0000000000..cbfb285d99 --- /dev/null +++ b/packages/components/table-v2/src/renderers/header.tsx @@ -0,0 +1,52 @@ +import HeaderRow from '../table-header-row' +import { tryCall } from '../utils' + +import type { FunctionalComponent, UnwrapNestedRefs } from 'vue' +import type { UseNamespaceReturn } from '@element-plus/hooks' +import type { TableV2HeaderRendererParams } from '../table-header' +import type { UseTableReturn } from '../use-table' +import type { TableV2Props } from '../table' + +type HeaderRendererProps = TableV2HeaderRendererParams & + Pick & { + ns: UseNamespaceReturn + } & UnwrapNestedRefs> + +const HeaderRenderer: FunctionalComponent = ( + { + columns, + headerIndex, + style, + // derived from root + headerClass, + headerProps, + + ns, + // returned by use-table + resizingKey, + }, + { slots } +) => { + const param = { columns, headerIndex } + + const kls = [ + ns.e('header-row'), + tryCall(headerClass, param, ''), + { + [ns.is('resizing')]: Boolean(resizingKey), + [ns.is('customized')]: Boolean(slots.header), + }, + ] + + const extraProps = { + ...tryCall(headerProps, param), + class: kls, + columns, + headerIndex, + style, + } + + return {slots} +} + +export default HeaderRenderer diff --git a/packages/components/table-v2/src/renderers/main-table.tsx b/packages/components/table-v2/src/renderers/main-table.tsx new file mode 100644 index 0000000000..26b3621d0d --- /dev/null +++ b/packages/components/table-v2/src/renderers/main-table.tsx @@ -0,0 +1,23 @@ +import Table from '../table-grid' + +import type { FunctionalComponent, Ref } from 'vue' +import type { TableV2GridProps } from '../grid' +import type { TableGridInstance } from '../table-grid' + +export type MainTableRendererProps = TableV2GridProps & { + mainTableRef: Ref +} + +const MainTable: FunctionalComponent = ( + props: MainTableRendererProps, + { slots } +) => { + const { mainTableRef, ...rest } = props + return ( + + {slots} +
+ ) +} + +export default MainTable diff --git a/packages/components/table-v2/src/renderers/row.tsx b/packages/components/table-v2/src/renderers/row.tsx new file mode 100644 index 0000000000..4ed9bc0b40 --- /dev/null +++ b/packages/components/table-v2/src/renderers/row.tsx @@ -0,0 +1,105 @@ +import Row from '../table-row' +import { tryCall } from '../utils' + +import type { FunctionalComponent, UnwrapNestedRefs } from 'vue' +import type { UseNamespaceReturn } from '@element-plus/hooks' +import type { UseTableReturn } from '../use-table' +import type { TableV2Props } from '../table' +import type { TableGridRowSlotParams } from '../table-grid' + +type RowRendererProps = TableGridRowSlotParams & + Pick< + TableV2Props, + | 'expandColumnKey' + | 'estimatedRowHeight' + | 'rowProps' + | 'rowClass' + | 'rowKey' + | 'rowEventHandlers' + > & + UnwrapNestedRefs< + Pick< + UseTableReturn, + | 'depthMap' + | 'expandedRowKeys' + | 'hasFixedColumns' + | 'hoveringRowKey' + | 'onRowHovered' + | 'onRowExpanded' + > + > & { + ns: UseNamespaceReturn + } + +const RowRenderer: FunctionalComponent = ( + props, + { slots } +) => { + const { + columns, + depthMap, + expandColumnKey, + expandedRowKeys, + estimatedRowHeight, + hasFixedColumns, + hoveringRowKey, + rowData, + rowIndex, + style, + isScrolling, + rowProps, + rowClass, + rowKey, + rowEventHandlers, + ns, + onRowHovered, + onRowExpanded, + } = props + + const rowKls = tryCall(rowClass, { columns, rowData, rowIndex }, '') + const additionalProps = tryCall(rowProps, { + columns, + rowData, + rowIndex, + }) + const _rowKey = rowData[rowKey] + const depth = depthMap[_rowKey] || 0 + const canExpand = Boolean(expandColumnKey) + const isFixedRow = rowIndex < 0 + const kls = [ + ns.e('row'), + rowKls, + { + [ns.e(`row-depth-${depth}`)]: canExpand && rowIndex >= 0, + [ns.is('expanded')]: canExpand && expandedRowKeys.includes(_rowKey), + [ns.is('hovered')]: !isScrolling && _rowKey === hoveringRowKey, + [ns.is('fixed')]: !depth && isFixedRow, + [ns.is('customized')]: Boolean(slots.row), + }, + ] + + const onRowHover = hasFixedColumns ? onRowHovered : undefined + + const _rowProps = { + ...additionalProps, + columns, + class: kls, + depth, + expandColumnKey, + estimatedRowHeight: isFixedRow ? undefined : estimatedRowHeight, + isScrolling, + rowIndex, + rowData, + rowKey: _rowKey, + rowEventHandlers, + style, + } + + return ( + + {slots} + + ) +} + +export default RowRenderer diff --git a/packages/components/table-v2/src/table-header-row.tsx b/packages/components/table-v2/src/table-header-row.tsx index faff2c3526..61324f09d2 100644 --- a/packages/components/table-v2/src/table-header-row.tsx +++ b/packages/components/table-v2/src/table-header-row.tsx @@ -19,8 +19,8 @@ const TableV2HeaderRow = defineComponent({ }) }) - if (slots.default) { - Cells = slots.default({ + if (slots.header) { + Cells = slots.header({ cells: Cells, columns, headerIndex, diff --git a/packages/components/table-v2/src/table-row.tsx b/packages/components/table-v2/src/table-row.tsx index 4339b38937..bbcfae57d6 100644 --- a/packages/components/table-v2/src/table-row.tsx +++ b/packages/components/table-v2/src/table-row.tsx @@ -177,8 +177,8 @@ const TableV2Row = defineComponent({ }) }) - if (slots.default) { - ColumnCells = slots.default({ + if (slots.row) { + ColumnCells = slots.row({ cells: ColumnCells.map((node) => { if (isArray(node) && node.length === 1) { return node[0] diff --git a/packages/components/table-v2/src/table-v2.tsx b/packages/components/table-v2/src/table-v2.tsx index 453b7eb5e8..37ba4a5b4d 100644 --- a/packages/components/table-v2/src/table-v2.tsx +++ b/packages/components/table-v2/src/table-v2.tsx @@ -1,28 +1,21 @@ import { computed, defineComponent, provide, unref } from 'vue' -import { get } from 'lodash-unified' import { useNamespace } from '@element-plus/hooks' -import { isFunction, isObject } from '@element-plus/utils' import { useTable } from './use-table' -import { enforceUnit, tryCall } from './utils' +import { enforceUnit } from './utils' import { TableV2InjectionKey } from './tokens' -import { Alignment, SortOrder, oppositeOrderMap } from './constants' -import { placeholderSign } from './private' import { tableV2Props } from './table' -// components -import Table from './table-grid' -import TableRow from './table-row' -import TableHeaderRow from './table-header-row' -import TableCell from './table-cell' -import TableHeaderCell from './table-header-cell' -import ColumnResizer from './table-column-resizer' -import ExpandIcon from './expand-icon' -import SortIcon from './sort-icon' +// renderers +import MainTable from './renderers/main-table' +import Row from './renderers/row' +import Cell from './renderers/cell' +import Header from './renderers/header' +import HeaderCell from './renderers/header-cell' -import type { CSSProperties, VNode } from 'vue' +import type { CSSProperties } from 'vue' +import type { TableV2GridProps } from './grid' import type { TableGridRowSlotParams } from './table-grid' import type { TableV2RowCellRenderParam } from './table-row' import type { TableV2HeaderRendererParams } from './table-header' -import type { TableV2HeaderCell } from './header-cell' import type { TableV2HeaderRowCellRendererParams } from './table-header-row' @@ -80,39 +73,6 @@ const TableV2 = defineComponent({ () => unref(bodyWidth) + (props.fixed ? unref(vScrollbarSize) : 0) ) - function renderMainTable() { - const { - cache, - fixedData, - estimatedRowHeight, - headerHeight, - rowHeight, - width, - } = props - - return ( - - {{ row: renderTableRow, header: renderHeader }} -
- ) - } - // function renderLeftTable() { // const columns = unref(fixedColumnsOnLeft) // if (columns.length === 0) return @@ -124,279 +84,8 @@ const TableV2 = defineComponent({ // function renderRightTable() {} - function renderHeader({ - columns, - headerIndex, - style, - }: TableV2HeaderRendererParams) { - const param = { columns, headerIndex } - - const headerClass = [ - ns.e('header-row'), - tryCall(props.headerClass, param, ''), - { - [ns.is('resizing')]: unref(resizingKey), - [ns.is('customized')]: Boolean(slots.header), - }, - ] - - const headerProps = { - ...tryCall(props.headerProps, param), - class: headerClass, - columns, - headerIndex, - style, - } - - return ( - - {{ - default: slots.header, - cell: renderHeaderCell, - }} - - ) - } - // function renderFooter() {} - function renderTableRow({ - columns, - rowData, - rowIndex, - style, - isScrolling, - }: TableGridRowSlotParams) { - const { - expandColumnKey, - estimatedRowHeight, - rowProps, - rowClass, - rowKey, - rowEventHandlers, - } = props - - const rowKls = tryCall(rowClass, { columns, rowData, rowIndex }, '') - const additionalProps = tryCall(rowProps, { - columns, - rowData, - rowIndex, - }) - const _rowKey = rowData[rowKey] - const depth = unref(depthMap)[_rowKey] || 0 - const canExpand = Boolean(expandColumnKey) - const isFixedRow = rowIndex < 0 - const kls = [ - ns.e('row'), - rowKls, - { - [ns.e(`row-depth-${depth}`)]: canExpand && rowIndex >= 0, - [ns.is('expanded')]: - canExpand && unref(expandedRowKeys).includes(_rowKey), - [ns.is('hovered')]: !isScrolling && _rowKey === unref(hoveringRowKey), - [ns.is('fixed')]: !depth && isFixedRow, - [ns.is('customized')]: Boolean(slots.row), - }, - ] - - const onRowHover = unref(hasFixedColumns) ? onRowHovered : undefined - - const _rowProps = { - ...additionalProps, - columns, - class: kls, - depth, - expandColumnKey, - estimatedRowHeight: isFixedRow ? undefined : estimatedRowHeight, - isScrolling, - rowIndex, - rowData, - rowKey: _rowKey, - rowEventHandlers, - style, - } - - const children = { - ...(slots.row ? { default: slots.row } : {}), - cell: renderRowCell, - } - - return ( - - {children} - - ) - } - - function renderRowCell({ - columns, - column, - columnIndex, - depth, - expandIconProps, - isScrolling, - rowData, - rowIndex, - }: TableV2RowCellRenderParam) { - const cellStyle = enforceUnit(unref(columnsStyles)[column.key]) - - if (column.placeholderSign === placeholderSign) { - return ( -
- ) - } - const { dataKey, dataGetter } = column - - const CellComponent = slots.cell || ((props) => ) - const cellData = isFunction(dataGetter) - ? dataGetter({ columns, column, columnIndex, rowData, rowIndex }) - : get(rowData, dataKey ?? '') - - const cellProps = { - class: ns.e('cell-text'), - columns, - column, - columnIndex, - cellData, - isScrolling, - rowData, - rowIndex, - } - - const Cell = CellComponent(cellProps) - - const kls = [ - ns.e('row-cell'), - column.align === Alignment.CENTER && ns.is('align-center'), - column.align === Alignment.RIGHT && ns.is('align-right'), - ] - - const { expandColumnKey, indentSize, iconSize, rowKey } = props - - const expandable = rowIndex >= 0 && column.key === expandColumnKey - const expanded = - rowIndex >= 0 && unref(expandedRowKeys).includes(rowData[rowKey]) - - let IconOrPlaceholder: VNode | undefined - const iconStyle = `margin-inline-start: ${depth * indentSize}px;` - if (expandable) { - if (isObject(expandIconProps)) { - IconOrPlaceholder = ( - - ) - } else { - IconOrPlaceholder = ( -
- ) - } - } - - return ( -
- {IconOrPlaceholder} - {Cell} -
- ) - } - - function renderHeaderCell( - renderHeaderCellProps: TableV2HeaderRowCellRendererParams - ) { - const { column } = renderHeaderCellProps - - if (column.placeholderSign === placeholderSign) { - return - } - - const { headerCellRenderer, headerClass, sortable, resizable } = column - - /** - * render Cell children - */ - const cellRenderer = - headerCellRenderer || - ((props: TableV2HeaderCell) => ) - - const Cell = cellRenderer({ - ...renderHeaderCellProps, - class: ns.e('header-cell-text'), - }) - - /** - * Render cell container and sort indicator - */ - const { sortBy, sortState, headerCellProps } = props - - const cellKls = [ - ns.e('header-cell'), - ...tryCall(headerClass, renderHeaderCellProps, ''), - column.align === Alignment.CENTER && ns.is('align-center'), - column.align === Alignment.RIGHT && ns.is('align-right'), - sortable && ns.is('sortable'), - column.key === unref(resizingKey) && ns.is('resizing'), - ] - - let sorting: boolean, sortOrder: SortOrder - if (sortState) { - const order = sortState[column.key] - sorting = Boolean(oppositeOrderMap[order]) - sortOrder = sorting ? order : SortOrder.ASC - } else { - sorting = column.key === sortBy.key - sortOrder = sorting ? sortBy.order : SortOrder.ASC - } - - const cellProps = { - ...tryCall(headerCellProps, renderHeaderCellProps), - onClick: column.sortable ? onColumnSorted : undefined, - class: cellKls, - style: unref(columnsStyles)[column.key], - ['data-key']: column.key, - } - - return ( -
- {Cell} - {sortable && } - {resizable && ( - - )} -
- ) - } - provide(TableV2InjectionKey, { ns, isResetting, @@ -405,9 +94,118 @@ const TableV2 = defineComponent({ }) return () => { + const { + cache, + estimatedRowHeight, + expandColumnKey, + fixedData, + headerHeight, + headerClass, + headerProps, + headerCellProps, + sortBy, + sortState, + rowHeight, + rowClass, + rowEventHandlers, + rowKey, + rowProps, + indentSize, + iconSize, + useIsScrolling, + width, + } = props + + const mainTableProps: TableV2GridProps = { + cache, + class: ns.e('main'), + columns: unref(mainColumns), + data: unref(data), + fixedData, + estimatedRowHeight, + bodyWidth: unref(bodyWidth), + headerHeight, + headerWidth: unref(headerWidth), + rowHeight, + height: unref(mainTableHeight), + useIsScrolling, + width, + onRowsRendered, + onScroll, + } + + const tableRowProps = { + ns, + depthMap: unref(depthMap), + expandedRowKeys: unref(expandedRowKeys), + estimatedRowHeight, + hasFixedColumns: unref(hasFixedColumns), + hoveringRowKey: unref(hoveringRowKey), + rowProps, + rowClass, + rowKey, + rowEventHandlers, + onRowHovered, + onRowExpanded, + } + + const tableCellProps = { + expandColumnKey, + indentSize, + iconSize, + rowKey, + columnsStyles: unref(columnsStyles), + expandedRowKeys: unref(expandedRowKeys), + ns, + } + + const tableHeaderProps = { + ns, + headerClass, + headerProps, + resizingKey: unref(resizingKey), + } + + const tableHeaderCellProps = { + ns, + + sortBy, + sortState, + headerCellProps, + resizingKey: unref(resizingKey), + columnsStyles: unref(columnsStyles), + onColumnResizeEnd, + onColumnResizeStart, + onColumnResized, + onColumnSorted, + } + return (
- {renderMainTable()} + + {{ + row: (props: TableGridRowSlotParams) => ( + + {{ + row: slots.row, + cell: (props: TableV2RowCellRenderParam) => ( + + ), + }} + + ), + header: (props: TableV2HeaderRendererParams) => ( +
+ {{ + header: slots.header, + cell: (props: TableV2HeaderRowCellRendererParams) => ( + + ), + }} +
+ ), + }} +
) } diff --git a/packages/components/table-v2/src/table.ts b/packages/components/table-v2/src/table.ts index bafeac2726..e70ec872db 100644 --- a/packages/components/table-v2/src/table.ts +++ b/packages/components/table-v2/src/table.ts @@ -149,6 +149,7 @@ export const tableV2Props = buildProps({ width: requiredNumber, height: requiredNumber, maxHeight: Number, + useIsScrolling: Boolean, indentSize: { type: Number, default: 12, diff --git a/packages/components/table-v2/src/use-table.ts b/packages/components/table-v2/src/use-table.ts index 5afec052f4..829ece87e9 100644 --- a/packages/components/table-v2/src/use-table.ts +++ b/packages/components/table-v2/src/use-table.ts @@ -396,3 +396,5 @@ function useTable(props: TableV2Props) { } export { useTable } + +export type UseTableReturn = ReturnType diff --git a/packages/components/virtual-list/src/types.ts b/packages/components/virtual-list/src/types.ts index 12411835c9..8aca2fdaeb 100644 --- a/packages/components/virtual-list/src/types.ts +++ b/packages/components/virtual-list/src/types.ts @@ -186,7 +186,6 @@ export type GridDefaultSlotParams = { columnIndex: number rowIndex: number data: any - depth key: number | string isScrolling?: boolean style: CSSProperties