es-grid-template 1.9.23 → 1.9.25

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,205 @@ 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;
391
+ /**
392
+ * @deprecated
393
+ * @since 1.9.25
394
+ */
350
395
  showCustomTooltip?: boolean;
351
396
  /** Context Menu */
397
+ /** Danh sách menu chuột phải */
352
398
  contextMenuItems?: ContextMenuItem[];
399
+ /**
400
+ * @deprecated
401
+ * @since 1.9.25
402
+ */
353
403
  showDefaultContext?: boolean;
404
+ /**
405
+ * Danh sách menu cần ẩn
406
+ * Có thể trả về động theo row hiện tại
407
+ */
354
408
  contextMenuHidden?: string[] | ((args?: Omit<ContextInfo<RecordType>, 'item' | 'event'>) => string[]);
409
+ /** func khi mở context menu */
355
410
  contextMenuOpen?: (args: Omit<ContextInfo<RecordType>, 'item'>) => void;
411
+ /** func khi click menu */
356
412
  contextMenuClick?: (args: ContextInfo<RecordType>) => void;
413
+ /** Double click vào row */
357
414
  recordDoubleClick?: (args: RecordDoubleClickEventArgs<RecordType>) => void;
415
+ /** Click vào row */
358
416
  recordClick?: (args: RecordDoubleClickEventArgs<RecordType>) => void;
359
417
  /** Filter */
418
+ /** Hiển thị filter nâng cao */
360
419
  showAdvanceFilter?: boolean;
420
+ /** Cho phép filter */
361
421
  allowFiltering?: boolean;
422
+ /** Filter mặc định */
362
423
  defaultFilter?: FilterItem[];
424
+ /** func khi filter thay đổi */
363
425
  onFilter?: (query: FilterItem[]) => void;
426
+ /** Nguồn dữ liệu filter */
364
427
  dataSourceFilter?: SourceFilter[];
428
+ /**
429
+ * Custom popup filter cho từng cột
430
+ */
365
431
  onFilterClick?: (column: ColumnTable<RecordType>, callback: (key: string, data: any) => void) => void;
366
432
  /** mặc định so sánh không dấu + lowercase
367
433
  * ignoreAccents !== false => không khai báo hoặc ignoreAccents = true => so sánh không dấu
368
434
  * ignoreAccents = false => so sánh có dấu + uperrcase sensitive
369
435
  * **/
370
436
  ignoreAccents?: boolean;
371
- /** Sorter */
372
- sortMultiple?: boolean;
437
+ /** Cho phép sort */
373
438
  allowSortering?: boolean;
439
+ /** Cho phép sort nhiều cột */
440
+ sortMultiple?: boolean;
441
+ /** func khi sort */
374
442
  onSorter?: (args: Sorter[]) => void;
443
+ /** Sort mặc định */
375
444
  defaultSorter?: Sorter[];
376
- /** selected record feature */
377
445
  selectionSettings?: SelectionSettings;
446
+ /** Cấu hình chọn row */
378
447
  rowSelection?: RowSelection<RecordType>;
448
+ /** func khi chọn row */
379
449
  rowSelected?: (args: {
380
450
  type: string;
381
451
  rowData: RecordType;
382
452
  selected: RecordType | RecordType[];
383
453
  }) => void;
384
- /** resize column width feature */
454
+ /** Cho phép resize độ rộng cột */
385
455
  allowResizing?: boolean;
456
+ /** Hiển thị dòng tổng hợp */
386
457
  summary?: boolean;
458
+ /** Dữ liệu footer */
387
459
  footerDataSource?: any[];
460
+ /** Hiển thị text khi không có dữ liệu */
388
461
  showEmptyText?: boolean;
462
+ /** Cấu hình command column */
389
463
  commandSettings?: CommandSettings;
464
+ /**
465
+ * Func khi click command
466
+ * Ví dụ: Edit, Delete, Copy...
467
+ */
390
468
  commandClick?: (args: CommandClick<RecordType>) => void;
469
+ /** Callback khi expand/collapse row */
391
470
  onExpandClick?: (args: {
392
471
  expandedKeys: string[];
393
472
  key: string;
394
473
  rowData: any;
395
474
  }) => void;
475
+ /** Cấu hình expandable row */
396
476
  expandable?: ExpandableConfig<RecordType>;
