es-grid-template 1.9.23 → 1.9.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -312,105 +312,195 @@ export type IFTheme = {
312
312
  cssVariables?: BaseTableCSSVariables;
313
313
  };
314
314
  export type TableProps<RecordType = AnyObject> = {
315
+ /** Unique id của table */
315
316
  id?: string;
317
+ /** Theme hiển thị của table */
316
318
  theme?: IFTheme;
319
+ /** Hiển thị border bao quanh toàn bộ table */
317
320
  useOuterBorder?: boolean;
321
+ /** Hiển thị header */
318
322
  showHeader?: boolean;
323
+ /** CSS class cho container table */
319
324
  className?: string;
325
+ /** Tiêu đề table */
320
326
  title?: ReactNode | ((data: RecordType) => ReactNode);
327
+ /** Tiêu đề khi table ở chế độ fullscreen */
321
328
  fullScreenTitle?: ReactNode | (() => ReactNode);
329
+ /** Tên field dùng làm key duy nhất cho mỗi record */
322
330
  rowKey?: string;
331
+ /** Nguồn dữ liệu */
323
332
  dataSource: RecordType[];
333
+ /** Danh sách cột */
324
334
  columns: ColumnsTable<RecordType>;
335
+ /** Hiển thị loading */
325
336
  loading?: boolean;
337
+ /** Chiều cao table */
326
338
  height?: number;
339
+ /** Chiều cao tối thiểu table */
327
340
  minHeight?: number;
341
+ /** Format dữ liệu mặc định */
328
342
  format?: IFormat;
329
- /** useTranslation 'react-i18next' */
343
+ /**
344
+ * Hàm translate
345
+ * useTranslation 'react-i18next'
346
+ * {t} = useTranslation()
347
+ * */
330
348
  t?: any;
331
349
  /** Language code: exp: vi || en || ja || zh .... */
332
350
  lang?: string;
351
+ /** custom Locale cấu hình cho table */
333
352
  locale?: Locale;
353
+ /** Cấu hình wrap text */
334
354
  wrapSettings?: IWrapSettings;
355
+ /** Bật chế độ tải dữ liệu vô hạn */
335
356
  infiniteScroll?: boolean;
357
+ /** Hàm gọi khi cuộn đến cuối */
336
358
  next?: () => void;
359
+ /** Ngưỡng trigger load thêm dữ liệu */
337
360
  onEndReachedThreshold?: number;
338
- /** Group data by columns */
361
+ /** Cho phép group dữ liệu */
339
362
  groupAble?: boolean;
363
+ /** Danh sách cột dùng để group */
340
364
  groupColumns?: string[];
365
+ /** Cấu hình group */
341
366
  groupSetting?: IGroupSetting;
342
- /** Toolbar */
367
+ /** Hiển thị toolbar */
343
368
  showToolbar?: boolean;
369
+ /** Các action trên toolbar
370
+ *
371
+ * các Key mặc định:
372
+ * key: 'ADD' : Thêm dòng mới vào cuối
373
+ * key: 'DUPLICATE' : Nhân bản dòng được chọn (nếu có)
374
+ * key: 'INSERT_AFTER' : Chèn dòng mới sau dòng được chọn
375
+ * key: 'INSERT_BEFORE' : Chèn dòng mới trước dòng được chọn
376
+ * key: 'INSERT_CHILDREN' : Chèn dòng con vào dòng được chọn
377
+ * key: 'DELETE' : Xóa tất cả dòng
378
+ * key: 'DELETE_ROWS' : Xóa các dòng được chọn
379
+ */
344
380
  toolbarItems?: ToolbarItem[];
381
+ /** Chế độ hiển thị toolbar */
345
382
  toolbarMode?: 'scroll';
383
+ /** Hiển thị chức năng chọn cột */
346
384
  showColumnChoose?: boolean;
385
+ /** Callback khi thay đổi cột hiển thị */
347
386
  onChooseColumns?: (props: IOnChooseColumns) => void;
387
+ /** Cho phép fullscreen */
348
388
  fullScreen?: boolean;
389
+ /** Cấu hình phân trang, false để tắt */
349
390
  pagination?: false | PaginationConfig;
350
- showCustomTooltip?: boolean;
351
391
  /** Context Menu */
392
+ /** Danh sách menu chuột phải */
352
393
  contextMenuItems?: ContextMenuItem[];
353
- showDefaultContext?: boolean;
394
+ /**
395
+ * Danh sách menu cần ẩn
396
+ * Có thể trả về động theo row hiện tại
397
+ */
354
398
  contextMenuHidden?: string[] | ((args?: Omit<ContextInfo<RecordType>, 'item' | 'event'>) => string[]);
399
+ /** func khi mở context menu */
355
400
  contextMenuOpen?: (args: Omit<ContextInfo<RecordType>, 'item'>) => void;
401
+ /** func khi click menu */
356
402
  contextMenuClick?: (args: ContextInfo<RecordType>) => void;
403
+ /** Double click vào row */
357
404
  recordDoubleClick?: (args: RecordDoubleClickEventArgs<RecordType>) => void;
405
+ /** Click vào row */
358
406
  recordClick?: (args: RecordDoubleClickEventArgs<RecordType>) => void;
359
407
  /** Filter */
408
+ /** Hiển thị filter nâng cao */
360
409
  showAdvanceFilter?: boolean;
410
+ /** Cho phép filter */
361
411
  allowFiltering?: boolean;
412
+ /** Filter mặc định */
362
413
  defaultFilter?: FilterItem[];
414
+ /** func khi filter thay đổi */
363
415
  onFilter?: (query: FilterItem[]) => void;
416
+ /** Nguồn dữ liệu filter */
364
417
  dataSourceFilter?: SourceFilter[];
418
+ /**
419
+ * Custom popup filter cho từng cột
420
+ */
365
421
  onFilterClick?: (column: ColumnTable<RecordType>, callback: (key: string, data: any) => void) => void;
366
422
  /** mặc định so sánh không dấu + lowercase
367
423
  * ignoreAccents !== false => không khai báo hoặc ignoreAccents = true => so sánh không dấu
368
424
  * ignoreAccents = false => so sánh có dấu + uperrcase sensitive
369
425
  * **/
370
426
  ignoreAccents?: boolean;
371
- /** Sorter */
372
- sortMultiple?: boolean;
427
+ /** Cho phép sort */
373
428
  allowSortering?: boolean;
429
+ /** Cho phép sort nhiều cột */
430
+ sortMultiple?: boolean;
431
+ /** func khi sort */
374
432
  onSorter?: (args: Sorter[]) => void;
433
+ /** Sort mặc định */
375
434
  defaultSorter?: Sorter[];
376
- /** selected record feature */
377
435
  selectionSettings?: SelectionSettings;
436
+ /** Cấu hình chọn row */
378
437
  rowSelection?: RowSelection<RecordType>;
438
+ /** func khi chọn row */
379
439
  rowSelected?: (args: {
380
440
  type: string;
381
441
  rowData: RecordType;
382
442
  selected: RecordType | RecordType[];
383
443
  }) => void;
384
- /** resize column width feature */
444
+ /** Cho phép resize độ rộng cột */
385
445
  allowResizing?: boolean;
446
+ /** Hiển thị dòng tổng hợp */
386
447
  summary?: boolean;
448
+ /** Dữ liệu footer */
387
449
  footerDataSource?: any[];
450
+ /** Hiển thị text khi không có dữ liệu */
388
451
  showEmptyText?: boolean;
452
+ /** Cấu hình command column */
389
453
  commandSettings?: CommandSettings;
454
+ /**
455
+ * Func khi click command
456
+ * Ví dụ: Edit, Delete, Copy...
457
+ */
390
458
  commandClick?: (args: CommandClick<RecordType>) => void;
459
+ /** Callback khi expand/collapse row */
391
460
  onExpandClick?: (args: {
392
461
  expandedKeys: string[];
393
462
  key: string;
394
463
  rowData: any;
395
464
  }) => void;
465
+ /** Cấu hình expandable row */
396
466
  expandable?: ExpandableConfig<RecordType>;
467
+ /** custom Action area bên trái thanh toolbar */
397
468
  actionTemplate?: ReactNode | ReactElement | (() => ReactNode | ReactElement);
469
+ /** Nội dung hiển thị cuối table */
398
470
  bottom?: ReactNode;
399
471
  rowClassName?: string | RowClassName<RecordType>;
400
472
  onRowStyles?: CSSProperties | ((data: RecordType) => CSSProperties);
401
473
  onRowHeaderStyles?: Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'> | (() => Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'>);
