utils.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. /* eslint-disable max-len */
  2. /* eslint-disable no-param-reassign */
  3. /* eslint-disable eqeqeq */
  4. import {
  5. cloneDeepWith,
  6. isEqualWith,
  7. get,
  8. filter,
  9. find,
  10. map,
  11. clone as lodashClone,
  12. each,
  13. findIndex,
  14. some,
  15. includes,
  16. toString
  17. } from 'lodash-es';
  18. import { strings, numbers } from './constants';
  19. import isNullOrUndefined from '../utils/isNullOrUndefined';
  20. export function cloneDeep(value: any, customizer?: (v: any) => any) {
  21. return cloneDeepWith(value, v => {
  22. if (typeof v === 'function') {
  23. return v;
  24. } else if (typeof customizer === 'function') {
  25. return customizer(v);
  26. }
  27. return undefined;
  28. });
  29. }
  30. export function equalWith(value: any, other: any, customizer?: (...args: any[]) => boolean) {
  31. return isEqualWith(value, other, (objVal, othVal, ...rest) => {
  32. if (typeof objVal === 'function' && typeof othVal === 'function') {
  33. return toString(objVal) === toString(othVal);
  34. }
  35. if (typeof customizer === 'function') {
  36. return customizer(objVal, othVal, ...rest);
  37. }
  38. // If customizer returns undefined, comparisons are handled by isEqual instead
  39. return undefined;
  40. });
  41. }
  42. export function getColumnKey(column: any, keyPropNames: any[]): any {
  43. keyPropNames = Array.isArray(keyPropNames) ? keyPropNames : ['key', 'dataIndex'];
  44. let key = null;
  45. each(keyPropNames, propName => {
  46. key = get(column, propName);
  47. if (key != null) {
  48. return false;
  49. }
  50. return undefined;
  51. });
  52. return key;
  53. }
  54. export function mergeColumns(oldColumns: any[] = [], newColumns: any[] = [], keyPropNames: any[] = null, deep = true) {
  55. const finalColumns: any[] = [];
  56. const clone = deep ? cloneDeep : lodashClone;
  57. map(newColumns, newColumn => {
  58. newColumn = { ...newColumn };
  59. const key = getColumnKey(newColumn, keyPropNames);
  60. const oldColumn = key != null && find(oldColumns, item => getColumnKey(item, keyPropNames) === key);
  61. if (oldColumn) {
  62. finalColumns.push(
  63. clone({
  64. ...oldColumn,
  65. ...newColumn,
  66. })
  67. );
  68. } else {
  69. finalColumns.push(clone(newColumn));
  70. }
  71. });
  72. return finalColumns;
  73. }
  74. /**
  75. *
  76. * @param {Array<number>} arr
  77. * @param {number} [beginIndex] begin index, included
  78. * @param {number} [endIndex] end index, not included
  79. * @returns {number}
  80. */
  81. export function arrayAdd(arr: any[] = [], beginIndex = 0, endIndex?: number) {
  82. beginIndex = beginIndex < 0 || typeof beginIndex !== 'number' ? 0 : beginIndex;
  83. endIndex = endIndex > arr.length || typeof endIndex !== 'number' ? arr.length : endIndex;
  84. let result = 0;
  85. each(arr, (value, index) => {
  86. if (index >= beginIndex && index < endIndex) {
  87. result += typeof value === 'number' && !isNaN(value) ? value : 0;
  88. }
  89. });
  90. return result;
  91. }
  92. export function isLastLeftFixed(columns: Record<string, any>[], column: Record<string, any>, checkKeys = ['key']) {
  93. const leftFixedColumns = filter(columns, col => col.fixed === true || col.fixed === 'left');
  94. const index = findIndex(leftFixedColumns, col =>
  95. checkKeys.every(key => col[key] != null && col[key] === column[key])
  96. );
  97. return leftFixedColumns.length > 0 && index === leftFixedColumns.length - 1;
  98. }
  99. export function isFirstFixedRight(columns: Record<string, any>[], column: Record<string, any>, checkKeys = ['key']) {
  100. const rightFixedColumns = filter(columns, col => col.fixed === 'right');
  101. const index = findIndex(rightFixedColumns, col =>
  102. checkKeys.every(key => col[key] != null && col[key] === column[key])
  103. );
  104. return rightFixedColumns.length > 0 && index === 0;
  105. }
  106. export function isAnyFixed(columns: Record<string, any>[], fixedSet = ['left', true, 'right']) {
  107. if (typeof fixedSet === 'string' || typeof fixedSet === 'boolean') {
  108. fixedSet = [fixedSet];
  109. }
  110. return fixedSet.length > 0 && some(columns, col => fixedSet.includes(col.fixed));
  111. }
  112. export function isAnyFixedRight(columns: Record<string, any>[]) {
  113. return some(columns, col => col.fixed === 'right');
  114. }
  115. export function isFixedLeft(column: Record<string, any>) {
  116. return ['left', true].includes(get(column, 'fixed'));
  117. }
  118. export function isFixedRight(column: Record<string, any>) {
  119. return ['right'].includes(get(column, 'fixed'));
  120. }
  121. export function isFixed(column: Record<string, any>) {
  122. return isFixedLeft(column) || isFixedRight(column);
  123. }
  124. export function isInnerColumnKey(key: string | number) {
  125. return [
  126. strings.DEFAULT_KEY_COLUMN_EXPAND,
  127. strings.DEFAULT_KEY_COLUMN_SCROLLBAR,
  128. strings.DEFAULT_KEY_COLUMN_SELECTION,
  129. ].includes(key as any);
  130. }
  131. export function isExpandedColumn(column: Record<string, any>) {
  132. return get(column, 'key') === strings.DEFAULT_KEY_COLUMN_EXPAND;
  133. }
  134. export function isScrollbarColumn(column: Record<string, any>) {
  135. return get(column, 'key') === strings.DEFAULT_KEY_COLUMN_SCROLLBAR;
  136. }
  137. export function isSelectionColumn(column: Record<string, any>) {
  138. return get(column, 'key') === strings.DEFAULT_KEY_COLUMN_SELECTION;
  139. }
  140. export function filterColumns(columns: Record<string, any>[], ignoreKeys = [strings.DEFAULT_KEY_COLUMN_SCROLLBAR]) {
  141. return filter(columns, col => !ignoreKeys.includes(col.key));
  142. }
  143. /**
  144. * get width of scroll bar
  145. * @param {Array} columns
  146. * @returns {Number|undefined}
  147. */
  148. export function getScrollbarColumnWidth(columns: Record<string, any>[] = []) {
  149. const len = columns.length;
  150. if (len) {
  151. const lastColumn = columns[len - 1];
  152. if (get(lastColumn, 'key') === strings.DEFAULT_KEY_COLUMN_SCROLLBAR) {
  153. return get(lastColumn, 'width', 0);
  154. }
  155. }
  156. }
  157. export function getRecordKey(record: Record<string, any>, rowKey: string | number | ((record: any) => string | number)) {
  158. if (rowKey === undefined) {
  159. rowKey = 'key';
  160. }
  161. return typeof rowKey === 'function' ? rowKey(record) : get(record, rowKey);
  162. }
  163. /**
  164. * Determine whether the expandedRowKeys includes a key (rowKey will be added to expandedRowKeys when the expand button is clicked)
  165. * @param {*} expandedRowKeys
  166. * @param {*} key
  167. */
  168. export function isExpanded(expandedRowKeys: (string | number)[], key: string | number) {
  169. return key != null && includes(expandedRowKeys, key);
  170. }
  171. /**
  172. * Determine whether the selectedKeysSet includes the key
  173. * @param {Set} selectedRowKeysSet
  174. * @param {String} key
  175. */
  176. export function isSelected(selectedRowKeysSet: Set<string | number>, key: string | number) {
  177. return key !== null && selectedRowKeysSet.has(key);
  178. }
  179. /**
  180. * Whether the key is included in the disabledRowKeysSet
  181. * @param {Set} disabledRowKeysSet
  182. * @param {String} key
  183. */
  184. export function isDisabled(disabledRowKeysSet: Set<string | number>, key: string | number) {
  185. return key !== null && disabledRowKeysSet.has(key);
  186. }
  187. export function getRecord(data: any[], recordKey: string | number, rowKey: string | number | ((record: any) => string | number)) {
  188. if (rowKey === undefined) {
  189. rowKey = 'key';
  190. }
  191. return find(data, record => recordKey != null && recordKey !== '' && getRecordKey(record, rowKey) === recordKey);
  192. }
  193. export function getRecordChildren(record: Record<string, any>, childrenRecordName: string) {
  194. if (childrenRecordName === undefined) {
  195. childrenRecordName = 'children';
  196. }
  197. return get(record, childrenRecordName);
  198. }
  199. export function genExpandedRowKey(recordKey = '', suffix?: string) {
  200. if (suffix === undefined) {
  201. suffix = '__expanded_row';
  202. }
  203. return recordKey + suffix;
  204. }
  205. export function getDefaultVirtualizedRowConfig(size = '', sectionRow = false) {
  206. const config: { height?: number; minHeight?: number } = {};
  207. if (size === 'small') {
  208. config.height = sectionRow ?
  209. numbers.DEFAULT_VIRTUALIZED_SECTION_ROW_SMALL_HEIGHT :
  210. numbers.DEFAULT_VIRTUALIZED_ROW_SMALL_HEIGHT;
  211. config.minHeight = numbers.DEFAULT_VIRTUALIZED_ROW_SMALL_MIN_HEIGHT;
  212. } else if (size === 'middle') {
  213. config.height = sectionRow ?
  214. numbers.DEFAULT_VIRTUALIZED_SECTION_ROW_MIDDLE_HEIGHT :
  215. numbers.DEFAULT_VIRTUALIZED_ROW_MIDDLE_HEIGHT;
  216. config.minHeight = numbers.DEFAULT_VIRTUALIZED_ROW_MIDDLE_MIN_HEIGHT;
  217. } else {
  218. config.height = sectionRow ?
  219. numbers.DEFAULT_VIRTUALIZED_SECTION_ROW_HEIGHT :
  220. numbers.DEFAULT_VIRTUALIZED_ROW_HEIGHT;
  221. config.minHeight = numbers.DEFAULT_VIRTUALIZED_ROW_MIN_HEIGHT;
  222. }
  223. return config;
  224. }
  225. export function flattenColumns(cols: Record<string, any>[], childrenColumnName = 'children'): Record<string, any>[] {
  226. const list = [];
  227. if (Array.isArray(cols) && cols.length) {
  228. for (const col of cols) {
  229. if (Array.isArray(col[childrenColumnName]) && col[childrenColumnName].length) {
  230. list.push(...flattenColumns(col[childrenColumnName], childrenColumnName));
  231. } else {
  232. list.push(col);
  233. }
  234. }
  235. }
  236. return list;
  237. }
  238. export function assignColumnKeys(columns: Record<string, any>[], childrenColumnName = 'children', level = 0) {
  239. const sameLevelCols: Record<string, any>[] = [];
  240. each(columns, (column, index) => {
  241. if (column.key == null) {
  242. column.key = `${level}-${index}`;
  243. }
  244. if (Array.isArray(column[childrenColumnName]) && column[childrenColumnName].length) {
  245. sameLevelCols.push(...column[childrenColumnName]);
  246. }
  247. });
  248. if (sameLevelCols.length) {
  249. assignColumnKeys(sameLevelCols, childrenColumnName, level + 1);
  250. }
  251. return columns;
  252. }
  253. export function sliceColumnsByLevel(columns: any[], targetLevel = 0, childrenColumnName = 'children', currentLevel = 0) {
  254. const slicedColumns: any[] = [];
  255. if (Array.isArray(columns) && columns.length && currentLevel <= targetLevel) {
  256. columns.forEach(column => {
  257. const children = column[childrenColumnName];
  258. if (Array.isArray(children) && children.length && currentLevel < targetLevel) {
  259. slicedColumns.push(...sliceColumnsByLevel(children, targetLevel, childrenColumnName, currentLevel + 1));
  260. } else {
  261. slicedColumns.push(column);
  262. }
  263. });
  264. }
  265. return slicedColumns;
  266. }
  267. export function getColumnsByLevel(
  268. columns: Record<string, any>[],
  269. targetLevel = 0,
  270. targetColumns: Record<string, any>[] = [],
  271. currentLevel = 0,
  272. childrenColumnName = 'children'
  273. ) {
  274. if (Array.isArray(columns) && columns.length) {
  275. if (targetLevel === currentLevel) {
  276. targetColumns.push(...columns);
  277. } else {
  278. columns.forEach(column => {
  279. getColumnsByLevel(
  280. column[childrenColumnName],
  281. targetLevel,
  282. targetColumns,
  283. currentLevel + 1,
  284. childrenColumnName
  285. );
  286. });
  287. }
  288. }
  289. return targetColumns;
  290. }
  291. export function getAllLevelColumns(columns: Record<string, any>[], childrenColumnName = 'children') {
  292. const all = [];
  293. if (Array.isArray(columns) && columns.length) {
  294. all.push([...columns]);
  295. const sameLevelColumns: Record<string, any>[] = [];
  296. columns.forEach(column => {
  297. const children = column[childrenColumnName];
  298. if (Array.isArray(children) && children.length) {
  299. sameLevelColumns.push(...children);
  300. }
  301. });
  302. if (sameLevelColumns.length) {
  303. all.push(sameLevelColumns);
  304. }
  305. }
  306. return all;
  307. }
  308. export function getColumnByLevelIndex(columns: Record<string, any>[], index: number, level = 0, childrenColumnName = 'children') {
  309. const allLevelColumns = getAllLevelColumns(columns, childrenColumnName);
  310. return allLevelColumns[level][index];
  311. }
  312. export function findColumn(columns: Record<string, any>[], column: Record<string, any>, childrenColumnName = 'children') {
  313. let found: any;
  314. each(columns, item => {
  315. if (item && item.key != null && !found) {
  316. if (item.key === column.key) {
  317. found = item;
  318. }
  319. }
  320. if (item && Array.isArray(item[childrenColumnName]) && !found) {
  321. found = findColumn(item[childrenColumnName], column, childrenColumnName);
  322. }
  323. if (found) {
  324. return false;
  325. }
  326. return undefined;
  327. });
  328. return found;
  329. }
  330. export function expandBtnShouldInRow(props: ExpandBtnShouldInRowProps) {
  331. const { expandedRowRender, dataSource, hideExpandedColumn, childrenRecordName, rowExpandable } = props;
  332. const hasExpandedRowRender = typeof expandedRowRender === 'function';
  333. return (
  334. (hideExpandedColumn && hasExpandedRowRender) ||
  335. (!hasExpandedRowRender && dataSource.some(record => {
  336. const children = get(record, childrenRecordName);
  337. if ((Array.isArray(children) && children.length) || rowExpandable(record)) {
  338. return true;
  339. } else {
  340. return false;
  341. }
  342. }))
  343. );
  344. }
  345. export type ExpandBtnShouldInRowProps = {
  346. expandedRowRender: (record?: Record<string, any>, index?: number, expanded?: boolean) => any;
  347. dataSource: Record<string, any>[];
  348. hideExpandedColumn: boolean;
  349. childrenRecordName: string;
  350. rowExpandable: (record?: Record<string, any>) => boolean;
  351. };
  352. /**
  353. * merge query
  354. * @param {*} query
  355. * @param {*} queries
  356. */
  357. export function mergeQueries(query: Record<string, any>, queries: Record<string, any>[] = []) {
  358. let _mergedQuery;
  359. const idx = queries.findIndex(item => {
  360. if (query.dataIndex === item.dataIndex) {
  361. _mergedQuery = { ...item, ...query };
  362. return true;
  363. }
  364. return false;
  365. });
  366. if (idx > -1) {
  367. queries.splice(idx, 1, _mergedQuery);
  368. } else {
  369. queries.push(_mergedQuery);
  370. }
  371. return [...queries];
  372. }
  373. /**
  374. * Replace the width of the newColumns column with the width of the column after resize
  375. * @param {Object[]} columns columns retain the column width after resize
  376. * @param {Object[]} newColumns
  377. */
  378. export function withResizeWidth(columns: Record<string, any>[], newColumns: Record<string, any>[]) {
  379. const _newColumns = cloneDeep(newColumns);
  380. for (const column of columns) {
  381. if (!isNullOrUndefined(column.width)) {
  382. const currentColumn = column.key;
  383. const columnIndex = findIndex(_newColumns, item => (item as any).key === currentColumn);
  384. if (columnIndex !== -1) {
  385. _newColumns[columnIndex].width = get(column, 'width');
  386. }
  387. }
  388. }
  389. return _newColumns;
  390. }
  391. /**
  392. * Pure function version of the same function in table foundation
  393. * This is not accessible in getDerivedStateFromProps, so fork one out
  394. */
  395. export function getAllDisabledRowKeys({ dataSource, getCheckboxProps, childrenRecordName, rowKey }: GetAllDisabledRowKeysProps): (string | number)[] {
  396. const disabledRowKeys = [];
  397. if (Array.isArray(dataSource) && dataSource.length && typeof getCheckboxProps === 'function') {
  398. for (const record of dataSource) {
  399. const props = getCheckboxProps(record);
  400. const recordKey = typeof rowKey === 'function' ? rowKey(record) : get(record, rowKey);
  401. if (props && props.disabled) {
  402. disabledRowKeys.push(recordKey);
  403. }
  404. const children = get(record, childrenRecordName);
  405. if (Array.isArray(children) && children.length) {
  406. const keys = getAllDisabledRowKeys({ dataSource: children, getCheckboxProps });
  407. disabledRowKeys.push(...keys);
  408. }
  409. }
  410. }
  411. return disabledRowKeys;
  412. }
  413. export interface GetAllDisabledRowKeysProps {
  414. dataSource: Record<string, any>[];
  415. getCheckboxProps: (record?: Record<string, any>) => any;
  416. childrenRecordName?: string;
  417. rowKey?: string | number | ((record: Record<string, any>) => string | number);
  418. }