ResizableTable.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /* eslint-disable max-len */
  2. /* eslint-disable @typescript-eslint/no-shadow */
  3. /* eslint-disable react-hooks/exhaustive-deps */
  4. /* eslint-disable react/destructuring-assignment */
  5. /* eslint-disable max-lines-per-function */
  6. import React, { useState, useEffect, useMemo } from 'react';
  7. import { merge, get, find, noop } from 'lodash';
  8. import { addClass, removeClass } from '@douyinfe/semi-foundation/utils/classnames';
  9. import { strings, numbers } from '@douyinfe/semi-foundation/table/constants';
  10. import { mergeColumns, assignColumnKeys, findColumn, withResizeWidth } from '@douyinfe/semi-foundation/table/utils';
  11. import Table from './Table';
  12. import { cloneDeep } from './utils';
  13. import getColumns from './getColumns';
  14. import ResizableHeaderCell from './ResizableHeaderCell';
  15. import { TableProps, ColumnProps } from './interface';
  16. const ResizableTable = (props: TableProps = {}, ref: React.MutableRefObject<Table<any>> | ((instance: Table<any>) => void)) => {
  17. const { components: propComponents, columns: propColumns, resizable, ...restProps } = props;
  18. const childrenColumnName = 'children';
  19. const onResize = get(resizable, 'onResize', noop);
  20. const onResizeStart = get(resizable, 'onResizeStart', noop);
  21. const onResizeStop = get(resizable, 'onResizeStop', noop);
  22. /**
  23. * 此处关于 columns 有三个存储
  24. *
  25. * 1. rawColumns 是根据 props.columns 或者 props.children 解析出来的原始 columns
  26. * 2. newColumns 是 rawColumns 的深拷贝,同时根据 props.expandedRowRender、props.hideExpandedColumn 和 props.rowSelection
  27. * 这三个参数加入了【选择列】以及【展开列】
  28. * 3. columns 是当前组件中存储的 state,一般情况下与 newColumns 相等,但是会保存列当前伸缩的宽度
  29. */
  30. /**
  31. * There are three stores for columns here
  32. *
  33. * 1. rawColumns are the original columns parsed according to props.columns or props.children
  34. * 2. newColumns is a deep copy of rawColumns, based on props.expandedRowRender, props.hideExpandedColumn and props.rowSelection
  35. * These three parameters have been added [Select Column] and [Expand Column]
  36. * 3. columns is the state stored in the current component, which is generally equal to newColumns, but it will save the current stretched width of the column
  37. */
  38. const parsedColumns = Array.isArray(propColumns) && propColumns.length ? propColumns : getColumns(props.children);
  39. const rawColumns = assignColumnKeys(cloneDeep(parsedColumns), childrenColumnName);
  40. const newColumns = assignColumnKeys(cloneDeep(parsedColumns), childrenColumnName);
  41. if (
  42. typeof props.expandedRowRender === 'function' &&
  43. !props.hideExpandedColumn &&
  44. !find(rawColumns, item => item.key === strings.DEFAULT_KEY_COLUMN_EXPAND)
  45. ) {
  46. newColumns.unshift({ key: strings.DEFAULT_KEY_COLUMN_EXPAND, width: numbers.DEFAULT_WIDTH_COLUMN_EXPAND });
  47. }
  48. if (props.rowSelection && !find(rawColumns, item => item.key === strings.DEFAULT_KEY_COLUMN_SELECTION)) {
  49. newColumns.unshift({
  50. width: get(props, 'rowSelection.width', numbers.DEFAULT_WIDTH_COLUMN_SELECTION),
  51. key: strings.DEFAULT_KEY_COLUMN_SELECTION,
  52. });
  53. }
  54. const [columns, setColumns] = useState(newColumns);
  55. useEffect(() => {
  56. // If there is a resize value, the width does not use the default value fix#1072
  57. const _newColumns = withResizeWidth(columns, newColumns);
  58. setColumns(mergeColumns(columns, _newColumns));
  59. }, [propColumns, props.expandedRowRender, props.hideExpandedColumn, props.rowSelection]);
  60. const components = useMemo(() => merge(
  61. {
  62. header: {
  63. cell: ResizableHeaderCell,
  64. },
  65. },
  66. propComponents
  67. ), [propComponents]);
  68. const handlerClassName = get(resizable, 'handlerClassName', 'resizing');
  69. const handleResize = (column: ColumnProps) => (e: React.MouseEvent, { size }: { size: { width: number } }) => {
  70. const nextColumns = cloneDeep(columns);
  71. const curColumn: ColumnProps = findColumn(nextColumns, column, childrenColumnName);
  72. let nextColumn = {
  73. ...curColumn,
  74. width: size.width,
  75. };
  76. const customProps = onResize(nextColumn) || {};
  77. nextColumn = {
  78. ...nextColumn,
  79. ...customProps,
  80. };
  81. Object.assign(curColumn, nextColumn);
  82. setColumns(nextColumns);
  83. };
  84. const handleResizeStart = (column: ColumnProps<any>) => (e: React.MouseEvent) => {
  85. const nextColumns = cloneDeep(columns);
  86. const curColumn: ColumnProps = findColumn(nextColumns, column, childrenColumnName);
  87. let nextColumn: ColumnProps = {
  88. ...curColumn,
  89. className: addClass(curColumn.className, handlerClassName),
  90. };
  91. const customProps = onResizeStart(nextColumn) || {};
  92. nextColumn = {
  93. ...nextColumn,
  94. ...customProps,
  95. };
  96. Object.assign(curColumn, nextColumn);
  97. setColumns(nextColumns);
  98. };
  99. const handleResizeStop = (column: ColumnProps) => (e: React.MouseEvent) => {
  100. const nextColumns = cloneDeep(columns);
  101. const curColumn: ColumnProps = findColumn(nextColumns, column, childrenColumnName);
  102. let nextColumn = {
  103. ...curColumn,
  104. className: removeClass(curColumn.className, handlerClassName),
  105. };
  106. const customProps = onResizeStop(nextColumn) || {};
  107. nextColumn = {
  108. ...nextColumn,
  109. ...customProps,
  110. };
  111. Object.assign(curColumn, nextColumn);
  112. setColumns(nextColumns);
  113. };
  114. const resizableRender = (col: ColumnProps, index: number, level = 0) => ({
  115. ...col,
  116. onHeaderCell: (column: ColumnProps) => ({
  117. width: column.width,
  118. onResize: handleResize(column),
  119. onResizeStart: handleResizeStart(column),
  120. onResizeStop: handleResizeStop(column),
  121. }),
  122. });
  123. const assignResizableRender = (columns: ColumnProps[] = [], level = 0) => (Array.isArray(columns) && columns.length ?
  124. columns.map((col, index) => {
  125. Object.assign(col, resizableRender(col, index, level));
  126. const children = col[childrenColumnName];
  127. if (Array.isArray(children) && children.length) {
  128. col[childrenColumnName] = assignResizableRender(children, level + 1);
  129. }
  130. return col;
  131. }) :
  132. []);
  133. const finalColumns = useMemo(() => assignResizableRender(columns), [columns]);
  134. return <Table {...restProps} columns={finalColumns} components={components} ref={ref} />;
  135. };
  136. export default React.forwardRef<Table<any>, TableProps>(ResizableTable);