402
474
  onRowFooterStyles?: Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'> | (() => Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'>);
475
+ /** Bật virtual scroll */
403
476
  useVirtual?: {
477
+ /** Virtual theo chiều ngang */
404
478
  horizontal?: boolean;
479
+ /** Virtual theo chiều dọc */
405
480
  vertical?: boolean;
406
481
  };
482
+ /** Cho phép chỉnh sửa dữ liệu */
407
483
  editAble?: boolean;
484
+ /** Callback khi data thay đổi */
408
485
  onDataChange?: (data: RecordType[]) => void;
486
+ /** Giá trị mặc định khi thêm mới */
409
487
  defaultValue?: AnyObject | (() => AnyObject);
488
+ /** Callback xử lý paste dữ liệu */
410
489
  onCellPaste?: ICellPasteModel<RecordType>;
490
+ /**
491
+ * Callback khi cell thay đổi giá trị
492
+ *
493
+ * handleCallback dùng để cập nhật giá trị sau khi xử lý custom
494
+ */
411
495
  onCellChange?: (args: CellChangeArgs<RecordType>, handleCallback: (rowData: any, index: any, value?: any) => void) => void;
496
+ /** Callback click cell */
412
497
  onCellClick?: (args: ICellClick, callback?: any) => void;
498
+ /** Xác định row có được edit hay không */
413
499
  rowEditable?: (rowData: RecordType) => boolean;
