diff --git a/packages/components/table-v2/src/cell.ts b/packages/components/table-v2/src/cell.ts index 7b8f830edc..d1e8a6743e 100644 --- a/packages/components/table-v2/src/cell.ts +++ b/packages/components/table-v2/src/cell.ts @@ -1,16 +1,14 @@ import { buildProps, definePropType } from '@element-plus/utils' +import { column } from './common' import type { ExtractPropTypes } from 'vue' -import type { Column } from './types' export const tableV2CellProps = buildProps({ class: String, cellData: { type: definePropType([String, Boolean, Number, Object]), }, - column: { - type: definePropType>(Object), - }, + column, columnIndex: Number, rowData: { type: definePropType(Object), diff --git a/packages/components/table-v2/src/column-resizer.ts b/packages/components/table-v2/src/column-resizer.ts new file mode 100644 index 0000000000..e3dfabd6ab --- /dev/null +++ b/packages/components/table-v2/src/column-resizer.ts @@ -0,0 +1,31 @@ +import { buildProps, definePropType } from '@element-plus/utils' +import { column, styleType } from './common' + +import type { ExtractPropTypes } from 'vue' +import type { AnyColumn } from './common' + +type ResizeTimingFn = (column: AnyColumn) => void +type ResizeHandler = (column: AnyColumn, width: number) => void + +export const tableV2ColumnResizerProps = buildProps({ + column: { ...column, required: true }, + onResize: { + type: definePropType(Function), + }, + + onResizeStart: { + type: definePropType(Function), + }, + onResizeStop: { + type: definePropType(Function), + }, + minWidth: { + type: Number, + default: 30, + }, + style: styleType, +} as const) + +export type TableV2ColumnResizerProps = ExtractPropTypes< + typeof tableV2ColumnResizerProps +> diff --git a/packages/components/table-v2/src/common.ts b/packages/components/table-v2/src/common.ts index 0efaed3c0b..3eabb7a6ad 100644 --- a/packages/components/table-v2/src/common.ts +++ b/packages/components/table-v2/src/common.ts @@ -3,7 +3,7 @@ import { definePropType, mutable } from '@element-plus/utils' import type { CSSProperties } from 'vue' import type { Column, KeyType } from './types' -type AnyColumn = Column +export type AnyColumn = Column /** * @Note even though we can use `string[] | string` as the type but for diff --git a/packages/components/table-v2/src/sort-icon.tsx b/packages/components/table-v2/src/sort-icon.tsx new file mode 100644 index 0000000000..7f6a1cd455 --- /dev/null +++ b/packages/components/table-v2/src/sort-icon.tsx @@ -0,0 +1,21 @@ +import { defineComponent } from 'vue' +import ElIcon from '@element-plus/components/icon' +import { SortDown, SortUp } from '@element-plus/icons-vue' +import { SortOrder } from './constants' + +export type SortIconProps = { + sortOrder: SortOrder + class?: string +} + +const SortIcon = defineComponent((props: SortIconProps) => { + const { sortOrder } = props + + return ( + + {sortOrder === SortOrder.ASC ? SortUp : SortDown} + + ) +}) + +export default SortIcon diff --git a/packages/components/table-v2/src/table-column-resizer.tsx b/packages/components/table-v2/src/table-column-resizer.tsx new file mode 100644 index 0000000000..04aa5c25d6 --- /dev/null +++ b/packages/components/table-v2/src/table-column-resizer.tsx @@ -0,0 +1,160 @@ +import { + computed, + defineComponent, + onBeforeUnmount, + ref, + shallowRef, + unref, + watch, + withModifiers, +} from 'vue' +import { NOOP } from '@vue/shared' +import { useNamespace } from '@element-plus/hooks' +import { tableV2ColumnResizerProps } from './column-resizer' + +import type { TableV2ColumnResizerProps } from './column-resizer' + +const COMPONENT_NAME = 'ColumnResizer' + +const ColumnResizer = defineComponent({ + name: COMPONENT_NAME, + props: tableV2ColumnResizerProps, + setup(props) { + const { resizerRef, onClick, onMousedown, onTouchStart, onDragEnd } = + useColumnResizer(props) + + return () => ( +
+ ) + }, +}) + +function useColumnResizer(props: TableV2ColumnResizerProps) { + const ns = useNamespace('') + const isDragging = shallowRef(false) + const resizerRef = ref() + const width = shallowRef(0) + const lastX = shallowRef(null) + const ownerDocument = computed(() => unref(resizerRef)?.ownerDocument) + + const onDragStart = withModifiers(() => { + isDragging.value = true + const column = props.column! + width.value = column.width + + props.onResizeStart?.(column) + }, ['left']) + + const addEvents = ( + drag: 'mousemove' | 'touchmove', + end: 'mouseup' | 'touchend' + ) => { + const _ownerDocument = unref(ownerDocument)! + + if (!_ownerDocument) return + _ownerDocument.addEventListener(drag, onDrag) + _ownerDocument.addEventListener(end, onDragEnd) + } + + const detachEvents = () => { + const _ownerDocument = unref(ownerDocument)! + + if (!_ownerDocument) return + _ownerDocument.removeEventListener('mousemove', onDrag) + _ownerDocument.removeEventListener('touchmove', onDrag) + _ownerDocument.removeEventListener('mouseup', onDragEnd) + _ownerDocument.removeEventListener('touchend', onDragEnd) + } + + const onDragEnd = () => { + if (!unref(isDragging)) return + const { column, onResizeStop } = props + isDragging.value = false + onResizeStop?.(column) + detachEvents() + } + + const onClick = withModifiers(NOOP, ['stop']) + const onMousedown = (e: MouseEvent) => { + onDragStart(e) + addEvents('mousemove', 'mouseup') + } + + const onTouchStart = (e: TouchEvent) => { + onDragStart(e) + addEvents('touchmove', 'touchend') + } + + function onDrag(e: MouseEvent | TouchEvent) { + const { type, preventDefault } = e + let { clientX } = e as MouseEvent + const { targetTouches } = e as TouchEvent + if (type === 'touchmove') { + preventDefault() + if (targetTouches?.[0]) clientX = targetTouches[0].clientX + } + + const { offsetParent } = unref(resizerRef)! + const offsetParentRect = offsetParent!.getBoundingClientRect() + const x = clientX + offsetParent!.scrollLeft - offsetParentRect.left + + const _lastX = unref(lastX) + + if (_lastX === null) { + lastX.value = x + return + } + + const { column, minWidth: propMinWidth } = props + const { width: columnWidth, maxWidth, minWidth = propMinWidth } = column! + const movedX = x - _lastX + if (!movedX) return + + width.value = columnWidth + movedX + lastX.value = x + + let newWidth = unref(width) + if (maxWidth && newWidth > maxWidth) { + newWidth = maxWidth + } else if (newWidth < minWidth) { + newWidth = minWidth + } + + if (newWidth === unref(width)) return + props.onResize?.(column, newWidth) + } + + watch(isDragging, (val) => { + const _ownerDocument = unref(ownerDocument) + if (!_ownerDocument) return + + if (val) { + _ownerDocument.body.classList.add(`${unref(ns.namespace)}-dragging`) + } else { + _ownerDocument.body.classList.remove(`${unref(ns.namespace)}-dragging`) + } + }) + + onBeforeUnmount(() => { + detachEvents() + }) + + return { + resizerRef, + onClick, + onMousedown, + onTouchStart, + onDragEnd, + } +} + +export default ColumnResizer +export { useColumnResizer } diff --git a/packages/components/table-v2/src/table-v2.tsx b/packages/components/table-v2/src/table-v2.tsx index d40b82adbc..849c422c3c 100644 --- a/packages/components/table-v2/src/table-v2.tsx +++ b/packages/components/table-v2/src/table-v2.tsx @@ -5,7 +5,7 @@ import { isFunction, isObject } from '@element-plus/utils' import { useTable } from './use-table' import { tryCall } from './utils' import { TableV2InjectionKey } from './tokens' -import { Alignment } from './constants' +import { Alignment, SortOrder, oppositeOrderMap } from './constants' import { placeholderSign } from './private' import { tableV2Props } from './table' // components @@ -14,7 +14,9 @@ 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' import type { VNode } from 'vue' import type { TableGridRowSlotParams } from './table-grid' @@ -49,6 +51,9 @@ const TableV2 = defineComponent({ vScrollbarSize, onColumnSorted, + onColumnResized, + onColumnResizeStart, + onColumnResizeEnd, onRowHovered, onRowExpanded, onRowsRendered, @@ -302,7 +307,7 @@ const TableV2 = defineComponent({ return } - const { headerCellRenderer, headerClass } = column + const { headerCellRenderer, headerClass, sortable, resizable } = column /** * render Cell children @@ -319,7 +324,7 @@ const TableV2 = defineComponent({ /** * Render cell container and sort indicator */ - // const { sortBy, sortState, headerCellProps } = props + const { sortBy, sortState, headerCellProps } = props const element = 'header-cell' const cellKls = [ @@ -327,29 +332,43 @@ const TableV2 = defineComponent({ ...tryCall(headerClass, renderHeaderCellProps, ''), column.align === Alignment.CENTER && ns.em(element, 'align-center'), column.align === Alignment.RIGHT && ns.em(element, 'align-right'), - column.sortable && ns.is('sortable'), + 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 - // } + 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(props.headerCellProps, renderHeaderCellProps), + ...tryCall(headerCellProps, renderHeaderCellProps), onClick: column.sortable ? onColumnSorted : undefined, class: cellKls, style: unref(columnsStyles)[column.key], dataKey: column.key, } - return
{Cell}
+ return ( +
+ {Cell} + {sortable && } + {resizable && ( + + )} +
+ ) } provide(TableV2InjectionKey, { diff --git a/packages/components/table-v2/src/table.ts b/packages/components/table-v2/src/table.ts index bc9bff2d5b..4630f63172 100644 --- a/packages/components/table-v2/src/table.ts +++ b/packages/components/table-v2/src/table.ts @@ -178,7 +178,7 @@ export const tableV2Props = buildProps({ }, onRowExpand: tableV2RowProps.onRowExpand, onScroll: tableV2GridProps.onScroll, - onRowRendered: tableV2GridProps.onRowRendered, + onRowRendered: tableV2GridProps.onRowsRendered, rowEventHandlers: tableV2RowProps.rowEventHandlers, } as const) diff --git a/packages/components/table-v2/src/use-table.ts b/packages/components/table-v2/src/use-table.ts index d32db03b98..e38211c07b 100644 --- a/packages/components/table-v2/src/use-table.ts +++ b/packages/components/table-v2/src/use-table.ts @@ -204,7 +204,10 @@ function useTable(props: TableV2Props) { props.onExpandedRowsChange?.(_expandedRowKeys) } - function onColumnResized(key: KeyType, width: number) { + function onColumnResized( + { key }: TableV2Props['columns'][number], + width: number + ) { resizingWidth.value = width const column = getColumn(key)! @@ -212,7 +215,7 @@ function useTable(props: TableV2Props) { props.onColumnResize?.(column, width) } - function onColumnResizeStart(key: KeyType) { + function onColumnResizeStart({ key }: TableV2Props['columns'][number]) { resizingKey.value = key }