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 +16 -0
- package/es/grid-component/GridRefreshButton.css +9 -9
- package/es/grid-component/type.d.ts +2 -0
- package/es/group-component/hook/utils.d.ts +9 -9
- package/es/table-virtuoso/InternalTable.js +54 -11
- package/es/table-virtuoso/table/Grid.js +37 -8
- package/lib/grid-component/GridRefreshButton.css +9 -9
- package/lib/grid-component/type.d.ts +2 -0
- package/lib/table-virtuoso/InternalTable.js +53 -10
- package/lib/table-virtuoso/table/Grid.js +37 -8
- package/package.json +2 -2
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").
|
|
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").
|
|
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").
|
|
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").
|
|
193
|
-
linkTemplate?: import("react").
|
|
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").
|
|
199
|
-
onCellStyles?: Omit<CSSProperties, "
|
|
200
|
-
onCellHeaderStyles?: Omit<CSSProperties, "
|
|
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, "
|
|
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(
|
|
146
|
-
}, [
|
|
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:
|
|
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 ||
|
|
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(
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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:
|
|
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)(
|
|
156
|
-
}, [
|
|
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:
|
|
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 ||
|
|
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)(
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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:
|
|
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.
|
|
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": "
|
|
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",
|