import React, { CSSProperties, ReactNode, useState } from 'react'
import styled from 'styled-components'
import type { ArtColumn, SortItem, SortOrder, TableTransform } from '../interfaces'
import { internals } from '../internals'
import { collectNodes, isLeafNode, layeredSort, mergeCellProps, smartCompare } from '../utils'
import { warnTransformsDeprecated } from './warnTransformsDeprecated'
function SortIcon({
size = 32,
style,
className,
order,
}: {
style?: CSSProperties
className?: string
size?: number
order?: SortOrder
}) {
return (
)
}
function DefaultSortHeaderCell({ children, column, onToggle, sortOrder, sortIndex, sortOptions }: SortHeaderCellProps) {
// 通过 justify-content 来与 col.align 保持对齐方向一致
const justifyContent = column.align === 'right' ? 'flex-end' : column.align === 'center' ? 'center' : 'flex-start'
return (
{children}
{sortOptions.mode === 'multiple' && sortIndex != -1 && (
{sortIndex + 1}
)}
)
}
function hasAnySortableColumns(cols: ArtColumn[]): boolean {
return cols.some(
(col) => Boolean(col.features?.sortable) || (!isLeafNode(col) && hasAnySortableColumns(col.children)),
)
}
const TableHeaderCell = styled.div`
cursor: pointer;
display: flex;
align-items: center;
`
export interface SortHeaderCellProps {
/** 调用 makeSortTransform(...) 时的参数 */
sortOptions: Required>
/** 在添加排序相关的内容之前 表头原有的渲染内容 */
children: ReactNode
/** 当前排序 */
sortOrder: SortOrder
/** 多列排序下,sortIndex 指明了当前排序字段起作用的顺序. 当 sortOrder 为 none 时,sortIndex 固定为 -1 */
sortIndex: number
/** 当前列的配置 */
column: ArtColumn
/** 切换排序的回调 */
onToggle(e: React.MouseEvent): void
}
export interface SortOptions {
/** 排序字段列表 */
sorts: SortItem[]
/** 更新排序字段列表的回调函数 */
onChangeSorts(nextSorts: SortItem[]): void
/** 排序切换顺序 */
orders?: SortOrder[]
/** 排序模式。单选 single,多选 multiple,默认为多选 */
mode?: 'single' | 'multiple'
/** 自定义排序表头 */
SortHeaderCell?: React.ComponentType
/** 是否保持 dataSource 不变 */
keepDataSource?: boolean
/** 排序激活时 是否高亮这一列的单元格 */
highlightColumnWhenActive?: boolean
/** 是否对触发 onChangeOpenKeys 的 click 事件调用 event.stopPropagation() */
stopClickEventPropagation?: boolean
}
/** @deprecated transform 用法已经过时,请使用 pipeline 来对表格进行拓展 */
export function makeSortTransform({
sorts: inputSorts,
onChangeSorts: inputOnChangeSorts,
orders = ['desc', 'asc', 'none'],
mode = 'multiple',
SortHeaderCell = DefaultSortHeaderCell,
keepDataSource,
highlightColumnWhenActive,
stopClickEventPropagation,
}: SortOptions): TableTransform {
warnTransformsDeprecated('makeSortTransform')
const filteredInputSorts = inputSorts.filter((s) => s.order !== 'none')
// 单字段排序的情况下 sorts 中只有第一个排序字段才会生效
const sorts = mode === 'multiple' ? filteredInputSorts : filteredInputSorts.slice(0, 1)
const onChangeSorts =
mode === 'multiple'
? inputOnChangeSorts
: (nextSorts: SortItem[]) => {
// 单字段排序的情况下,nextSorts 中只有最后一个排序字段才会生效
const len = nextSorts.length
inputOnChangeSorts(nextSorts.slice(len - 1))
}
const sortOptions = {
sorts,
onChangeSorts,
orders,
mode,
keepDataSource,
highlightColumnWhenActive,
stopClickEventPropagation,
}
const sortMap = new Map(sorts.map((sort, index) => [sort.code, { index, ...sort }]))
return ({ dataSource, columns }) => {
if (process.env.NODE_ENV !== 'production') {
if (!hasAnySortableColumns(columns)) {
console.warn(
'[ali-react-table] commonTransform.sort 缺少可排序的列,请通过 column.features.sortable 来指定哪些列可排序',
columns,
)
}
}
return { columns: processColumns(columns), dataSource: processDataSource(dataSource) }
function processDataSource(dataSource: any[]) {
if (keepDataSource) {
return dataSource
}
const sortColumnsMap = new Map(
collectNodes(columns, 'leaf-only')
.filter((col) => col.features?.sortable != null)
.map((col) => [col.code, col]),
)
return layeredSort(dataSource, (x, y) => {
for (const { code, order } of sorts) {
const column = sortColumnsMap.get(code)
// 如果 code 对应的 column 不可排序,我们跳过该 code
if (column == null) {
continue
}
const sortable = column.features.sortable
const compareFn = typeof sortable === 'function' ? sortable : smartCompare
const xValue = internals.safeGetValue(column, x, -1)
const yValue = internals.safeGetValue(column, y, -1)
const cmp = compareFn(xValue, yValue)
if (cmp !== 0) {
return cmp * (order === 'asc' ? 1 : -1)
}
}
return 0
})
}
// 在「升序 - 降序 - 不排序」之间不断切换
function toggle(code: string) {
const sort = sortMap.get(code)
if (sort == null) {
onChangeSorts(sorts.concat([{ code, order: orders[0] }]))
} else {
const index = sorts.findIndex((s) => s.code === code)
const nextSorts = sorts.slice(0, index + 1)
const nextOrder = getNextOrder(sort.order)
if (nextOrder === 'none') {
nextSorts.pop()
} else {
nextSorts[index] = { ...nextSorts[index], order: nextOrder }
}
onChangeSorts(nextSorts)
}
}
function processColumns(columns: ArtColumn[]) {
return columns.map(dfs)
function dfs(col: ArtColumn): ArtColumn {
const result = { ...col }
const sortable = col.code && (col.features?.sortable || sortMap.has(col.code))
const active = sortable && sortMap.has(col.code)
if (sortable) {
let sortIndex = -1
let sortOrder: SortOrder = 'none'
if (active) {
const { order, index } = sortMap.get(col.code)
sortOrder = order
sortIndex = index
if (highlightColumnWhenActive) {
result.headerCellProps = mergeCellProps(col.headerCellProps, {
style: { background: 'var(--header-highlight-bgcolor)' } as any,
})
result.getCellProps = (value, row, rowIndex) => {
const prevCellProps = internals.safeGetCellProps(col, row, rowIndex)
return mergeCellProps(prevCellProps, {
style: { background: 'var(--highlight-bgcolor)' } as any,
})
}
}
}
result.title = (
{
if (stopClickEventPropagation) {
e.stopPropagation()
}
toggle(col.code)
}}
sortOrder={sortOrder}
column={col}
sortIndex={sortIndex}
sortOptions={sortOptions}
>
{internals.safeRenderHeader(col)}
)
}
if (!isLeafNode(col)) {
result.children = col.children.map(dfs)
}
return result
}
}
}
function getNextOrder(order: SortOrder) {
const idx = orders.indexOf(order)
return orders[idx === orders.length - 1 ? 0 : idx + 1]
}
}
/** @deprecated transform 用法已经过时,请使用 pipeline 来对表格进行拓展 */
export function useSortTransform({
defaultSorts = [],
...others
}: Omit & { defaultSorts?: SortItem[] } = {}) {
const [sorts, onChangeSorts] = useState(defaultSorts)
return makeSortTransform({ sorts, onChangeSorts, ...others })
}