@mui/x-data-grid 9.12.0 → 9.14.0
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 +301 -0
- package/DataGrid/DataGrid.js +1 -1
- package/DataGrid/DataGrid.mjs +1 -1
- package/components/GridPagination.js +1 -1
- package/components/GridPagination.mjs +1 -1
- package/components/GridRow.d.mts +1 -0
- package/components/GridRow.d.ts +1 -0
- package/components/GridRow.js +41 -6
- package/components/GridRow.mjs +41 -6
- package/components/GridScrollArea.js +20 -7
- package/components/GridScrollArea.mjs +21 -8
- package/components/base/GridOverlays.js +6 -5
- package/components/base/GridOverlays.mjs +6 -5
- package/components/cell/GridActionsCell.js +53 -50
- package/components/cell/GridActionsCell.mjs +53 -50
- package/components/cell/GridActionsCellItem.js +0 -1
- package/components/cell/GridActionsCellItem.mjs +0 -1
- package/components/cell/GridCell.js +15 -4
- package/components/cell/GridCell.mjs +15 -4
- package/components/cell/GridEditBooleanCell.d.mts +1 -1
- package/components/cell/GridEditBooleanCell.d.ts +1 -1
- package/components/cell/GridEditBooleanCell.js +4 -4
- package/components/cell/GridEditBooleanCell.mjs +4 -4
- package/components/cell/GridEditDateCell.d.mts +1 -1
- package/components/cell/GridEditDateCell.d.ts +1 -1
- package/components/cell/GridEditDateCell.js +5 -5
- package/components/cell/GridEditDateCell.mjs +5 -5
- package/components/cell/GridEditInputCell.d.mts +1 -1
- package/components/cell/GridEditInputCell.d.ts +1 -1
- package/components/cell/GridEditInputCell.js +1 -1
- package/components/cell/GridEditInputCell.mjs +1 -1
- package/components/cell/GridEditLongTextCell.d.mts +1 -1
- package/components/cell/GridEditLongTextCell.d.ts +1 -1
- package/components/cell/GridEditLongTextCell.js +2 -2
- package/components/cell/GridEditLongTextCell.mjs +2 -2
- package/components/cell/GridEditSingleSelectCell.d.mts +1 -1
- package/components/cell/GridEditSingleSelectCell.d.ts +1 -1
- package/components/cell/GridEditSingleSelectCell.js +4 -4
- package/components/cell/GridEditSingleSelectCell.mjs +4 -4
- package/components/quickFilter/QuickFilter.js +7 -2
- package/components/quickFilter/QuickFilter.mjs +7 -2
- package/components/toolbarV8/Toolbar.d.mts +5 -1
- package/components/toolbarV8/Toolbar.d.ts +5 -1
- package/components/toolbarV8/Toolbar.js +34 -12
- package/components/toolbarV8/Toolbar.mjs +33 -11
- package/components/virtualization/GridVirtualScroller.js +3 -0
- package/components/virtualization/GridVirtualScroller.mjs +3 -0
- package/hooks/core/useGridVirtualizer.js +14 -1
- package/hooks/core/useGridVirtualizer.mjs +14 -1
- package/hooks/features/columnResize/useGridColumnResize.js +4 -1
- package/hooks/features/columnResize/useGridColumnResize.mjs +4 -1
- package/hooks/features/dataSource/models.d.mts +2 -2
- package/hooks/features/dataSource/models.d.ts +2 -2
- package/hooks/features/dataSource/useGridDataSourceBase.js +12 -8
- package/hooks/features/dataSource/useGridDataSourceBase.mjs +13 -9
- package/hooks/features/pivoting/gridPivotingInterfaces.d.mts +2 -2
- package/hooks/features/pivoting/gridPivotingInterfaces.d.ts +2 -2
- package/hooks/features/rowSelection/useGridRowSelection.d.mts +1 -1
- package/hooks/features/rowSelection/useGridRowSelection.d.ts +1 -1
- package/hooks/features/rowSelection/useGridRowSelection.js +51 -10
- package/hooks/features/rowSelection/useGridRowSelection.mjs +53 -12
- package/hooks/features/rowSelection/utils.js +5 -0
- package/hooks/features/rowSelection/utils.mjs +5 -0
- package/hooks/features/rows/gridRowsUtils.d.mts +37 -3
- package/hooks/features/rows/gridRowsUtils.d.ts +37 -3
- package/hooks/features/rows/gridRowsUtils.js +120 -23
- package/hooks/features/rows/gridRowsUtils.mjs +115 -22
- package/hooks/features/rows/useGridRows.js +10 -1
- package/hooks/features/rows/useGridRows.mjs +10 -1
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/internals/index.d.mts +2 -2
- package/internals/index.d.ts +2 -2
- package/internals/index.js +28 -0
- package/internals/index.mjs +2 -2
- package/material/variables.d.mts +6 -1
- package/material/variables.d.ts +6 -1
- package/material/variables.js +17 -3
- package/material/variables.mjs +14 -4
- package/models/api/gridRowApi.d.mts +7 -7
- package/models/api/gridRowApi.d.ts +7 -7
- package/models/gridDataSource.d.mts +3 -0
- package/models/gridDataSource.d.ts +3 -0
- package/models/gridRows.d.mts +28 -1
- package/models/gridRows.d.ts +28 -1
- package/models/props/DataGridProps.d.mts +3 -3
- package/models/props/DataGridProps.d.ts +3 -3
- package/package.json +6 -6
- package/utils/keyboardUtils.d.mts +1 -0
- package/utils/keyboardUtils.d.ts +1 -0
- package/utils/keyboardUtils.js +6 -0
- package/utils/keyboardUtils.mjs +5 -0
|
@@ -9,4 +9,4 @@ export declare const rowSelectionStateInitializer: GridStateInitializer<Pick<Dat
|
|
|
9
9
|
* @requires useGridFocus (state) - can be after
|
|
10
10
|
* @requires useGridKeyboardNavigation (`cellKeyDown` event must first be consumed by it)
|
|
11
11
|
*/
|
|
12
|
-
export declare const useGridRowSelection: (apiRef: RefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'checkboxSelection' | 'rowSelectionModel' | 'onRowSelectionModelChange' | 'disableMultipleRowSelection' | 'disableRowSelectionOnClick' | 'disableRowSelectionExcludeModel' | 'isRowSelectable' | 'checkboxSelectionVisibleOnly' | 'pagination' | 'paginationMode' | 'filterMode' | 'classes' | 'keepNonExistentRowsSelected' | 'rowSelection' | 'rowSelectionPropagation' | 'signature'>) => void;
|
|
12
|
+
export declare const useGridRowSelection: (apiRef: RefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'checkboxSelection' | 'rowSelectionModel' | 'onRowSelectionModelChange' | 'disableMultipleRowSelection' | 'disableRowSelectionOnClick' | 'disableRowSelectionExcludeModel' | 'isRowSelectable' | 'checkboxSelectionVisibleOnly' | 'pagination' | 'paginationMode' | 'filterMode' | 'classes' | 'keepNonExistentRowsSelected' | 'rowSelection' | 'rowSelectionPropagation' | 'dataSource' | 'signature'>) => void;
|
|
@@ -9,4 +9,4 @@ export declare const rowSelectionStateInitializer: GridStateInitializer<Pick<Dat
|
|
|
9
9
|
* @requires useGridFocus (state) - can be after
|
|
10
10
|
* @requires useGridKeyboardNavigation (`cellKeyDown` event must first be consumed by it)
|
|
11
11
|
*/
|
|
12
|
-
export declare const useGridRowSelection: (apiRef: RefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'checkboxSelection' | 'rowSelectionModel' | 'onRowSelectionModelChange' | 'disableMultipleRowSelection' | 'disableRowSelectionOnClick' | 'disableRowSelectionExcludeModel' | 'isRowSelectable' | 'checkboxSelectionVisibleOnly' | 'pagination' | 'paginationMode' | 'filterMode' | 'classes' | 'keepNonExistentRowsSelected' | 'rowSelection' | 'rowSelectionPropagation' | 'signature'>) => void;
|
|
12
|
+
export declare const useGridRowSelection: (apiRef: RefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'checkboxSelection' | 'rowSelectionModel' | 'onRowSelectionModelChange' | 'disableMultipleRowSelection' | 'disableRowSelectionOnClick' | 'disableRowSelectionExcludeModel' | 'isRowSelectable' | 'checkboxSelectionVisibleOnly' | 'pagination' | 'paginationMode' | 'filterMode' | 'classes' | 'keepNonExistentRowsSelected' | 'rowSelection' | 'rowSelectionPropagation' | 'dataSource' | 'signature'>) => void;
|
|
@@ -17,6 +17,7 @@ var _useGridApiMethod = require("../../utils/useGridApiMethod");
|
|
|
17
17
|
var _useGridLogger = require("../../utils/useGridLogger");
|
|
18
18
|
var _useGridSelector = require("../../utils/useGridSelector");
|
|
19
19
|
var _gridRowsSelector = require("../rows/gridRowsSelector");
|
|
20
|
+
var _gridRowsUtils = require("../rows/gridRowsUtils");
|
|
20
21
|
var _gridRowSelectionSelector = require("./gridRowSelectionSelector");
|
|
21
22
|
var _gridFocusStateSelector = require("../focus/gridFocusStateSelector");
|
|
22
23
|
var _gridFilterSelector = require("../filter/gridFilterSelector");
|
|
@@ -127,6 +128,10 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
127
128
|
if (props.rowSelection === false) {
|
|
128
129
|
return false;
|
|
129
130
|
}
|
|
131
|
+
const rowNode = (0, _gridRowsSelector.gridRowNodeSelector)(apiRef, id);
|
|
132
|
+
if (rowNode?.type === 'footer' || rowNode?.type === 'pinnedRow' || rowNode?.type === 'skeletonRow') {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
130
135
|
|
|
131
136
|
// If `keepNonExistentRowsSelected` is true, we might run in a case where row selectability is checked for a row that does not exist.
|
|
132
137
|
// Since that row was previously selected (otherwise it would not be checked at this point), we return true.
|
|
@@ -136,10 +141,6 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
136
141
|
if (propIsRowSelectable && !propIsRowSelectable(apiRef.current.getRowParams(id))) {
|
|
137
142
|
return false;
|
|
138
143
|
}
|
|
139
|
-
const rowNode = (0, _gridRowsSelector.gridRowNodeSelector)(apiRef, id);
|
|
140
|
-
if (rowNode?.type === 'footer' || rowNode?.type === 'pinnedRow') {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
144
|
return true;
|
|
144
145
|
}, [apiRef, props.rowSelection, props.keepNonExistentRowsSelected, propIsRowSelectable]);
|
|
145
146
|
const getSelectedRows = React.useCallback(() => (0, _gridRowSelectionSelector.gridRowSelectionIdsSelector)(apiRef), [apiRef]);
|
|
@@ -315,6 +316,7 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
315
316
|
* EVENTS
|
|
316
317
|
*/
|
|
317
318
|
const isFirstRender = React.useRef(true);
|
|
319
|
+
const previousRowsLookupRef = React.useRef(null);
|
|
318
320
|
const removeOutdatedSelection = React.useCallback(() => {
|
|
319
321
|
if (isFirstRender.current) {
|
|
320
322
|
return;
|
|
@@ -323,6 +325,11 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
323
325
|
const rowsLookup = (0, _gridRowsSelector.gridRowsLookupSelector)(apiRef);
|
|
324
326
|
const rowTree = (0, _gridRowsSelector.gridRowTreeSelector)(apiRef);
|
|
325
327
|
const filteredRowsLookup = (0, _gridFilterSelector.gridFilteredRowsLookupSelector)(apiRef);
|
|
328
|
+
const previousRowsLookup = previousRowsLookupRef.current;
|
|
329
|
+
previousRowsLookupRef.current = rowsLookup;
|
|
330
|
+
// Read the depth at call time. The event that runs this callback can fire in the same
|
|
331
|
+
// tick as the state update that changes the depth, before the render-scope value updates.
|
|
332
|
+
const isNested = (0, _gridRowsSelector.gridRowMaximumTreeDepthSelector)(apiRef) > 1;
|
|
326
333
|
const isNonExistent = id => {
|
|
327
334
|
if (props.filterMode === 'server') {
|
|
328
335
|
return !rowsLookup[id];
|
|
@@ -350,6 +357,32 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
350
357
|
return node.children.some(childId => isOrHasSelectableDescendant(childId));
|
|
351
358
|
};
|
|
352
359
|
let hasChanged = false;
|
|
360
|
+
|
|
361
|
+
// Rows can load after their parent was selected (server-side lazy loading).
|
|
362
|
+
// They arrive unselected, so the parent re-derivation below would drop the parent.
|
|
363
|
+
// With `rowSelectionPropagation.descendants`, the rows that appeared since the last run
|
|
364
|
+
// inherit the selection state of their closest previously known ancestor instead.
|
|
365
|
+
if (isNested && props.rowSelectionPropagation?.descendants && currentSelection.type === 'include' && currentSelection.ids.size > 0 && previousRowsLookup !== null && previousRowsLookup !== rowsLookup) {
|
|
366
|
+
const isNewRow = id => rowsLookup[id] !== undefined && previousRowsLookup[id] === undefined;
|
|
367
|
+
const inheritsSelection = id => {
|
|
368
|
+
const parentId = rowTree[id]?.parent;
|
|
369
|
+
if (parentId == null || parentId === _gridRowsUtils.GRID_ROOT_GROUP_ID) {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
if (selectionManager.has(parentId)) {
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
// A row that appeared together with its parent reads the state further up the chain.
|
|
376
|
+
return isNewRow(parentId) ? inheritsSelection(parentId) : false;
|
|
377
|
+
};
|
|
378
|
+
const dataRowIds = (0, _gridRowsSelector.gridDataRowIdsSelector)(apiRef);
|
|
379
|
+
for (const id of dataRowIds) {
|
|
380
|
+
if (isNewRow(id) && filteredRowsLookup[id] !== false && inheritsSelection(id) && apiRef.current.isRowSelectable(id)) {
|
|
381
|
+
selectionManager.select(id);
|
|
382
|
+
hasChanged = true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
353
386
|
for (const id of currentSelection.ids) {
|
|
354
387
|
if (isNonExistent(id)) {
|
|
355
388
|
if (props.keepNonExistentRowsSelected) {
|
|
@@ -382,9 +415,10 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
382
415
|
// For nested data, on row tree updation (filtering, adding rows, etc.) when the selection is
|
|
383
416
|
// not empty, we need to re-run scanning of the tree to propagate the selection changes
|
|
384
417
|
// Example: A parent whose de-selected children are filtered out should now be selected
|
|
385
|
-
|
|
386
|
-
//
|
|
387
|
-
|
|
418
|
+
// With a data source, an `exclude` model is left alone: the grid only knows the loaded
|
|
419
|
+
// subset of the rows, so rewriting the model into an `include` of the known rows would
|
|
420
|
+
// drop the "rows that load later are selected too" semantics of the select-all state.
|
|
421
|
+
const shouldReapplyPropagation = isNested && props.rowSelectionPropagation?.parents && (newSelectionModel.type === 'exclude' ? props.dataSource === undefined : newSelectionModel.ids.size > 0);
|
|
388
422
|
if (hasChanged || shouldReapplyPropagation) {
|
|
389
423
|
if (shouldReapplyPropagation) {
|
|
390
424
|
if (newSelectionModel.type === 'exclude') {
|
|
@@ -398,13 +432,20 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
398
432
|
}
|
|
399
433
|
apiRef.current.selectRows(selectedRowIds, true, true);
|
|
400
434
|
} else {
|
|
401
|
-
|
|
435
|
+
// `getPropagatedRowSelectionModel` reads the sibling selection state from the model
|
|
436
|
+
// built above, so the rows selected during this run count for the parent derivation.
|
|
437
|
+
// `selectRows` would read the state from before this run instead.
|
|
438
|
+
const propagatedModel = apiRef.current.getPropagatedRowSelectionModel(newSelectionModel);
|
|
439
|
+
const isSelectionValid = propagatedModel.ids.size < 2 || canHaveMultipleSelection;
|
|
440
|
+
if (isSelectionValid && (propagatedModel.type !== currentSelection.type || propagatedModel.ids.size !== currentSelection.ids.size || !Array.from(propagatedModel.ids).every(id => currentSelection.ids.has(id)))) {
|
|
441
|
+
apiRef.current.setRowSelectionModel(propagatedModel, 'multipleRowsSelection');
|
|
442
|
+
}
|
|
402
443
|
}
|
|
403
444
|
} else {
|
|
404
445
|
apiRef.current.setRowSelectionModel(newSelectionModel, 'multipleRowsSelection');
|
|
405
446
|
}
|
|
406
447
|
}
|
|
407
|
-
}, [apiRef,
|
|
448
|
+
}, [apiRef, props.rowSelectionPropagation?.parents, props.rowSelectionPropagation?.descendants, props.keepNonExistentRowsSelected, props.filterMode, props.dataSource, canHaveMultipleSelection, getRowsToBeSelected]);
|
|
408
449
|
const handleSingleRowSelection = React.useCallback((id, event) => {
|
|
409
450
|
const hasCtrlKey = event.metaKey || event.ctrlKey;
|
|
410
451
|
|
|
@@ -542,7 +583,7 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
542
583
|
handleSingleRowSelection(params.id, event);
|
|
543
584
|
return;
|
|
544
585
|
}
|
|
545
|
-
if (
|
|
586
|
+
if ((0, _keyboardUtils.isSelectAllShortcut)(event)) {
|
|
546
587
|
event.preventDefault();
|
|
547
588
|
if (canHaveMultipleSelection) {
|
|
548
589
|
toggleAllRows(true);
|
|
@@ -9,13 +9,14 @@ import { useGridEvent } from "../../utils/useGridEvent.mjs";
|
|
|
9
9
|
import { useGridApiMethod } from "../../utils/useGridApiMethod.mjs";
|
|
10
10
|
import { useGridLogger } from "../../utils/useGridLogger.mjs";
|
|
11
11
|
import { useGridSelector } from "../../utils/useGridSelector.mjs";
|
|
12
|
-
import { gridRowsLookupSelector, gridRowMaximumTreeDepthSelector, gridRowNodeSelector, gridRowTreeSelector } from "../rows/gridRowsSelector.mjs";
|
|
12
|
+
import { gridDataRowIdsSelector, gridRowsLookupSelector, gridRowMaximumTreeDepthSelector, gridRowNodeSelector, gridRowTreeSelector } from "../rows/gridRowsSelector.mjs";
|
|
13
|
+
import { GRID_ROOT_GROUP_ID } from "../rows/gridRowsUtils.mjs";
|
|
13
14
|
import { gridRowSelectionManagerSelector, gridRowSelectionStateSelector, gridRowSelectionCountSelector, gridRowSelectionIdsSelector } from "./gridRowSelectionSelector.mjs";
|
|
14
15
|
import { gridFocusCellSelector } from "../focus/gridFocusStateSelector.mjs";
|
|
15
16
|
import { gridExpandedSortedRowIdsSelector, gridFilteredRowsLookupSelector, gridFilterModelSelector, gridQuickFilterValuesSelector } from "../filter/gridFilterSelector.mjs";
|
|
16
17
|
import { GRID_CHECKBOX_SELECTION_COL_DEF, GRID_ACTIONS_COLUMN_TYPE } from "../../../colDef/index.mjs";
|
|
17
18
|
import { GridCellModes } from "../../../models/gridEditRowModel.mjs";
|
|
18
|
-
import { isKeyboardEvent, isNavigationKey } from "../../../utils/keyboardUtils.mjs";
|
|
19
|
+
import { isKeyboardEvent, isNavigationKey, isSelectAllShortcut } from "../../../utils/keyboardUtils.mjs";
|
|
19
20
|
import { getVisibleRows } from "../../utils/useGridVisibleRows.mjs";
|
|
20
21
|
import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from "../../../internals/constants.mjs";
|
|
21
22
|
import { gridClasses } from "../../../constants/gridClasses.mjs";
|
|
@@ -119,6 +120,10 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
119
120
|
if (props.rowSelection === false) {
|
|
120
121
|
return false;
|
|
121
122
|
}
|
|
123
|
+
const rowNode = gridRowNodeSelector(apiRef, id);
|
|
124
|
+
if (rowNode?.type === 'footer' || rowNode?.type === 'pinnedRow' || rowNode?.type === 'skeletonRow') {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
122
127
|
|
|
123
128
|
// If `keepNonExistentRowsSelected` is true, we might run in a case where row selectability is checked for a row that does not exist.
|
|
124
129
|
// Since that row was previously selected (otherwise it would not be checked at this point), we return true.
|
|
@@ -128,10 +133,6 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
128
133
|
if (propIsRowSelectable && !propIsRowSelectable(apiRef.current.getRowParams(id))) {
|
|
129
134
|
return false;
|
|
130
135
|
}
|
|
131
|
-
const rowNode = gridRowNodeSelector(apiRef, id);
|
|
132
|
-
if (rowNode?.type === 'footer' || rowNode?.type === 'pinnedRow') {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
136
|
return true;
|
|
136
137
|
}, [apiRef, props.rowSelection, props.keepNonExistentRowsSelected, propIsRowSelectable]);
|
|
137
138
|
const getSelectedRows = React.useCallback(() => gridRowSelectionIdsSelector(apiRef), [apiRef]);
|
|
@@ -307,6 +308,7 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
307
308
|
* EVENTS
|
|
308
309
|
*/
|
|
309
310
|
const isFirstRender = React.useRef(true);
|
|
311
|
+
const previousRowsLookupRef = React.useRef(null);
|
|
310
312
|
const removeOutdatedSelection = React.useCallback(() => {
|
|
311
313
|
if (isFirstRender.current) {
|
|
312
314
|
return;
|
|
@@ -315,6 +317,11 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
315
317
|
const rowsLookup = gridRowsLookupSelector(apiRef);
|
|
316
318
|
const rowTree = gridRowTreeSelector(apiRef);
|
|
317
319
|
const filteredRowsLookup = gridFilteredRowsLookupSelector(apiRef);
|
|
320
|
+
const previousRowsLookup = previousRowsLookupRef.current;
|
|
321
|
+
previousRowsLookupRef.current = rowsLookup;
|
|
322
|
+
// Read the depth at call time. The event that runs this callback can fire in the same
|
|
323
|
+
// tick as the state update that changes the depth, before the render-scope value updates.
|
|
324
|
+
const isNested = gridRowMaximumTreeDepthSelector(apiRef) > 1;
|
|
318
325
|
const isNonExistent = id => {
|
|
319
326
|
if (props.filterMode === 'server') {
|
|
320
327
|
return !rowsLookup[id];
|
|
@@ -342,6 +349,32 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
342
349
|
return node.children.some(childId => isOrHasSelectableDescendant(childId));
|
|
343
350
|
};
|
|
344
351
|
let hasChanged = false;
|
|
352
|
+
|
|
353
|
+
// Rows can load after their parent was selected (server-side lazy loading).
|
|
354
|
+
// They arrive unselected, so the parent re-derivation below would drop the parent.
|
|
355
|
+
// With `rowSelectionPropagation.descendants`, the rows that appeared since the last run
|
|
356
|
+
// inherit the selection state of their closest previously known ancestor instead.
|
|
357
|
+
if (isNested && props.rowSelectionPropagation?.descendants && currentSelection.type === 'include' && currentSelection.ids.size > 0 && previousRowsLookup !== null && previousRowsLookup !== rowsLookup) {
|
|
358
|
+
const isNewRow = id => rowsLookup[id] !== undefined && previousRowsLookup[id] === undefined;
|
|
359
|
+
const inheritsSelection = id => {
|
|
360
|
+
const parentId = rowTree[id]?.parent;
|
|
361
|
+
if (parentId == null || parentId === GRID_ROOT_GROUP_ID) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
if (selectionManager.has(parentId)) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
// A row that appeared together with its parent reads the state further up the chain.
|
|
368
|
+
return isNewRow(parentId) ? inheritsSelection(parentId) : false;
|
|
369
|
+
};
|
|
370
|
+
const dataRowIds = gridDataRowIdsSelector(apiRef);
|
|
371
|
+
for (const id of dataRowIds) {
|
|
372
|
+
if (isNewRow(id) && filteredRowsLookup[id] !== false && inheritsSelection(id) && apiRef.current.isRowSelectable(id)) {
|
|
373
|
+
selectionManager.select(id);
|
|
374
|
+
hasChanged = true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
345
378
|
for (const id of currentSelection.ids) {
|
|
346
379
|
if (isNonExistent(id)) {
|
|
347
380
|
if (props.keepNonExistentRowsSelected) {
|
|
@@ -374,9 +407,10 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
374
407
|
// For nested data, on row tree updation (filtering, adding rows, etc.) when the selection is
|
|
375
408
|
// not empty, we need to re-run scanning of the tree to propagate the selection changes
|
|
376
409
|
// Example: A parent whose de-selected children are filtered out should now be selected
|
|
377
|
-
|
|
378
|
-
//
|
|
379
|
-
|
|
410
|
+
// With a data source, an `exclude` model is left alone: the grid only knows the loaded
|
|
411
|
+
// subset of the rows, so rewriting the model into an `include` of the known rows would
|
|
412
|
+
// drop the "rows that load later are selected too" semantics of the select-all state.
|
|
413
|
+
const shouldReapplyPropagation = isNested && props.rowSelectionPropagation?.parents && (newSelectionModel.type === 'exclude' ? props.dataSource === undefined : newSelectionModel.ids.size > 0);
|
|
380
414
|
if (hasChanged || shouldReapplyPropagation) {
|
|
381
415
|
if (shouldReapplyPropagation) {
|
|
382
416
|
if (newSelectionModel.type === 'exclude') {
|
|
@@ -390,13 +424,20 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
390
424
|
}
|
|
391
425
|
apiRef.current.selectRows(selectedRowIds, true, true);
|
|
392
426
|
} else {
|
|
393
|
-
|
|
427
|
+
// `getPropagatedRowSelectionModel` reads the sibling selection state from the model
|
|
428
|
+
// built above, so the rows selected during this run count for the parent derivation.
|
|
429
|
+
// `selectRows` would read the state from before this run instead.
|
|
430
|
+
const propagatedModel = apiRef.current.getPropagatedRowSelectionModel(newSelectionModel);
|
|
431
|
+
const isSelectionValid = propagatedModel.ids.size < 2 || canHaveMultipleSelection;
|
|
432
|
+
if (isSelectionValid && (propagatedModel.type !== currentSelection.type || propagatedModel.ids.size !== currentSelection.ids.size || !Array.from(propagatedModel.ids).every(id => currentSelection.ids.has(id)))) {
|
|
433
|
+
apiRef.current.setRowSelectionModel(propagatedModel, 'multipleRowsSelection');
|
|
434
|
+
}
|
|
394
435
|
}
|
|
395
436
|
} else {
|
|
396
437
|
apiRef.current.setRowSelectionModel(newSelectionModel, 'multipleRowsSelection');
|
|
397
438
|
}
|
|
398
439
|
}
|
|
399
|
-
}, [apiRef,
|
|
440
|
+
}, [apiRef, props.rowSelectionPropagation?.parents, props.rowSelectionPropagation?.descendants, props.keepNonExistentRowsSelected, props.filterMode, props.dataSource, canHaveMultipleSelection, getRowsToBeSelected]);
|
|
400
441
|
const handleSingleRowSelection = React.useCallback((id, event) => {
|
|
401
442
|
const hasCtrlKey = event.metaKey || event.ctrlKey;
|
|
402
443
|
|
|
@@ -534,7 +575,7 @@ You need to upgrade to DataGridPro or DataGridPremium component to unlock multip
|
|
|
534
575
|
handleSingleRowSelection(params.id, event);
|
|
535
576
|
return;
|
|
536
577
|
}
|
|
537
|
-
if (
|
|
578
|
+
if (isSelectAllShortcut(event)) {
|
|
538
579
|
event.preventDefault();
|
|
539
580
|
if (canHaveMultipleSelection) {
|
|
540
581
|
toggleAllRows(true);
|
|
@@ -67,6 +67,11 @@ const checkboxPropsSelector = exports.checkboxPropsSelector = (0, _createSelecto
|
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
const node = rowTree[itemToTraverseId];
|
|
70
|
+
// Skeleton rows are placeholders for rows that are not loaded yet.
|
|
71
|
+
// They have no selection state, so they do not affect the parent checkbox state.
|
|
72
|
+
if (node?.type === 'skeletonRow') {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
70
75
|
if (node?.type === 'group') {
|
|
71
76
|
node.children.forEach(traverseDescendants);
|
|
72
77
|
}
|
|
@@ -60,6 +60,11 @@ export const checkboxPropsSelector = createSelector(gridColumnDefinitionsSelecto
|
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
const node = rowTree[itemToTraverseId];
|
|
63
|
+
// Skeleton rows are placeholders for rows that are not loaded yet.
|
|
64
|
+
// They have no selection state, so they do not affect the parent checkbox state.
|
|
65
|
+
if (node?.type === 'skeletonRow') {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
63
68
|
if (node?.type === 'group') {
|
|
64
69
|
node.children.forEach(traverseDescendants);
|
|
65
70
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RefObject } from '@mui/x-internals/types';
|
|
2
|
-
import type { GridAutoGeneratedGroupNode, GridAutoGeneratedPinnedRowNode, GridColDef, GridFooterNode, GridGroupNode, GridRowId, GridRowIdGetter, GridRowModel, GridRowModelUpdate, GridRowTreeConfig, GridSkeletonRowNode, GridTreeNode, GridValidRowModel } from "../../../models/index.mjs";
|
|
2
|
+
import type { GridAutoGeneratedGroupNode, GridAutoGeneratedPinnedRowNode, GridColDef, GridFooterNode, GridGroupNode, GridRowId, GridRowIdGetter, GridRowModel, GridRowModelUpdate, GridRowModelReplace, GridRowTreeConfig, GridSkeletonRowNode, GridTreeNode, GridValidRowModel } from "../../../models/index.mjs";
|
|
3
3
|
import type { DataGridProcessedProps } from "../../../models/props/DataGridProps.mjs";
|
|
4
4
|
import type { GridApiCommunity, GridPrivateApiCommunity } from "../../../models/api/gridApiCommunity.mjs";
|
|
5
5
|
import type { GridRowsInternalCache, GridRowsState, GridRowTreeCreationParams } from "./gridRowsInterfaces.mjs";
|
|
@@ -43,6 +43,40 @@ export declare const getRowsStateFromCache: ({
|
|
|
43
43
|
export declare const isAutogeneratedRow: (row: GridRowModel) => boolean;
|
|
44
44
|
export declare const isAutogeneratedRowNode: (rowNode: GridTreeNode) => rowNode is GridFooterNode | GridSkeletonRowNode | GridAutoGeneratedGroupNode | GridAutoGeneratedPinnedRowNode;
|
|
45
45
|
export declare const getTreeNodeDescendants: (tree: GridRowTreeConfig, parentId: GridRowId, skipAutoGeneratedRows: boolean, directChildrenOnly?: boolean) => GridRowId[];
|
|
46
|
+
export declare const isReplaceUpdate: (update: GridRowModelUpdate | GridRowModelReplace) => update is GridRowModelReplace;
|
|
47
|
+
/**
|
|
48
|
+
* Extracts the replacement row from a `{ _action: 'replace', row }` update.
|
|
49
|
+
* The marker lives on the throwaway envelope, so the row object itself is never touched.
|
|
50
|
+
* @param {GridRowModelReplace} update The update provided to `updateRows()`.
|
|
51
|
+
* @returns {GridRowModel} The object to store as the row, verbatim.
|
|
52
|
+
*/
|
|
53
|
+
export declare const getReplaceRow: (update: GridRowModelReplace) => GridRowModel;
|
|
54
|
+
/**
|
|
55
|
+
* Merges a partial update into the existing row.
|
|
56
|
+
* Prototype-preservation rules:
|
|
57
|
+
* 1. If partialRow is a class instance (non-plain object), use its prototype — the
|
|
58
|
+
* caller is passing in a fully-constructed instance and owns the prototype chain.
|
|
59
|
+
* 2. If partialRow is a plain object (Object.prototype), prefer oldRow's prototype so
|
|
60
|
+
* that a partial-update object does not silently discard the existing row's class.
|
|
61
|
+
* 3. If both are plain objects the result is also a plain object (no-op).
|
|
62
|
+
*
|
|
63
|
+
* Note: private class fields (#field syntax) cannot be preserved through a spread-style
|
|
64
|
+
* merge because the brand check is tied to the original instance. Rows that rely on
|
|
65
|
+
* private fields must either supply a fully-constructed instance as partialRow (rule 1
|
|
66
|
+
* above) or use `_action: 'replace'` rather than a plain-object partial update.
|
|
67
|
+
* @param {GridRowModel} oldRow The row currently stored in the lookup.
|
|
68
|
+
* @param {GridRowModelUpdate} partialRow The update to merge into it.
|
|
69
|
+
* @returns {GridRowModel} The merged row.
|
|
70
|
+
*/
|
|
71
|
+
export declare const mergeRowUpdate: (oldRow: GridRowModel, partialRow: GridRowModelUpdate) => GridRowModel;
|
|
72
|
+
/**
|
|
73
|
+
* Warns when a replace demotes a class instance to a plain object, which happens when the
|
|
74
|
+
* replacement was built with a spread instead of being passed as-is.
|
|
75
|
+
* Dev-only, does nothing in production.
|
|
76
|
+
* @param {GridRowModel | undefined} oldRow The row being replaced, if any.
|
|
77
|
+
* @param {GridRowModel} replacement The row provided in a `{ _action: 'replace', row }` update.
|
|
78
|
+
*/
|
|
79
|
+
export declare const warnIfReplaceLosesPrototype: (oldRow: GridRowModel | undefined, replacement: GridRowModel) => void;
|
|
46
80
|
export declare const updateCacheWithNewRows: ({
|
|
47
81
|
previousCache,
|
|
48
82
|
getRowId,
|
|
@@ -51,10 +85,10 @@ export declare const updateCacheWithNewRows: ({
|
|
|
51
85
|
}: {
|
|
52
86
|
previousCache: GridRowsInternalCache;
|
|
53
87
|
getRowId: DataGridProcessedProps['getRowId'];
|
|
54
|
-
updates: GridRowModelUpdate
|
|
88
|
+
updates: Array<GridRowModelUpdate | GridRowModelReplace>;
|
|
55
89
|
groupKeys?: string[];
|
|
56
90
|
}) => GridRowsInternalCache;
|
|
57
91
|
export declare const minimalContentHeight = "var(--DataGrid-overlayHeight, calc(var(--height) * 2))";
|
|
58
|
-
export declare function computeRowsUpdates(apiRef: RefObject<GridApiCommunity>, updates: GridRowModelUpdate
|
|
92
|
+
export declare function computeRowsUpdates(apiRef: RefObject<GridApiCommunity>, updates: Array<GridRowModelUpdate | GridRowModelReplace>, getRowId: DataGridProcessedProps['getRowId']): (GridRowModelReplace<GridValidRowModel> | GridRowModelUpdate)[];
|
|
59
93
|
export declare const getValidRowHeight: (rowHeightProp: any, defaultRowHeight: number, warningMessage: string) => number;
|
|
60
94
|
export declare const rowHeightWarning: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RefObject } from '@mui/x-internals/types';
|
|
2
|
-
import type { GridAutoGeneratedGroupNode, GridAutoGeneratedPinnedRowNode, GridColDef, GridFooterNode, GridGroupNode, GridRowId, GridRowIdGetter, GridRowModel, GridRowModelUpdate, GridRowTreeConfig, GridSkeletonRowNode, GridTreeNode, GridValidRowModel } from "../../../models/index.js";
|
|
2
|
+
import type { GridAutoGeneratedGroupNode, GridAutoGeneratedPinnedRowNode, GridColDef, GridFooterNode, GridGroupNode, GridRowId, GridRowIdGetter, GridRowModel, GridRowModelUpdate, GridRowModelReplace, GridRowTreeConfig, GridSkeletonRowNode, GridTreeNode, GridValidRowModel } from "../../../models/index.js";
|
|
3
3
|
import type { DataGridProcessedProps } from "../../../models/props/DataGridProps.js";
|
|
4
4
|
import type { GridApiCommunity, GridPrivateApiCommunity } from "../../../models/api/gridApiCommunity.js";
|
|
5
5
|
import type { GridRowsInternalCache, GridRowsState, GridRowTreeCreationParams } from "./gridRowsInterfaces.js";
|
|
@@ -43,6 +43,40 @@ export declare const getRowsStateFromCache: ({
|
|
|
43
43
|
export declare const isAutogeneratedRow: (row: GridRowModel) => boolean;
|
|
44
44
|
export declare const isAutogeneratedRowNode: (rowNode: GridTreeNode) => rowNode is GridFooterNode | GridSkeletonRowNode | GridAutoGeneratedGroupNode | GridAutoGeneratedPinnedRowNode;
|
|
45
45
|
export declare const getTreeNodeDescendants: (tree: GridRowTreeConfig, parentId: GridRowId, skipAutoGeneratedRows: boolean, directChildrenOnly?: boolean) => GridRowId[];
|
|
46
|
+
export declare const isReplaceUpdate: (update: GridRowModelUpdate | GridRowModelReplace) => update is GridRowModelReplace;
|
|
47
|
+
/**
|
|
48
|
+
* Extracts the replacement row from a `{ _action: 'replace', row }` update.
|
|
49
|
+
* The marker lives on the throwaway envelope, so the row object itself is never touched.
|
|
50
|
+
* @param {GridRowModelReplace} update The update provided to `updateRows()`.
|
|
51
|
+
* @returns {GridRowModel} The object to store as the row, verbatim.
|
|
52
|
+
*/
|
|
53
|
+
export declare const getReplaceRow: (update: GridRowModelReplace) => GridRowModel;
|
|
54
|
+
/**
|
|
55
|
+
* Merges a partial update into the existing row.
|
|
56
|
+
* Prototype-preservation rules:
|
|
57
|
+
* 1. If partialRow is a class instance (non-plain object), use its prototype — the
|
|
58
|
+
* caller is passing in a fully-constructed instance and owns the prototype chain.
|
|
59
|
+
* 2. If partialRow is a plain object (Object.prototype), prefer oldRow's prototype so
|
|
60
|
+
* that a partial-update object does not silently discard the existing row's class.
|
|
61
|
+
* 3. If both are plain objects the result is also a plain object (no-op).
|
|
62
|
+
*
|
|
63
|
+
* Note: private class fields (#field syntax) cannot be preserved through a spread-style
|
|
64
|
+
* merge because the brand check is tied to the original instance. Rows that rely on
|
|
65
|
+
* private fields must either supply a fully-constructed instance as partialRow (rule 1
|
|
66
|
+
* above) or use `_action: 'replace'` rather than a plain-object partial update.
|
|
67
|
+
* @param {GridRowModel} oldRow The row currently stored in the lookup.
|
|
68
|
+
* @param {GridRowModelUpdate} partialRow The update to merge into it.
|
|
69
|
+
* @returns {GridRowModel} The merged row.
|
|
70
|
+
*/
|
|
71
|
+
export declare const mergeRowUpdate: (oldRow: GridRowModel, partialRow: GridRowModelUpdate) => GridRowModel;
|
|
72
|
+
/**
|
|
73
|
+
* Warns when a replace demotes a class instance to a plain object, which happens when the
|
|
74
|
+
* replacement was built with a spread instead of being passed as-is.
|
|
75
|
+
* Dev-only, does nothing in production.
|
|
76
|
+
* @param {GridRowModel | undefined} oldRow The row being replaced, if any.
|
|
77
|
+
* @param {GridRowModel} replacement The row provided in a `{ _action: 'replace', row }` update.
|
|
78
|
+
*/
|
|
79
|
+
export declare const warnIfReplaceLosesPrototype: (oldRow: GridRowModel | undefined, replacement: GridRowModel) => void;
|
|
46
80
|
export declare const updateCacheWithNewRows: ({
|
|
47
81
|
previousCache,
|
|
48
82
|
getRowId,
|
|
@@ -51,10 +85,10 @@ export declare const updateCacheWithNewRows: ({
|
|
|
51
85
|
}: {
|
|
52
86
|
previousCache: GridRowsInternalCache;
|
|
53
87
|
getRowId: DataGridProcessedProps['getRowId'];
|
|
54
|
-
updates: GridRowModelUpdate
|
|
88
|
+
updates: Array<GridRowModelUpdate | GridRowModelReplace>;
|
|
55
89
|
groupKeys?: string[];
|
|
56
90
|
}) => GridRowsInternalCache;
|
|
57
91
|
export declare const minimalContentHeight = "var(--DataGrid-overlayHeight, calc(var(--height) * 2))";
|
|
58
|
-
export declare function computeRowsUpdates(apiRef: RefObject<GridApiCommunity>, updates: GridRowModelUpdate
|
|
92
|
+
export declare function computeRowsUpdates(apiRef: RefObject<GridApiCommunity>, updates: Array<GridRowModelUpdate | GridRowModelReplace>, getRowId: DataGridProcessedProps['getRowId']): (GridRowModelReplace<GridValidRowModel> | GridRowModelUpdate)[];
|
|
59
93
|
export declare const getValidRowHeight: (rowHeightProp: any, defaultRowHeight: number, warningMessage: string) => number;
|
|
60
94
|
export declare const rowHeightWarning: string;
|
|
@@ -7,9 +7,10 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
7
7
|
exports.buildRootGroup = exports.GRID_ROOT_GROUP_ID = exports.GRID_ID_AUTOGENERATED = void 0;
|
|
8
8
|
exports.checkGridRowIdIsValid = checkGridRowIdIsValid;
|
|
9
9
|
exports.computeRowsUpdates = computeRowsUpdates;
|
|
10
|
-
exports.updateCacheWithNewRows = exports.rowHeightWarning = exports.minimalContentHeight = exports.isAutogeneratedRowNode = exports.isAutogeneratedRow = exports.getValidRowHeight = exports.getTreeNodeDescendants = exports.getTopLevelRowCount = exports.getRowsStateFromCache = exports.getRowValue = exports.getRowIdFromRowModel = exports.createRowsInternalCache = void 0;
|
|
10
|
+
exports.warnIfReplaceLosesPrototype = exports.updateCacheWithNewRows = exports.rowHeightWarning = exports.minimalContentHeight = exports.mergeRowUpdate = exports.isReplaceUpdate = exports.isAutogeneratedRowNode = exports.isAutogeneratedRow = exports.getValidRowHeight = exports.getTreeNodeDescendants = exports.getTopLevelRowCount = exports.getRowsStateFromCache = exports.getRowValue = exports.getRowIdFromRowModel = exports.getReplaceRow = exports.createRowsInternalCache = void 0;
|
|
11
11
|
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
12
12
|
var _formatErrorMessage2 = _interopRequireDefault(require("@mui/x-internals/formatErrorMessage"));
|
|
13
|
+
var _warning = require("@mui/x-internals/warning");
|
|
13
14
|
var _gridRowsSelector = require("./gridRowsSelector");
|
|
14
15
|
const GRID_ROOT_GROUP_ID = exports.GRID_ROOT_GROUP_ID = `auto-generated-group-node-root`;
|
|
15
16
|
const GRID_ID_AUTOGENERATED = exports.GRID_ID_AUTOGENERATED = Symbol('mui.id_autogenerated');
|
|
@@ -178,6 +179,83 @@ const getTreeNodeDescendants = (tree, parentId, skipAutoGeneratedRows, directChi
|
|
|
178
179
|
return validDescendants;
|
|
179
180
|
};
|
|
180
181
|
exports.getTreeNodeDescendants = getTreeNodeDescendants;
|
|
182
|
+
const isReplaceUpdate = update =>
|
|
183
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
184
|
+
update._action === 'replace';
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Extracts the replacement row from a `{ _action: 'replace', row }` update.
|
|
188
|
+
* The marker lives on the throwaway envelope, so the row object itself is never touched.
|
|
189
|
+
* @param {GridRowModelReplace} update The update provided to `updateRows()`.
|
|
190
|
+
* @returns {GridRowModel} The object to store as the row, verbatim.
|
|
191
|
+
*/
|
|
192
|
+
exports.isReplaceUpdate = isReplaceUpdate;
|
|
193
|
+
const getReplaceRow = update => {
|
|
194
|
+
const {
|
|
195
|
+
row
|
|
196
|
+
} = update;
|
|
197
|
+
if (row == null) {
|
|
198
|
+
throw new Error(process.env.NODE_ENV !== "production" ? `MUI X Data Grid: A row update with \`_action: 'replace'\` was provided without a \`row\` property.
|
|
199
|
+
Without it, the Data Grid does not know which object to store for that row.
|
|
200
|
+
Provide the replacement object in the \`row\` property, for example \`updateRows([{ _action: 'replace', row }])\`.
|
|
201
|
+
For more detail, see https://mui.com/x/react-data-grid/row-updates/.` : (0, _formatErrorMessage2.default)(304));
|
|
202
|
+
}
|
|
203
|
+
return row;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Merges a partial update into the existing row.
|
|
208
|
+
* Prototype-preservation rules:
|
|
209
|
+
* 1. If partialRow is a class instance (non-plain object), use its prototype — the
|
|
210
|
+
* caller is passing in a fully-constructed instance and owns the prototype chain.
|
|
211
|
+
* 2. If partialRow is a plain object (Object.prototype), prefer oldRow's prototype so
|
|
212
|
+
* that a partial-update object does not silently discard the existing row's class.
|
|
213
|
+
* 3. If both are plain objects the result is also a plain object (no-op).
|
|
214
|
+
*
|
|
215
|
+
* Note: private class fields (#field syntax) cannot be preserved through a spread-style
|
|
216
|
+
* merge because the brand check is tied to the original instance. Rows that rely on
|
|
217
|
+
* private fields must either supply a fully-constructed instance as partialRow (rule 1
|
|
218
|
+
* above) or use `_action: 'replace'` rather than a plain-object partial update.
|
|
219
|
+
* @param {GridRowModel} oldRow The row currently stored in the lookup.
|
|
220
|
+
* @param {GridRowModelUpdate} partialRow The update to merge into it.
|
|
221
|
+
* @returns {GridRowModel} The merged row.
|
|
222
|
+
*/
|
|
223
|
+
exports.getReplaceRow = getReplaceRow;
|
|
224
|
+
const mergeRowUpdate = (oldRow, partialRow) => {
|
|
225
|
+
const partialRowProto = Object.getPrototypeOf(partialRow);
|
|
226
|
+
const isPartialRowPlain = partialRowProto === Object.prototype || partialRowProto === null;
|
|
227
|
+
const proto = isPartialRowPlain ? Object.getPrototypeOf(oldRow) : partialRowProto;
|
|
228
|
+
const merged = Object.create(proto);
|
|
229
|
+
Object.assign(merged, oldRow, partialRow);
|
|
230
|
+
return merged;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Warns when a replace demotes a class instance to a plain object, which happens when the
|
|
235
|
+
* replacement was built with a spread instead of being passed as-is.
|
|
236
|
+
* Dev-only, does nothing in production.
|
|
237
|
+
* @param {GridRowModel | undefined} oldRow The row being replaced, if any.
|
|
238
|
+
* @param {GridRowModel} replacement The row provided in a `{ _action: 'replace', row }` update.
|
|
239
|
+
*/
|
|
240
|
+
exports.mergeRowUpdate = mergeRowUpdate;
|
|
241
|
+
const warnIfReplaceLosesPrototype = (oldRow, replacement) => {
|
|
242
|
+
if (process.env.NODE_ENV === 'production' || !oldRow) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const replacementProto = Object.getPrototypeOf(replacement);
|
|
246
|
+
if (replacementProto !== Object.prototype && replacementProto !== null) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const oldRowProto = Object.getPrototypeOf(oldRow);
|
|
250
|
+
if (oldRowProto === Object.prototype || oldRowProto === null) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// The message must stay free of row-specific details: `warnOnce()` caches by message, so
|
|
255
|
+
// interpolating the id would log once per row and grow the cache with every update.
|
|
256
|
+
(0, _warning.warnOnce)(["MUI X Data Grid: A plain object was provided as the `row` of a `_action: 'replace'` update, but the row it replaces is a class instance.", "Building the replacement with a spread (`{ _action: 'replace', row: { ...row } }`) creates a plain object, dropping the prototype chain and the private fields that `_action: 'replace'` exists to preserve.", "Pass the instance itself instead, for example `{ _action: 'replace', row }`.", 'For more detail, see https://mui.com/x/react-data-grid/row-updates/.']);
|
|
257
|
+
};
|
|
258
|
+
exports.warnIfReplaceLosesPrototype = warnIfReplaceLosesPrototype;
|
|
181
259
|
const updateCacheWithNewRows = ({
|
|
182
260
|
previousCache,
|
|
183
261
|
getRowId,
|
|
@@ -191,10 +269,28 @@ const updateCacheWithNewRows = ({
|
|
|
191
269
|
// Remove duplicate updates.
|
|
192
270
|
// A server can batch updates, and send several updates for the same row in one fn call.
|
|
193
271
|
const uniqueUpdates = new Map();
|
|
272
|
+
// Ids whose accumulated update is stored as the row instead of being merged into the
|
|
273
|
+
// existing one. The flag lives outside the row object so that it is never mutated.
|
|
274
|
+
const replaceIds = new Set();
|
|
194
275
|
updates.forEach(update => {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
276
|
+
// A replace is provided as a `{ _action: 'replace', row }` envelope: the marker stays
|
|
277
|
+
// on the throwaway envelope and the inner row is used verbatim, so that the object
|
|
278
|
+
// provided by the caller is the one that ends up in the lookup.
|
|
279
|
+
const isReplace = isReplaceUpdate(update);
|
|
280
|
+
const row = isReplace ? getReplaceRow(update) : update;
|
|
281
|
+
const id = getRowIdFromRowModel(row, getRowId, 'A row was provided without id when calling updateRows():');
|
|
282
|
+
if (isReplace) {
|
|
283
|
+
// A replace resets whatever has been accumulated for that id.
|
|
284
|
+
uniqueUpdates.set(id, row);
|
|
285
|
+
replaceIds.add(id);
|
|
286
|
+
} else if (uniqueUpdates.has(id)) {
|
|
287
|
+
const accumulatedUpdate = uniqueUpdates.get(id);
|
|
288
|
+
if (process.env.NODE_ENV !== 'production' && replaceIds.has(id)) {
|
|
289
|
+
(0, _warning.warnOnce)(["MUI X Data Grid: A row was provided with `_action: 'replace'` but it is not the last update for that row in this batch.", 'The remaining updates are merged onto the replacement, so the row keeps its prototype but is neither the same object nor carries its `#private` fields, which a merge cannot copy.', 'Make the replace the last update for that row if `apiRef.current.getRow(id)` must return the object you passed in.', 'For more detail, see https://mui.com/x/react-data-grid/row-updates/.']);
|
|
290
|
+
}
|
|
291
|
+
// The merge keeps the accumulated entry's prototype and the id stays in `replaceIds`,
|
|
292
|
+
// so the result is still applied as a replace.
|
|
293
|
+
uniqueUpdates.set(id, mergeRowUpdate(accumulatedUpdate, update));
|
|
198
294
|
} else {
|
|
199
295
|
uniqueUpdates.set(id, update);
|
|
200
296
|
}
|
|
@@ -246,6 +342,15 @@ const updateCacheWithNewRows = ({
|
|
|
246
342
|
}
|
|
247
343
|
const oldRow = dataRowIdToModelLookup[id];
|
|
248
344
|
|
|
345
|
+
// Action === "replace"
|
|
346
|
+
// The bookkeeping is the same as for "modify" / "insert" below, only the way the row
|
|
347
|
+
// is stored differs: the object is stored as-is so that its prototype chain, private
|
|
348
|
+
// fields (#field) and reference identity are all preserved.
|
|
349
|
+
const isReplace = replaceIds.has(id);
|
|
350
|
+
if (isReplace) {
|
|
351
|
+
warnIfReplaceLosesPrototype(oldRow, partialRow);
|
|
352
|
+
}
|
|
353
|
+
|
|
249
354
|
// Action === "modify"
|
|
250
355
|
if (oldRow) {
|
|
251
356
|
// If the data row has been removed since the last state update,
|
|
@@ -261,23 +366,7 @@ const updateCacheWithNewRows = ({
|
|
|
261
366
|
}
|
|
262
367
|
|
|
263
368
|
// Update the data row lookups.
|
|
264
|
-
|
|
265
|
-
// 1. If partialRow is a class instance (non-plain object), use its prototype — the
|
|
266
|
-
// caller is passing in a fully-constructed instance and owns the prototype chain.
|
|
267
|
-
// 2. If partialRow is a plain object (Object.prototype), prefer oldRow's prototype so
|
|
268
|
-
// that a partial-update object does not silently discard the existing row's class.
|
|
269
|
-
// 3. If both are plain objects the result is also a plain object (no-op).
|
|
270
|
-
//
|
|
271
|
-
// Note: private class fields (#field syntax) cannot be preserved through a spread-style
|
|
272
|
-
// merge because the brand check is tied to the original instance. Rows that rely on
|
|
273
|
-
// private fields must supply a fully-constructed instance as partialRow (rule 1 above)
|
|
274
|
-
// rather than a plain-object partial update.
|
|
275
|
-
const partialRowProto = Object.getPrototypeOf(partialRow);
|
|
276
|
-
const isPartialRowPlain = partialRowProto === Object.prototype || partialRowProto === null;
|
|
277
|
-
const proto = isPartialRowPlain ? Object.getPrototypeOf(oldRow) : partialRowProto;
|
|
278
|
-
const merged = Object.create(proto);
|
|
279
|
-
Object.assign(merged, oldRow, partialRow);
|
|
280
|
-
dataRowIdToModelLookup[id] = merged;
|
|
369
|
+
dataRowIdToModelLookup[id] = isReplace ? partialRow : mergeRowUpdate(oldRow, partialRow);
|
|
281
370
|
return;
|
|
282
371
|
}
|
|
283
372
|
|
|
@@ -319,16 +408,24 @@ const minimalContentHeight = exports.minimalContentHeight = 'var(--DataGrid-over
|
|
|
319
408
|
function computeRowsUpdates(apiRef, updates, getRowId) {
|
|
320
409
|
const nonPinnedRowsUpdates = [];
|
|
321
410
|
updates.forEach(update => {
|
|
322
|
-
const
|
|
411
|
+
const isReplace = isReplaceUpdate(update);
|
|
412
|
+
const row = isReplace ? getReplaceRow(update) : update;
|
|
413
|
+
const id = getRowIdFromRowModel(row, getRowId, 'A row was provided without id when calling updateRows():');
|
|
323
414
|
const rowNode = (0, _gridRowsSelector.gridRowNodeSelector)(apiRef, id);
|
|
324
415
|
if (rowNode?.type === 'pinnedRow') {
|
|
325
416
|
// @ts-ignore because otherwise `release:build` doesn't work
|
|
326
417
|
const pinnedRowsCache = apiRef.current.caches.pinnedRows;
|
|
327
418
|
const prevModel = pinnedRowsCache.idLookup[id];
|
|
328
419
|
if (prevModel) {
|
|
329
|
-
|
|
420
|
+
if (isReplace) {
|
|
421
|
+
warnIfReplaceLosesPrototype(prevModel, row);
|
|
422
|
+
pinnedRowsCache.idLookup[id] = row;
|
|
423
|
+
} else {
|
|
424
|
+
pinnedRowsCache.idLookup[id] = mergeRowUpdate(prevModel, update);
|
|
425
|
+
}
|
|
330
426
|
}
|
|
331
427
|
} else {
|
|
428
|
+
// The envelope is passed through untouched, `updateCacheWithNewRows` unwraps it.
|
|
332
429
|
nonPinnedRowsUpdates.push(update);
|
|
333
430
|
}
|
|
334
431
|
});
|