477
+ /** custom Action area bên trái thanh toolbar */
397
478
  actionTemplate?: ReactNode | ReactElement | (() => ReactNode | ReactElement);
479
+ /** Nội dung hiển thị cuối table */
398
480
  bottom?: ReactNode;
399
481
  rowClassName?: string | RowClassName<RecordType>;
400
482
  onRowStyles?: CSSProperties | ((data: RecordType) => CSSProperties);
401
483
  onRowHeaderStyles?: Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'> | (() => Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'>);
402
484
  onRowFooterStyles?: Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'> | (() => Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'>);
485
+ /** Bật virtual scroll */
403
486
  useVirtual?: {
487
+ /** Virtual theo chiều ngang */
404
488
  horizontal?: boolean;
489
+ /** Virtual theo chiều dọc */
405
490
  vertical?: boolean;
406
491
  };
492
+ /** Cho phép chỉnh sửa dữ liệu */
407
493
  editAble?: boolean;
494
+ /** Callback khi data thay đổi */
408
495
  onDataChange?: (data: RecordType[]) => void;
496
+ /** Giá trị mặc định khi thêm mới */
409
497
  defaultValue?: AnyObject | (() => AnyObject);
498
+ /** Callback xử lý paste dữ liệu */
410
499
  onCellPaste?: ICellPasteModel<RecordType>;
500
+ /**
501
+ * Callback khi cell thay đổi giá trị
502
+ *
503
+ * handleCallback dùng để cập nhật giá trị sau khi xử lý custom
504
+ */
411
505
  onCellChange?: (args: CellChangeArgs<RecordType>, handleCallback: (rowData: any, index: any, value?: any) => void) => void;
506
+ /** Callback click cell */
412
507
  onCellClick?: (args: ICellClick, callback?: any) => void;
508
+ /** Xác định row có được edit hay không */
413
509
  rowEditable?: (rowData: RecordType) => boolean;
510
+ /** Validate dữ liệu
511
+ *
512
+ * exp: validate= yup.object().shape({})
513
+ */
414
514
  validate?: any;
415
515
  onBlur?: (data: RecordType[]) => void;
416
516
  };
@@ -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,205 @@ 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;
391
+ /**
392
+ * @deprecated
393
+ * @since 1.9.25
394
+ */
350
395
  showCustomTooltip?: boolean;
351
396
  /** Context Menu */
397
+ /** Danh sách menu chuột phải */
352
398
  contextMenuItems?: ContextMenuItem[];
399
+ /**
400
+ * @deprecated
401
+ * @since 1.9.25
402
+ */
353
403
  showDefaultContext?: boolean;
404
+ /**
405
+ * Danh sách menu cần ẩn
406
+ * Có thể trả về động theo row hiện tại
407
+ */
354
408
  contextMenuHidden?: string[] | ((args?: Omit<ContextInfo<RecordType>, 'item' | 'event'>) => string[]);
409
+ /** func khi mở context menu */
355
410
  contextMenuOpen?: (args: Omit<ContextInfo<RecordType>, 'item'>) => void;
411
+ /** func khi click menu */
356
412
  contextMenuClick?: (args: ContextInfo<RecordType>) => void;
413
+ /** Double click vào row */
357
414
  recordDoubleClick?: (args: RecordDoubleClickEventArgs<RecordType>) => void;
415
+ /** Click vào row */
358
416
  recordClick?: (args: RecordDoubleClickEventArgs<RecordType>) => void;
359
417
  /** Filter */
418
+ /** Hiển thị filter nâng cao */
360
419
  showAdvanceFilter?: boolean;
420
+ /** Cho phép filter */
361
421
  allowFiltering?: boolean;
422
+ /** Filter mặc định */
362
423
  defaultFilter?: FilterItem[];
424
+ /** func khi filter thay đổi */
363
425
  onFilter?: (query: FilterItem[]) => void;
426
+ /** Nguồn dữ liệu filter */
364
427
  dataSourceFilter?: SourceFilter[];
428
+ /**
429
+ * Custom popup filter cho từng cột
430
+ */
365
431
  onFilterClick?: (column: ColumnTable<RecordType>, callback: (key: string, data: any) => void) => void;
366
432
  /** mặc định so sánh không dấu + lowercase
367
433
  * ignoreAccents !== false => không khai báo hoặc ignoreAccents = true => so sánh không dấu
368
434
  * ignoreAccents = false => so sánh có dấu + uperrcase sensitive
369
435
  * **/
