refactor: table
parent
b8319bdb38
commit
c9db47533f
|
@ -0,0 +1,14 @@
|
|||
import type { UnwrapRef } from 'vue';
|
||||
import { reactive, toRef } from 'vue';
|
||||
|
||||
/**
|
||||
* Reactively pick fields from a reactive object
|
||||
*
|
||||
* @see https://vueuse.js.org/reactivePick
|
||||
*/
|
||||
export function reactivePick<T extends object, K extends keyof T>(
|
||||
obj: T,
|
||||
...keys: K[]
|
||||
): { [S in K]: UnwrapRef<T[S]> } {
|
||||
return reactive(Object.fromEntries(keys.map(k => [k, toRef(obj, k)]))) as any;
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
import classNames from 'ant-design-vue/es/_util/classNames';
|
||||
import { isValidElement } from 'ant-design-vue/es/_util/props-util';
|
||||
import { CSSProperties, defineComponent, HTMLAttributes } from 'vue';
|
||||
|
||||
import type {
|
||||
DataIndex,
|
||||
ColumnType,
|
||||
RenderedCell,
|
||||
CustomizeComponent,
|
||||
CellType,
|
||||
DefaultRecordType,
|
||||
AlignType,
|
||||
CellEllipsisType,
|
||||
} from '../interface';
|
||||
import { getPathValue, validateValue } from '../utils/valueUtil';
|
||||
|
||||
function isRenderCell<RecordType = DefaultRecordType>(
|
||||
data: RenderedCell<RecordType>,
|
||||
): data is RenderedCell<RecordType> {
|
||||
return data && typeof data === 'object' && !Array.isArray(data) && !isValidElement(data);
|
||||
}
|
||||
|
||||
export interface CellProps<RecordType = DefaultRecordType> {
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
record?: RecordType;
|
||||
/** `record` index. Not `column` index. */
|
||||
index?: number;
|
||||
dataIndex?: DataIndex;
|
||||
customRender?: ColumnType<RecordType>['customRender'];
|
||||
component?: CustomizeComponent;
|
||||
children?: any;
|
||||
colSpan?: number;
|
||||
rowSpan?: number;
|
||||
ellipsis?: CellEllipsisType;
|
||||
align?: AlignType;
|
||||
|
||||
// Fixed
|
||||
fixLeft?: number | false;
|
||||
fixRight?: number | false;
|
||||
firstFixLeft?: boolean;
|
||||
lastFixLeft?: boolean;
|
||||
firstFixRight?: boolean;
|
||||
lastFixRight?: boolean;
|
||||
|
||||
// Additional
|
||||
/** @private Used for `expandable` with nest tree */
|
||||
appendNode?: any;
|
||||
additionalProps?: Omit<HTMLAttributes, 'style'> & { style?: CSSProperties };
|
||||
|
||||
rowType?: 'header' | 'body' | 'footer';
|
||||
|
||||
isSticky?: boolean;
|
||||
|
||||
column?: ColumnType<RecordType>;
|
||||
}
|
||||
export default defineComponent<CellProps>({
|
||||
name: 'Cell',
|
||||
props: [] as any,
|
||||
slots: ['appendNode'],
|
||||
setup(props) {
|
||||
return () => {
|
||||
const {
|
||||
prefixCls,
|
||||
className,
|
||||
record,
|
||||
index,
|
||||
dataIndex,
|
||||
customRender,
|
||||
children,
|
||||
component: Component = 'td',
|
||||
colSpan,
|
||||
rowSpan,
|
||||
fixLeft,
|
||||
fixRight,
|
||||
firstFixLeft,
|
||||
lastFixLeft,
|
||||
firstFixRight,
|
||||
lastFixRight,
|
||||
appendNode,
|
||||
additionalProps = {},
|
||||
ellipsis,
|
||||
align,
|
||||
rowType,
|
||||
isSticky,
|
||||
column,
|
||||
} = props;
|
||||
const cellPrefixCls = `${prefixCls}-cell`;
|
||||
|
||||
// ==================== Child Node ====================
|
||||
let cellProps: CellType;
|
||||
let childNode;
|
||||
|
||||
if (validateValue(children)) {
|
||||
childNode = children;
|
||||
} else {
|
||||
const value = getPathValue(record, dataIndex);
|
||||
|
||||
// Customize render node
|
||||
childNode = value;
|
||||
if (customRender) {
|
||||
const renderData = customRender({ text: value, value, record, index, column });
|
||||
|
||||
if (isRenderCell(renderData)) {
|
||||
childNode = renderData.children;
|
||||
cellProps = renderData.props;
|
||||
} else {
|
||||
childNode = renderData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not crash if final `childNode` is not validate ReactNode
|
||||
if (
|
||||
typeof childNode === 'object' &&
|
||||
!Array.isArray(childNode) &&
|
||||
!isValidElement(childNode)
|
||||
) {
|
||||
childNode = null;
|
||||
}
|
||||
|
||||
if (ellipsis && (lastFixLeft || firstFixRight)) {
|
||||
childNode = <span class={`${cellPrefixCls}-content`}>{childNode}</span>;
|
||||
}
|
||||
|
||||
const {
|
||||
colSpan: cellColSpan,
|
||||
rowSpan: cellRowSpan,
|
||||
style: cellStyle,
|
||||
className: cellClassName,
|
||||
class: cellClass,
|
||||
...restCellProps
|
||||
} = cellProps || {};
|
||||
const mergedColSpan = cellColSpan !== undefined ? cellColSpan : colSpan;
|
||||
const mergedRowSpan = cellRowSpan !== undefined ? cellRowSpan : rowSpan;
|
||||
|
||||
if (mergedColSpan === 0 || mergedRowSpan === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ====================== Fixed =======================
|
||||
const fixedStyle: CSSProperties = {};
|
||||
const isFixLeft = typeof fixLeft === 'number';
|
||||
const isFixRight = typeof fixRight === 'number';
|
||||
|
||||
if (isFixLeft) {
|
||||
fixedStyle.position = 'sticky';
|
||||
fixedStyle.left = `${fixLeft}px`;
|
||||
}
|
||||
if (isFixRight) {
|
||||
fixedStyle.position = 'sticky';
|
||||
|
||||
fixedStyle.right = `${fixRight}px`;
|
||||
}
|
||||
|
||||
// ====================== Align =======================
|
||||
const alignStyle: CSSProperties = {};
|
||||
if (align) {
|
||||
alignStyle.textAlign = align;
|
||||
}
|
||||
|
||||
// ====================== Render ======================
|
||||
let title: string;
|
||||
const ellipsisConfig: CellEllipsisType = ellipsis === true ? { showTitle: true } : ellipsis;
|
||||
if (ellipsisConfig && (ellipsisConfig.showTitle || rowType === 'header')) {
|
||||
debugger;
|
||||
if (typeof childNode === 'string' || typeof childNode === 'number') {
|
||||
title = childNode.toString();
|
||||
} else if (isValidElement(childNode) && typeof childNode.props.children === 'string') {
|
||||
title = childNode.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
const componentProps = {
|
||||
title,
|
||||
...restCellProps,
|
||||
...additionalProps,
|
||||
colSpan: mergedColSpan && mergedColSpan !== 1 ? mergedColSpan : null,
|
||||
rowSpan: mergedRowSpan && mergedRowSpan !== 1 ? mergedRowSpan : null,
|
||||
class: classNames(
|
||||
cellPrefixCls,
|
||||
className,
|
||||
{
|
||||
[`${cellPrefixCls}-fix-left`]: isFixLeft,
|
||||
[`${cellPrefixCls}-fix-left-first`]: firstFixLeft,
|
||||
[`${cellPrefixCls}-fix-left-last`]: lastFixLeft,
|
||||
[`${cellPrefixCls}-fix-right`]: isFixRight,
|
||||
[`${cellPrefixCls}-fix-right-first`]: firstFixRight,
|
||||
[`${cellPrefixCls}-fix-right-last`]: lastFixRight,
|
||||
[`${cellPrefixCls}-ellipsis`]: ellipsis,
|
||||
[`${cellPrefixCls}-with-append`]: appendNode,
|
||||
[`${cellPrefixCls}-fix-sticky`]: (isFixLeft || isFixRight) && isSticky,
|
||||
},
|
||||
additionalProps.class,
|
||||
cellClassName,
|
||||
cellClass,
|
||||
),
|
||||
style: {
|
||||
...additionalProps.style,
|
||||
...alignStyle,
|
||||
...fixedStyle,
|
||||
...cellStyle,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Component {...componentProps}>
|
||||
{appendNode}
|
||||
{childNode}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
import type { ColumnType } from './interface';
|
||||
import { INTERNAL_COL_DEFINE } from './utils/legacyUtil';
|
||||
|
||||
export interface ColGroupProps<RecordType> {
|
||||
colWidths: readonly (number | string)[];
|
||||
columns?: readonly ColumnType<RecordType>[];
|
||||
columCount?: number;
|
||||
}
|
||||
|
||||
function ColGroup<RecordType>({ colWidths, columns, columCount }: ColGroupProps<RecordType>) {
|
||||
const cols = [];
|
||||
const len = columCount || columns.length;
|
||||
|
||||
// Only insert col with width & additional props
|
||||
// Skip if rest col do not have any useful info
|
||||
let mustInsert = false;
|
||||
for (let i = len - 1; i >= 0; i -= 1) {
|
||||
const width = colWidths[i];
|
||||
const column = columns && columns[i];
|
||||
const additionalProps = column && column[INTERNAL_COL_DEFINE];
|
||||
|
||||
if (width || additionalProps || mustInsert) {
|
||||
cols.unshift(
|
||||
<col
|
||||
key={i}
|
||||
style={{ width: typeof width === 'number' ? `${width}px` : width }}
|
||||
{...additionalProps}
|
||||
/>,
|
||||
);
|
||||
mustInsert = true;
|
||||
}
|
||||
}
|
||||
|
||||
return <colgroup>{cols}</colgroup>;
|
||||
}
|
||||
|
||||
export default ColGroup;
|
|
@ -0,0 +1,127 @@
|
|||
import classNames from 'ant-design-vue/es/_util/classNames';
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { useInjectTable } from '../context/TableContext';
|
||||
import type {
|
||||
ColumnsType,
|
||||
CellType,
|
||||
StickyOffsets,
|
||||
ColumnType,
|
||||
GetComponentProps,
|
||||
ColumnGroupType,
|
||||
DefaultRecordType,
|
||||
} from '../interface';
|
||||
import HeaderRow from './HeaderRow';
|
||||
|
||||
function parseHeaderRows<RecordType>(
|
||||
rootColumns: ColumnsType<RecordType>,
|
||||
): CellType<RecordType>[][] {
|
||||
const rows: CellType<RecordType>[][] = [];
|
||||
|
||||
function fillRowCells(
|
||||
columns: ColumnsType<RecordType>,
|
||||
colIndex: number,
|
||||
rowIndex: number = 0,
|
||||
): number[] {
|
||||
// Init rows
|
||||
rows[rowIndex] = rows[rowIndex] || [];
|
||||
|
||||
let currentColIndex = colIndex;
|
||||
const colSpans: number[] = columns.filter(Boolean).map(column => {
|
||||
const cell: CellType<RecordType> = {
|
||||
key: column.key,
|
||||
className: classNames(column.className, column.class),
|
||||
children: column.title,
|
||||
column,
|
||||
colStart: currentColIndex,
|
||||
};
|
||||
|
||||
let colSpan: number = 1;
|
||||
|
||||
const subColumns = (column as ColumnGroupType<RecordType>).children;
|
||||
if (subColumns && subColumns.length > 0) {
|
||||
colSpan = fillRowCells(subColumns, currentColIndex, rowIndex + 1).reduce(
|
||||
(total, count) => total + count,
|
||||
0,
|
||||
);
|
||||
cell.hasSubColumns = true;
|
||||
}
|
||||
|
||||
if ('colSpan' in column) {
|
||||
({ colSpan } = column);
|
||||
}
|
||||
|
||||
if ('rowSpan' in column) {
|
||||
cell.rowSpan = column.rowSpan;
|
||||
}
|
||||
|
||||
cell.colSpan = colSpan;
|
||||
cell.colEnd = cell.colStart + colSpan - 1;
|
||||
rows[rowIndex].push(cell);
|
||||
|
||||
currentColIndex += colSpan;
|
||||
|
||||
return colSpan;
|
||||
});
|
||||
|
||||
return colSpans;
|
||||
}
|
||||
|
||||
// Generate `rows` cell data
|
||||
fillRowCells(rootColumns, 0);
|
||||
|
||||
// Handle `rowSpan`
|
||||
const rowCount = rows.length;
|
||||
for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
|
||||
rows[rowIndex].forEach(cell => {
|
||||
if (!('rowSpan' in cell) && !cell.hasSubColumns) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
cell.rowSpan = rowCount - rowIndex;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
export interface HeaderProps<RecordType = DefaultRecordType> {
|
||||
columns: ColumnsType<RecordType>;
|
||||
flattenColumns: readonly ColumnType<RecordType>[];
|
||||
stickyOffsets: StickyOffsets;
|
||||
onHeaderRow: GetComponentProps<readonly ColumnType<RecordType>[]>;
|
||||
}
|
||||
|
||||
export default defineComponent<HeaderProps>({
|
||||
name: 'Header',
|
||||
props: ['columns', 'flattenColumns', 'stickyOffsets', 'onHeaderRow'] as any,
|
||||
setup(props) {
|
||||
const tableContext = useInjectTable();
|
||||
const rows = computed(() => parseHeaderRows(props.columns));
|
||||
return () => {
|
||||
const { prefixCls, getComponent } = tableContext;
|
||||
const { stickyOffsets, flattenColumns, onHeaderRow } = props;
|
||||
const WrapperComponent = getComponent(['header', 'wrapper'], 'thead');
|
||||
const trComponent = getComponent(['header', 'row'], 'tr');
|
||||
const thComponent = getComponent(['header', 'cell'], 'th');
|
||||
return (
|
||||
<WrapperComponent class={`${prefixCls}-thead`}>
|
||||
{rows.value.map((row, rowIndex) => {
|
||||
const rowNode = (
|
||||
<HeaderRow
|
||||
key={rowIndex}
|
||||
flattenColumns={flattenColumns}
|
||||
cells={row}
|
||||
stickyOffsets={stickyOffsets}
|
||||
rowComponent={trComponent}
|
||||
cellComponent={thComponent}
|
||||
onHeaderRow={onHeaderRow}
|
||||
index={rowIndex}
|
||||
/>
|
||||
);
|
||||
|
||||
return rowNode;
|
||||
})}
|
||||
</WrapperComponent>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
import { defineComponent } from 'vue';
|
||||
import Cell from '../Cell';
|
||||
import { useInjectTable } from '../context/TableContext';
|
||||
import {
|
||||
CellType,
|
||||
StickyOffsets,
|
||||
ColumnType,
|
||||
CustomizeComponent,
|
||||
GetComponentProps,
|
||||
DefaultRecordType,
|
||||
} from '../interface';
|
||||
import { getCellFixedInfo } from '../utils/fixUtil';
|
||||
import { getColumnsKey } from '../utils/valueUtil';
|
||||
|
||||
export interface RowProps<RecordType = DefaultRecordType> {
|
||||
cells: readonly CellType<RecordType>[];
|
||||
stickyOffsets: StickyOffsets;
|
||||
flattenColumns: readonly ColumnType<RecordType>[];
|
||||
rowComponent: CustomizeComponent;
|
||||
cellComponent: CustomizeComponent;
|
||||
onHeaderRow: GetComponentProps<readonly ColumnType<RecordType>[]>;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export default defineComponent<RowProps>({
|
||||
name: 'HeaderRow',
|
||||
props: [
|
||||
'cells',
|
||||
'stickyOffsets',
|
||||
'flattenColumns',
|
||||
'rowComponent',
|
||||
'cellComponent',
|
||||
'index',
|
||||
'onHeaderRow',
|
||||
] as any,
|
||||
setup(props: RowProps) {
|
||||
const tableContext = useInjectTable();
|
||||
return () => {
|
||||
const { prefixCls, direction } = tableContext;
|
||||
const {
|
||||
cells,
|
||||
stickyOffsets,
|
||||
flattenColumns,
|
||||
rowComponent: RowComponent,
|
||||
cellComponent: CellComponent,
|
||||
onHeaderRow,
|
||||
index,
|
||||
} = props;
|
||||
|
||||
let rowProps;
|
||||
if (onHeaderRow) {
|
||||
rowProps = onHeaderRow(
|
||||
cells.map(cell => cell.column),
|
||||
index,
|
||||
);
|
||||
}
|
||||
|
||||
const columnsKey = getColumnsKey(cells.map(cell => cell.column));
|
||||
|
||||
return (
|
||||
<RowComponent {...rowProps}>
|
||||
{cells.map((cell: CellType, cellIndex) => {
|
||||
const { column } = cell;
|
||||
const fixedInfo = getCellFixedInfo(
|
||||
cell.colStart,
|
||||
cell.colEnd,
|
||||
flattenColumns,
|
||||
stickyOffsets,
|
||||
direction,
|
||||
);
|
||||
|
||||
let additionalProps;
|
||||
if (column && column.onHeaderCell) {
|
||||
additionalProps = cell.column.onHeaderCell(column);
|
||||
}
|
||||
|
||||
return (
|
||||
<Cell
|
||||
{...cell}
|
||||
ellipsis={column.ellipsis}
|
||||
align={column.align}
|
||||
component={CellComponent}
|
||||
prefixCls={prefixCls}
|
||||
key={columnsKey[cellIndex]}
|
||||
{...fixedInfo}
|
||||
additionalProps={additionalProps}
|
||||
rowType="header"
|
||||
column={column}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</RowComponent>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
function Panel(_, { slots }) {
|
||||
return <div>{slots.default?.()}</div>;
|
||||
}
|
||||
|
||||
Panel.displayName = 'Panel';
|
||||
|
||||
export default Panel;
|
|
@ -0,0 +1,43 @@
|
|||
import {
|
||||
ColumnType,
|
||||
DefaultRecordType,
|
||||
ColumnsType,
|
||||
TableLayout,
|
||||
RenderExpandIcon,
|
||||
ExpandableType,
|
||||
RowClassName,
|
||||
TriggerEventHandler,
|
||||
ExpandedRowRender,
|
||||
} from '../interface';
|
||||
import { inject, InjectionKey, provide } from 'vue';
|
||||
|
||||
export interface BodyContextProps<RecordType = DefaultRecordType> {
|
||||
rowClassName: string | RowClassName<RecordType>;
|
||||
expandedRowClassName: RowClassName<RecordType>;
|
||||
|
||||
columns: ColumnsType<RecordType>;
|
||||
flattenColumns: readonly ColumnType<RecordType>[];
|
||||
|
||||
componentWidth: number;
|
||||
tableLayout: TableLayout;
|
||||
fixHeader: boolean;
|
||||
fixColumn: boolean;
|
||||
horizonScroll: boolean;
|
||||
|
||||
indentSize: number;
|
||||
expandableType: ExpandableType;
|
||||
expandRowByClick: boolean;
|
||||
expandedRowRender: ExpandedRowRender<RecordType>;
|
||||
expandIcon: RenderExpandIcon<RecordType>;
|
||||
onTriggerExpand: TriggerEventHandler<RecordType>;
|
||||
expandIconColumnIndex: number;
|
||||
}
|
||||
export const BodyContextKey: InjectionKey<BodyContextProps> = Symbol('BodyContextProps');
|
||||
|
||||
export const useProvideBody = (props: BodyContextProps) => {
|
||||
provide(BodyContextKey, props);
|
||||
};
|
||||
|
||||
export const useInjectBody = () => {
|
||||
return inject(BodyContextKey, {} as BodyContextProps);
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
import { inject, InjectionKey, provide } from 'vue';
|
||||
import { Key } from '../interface';
|
||||
|
||||
interface ResizeContextProps {
|
||||
onColumnResize: (columnKey: Key, width: number) => void;
|
||||
}
|
||||
|
||||
export const ResizeContextKey: InjectionKey<ResizeContextProps> = Symbol('ResizeContextProps');
|
||||
|
||||
export const useProvideResize = (props: ResizeContextProps) => {
|
||||
provide(ResizeContextKey, props);
|
||||
};
|
||||
|
||||
export const useInjectResize = () => {
|
||||
return inject(ResizeContextKey, { onColumnResize: () => {} });
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
import { inject, InjectionKey, provide } from 'vue';
|
||||
import { GetComponent } from '../interface';
|
||||
import { FixedInfo } from '../utils/fixUtil';
|
||||
|
||||
export interface TableContextProps {
|
||||
// Table context
|
||||
prefixCls: string;
|
||||
|
||||
getComponent: GetComponent;
|
||||
|
||||
scrollbarSize: number;
|
||||
|
||||
direction: 'ltr' | 'rtl';
|
||||
|
||||
fixedInfoList: readonly FixedInfo[];
|
||||
|
||||
isSticky: boolean;
|
||||
}
|
||||
|
||||
export const BodyContextKey: InjectionKey<TableContextProps> = Symbol('TableContextProps');
|
||||
|
||||
export const useProvideTable = (props: TableContextProps) => {
|
||||
provide(BodyContextKey, props);
|
||||
};
|
||||
|
||||
export const useInjectTable = () => {
|
||||
return inject(BodyContextKey, {} as TableContextProps);
|
||||
};
|
|
@ -0,0 +1,238 @@
|
|||
import { warning } from 'ant-design-vue/es/vc-util/warning';
|
||||
import { computed, ComputedRef, Ref, watchEffect } from 'vue';
|
||||
import type {
|
||||
ColumnsType,
|
||||
ColumnType,
|
||||
FixedType,
|
||||
Key,
|
||||
GetRowKey,
|
||||
TriggerEventHandler,
|
||||
RenderExpandIcon,
|
||||
ColumnGroupType,
|
||||
} from '../interface';
|
||||
import { INTERNAL_COL_DEFINE } from '../utils/legacyUtil';
|
||||
|
||||
export function convertChildrenToColumns<RecordType>(
|
||||
children: any[] = [],
|
||||
): ColumnsType<RecordType> {
|
||||
return children.map(({ key, props }) => {
|
||||
const { children: nodeChildren, ...restProps } = props;
|
||||
const column = {
|
||||
key,
|
||||
...restProps,
|
||||
};
|
||||
|
||||
if (nodeChildren) {
|
||||
column.children = convertChildrenToColumns(nodeChildren);
|
||||
}
|
||||
|
||||
return column;
|
||||
});
|
||||
}
|
||||
|
||||
function flatColumns<RecordType>(columns: ColumnsType<RecordType>): ColumnType<RecordType>[] {
|
||||
return columns.reduce((list, column) => {
|
||||
const { fixed } = column;
|
||||
|
||||
// Convert `fixed='true'` to `fixed='left'` instead
|
||||
const parsedFixed = fixed === true ? 'left' : fixed;
|
||||
|
||||
const subColumns = (column as ColumnGroupType<RecordType>).children;
|
||||
if (subColumns && subColumns.length > 0) {
|
||||
return [
|
||||
...list,
|
||||
...flatColumns(subColumns).map(subColum => ({
|
||||
fixed: parsedFixed,
|
||||
...subColum,
|
||||
})),
|
||||
];
|
||||
}
|
||||
return [
|
||||
...list,
|
||||
{
|
||||
...column,
|
||||
fixed: parsedFixed,
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
}
|
||||
|
||||
function warningFixed(flattenColumns: readonly { fixed?: FixedType }[]) {
|
||||
let allFixLeft = true;
|
||||
for (let i = 0; i < flattenColumns.length; i += 1) {
|
||||
const col = flattenColumns[i];
|
||||
if (allFixLeft && col.fixed !== 'left') {
|
||||
allFixLeft = false;
|
||||
} else if (!allFixLeft && col.fixed === 'left') {
|
||||
warning(false, `Index ${i - 1} of \`columns\` missing \`fixed='left'\` prop.`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let allFixRight = true;
|
||||
for (let i = flattenColumns.length - 1; i >= 0; i -= 1) {
|
||||
const col = flattenColumns[i];
|
||||
if (allFixRight && col.fixed !== 'right') {
|
||||
allFixRight = false;
|
||||
} else if (!allFixRight && col.fixed === 'right') {
|
||||
warning(false, `Index ${i + 1} of \`columns\` missing \`fixed='right'\` prop.`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function revertForRtl<RecordType>(columns: ColumnsType<RecordType>): ColumnsType<RecordType> {
|
||||
return columns.map(column => {
|
||||
const { fixed, ...restProps } = column;
|
||||
|
||||
// Convert `fixed='left'` to `fixed='right'` instead
|
||||
let parsedFixed = fixed;
|
||||
if (fixed === 'left') {
|
||||
parsedFixed = 'right';
|
||||
} else if (fixed === 'right') {
|
||||
parsedFixed = 'left';
|
||||
}
|
||||
return {
|
||||
fixed: parsedFixed,
|
||||
...restProps,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse `columns` & `children` into `columns`.
|
||||
*/
|
||||
function useColumns<RecordType>(
|
||||
{
|
||||
prefixCls,
|
||||
columns: baseColumns,
|
||||
// children,
|
||||
expandable,
|
||||
expandedKeys,
|
||||
getRowKey,
|
||||
onTriggerExpand,
|
||||
expandIcon,
|
||||
rowExpandable,
|
||||
expandIconColumnIndex,
|
||||
direction,
|
||||
expandRowByClick,
|
||||
columnWidth,
|
||||
fixed,
|
||||
}: {
|
||||
prefixCls?: Ref<string>;
|
||||
columns?: Ref<ColumnsType<RecordType>>;
|
||||
// children?: React.ReactNode;
|
||||
expandable: Ref<boolean>;
|
||||
expandedKeys: Ref<Set<Key>>;
|
||||
getRowKey: GetRowKey<RecordType>;
|
||||
onTriggerExpand: TriggerEventHandler<RecordType>;
|
||||
expandIcon?: Ref<RenderExpandIcon<RecordType>>;
|
||||
rowExpandable?: Ref<(record: RecordType) => boolean>;
|
||||
expandIconColumnIndex?: Ref<number>;
|
||||
direction?: Ref<'ltr' | 'rtl'>;
|
||||
expandRowByClick?: Ref<boolean>;
|
||||
columnWidth?: Ref<number | string>;
|
||||
fixed?: Ref<FixedType>;
|
||||
},
|
||||
transformColumns: (columns: ColumnsType<RecordType>) => ColumnsType<RecordType>,
|
||||
): [ComputedRef<ColumnsType<RecordType>>, ComputedRef<readonly ColumnType<RecordType>[]>] {
|
||||
// const baseColumns = React.useMemo<ColumnsType<RecordType>>(
|
||||
// () => columns || convertChildrenToColumns(children),
|
||||
// [columns, children],
|
||||
// );
|
||||
|
||||
// Add expand column
|
||||
const withExpandColumns = computed<ColumnsType<RecordType>>(() => {
|
||||
if (expandable.value) {
|
||||
const expandColIndex = expandIconColumnIndex.value || 0;
|
||||
const prevColumn = baseColumns[expandColIndex];
|
||||
|
||||
let fixedColumn: FixedType | null;
|
||||
if ((fixed.value === 'left' || fixed.value) && !expandIconColumnIndex.value) {
|
||||
fixedColumn = 'left';
|
||||
} else if (
|
||||
(fixed.value === 'right' || fixed.value) &&
|
||||
expandIconColumnIndex.value === baseColumns.value.length
|
||||
) {
|
||||
fixedColumn = 'right';
|
||||
} else {
|
||||
fixedColumn = prevColumn ? prevColumn.fixed : null;
|
||||
}
|
||||
const expandedKeysValue = expandedKeys.value;
|
||||
const rowExpandableValue = rowExpandable.value;
|
||||
const expandIconValue = expandIcon.value;
|
||||
const prefixClsValue = prefixCls.value;
|
||||
const expandRowByClickValue = expandRowByClick.value;
|
||||
const expandColumn = {
|
||||
[INTERNAL_COL_DEFINE]: {
|
||||
class: `${prefixCls.value}-expand-icon-col`,
|
||||
},
|
||||
title: '',
|
||||
fixed: fixedColumn,
|
||||
class: `${prefixCls.value}-row-expand-icon-cell`,
|
||||
width: columnWidth.value,
|
||||
render: (_, record, index) => {
|
||||
const rowKey = getRowKey(record, index);
|
||||
const expanded = expandedKeysValue.has(rowKey);
|
||||
const recordExpandable = rowExpandableValue ? rowExpandableValue(record) : true;
|
||||
|
||||
const icon = expandIconValue({
|
||||
prefixCls: prefixClsValue,
|
||||
expanded,
|
||||
expandable: recordExpandable,
|
||||
record,
|
||||
onExpand: onTriggerExpand,
|
||||
});
|
||||
|
||||
if (expandRowByClickValue) {
|
||||
return <span onClick={e => e.stopPropagation()}>{icon}</span>;
|
||||
}
|
||||
return icon;
|
||||
},
|
||||
};
|
||||
|
||||
// Insert expand column in the target position
|
||||
const cloneColumns = baseColumns.value.slice();
|
||||
if (expandColIndex >= 0) {
|
||||
cloneColumns.splice(expandColIndex, 0, expandColumn);
|
||||
}
|
||||
return cloneColumns;
|
||||
}
|
||||
return baseColumns.value;
|
||||
});
|
||||
|
||||
const mergedColumns = computed(() => {
|
||||
let finalColumns = withExpandColumns.value;
|
||||
if (transformColumns) {
|
||||
finalColumns = transformColumns(finalColumns);
|
||||
}
|
||||
|
||||
// Always provides at least one column for table display
|
||||
if (!finalColumns.length) {
|
||||
finalColumns = [
|
||||
{
|
||||
customRender: () => null,
|
||||
},
|
||||
];
|
||||
}
|
||||
return finalColumns;
|
||||
});
|
||||
|
||||
const flattenColumns = computed(() => {
|
||||
if (direction.value === 'rtl') {
|
||||
return revertForRtl(flatColumns(mergedColumns.value));
|
||||
}
|
||||
return flatColumns(mergedColumns.value);
|
||||
});
|
||||
// Only check out of production since it's waste for each render
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
watchEffect(() => {
|
||||
setTimeout(() => {
|
||||
warningFixed(flattenColumns.value);
|
||||
});
|
||||
});
|
||||
}
|
||||
return [mergedColumns, flattenColumns];
|
||||
}
|
||||
|
||||
export default useColumns;
|
|
@ -0,0 +1,85 @@
|
|||
import type { Ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import type { GetRowKey, Key } from '../interface';
|
||||
|
||||
// recursion (flat tree structure)
|
||||
function flatRecord<T>(
|
||||
record: T,
|
||||
indent: number,
|
||||
childrenColumnName: string,
|
||||
expandedKeys: Set<Key>,
|
||||
getRowKey: GetRowKey<T>,
|
||||
) {
|
||||
const arr = [];
|
||||
|
||||
arr.push({
|
||||
record,
|
||||
indent,
|
||||
});
|
||||
|
||||
const key = getRowKey(record);
|
||||
|
||||
const expanded = expandedKeys?.has(key);
|
||||
|
||||
if (record && Array.isArray(record[childrenColumnName]) && expanded) {
|
||||
// expanded state, flat record
|
||||
for (let i = 0; i < record[childrenColumnName].length; i += 1) {
|
||||
const tempArr = flatRecord(
|
||||
record[childrenColumnName][i],
|
||||
indent + 1,
|
||||
childrenColumnName,
|
||||
expandedKeys,
|
||||
getRowKey,
|
||||
);
|
||||
|
||||
arr.push(...tempArr);
|
||||
}
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* flat tree data on expanded state
|
||||
*
|
||||
* @export
|
||||
* @template T
|
||||
* @param {*} data : table data
|
||||
* @param {string} childrenColumnName : 指定树形结构的列名
|
||||
* @param {Set<Key>} expandedKeys : 展开的行对应的keys
|
||||
* @param {GetRowKey<T>} getRowKey : 获取当前rowKey的方法
|
||||
* @returns flattened data
|
||||
*/
|
||||
export default function useFlattenRecords<T>(
|
||||
dataRef: Ref<[]>,
|
||||
childrenColumnNameRef: Ref<string>,
|
||||
expandedKeysRef: Ref<Set<Key>>,
|
||||
getRowKey: GetRowKey<T>,
|
||||
) {
|
||||
const arr: Ref<{ record: T; indent: number }[]> = computed(() => {
|
||||
const childrenColumnName = childrenColumnNameRef.value;
|
||||
const expandedKeys = expandedKeysRef.value;
|
||||
const data = dataRef.value;
|
||||
if (expandedKeys?.size) {
|
||||
const temp: { record: T; indent: number }[] = [];
|
||||
|
||||
// collect flattened record
|
||||
for (let i = 0; i < data?.length; i += 1) {
|
||||
const record = data[i];
|
||||
|
||||
temp.push(...flatRecord<T>(record, 0, childrenColumnName, expandedKeys, getRowKey));
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
return data?.map(item => {
|
||||
return {
|
||||
record: item,
|
||||
indent: 0,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return arr;
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
import type { Ref, UnwrapRef } from 'vue';
|
||||
import { getCurrentInstance, onBeforeUnmount, ref } from 'vue';
|
||||
|
||||
export type Updater<State> = (prev: State) => State;
|
||||
|
||||
/**
|
||||
* Execute code before next frame but async
|
||||
*/
|
||||
export function useLayoutState<State>(
|
||||
defaultState: State,
|
||||
): [Ref<State>, (updater: Updater<State>) => void] {
|
||||
const stateRef = ref<State>(defaultState);
|
||||
// const [, forceUpdate] = useState({});
|
||||
|
||||
const lastPromiseRef = ref<Promise<void>>(null);
|
||||
const updateBatchRef = ref<Updater<State>[]>([]);
|
||||
const instance = getCurrentInstance();
|
||||
function setFrameState(updater: Updater<State>) {
|
||||
updateBatchRef.value.push(updater);
|
||||
|
||||
const promise = Promise.resolve();
|
||||
lastPromiseRef.value = promise;
|
||||
|
||||
promise.then(() => {
|
||||
if (lastPromiseRef.value === promise) {
|
||||
const prevBatch = updateBatchRef.value;
|
||||
const prevState = stateRef.value;
|
||||
updateBatchRef.value = [];
|
||||
|
||||
prevBatch.forEach(batchUpdater => {
|
||||
stateRef.value = batchUpdater(stateRef.value as State) as UnwrapRef<State>;
|
||||
});
|
||||
|
||||
lastPromiseRef.value = null;
|
||||
|
||||
if (prevState !== stateRef.value) {
|
||||
instance.update();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
lastPromiseRef.value = null;
|
||||
});
|
||||
|
||||
return [stateRef as Ref<State>, setFrameState];
|
||||
}
|
||||
|
||||
/** Lock frame, when frame pass reset the lock. */
|
||||
export function useTimeoutLock<State>(
|
||||
defaultState?: State,
|
||||
): [(state: UnwrapRef<State>) => void, () => UnwrapRef<State> | null] {
|
||||
const frameRef = ref<State | null>(defaultState || null);
|
||||
const timeoutRef = ref<number>();
|
||||
|
||||
function cleanUp() {
|
||||
window.clearTimeout(timeoutRef.value);
|
||||
}
|
||||
|
||||
function setState(newState: UnwrapRef<State>) {
|
||||
frameRef.value = newState;
|
||||
cleanUp();
|
||||
|
||||
timeoutRef.value = window.setTimeout(() => {
|
||||
frameRef.value = null;
|
||||
timeoutRef.value = undefined;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function getState() {
|
||||
return frameRef.value;
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
cleanUp();
|
||||
});
|
||||
|
||||
return [setState, getState];
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import canUseDom from '../../_util/canUseDom';
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import type { TableSticky } from '../interface';
|
||||
|
||||
// fix ssr render
|
||||
const defaultContainer = canUseDom() ? window : null;
|
||||
|
||||
/** Sticky header hooks */
|
||||
export default function useSticky(
|
||||
stickyRef: Ref<boolean | TableSticky>,
|
||||
prefixClsRef: Ref<string>,
|
||||
): ComputedRef<{
|
||||
isSticky: boolean;
|
||||
offsetHeader: number;
|
||||
offsetSummary: number;
|
||||
offsetScroll: number;
|
||||
stickyClassName: string;
|
||||
container: Window | HTMLElement;
|
||||
}> {
|
||||
return computed(() => {
|
||||
const {
|
||||
offsetHeader = 0,
|
||||
offsetSummary = 0,
|
||||
offsetScroll = 0,
|
||||
getContainer = () => defaultContainer,
|
||||
} = typeof stickyRef.value === 'object' ? stickyRef.value : {};
|
||||
|
||||
const container = getContainer() || defaultContainer;
|
||||
const isSticky = !!stickyRef.value;
|
||||
return {
|
||||
isSticky,
|
||||
stickyClassName: isSticky ? `${prefixClsRef.value}-sticky-holder` : '',
|
||||
offsetHeader,
|
||||
offsetSummary,
|
||||
offsetScroll,
|
||||
container,
|
||||
};
|
||||
});
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import type { ComputedRef, Ref } from 'vue';
|
||||
|
||||
import { computed } from 'vue';
|
||||
import type { StickyOffsets } from '../interface';
|
||||
|
||||
/**
|
||||
* Get sticky column offset width
|
||||
*/
|
||||
function useStickyOffsets(
|
||||
colWidthsRef: Ref<number[]>,
|
||||
columnCountRef: Ref<number>,
|
||||
directionRef: Ref<'ltr' | 'rtl'>,
|
||||
) {
|
||||
const stickyOffsets: ComputedRef<StickyOffsets> = computed(() => {
|
||||
const leftOffsets: number[] = [];
|
||||
const rightOffsets: number[] = [];
|
||||
let left = 0;
|
||||
let right = 0;
|
||||
|
||||
const colWidths = colWidthsRef.value;
|
||||
const columnCount = columnCountRef.value;
|
||||
const direction = directionRef.value;
|
||||
|
||||
for (let start = 0; start < columnCount; start += 1) {
|
||||
if (direction === 'rtl') {
|
||||
// Left offset
|
||||
rightOffsets[start] = right;
|
||||
right += colWidths[start] || 0;
|
||||
|
||||
// Right offset
|
||||
const end = columnCount - start - 1;
|
||||
leftOffsets[end] = left;
|
||||
left += colWidths[end] || 0;
|
||||
} else {
|
||||
// Left offset
|
||||
leftOffsets[start] = left;
|
||||
left += colWidths[start] || 0;
|
||||
|
||||
// Right offset
|
||||
const end = columnCount - start - 1;
|
||||
rightOffsets[end] = right;
|
||||
right += colWidths[end] || 0;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
left: leftOffsets,
|
||||
right: rightOffsets,
|
||||
};
|
||||
});
|
||||
|
||||
return stickyOffsets;
|
||||
}
|
||||
|
||||
export default useStickyOffsets;
|
|
@ -0,0 +1,10 @@
|
|||
// base rc-table@7.17.2
|
||||
import Table from './Table';
|
||||
import { FooterComponents as Summary } from './Footer';
|
||||
import Column from './sugar/Column';
|
||||
import ColumnGroup from './sugar/ColumnGroup';
|
||||
import { INTERNAL_COL_DEFINE } from './utils/legacyUtil';
|
||||
|
||||
export { Summary, Column, ColumnGroup, INTERNAL_COL_DEFINE };
|
||||
|
||||
export default Table;
|
|
@ -0,0 +1,226 @@
|
|||
/**
|
||||
* ColumnType which applied in antd: https://ant.design/components/table-cn/#Column
|
||||
* - defaultSortOrder
|
||||
* - filterDropdown
|
||||
* - filterDropdownVisible
|
||||
* - filtered
|
||||
* - filteredValue
|
||||
* - filterIcon
|
||||
* - filterMultiple
|
||||
* - filters
|
||||
* - sorter
|
||||
* - sortOrder
|
||||
* - sortDirections
|
||||
* - onFilter
|
||||
* - onFilterDropdownVisibleChange
|
||||
*/
|
||||
|
||||
import type { CSSProperties, DefineComponent, FunctionalComponent, HTMLAttributes, Ref } from 'vue';
|
||||
|
||||
export type Key = number | string;
|
||||
|
||||
export type FixedType = 'left' | 'right' | boolean;
|
||||
|
||||
export type DefaultRecordType = Record<string, any>;
|
||||
|
||||
export type TableLayout = 'auto' | 'fixed';
|
||||
|
||||
// ==================== Row =====================
|
||||
export type RowClassName<RecordType> = (
|
||||
record: RecordType,
|
||||
index: number,
|
||||
indent: number,
|
||||
) => string;
|
||||
|
||||
// =================== Column ===================
|
||||
export interface CellType<RecordType = DefaultRecordType> {
|
||||
key?: Key;
|
||||
class?: string;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
children?: any;
|
||||
column?: ColumnsType<RecordType>[number];
|
||||
colSpan?: number;
|
||||
rowSpan?: number;
|
||||
|
||||
/** Only used for table header */
|
||||
hasSubColumns?: boolean;
|
||||
colStart?: number;
|
||||
colEnd?: number;
|
||||
}
|
||||
|
||||
export interface RenderedCell<RecordType> {
|
||||
props?: CellType<RecordType>;
|
||||
children?: any;
|
||||
}
|
||||
|
||||
export type DataIndex = string | number | readonly (string | number)[];
|
||||
|
||||
export type CellEllipsisType = { showTitle?: boolean } | boolean;
|
||||
|
||||
interface ColumnSharedType<RecordType> {
|
||||
title?: any;
|
||||
key?: Key;
|
||||
class?: string;
|
||||
className?: string;
|
||||
fixed?: FixedType;
|
||||
onHeaderCell?: GetComponentProps<ColumnsType<RecordType>[number]>;
|
||||
ellipsis?: CellEllipsisType;
|
||||
align?: AlignType;
|
||||
}
|
||||
|
||||
export interface ColumnGroupType<RecordType> extends ColumnSharedType<RecordType> {
|
||||
children: ColumnsType<RecordType>;
|
||||
}
|
||||
|
||||
export type AlignType = 'left' | 'center' | 'right';
|
||||
|
||||
export interface ColumnType<RecordType> extends ColumnSharedType<RecordType> {
|
||||
colSpan?: number;
|
||||
dataIndex?: DataIndex;
|
||||
customRender?: (opt: {
|
||||
value: any;
|
||||
text: any; // 兼容 V2
|
||||
record: RecordType;
|
||||
index: number;
|
||||
column: ColumnType<RecordType>;
|
||||
}) => any | RenderedCell<RecordType>;
|
||||
rowSpan?: number;
|
||||
width?: number | string;
|
||||
onCell?: GetComponentProps<RecordType>;
|
||||
/** @deprecated Please use `onCell` instead */
|
||||
onCellClick?: (record: RecordType, e: MouseEvent) => void;
|
||||
}
|
||||
|
||||
export type ColumnsType<RecordType = unknown> = readonly (
|
||||
| ColumnGroupType<RecordType>
|
||||
| ColumnType<RecordType>
|
||||
)[];
|
||||
|
||||
export type GetRowKey<RecordType> = (record: RecordType, index?: number) => Key;
|
||||
|
||||
// ================= Fix Column =================
|
||||
export interface StickyOffsets {
|
||||
left: readonly number[];
|
||||
right: readonly number[];
|
||||
isSticky?: boolean;
|
||||
}
|
||||
|
||||
// ================= Customized =================
|
||||
export type GetComponentProps<DataType> = (
|
||||
data: DataType,
|
||||
index?: number,
|
||||
) => Omit<HTMLAttributes, 'style'> & { style?: CSSProperties };
|
||||
|
||||
type Component<P> = DefineComponent<P> | FunctionalComponent<P> | string;
|
||||
|
||||
export type CustomizeComponent = Component<any>;
|
||||
|
||||
export type CustomizeScrollBody<RecordType> = (
|
||||
data: readonly RecordType[],
|
||||
info: {
|
||||
scrollbarSize: number;
|
||||
ref: Ref<{ scrollLeft: number }>;
|
||||
onScroll: (info: { currentTarget?: HTMLElement; scrollLeft?: number }) => void;
|
||||
},
|
||||
) => any;
|
||||
|
||||
export interface TableComponents<RecordType> {
|
||||
table?: CustomizeComponent;
|
||||
header?: {
|
||||
wrapper?: CustomizeComponent;
|
||||
row?: CustomizeComponent;
|
||||
cell?: CustomizeComponent;
|
||||
};
|
||||
body?:
|
||||
| CustomizeScrollBody<RecordType>
|
||||
| {
|
||||
wrapper?: CustomizeComponent;
|
||||
row?: CustomizeComponent;
|
||||
cell?: CustomizeComponent;
|
||||
};
|
||||
}
|
||||
|
||||
export type GetComponent = (
|
||||
path: readonly string[],
|
||||
defaultComponent?: CustomizeComponent,
|
||||
) => CustomizeComponent;
|
||||
|
||||
// =================== Expand ===================
|
||||
export type ExpandableType = false | 'row' | 'nest';
|
||||
|
||||
export interface LegacyExpandableProps<RecordType> {
|
||||
/** @deprecated Use `expandable.expandedRowKeys` instead */
|
||||
expandedRowKeys?: Key[];
|
||||
/** @deprecated Use `expandable.defaultExpandedRowKeys` instead */
|
||||
defaultExpandedRowKeys?: Key[];
|
||||
/** @deprecated Use `expandable.expandedRowRender` instead */
|
||||
expandedRowRender?: ExpandedRowRender<RecordType>;
|
||||
/** @deprecated Use `expandable.expandRowByClick` instead */
|
||||
expandRowByClick?: boolean;
|
||||
/** @deprecated Use `expandable.expandIcon` instead */
|
||||
expandIcon?: RenderExpandIcon<RecordType>;
|
||||
/** @deprecated Use `expandable.onExpand` instead */
|
||||
onExpand?: (expanded: boolean, record: RecordType) => void;
|
||||
/** @deprecated Use `expandable.onExpandedRowsChange` instead */
|
||||
onExpandedRowsChange?: (expandedKeys: Key[]) => void;
|
||||
/** @deprecated Use `expandable.defaultExpandAllRows` instead */
|
||||
defaultExpandAllRows?: boolean;
|
||||
/** @deprecated Use `expandable.indentSize` instead */
|
||||
indentSize?: number;
|
||||
/** @deprecated Use `expandable.expandIconColumnIndex` instead */
|
||||
expandIconColumnIndex?: number;
|
||||
/** @deprecated Use `expandable.expandedRowClassName` instead */
|
||||
expandedRowClassName?: RowClassName<RecordType>;
|
||||
/** @deprecated Use `expandable.childrenColumnName` instead */
|
||||
childrenColumnName?: string;
|
||||
}
|
||||
|
||||
export type ExpandedRowRender<ValueType> = (
|
||||
record: ValueType,
|
||||
index: number,
|
||||
indent: number,
|
||||
expanded: boolean,
|
||||
) => any;
|
||||
|
||||
export interface RenderExpandIconProps<RecordType> {
|
||||
prefixCls: string;
|
||||
expanded: boolean;
|
||||
record: RecordType;
|
||||
expandable: boolean;
|
||||
onExpand: TriggerEventHandler<RecordType>;
|
||||
}
|
||||
|
||||
export type RenderExpandIcon<RecordType> = (props: RenderExpandIconProps<RecordType>) => any;
|
||||
|
||||
export interface ExpandableConfig<RecordType> {
|
||||
expandedRowKeys?: readonly Key[];
|
||||
defaultExpandedRowKeys?: readonly Key[];
|
||||
expandedRowRender?: ExpandedRowRender<RecordType>;
|
||||
expandRowByClick?: boolean;
|
||||
expandIcon?: RenderExpandIcon<RecordType>;
|
||||
onExpand?: (expanded: boolean, record: RecordType) => void;
|
||||
onExpandedRowsChange?: (expandedKeys: readonly Key[]) => void;
|
||||
defaultExpandAllRows?: boolean;
|
||||
indentSize?: number;
|
||||
expandIconColumnIndex?: number;
|
||||
expandedRowClassName?: RowClassName<RecordType>;
|
||||
childrenColumnName?: string;
|
||||
rowExpandable?: (record: RecordType) => boolean;
|
||||
columnWidth?: number | string;
|
||||
fixed?: FixedType;
|
||||
}
|
||||
|
||||
// =================== Render ===================
|
||||
export type PanelRender<RecordType> = (data: readonly RecordType[]) => any;
|
||||
|
||||
// =================== Events ===================
|
||||
export type TriggerEventHandler<RecordType> = (record: RecordType, event: MouseEvent) => void;
|
||||
|
||||
// =================== Sticky ===================
|
||||
export interface TableSticky {
|
||||
offsetHeader?: number;
|
||||
offsetSummary?: number;
|
||||
offsetScroll?: number;
|
||||
getContainer?: () => Window | HTMLElement;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import { FunctionalComponent } from 'vue';
|
||||
import { ColumnType } from '../interface';
|
||||
|
||||
export type ColumnProps<RecordType> = ColumnType<RecordType>;
|
||||
|
||||
/* istanbul ignore next */
|
||||
/**
|
||||
* This is a syntactic sugar for `columns` prop.
|
||||
* So HOC will not work on this.
|
||||
*/
|
||||
const Column: { <T>(arg: T): FunctionalComponent<ColumnProps<T>> } = () => null;
|
||||
|
||||
export default Column;
|
|
@ -0,0 +1,13 @@
|
|||
import { ColumnType } from '../interface';
|
||||
import { FunctionalComponent } from 'vue';
|
||||
/* istanbul ignore next */
|
||||
/**
|
||||
* This is a syntactic sugar for `columns` prop.
|
||||
* So HOC will not work on this.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export type ColumnGroupProps<RecordType> = ColumnType<RecordType>;
|
||||
|
||||
const ColumnGroup: { <T>(arg: T): FunctionalComponent<ColumnGroupProps<T>> } = () => null;
|
||||
|
||||
export default ColumnGroup;
|
|
@ -0,0 +1,51 @@
|
|||
import { RenderExpandIconProps, Key, GetRowKey } from '../interface';
|
||||
|
||||
export function renderExpandIcon<RecordType>({
|
||||
prefixCls,
|
||||
record,
|
||||
onExpand,
|
||||
expanded,
|
||||
expandable,
|
||||
}: RenderExpandIconProps<RecordType>) {
|
||||
const expandClassName = `${prefixCls}-row-expand-icon`;
|
||||
|
||||
if (!expandable) {
|
||||
return <span class={[expandClassName, `${prefixCls}-row-spaced`]} />;
|
||||
}
|
||||
|
||||
const onClick = event => {
|
||||
onExpand(record, event);
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
class={{
|
||||
[expandClassName]: true,
|
||||
[`${prefixCls}-row-expanded`]: expanded,
|
||||
[`${prefixCls}-row-collapsed`]: !expanded,
|
||||
}}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function findAllChildrenKeys<RecordType>(
|
||||
data: readonly RecordType[],
|
||||
getRowKey: GetRowKey<RecordType>,
|
||||
childrenColumnName: string,
|
||||
): Key[] {
|
||||
const keys: Key[] = [];
|
||||
|
||||
function dig(list: readonly RecordType[]) {
|
||||
(list || []).forEach((item, index) => {
|
||||
keys.push(getRowKey(item, index));
|
||||
|
||||
dig((item as any)[childrenColumnName]);
|
||||
});
|
||||
}
|
||||
|
||||
dig(data);
|
||||
|
||||
return keys;
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
import type { StickyOffsets, FixedType } from '../interface';
|
||||
|
||||
export interface FixedInfo {
|
||||
fixLeft: number | false;
|
||||
fixRight: number | false;
|
||||
lastFixLeft: boolean;
|
||||
firstFixRight: boolean;
|
||||
|
||||
// For Rtl Direction
|
||||
lastFixRight: boolean;
|
||||
firstFixLeft: boolean;
|
||||
|
||||
isSticky: boolean;
|
||||
}
|
||||
|
||||
export function getCellFixedInfo(
|
||||
colStart: number,
|
||||
colEnd: number,
|
||||
columns: readonly { fixed?: FixedType }[],
|
||||
stickyOffsets: StickyOffsets,
|
||||
direction: 'ltr' | 'rtl',
|
||||
): FixedInfo {
|
||||
const startColumn = columns[colStart] || {};
|
||||
const endColumn = columns[colEnd] || {};
|
||||
|
||||
let fixLeft: number;
|
||||
let fixRight: number;
|
||||
|
||||
if (startColumn.fixed === 'left') {
|
||||
fixLeft = stickyOffsets.left[colStart];
|
||||
} else if (endColumn.fixed === 'right') {
|
||||
fixRight = stickyOffsets.right[colEnd];
|
||||
}
|
||||
|
||||
let lastFixLeft = false;
|
||||
let firstFixRight = false;
|
||||
|
||||
let lastFixRight = false;
|
||||
let firstFixLeft = false;
|
||||
|
||||
const nextColumn = columns[colEnd + 1];
|
||||
const prevColumn = columns[colStart - 1];
|
||||
|
||||
if (direction === 'rtl') {
|
||||
if (fixLeft !== undefined) {
|
||||
const prevFixLeft = prevColumn && prevColumn.fixed === 'left';
|
||||
firstFixLeft = !prevFixLeft;
|
||||
} else if (fixRight !== undefined) {
|
||||
const nextFixRight = nextColumn && nextColumn.fixed === 'right';
|
||||
lastFixRight = !nextFixRight;
|
||||
}
|
||||
} else if (fixLeft !== undefined) {
|
||||
const nextFixLeft = nextColumn && nextColumn.fixed === 'left';
|
||||
lastFixLeft = !nextFixLeft;
|
||||
} else if (fixRight !== undefined) {
|
||||
const prevFixRight = prevColumn && prevColumn.fixed === 'right';
|
||||
firstFixRight = !prevFixRight;
|
||||
}
|
||||
|
||||
return {
|
||||
fixLeft,
|
||||
fixRight,
|
||||
lastFixLeft,
|
||||
firstFixRight,
|
||||
lastFixRight,
|
||||
firstFixLeft,
|
||||
isSticky: stickyOffsets.isSticky,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import { warning } from '../../vc-util/warning';
|
||||
import type { ExpandableConfig, LegacyExpandableProps } from '../interface';
|
||||
|
||||
export const INTERNAL_COL_DEFINE = 'RC_TABLE_INTERNAL_COL_DEFINE';
|
||||
|
||||
export function getExpandableProps<RecordType>(
|
||||
props: LegacyExpandableProps<RecordType> & {
|
||||
expandable?: ExpandableConfig<RecordType>;
|
||||
},
|
||||
): ExpandableConfig<RecordType> {
|
||||
const { expandable, ...legacyExpandableConfig } = props;
|
||||
|
||||
if ('expandable' in props) {
|
||||
return {
|
||||
...legacyExpandableConfig,
|
||||
...expandable,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
[
|
||||
'indentSize',
|
||||
'expandedRowKeys',
|
||||
'defaultExpandedRowKeys',
|
||||
'defaultExpandAllRows',
|
||||
'expandedRowRender',
|
||||
'expandRowByClick',
|
||||
'expandIcon',
|
||||
'onExpand',
|
||||
'onExpandedRowsChange',
|
||||
'expandedRowClassName',
|
||||
'expandIconColumnIndex',
|
||||
].some(prop => prop in props)
|
||||
) {
|
||||
warning(false, 'expanded related props have been moved into `expandable`.');
|
||||
}
|
||||
|
||||
return legacyExpandableConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only data- and aria- key/value pairs
|
||||
* @param {object} props
|
||||
*/
|
||||
export function getDataAndAriaProps(props: object) {
|
||||
/* eslint-disable no-param-reassign */
|
||||
return Object.keys(props).reduce((memo, key) => {
|
||||
if (key.substr(0, 5) === 'data-' || key.substr(0, 5) === 'aria-') {
|
||||
memo[key] = props[key];
|
||||
}
|
||||
return memo;
|
||||
}, {});
|
||||
/* eslint-enable */
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
import { Key, DataIndex } from '../interface';
|
||||
|
||||
const INTERNAL_KEY_PREFIX = 'RC_TABLE_KEY';
|
||||
|
||||
function toArray<T>(arr: T | readonly T[]): T[] {
|
||||
if (arr === undefined || arr === null) {
|
||||
return [];
|
||||
}
|
||||
return (Array.isArray(arr) ? arr : [arr]) as T[];
|
||||
}
|
||||
|
||||
export function getPathValue<ValueType, ObjectType extends object>(
|
||||
record: ObjectType,
|
||||
path: DataIndex,
|
||||
): ValueType {
|
||||
// Skip if path is empty
|
||||
if (!path && typeof path !== 'number') {
|
||||
return record as unknown as ValueType;
|
||||
}
|
||||
|
||||
const pathList = toArray(path);
|
||||
|
||||
let current: ValueType | ObjectType = record;
|
||||
|
||||
for (let i = 0; i < pathList.length; i += 1) {
|
||||
if (!current) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const prop = pathList[i];
|
||||
current = current[prop];
|
||||
}
|
||||
|
||||
return current as ValueType;
|
||||
}
|
||||
|
||||
interface GetColumnKeyColumn {
|
||||
key?: Key;
|
||||
dataIndex?: DataIndex;
|
||||
}
|
||||
|
||||
export function getColumnsKey(columns: readonly GetColumnKeyColumn[]) {
|
||||
const columnKeys: Key[] = [];
|
||||
const keys: Record<Key, boolean> = {};
|
||||
|
||||
columns.forEach(column => {
|
||||
const { key, dataIndex } = column || {};
|
||||
|
||||
let mergedKey = key || toArray(dataIndex).join('-') || INTERNAL_KEY_PREFIX;
|
||||
while (keys[mergedKey]) {
|
||||
mergedKey = `${mergedKey}_next`;
|
||||
}
|
||||
keys[mergedKey] = true;
|
||||
|
||||
columnKeys.push(mergedKey);
|
||||
});
|
||||
|
||||
return columnKeys;
|
||||
}
|
||||
|
||||
export function mergeObject<ReturnObject extends object>(
|
||||
...objects: Partial<ReturnObject>[]
|
||||
): ReturnObject {
|
||||
const merged: Partial<ReturnObject> = {};
|
||||
|
||||
/* eslint-disable no-param-reassign */
|
||||
function fillProps(obj: object, clone: object) {
|
||||
if (clone) {
|
||||
Object.keys(clone).forEach(key => {
|
||||
const value = clone[key];
|
||||
if (value && typeof value === 'object') {
|
||||
obj[key] = obj[key] || {};
|
||||
fillProps(obj[key], value);
|
||||
} else {
|
||||
obj[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
objects.forEach(clone => {
|
||||
fillProps(merged, clone);
|
||||
});
|
||||
|
||||
return merged as ReturnObject;
|
||||
}
|
||||
|
||||
export function validateValue<T>(val: T) {
|
||||
return val !== null && val !== undefined;
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
"ant-design-vue": ["components/index.ts"],
|
||||
"ant-design-vue/es/*": ["components/*"]
|
||||
},
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"strictNullChecks": false,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
|
@ -14,7 +15,6 @@
|
|||
"noUnusedLocals": true,
|
||||
"noImplicitAny": false,
|
||||
"target": "es6",
|
||||
"lib": ["dom", "es2017"],
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"importsNotUsedAsValues": "preserve"
|
||||
|
|
Loading…
Reference in New Issue