mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
feat(components): [virtual-table] renderers (#7224)
- Fix custom renderer for column cell and header column cell. - Add helper method for rendering cells. - Update column interface for correctness. - Remove some unused code in `use-columns`.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export type { Column } from './src/types'
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { buildProps, definePropType } from '@element-plus/utils'
|
||||
|
||||
import type { CSSProperties, ExtractPropTypes } from 'vue'
|
||||
import type { CSSProperties, Component, ExtractPropTypes } from 'vue'
|
||||
import type {
|
||||
Alignment,
|
||||
CellRenderer,
|
||||
CellRendererParams,
|
||||
FixedDirection,
|
||||
HeaderRenderer,
|
||||
HeaderCellRenderer,
|
||||
HeaderCellRendererParams,
|
||||
} from './types'
|
||||
|
||||
const widthType = {
|
||||
@@ -44,11 +46,15 @@ export const tableV2ColumnProps = buildProps({
|
||||
|
||||
// getters & renderers
|
||||
cellRenderer: {
|
||||
type: definePropType<CellRenderer<any>>(Function),
|
||||
type: definePropType<
|
||||
CellRenderer<any> | Component<CellRendererParams<any>>
|
||||
>([Function, Object]),
|
||||
},
|
||||
|
||||
headerRenderer: {
|
||||
type: definePropType<HeaderRenderer<any>>(Function),
|
||||
type: definePropType<
|
||||
HeaderCellRenderer<any> | Component<HeaderCellRendererParams<any>>
|
||||
>([Function, Object]),
|
||||
},
|
||||
} as const)
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@ import TableCell from '../table-cell'
|
||||
import ExpandIcon from '../expand-icon'
|
||||
import { Alignment } from '../constants'
|
||||
import { placeholderSign } from '../private'
|
||||
import { enforceUnit } from '../utils'
|
||||
import { componentToSlot, enforceUnit } from '../utils'
|
||||
|
||||
import type { FunctionalComponent, UnwrapNestedRefs, VNode } from 'vue'
|
||||
import type { CellRendererParams } from '../types'
|
||||
import type { TableV2RowCellRenderParam } from '../table-row'
|
||||
import type { UseNamespaceReturn } from '@element-plus/hooks'
|
||||
import type { UseTableReturn } from '../use-table'
|
||||
@@ -51,9 +52,15 @@ const CellRenderer: FunctionalComponent<CellRendererProps> = (
|
||||
if (column.placeholderSign === placeholderSign) {
|
||||
return <div class={ns.em('row-cell', 'placeholder')} style={cellStyle} />
|
||||
}
|
||||
const { dataKey, dataGetter } = column
|
||||
const { cellRenderer, dataKey, dataGetter } = column
|
||||
|
||||
const columnCellRenderer = componentToSlot(cellRenderer)
|
||||
|
||||
const CellComponent =
|
||||
columnCellRenderer ||
|
||||
slots.default ||
|
||||
((props: CellRendererParams<any>) => <TableCell {...props} />)
|
||||
|
||||
const CellComponent = slots.cell || ((props) => <TableCell {...props} />)
|
||||
const cellData = isFunction(dataGetter)
|
||||
? dataGetter({ columns, column, columnIndex, rowData, rowIndex })
|
||||
: get(rowData, dataKey ?? '')
|
||||
|
||||
@@ -3,7 +3,7 @@ import HeaderCell from '../table-header-cell'
|
||||
import SortIcon from '../sort-icon'
|
||||
import { Alignment, SortOrder, oppositeOrderMap } from '../constants'
|
||||
import { placeholderSign } from '../private'
|
||||
import { tryCall } from '../utils'
|
||||
import { componentToSlot, tryCall } from '../utils'
|
||||
|
||||
import type { FunctionalComponent, UnwrapNestedRefs } from 'vue'
|
||||
import type { UseNamespaceReturn } from '@element-plus/hooks'
|
||||
@@ -28,7 +28,8 @@ type HeaderCellRendererProps = TableV2HeaderRowCellRendererParams &
|
||||
}
|
||||
|
||||
const HeaderCellRenderer: FunctionalComponent<HeaderCellRendererProps> = (
|
||||
props
|
||||
props,
|
||||
{ slots }
|
||||
) => {
|
||||
const {
|
||||
column,
|
||||
@@ -52,14 +53,18 @@ const HeaderCellRenderer: FunctionalComponent<HeaderCellRendererProps> = (
|
||||
/**
|
||||
* render Cell children
|
||||
*/
|
||||
const cellRenderer =
|
||||
headerCellRenderer ||
|
||||
((props: TableV2HeaderCell) => <HeaderCell {...props} />)
|
||||
|
||||
const Cell = cellRenderer({
|
||||
const cellProps = {
|
||||
...props,
|
||||
class: ns.e('header-cell-text'),
|
||||
})
|
||||
}
|
||||
|
||||
const cellRenderer =
|
||||
componentToSlot<typeof cellProps>(headerCellRenderer) ||
|
||||
slots.default ||
|
||||
((props: TableV2HeaderCell) => <HeaderCell {...props} />)
|
||||
|
||||
const Cell = cellRenderer(cellProps)
|
||||
|
||||
/**
|
||||
* Render cell container and sort indicator
|
||||
@@ -85,7 +90,7 @@ const HeaderCellRenderer: FunctionalComponent<HeaderCellRendererProps> = (
|
||||
// column.key === resizingKey && ns.is('resizing'),
|
||||
]
|
||||
|
||||
const cellProps = {
|
||||
const cellWrapperProps = {
|
||||
...tryCall(headerCellProps, props),
|
||||
onClick: column.sortable ? onColumnSorted : undefined,
|
||||
class: cellKls,
|
||||
@@ -95,7 +100,7 @@ const HeaderCellRenderer: FunctionalComponent<HeaderCellRendererProps> = (
|
||||
|
||||
// For now we don't deliver resizable column feature since it has some UX issue.
|
||||
return (
|
||||
<div {...cellProps}>
|
||||
<div {...cellWrapperProps}>
|
||||
{Cell}
|
||||
|
||||
{sortable && (
|
||||
|
||||
@@ -19,6 +19,7 @@ import type { TableV2HeaderRendererParams } from './table-header'
|
||||
import type { TableV2HeaderRowCellRendererParams } from './table-header-row'
|
||||
|
||||
const COMPONENT_NAME = 'ElTableV2'
|
||||
|
||||
const TableV2 = defineComponent({
|
||||
name: COMPONENT_NAME,
|
||||
props: tableV2Props,
|
||||
@@ -221,9 +222,14 @@ const TableV2 = defineComponent({
|
||||
<Row {...props} {...tableRowProps}>
|
||||
{{
|
||||
row: slots.row,
|
||||
cell: (props: TableV2RowCellRenderParam) => (
|
||||
<Cell {...props} {...tableCellProps} />
|
||||
),
|
||||
cell: (props: TableV2RowCellRenderParam) =>
|
||||
slots.cell ? (
|
||||
<Cell {...props} {...tableCellProps}>
|
||||
{slots.cell}
|
||||
</Cell>
|
||||
) : (
|
||||
<Cell {...props} {...tableCellProps} />
|
||||
),
|
||||
}}
|
||||
</Row>
|
||||
),
|
||||
@@ -231,9 +237,14 @@ const TableV2 = defineComponent({
|
||||
<Header {...props} {...tableHeaderProps}>
|
||||
{{
|
||||
header: slots.header,
|
||||
cell: (props: TableV2HeaderRowCellRendererParams) => (
|
||||
<HeaderCell {...props} {...tableHeaderCellProps} />
|
||||
),
|
||||
cell: (props: TableV2HeaderRowCellRendererParams) =>
|
||||
slots['header-cell'] ? (
|
||||
<HeaderCell {...props} {...tableHeaderCellProps}>
|
||||
{slots['header-cell']}
|
||||
</HeaderCell>
|
||||
) : (
|
||||
<HeaderCell {...props} {...tableHeaderCellProps} />
|
||||
),
|
||||
}}
|
||||
</Header>
|
||||
),
|
||||
|
||||
@@ -18,7 +18,7 @@ export type ColumnCommonParams<T> = {
|
||||
columnIndex: number
|
||||
}
|
||||
|
||||
export type HeaderRendererParams<T> = {
|
||||
export type HeaderCellRendererParams<T> = {
|
||||
headerIndex: number
|
||||
} & ColumnCommonParams<T>
|
||||
|
||||
@@ -49,7 +49,9 @@ export type HeaderClassGetter<T> = (
|
||||
*/
|
||||
export type CellRenderer<T> = (params: CellRendererParams<T>) => VNode
|
||||
|
||||
export type HeaderCellRenderer<T> = (params: HeaderRendererParams<T>) => VNode
|
||||
export type HeaderCellRenderer<T> = (
|
||||
params: HeaderCellRendererParams<T>
|
||||
) => VNode
|
||||
|
||||
export type Column<T = any> = {
|
||||
key: KeyType
|
||||
@@ -61,7 +63,7 @@ export type Column<T = any> = {
|
||||
fixed?: true | FixedDirection
|
||||
title?: string
|
||||
hidden?: boolean
|
||||
headerClass: HeaderClassGetter<T> | string
|
||||
headerClass?: HeaderClassGetter<T> | string
|
||||
maxWidth?: number
|
||||
minWidth?: number
|
||||
resizable?: boolean
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { computed, ref, unref, watchEffect } from 'vue'
|
||||
import { computed, unref } from 'vue'
|
||||
import { placeholderSign } from './private'
|
||||
|
||||
import type { CSSProperties, Ref } from 'vue'
|
||||
@@ -34,33 +34,9 @@ const calcColumnStyle = (
|
||||
return style
|
||||
}
|
||||
|
||||
const mapColumns = (columns: AnyColumn, currentColumns: AnyColumn) => {
|
||||
return columns.map((column) => {
|
||||
if (!column.resizable) return column
|
||||
|
||||
let { width } = column
|
||||
if (column.resizable) {
|
||||
const idx = columns.findIndex(
|
||||
(predicated) => column.key === predicated.key
|
||||
)
|
||||
if (idx >= 0 && columns[idx].width === column.width) {
|
||||
width = currentColumns[idx].width
|
||||
}
|
||||
}
|
||||
return {
|
||||
...column,
|
||||
width,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function useColumns(columns: Ref<AnyColumn>, fixed: Ref<boolean>) {
|
||||
let __columns: AnyColumn = []
|
||||
|
||||
const _columns = ref<AnyColumn>([])
|
||||
|
||||
const visibleColumns = computed(() => {
|
||||
return unref(_columns).filter((column) => !column.hidden)
|
||||
return unref(columns).filter((column) => !column.hidden)
|
||||
})
|
||||
|
||||
const fixedColumnsOnLeft = computed(() =>
|
||||
@@ -106,9 +82,9 @@ function useColumns(columns: Ref<AnyColumn>, fixed: Ref<boolean>) {
|
||||
})
|
||||
|
||||
const columnsStyles = computed(() => {
|
||||
const columns = unref(_columns)
|
||||
const _columns = unref(columns)
|
||||
|
||||
return columns.reduce<Record<Column<any>['key'], CSSProperties>>(
|
||||
return _columns.reduce<Record<Column<any>['key'], CSSProperties>>(
|
||||
(style, column) => {
|
||||
style[column.key] = calcColumnStyle(column, unref(fixed))
|
||||
return style
|
||||
@@ -124,14 +100,8 @@ function useColumns(columns: Ref<AnyColumn>, fixed: Ref<boolean>) {
|
||||
)
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
const mappedColumns = mapColumns(unref(columns), __columns)
|
||||
_columns.value = mappedColumns
|
||||
__columns = mappedColumns
|
||||
})
|
||||
|
||||
const getColumn = (key: KeyType) => {
|
||||
return unref(_columns).find((column) => column.key === key)
|
||||
return unref(columns).find((column) => column.key === key)
|
||||
}
|
||||
|
||||
const getColumnStyle = (key: KeyType) => {
|
||||
@@ -143,7 +113,7 @@ function useColumns(columns: Ref<AnyColumn>, fixed: Ref<boolean>) {
|
||||
}
|
||||
|
||||
return {
|
||||
columns: _columns,
|
||||
columns,
|
||||
columnsStyles,
|
||||
columnsTotalWidth,
|
||||
fixedColumnsOnLeft,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { h, isVNode } from 'vue'
|
||||
import { addUnit, isArray, isFunction } from '@element-plus/utils'
|
||||
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type { CSSProperties, Component, Slot } from 'vue'
|
||||
|
||||
const sumReducer = (sum: number, num: number) => sum + num
|
||||
|
||||
@@ -23,3 +24,10 @@ export const enforceUnit = (style: CSSProperties) => {
|
||||
|
||||
return style
|
||||
}
|
||||
|
||||
export const componentToSlot = <T>(
|
||||
ComponentLike: JSX.Element | ((props: T) => Component<T>) | undefined
|
||||
) =>
|
||||
isVNode(ComponentLike)
|
||||
? (props: T) => h(ComponentLike, props)
|
||||
: (ComponentLike as Slot)
|
||||
|
||||
Reference in New Issue
Block a user