370
436
  ignoreAccents?: boolean;
371
- /** Sorter */
372
- sortMultiple?: boolean;
437
+ /** Cho phép sort */
373
438
  allowSortering?: boolean;
439
+ /** Cho phép sort nhiều cột */
440
+ sortMultiple?: boolean;
441
+ /** func khi sort */
374
442
  onSorter?: (args: Sorter[]) => void;
443
+ /** Sort mặc định */
375
444
  defaultSorter?: Sorter[];
376
- /** selected record feature */
377
445
  selectionSettings?: SelectionSettings;
446
+ /** Cấu hình chọn row */
378
447
  rowSelection?: RowSelection<RecordType>;
448
+ /** func khi chọn row */
379
449
  rowSelected?: (args: {
380
450
  type: string;
381
451
  rowData: RecordType;
382
452
  selected: RecordType | RecordType[];
383
453
  }) => void;
384
- /** resize column width feature */
454
+ /** Cho phép resize độ rộng cột */
385
455
  allowResizing?: boolean;
456
+ /** Hiển thị dòng tổng hợp */
386
457
  summary?: boolean;
458
+ /** Dữ liệu footer */
387
459
  footerDataSource?: any[];
460
+ /** Hiển thị text khi không có dữ liệu */
388
461
  showEmptyText?: boolean;
462
+ /** Cấu hình command column */
389
463
  commandSettings?: CommandSettings;
464
+ /**
465
+ * Func khi click command
466
+ * Ví dụ: Edit, Delete, Copy...
467
+ */
390
468
  commandClick?: (args: CommandClick<RecordType>) => void;
469
+ /** Callback khi expand/collapse row */
391
470
  onExpandClick?: (args: {
392
471
  expandedKeys: string[];
393
472
  key: string;
394
473
  rowData: any;
395
474
  }) => void;
475
+ /** Cấu hình expandable row */
396
476
  expandable?: ExpandableConfig<RecordType>;
477
+ /** custom Action area bên trái thanh toolbar */
397
478
  actionTemplate?: ReactNode | ReactElement | (() => ReactNode | ReactElement);
479
+ /** Nội dung hiển thị cuối table */
398
480
  bottom?: ReactNode;
399
481
  rowClassName?: string | RowClassName<RecordType>;
400
482
  onRowStyles?: CSSProperties | ((data: RecordType) => CSSProperties);
401
483
  onRowHeaderStyles?: Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'> | (() => Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'>);
402
484
  onRowFooterStyles?: Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'> | (() => Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'>);
485
+ /** Bật virtual scroll */
403
486
  useVirtual?: {
487
+ /** Virtual theo chiều ngang */
404
488
  horizontal?: boolean;
489
+ /** Virtual theo chiều dọc */
405
490
  vertical?: boolean;
406
491
  };
492
+ /** Cho phép chỉnh sửa dữ liệu */
407
493
  editAble?: boolean;
494
+ /** Callback khi data thay đổi */
408
495
  onDataChange?: (data: RecordType[]) => void;
496
+ /** Giá trị mặc định khi thêm mới */
409
497
  defaultValue?: AnyObject | (() => AnyObject);
498
+ /** Callback xử lý paste dữ liệu */
410
499
  onCellPaste?: ICellPasteModel<RecordType>;
500
+ /**
501
+ * Callback khi cell thay đổi giá trị
502
+ *
503
+ * handleCallback dùng để cập nhật giá trị sau khi xử lý custom
504
+ */
411
505
  onCellChange?: (args: CellChangeArgs<RecordType>, handleCallback: (rowData: any, index: any, value?: any) => void) => void;
506
+ /** Callback click cell */
412
507
  onCellClick?: (args: ICellClick, callback?: any) => void;
508
+ /** Xác định row có được edit hay không */
413
509
  rowEditable?: (rowData: RecordType) => boolean;
510
+ /** Validate dữ liệu
511
+ *
512
+ * exp: validate= yup.object().shape({})
513
+ */
414
514
  validate?: any;
415
515
  onBlur?: (data: RecordType[]) => void;
416
516
  };
@@ -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.25",
4
4
  "description": "es-grid-template",
5
5
  "keywords": [
6
6
  "react",