es-grid-template 1.9.94 → 1.9.95

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## 1.9.95 / 06-11-2026
4
+
5
+ ### Added
6
+ - Added opt-in `selectionSettings.pruneSelectionOutsideView` (default `false`) to keep selected rows within the current filtered data view.
7
+
8
+ ### Fixed
9
+ - Preserved existing selection on sorting, grouping, expand/collapse and virtual scrolling when rows remain in the view.
10
+ - Synchronized selection callbacks with current view rows, preserving original `children` data.
11
+ - Fixed group selection with disabled child rows, including `getCheckboxProps` rules based on tree structure.
12
+ - Kept existing selection during temporary loading and preserved legacy behavior when the option is disabled.
13
+
14
+ ## 0.0.1 / 20-02-2025
15
+
16
+ add package
@@ -1,9 +1,9 @@
1
- .ui-rc-grid-refresh-spin {
2
- transform-origin: center;
3
- transform-box: fill-box;
4
- animation: ui-rc-grid-refresh-spin 0.8s linear infinite;
5
- }
6
-
7
- @keyframes ui-rc-grid-refresh-spin {
8
- to { transform: rotate(360deg); }
9
- }
1
+ .ui-rc-grid-refresh-spin {
2
+ transform-origin: center;
3
+ transform-box: fill-box;
4
+ animation: ui-rc-grid-refresh-spin 0.8s linear infinite;
5
+ }
6
+
7
+ @keyframes ui-rc-grid-refresh-spin {
8
+ to { transform: rotate(360deg); }
9
+ }
@@ -800,6 +800,8 @@ export type SelectionSettings<T = AnyObject> = {
800
800
  checkedStrategy?: 'all' | 'parent' | 'child';
801
801
  columnWidth?: number;
802
802
  hideSelectAll?: boolean;
803
+ /** When true, keep selection only for records in the current filtered data view. Default: false. */
804
+ pruneSelection?: boolean;
803
805
  selectedRowKeys?: Key[];
804
806
  defaultSelectedRowKeys?: Key[];
805
807
  getCheckboxProps?: (record: T) => Partial<Omit<CheckboxProps, 'checked' | 'defaultChecked'>>;
@@ -167,7 +167,7 @@ export declare const fixColumnsLeft: <RecordType>(columns: ColumnTable<RecordTyp
167
167
  haveSum?: boolean;
168
168
  isSummary?: boolean;
169
169
  sumGroup?: boolean;
170
- summaryTemplate?: (data: number, key: string) => import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
170
+ summaryTemplate?: (data: number, key: string) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode;
171
171
  sorter?: boolean;
172
172
  allowFiltering?: boolean;
173
173
  allowSortering?: boolean;
@@ -179,27 +179,27 @@ export declare const fixColumnsLeft: <RecordType>(columns: ColumnTable<RecordTyp
179
179
  showFilterSearch?: boolean;
180
180
  hidden?: boolean;
181
181
  visible?: boolean;
182
- headerTooltip?: string | boolean | (() => import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>);
182
+ headerTooltip?: string | boolean | (() => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
183
183
  columnGroupText?: string;
184
184
  headerTextAlign?: import("./../../grid-component/type").ITextAlign;
185
- headerTemplate?: import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | ((column: ColumnTable<RecordType>) => import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>);
185
+ headerTemplate?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode | ((column: ColumnTable<RecordType>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
186
186
  fixedType?: import("./../../grid-component/type").FixedType;
187
187
  headerTextWrap?: boolean;
188
188
  ellipsis?: boolean;
189
189
  allowResizing?: boolean;
190
190
  align?: import("./../../grid-component/type").ITextAlign;
191
191
  textAlign?: import("./../../grid-component/type").ITextAlign;
192
- template?: import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | ((args: import("./../../grid-component/type").ColumnTemplate<RecordType>) => import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>);
193
- linkTemplate?: import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | ((args: import("./../../grid-component/type").ColumnTemplate<RecordType>) => import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>);
192
+ template?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode | ((args: import("./../../grid-component/type").ColumnTemplate<RecordType>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
193
+ linkTemplate?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode | ((args: import("./../../grid-component/type").ColumnTemplate<RecordType>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
194
194
  showTooltip?: boolean;
195
195
  tooltipDescription?: string | ((args: {
196
196
  value: any;
197
197
  rowData: RecordType;
198
- }) => import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>);
199
- onCellStyles?: Omit<CSSProperties, "right" | "left" | "position" | "display" | "width" | "minWidth"> | ((cellValue: any, cell: import("@tanstack/react-table").Cell<RecordType, unknown>) => Omit<CSSProperties, "right" | "left" | "position" | "display" | "width" | "minWidth">);
200
- onCellHeaderStyles?: Omit<CSSProperties, "right" | "left" | "position" | "display" | "width" | "minWidth"> | ((cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "right" | "left" | "position" | "display" | "width" | "minWidth">);
198
+ }) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
199
+ onCellStyles?: Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display"> | ((cellValue: any, cell: import("@tanstack/react-table").Cell<RecordType, unknown>) => Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display">);
200
+ onCellHeaderStyles?: Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display"> | ((cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display">);
201
201
  onCell?: (rowData: RecordType, index: number) => import("react").TdHTMLAttributes<HTMLTableCellElement>;
202
- onCellFooterStyles?: Omit<CSSProperties, "right" | "left" | "position" | "display" | "width" | "minWidth"> | ((cellValue: any, cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "right" | "left" | "position" | "display" | "width" | "minWidth">);
202
+ onCellFooterStyles?: Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display"> | ((cellValue: any, cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display">);
203
203
  getValue?: (row: any, rowIndex: number) => any;
204
204
  getCellProps?: (value: any, row: any, rowIndex: number) => import("./../../grid-component/type").CellProps;
205
205
  headerCellProps?: import("./../../grid-component/type").CellProps;
@@ -8,7 +8,7 @@ import 'dayjs/locale/vi';
8
8
  import { faker } from '@faker-js/faker';
9
9
  import useMergedState from "rc-util/es/hooks/useMergedState";
10
10
  import { getCoreRowModel, getExpandedRowModel, getGroupedRowModel, getPaginationRowModel, useReactTable } from '@tanstack/react-table';
11
- import { addRowIdArray, convertFlatColumn1, convertToObj, convertToObjTrue, filterDataByColumns, findAllChildrenKeys2, flatColumns2, getAllRowKey, getDiffent2Array, getFixedFields, getInvisibleColumns, groupArrayByColumns, isTreeArray, removeKeys } from "./hook/utils";
11
+ import { addRowIdArray, convertFlatColumn1, convertToObj, convertToObjTrue, filterDataByColumns, findAllChildrenKeys2, flattenArray, flattenData, flatColumns2, getAllRowKey, getDiffent2Array, getFixedFields, getInvisibleColumns, groupArrayByColumns, isTreeArray, removeKeys } from "./hook/utils";
12
12
  // import { convertToTanStackColumns } from './hook/useColumns'
13
13
  import { removeInvisibleColumns } from "../grid-component/hooks";
14
14
  import Grid from "./table/Grid";
@@ -82,6 +82,7 @@ const InternalTable = props => {
82
82
  const [columnResizeMode] = React.useState('onChange');
83
83
  const [columnResizeDirection] = React.useState('ltr');
84
84
  const [rowSelection, setRowSelection] = React.useState({});
85
+ const pruneSelection = selectionSettings?.pruneSelection === true;
85
86
 
86
87
  // const [grouping, setGrouping] = React.useState<GroupingState>([])
87
88
 
@@ -118,6 +119,9 @@ const InternalTable = props => {
118
119
  return isTreeArray(originData);
119
120
  }, [originData]);
120
121
  React.useEffect(() => {
122
+ if (pruneSelection) {
123
+ return;
124
+ }
121
125
  if (groupColumns && groupColumns.length > 0 && groupSetting?.client !== false && isDataOriginTree === false) {
122
126
  const abc = groupArrayByColumns(originData, groupColumns);
123
127
  const rs = filterDataByColumns(abc, filterStates, sorterStates, []);
@@ -126,9 +130,48 @@ const InternalTable = props => {
126
130
  const rs = filterDataByColumns(originData, filterStates, sorterStates, []);
127
131
  setTableData(rs);
128
132
  }
129
- }, [groupColumns, originData, filterStates, sorterStates, groupSetting?.client, isDataOriginTree]);
133
+ }, [groupColumns, originData, filterStates, sorterStates, groupSetting?.client, isDataOriginTree, pruneSelection]);
130
134
  // }, [groupColumns, originData, filterStates, sorterStates, groupSetting?.client])
131
135
 
136
+ // Resolve the opt-in view before paint, including inline grouping arrays.
137
+ const groupColumnsKey = JSON.stringify(groupColumns ?? []);
138
+ const viewData = React.useMemo(() => {
139
+ if (!pruneSelection) {
140
+ return tableData;
141
+ }
142
+ const groups = JSON.parse(groupColumnsKey);
143
+ const grouped = groups.length && groupSetting?.client !== false && !isDataOriginTree ? groupArrayByColumns(originData, groups) : originData;
144
+ return filterDataByColumns(grouped, filterStates, sorterStates, []);
145
+ }, [pruneSelection, tableData, groupColumnsKey, originData, groupSetting?.client, isDataOriginTree, filterStates, sorterStates]);
146
+ const effectiveRowSelection = React.useMemo(() => {
147
+ if (!pruneSelection || props.loading) {
148
+ return rowSelection;
149
+ }
150
+ const ids = new Set(flattenArray(viewData).map(row => String(row.rowId)));
151
+ const next = Object.fromEntries(Object.entries(rowSelection).filter(([selectedId, checked]) => checked && ids.has(selectedId)));
152
+
153
+ // A group can stay checked only if its remaining real children are checked.
154
+ const checkGroups = rows => {
155
+ rows.forEach(row => {
156
+ if (row.isGroupRow && next[String(row.rowId)]) {
157
+ const children = flattenData('children', row.children ?? []).filter(child => !child.isGroupRow && !selectionSettings?.getCheckboxProps?.(child)?.disabled);
158
+ if (!children.length || children.some(child => !next[String(child.rowId)])) {
159
+ delete next[String(row.rowId)];
160
+ }
161
+ }
162
+ if (Array.isArray(row.children)) {
163
+ checkGroups(row.children);
164
+ }
165
+ });
166
+ };
167
+ checkGroups(viewData);
168
+ return Object.keys(next).length === Object.keys(rowSelection).length ? rowSelection : next;
169
+ }, [pruneSelection, props.loading, rowSelection, viewData, selectionSettings?.getCheckboxProps]);
170
+ React.useEffect(() => {
171
+ if (effectiveRowSelection !== rowSelection) {
172
+ setRowSelection(prev => prev === rowSelection ? effectiveRowSelection : prev);
173
+ }
174
+ }, [effectiveRowSelection, rowSelection]);
132
175
  React.useEffect(() => {
133
176
  // setColumns(updateWidthsByOther(propsColumns, columns))
134
177
  setColumns(propsColumns);
@@ -142,8 +185,8 @@ const InternalTable = props => {
142
185
  });
143
186
  }, [t, columns, format, editAble]);
144
187
  const isDataTree = React.useMemo(() => {
145
- return isTreeArray(tableData);
146
- }, [tableData]);
188
+ return isTreeArray(viewData);
189
+ }, [viewData]);
147
190
  const columnPinning = React.useMemo(() => {
148
191
  return {
149
192
  left: getFixedFields(columns, 'left'),
@@ -203,11 +246,11 @@ const InternalTable = props => {
203
246
  };
204
247
  const table = useReactTable({
205
248
  _features: [OperatorFeature],
206
- data: tableData,
249
+ data: viewData,
207
250
  // data: mergedData,
208
251
  columns: mergedColumns,
209
252
  state: {
210
- rowSelection,
253
+ rowSelection: effectiveRowSelection,
211
254
  expanded,
212
255
  columnPinning,
213
256
  columnVisibility: columnsHiddenKeys ? convertToObj(columnsHiddenKeys) : columnVisibility,
@@ -282,20 +325,20 @@ const InternalTable = props => {
282
325
  React.useEffect(() => {
283
326
  if (expandable?.preserveExpandedOnDataChange) {
284
327
  // Chờ cây có dữ liệu mới khởi tạo; những lần thay đổi tableData sau đó không reset expanded.
285
- if (initializedExpansionRef.current || tableData.length === 0) {
328
+ if (initializedExpansionRef.current || viewData.length === 0) {
286
329
  return;
287
330
  }
288
331
  initializedExpansionRef.current = true;
289
332
  }
290
333
  if (defaultExpandAllRows) {
291
- const allKeys = findAllChildrenKeys2(tableData, rowKey, 'children');
334
+ const allKeys = findAllChildrenKeys2(viewData, rowKey, 'children');
292
335
  setExpanded(convertToObjTrue(allKeys));
293
336
  } else {
294
337
  if (defaultExpandedRowKeys) {
295
338
  setExpanded(convertToObjTrue(defaultExpandedRowKeys ?? []));
296
339
  }
297
340
  }
298
- }, [defaultExpandAllRows, defaultExpandedRowKeys, tableData, expandable?.preserveExpandedOnDataChange]);
341
+ }, [defaultExpandAllRows, defaultExpandedRowKeys, viewData, expandable?.preserveExpandedOnDataChange]);
299
342
  const triggerGroupColumns = groupedColumns => {
300
343
  // const abc = updateColumnsByGroup(columns, groupedColumns)
301
344
 
@@ -382,7 +425,7 @@ const InternalTable = props => {
382
425
  originData: originData
383
426
  // dataSource={mergedData}
384
427
  ,
385
- dataSource: tableData,
428
+ dataSource: viewData,
386
429
  locale: locale,
387
430
  format: format
388
431
  // columns={convertColumns(mergedColumns)}
@@ -417,7 +460,7 @@ const InternalTable = props => {
417
460
  columnSizing: columnSizing,
418
461
  columnSizingInfo: columnSizingInfo,
419
462
  setColumnSizing: setColumnSizing,
420
- rowSelection: rowSelection,
463
+ rowSelection: effectiveRowSelection,
421
464
  pagination: pagination
422
465
  })))));
423
466
  };
@@ -51,16 +51,46 @@ const Grid = props => {
51
51
  setColumnSizing,
52
52
  columnFilters,
53
53
  setColumnOrder,
54
- rowSelection,
55
54
  className: tableClassNames,
56
55
  ...rest
57
56
  } = props;
57
+ const selectedRowState = table.getState().rowSelection;
58
58
  const [isSelectionChange, setIsSelectionChange] = React.useState({
59
59
  isChange: false,
60
60
  type: '',
61
61
  rowData: {},
62
62
  rowsData: []
63
63
  });
64
+ const rowSelectedRef = React.useRef(rowSelected);
65
+ React.useEffect(() => {
66
+ rowSelectedRef.current = rowSelected;
67
+ }, [rowSelected]);
68
+ const pruneSelection = selectionSettings?.pruneSelection === true;
69
+ const selectedViewRows = React.useMemo(() => {
70
+ if (!pruneSelection || props.loading) {
71
+ return [];
72
+ }
73
+ return table.getCoreRowModel().flatRows.filter(row => !row.original.isGroupRow && selectedRowState[row.id]).map(row => row.original);
74
+ }, [pruneSelection, props.loading, table, dataSource, selectedRowState]);
75
+ const selectedViewIds = JSON.stringify(selectedViewRows.map(row => String(row.rowId)).sort());
76
+ const lastNotifiedSelection = React.useRef(null);
77
+
78
+ // Keep event handlers in sync with the view without another state/effect.
79
+ const selectionChange = pruneSelection && !props.loading ? {
80
+ ...isSelectionChange,
81
+ rowsData: selectedViewRows
82
+ } : isSelectionChange;
83
+ React.useEffect(() => {
84
+ if (!pruneSelection || props.loading || lastNotifiedSelection.current === selectedViewIds) {
85
+ return;
86
+ }
87
+ lastNotifiedSelection.current = selectedViewIds;
88
+ rowSelected?.({
89
+ type: isSelectionChange.isChange ? isSelectionChange.type : 'selectionPruned',
90
+ rowData: isSelectionChange.rowData,
91
+ selected: selectedViewRows
92
+ });
93
+ }, [pruneSelection, props.loading, selectedViewIds, selectedViewRows, rowSelected, isSelectionChange.isChange, isSelectionChange.type, isSelectionChange.rowData]);
64
94
  const [sorterChange, setSorterChange] = React.useState(false);
65
95
  const [filterChange, setFilterChange] = React.useState(false);
66
96
  const [isFullScreen, setIsFullScreen] = React.useState(false);
@@ -77,18 +107,17 @@ const Grid = props => {
77
107
  // }, [mergedSelectedKeys])
78
108
 
79
109
  React.useEffect(() => {
80
- if (isSelectionChange.isChange) {
81
- const aa = table.getState().rowSelection;
82
- const ids = Object.keys(aa);
110
+ if (!pruneSelection && isSelectionChange.isChange) {
111
+ const ids = Object.keys(selectedRowState);
83
112
  const ssss = filterByIds(ids, isSelectionChange.rowsData ?? []); // lấy rowsData của trang hiện tại
84
113
 
85
- rowSelected?.({
114
+ rowSelectedRef.current?.({
86
115
  type: isSelectionChange.type,
87
116
  rowData: isSelectionChange.rowData,
88
117
  selected: ssss.filter(x => !x.isGroupRow)
89
118
  });
90
119
  }
91
- }, [isSelectionChange, rowSelection, table.getState().rowSelection]);
120
+ }, [isSelectionChange, selectedRowState, pruneSelection]);
92
121
  React.useEffect(() => {
93
122
  if (sorterChange) {
94
123
  const aa = table.getState().sorting;
@@ -179,7 +208,7 @@ const Grid = props => {
179
208
  originData: originData,
180
209
  prefix: prefix,
181
210
  selectionSettings: selectionSettings,
182
- isSelectionChange: isSelectionChange,
211
+ isSelectionChange: selectionChange,
183
212
  setIsSelectionChange: setIsSelectionChange,
184
213
  setSorterChange: setSorterChange,
185
214
  setFilterChange: setFilterChange,
@@ -249,7 +278,7 @@ const Grid = props => {
249
278
  originData: originData,
250
279
  prefix: prefix,
251
280
  selectionSettings: selectionSettings,
252
- isSelectionChange: isSelectionChange,
281
+ isSelectionChange: selectionChange,
253
282
  setIsSelectionChange: setIsSelectionChange,
254
283
  setSorterChange: setSorterChange,
255
284
  setFilterChange: setFilterChange,
@@ -1,9 +1,9 @@
1
- .ui-rc-grid-refresh-spin {
2
- transform-origin: center;
3
- transform-box: fill-box;
4
- animation: ui-rc-grid-refresh-spin 0.8s linear infinite;
5
- }
6
-
7
- @keyframes ui-rc-grid-refresh-spin {
8
- to { transform: rotate(360deg); }
9
- }
1
+ .ui-rc-grid-refresh-spin {
2
+ transform-origin: center;
3
+ transform-box: fill-box;
4
+ animation: ui-rc-grid-refresh-spin 0.8s linear infinite;
5
+ }
6
+
7
+ @keyframes ui-rc-grid-refresh-spin {
8
+ to { transform: rotate(360deg); }
9
+ }
@@ -800,6 +800,8 @@ export type SelectionSettings<T = AnyObject> = {
800
800
  checkedStrategy?: 'all' | 'parent' | 'child';
801
801
  columnWidth?: number;
802
802
  hideSelectAll?: boolean;
803
+ /** When true, keep selection only for records in the current filtered data view. Default: false. */
804
+ pruneSelection?: boolean;
803
805
  selectedRowKeys?: Key[];
804
806
  defaultSelectedRowKeys?: Key[];
805
807
  getCheckboxProps?: (record: T) => Partial<Omit<CheckboxProps, 'checked' | 'defaultChecked'>>;
@@ -92,6 +92,7 @@ const InternalTable = props => {
92
92
  const [columnResizeMode] = _react.default.useState('onChange');
93
93
  const [columnResizeDirection] = _react.default.useState('ltr');
94
94
  const [rowSelection, setRowSelection] = _react.default.useState({});
95
+ const pruneSelection = selectionSettings?.pruneSelection === true;
95
96
 
96
97
  // const [grouping, setGrouping] = React.useState<GroupingState>([])
97
98
 
@@ -128,6 +129,9 @@ const InternalTable = props => {
128
129
  return (0, _utils.isTreeArray)(originData);
129
130
  }, [originData]);
130
131
  _react.default.useEffect(() => {
132
+ if (pruneSelection) {
133
+ return;
134
+ }
131
135
  if (groupColumns && groupColumns.length > 0 && groupSetting?.client !== false && isDataOriginTree === false) {
132
136
  const abc = (0, _utils.groupArrayByColumns)(originData, groupColumns);
133
137
  const rs = (0, _utils.filterDataByColumns)(abc, filterStates, sorterStates, []);
@@ -136,9 +140,48 @@ const InternalTable = props => {
136
140
  const rs = (0, _utils.filterDataByColumns)(originData, filterStates, sorterStates, []);
137
141
  setTableData(rs);
138
142
  }
139
- }, [groupColumns, originData, filterStates, sorterStates, groupSetting?.client, isDataOriginTree]);
143
+ }, [groupColumns, originData, filterStates, sorterStates, groupSetting?.client, isDataOriginTree, pruneSelection]);
140
144
  // }, [groupColumns, originData, filterStates, sorterStates, groupSetting?.client])
141
145
 
146
+ // Resolve the opt-in view before paint, including inline grouping arrays.
147
+ const groupColumnsKey = JSON.stringify(groupColumns ?? []);
148
+ const viewData = _react.default.useMemo(() => {
149
+ if (!pruneSelection) {
150
+ return tableData;
151
+ }
152
+ const groups = JSON.parse(groupColumnsKey);
153
+ const grouped = groups.length && groupSetting?.client !== false && !isDataOriginTree ? (0, _utils.groupArrayByColumns)(originData, groups) : originData;
154
+ return (0, _utils.filterDataByColumns)(grouped, filterStates, sorterStates, []);
155
+ }, [pruneSelection, tableData, groupColumnsKey, originData, groupSetting?.client, isDataOriginTree, filterStates, sorterStates]);
156
+ const effectiveRowSelection = _react.default.useMemo(() => {
157
+ if (!pruneSelection || props.loading) {
158
+ return rowSelection;
159
+ }
160
+ const ids = new Set((0, _utils.flattenArray)(viewData).map(row => String(row.rowId)));
161
+ const next = Object.fromEntries(Object.entries(rowSelection).filter(([selectedId, checked]) => checked && ids.has(selectedId)));
162
+
163
+ // A group can stay checked only if its remaining real children are checked.
164
+ const checkGroups = rows => {
165
+ rows.forEach(row => {
166
+ if (row.isGroupRow && next[String(row.rowId)]) {
167
+ const children = (0, _utils.flattenData)('children', row.children ?? []).filter(child => !child.isGroupRow && !selectionSettings?.getCheckboxProps?.(child)?.disabled);
168
+ if (!children.length || children.some(child => !next[String(child.rowId)])) {
169
+ delete next[String(row.rowId)];
170
+ }
171
+ }
172
+ if (Array.isArray(row.children)) {
173
+ checkGroups(row.children);
174
+ }
175
+ });
176
+ };
177
+ checkGroups(viewData);
178
+ return Object.keys(next).length === Object.keys(rowSelection).length ? rowSelection : next;
179
+ }, [pruneSelection, props.loading, rowSelection, viewData, selectionSettings?.getCheckboxProps]);
180
+ _react.default.useEffect(() => {
181
+ if (effectiveRowSelection !== rowSelection) {
182
+ setRowSelection(prev => prev === rowSelection ? effectiveRowSelection : prev);
183
+ }
184
+ }, [effectiveRowSelection, rowSelection]);
142
185
  _react.default.useEffect(() => {
143
186
  // setColumns(updateWidthsByOther(propsColumns, columns))
144
187
  setColumns(propsColumns);
@@ -152,8 +195,8 @@ const InternalTable = props => {
152
195
  });
153
196
  }, [t, columns, format, editAble]);
154
197
  const isDataTree = _react.default.useMemo(() => {
155
- return (0, _utils.isTreeArray)(tableData);
156
- }, [tableData]);
198
+ return (0, _utils.isTreeArray)(viewData);
199
+ }, [viewData]);
157
200
  const columnPinning = _react.default.useMemo(() => {
158
201
  return {
159
202
  left: (0, _utils.getFixedFields)(columns, 'left'),
@@ -213,11 +256,11 @@ const InternalTable = props => {
213
256
  };
214
257
  const table = (0, _reactTable.useReactTable)({
215
258
  _features: [_operator.OperatorFeature],
216
- data: tableData,
259
+ data: viewData,
217
260
  // data: mergedData,
218
261
  columns: mergedColumns,
219
262
  state: {
220
- rowSelection,
263
+ rowSelection: effectiveRowSelection,
221
264
  expanded,
222
265
  columnPinning,
223
266
  columnVisibility: columnsHiddenKeys ? (0, _utils.convertToObj)(columnsHiddenKeys) : columnVisibility,
@@ -292,20 +335,20 @@ const InternalTable = props => {
292
335
  _react.default.useEffect(() => {
293
336
  if (expandable?.preserveExpandedOnDataChange) {
294
337
  // Chờ cây có dữ liệu mới khởi tạo; những lần thay đổi tableData sau đó không reset expanded.
295
- if (initializedExpansionRef.current || tableData.length === 0) {
338
+ if (initializedExpansionRef.current || viewData.length === 0) {
296
339
  return;
297
340
  }
298
341
  initializedExpansionRef.current = true;
299
342
  }
300
343
  if (defaultExpandAllRows) {
301
- const allKeys = (0, _utils.findAllChildrenKeys2)(tableData, rowKey, 'children');
344
+ const allKeys = (0, _utils.findAllChildrenKeys2)(viewData, rowKey, 'children');
302
345
  setExpanded((0, _utils.convertToObjTrue)(allKeys));
303
346
  } else {
304
347
  if (defaultExpandedRowKeys) {
305
348
  setExpanded((0, _utils.convertToObjTrue)(defaultExpandedRowKeys ?? []));
306
349
  }
307
350
  }
308
- }, [defaultExpandAllRows, defaultExpandedRowKeys, tableData, expandable?.preserveExpandedOnDataChange]);
351
+ }, [defaultExpandAllRows, defaultExpandedRowKeys, viewData, expandable?.preserveExpandedOnDataChange]);
309
352
  const triggerGroupColumns = groupedColumns => {
310
353
  // const abc = updateColumnsByGroup(columns, groupedColumns)
311
354
 
@@ -392,7 +435,7 @@ const InternalTable = props => {
392
435
  originData: originData
393
436
  // dataSource={mergedData}
394
437
  ,
395
- dataSource: tableData,
438
+ dataSource: viewData,
396
439
  locale: locale,
397
440
  format: format
398
441
  // columns={convertColumns(mergedColumns)}
@@ -427,7 +470,7 @@ const InternalTable = props => {
427
470
  columnSizing: columnSizing,
428
471
  columnSizingInfo: columnSizingInfo,
429
472
  setColumnSizing: setColumnSizing,
430
- rowSelection: rowSelection,
473
+ rowSelection: effectiveRowSelection,
431
474
  pagination: pagination
432
475
  })))));
433
476
  };
@@ -60,16 +60,46 @@ const Grid = props => {
60
60
  setColumnSizing,
61
61
  columnFilters,
62
62
  setColumnOrder,
63
- rowSelection,
64
63
  className: tableClassNames,
65
64
  ...rest
66
65
  } = props;
66
+ const selectedRowState = table.getState().rowSelection;
67
67
  const [isSelectionChange, setIsSelectionChange] = _react.default.useState({
68
68
  isChange: false,
69
69
  type: '',
70
70
  rowData: {},
71
71
  rowsData: []
72
72
  });
73
+ const rowSelectedRef = _react.default.useRef(rowSelected);
74
+ _react.default.useEffect(() => {
75
+ rowSelectedRef.current = rowSelected;
76
+ }, [rowSelected]);
77
+ const pruneSelection = selectionSettings?.pruneSelection === true;
78
+ const selectedViewRows = _react.default.useMemo(() => {
79
+ if (!pruneSelection || props.loading) {
80
+ return [];
81
+ }
82
+ return table.getCoreRowModel().flatRows.filter(row => !row.original.isGroupRow && selectedRowState[row.id]).map(row => row.original);
83
+ }, [pruneSelection, props.loading, table, dataSource, selectedRowState]);
84
+ const selectedViewIds = JSON.stringify(selectedViewRows.map(row => String(row.rowId)).sort());
85
+ const lastNotifiedSelection = _react.default.useRef(null);
86
+
87
+ // Keep event handlers in sync with the view without another state/effect.
88
+ const selectionChange = pruneSelection && !props.loading ? {
89
+ ...isSelectionChange,
90
+ rowsData: selectedViewRows
91
+ } : isSelectionChange;
92
+ _react.default.useEffect(() => {
93
+ if (!pruneSelection || props.loading || lastNotifiedSelection.current === selectedViewIds) {
94
+ return;
95
+ }
96
+ lastNotifiedSelection.current = selectedViewIds;
97
+ rowSelected?.({
98
+ type: isSelectionChange.isChange ? isSelectionChange.type : 'selectionPruned',
99
+ rowData: isSelectionChange.rowData,
100
+ selected: selectedViewRows
101
+ });
102
+ }, [pruneSelection, props.loading, selectedViewIds, selectedViewRows, rowSelected, isSelectionChange.isChange, isSelectionChange.type, isSelectionChange.rowData]);
73
103
  const [sorterChange, setSorterChange] = _react.default.useState(false);
74
104
  const [filterChange, setFilterChange] = _react.default.useState(false);
75
105
  const [isFullScreen, setIsFullScreen] = _react.default.useState(false);
@@ -86,18 +116,17 @@ const Grid = props => {
86
116
  // }, [mergedSelectedKeys])
87
117
 
88
118
  _react.default.useEffect(() => {
89
- if (isSelectionChange.isChange) {
90
- const aa = table.getState().rowSelection;
91
- const ids = Object.keys(aa);
119
+ if (!pruneSelection && isSelectionChange.isChange) {
120
+ const ids = Object.keys(selectedRowState);
92
121
  const ssss = (0, _utils.filterByIds)(ids, isSelectionChange.rowsData ?? []); // lấy rowsData của trang hiện tại
93
122
 
94
- rowSelected?.({
123
+ rowSelectedRef.current?.({
95
124
  type: isSelectionChange.type,
96
125
  rowData: isSelectionChange.rowData,
97
126
  selected: ssss.filter(x => !x.isGroupRow)
98
127
  });
99
128
  }
100
- }, [isSelectionChange, rowSelection, table.getState().rowSelection]);
129
+ }, [isSelectionChange, selectedRowState, pruneSelection]);
101
130
  _react.default.useEffect(() => {
102
131
  if (sorterChange) {
103
132
  const aa = table.getState().sorting;
@@ -188,7 +217,7 @@ const Grid = props => {
188
217
  originData: originData,
189
218
  prefix: prefix,
190
219
  selectionSettings: selectionSettings,
191
- isSelectionChange: isSelectionChange,
220
+ isSelectionChange: selectionChange,
192
221
  setIsSelectionChange: setIsSelectionChange,
193
222
  setSorterChange: setSorterChange,
194
223
  setFilterChange: setFilterChange,
@@ -258,7 +287,7 @@ const Grid = props => {
258
287
  originData: originData,
259
288
  prefix: prefix,
260
289
  selectionSettings: selectionSettings,
261
- isSelectionChange: isSelectionChange,
290
+ isSelectionChange: selectionChange,
262
291
  setIsSelectionChange: setIsSelectionChange,
263
292
  setSorterChange: setSorterChange,
264
293
  setFilterChange: setFilterChange,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "es-grid-template",
3
- "version": "1.9.94",
3
+ "version": "1.9.95",
4
4
  "description": "es-grid-template",
5
5
  "keywords": [
6
6
  "react",
@@ -47,7 +47,7 @@
47
47
  "@hookform/resolvers": "5.0.1",
48
48
  "@popperjs/core": "2.11.8",
49
49
  "@rc-component/color-picker": "2.0.1",
50
- "@rc-component/father-plugin": "^2.0.1",
50
+ "@rc-component/father-plugin": "2.0.1",
51
51
  "@rc-component/trigger": "2.0.0",
52
52
  "@rc-component/util": "1.0.1",
53
53
  "@tanstack/react-table": "8.21.3",