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
	
	 tangjinzhou
						tangjinzhou