mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-12-02 11:17:46 +08:00
feat(components): [virtual-list] renderer (#7167)
- Add column-resizer for resizing column - Add sort and column resizer to row header renderer
This commit is contained in:
parent
bc4f82d7b5
commit
971d58d9f4
@ -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<any>([String, Boolean, Number, Object]),
|
||||
},
|
||||
column: {
|
||||
type: definePropType<Column<any>>(Object),
|
||||
},
|
||||
column,
|
||||
columnIndex: Number,
|
||||
rowData: {
|
||||
type: definePropType<any>(Object),
|
||||
|
31
packages/components/table-v2/src/column-resizer.ts
Normal file
31
packages/components/table-v2/src/column-resizer.ts
Normal file
@ -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<ResizeHandler>(Function),
|
||||
},
|
||||
|
||||
onResizeStart: {
|
||||
type: definePropType<ResizeTimingFn>(Function),
|
||||
},
|
||||
onResizeStop: {
|
||||
type: definePropType<ResizeTimingFn>(Function),
|
||||
},
|
||||
minWidth: {
|
||||
type: Number,
|
||||
default: 30,
|
||||
},
|
||||
style: styleType,
|
||||
} as const)
|
||||
|
||||
export type TableV2ColumnResizerProps = ExtractPropTypes<
|
||||
typeof tableV2ColumnResizerProps
|
||||
>
|
@ -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<any>
|
||||
export type AnyColumn = Column<any>
|
||||
|
||||
/**
|
||||
* @Note even though we can use `string[] | string` as the type but for
|
||||
|
21
packages/components/table-v2/src/sort-icon.tsx
Normal file
21
packages/components/table-v2/src/sort-icon.tsx
Normal file
@ -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 (
|
||||
<ElIcon size={14} class={props.class}>
|
||||
{sortOrder === SortOrder.ASC ? SortUp : SortDown}
|
||||
</ElIcon>
|
||||
)
|
||||
})
|
||||
|
||||
export default SortIcon
|
160
packages/components/table-v2/src/table-column-resizer.tsx
Normal file
160
packages/components/table-v2/src/table-column-resizer.tsx
Normal file
@ -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 () => (
|
||||
<div
|
||||
ref={resizerRef}
|
||||
onClick={onClick}
|
||||
onMousedown={onMousedown}
|
||||
onMouseup={onDragEnd}
|
||||
onTouchstart={onTouchStart}
|
||||
onTouchend={onDragEnd}
|
||||
style={props.style}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
function useColumnResizer(props: TableV2ColumnResizerProps) {
|
||||
const ns = useNamespace('')
|
||||
const isDragging = shallowRef(false)
|
||||
const resizerRef = ref<HTMLElement>()
|
||||
const width = shallowRef(0)
|
||||
const lastX = shallowRef<null | number>(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 }
|
@ -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 <div {...cellProps}>{Cell}</div>
|
||||
return (
|
||||
<div {...cellProps}>
|
||||
{Cell}
|
||||
{sortable && <SortIcon sortOrder={sortOrder} />}
|
||||
{resizable && (
|
||||
<ColumnResizer
|
||||
class={ns.e('column-resizer')}
|
||||
column={column}
|
||||
onResize={onColumnResized}
|
||||
onResizeStart={onColumnResizeStart}
|
||||
onResizeStop={onColumnResizeEnd}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
provide(TableV2InjectionKey, {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user