500
+ /** Validate dữ liệu
501
+ *
502
+ * exp: validate= yup.object().shape({})
503
+ */
414
504
  validate?: any;
415
505
  onBlur?: (data: RecordType[]) => void;
416
506
  };
@@ -194,10 +194,10 @@ export declare const fixColumnsLeft: <RecordType>(columns: ColumnTable<RecordTyp
194
194
  value: any;
195
195
  rowData: RecordType;
196
196
  }) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
197
- onCellStyles?: Omit<CSSProperties, "position" | "display" | "left" | "minWidth" | "right" | "width"> | ((cellValue: any, cell: import("@tanstack/react-table").Cell<RecordType, unknown>) => Omit<CSSProperties, "position" | "display" | "left" | "minWidth" | "right" | "width">);
198
- onCellHeaderStyles?: Omit<CSSProperties, "position" | "display" | "left" | "minWidth" | "right" | "width"> | ((cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "position" | "display" | "left" | "minWidth" | "right" | "width">);
197
+ onCellStyles?: Omit<CSSProperties, "position" | "left" | "right" | "display" | "width" | "minWidth"> | ((cellValue: any, cell: import("@tanstack/react-table").Cell<RecordType, unknown>) => Omit<CSSProperties, "position" | "left" | "right" | "display" | "width" | "minWidth">);
198
+ onCellHeaderStyles?: Omit<CSSProperties, "position" | "left" | "right" | "display" | "width" | "minWidth"> | ((cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "position" | "left" | "right" | "display" | "width" | "minWidth">);
199
199
  onCell?: (rowData: RecordType, index: number) => import("react").TdHTMLAttributes<HTMLTableCellElement>;
200
- onCellFooterStyles?: Omit<CSSProperties, "position" | "display" | "left" | "minWidth" | "right" | "width"> | ((cellValue: any, cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "position" | "display" | "left" | "minWidth" | "right" | "width">);
200
+ onCellFooterStyles?: Omit<CSSProperties, "position" | "left" | "right" | "display" | "width" | "minWidth"> | ((cellValue: any, cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "position" | "left" | "right" | "display" | "width" | "minWidth">);
201
201
  getValue?: (row: any, rowIndex: number) => any;
202
202
  getCellProps?: (value: any, row: any, rowIndex: number) => import("./../../grid-component/type").CellProps;
203
203
  headerCellProps?: import("./../../grid-component/type").CellProps;
@@ -1,7 +1,9 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import React, { Fragment } from "react";
3
3
  import { useCopyToClipboard } from 'usehooks-ts';
4
- import { checkDecimalSeparator, checkThousandSeparator, detectSeparators, findItemByKey, flattenArray, flattenData, getAllRowKey, getColIdsBetween, getDefaultValue, getEditType, getFormat, getRowIdsBetween, getSelectedCellMatrix, getTableHeight, isEditable, isFormattedNumber, newGuid, parseExcelClipboardText, sumSize,
4
+ import { checkDecimalSeparator, checkThousandSeparator, detectSeparators, findItemByKey, flattenArray, flattenData, getAllRowKey, getColIdsBetween, getDefaultValue, getEditType, getFormat, getRowIdsBetween, getSelectedCellMatrix, getTableHeight, isEditable, isFormattedNumber, newGuid, parseExcelClipboard,
5
+ // parseExcelClipboardText,
6
+ sumSize,
5
7
  // sumSize,
6
8
  unFlattenData, updateArrayByKey, updateColumnWidthsRecursive, updateOrInsert } from "./hook/utils";
7
9
  import { useVirtualizer } from "@tanstack/react-virtual";
@@ -522,7 +524,7 @@ const TableContainerEdit = props => {
522
524
  triggerPaste?.(pastedRows, pastedColumnsArray, newDataSource, []);
523
525
  }
524
526
  }, [dataSource, format, onCellPaste?.maxRowsPaste, originData, rowKey, startCell, table, triggerPaste]);
525
- const handlePasteToTable = React.useCallback(pasteData => {
527
+ const handlePasteToTable = React.useCallback((pasteData, e) => {
526
528
  if (!startCell) return;
527
529
 
528
530
  // const pastedRows = pasted.trim().split('\n').map(row => row.split('\t'));
@@ -535,8 +537,9 @@ const TableContainerEdit = props => {
535
537
 
536
538
  // )
537
539
 
538
- // const rowsPasted = parseExcelText(pasteData)
539
- const rowsPasted = parseExcelClipboardText(pasteData);
540
+ const rowsPasted = parseExcelClipboard(e);
541
+ // const rowsPasted2 = parseExcelClipboardText(pasteData)
542
+
540
543
  if (rowsPasted.length > (onCellPaste?.maxRowsPaste ?? 200)) {
541
544
  // bật popup thông báo
542
545
 
@@ -583,7 +586,7 @@ const TableContainerEdit = props => {
583
586
  if (startCell && !editingKey) {
584
587
  e.preventDefault(); // Chặn hành vi mặc định
585
588
  const clipboardText = e.clipboardData?.getData('text/plain') ?? '';
586
- handlePasteToTable(clipboardText);
589
+ handlePasteToTable(clipboardText, e);
587
590
  }
588
591
  };
589
592
  document.addEventListener('paste', handlePaste);
@@ -631,7 +634,7 @@ const TableContainerEdit = props => {
631
634
  return () => {
632
635
  document.removeEventListener('mousedown', handleClickOutside);
633
636
  };
634
- }, [dataSource, editingKey, id, onBlur]);
637
+ }, [dataSource, editingKey, endCell, focusedCell, id, onBlur, rangeState, startCell]);
635
638
  const columnSizingState = table.getState().columnSizing;
636
639
  React.useEffect(() => {
637
640
  requestAnimationFrame(() => {
@@ -399,7 +399,13 @@ const TableBodyCell = props => {
399
399
  if (e.target.firstChild?.clientWidth < e.target.firstChild?.scrollWidth) {
400
400
  setIsOpenTooltip(true);
401
401
  }
402
- },
402
+ }
403
+
404
+ // onMouseLeave={() => {
405
+ // setIsOpenTooltip(false)
406
+ // }}
407
+ ,
408
+
403
409
  onKeyDown: e => {
404
410
  const flatRows = table.getRowModel().flatRows;
405
411
  if (e.key === 'ArrowDown' && rowNumber < flatRows.length - 1) {
@@ -2389,12 +2389,20 @@ export function parseExcelClipboardText(text) {
2389
2389
  }
2390
2390
  export function parseExcelClipboard(e) {
2391
2391
  const text = e.clipboardData?.getData('text/plain') ?? '';
2392
- if (!text) return [];
2392
+ if (!text) {
2393
+ return [];
2394
+ }
2393
2395
 
2394
2396
  // Excel thường dùng \r\n giữa dòng, \t giữa cột
2395
- const rows = text.split(/\r?\n/) // tách theo dòng
2396
- .filter(r => r.trim() !== '') // bỏ dòng trống
2397
- .map(row => row.split('\t') // tách theo cột
2397
+ const rawRows = text.split(/\r?\n/); // tách theo dòng
2398
+
2399
+ // Loại bỏ chỉ các dòng trống ở cuối (trailing empty rows), giữ nguyên dòng trống ở giữa
2400
+ let lastNonEmpty = rawRows.length - 1;
2401
+ while (lastNonEmpty >= 0 && rawRows[lastNonEmpty].trim() === '') {
2402
+ lastNonEmpty--;
2403
+ }
2404
+ const trimmedRows = lastNonEmpty >= 0 ? rawRows.slice(0, lastNonEmpty + 1) : [];
2405
+ const rows = trimmedRows.map(row => row.split('\t') // tách theo cột
2398
2406
  .map(cell => cell.replace(/\r/g, '').replace(/\n/g, '\n')) // giữ xuống dòng trong ô
2399
2407
  );
2400
2408
  return rows;
@@ -244,6 +244,7 @@ const TableWrapper = props => {
244
244
  content,
245
245
  activeAnchor
246
246
  }) => activeAnchor?.getAttribute('data-tooltip-sort') || content
247
+ // float
247
248
  }), /*#__PURE__*/React.createElement(Tooltip, {
248
249
  id: `${id}-tooltip-error`,
249
250
  style: {
@@ -312,105 +312,195 @@ export type IFTheme = {
312
312
  cssVariables?: BaseTableCSSVariables;
313
313
  };
314
314
  export type TableProps<RecordType = AnyObject> = {
315
+ /** Unique id của table */
315
316
  id?: string;
317
+ /** Theme hiển thị của table */
316
318
  theme?: IFTheme;
319
+ /** Hiển thị border bao quanh toàn bộ table */
317
320
  useOuterBorder?: boolean;
321
+ /** Hiển thị header */
318
322
  showHeader?: boolean;
323
+ /** CSS class cho container table */
319
324
  className?: string;
325
+ /** Tiêu đề table */
320
326
  title?: ReactNode | ((data: RecordType) => ReactNode);
327
+ /** Tiêu đề khi table ở chế độ fullscreen */
321
328
  fullScreenTitle?: ReactNode | (() => ReactNode);
329
+ /** Tên field dùng làm key duy nhất cho mỗi record */
322
330
  rowKey?: string;
331
+ /** Nguồn dữ liệu */
323
332
  dataSource: RecordType[];
333
+ /** Danh sách cột */
324
334
  columns: ColumnsTable<RecordType>;
335
+ /** Hiển thị loading */
325
336
  loading?: boolean;
337
+ /** Chiều cao table */
326
338
  height?: number;
339
+ /** Chiều cao tối thiểu table */
327
340
  minHeight?: number;
341
+ /** Format dữ liệu mặc định */
328
342
  format?: IFormat;
329
- /** useTranslation 'react-i18next' */
343
+ /**
344
+ * Hàm translate
345
+ * useTranslation 'react-i18next'
346
+ * {t} = useTranslation()
347
+ * */
330
348
  t?: any;
331
349
  /** Language code: exp: vi || en || ja || zh .... */
332
350
  lang?: string;
351
+ /** custom Locale cấu hình cho table */
333
352
  locale?: Locale;
353
+ /** Cấu hình wrap text */
334
354
  wrapSettings?: IWrapSettings;
355
+ /** Bật chế độ tải dữ liệu vô hạn */
335
356
  infiniteScroll?: boolean;
357
+ /** Hàm gọi khi cuộn đến cuối */
336
358
  next?: () => void;
359
+ /** Ngưỡng trigger load thêm dữ liệu */
337
360
  onEndReachedThreshold?: number;
338
- /** Group data by columns */
361
+ /** Cho phép group dữ liệu */
339
362
  groupAble?: boolean;
363
+ /** Danh sách cột dùng để group */
340
364
  groupColumns?: string[];
365
+ /** Cấu hình group */
341
366
  groupSetting?: IGroupSetting;
342
- /** Toolbar */
367
+ /** Hiển thị toolbar */
343
368
  showToolbar?: boolean;
369
+ /** Các action trên toolbar
370
+ *
371
+ * các Key mặc định:
372
+ * key: 'ADD' : Thêm dòng mới vào cuối
373
+ * key: 'DUPLICATE' : Nhân bản dòng được chọn (nếu có)
374
+ * key: 'INSERT_AFTER' : Chèn dòng mới sau dòng được chọn
375
+ * key: 'INSERT_BEFORE' : Chèn dòng mới trước dòng được chọn
376
+ * key: 'INSERT_CHILDREN' : Chèn dòng con vào dòng được chọn
377
+ * key: 'DELETE' : Xóa tất cả dòng
378
+ * key: 'DELETE_ROWS' : Xóa các dòng được chọn
379
+ */
344
380
  toolbarItems?: ToolbarItem[];
381
+ /** Chế độ hiển thị toolbar */
345
382
  toolbarMode?: 'scroll';
383
+ /** Hiển thị chức năng chọn cột */
346
384
  showColumnChoose?: boolean;
385
+ /** Callback khi thay đổi cột hiển thị */
347
386
  onChooseColumns?: (props: IOnChooseColumns) => void;
387
+ /** Cho phép fullscreen */
348
388
  fullScreen?: boolean;
389
+ /** Cấu hình phân trang, false để tắt */
349
390
  pagination?: false | PaginationConfig;
350
- showCustomTooltip?: boolean;
351
391
  /** Context Menu */
392
+ /** Danh sách menu chuột phải */
352
393
  contextMenuItems?: ContextMenuItem[];
353
- showDefaultContext?: boolean;
394
+ /**
395
+ * Danh sách menu cần ẩn
396
+ * Có thể trả về động theo row hiện tại
397
+ */
354
398
  contextMenuHidden?: string[] | ((args?: Omit<ContextInfo<RecordType>, 'item' | 'event'>) => string[]);
399
+ /** func khi mở context menu */
355
400
  contextMenuOpen?: (args: Omit<ContextInfo<RecordType>, 'item'>) => void;
401
+ /** func khi click menu */
356
402
  contextMenuClick?: (args: ContextInfo<RecordType>) => void;
403
+ /** Double click vào row */
357
404
  recordDoubleClick?: (args: RecordDoubleClickEventArgs<RecordType>) => void;
405
+ /** Click vào row */
358
406
  recordClick?: (args: RecordDoubleClickEventArgs<RecordType>) => void;
359
407
  /** Filter */
408
+ /** Hiển thị filter nâng cao */
360
409
  showAdvanceFilter?: boolean;
410
+ /** Cho phép filter */
361
411
  allowFiltering?: boolean;
412
+ /** Filter mặc định */
362
413
  defaultFilter?: FilterItem[];
414
+ /** func khi filter thay đổi */
363
415
  onFilter?: (query: FilterItem[]) => void;
416
+ /** Nguồn dữ liệu filter */
364
417
  dataSourceFilter?: SourceFilter[];
418
+ /**
419
+ * Custom popup filter cho từng cột
420
+ */
365
421
  onFilterClick?: (column: ColumnTable<RecordType>, callback: (key: string, data: any) => void) => void;
366
422
  /** mặc định so sánh không dấu + lowercase
367
423
  * ignoreAccents !== false => không khai báo hoặc ignoreAccents = true => so sánh không dấu
368
424
  * ignoreAccents = false => so sánh có dấu + uperrcase sensitive
369
425
  * **/
370
426
  ignoreAccents?: boolean;
371
- /** Sorter */
372
- sortMultiple?: boolean;
427
+ /** Cho phép sort */
373
428
  allowSortering?: boolean;
429
+ /** Cho phép sort nhiều cột */
430
+ sortMultiple?: boolean;
431
+ /** func khi sort */
374
432
  onSorter?: (args: Sorter[]) => void;
433
+ /** Sort mặc định */
375
434
  defaultSorter?: Sorter[];
376
- /** selected record feature */
377
435
  selectionSettings?: SelectionSettings;
436
+ /** Cấu hình chọn row */
378
437
  rowSelection?: RowSelection<RecordType>;
438
+ /** func khi chọn row */
379
439
  rowSelected?: (args: {
380
440
  type: string;
381
441
  rowData: RecordType;
382
442
  selected: RecordType | RecordType[];
383
443
  }) => void;
384
- /** resize column width feature */
444
+ /** Cho phép resize độ rộng cột */
385
445
  allowResizing?: boolean;
446
+ /** Hiển thị dòng tổng hợp */
386
447
  summary?: boolean;
448
+ /** Dữ liệu footer */
387
449
  footerDataSource?: any[];
450
+ /** Hiển thị text khi không có dữ liệu */
388
451
  showEmptyText?: boolean;
452
+ /** Cấu hình command column */
389
453
  commandSettings?: CommandSettings;
454
+ /**
455
+ * Func khi click command
456
+ * Ví dụ: Edit, Delete, Copy...
457
+ */
390
458
  commandClick?: (args: CommandClick<RecordType>) => void;
459
+ /** Callback khi expand/collapse row */
391
460
  onExpandClick?: (args: {
392
461
  expandedKeys: string[];
393
462
  key: string;
394
463
  rowData: any;
395
464
  }) => void;
465
+ /** Cấu hình expandable row */
396
466
  expandable?: ExpandableConfig<RecordType>;
467
+ /** custom Action area bên trái thanh toolbar */
397
468
  actionTemplate?: ReactNode | ReactElement | (() => ReactNode | ReactElement);
469
+ /** Nội dung hiển thị cuối table */
398
470
  bottom?: ReactNode;
399
471
  rowClassName?: string | RowClassName<RecordType>;
400
472
  onRowStyles?: CSSProperties | ((data: RecordType) => CSSProperties);
401
473
  onRowHeaderStyles?: Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'> | (() => Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'>);
402
474
  onRowFooterStyles?: Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'> | (() => Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'>);
475
+ /** Bật virtual scroll */
403
476
  useVirtual?: {
477
+ /** Virtual theo chiều ngang */
404
478
  horizontal?: boolean;
479
+ /** Virtual theo chiều dọc */
405
480
  vertical?: boolean;
406
481
  };
482
+ /** Cho phép chỉnh sửa dữ liệu */
407
483
  editAble?: boolean;
484
+ /** Callback khi data thay đổi */
408
485
  onDataChange?: (data: RecordType[]) => void;
486
+ /** Giá trị mặc định khi thêm mới */
409
487
  defaultValue?: AnyObject | (() => AnyObject);
488
+ /** Callback xử lý paste dữ liệu */
410
489
  onCellPaste?: ICellPasteModel<RecordType>;
490
+ /**
491
+ * Callback khi cell thay đổi giá trị
492
+ *
493
+ * handleCallback dùng để cập nhật giá trị sau khi xử lý custom
494
+ */
411
495
  onCellChange?: (args: CellChangeArgs<RecordType>, handleCallback: (rowData: any, index: any, value?: any) => void) => void;
496
+ /** Callback click cell */
412
497
  onCellClick?: (args: ICellClick, callback?: any) => void;
498
+ /** Xác định row có được edit hay không */
413
499
  rowEditable?: (rowData: RecordType) => boolean;
500
+ /** Validate dữ liệu
501
+ *
502
+ * exp: validate= yup.object().shape({})
503
+ */
414
504
  validate?: any;
415
505
  onBlur?: (data: RecordType[]) => void;
416
506
  };
@@ -529,7 +529,7 @@ const TableContainerEdit = props => {
529
529
  triggerPaste?.(pastedRows, pastedColumnsArray, newDataSource, []);
530
530
  }
531
531
  }, [dataSource, format, onCellPaste?.maxRowsPaste, originData, rowKey, startCell, table, triggerPaste]);
532
- const handlePasteToTable = _react.default.useCallback(pasteData => {
532
+ const handlePasteToTable = _react.default.useCallback((pasteData, e) => {
533
533
  if (!startCell) return;
534
534
 
535
535
  // const pastedRows = pasted.trim().split('\n').map(row => row.split('\t'));
@@ -542,8 +542,9 @@ const TableContainerEdit = props => {
542
542
 
543
543
  // )
544
544
 
545
- // const rowsPasted = parseExcelText(pasteData)
546
- const rowsPasted = (0, _utils.parseExcelClipboardText)(pasteData);
545
+ const rowsPasted = (0, _utils.parseExcelClipboard)(e);
546
+ // const rowsPasted2 = parseExcelClipboardText(pasteData)
547
+
547
548
  if (rowsPasted.length > (onCellPaste?.maxRowsPaste ?? 200)) {
548
549
  // bật popup thông báo
549
550
 
@@ -590,7 +591,7 @@ const TableContainerEdit = props => {
590
591
  if (startCell && !editingKey) {
591
592
  e.preventDefault(); // Chặn hành vi mặc định
592
593
  const clipboardText = e.clipboardData?.getData('text/plain') ?? '';
593
- handlePasteToTable(clipboardText);
594
+ handlePasteToTable(clipboardText, e);
594
595
  }
595
596
  };
596
597
  document.addEventListener('paste', handlePaste);
@@ -638,7 +639,7 @@ const TableContainerEdit = props => {
638
639
  return () => {
639
640
  document.removeEventListener('mousedown', handleClickOutside);
640
641
  };
641
- }, [dataSource, editingKey, id, onBlur]);
642
+ }, [dataSource, editingKey, endCell, focusedCell, id, onBlur, rangeState, startCell]);
642
643
  const columnSizingState = table.getState().columnSizing;
643
644
  _react.default.useEffect(() => {
644
645
  requestAnimationFrame(() => {
@@ -406,7 +406,13 @@ const TableBodyCell = props => {
406
406
  if (e.target.firstChild?.clientWidth < e.target.firstChild?.scrollWidth) {
407
407
  setIsOpenTooltip(true);
408
408
  }
409
- },
409
+ }
410
+
411
+ // onMouseLeave={() => {
412
+ // setIsOpenTooltip(false)
413
+ // }}
414
+ ,
415
+
410
416
  onKeyDown: e => {
411
417
  const flatRows = table.getRowModel().flatRows;
412
418
  if (e.key === 'ArrowDown' && rowNumber < flatRows.length - 1) {
@@ -2518,12 +2518,20 @@ function parseExcelClipboardText(text) {
2518
2518
  }
2519
2519
  function parseExcelClipboard(e) {
2520
2520
  const text = e.clipboardData?.getData('text/plain') ?? '';
2521
- if (!text) return [];
2521
+ if (!text) {
2522
+ return [];
2523
+ }
2522
2524
 
2523
2525
  // Excel thường dùng \r\n giữa dòng, \t giữa cột
2524
- const rows = text.split(/\r?\n/) // tách theo dòng
2525
- .filter(r => r.trim() !== '') // bỏ dòng trống
2526
- .map(row => row.split('\t') // tách theo cột
2526
+ const rawRows = text.split(/\r?\n/); // tách theo dòng
2527
+
2528
+ // Loại bỏ chỉ các dòng trống ở cuối (trailing empty rows), giữ nguyên dòng trống ở giữa
2529
+ let lastNonEmpty = rawRows.length - 1;
2530
+ while (lastNonEmpty >= 0 && rawRows[lastNonEmpty].trim() === '') {
2531
+ lastNonEmpty--;
2532
+ }
2533
+ const trimmedRows = lastNonEmpty >= 0 ? rawRows.slice(0, lastNonEmpty + 1) : [];
2534
+ const rows = trimmedRows.map(row => row.split('\t') // tách theo cột
2527
2535
  .map(cell => cell.replace(/\r/g, '').replace(/\n/g, '\n')) // giữ xuống dòng trong ô
2528
2536
  );
2529
2537
  return rows;
@@ -253,6 +253,7 @@ const TableWrapper = props => {
253
253
  content,
254
254
  activeAnchor
255
255
  }) => activeAnchor?.getAttribute('data-tooltip-sort') || content
256
+ // float
256
257
  }), /*#__PURE__*/_react.default.createElement(_reactTooltip.Tooltip, {
257
258
  id: `${id}-tooltip-error`,
258
259
  style: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "es-grid-template",
3
- "version": "1.9.23",
3
+ "version": "1.9.24",
4
4
  "description": "es-grid-template",
5
5
  "keywords": [
6
6
  "react",