@redsift/table-pro 12.5.4
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/CONTRIBUTING.md +435 -0
- package/_internal/BaseComponents.js +3 -0
- package/_internal/BaseComponents.js.map +1 -0
- package/_internal/BaseIconButton.js +126 -0
- package/_internal/BaseIconButton.js.map +1 -0
- package/_internal/BaseTextField.js +26 -0
- package/_internal/BaseTextField.js.map +1 -0
- package/_internal/DataGrid.js +2 -0
- package/_internal/DataGrid.js.map +1 -0
- package/_internal/DataGrid2.js +507 -0
- package/_internal/DataGrid2.js.map +1 -0
- package/_internal/GridToolbarFilterSemanticField.js +2 -0
- package/_internal/GridToolbarFilterSemanticField.js.map +1 -0
- package/_internal/GridToolbarFilterSemanticField2.js +6962 -0
- package/_internal/GridToolbarFilterSemanticField2.js.map +1 -0
- package/_internal/Pagination.js +2 -0
- package/_internal/Pagination.js.map +1 -0
- package/_internal/ServerSideControlledPagination.js +324 -0
- package/_internal/ServerSideControlledPagination.js.map +1 -0
- package/_internal/StatefulDataGrid.js +2 -0
- package/_internal/StatefulDataGrid.js.map +1 -0
- package/_internal/StatefulDataGrid2.js +3237 -0
- package/_internal/StatefulDataGrid2.js.map +1 -0
- package/_internal/TextCell.js +2 -0
- package/_internal/TextCell.js.map +1 -0
- package/_internal/TextCell2.js +66 -0
- package/_internal/TextCell2.js.map +1 -0
- package/_internal/Toolbar.js +2 -0
- package/_internal/Toolbar.js.map +1 -0
- package/_internal/Toolbar2.js +102 -0
- package/_internal/Toolbar2.js.map +1 -0
- package/_internal/ToolbarWrapper.js +2 -0
- package/_internal/ToolbarWrapper.js.map +1 -0
- package/_internal/ToolbarWrapper2.js +53 -0
- package/_internal/ToolbarWrapper2.js.map +1 -0
- package/_internal/_rollupPluginBabelHelpers.js +93 -0
- package/_internal/_rollupPluginBabelHelpers.js.map +1 -0
- package/_internal/useControlledDatagridState.js +170 -0
- package/_internal/useControlledDatagridState.js.map +1 -0
- package/index.d.ts +1223 -0
- package/index.js +721 -0
- package/index.js.map +1 -0
- package/package.json +96 -0
- package/style/index.css +7 -0
|
@@ -0,0 +1,3237 @@
|
|
|
1
|
+
import { _ as _objectSpread2, a as _objectWithoutProperties, b as _extends } from './_rollupPluginBabelHelpers.js';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import React__default, { useCallback, useEffect, useRef, useMemo, forwardRef, useState } from 'react';
|
|
4
|
+
import { createTheme, ThemeProvider as ThemeProvider$1 } from '@mui/material/styles';
|
|
5
|
+
import classNames from 'classnames';
|
|
6
|
+
import { LicenseInfo } from '@mui/x-license';
|
|
7
|
+
import { Icon, useTheme, RedsiftColorBlueN, RedsiftColorNeutralXDarkGrey, RedsiftColorNeutralWhite, ThemeProvider } from '@redsift/design-system';
|
|
8
|
+
import { getGridNumericOperators as getGridNumericOperators$1, GridFilterInputValue, GridFilterInputSingleSelect, GridFilterInputMultipleValue, GridFilterInputMultipleSingleSelect, getGridStringOperators as getGridStringOperators$1, getGridBooleanOperators, getGridDateOperators, getGridSingleSelectOperators, GridLogicOperator, useGridApiRef, gridFilteredSortedRowEntriesSelector, gridFilteredSortedRowIdsSelector, DataGridPro } from '@mui/x-data-grid-pro';
|
|
9
|
+
import { u as useControlledDatagridState, S as StyledDataGrid } from './useControlledDatagridState.js';
|
|
10
|
+
import Box from '@mui/material/Box';
|
|
11
|
+
import TextField from '@mui/material/TextField';
|
|
12
|
+
import { mdiSync } from '@redsift/icons';
|
|
13
|
+
import { decompressFromEncodedURIComponent, compressToEncodedURIComponent } from 'lz-string';
|
|
14
|
+
import { n as normalizeRowSelectionModel, o as onServerSideSelectionStatusChange, g as getSelectionCount, i as isRowSelected, S as ServerSideControlledPagination, C as ControlledPagination } from './ServerSideControlledPagination.js';
|
|
15
|
+
import { B as BaseButton, a as BaseCheckbox, c as BaseIconButton, b as BaseIcon } from './BaseIconButton.js';
|
|
16
|
+
|
|
17
|
+
const SUBMIT_FILTER_STROKE_TIME = 500;
|
|
18
|
+
const InputNumberInterval = props => {
|
|
19
|
+
var _item$value;
|
|
20
|
+
const {
|
|
21
|
+
item,
|
|
22
|
+
applyValue,
|
|
23
|
+
focusElementRef = null
|
|
24
|
+
} = props;
|
|
25
|
+
const filterTimeout = React.useRef();
|
|
26
|
+
const [filterValueState, setFilterValueState] = React.useState((_item$value = item.value) !== null && _item$value !== void 0 ? _item$value : '');
|
|
27
|
+
const [applying, setIsApplying] = React.useState(false);
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
return () => {
|
|
30
|
+
clearTimeout(filterTimeout.current);
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
React.useEffect(() => {
|
|
34
|
+
var _item$value2;
|
|
35
|
+
const itemValue = (_item$value2 = item.value) !== null && _item$value2 !== void 0 ? _item$value2 : [undefined, undefined];
|
|
36
|
+
setFilterValueState(itemValue);
|
|
37
|
+
}, [item.value]);
|
|
38
|
+
const updateFilterValue = (lowerBound, upperBound) => {
|
|
39
|
+
clearTimeout(filterTimeout.current);
|
|
40
|
+
setFilterValueState([lowerBound, upperBound]);
|
|
41
|
+
setIsApplying(true);
|
|
42
|
+
filterTimeout.current = setTimeout(() => {
|
|
43
|
+
setIsApplying(false);
|
|
44
|
+
applyValue(_objectSpread2(_objectSpread2({}, item), {}, {
|
|
45
|
+
value: [lowerBound, upperBound]
|
|
46
|
+
}));
|
|
47
|
+
}, SUBMIT_FILTER_STROKE_TIME);
|
|
48
|
+
};
|
|
49
|
+
const handleUpperFilterChange = event => {
|
|
50
|
+
const newUpperBound = event.target.value;
|
|
51
|
+
updateFilterValue(filterValueState[0], newUpperBound);
|
|
52
|
+
};
|
|
53
|
+
const handleLowerFilterChange = event => {
|
|
54
|
+
const newLowerBound = event.target.value;
|
|
55
|
+
updateFilterValue(newLowerBound, filterValueState[1]);
|
|
56
|
+
};
|
|
57
|
+
return /*#__PURE__*/React.createElement(Box, {
|
|
58
|
+
sx: {
|
|
59
|
+
display: 'inline-flex',
|
|
60
|
+
flexDirection: 'row',
|
|
61
|
+
alignItems: 'end',
|
|
62
|
+
height: 48,
|
|
63
|
+
pl: '20px'
|
|
64
|
+
}
|
|
65
|
+
}, /*#__PURE__*/React.createElement(TextField, {
|
|
66
|
+
name: "lower-bound-input",
|
|
67
|
+
placeholder: "From",
|
|
68
|
+
label: "From",
|
|
69
|
+
variant: "standard",
|
|
70
|
+
value: Number(filterValueState[0]),
|
|
71
|
+
onChange: handleLowerFilterChange,
|
|
72
|
+
type: "number",
|
|
73
|
+
inputRef: focusElementRef,
|
|
74
|
+
sx: {
|
|
75
|
+
mr: 2
|
|
76
|
+
}
|
|
77
|
+
}), /*#__PURE__*/React.createElement(TextField, {
|
|
78
|
+
name: "upper-bound-input",
|
|
79
|
+
placeholder: "To",
|
|
80
|
+
label: "To",
|
|
81
|
+
variant: "standard",
|
|
82
|
+
value: Number(filterValueState[1]),
|
|
83
|
+
onChange: handleUpperFilterChange,
|
|
84
|
+
type: "number",
|
|
85
|
+
InputProps: applying ? {
|
|
86
|
+
endAdornment: /*#__PURE__*/React.createElement(Icon, {
|
|
87
|
+
icon: mdiSync
|
|
88
|
+
})
|
|
89
|
+
} : {}
|
|
90
|
+
}));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const isBetweenOperator = {
|
|
94
|
+
label: 'is between',
|
|
95
|
+
value: 'isBetween',
|
|
96
|
+
getApplyFilterFn: filterItem => {
|
|
97
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
if (!Array.isArray(filterItem.value) || filterItem.value.length !== 2) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (filterItem.value[0] == null || filterItem.value[1] == null) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
if (typeof filterItem.value[0] !== 'number' || typeof filterItem.value[1] !== 'number') {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return value => {
|
|
110
|
+
return value !== null && value !== undefined && filterItem.value[0] <= value && value <= filterItem.value[1];
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
InputComponent: InputNumberInterval
|
|
114
|
+
};
|
|
115
|
+
const IS_BETWEEN = isBetweenOperator;
|
|
116
|
+
|
|
117
|
+
const getGridNumericOperators = () => [...getGridNumericOperators$1(), IS_BETWEEN];
|
|
118
|
+
|
|
119
|
+
const doesNotContain = {
|
|
120
|
+
label: 'does not contain',
|
|
121
|
+
value: 'doesNotContain',
|
|
122
|
+
getApplyFilterFn: filterItem => {
|
|
123
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return value => {
|
|
127
|
+
if (filterItem.value.length === 0) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (value == null) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
if (String(value).indexOf(filterItem.value) !== -1) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
InputComponent: GridFilterInputValue
|
|
140
|
+
};
|
|
141
|
+
const DOES_NOT_CONTAIN = doesNotContain;
|
|
142
|
+
|
|
143
|
+
const doesNotEndWithOperator = {
|
|
144
|
+
label: 'does not end with',
|
|
145
|
+
value: 'doesNotEndWith',
|
|
146
|
+
getApplyFilterFn: filterItem => {
|
|
147
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return value => {
|
|
151
|
+
if (value == null) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
return !String(value).endsWith(filterItem.value);
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
InputComponent: GridFilterInputValue
|
|
158
|
+
};
|
|
159
|
+
const DOES_NOT_END_WITH = doesNotEndWithOperator;
|
|
160
|
+
|
|
161
|
+
const doesNotEqual = {
|
|
162
|
+
label: 'does not equal',
|
|
163
|
+
value: 'doesNotEqual',
|
|
164
|
+
getApplyFilterFn: filterItem => {
|
|
165
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
return value => {
|
|
169
|
+
if (value == null) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
if (String(value) === filterItem.value) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
return true;
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
InputComponent: GridFilterInputValue
|
|
179
|
+
};
|
|
180
|
+
const DOES_NOT_EQUAL = doesNotEqual;
|
|
181
|
+
|
|
182
|
+
const doesNotHaveOperator = {
|
|
183
|
+
label: "doesn't have",
|
|
184
|
+
value: 'doesNotHave',
|
|
185
|
+
getApplyFilterFn: filterItem => {
|
|
186
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
return value => {
|
|
190
|
+
if (value == null) {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
const cellValues = Array.isArray(value) ? value : [value];
|
|
194
|
+
return !cellValues.map(value => String(value)).includes(filterItem.value);
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
InputComponent: GridFilterInputValue
|
|
198
|
+
};
|
|
199
|
+
const DOES_NOT_HAVE = doesNotHaveOperator;
|
|
200
|
+
const DOES_NOT_HAVE_WITH_SELECT = _objectSpread2(_objectSpread2({}, DOES_NOT_HAVE), {}, {
|
|
201
|
+
InputComponent: GridFilterInputSingleSelect
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const doesNotStartWithOperator = {
|
|
205
|
+
label: 'does not start with',
|
|
206
|
+
value: 'doesNotStartWith',
|
|
207
|
+
getApplyFilterFn: filterItem => {
|
|
208
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
return value => {
|
|
212
|
+
if (value == null) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return !String(value).startsWith(filterItem.value);
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
InputComponent: GridFilterInputValue
|
|
219
|
+
};
|
|
220
|
+
const DOES_NOT_START_WITH = doesNotStartWithOperator;
|
|
221
|
+
|
|
222
|
+
const hasOperator = {
|
|
223
|
+
label: 'has',
|
|
224
|
+
value: 'has',
|
|
225
|
+
getApplyFilterFn: filterItem => {
|
|
226
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
return value => {
|
|
230
|
+
if (value == null) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
const cellValues = Array.isArray(value) ? value : [value];
|
|
234
|
+
return cellValues.map(value => String(value)).includes(filterItem.value);
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
InputComponent: GridFilterInputValue
|
|
238
|
+
};
|
|
239
|
+
const HAS = hasOperator;
|
|
240
|
+
const HAS_WITH_SELECT = _objectSpread2(_objectSpread2({}, HAS), {}, {
|
|
241
|
+
InputComponent: GridFilterInputSingleSelect
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const hasOnlyOperator = {
|
|
245
|
+
label: 'has only',
|
|
246
|
+
value: 'hasOnly',
|
|
247
|
+
getApplyFilterFn: filterItem => {
|
|
248
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
return value => {
|
|
252
|
+
if (value == null) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
const cellValues = Array.isArray(value) ? value : [value];
|
|
256
|
+
return cellValues.length === 1 && String(cellValues[0]) === filterItem.value;
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
InputComponent: GridFilterInputValue
|
|
260
|
+
};
|
|
261
|
+
const HAS_ONLY = hasOnlyOperator;
|
|
262
|
+
const HAS_ONLY_WITH_SELECT = _objectSpread2(_objectSpread2({}, HAS_ONLY), {}, {
|
|
263
|
+
InputComponent: GridFilterInputSingleSelect
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const isOperator = {
|
|
267
|
+
label: 'is',
|
|
268
|
+
value: 'is',
|
|
269
|
+
getApplyFilterFn: filterItem => {
|
|
270
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
return value => {
|
|
274
|
+
if (value == null) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
if (Array.isArray(value)) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
return String(value) === filterItem.value;
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
InputComponent: GridFilterInputValue
|
|
284
|
+
};
|
|
285
|
+
const IS = isOperator;
|
|
286
|
+
const IS_WITH_SELECT = _objectSpread2(_objectSpread2({}, IS), {}, {
|
|
287
|
+
InputComponent: GridFilterInputSingleSelect
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const isNotOperator = {
|
|
291
|
+
label: 'is not',
|
|
292
|
+
value: 'isNot',
|
|
293
|
+
getApplyFilterFn: filterItem => {
|
|
294
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
return value => {
|
|
298
|
+
if (value == null) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
if (Array.isArray(value)) {
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
return String(value) !== filterItem.value;
|
|
305
|
+
};
|
|
306
|
+
},
|
|
307
|
+
InputComponent: GridFilterInputValue
|
|
308
|
+
};
|
|
309
|
+
const IS_NOT = isNotOperator;
|
|
310
|
+
const IS_NOT_WITH_SELECT = _objectSpread2(_objectSpread2({}, IS_NOT), {}, {
|
|
311
|
+
InputComponent: GridFilterInputSingleSelect
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const containsAnyOfOperator = {
|
|
315
|
+
label: 'contains any of',
|
|
316
|
+
value: 'containsAnyOf',
|
|
317
|
+
getApplyFilterFn: filterItem => {
|
|
318
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
return value => {
|
|
322
|
+
if (filterItem.value.length === 0) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
if (value == null) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
const paramValues = Array.isArray(value) ? value : [value];
|
|
329
|
+
let match = false;
|
|
330
|
+
filterItem.value.forEach(filteredValue => {
|
|
331
|
+
paramValues.forEach(paramValue => {
|
|
332
|
+
if (String(paramValue).indexOf(filteredValue) !== -1) {
|
|
333
|
+
match = true;
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
return match;
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
InputComponent: GridFilterInputMultipleValue
|
|
341
|
+
};
|
|
342
|
+
const CONTAINS_ANY_OF = containsAnyOfOperator;
|
|
343
|
+
|
|
344
|
+
const doesNotContainAnyOfOperator = {
|
|
345
|
+
label: 'does not contain any of',
|
|
346
|
+
value: 'doesNotContainAnyOf',
|
|
347
|
+
getApplyFilterFn: filterItem => {
|
|
348
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
return value => {
|
|
352
|
+
if (filterItem.value.length === 0) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
if (value == null) {
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
const paramValues = Array.isArray(value) ? value : [value];
|
|
359
|
+
for (const filteredValue of filterItem.value) {
|
|
360
|
+
for (const paramValue of paramValues) {
|
|
361
|
+
if (String(paramValue).indexOf(filteredValue) !== -1) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return true;
|
|
367
|
+
};
|
|
368
|
+
},
|
|
369
|
+
InputComponent: GridFilterInputMultipleValue
|
|
370
|
+
};
|
|
371
|
+
const DOES_NOT_CONTAIN_ANY_OF = doesNotContainAnyOfOperator;
|
|
372
|
+
|
|
373
|
+
const doesNotEndWithAnyOfOperator = {
|
|
374
|
+
label: 'does not end with any of',
|
|
375
|
+
value: 'doesNotEndWithAnyOf',
|
|
376
|
+
getApplyFilterFn: filterItem => {
|
|
377
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
return value => {
|
|
381
|
+
if (filterItem.value.length === 0) {
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
if (value == null) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
const paramValues = Array.isArray(value) ? value : [value];
|
|
388
|
+
for (const filteredValue of filterItem.value) {
|
|
389
|
+
for (const paramValue of paramValues) {
|
|
390
|
+
if (String(paramValue).endsWith(filteredValue)) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return true;
|
|
396
|
+
};
|
|
397
|
+
},
|
|
398
|
+
InputComponent: GridFilterInputMultipleValue
|
|
399
|
+
};
|
|
400
|
+
const DOES_NOT_END_WITH_ANY_OF = doesNotEndWithAnyOfOperator;
|
|
401
|
+
|
|
402
|
+
const doesNotHaveAnyOf = {
|
|
403
|
+
label: "doesn't have any of",
|
|
404
|
+
value: 'doesNotHaveAnyOf',
|
|
405
|
+
getApplyFilterFn: filterItem => {
|
|
406
|
+
if (!filterItem.field || !filterItem.value || !Array.isArray(filterItem.value) || filterItem.value.length === 0) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
return value => {
|
|
410
|
+
if (value == null) {
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
const cellValues = Array.isArray(value) ? value : [value];
|
|
414
|
+
|
|
415
|
+
// Return true only if none of the filter values are in the cell values
|
|
416
|
+
return filterItem.value.every(filterVal => !cellValues.map(value => String(value)).includes(filterVal));
|
|
417
|
+
};
|
|
418
|
+
},
|
|
419
|
+
InputComponent: GridFilterInputMultipleValue
|
|
420
|
+
};
|
|
421
|
+
const DOES_NOT_HAVE_ANY_OF = doesNotHaveAnyOf;
|
|
422
|
+
const DOES_NOT_HAVE_ANY_OF_WITH_SELECT = _objectSpread2(_objectSpread2({}, DOES_NOT_HAVE_ANY_OF), {}, {
|
|
423
|
+
InputComponent: GridFilterInputMultipleSingleSelect
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const doesNotStartWithAnyOfOperator = {
|
|
427
|
+
label: 'does not start with any of',
|
|
428
|
+
value: 'doesNotStartWithAnyOf',
|
|
429
|
+
getApplyFilterFn: filterItem => {
|
|
430
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
return value => {
|
|
434
|
+
if (filterItem.value.length === 0) {
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
if (value == null) {
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
const paramValues = Array.isArray(value) ? value : [value];
|
|
441
|
+
for (const filteredValue of filterItem.value) {
|
|
442
|
+
for (const paramValue of paramValues) {
|
|
443
|
+
if (String(paramValue).startsWith(filteredValue)) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return true;
|
|
449
|
+
};
|
|
450
|
+
},
|
|
451
|
+
InputComponent: GridFilterInputMultipleValue
|
|
452
|
+
};
|
|
453
|
+
const DOES_NOT_START_WITH_ANY_OF = doesNotStartWithAnyOfOperator;
|
|
454
|
+
|
|
455
|
+
const endsWithAnyOfOperator = {
|
|
456
|
+
label: 'ends with any of',
|
|
457
|
+
value: 'endsWithAnyOf',
|
|
458
|
+
getApplyFilterFn: filterItem => {
|
|
459
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
return value => {
|
|
463
|
+
if (filterItem.value.length === 0) {
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
if (value == null) {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
const paramValues = Array.isArray(value) ? value : [value];
|
|
470
|
+
let match = false;
|
|
471
|
+
filterItem.value.forEach(filteredValue => {
|
|
472
|
+
paramValues.forEach(paramValue => {
|
|
473
|
+
if (String(paramValue).endsWith(filteredValue)) {
|
|
474
|
+
match = true;
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
return match;
|
|
479
|
+
};
|
|
480
|
+
},
|
|
481
|
+
InputComponent: GridFilterInputMultipleValue
|
|
482
|
+
};
|
|
483
|
+
const ENDS_WITH_ANY_OF = endsWithAnyOfOperator;
|
|
484
|
+
|
|
485
|
+
const hasAllOfOperator = {
|
|
486
|
+
label: 'has all of',
|
|
487
|
+
value: 'hasAllOf',
|
|
488
|
+
getApplyFilterFn: filterItem => {
|
|
489
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
return value => {
|
|
493
|
+
if (filterItem.value.length === 0) {
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
if (value == null) {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
const cellValues = Array.isArray(value) ? value : [value];
|
|
500
|
+
const cellStrings = cellValues.map(value => String(value));
|
|
501
|
+
const filterItemValues = Array.isArray(filterItem.value) ? filterItem.value : [filterItem.value];
|
|
502
|
+
return filterItemValues.every(v => cellStrings.includes(v));
|
|
503
|
+
};
|
|
504
|
+
},
|
|
505
|
+
InputComponent: GridFilterInputMultipleValue
|
|
506
|
+
};
|
|
507
|
+
const HAS_ALL_OF = hasAllOfOperator;
|
|
508
|
+
const HAS_ALL_OF_WITH_SELECT = _objectSpread2(_objectSpread2({}, HAS_ALL_OF), {}, {
|
|
509
|
+
InputComponent: GridFilterInputMultipleSingleSelect
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const hasAnyOfOperator = {
|
|
513
|
+
label: 'has any of',
|
|
514
|
+
value: 'hasAnyOf',
|
|
515
|
+
getApplyFilterFn: filterItem => {
|
|
516
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
return value => {
|
|
520
|
+
if (filterItem.value.length === 0) {
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
if (value == null) {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
const cellValues = Array.isArray(value) ? value : [value];
|
|
527
|
+
const filterItemValues = Array.isArray(filterItem.value) ? filterItem.value : [filterItem.value];
|
|
528
|
+
return filterItemValues.some(v => cellValues.map(value => String(value)).includes(v));
|
|
529
|
+
};
|
|
530
|
+
},
|
|
531
|
+
InputComponent: GridFilterInputMultipleValue
|
|
532
|
+
};
|
|
533
|
+
const HAS_ANY_OF = hasAnyOfOperator;
|
|
534
|
+
const HAS_ANY_OF_WITH_SELECT = _objectSpread2(_objectSpread2({}, HAS_ANY_OF), {}, {
|
|
535
|
+
InputComponent: GridFilterInputMultipleSingleSelect
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
const isAnyOfOperator = {
|
|
539
|
+
label: 'is any of',
|
|
540
|
+
value: 'isAnyOf',
|
|
541
|
+
getApplyFilterFn: filterItem => {
|
|
542
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
return value => {
|
|
546
|
+
if (filterItem.value.length === 0) {
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
if (value == null) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
const paramValues = Array.isArray(value) ? value : [value];
|
|
553
|
+
for (const paramValue of paramValues) {
|
|
554
|
+
if (filterItem.value.includes(String(paramValue))) {
|
|
555
|
+
return true;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return false;
|
|
559
|
+
};
|
|
560
|
+
},
|
|
561
|
+
InputComponent: GridFilterInputMultipleValue
|
|
562
|
+
};
|
|
563
|
+
const IS_ANY_OF = isAnyOfOperator;
|
|
564
|
+
const IS_ANY_OF_WITH_SELECT = _objectSpread2(_objectSpread2({}, IS_ANY_OF), {}, {
|
|
565
|
+
InputComponent: GridFilterInputMultipleSingleSelect
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
const isNotAnyOfOperator = {
|
|
569
|
+
label: 'is not any of',
|
|
570
|
+
value: 'isNotAnyOf',
|
|
571
|
+
getApplyFilterFn: filterItem => {
|
|
572
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
return value => {
|
|
576
|
+
if (filterItem.value.length === 0) {
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
if (value == null) {
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
const paramValues = Array.isArray(value) ? value : [value];
|
|
583
|
+
for (const paramValue of paramValues) {
|
|
584
|
+
if (filterItem.value.includes(String(paramValue))) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return true;
|
|
589
|
+
};
|
|
590
|
+
},
|
|
591
|
+
InputComponent: GridFilterInputMultipleValue
|
|
592
|
+
};
|
|
593
|
+
const IS_NOT_ANY_OF = isNotAnyOfOperator;
|
|
594
|
+
const IS_NOT_ANY_OF_WITH_SELECT = _objectSpread2(_objectSpread2({}, IS_NOT_ANY_OF), {}, {
|
|
595
|
+
InputComponent: GridFilterInputMultipleSingleSelect
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
const startsWithAnyOfOperator = {
|
|
599
|
+
label: 'starts with any of',
|
|
600
|
+
value: 'startsWithAnyOf',
|
|
601
|
+
getApplyFilterFn: filterItem => {
|
|
602
|
+
if (!filterItem.field || !filterItem.value || !filterItem.operator) {
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
return value => {
|
|
606
|
+
if (filterItem.value.length === 0) {
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
if (value == null) {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
const paramValues = Array.isArray(value) ? value : [value];
|
|
613
|
+
let match = false;
|
|
614
|
+
filterItem.value.forEach(filteredValue => {
|
|
615
|
+
paramValues.forEach(paramValue => {
|
|
616
|
+
if (String(paramValue).startsWith(filteredValue)) {
|
|
617
|
+
match = true;
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
return match;
|
|
622
|
+
};
|
|
623
|
+
},
|
|
624
|
+
InputComponent: GridFilterInputMultipleValue
|
|
625
|
+
};
|
|
626
|
+
const STARTS_WITH_ANY_OF = startsWithAnyOfOperator;
|
|
627
|
+
|
|
628
|
+
const getGridStringArrayOperators = () => [CONTAINS_ANY_OF, DOES_NOT_CONTAIN_ANY_OF, ENDS_WITH_ANY_OF, DOES_NOT_END_WITH_ANY_OF, IS_ANY_OF, IS_NOT_ANY_OF, STARTS_WITH_ANY_OF, DOES_NOT_START_WITH_ANY_OF];
|
|
629
|
+
const getGridStringArrayOperatorsWithSelect = () => [IS_WITH_SELECT, IS_NOT_WITH_SELECT, IS_ANY_OF_WITH_SELECT, IS_NOT_ANY_OF_WITH_SELECT, STARTS_WITH_ANY_OF, DOES_NOT_START_WITH_ANY_OF, ENDS_WITH_ANY_OF, DOES_NOT_END_WITH_ANY_OF, CONTAINS_ANY_OF, DOES_NOT_CONTAIN_ANY_OF];
|
|
630
|
+
const getGridStringArrayOperatorsWithSelectOnStringArrayColumns = () => [HAS_WITH_SELECT, HAS_ANY_OF_WITH_SELECT, HAS_ALL_OF_WITH_SELECT, HAS_ONLY_WITH_SELECT, DOES_NOT_HAVE_WITH_SELECT, DOES_NOT_HAVE_ANY_OF_WITH_SELECT];
|
|
631
|
+
|
|
632
|
+
const getGridStringOperators = () => [...getGridStringOperators$1().filter(operator => !['isAnyOf'].includes(operator.value)), DOES_NOT_CONTAIN, DOES_NOT_EQUAL, DOES_NOT_START_WITH, DOES_NOT_END_WITH, ...getGridStringArrayOperators()];
|
|
633
|
+
|
|
634
|
+
const isEmptyOperator = {
|
|
635
|
+
label: 'is empty',
|
|
636
|
+
value: 'isEmpty',
|
|
637
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
638
|
+
getApplyFilterFn: _filterItem => {
|
|
639
|
+
return value => {
|
|
640
|
+
if (value == null) {
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
if (Array.isArray(value)) {
|
|
644
|
+
return value.length === 0;
|
|
645
|
+
}
|
|
646
|
+
if (typeof value === 'string') {
|
|
647
|
+
return value.trim() === '';
|
|
648
|
+
}
|
|
649
|
+
return false;
|
|
650
|
+
};
|
|
651
|
+
},
|
|
652
|
+
requiresFilterValue: false
|
|
653
|
+
};
|
|
654
|
+
const isNotEmptyOperator = {
|
|
655
|
+
label: 'is not empty',
|
|
656
|
+
value: 'isNotEmpty',
|
|
657
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
658
|
+
getApplyFilterFn: _filterItem => {
|
|
659
|
+
return value => {
|
|
660
|
+
if (value == null) {
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
if (Array.isArray(value)) {
|
|
664
|
+
return value.length > 0;
|
|
665
|
+
}
|
|
666
|
+
if (typeof value === 'string') {
|
|
667
|
+
return value.trim() !== '';
|
|
668
|
+
}
|
|
669
|
+
return true;
|
|
670
|
+
};
|
|
671
|
+
},
|
|
672
|
+
requiresFilterValue: false
|
|
673
|
+
};
|
|
674
|
+
const ARRAY_IS_EMPTY = isEmptyOperator;
|
|
675
|
+
const ARRAY_IS_NOT_EMPTY = isNotEmptyOperator;
|
|
676
|
+
|
|
677
|
+
// istanbul ignore file
|
|
678
|
+
const operatorList = {
|
|
679
|
+
// Default types
|
|
680
|
+
string: getGridStringOperators$1(),
|
|
681
|
+
number: getGridNumericOperators$1(),
|
|
682
|
+
boolean: getGridBooleanOperators(),
|
|
683
|
+
date: getGridDateOperators(),
|
|
684
|
+
dateTime: getGridDateOperators(true),
|
|
685
|
+
singleSelect: getGridSingleSelectOperators(),
|
|
686
|
+
// Extended types (used by createColumn)
|
|
687
|
+
rsString: getGridStringOperators(),
|
|
688
|
+
rsNumber: getGridNumericOperators(),
|
|
689
|
+
rsSingleSelect: [CONTAINS_ANY_OF, ENDS_WITH_ANY_OF, IS_ANY_OF_WITH_SELECT, IS_NOT_WITH_SELECT, IS_WITH_SELECT, STARTS_WITH_ANY_OF],
|
|
690
|
+
rsSingleSelectWithShortOperatorList: [IS_WITH_SELECT, IS_NOT_WITH_SELECT, IS_ANY_OF_WITH_SELECT],
|
|
691
|
+
rsMultipleSelect: [HAS_WITH_SELECT, HAS_ANY_OF_WITH_SELECT, HAS_ALL_OF_WITH_SELECT, HAS_ONLY_WITH_SELECT, DOES_NOT_HAVE_WITH_SELECT, DOES_NOT_HAVE_ANY_OF_WITH_SELECT, ARRAY_IS_EMPTY, ARRAY_IS_NOT_EMPTY],
|
|
692
|
+
rsMultipleSelectWithShortOperatorList: [HAS_WITH_SELECT, DOES_NOT_HAVE_WITH_SELECT, HAS_ANY_OF_WITH_SELECT],
|
|
693
|
+
// Custom types
|
|
694
|
+
rsStringArray: getGridStringArrayOperators()
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
const PAGINATION_MODEL_KEY = 'paginationModel';
|
|
698
|
+
const FILTER_MODEL_KEY = 'filterModel';
|
|
699
|
+
const SORT_MODEL_KEY = 'sortModel';
|
|
700
|
+
const VISIBILITY_MODEL_KEY = 'visibilityModel';
|
|
701
|
+
const PINNED_COLUMNS = 'pinnedColumns';
|
|
702
|
+
const DIMENSION_MODEL_KEY = 'dimension';
|
|
703
|
+
const FILTER_SEARCH_KEY = 'searchModel';
|
|
704
|
+
const DENSITY_MODEL_KEY = 'densityModel';
|
|
705
|
+
const COLUMN_ORDER_MODEL_KEY = 'columnOrderModel';
|
|
706
|
+
const CATEGORIES = [PAGINATION_MODEL_KEY, FILTER_MODEL_KEY, SORT_MODEL_KEY, VISIBILITY_MODEL_KEY, DIMENSION_MODEL_KEY, FILTER_SEARCH_KEY, PINNED_COLUMNS, DENSITY_MODEL_KEY, COLUMN_ORDER_MODEL_KEY];
|
|
707
|
+
/**
|
|
708
|
+
* Build the localStorage key for a specific grid state category.
|
|
709
|
+
* Consumers can use this to read or clear individual state entries directly.
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* ```ts
|
|
713
|
+
* const key = buildStorageKey({ id: pathname, version: 2, category: DENSITY_MODEL_KEY });
|
|
714
|
+
* localStorage.removeItem(key);
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
717
|
+
const buildStorageKey = _ref => {
|
|
718
|
+
let {
|
|
719
|
+
id,
|
|
720
|
+
version,
|
|
721
|
+
category
|
|
722
|
+
} = _ref;
|
|
723
|
+
return `${id}:${version}:${category}`;
|
|
724
|
+
};
|
|
725
|
+
const clearPreviousVersionStorage = (id, previousLocalStorageVersions) => {
|
|
726
|
+
for (const version of previousLocalStorageVersions) {
|
|
727
|
+
const keysToDelete = CATEGORIES.map(category => buildStorageKey({
|
|
728
|
+
id,
|
|
729
|
+
version,
|
|
730
|
+
category
|
|
731
|
+
}));
|
|
732
|
+
for (const keyToDelete of keysToDelete) {
|
|
733
|
+
try {
|
|
734
|
+
window.localStorage.removeItem(keyToDelete);
|
|
735
|
+
} catch (e) {
|
|
736
|
+
// Ignore
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Clear localStorage keys for all versions from 0 to `maxVersion` (inclusive).
|
|
744
|
+
* Useful when the consumer wants to wipe all persisted grid state regardless of which version is active.
|
|
745
|
+
*/
|
|
746
|
+
const clearAllVersionStorage = (id, maxVersion) => {
|
|
747
|
+
const versions = Array.from({
|
|
748
|
+
length: maxVersion + 1
|
|
749
|
+
}, (_, i) => i);
|
|
750
|
+
clearPreviousVersionStorage(id, versions);
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Fully reset a `StatefulDataGrid` — clear localStorage for the given versions
|
|
755
|
+
* and strip all grid-owned URL params (`_*` and `v=`), preserving any non-grid params.
|
|
756
|
+
*
|
|
757
|
+
* Call from within a component where the router values are available:
|
|
758
|
+
* ```ts
|
|
759
|
+
* const { search, historyReplace } = useMyRouter();
|
|
760
|
+
* resetStatefulDataGridState({ id: 'myGrid', versions: [0, 1, 2], search, historyReplace });
|
|
761
|
+
* ```
|
|
762
|
+
*/
|
|
763
|
+
const resetStatefulDataGridState = _ref2 => {
|
|
764
|
+
let {
|
|
765
|
+
id,
|
|
766
|
+
versions,
|
|
767
|
+
search,
|
|
768
|
+
historyReplace
|
|
769
|
+
} = _ref2;
|
|
770
|
+
clearPreviousVersionStorage(id, versions);
|
|
771
|
+
const params = new URLSearchParams(search);
|
|
772
|
+
const keysToDelete = [];
|
|
773
|
+
params.forEach((_value, key) => {
|
|
774
|
+
if (key.startsWith('_') || key === 'v') {
|
|
775
|
+
keysToDelete.push(key);
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
for (const key of keysToDelete) {
|
|
779
|
+
params.delete(key);
|
|
780
|
+
}
|
|
781
|
+
historyReplace(params.toString());
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
/** Maximum query string length (chars) before compression kicks in. */
|
|
785
|
+
const URL_BUDGET = 2000;
|
|
786
|
+
|
|
787
|
+
/** Prefix added to compressed values so the deserialiser can detect them. */
|
|
788
|
+
const COMPRESSED_PREFIX = '~';
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Priority order for compressing URL params when the total query string exceeds the budget.
|
|
792
|
+
* Params listed first are compressed first (least valuable to read in the URL).
|
|
793
|
+
* The filter aggregate step uses the special key `_filters_aggregate`.
|
|
794
|
+
*/
|
|
795
|
+
const COMPRESSION_PRIORITY = ['_columnOrder', '_columnVisibility', '_pinnedColumnsLeft', '_pinnedColumnsRight', '_filters_aggregate', '_aggregation', '_rowGrouping', '_quickFilterValues', '_pivot'];
|
|
796
|
+
|
|
797
|
+
/** Params that are always short and should never be compressed. */
|
|
798
|
+
const NEVER_COMPRESS = new Set(['_sortColumn', '_pagination', '_density', '_logicOperator', 'v', 'tab']);
|
|
799
|
+
|
|
800
|
+
/** Compress a string value and add the `~` prefix. */
|
|
801
|
+
const compressValue = value => {
|
|
802
|
+
return COMPRESSED_PREFIX + compressToEncodedURIComponent(value);
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
/** If the value starts with `~`, decompress it; otherwise return as-is. */
|
|
806
|
+
const decompressValue = value => {
|
|
807
|
+
if (!value.startsWith(COMPRESSED_PREFIX)) {
|
|
808
|
+
return value;
|
|
809
|
+
}
|
|
810
|
+
const decompressed = decompressFromEncodedURIComponent(value.slice(COMPRESSED_PREFIX.length));
|
|
811
|
+
return decompressed !== null && decompressed !== void 0 ? decompressed : value;
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
/** Check whether a value is compressed (starts with `~`). */
|
|
815
|
+
const isCompressed = value => {
|
|
816
|
+
return value.startsWith(COMPRESSED_PREFIX);
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Calculate the total length of a query string built from a params map.
|
|
821
|
+
* Each entry contributes `key=value&` (the trailing `&` is omitted for the last entry
|
|
822
|
+
* but we include it for simplicity — the +1 per entry is close enough).
|
|
823
|
+
*/
|
|
824
|
+
const calculateQueryStringLength = params => {
|
|
825
|
+
let total = 0;
|
|
826
|
+
for (const [key, value] of params) {
|
|
827
|
+
// key=value& — the last & is off by one but this is a budget heuristic
|
|
828
|
+
total += key.length + 1 + value.length + 1;
|
|
829
|
+
}
|
|
830
|
+
// Subtract the trailing & that doesn't actually exist
|
|
831
|
+
return Math.max(0, total - 1);
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Given a set of filter param keys, aggregate them into a single `_filters` param
|
|
836
|
+
* by JSON-serialising the key-value entries and compressing the result.
|
|
837
|
+
*
|
|
838
|
+
* Returns null if aggregation doesn't save space.
|
|
839
|
+
*/
|
|
840
|
+
const tryAggregateFilters = (params, filterKeys) => {
|
|
841
|
+
if (filterKeys.length === 0) return null;
|
|
842
|
+
|
|
843
|
+
// Build a record of key→value for all filter params
|
|
844
|
+
const filterRecord = {};
|
|
845
|
+
let originalLength = 0;
|
|
846
|
+
for (const key of filterKeys) {
|
|
847
|
+
const value = params.get(key);
|
|
848
|
+
if (value !== undefined) {
|
|
849
|
+
filterRecord[key] = value;
|
|
850
|
+
originalLength += key.length + 1 + value.length + 1; // key=value&
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const json = JSON.stringify(filterRecord);
|
|
855
|
+
const compressed = compressValue(json);
|
|
856
|
+
const aggregatedLength = '_filters'.length + 1 + compressed.length; // _filters=~...
|
|
857
|
+
|
|
858
|
+
if (aggregatedLength < originalLength) {
|
|
859
|
+
return {
|
|
860
|
+
keysToRemove: filterKeys,
|
|
861
|
+
newKey: '_filters',
|
|
862
|
+
newValue: compressed
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
return null;
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Identify which param keys are individual filter params (not system params).
|
|
870
|
+
* Filter params are those that start with `_` but are not well-known system params.
|
|
871
|
+
*/
|
|
872
|
+
const getFilterParamKeys = params => {
|
|
873
|
+
const systemPrefixes = ['_sortColumn', '_pagination', '_density', '_logicOperator', '_quickFilterValues', '_columnVisibility', '_columnOrder', '_pinnedColumnsLeft', '_pinnedColumnsRight', '_rowGrouping', '_aggregation', '_pivot', '_filters'];
|
|
874
|
+
const filterKeys = [];
|
|
875
|
+
for (const key of params.keys()) {
|
|
876
|
+
if (!key.startsWith('_')) continue;
|
|
877
|
+
if (systemPrefixes.some(prefix => key === prefix || key.startsWith(prefix + '['))) {
|
|
878
|
+
// This includes _field[operator,type] style filter keys
|
|
879
|
+
if (key.includes('[')) {
|
|
880
|
+
filterKeys.push(key);
|
|
881
|
+
}
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
// Any other _prefixed key is a filter
|
|
885
|
+
filterKeys.push(key);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// Also include bracket-notation filter keys like _domain[contains,string]
|
|
889
|
+
for (const key of params.keys()) {
|
|
890
|
+
if (key.includes('[') && key.startsWith('_') && !filterKeys.includes(key)) {
|
|
891
|
+
filterKeys.push(key);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return filterKeys;
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Apply budget-based compression to a set of URL params.
|
|
899
|
+
*
|
|
900
|
+
* Algorithm:
|
|
901
|
+
* 1. If total query string length ≤ budget → return as-is
|
|
902
|
+
* 2. Iterate params in COMPRESSION_PRIORITY order
|
|
903
|
+
* 3. For each param, try compressing — only apply if it saves space
|
|
904
|
+
* 4. For the `_filters_aggregate` step, try collapsing all individual filter
|
|
905
|
+
* params into a single `_filters=~blob`
|
|
906
|
+
* 5. Stop as soon as total ≤ budget
|
|
907
|
+
*/
|
|
908
|
+
const applyBudgetCompression = function (params) {
|
|
909
|
+
let budget = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : URL_BUDGET;
|
|
910
|
+
const result = new Map(params);
|
|
911
|
+
let total = calculateQueryStringLength(result);
|
|
912
|
+
if (total <= budget) {
|
|
913
|
+
return result;
|
|
914
|
+
}
|
|
915
|
+
for (const priorityKey of COMPRESSION_PRIORITY) {
|
|
916
|
+
if (total <= budget) break;
|
|
917
|
+
|
|
918
|
+
// Special handling for filter aggregation
|
|
919
|
+
if (priorityKey === '_filters_aggregate') {
|
|
920
|
+
const filterKeys = getFilterParamKeys(result);
|
|
921
|
+
const aggregation = tryAggregateFilters(result, filterKeys);
|
|
922
|
+
if (aggregation) {
|
|
923
|
+
for (const key of aggregation.keysToRemove) {
|
|
924
|
+
result.delete(key);
|
|
925
|
+
}
|
|
926
|
+
result.set(aggregation.newKey, aggregation.newValue);
|
|
927
|
+
total = calculateQueryStringLength(result);
|
|
928
|
+
}
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Standard single-param compression
|
|
933
|
+
if (NEVER_COMPRESS.has(priorityKey)) continue;
|
|
934
|
+
const value = result.get(priorityKey);
|
|
935
|
+
if (value === undefined || isCompressed(value)) continue;
|
|
936
|
+
const compressed = compressValue(value);
|
|
937
|
+
if (compressed.length < value.length) {
|
|
938
|
+
result.set(priorityKey, compressed);
|
|
939
|
+
total = calculateQueryStringLength(result);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return result;
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
// Characters that need URL encoding in display format values
|
|
946
|
+
// These characters could cause CloudFront issues or parsing ambiguity
|
|
947
|
+
const CHARS_TO_ENCODE_IN_VALUES = {
|
|
948
|
+
':': '%3A',
|
|
949
|
+
// Colon - reserved
|
|
950
|
+
',': '%2C',
|
|
951
|
+
// Comma - used as list separator (only in list values)
|
|
952
|
+
'~': '%7E',
|
|
953
|
+
// Tilde - reserved
|
|
954
|
+
';': '%3B',
|
|
955
|
+
// Semicolon - reserved
|
|
956
|
+
'[': '%5B',
|
|
957
|
+
// Square bracket - used in internal format
|
|
958
|
+
']': '%5D',
|
|
959
|
+
// Square bracket - used in internal format
|
|
960
|
+
'(': '%28',
|
|
961
|
+
// Parenthesis - reserved
|
|
962
|
+
')': '%29' // Parenthesis - reserved
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Encodes special characters in a value for display format URLs.
|
|
967
|
+
* This ensures values containing colons, commas, etc. don't break parsing.
|
|
968
|
+
*/
|
|
969
|
+
const encodeDisplayValue = value => {
|
|
970
|
+
let encoded = value;
|
|
971
|
+
for (const [char, replacement] of Object.entries(CHARS_TO_ENCODE_IN_VALUES)) {
|
|
972
|
+
encoded = encoded.split(char).join(replacement);
|
|
973
|
+
}
|
|
974
|
+
return encoded;
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Decodes display format values back to their original form.
|
|
979
|
+
* Only reverses the specific encodings from encodeDisplayValue to ensure symmetry.
|
|
980
|
+
* Does NOT use decodeURIComponent, which would also decode literal percent-encoded
|
|
981
|
+
* sequences in the original data (e.g., a value containing literal "%3A" would be
|
|
982
|
+
* incorrectly decoded to ":").
|
|
983
|
+
*/
|
|
984
|
+
const decodeDisplayValue = value => {
|
|
985
|
+
let decoded = value;
|
|
986
|
+
for (const [char, encoded] of Object.entries(CHARS_TO_ENCODE_IN_VALUES)) {
|
|
987
|
+
decoded = decoded.split(encoded).join(char);
|
|
988
|
+
}
|
|
989
|
+
return decoded;
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Encodes array values for display format URLs.
|
|
994
|
+
* Each value is URL-encoded and joined with commas.
|
|
995
|
+
*/
|
|
996
|
+
const encodeDisplayArrayValue = values => {
|
|
997
|
+
return values.map(v => encodeDisplayValue(v)).join(',');
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Decodes comma-separated display format array values.
|
|
1002
|
+
* Handles URL-encoded commas within individual values.
|
|
1003
|
+
*/
|
|
1004
|
+
const decodeDisplayArrayValue = value => {
|
|
1005
|
+
if (value === '') {
|
|
1006
|
+
return [];
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Split by unencoded commas only (not %2C)
|
|
1010
|
+
// First, temporarily replace encoded commas, then split, then restore
|
|
1011
|
+
const TEMP_COMMA_PLACEHOLDER = '\x00COMMA\x00';
|
|
1012
|
+
const withPlaceholder = value.split('%2C').join(TEMP_COMMA_PLACEHOLDER);
|
|
1013
|
+
const parts = withPlaceholder.split(',');
|
|
1014
|
+
return parts.map(part => {
|
|
1015
|
+
const withCommasRestored = part.split(TEMP_COMMA_PLACEHOLDER).join('%2C');
|
|
1016
|
+
return decodeDisplayValue(withCommasRestored);
|
|
1017
|
+
});
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Converts internal bracket notation to display-friendly dot notation.
|
|
1022
|
+
*
|
|
1023
|
+
* Display format (CloudFront-safe, no brackets):
|
|
1024
|
+
* - `field[operator]=value` → `field.operator=value` (value URL-encoded if needed)
|
|
1025
|
+
* - `field[isAnyOf]=list[a,b,c]` → `field.isAnyOf=a,b,c` (comma-separated, values URL-encoded)
|
|
1026
|
+
* - `_sortColumn=[field,desc]` → `_sortColumn=field.desc`
|
|
1027
|
+
* - `_pagination=[0,25,next]` → `_pagination=0.25.next`
|
|
1028
|
+
* - `_pinnedColumnsLeft=[a,b,c]` → `_pinnedColumnsLeft=a,b,c` (comma-separated)
|
|
1029
|
+
* - `_columnVisibility=[a,b,c]` → `_columnVisibility=a,b,c` (comma-separated)
|
|
1030
|
+
*/
|
|
1031
|
+
const convertToDisplayFormat = search => {
|
|
1032
|
+
if (!search) return search;
|
|
1033
|
+
const cleanSearch = search.startsWith('?') ? search.slice(1) : search;
|
|
1034
|
+
const params = cleanSearch.split('&');
|
|
1035
|
+
const converted = params.map(param => {
|
|
1036
|
+
const eqIndex = param.indexOf('=');
|
|
1037
|
+
if (eqIndex !== -1) {
|
|
1038
|
+
const value = param.slice(eqIndex + 1);
|
|
1039
|
+
// Skip conversion for compressed values — already URL-safe
|
|
1040
|
+
if (isCompressed(value)) return param;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Handle _sortColumn=[field,direction]
|
|
1044
|
+
if (param.startsWith('_sortColumn=')) {
|
|
1045
|
+
const value = param.slice('_sortColumn='.length);
|
|
1046
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
1047
|
+
const inner = value.slice(1, -1);
|
|
1048
|
+
if (inner === '') {
|
|
1049
|
+
return '_sortColumn=';
|
|
1050
|
+
}
|
|
1051
|
+
const [field, direction] = inner.split(',');
|
|
1052
|
+
return `_sortColumn=${field}.${direction}`;
|
|
1053
|
+
}
|
|
1054
|
+
return param;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Handle _pagination=[page,pageSize,direction]
|
|
1058
|
+
if (param.startsWith('_pagination=')) {
|
|
1059
|
+
const value = param.slice('_pagination='.length);
|
|
1060
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
1061
|
+
const inner = value.slice(1, -1);
|
|
1062
|
+
if (inner === '') {
|
|
1063
|
+
return '_pagination=';
|
|
1064
|
+
}
|
|
1065
|
+
const [page, pageSize, direction] = inner.split(',');
|
|
1066
|
+
return `_pagination=${page}.${pageSize}.${direction}`;
|
|
1067
|
+
}
|
|
1068
|
+
return param;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Handle _pinnedColumnsLeft=[a,b,c] and _pinnedColumnsRight=[a,b,c]
|
|
1072
|
+
if (param.startsWith('_pinnedColumnsLeft=') || param.startsWith('_pinnedColumnsRight=')) {
|
|
1073
|
+
const eqIndex = param.indexOf('=');
|
|
1074
|
+
const key = param.slice(0, eqIndex);
|
|
1075
|
+
const value = param.slice(eqIndex + 1);
|
|
1076
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
1077
|
+
const inner = value.slice(1, -1);
|
|
1078
|
+
return `${key}=${inner}`;
|
|
1079
|
+
}
|
|
1080
|
+
return param;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Handle _columnVisibility=[a,b,c]
|
|
1084
|
+
if (param.startsWith('_columnVisibility=')) {
|
|
1085
|
+
const value = param.slice('_columnVisibility='.length);
|
|
1086
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
1087
|
+
const inner = value.slice(1, -1);
|
|
1088
|
+
return `_columnVisibility=${inner}`;
|
|
1089
|
+
}
|
|
1090
|
+
return param;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// Handle _columnOrder=[a,b,c]
|
|
1094
|
+
if (param.startsWith('_columnOrder=')) {
|
|
1095
|
+
const value = param.slice('_columnOrder='.length);
|
|
1096
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
1097
|
+
const inner = value.slice(1, -1);
|
|
1098
|
+
return `_columnOrder=${inner}`;
|
|
1099
|
+
}
|
|
1100
|
+
return param;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// Handle _rowGrouping=[a,b,c]
|
|
1104
|
+
if (param.startsWith('_rowGrouping=')) {
|
|
1105
|
+
const value = param.slice('_rowGrouping='.length);
|
|
1106
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
1107
|
+
const inner = value.slice(1, -1);
|
|
1108
|
+
return `_rowGrouping=${inner}`;
|
|
1109
|
+
}
|
|
1110
|
+
return param;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// _aggregation and _pivot do not use bracket notation — pass through
|
|
1114
|
+
if (param.startsWith('_aggregation=') || param.startsWith('_pivot=')) {
|
|
1115
|
+
return param;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Handle _field[operator,type]=value or _field[operator,type]=list[a,b,c]
|
|
1119
|
+
const bracketMatch = param.match(/^_([^[]+)\[([^\]]+)\]=(.*)$/);
|
|
1120
|
+
if (bracketMatch) {
|
|
1121
|
+
const [, field, operatorAndType, value] = bracketMatch;
|
|
1122
|
+
const [operator] = operatorAndType.split(',');
|
|
1123
|
+
|
|
1124
|
+
// Convert list[a,b,c] to comma-separated with URL-encoded values
|
|
1125
|
+
if (value.startsWith('list[') && value.endsWith(']')) {
|
|
1126
|
+
const listContent = value.slice(5, -1);
|
|
1127
|
+
if (listContent === '') {
|
|
1128
|
+
return `${field}.${operator}=`;
|
|
1129
|
+
}
|
|
1130
|
+
const items = listContent.split(',');
|
|
1131
|
+
return `${field}.${operator}=${encodeDisplayArrayValue(items)}`;
|
|
1132
|
+
}
|
|
1133
|
+
// URL-encode special characters in the value
|
|
1134
|
+
return `${field}.${operator}=${encodeDisplayValue(value)}`;
|
|
1135
|
+
}
|
|
1136
|
+
return param;
|
|
1137
|
+
});
|
|
1138
|
+
return converted.join('&');
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* Converts display-friendly dot notation back to internal bracket notation.
|
|
1143
|
+
*
|
|
1144
|
+
* Internal format (server-side compatible):
|
|
1145
|
+
* - `field.operator=value` → `_field[operator,type]=value`
|
|
1146
|
+
* - `field.isAnyOf=a,b,c` → `_field[isAnyOf,type]=list[a,b,c]`
|
|
1147
|
+
* - `_sortColumn=field.desc` → `_sortColumn=[field,desc]`
|
|
1148
|
+
* - `_pagination=0.25.next` → `_pagination=[0,25,next]`
|
|
1149
|
+
* - `_pinnedColumnsLeft=a,b,c` → `_pinnedColumnsLeft=[a,b,c]`
|
|
1150
|
+
* - `_columnVisibility=a,b,c` → `_columnVisibility=[a,b,c]`
|
|
1151
|
+
*/
|
|
1152
|
+
const convertFromDisplayFormat = (search, columns) => {
|
|
1153
|
+
if (!search) return search;
|
|
1154
|
+
const cleanSearch = search.startsWith('?') ? search.slice(1) : search;
|
|
1155
|
+
const params = cleanSearch.split('&');
|
|
1156
|
+
const converted = params.map(param => {
|
|
1157
|
+
const eqIndex = param.indexOf('=');
|
|
1158
|
+
if (eqIndex !== -1) {
|
|
1159
|
+
const value = param.slice(eqIndex + 1);
|
|
1160
|
+
// Skip conversion for compressed values — already URL-safe
|
|
1161
|
+
if (isCompressed(value)) return param;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// Handle _sortColumn=field.direction or _sortColumn=
|
|
1165
|
+
if (param.startsWith('_sortColumn=')) {
|
|
1166
|
+
const value = param.slice('_sortColumn='.length);
|
|
1167
|
+
if (value === '') {
|
|
1168
|
+
return '_sortColumn=[]';
|
|
1169
|
+
}
|
|
1170
|
+
// If it already has brackets, leave it alone
|
|
1171
|
+
if (value.startsWith('[')) {
|
|
1172
|
+
return param;
|
|
1173
|
+
}
|
|
1174
|
+
const dotIndex = value.indexOf('.');
|
|
1175
|
+
if (dotIndex !== -1) {
|
|
1176
|
+
const field = value.slice(0, dotIndex);
|
|
1177
|
+
const direction = value.slice(dotIndex + 1);
|
|
1178
|
+
return `_sortColumn=[${field},${direction}]`;
|
|
1179
|
+
}
|
|
1180
|
+
return param;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Handle _pagination=page.pageSize.direction or _pagination=
|
|
1184
|
+
if (param.startsWith('_pagination=')) {
|
|
1185
|
+
const value = param.slice('_pagination='.length);
|
|
1186
|
+
if (value === '') {
|
|
1187
|
+
return '_pagination=[]';
|
|
1188
|
+
}
|
|
1189
|
+
// If it already has brackets, leave it alone
|
|
1190
|
+
if (value.startsWith('[')) {
|
|
1191
|
+
return param;
|
|
1192
|
+
}
|
|
1193
|
+
const parts = value.split('.');
|
|
1194
|
+
if (parts.length === 3) {
|
|
1195
|
+
return `_pagination=[${parts.join(',')}]`;
|
|
1196
|
+
}
|
|
1197
|
+
return param;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// Handle _pinnedColumnsLeft=a,b,c and _pinnedColumnsRight=a,b,c
|
|
1201
|
+
if (param.startsWith('_pinnedColumnsLeft=') || param.startsWith('_pinnedColumnsRight=')) {
|
|
1202
|
+
const eqIndex = param.indexOf('=');
|
|
1203
|
+
const key = param.slice(0, eqIndex);
|
|
1204
|
+
const value = param.slice(eqIndex + 1);
|
|
1205
|
+
// If it already has brackets, leave it alone
|
|
1206
|
+
if (value.startsWith('[')) {
|
|
1207
|
+
return param;
|
|
1208
|
+
}
|
|
1209
|
+
return `${key}=[${value}]`;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// Handle _columnVisibility=a,b,c
|
|
1213
|
+
if (param.startsWith('_columnVisibility=')) {
|
|
1214
|
+
const value = param.slice('_columnVisibility='.length);
|
|
1215
|
+
// If it already has brackets, leave it alone
|
|
1216
|
+
if (value.startsWith('[')) {
|
|
1217
|
+
return param;
|
|
1218
|
+
}
|
|
1219
|
+
return `_columnVisibility=[${value}]`;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Handle _columnOrder=a,b,c
|
|
1223
|
+
if (param.startsWith('_columnOrder=')) {
|
|
1224
|
+
const value = param.slice('_columnOrder='.length);
|
|
1225
|
+
if (value.startsWith('[')) {
|
|
1226
|
+
return param;
|
|
1227
|
+
}
|
|
1228
|
+
return `_columnOrder=[${value}]`;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// Handle _rowGrouping=a,b,c
|
|
1232
|
+
if (param.startsWith('_rowGrouping=')) {
|
|
1233
|
+
const value = param.slice('_rowGrouping='.length);
|
|
1234
|
+
if (value.startsWith('[')) {
|
|
1235
|
+
return param;
|
|
1236
|
+
}
|
|
1237
|
+
return `_rowGrouping=[${value}]`;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// _aggregation, _pivot, _filters — pass through (no bracket conversion needed)
|
|
1241
|
+
if (param.startsWith('_aggregation=') || param.startsWith('_pivot=') || param.startsWith('_filters=')) {
|
|
1242
|
+
return param;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// Handle field.operator=value (dot notation for filters)
|
|
1246
|
+
const dotMatch = param.match(/^([^.]+)\.([a-zA-Z_]+)=(.*)$/);
|
|
1247
|
+
if (dotMatch) {
|
|
1248
|
+
const [, field, operator, rawValue] = dotMatch;
|
|
1249
|
+
|
|
1250
|
+
// Get column type for this field
|
|
1251
|
+
const column = columns.find(c => c.field === field);
|
|
1252
|
+
const columnType = (column === null || column === void 0 ? void 0 : column.type) || 'string';
|
|
1253
|
+
|
|
1254
|
+
// Check if this is a list operator
|
|
1255
|
+
const listOps = ['containsAnyOf', 'doesNotContainAnyOf', 'endsWithAnyOf', 'doesNotEndWithAnyOf', 'hasAllOf', 'hasAnyOf', 'doesNotHaveAnyOf', 'isAnyOf', 'isNotAnyOf', 'startsWithAnyOf', 'doesNotStartWithAnyOf', 'isBetween'];
|
|
1256
|
+
if (listOps.includes(operator)) {
|
|
1257
|
+
const items = decodeDisplayArrayValue(rawValue);
|
|
1258
|
+
return `_${field}[${operator},${columnType}]=list[${items.join(',')}]`;
|
|
1259
|
+
}
|
|
1260
|
+
// Decode URL-encoded special characters in the value
|
|
1261
|
+
const decodedValue = decodeDisplayValue(rawValue);
|
|
1262
|
+
return `_${field}[${operator},${columnType}]=${decodedValue}`;
|
|
1263
|
+
}
|
|
1264
|
+
return param;
|
|
1265
|
+
});
|
|
1266
|
+
return converted.join('&');
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Detects if search string is in display format and converts it to internal format.
|
|
1271
|
+
*/
|
|
1272
|
+
const getDecodedSearchFromUrl = (search, columns) => {
|
|
1273
|
+
const searchWithoutLeadingQuestion = search.startsWith('?') ? search.slice(1) : search;
|
|
1274
|
+
|
|
1275
|
+
// Check if it's using display format:
|
|
1276
|
+
// 1. Contains dot notation for filters like 'field.operator='
|
|
1277
|
+
// 2. Has empty _sortColumn= (display format) instead of _sortColumn=[] (internal format)
|
|
1278
|
+
// 3. Has _pinnedColumnsLeft=a,b without brackets
|
|
1279
|
+
// 4. Has _sortColumn=field.desc format
|
|
1280
|
+
// 5. Has _pagination=0.25.next format
|
|
1281
|
+
const hasDotNotationFilter = /[a-zA-Z_]+\.[a-zA-Z_]+=/.test(searchWithoutLeadingQuestion);
|
|
1282
|
+
const hasEmptySortColumn = /(_sortColumn)=(&|$)/.test(searchWithoutLeadingQuestion);
|
|
1283
|
+
const hasSortDotNotation = /_sortColumn=[^&[]+\.[^&]+/.test(searchWithoutLeadingQuestion);
|
|
1284
|
+
const hasPaginationDotNotation = /_pagination=[^&[]+\.[^&]+/.test(searchWithoutLeadingQuestion);
|
|
1285
|
+
const hasPinnedWithoutBrackets = /(_pinnedColumnsLeft|_pinnedColumnsRight)=[^&[]*(&|$)/.test(searchWithoutLeadingQuestion);
|
|
1286
|
+
const hasVisibilityWithoutBrackets = /_columnVisibility=[^&[]*(&|$)/.test(searchWithoutLeadingQuestion);
|
|
1287
|
+
const hasColumnOrderWithoutBrackets = /_columnOrder=[^&[]*(&|$)/.test(searchWithoutLeadingQuestion);
|
|
1288
|
+
const hasRowGroupingWithoutBrackets = /_rowGrouping=[^&[]*(&|$)/.test(searchWithoutLeadingQuestion);
|
|
1289
|
+
const hasBracketNotation = /\[.*\]=/.test(searchWithoutLeadingQuestion);
|
|
1290
|
+
const isDisplayFormat = (hasDotNotationFilter || hasEmptySortColumn || hasSortDotNotation || hasPaginationDotNotation || hasPinnedWithoutBrackets || hasVisibilityWithoutBrackets || hasColumnOrderWithoutBrackets || hasRowGroupingWithoutBrackets) && !hasBracketNotation;
|
|
1291
|
+
if (isDisplayFormat) {
|
|
1292
|
+
return '?' + convertFromDisplayFormat(searchWithoutLeadingQuestion, columns);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// Already in internal bracket format or empty
|
|
1296
|
+
return search;
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* Builds a display-format query string from internal format.
|
|
1301
|
+
*/
|
|
1302
|
+
const buildQueryParamsString = search => {
|
|
1303
|
+
const displaySearch = convertToDisplayFormat(search);
|
|
1304
|
+
return displaySearch.startsWith('?') ? displaySearch : `?${displaySearch}`;
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
/**
|
|
1308
|
+
* Compares two search strings for equality, ignoring parameter order.
|
|
1309
|
+
* This prevents infinite update loops when the same parameters appear in different order.
|
|
1310
|
+
*/
|
|
1311
|
+
const areSearchStringsEqual = (search1, search2) => {
|
|
1312
|
+
const strippedSearch1 = search1.startsWith('?') ? search1.slice(1) : search1;
|
|
1313
|
+
const strippedSearch2 = search2.startsWith('?') ? search2.slice(1) : search2;
|
|
1314
|
+
|
|
1315
|
+
// Quick check: if strings are identical, no need to parse
|
|
1316
|
+
if (strippedSearch1 === strippedSearch2) {
|
|
1317
|
+
return true;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// Parse and sort parameters for order-independent comparison
|
|
1321
|
+
const params1 = strippedSearch1.split('&').filter(Boolean).sort();
|
|
1322
|
+
const params2 = strippedSearch2.split('&').filter(Boolean).sort();
|
|
1323
|
+
if (params1.length !== params2.length) {
|
|
1324
|
+
return false;
|
|
1325
|
+
}
|
|
1326
|
+
return params1.every((param, index) => param === params2[index]);
|
|
1327
|
+
};
|
|
1328
|
+
const decodeValue = value => {
|
|
1329
|
+
if (value === '') {
|
|
1330
|
+
return undefined;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Handle array values encoded as list[item1,item2,...]
|
|
1334
|
+
if (value.startsWith('list[')) {
|
|
1335
|
+
const arrayContent = value.slice(5, -1); // Remove 'list[' and ']'
|
|
1336
|
+
if (arrayContent === '') {
|
|
1337
|
+
return [];
|
|
1338
|
+
}
|
|
1339
|
+
return arrayContent.split(',').filter(item => item);
|
|
1340
|
+
}
|
|
1341
|
+
return value;
|
|
1342
|
+
};
|
|
1343
|
+
const encodeValue = value => {
|
|
1344
|
+
if (value === undefined || value === null) {
|
|
1345
|
+
return '';
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// Handle array values - encode as list[item1,item2,...]
|
|
1349
|
+
if (Array.isArray(value)) {
|
|
1350
|
+
return `list[${value.map(String).join(',')}]`;
|
|
1351
|
+
}
|
|
1352
|
+
return String(value);
|
|
1353
|
+
};
|
|
1354
|
+
const urlSearchParamsToString = searchParams => {
|
|
1355
|
+
let searchString = '';
|
|
1356
|
+
for (const [key, value] of searchParams) {
|
|
1357
|
+
searchString = searchString + `${key}=${value}&`;
|
|
1358
|
+
}
|
|
1359
|
+
return searchString.slice(0, searchString.length - 1);
|
|
1360
|
+
};
|
|
1361
|
+
const numberOperatorEncoder = {
|
|
1362
|
+
'=': 'eq',
|
|
1363
|
+
'!=': 'ne',
|
|
1364
|
+
'>': 'gt',
|
|
1365
|
+
'>=': 'gte',
|
|
1366
|
+
'<': 'lt',
|
|
1367
|
+
'<=': 'lte'
|
|
1368
|
+
};
|
|
1369
|
+
const numberOperatorDecoder = {
|
|
1370
|
+
eq: '=',
|
|
1371
|
+
ne: '!=',
|
|
1372
|
+
gt: '>',
|
|
1373
|
+
gte: '>=',
|
|
1374
|
+
lt: '<',
|
|
1375
|
+
lte: '<='
|
|
1376
|
+
};
|
|
1377
|
+
const isOperatorValueValid = (field, operator, columns) => {
|
|
1378
|
+
const column = columns.find(column => column.field === field);
|
|
1379
|
+
if (!column) {
|
|
1380
|
+
return false;
|
|
1381
|
+
}
|
|
1382
|
+
const columnType = (column === null || column === void 0 ? void 0 : column.type) || 'string';
|
|
1383
|
+
const operators = column.filterOperators || operatorList[columnType];
|
|
1384
|
+
if (!operators) {
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1387
|
+
return !!operators.find(op => columnType === 'number' && Object.keys(numberOperatorEncoder).includes(op.value) ? numberOperatorEncoder[op.value] === operator : op.value === operator);
|
|
1388
|
+
};
|
|
1389
|
+
const listOperators = ['containsAnyOf', 'doesNotContainAnyOf', 'endsWithAnyOf', 'doesNotEndWithAnyOf', 'hasAllOf', 'hasAnyOf', 'doesNotHaveAnyOf', 'isAnyOf', 'isNotAnyOf', 'startsWithAnyOf', 'doesNotStartWithAnyOf', 'isBetween'];
|
|
1390
|
+
|
|
1391
|
+
// Check if the value doesn't break
|
|
1392
|
+
const isValueValid = (value, field, columns, operator) => {
|
|
1393
|
+
var _column$type;
|
|
1394
|
+
// every field accepts undefined as value for default
|
|
1395
|
+
if (value === undefined || value === '') {
|
|
1396
|
+
return true;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// xxxAnyOf accepts as value only lists, and we are declaring them in the
|
|
1400
|
+
// URL as `list=[...]`
|
|
1401
|
+
if (listOperators.includes(operator)) {
|
|
1402
|
+
return Array.isArray(value) || value === '';
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// We are accepting arrays only if they are of the 'xxxAnyOf' type
|
|
1406
|
+
if (Array.isArray(value) && !listOperators.includes(operator)) {
|
|
1407
|
+
return false;
|
|
1408
|
+
}
|
|
1409
|
+
const column = columns.find(column => column.field === field);
|
|
1410
|
+
if (!column) {
|
|
1411
|
+
return false;
|
|
1412
|
+
}
|
|
1413
|
+
const type = (_column$type = column['type']) !== null && _column$type !== void 0 ? _column$type : 'string';
|
|
1414
|
+
|
|
1415
|
+
// Only date and rating fail with 500s, other set themselves as undefined
|
|
1416
|
+
if (type !== 'date' && type !== 'rating') {
|
|
1417
|
+
return true;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// just checking that rating is a number.
|
|
1421
|
+
if (type === 'rating') {
|
|
1422
|
+
return !isNaN(Number(value));
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// format: YYYY-MM-DD
|
|
1426
|
+
// just verifying that the 3 values are numbers to avoid 500s,
|
|
1427
|
+
// If the value is invalid the form will appear as undefined
|
|
1428
|
+
if (type === 'date') {
|
|
1429
|
+
const dateSplitted = value.split('-');
|
|
1430
|
+
if (dateSplitted.length !== 3) {
|
|
1431
|
+
return false;
|
|
1432
|
+
}
|
|
1433
|
+
const [YYYY, MM, DD] = dateSplitted;
|
|
1434
|
+
return !isNaN(parseInt(YYYY)) && !isNaN(parseInt(MM)) && !isNaN(parseInt(DD));
|
|
1435
|
+
}
|
|
1436
|
+
return false;
|
|
1437
|
+
};
|
|
1438
|
+
|
|
1439
|
+
/** FILTERS */
|
|
1440
|
+
|
|
1441
|
+
// example:
|
|
1442
|
+
// unicodeDomain[contains]=a&unicodeDomain[contains]=dsa&logicOperator=and&tab=ignored
|
|
1443
|
+
const getFilterModelFromString = (searchString, columns) => {
|
|
1444
|
+
if (!searchString) {
|
|
1445
|
+
return 'invalid';
|
|
1446
|
+
}
|
|
1447
|
+
let logicOperator = GridLogicOperator.And;
|
|
1448
|
+
let quickFilterValues = [];
|
|
1449
|
+
const searchParams = new URLSearchParams();
|
|
1450
|
+
for (const [key, value] of new URLSearchParams(searchString)) {
|
|
1451
|
+
if (key.startsWith('_') && !['_logicOperator', '_sortColumn', '_pinnedColumnsLeft', '_pinnedColumnsRight', '_columnVisibility', '_pagination', '_quickFilterValues', '_columnOrder', '_rowGrouping', '_aggregation', '_pivot', '_density', '_filters'].includes(key)) {
|
|
1452
|
+
searchParams.set(key, value);
|
|
1453
|
+
}
|
|
1454
|
+
if (key === '_logicOperator') {
|
|
1455
|
+
logicOperator = value === GridLogicOperator.And || value === GridLogicOperator.Or ? value : GridLogicOperator.And;
|
|
1456
|
+
}
|
|
1457
|
+
if (key === '_quickFilterValues') {
|
|
1458
|
+
try {
|
|
1459
|
+
quickFilterValues = JSON.parse(decodeURIComponent(value));
|
|
1460
|
+
} catch {
|
|
1461
|
+
quickFilterValues = [];
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
let id = 5000;
|
|
1466
|
+
const fields = columns.map(column => column.field);
|
|
1467
|
+
let isInvalid = false;
|
|
1468
|
+
const items = [];
|
|
1469
|
+
searchParams.forEach((value, key) => {
|
|
1470
|
+
var _columns$find;
|
|
1471
|
+
if (isInvalid) {
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
const field = key.split('[')[0].slice(1);
|
|
1475
|
+
if (!fields.includes(field)) {
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
const columnType = (_columns$find = columns.find(column => column.field === field)) === null || _columns$find === void 0 ? void 0 : _columns$find.type;
|
|
1479
|
+
const left = key.split(']')[0];
|
|
1480
|
+
if (left.split('[').length < 2) {
|
|
1481
|
+
isInvalid = true;
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
const splitRight = key.split('[')[1].split(']')[0].split(',');
|
|
1485
|
+
const type = splitRight[1];
|
|
1486
|
+
if (type !== columnType) {
|
|
1487
|
+
isInvalid = true;
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
const operator = splitRight[0];
|
|
1491
|
+
if (!isOperatorValueValid(field, operator, columns) || Array.isArray(operator)) {
|
|
1492
|
+
isInvalid = true;
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
id += 1;
|
|
1496
|
+
const decodedValue = decodeValue(value);
|
|
1497
|
+
if (!isValueValid(decodedValue, field, columns, operator)) {
|
|
1498
|
+
isInvalid = true;
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
items.push({
|
|
1502
|
+
field,
|
|
1503
|
+
operator: columnType === 'number' && Object.keys(numberOperatorDecoder).includes(operator) ? numberOperatorDecoder[operator] : operator,
|
|
1504
|
+
id,
|
|
1505
|
+
value: listOperators.includes(operator) && decodedValue === '' ? [] : decodedValue,
|
|
1506
|
+
type
|
|
1507
|
+
});
|
|
1508
|
+
});
|
|
1509
|
+
if (isInvalid) {
|
|
1510
|
+
return 'invalid';
|
|
1511
|
+
}
|
|
1512
|
+
return {
|
|
1513
|
+
items,
|
|
1514
|
+
logicOperator,
|
|
1515
|
+
quickFilterValues
|
|
1516
|
+
};
|
|
1517
|
+
};
|
|
1518
|
+
const getSearchParamsFromFilterModel = filterModel => {
|
|
1519
|
+
var _filterModel$quickFil;
|
|
1520
|
+
const searchParams = new URLSearchParams();
|
|
1521
|
+
searchParams.set('_logicOperator', filterModel['logicOperator'] || '');
|
|
1522
|
+
filterModel['items'].forEach(item => {
|
|
1523
|
+
const {
|
|
1524
|
+
field,
|
|
1525
|
+
operator,
|
|
1526
|
+
value,
|
|
1527
|
+
type
|
|
1528
|
+
} = item;
|
|
1529
|
+
if (Object.keys(numberOperatorEncoder).includes(operator)) {
|
|
1530
|
+
searchParams.set(`_${field}[${numberOperatorEncoder[operator]},${encodeValue(type)}]`, encodeValue(value));
|
|
1531
|
+
} else {
|
|
1532
|
+
searchParams.set(`_${field}[${encodeValue(operator)},${encodeValue(type)}]`, encodeValue(value));
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
if ((_filterModel$quickFil = filterModel.quickFilterValues) !== null && _filterModel$quickFil !== void 0 && _filterModel$quickFil.length) {
|
|
1536
|
+
searchParams.set('_quickFilterValues', encodeURIComponent(JSON.stringify(filterModel.quickFilterValues)));
|
|
1537
|
+
}
|
|
1538
|
+
return searchParams;
|
|
1539
|
+
};
|
|
1540
|
+
|
|
1541
|
+
// Rules:
|
|
1542
|
+
// - if we have something in the URL, use that info
|
|
1543
|
+
// - if we don't have that, use the localStorage and update the URL
|
|
1544
|
+
// - if we don't have that, return an empty FilterModel
|
|
1545
|
+
const getFilterModel = (search, columns, localStorageFilters, setLocalStorageFilters, initialState, isNewVersion) => {
|
|
1546
|
+
const defaultValue = initialState && initialState.filter && initialState.filter.filterModel ? initialState.filter.filterModel : {
|
|
1547
|
+
items: [],
|
|
1548
|
+
logicOperator: GridLogicOperator.And
|
|
1549
|
+
};
|
|
1550
|
+
|
|
1551
|
+
// Persist initialState-derived filters to localStorage so all three sources stay in sync
|
|
1552
|
+
const persistDefaultFilters = () => {
|
|
1553
|
+
const searchFromDefault = getSearchParamsFromFilterModel(defaultValue);
|
|
1554
|
+
const searchString = urlSearchParamsToString(searchFromDefault);
|
|
1555
|
+
if (searchString !== localStorageFilters) {
|
|
1556
|
+
setLocalStorageFilters(searchString);
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
if (isNewVersion) {
|
|
1560
|
+
persistDefaultFilters();
|
|
1561
|
+
return defaultValue;
|
|
1562
|
+
}
|
|
1563
|
+
const filterModelFromSearch = getFilterModelFromString(search, columns);
|
|
1564
|
+
if (filterModelFromSearch !== 'invalid') {
|
|
1565
|
+
const searchFromFilterModel = getSearchParamsFromFilterModel(filterModelFromSearch);
|
|
1566
|
+
const searchString = urlSearchParamsToString(searchFromFilterModel);
|
|
1567
|
+
if (searchString !== localStorageFilters) {
|
|
1568
|
+
setLocalStorageFilters(searchString);
|
|
1569
|
+
}
|
|
1570
|
+
return filterModelFromSearch;
|
|
1571
|
+
}
|
|
1572
|
+
const filterModelFromLocalStorage = getFilterModelFromString(localStorageFilters, columns);
|
|
1573
|
+
if (filterModelFromLocalStorage !== 'invalid') {
|
|
1574
|
+
return filterModelFromLocalStorage;
|
|
1575
|
+
}
|
|
1576
|
+
persistDefaultFilters();
|
|
1577
|
+
return defaultValue;
|
|
1578
|
+
};
|
|
1579
|
+
|
|
1580
|
+
/** SORT */
|
|
1581
|
+
|
|
1582
|
+
const getSortingFromString = (searchString, columns) => {
|
|
1583
|
+
if (!searchString) {
|
|
1584
|
+
return 'invalid';
|
|
1585
|
+
}
|
|
1586
|
+
const searchParams = new URLSearchParams(searchString);
|
|
1587
|
+
const value = searchParams.get('_sortColumn');
|
|
1588
|
+
if (value === '' || value === null || value === '[]') {
|
|
1589
|
+
return [];
|
|
1590
|
+
}
|
|
1591
|
+
const fields = columns.map(column => column.field);
|
|
1592
|
+
const [column, order] = value.slice(1, value.length - 1).split(',');
|
|
1593
|
+
if (fields.includes(column) && (order === 'asc' || order === 'desc')) {
|
|
1594
|
+
return [{
|
|
1595
|
+
field: column,
|
|
1596
|
+
sort: order
|
|
1597
|
+
}];
|
|
1598
|
+
}
|
|
1599
|
+
return 'invalid';
|
|
1600
|
+
};
|
|
1601
|
+
const getSearchParamsFromSorting = sorting => {
|
|
1602
|
+
const searchParams = new URLSearchParams();
|
|
1603
|
+
searchParams.set('_sortColumn', sorting.length > 0 && sorting[0].sort ? `[${encodeValue(sorting[0].field)},${encodeValue(sorting[0].sort)}]` : '[]');
|
|
1604
|
+
return searchParams;
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1607
|
+
// Rules:
|
|
1608
|
+
// - if we have something in the URL, use that info
|
|
1609
|
+
// - if we don't have that, use the localStorage and update the URL
|
|
1610
|
+
// - if we don't have that, return an empty SortModel
|
|
1611
|
+
const getSortModel = (search, columns, localStorageSorting, setLocalStorageSorting, initialState, isNewVersion) => {
|
|
1612
|
+
var _initialState$sorting;
|
|
1613
|
+
const defaultValue = initialState !== null && initialState !== void 0 && (_initialState$sorting = initialState.sorting) !== null && _initialState$sorting !== void 0 && _initialState$sorting.sortModel ? initialState.sorting.sortModel : [];
|
|
1614
|
+
|
|
1615
|
+
// Persist initialState-derived sorting to localStorage so all three sources stay in sync
|
|
1616
|
+
const persistDefaultSort = () => {
|
|
1617
|
+
const searchFromDefault = getSearchParamsFromSorting(defaultValue);
|
|
1618
|
+
const searchString = urlSearchParamsToString(searchFromDefault);
|
|
1619
|
+
if (searchString !== localStorageSorting) {
|
|
1620
|
+
setLocalStorageSorting(searchString);
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
if (isNewVersion) {
|
|
1624
|
+
persistDefaultSort();
|
|
1625
|
+
return defaultValue;
|
|
1626
|
+
}
|
|
1627
|
+
const sorting = getSortingFromString(search, columns);
|
|
1628
|
+
if (sorting !== 'invalid') {
|
|
1629
|
+
const searchFromSortModel = getSearchParamsFromSorting(sorting);
|
|
1630
|
+
const searchString = urlSearchParamsToString(searchFromSortModel);
|
|
1631
|
+
if (searchString !== localStorageSorting) {
|
|
1632
|
+
setLocalStorageSorting(searchString);
|
|
1633
|
+
}
|
|
1634
|
+
return sorting;
|
|
1635
|
+
}
|
|
1636
|
+
const sortModelFromLocalStorage = getSortingFromString(localStorageSorting, columns);
|
|
1637
|
+
if (sortModelFromLocalStorage !== 'invalid') {
|
|
1638
|
+
return sortModelFromLocalStorage;
|
|
1639
|
+
}
|
|
1640
|
+
persistDefaultSort();
|
|
1641
|
+
return defaultValue;
|
|
1642
|
+
};
|
|
1643
|
+
|
|
1644
|
+
/** PAGINATION */
|
|
1645
|
+
|
|
1646
|
+
const getPaginationFromString = searchString => {
|
|
1647
|
+
if (!searchString) {
|
|
1648
|
+
return 'invalid';
|
|
1649
|
+
}
|
|
1650
|
+
const searchParams = new URLSearchParams(searchString);
|
|
1651
|
+
const value = searchParams.get('_pagination');
|
|
1652
|
+
if (value === '' || value === null || value === '[]') {
|
|
1653
|
+
return 'invalid';
|
|
1654
|
+
}
|
|
1655
|
+
const pagination = value.slice(1, value.length - 1).split(',');
|
|
1656
|
+
const page = parseFloat(pagination[0]);
|
|
1657
|
+
const pageSize = parseFloat(pagination[1]);
|
|
1658
|
+
const direction = pagination[2];
|
|
1659
|
+
if (!Number.isNaN(page) && !Number.isNaN(pageSize) && direction && ['next', 'back'].includes(direction)) {
|
|
1660
|
+
return {
|
|
1661
|
+
page,
|
|
1662
|
+
pageSize,
|
|
1663
|
+
direction: direction
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
return 'invalid';
|
|
1667
|
+
};
|
|
1668
|
+
const getSearchParamsFromPagination = pagination => {
|
|
1669
|
+
const searchParams = new URLSearchParams();
|
|
1670
|
+
searchParams.set('_pagination', `[${pagination.page},${pagination.pageSize},${pagination.direction}]`);
|
|
1671
|
+
return searchParams;
|
|
1672
|
+
};
|
|
1673
|
+
|
|
1674
|
+
// Rules:
|
|
1675
|
+
// - if we have something in the URL, use that info
|
|
1676
|
+
// - if we don't have that, use the localStorage and update the URL
|
|
1677
|
+
// - if we don't have that, return an empty PaginationModel
|
|
1678
|
+
const getPaginationModel = (search, localStoragePagination, setLocalStoragePagination, initialState, isNewVersion) => {
|
|
1679
|
+
var _initialState$paginat, _initialState$paginat2, _initialState$paginat3;
|
|
1680
|
+
// Extract pageSize from MUI's nested format: initialState.pagination.paginationModel.pageSize
|
|
1681
|
+
const initialPageSize = (_initialState$paginat = initialState === null || initialState === void 0 ? void 0 : (_initialState$paginat2 = initialState.pagination) === null || _initialState$paginat2 === void 0 ? void 0 : (_initialState$paginat3 = _initialState$paginat2.paginationModel) === null || _initialState$paginat3 === void 0 ? void 0 : _initialState$paginat3.pageSize) !== null && _initialState$paginat !== void 0 ? _initialState$paginat : 25;
|
|
1682
|
+
const defaultValue = {
|
|
1683
|
+
page: 0,
|
|
1684
|
+
pageSize: initialPageSize,
|
|
1685
|
+
direction: 'next'
|
|
1686
|
+
};
|
|
1687
|
+
|
|
1688
|
+
// Persist initialState-derived pagination to localStorage so all three sources stay in sync
|
|
1689
|
+
const persistDefaultPagination = () => {
|
|
1690
|
+
const searchFromDefault = getSearchParamsFromPagination(defaultValue);
|
|
1691
|
+
const searchString = urlSearchParamsToString(searchFromDefault);
|
|
1692
|
+
if (searchString !== localStoragePagination) {
|
|
1693
|
+
setLocalStoragePagination(searchString);
|
|
1694
|
+
}
|
|
1695
|
+
};
|
|
1696
|
+
if (isNewVersion) {
|
|
1697
|
+
persistDefaultPagination();
|
|
1698
|
+
return defaultValue;
|
|
1699
|
+
}
|
|
1700
|
+
const pagination = getPaginationFromString(search);
|
|
1701
|
+
if (pagination !== 'invalid') {
|
|
1702
|
+
const searchFromPaginationModel = getSearchParamsFromPagination(pagination);
|
|
1703
|
+
const searchString = urlSearchParamsToString(searchFromPaginationModel);
|
|
1704
|
+
if (searchString !== localStoragePagination) {
|
|
1705
|
+
setLocalStoragePagination(searchString);
|
|
1706
|
+
}
|
|
1707
|
+
return pagination;
|
|
1708
|
+
}
|
|
1709
|
+
const paginationModelFromLocalStorage = getPaginationFromString(localStoragePagination);
|
|
1710
|
+
if (paginationModelFromLocalStorage !== 'invalid') {
|
|
1711
|
+
return paginationModelFromLocalStorage;
|
|
1712
|
+
}
|
|
1713
|
+
persistDefaultPagination();
|
|
1714
|
+
return defaultValue;
|
|
1715
|
+
};
|
|
1716
|
+
|
|
1717
|
+
/** COLUMN VISIBILITY */
|
|
1718
|
+
|
|
1719
|
+
const getColumnVisibilityFromString = (searchString, columns) => {
|
|
1720
|
+
if (!searchString) {
|
|
1721
|
+
return 'invalid';
|
|
1722
|
+
}
|
|
1723
|
+
const searchParams = new URLSearchParams(searchString);
|
|
1724
|
+
const value = searchParams.get('_columnVisibility');
|
|
1725
|
+
if (value === '' || value === null || value === '[]') {
|
|
1726
|
+
return 'invalid';
|
|
1727
|
+
}
|
|
1728
|
+
const parsedFields = value.slice(1, value.length - 1).split(',');
|
|
1729
|
+
const fields = columns.map(column => column.field);
|
|
1730
|
+
const visibility = {};
|
|
1731
|
+
for (const field of fields) {
|
|
1732
|
+
visibility[field] = false;
|
|
1733
|
+
}
|
|
1734
|
+
for (const parsedField of parsedFields) {
|
|
1735
|
+
if (fields.includes(parsedField)) {
|
|
1736
|
+
visibility[parsedField] = true;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
if (Object.values(visibility).filter(v => v === true).length === 0) {
|
|
1740
|
+
return 'invalid';
|
|
1741
|
+
}
|
|
1742
|
+
return visibility;
|
|
1743
|
+
};
|
|
1744
|
+
const getSearchParamsFromColumnVisibility = (columnVisibility, columns) => {
|
|
1745
|
+
const searchParams = new URLSearchParams();
|
|
1746
|
+
const fields = columns.map(column => column.field);
|
|
1747
|
+
|
|
1748
|
+
// if column visibility model is empty, show all columns
|
|
1749
|
+
if (Object.keys(columnVisibility).length == 0) {
|
|
1750
|
+
searchParams.set('_columnVisibility', `[${fields.join(',')}]`);
|
|
1751
|
+
return searchParams;
|
|
1752
|
+
}
|
|
1753
|
+
const finalColumnVisibility = columns.filter(c => {
|
|
1754
|
+
var _c$hideable;
|
|
1755
|
+
return !((_c$hideable = c === null || c === void 0 ? void 0 : c.hideable) !== null && _c$hideable !== void 0 ? _c$hideable : true);
|
|
1756
|
+
}).map(c => c.field).reduce((acc, colName) => {
|
|
1757
|
+
return _objectSpread2(_objectSpread2({}, acc), {}, {
|
|
1758
|
+
[colName]: true
|
|
1759
|
+
});
|
|
1760
|
+
}, columnVisibility);
|
|
1761
|
+
const visibleColumns = fields.filter(column => finalColumnVisibility[column] !== false);
|
|
1762
|
+
searchParams.set('_columnVisibility', `[${visibleColumns.join(',')}]`);
|
|
1763
|
+
return searchParams;
|
|
1764
|
+
};
|
|
1765
|
+
|
|
1766
|
+
// Rules:
|
|
1767
|
+
// - if we have something in the URL, use that info
|
|
1768
|
+
// - if we don't have that, use the localStorage and update the URL
|
|
1769
|
+
// - if we don't have that, return an empty ColumnVisibilityModel (which is all columns)
|
|
1770
|
+
// NOTE: the `defaultHidden` is a custom field and not standard DataGrid
|
|
1771
|
+
// The reason is the following bug: https://github.com/mui/mui-x/issues/8407
|
|
1772
|
+
const getColumnsVisibility = (search, columns, localStorageColumnsVisibility, setLocalStorageColumnsVisibility, initialState, isNewVersion) => {
|
|
1773
|
+
var _initialState$columns3;
|
|
1774
|
+
const defaultValue = {};
|
|
1775
|
+
for (const column of columns) {
|
|
1776
|
+
var _initialState$columns, _initialState$columns2;
|
|
1777
|
+
const field = column.field;
|
|
1778
|
+
if ((initialState === null || initialState === void 0 ? void 0 : (_initialState$columns = initialState.columns) === null || _initialState$columns === void 0 ? void 0 : (_initialState$columns2 = _initialState$columns.columnVisibilityModel) === null || _initialState$columns2 === void 0 ? void 0 : _initialState$columns2[field]) !== undefined) {
|
|
1779
|
+
defaultValue[field] = initialState.columns.columnVisibilityModel[field];
|
|
1780
|
+
} else {
|
|
1781
|
+
defaultValue[field] = column.defaultHidden !== true; // undefined will be true
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// Persist initialState-derived column visibility to localStorage so all three sources stay in sync
|
|
1786
|
+
const persistDefaultVisibility = value => {
|
|
1787
|
+
const searchFromDefault = getSearchParamsFromColumnVisibility(value, columns);
|
|
1788
|
+
if (searchFromDefault.toString() !== localStorageColumnsVisibility) {
|
|
1789
|
+
setLocalStorageColumnsVisibility(searchFromDefault.toString());
|
|
1790
|
+
}
|
|
1791
|
+
};
|
|
1792
|
+
if (isNewVersion) {
|
|
1793
|
+
persistDefaultVisibility(defaultValue);
|
|
1794
|
+
return defaultValue;
|
|
1795
|
+
}
|
|
1796
|
+
const columnVisibility = getColumnVisibilityFromString(search, columns);
|
|
1797
|
+
if (columnVisibility !== 'invalid') {
|
|
1798
|
+
const searchColumnVisibility = getSearchParamsFromColumnVisibility(columnVisibility, columns);
|
|
1799
|
+
if (searchColumnVisibility.toString() !== localStorageColumnsVisibility) {
|
|
1800
|
+
setLocalStorageColumnsVisibility(searchColumnVisibility.toString());
|
|
1801
|
+
}
|
|
1802
|
+
return columnVisibility;
|
|
1803
|
+
}
|
|
1804
|
+
const columnVisibilityFromLocalStorage = getColumnVisibilityFromString(localStorageColumnsVisibility, columns);
|
|
1805
|
+
if (columnVisibilityFromLocalStorage !== 'invalid') {
|
|
1806
|
+
return columnVisibilityFromLocalStorage;
|
|
1807
|
+
}
|
|
1808
|
+
if (initialState !== null && initialState !== void 0 && (_initialState$columns3 = initialState.columns) !== null && _initialState$columns3 !== void 0 && _initialState$columns3.columnVisibilityModel) {
|
|
1809
|
+
persistDefaultVisibility(initialState.columns.columnVisibilityModel);
|
|
1810
|
+
return initialState.columns.columnVisibilityModel;
|
|
1811
|
+
}
|
|
1812
|
+
persistDefaultVisibility(defaultValue);
|
|
1813
|
+
return defaultValue;
|
|
1814
|
+
};
|
|
1815
|
+
const getPinnedColumnsFromString = (notParsed, tableColumns) => {
|
|
1816
|
+
if (!notParsed || notParsed.length === 1 && notParsed[0] === '?') {
|
|
1817
|
+
return 'invalid';
|
|
1818
|
+
}
|
|
1819
|
+
// remove the initial ? if present
|
|
1820
|
+
const parsed = notParsed[0] === '?' ? notParsed.slice(1) : notParsed;
|
|
1821
|
+
const pinnedColumns = {};
|
|
1822
|
+
for (const item of parsed.split('&')) {
|
|
1823
|
+
const fieldURL = item.split('=')[0];
|
|
1824
|
+
if (fieldURL !== '_pinnedColumnsLeft' && fieldURL !== '_pinnedColumnsRight') {
|
|
1825
|
+
continue;
|
|
1826
|
+
}
|
|
1827
|
+
const left = item.split(']')[0];
|
|
1828
|
+
if (left.split('[').length < 2) {
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
const encodedValues = item.split('[')[1].split(']')[0];
|
|
1832
|
+
if (typeof encodedValues !== 'string') {
|
|
1833
|
+
continue;
|
|
1834
|
+
}
|
|
1835
|
+
const fields = [...tableColumns.map(column => column.field), '__check__'];
|
|
1836
|
+
const columns = encodedValues.split(',').map(value => decodeValue(value)).filter(val => typeof val === 'string' && fields.includes(val));
|
|
1837
|
+
if (fieldURL === '_pinnedColumnsLeft') {
|
|
1838
|
+
pinnedColumns['left'] = columns;
|
|
1839
|
+
}
|
|
1840
|
+
if (fieldURL === '_pinnedColumnsRight') {
|
|
1841
|
+
pinnedColumns['right'] = columns;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
return pinnedColumns.left && pinnedColumns.left.length > 0 || pinnedColumns.right && pinnedColumns.right.length > 0 ? {
|
|
1845
|
+
left: pinnedColumns.left || [],
|
|
1846
|
+
right: pinnedColumns.right || []
|
|
1847
|
+
} : 'invalid';
|
|
1848
|
+
};
|
|
1849
|
+
const getSearchParamsFromPinnedColumns = pinnedColumns => {
|
|
1850
|
+
var _pinnedColumns$left, _pinnedColumns$right;
|
|
1851
|
+
const searchParams = new URLSearchParams();
|
|
1852
|
+
const pinnedColumnLeft = ((_pinnedColumns$left = pinnedColumns.left) === null || _pinnedColumns$left === void 0 ? void 0 : _pinnedColumns$left.map(val => encodeValue(val))) || [];
|
|
1853
|
+
const pinnedColumnRight = ((_pinnedColumns$right = pinnedColumns.right) === null || _pinnedColumns$right === void 0 ? void 0 : _pinnedColumns$right.map(val => encodeValue(val))) || [];
|
|
1854
|
+
searchParams.set('_pinnedColumnsLeft', `[${pinnedColumnLeft.join(',')}]`);
|
|
1855
|
+
searchParams.set('_pinnedColumnsRight', `[${pinnedColumnRight.join(',')}]`);
|
|
1856
|
+
return searchParams;
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1859
|
+
// Rules:
|
|
1860
|
+
// - if we have something in the URL, use that info
|
|
1861
|
+
// - if we don't have that, use the localStorage and update the URL
|
|
1862
|
+
// - if we don't have that, return an empty ColumnVisibilityModel (which is all columns)
|
|
1863
|
+
const getPinnedColumns = (search, columns, localStoragePinnedColumns, setLocalStoragePinnedColumns, initialState, isNewVersion) => {
|
|
1864
|
+
const defaultValue = initialState !== null && initialState !== void 0 && initialState.pinnedColumns ? {
|
|
1865
|
+
left: (initialState === null || initialState === void 0 ? void 0 : initialState.pinnedColumns['left']) || [],
|
|
1866
|
+
right: (initialState === null || initialState === void 0 ? void 0 : initialState.pinnedColumns['right']) || []
|
|
1867
|
+
} : {
|
|
1868
|
+
left: [],
|
|
1869
|
+
right: []
|
|
1870
|
+
};
|
|
1871
|
+
if (isNewVersion) {
|
|
1872
|
+
return defaultValue;
|
|
1873
|
+
}
|
|
1874
|
+
const pinnedColumns = getPinnedColumnsFromString(search, columns);
|
|
1875
|
+
if (pinnedColumns !== 'invalid') {
|
|
1876
|
+
const searchPinnedColumns = getSearchParamsFromPinnedColumns(pinnedColumns);
|
|
1877
|
+
if (searchPinnedColumns.toString() !== localStoragePinnedColumns) {
|
|
1878
|
+
setLocalStoragePinnedColumns(searchPinnedColumns.toString());
|
|
1879
|
+
}
|
|
1880
|
+
return pinnedColumns;
|
|
1881
|
+
}
|
|
1882
|
+
const pinnedColumnsFromLocalStorage = getPinnedColumnsFromString(localStoragePinnedColumns, columns);
|
|
1883
|
+
if (pinnedColumnsFromLocalStorage !== 'invalid') {
|
|
1884
|
+
return pinnedColumnsFromLocalStorage;
|
|
1885
|
+
}
|
|
1886
|
+
return defaultValue;
|
|
1887
|
+
};
|
|
1888
|
+
const getSearchParamsFromTab = search => {
|
|
1889
|
+
const searchParams = new URLSearchParams();
|
|
1890
|
+
const openTab = new URLSearchParams(search).get('tab');
|
|
1891
|
+
if (openTab) {
|
|
1892
|
+
searchParams.set('tab', openTab);
|
|
1893
|
+
}
|
|
1894
|
+
return searchParams;
|
|
1895
|
+
};
|
|
1896
|
+
|
|
1897
|
+
/** DENSITY */
|
|
1898
|
+
|
|
1899
|
+
const VALID_DENSITIES = ['compact', 'standard', 'comfortable'];
|
|
1900
|
+
const getDensityFromString = searchString => {
|
|
1901
|
+
if (!searchString) {
|
|
1902
|
+
return 'invalid';
|
|
1903
|
+
}
|
|
1904
|
+
const searchParams = new URLSearchParams(searchString);
|
|
1905
|
+
const value = searchParams.get('_density');
|
|
1906
|
+
if (value && VALID_DENSITIES.includes(value)) {
|
|
1907
|
+
return value;
|
|
1908
|
+
}
|
|
1909
|
+
return 'invalid';
|
|
1910
|
+
};
|
|
1911
|
+
const getSearchParamsFromDensity = density => {
|
|
1912
|
+
const searchParams = new URLSearchParams();
|
|
1913
|
+
searchParams.set('_density', density);
|
|
1914
|
+
return searchParams;
|
|
1915
|
+
};
|
|
1916
|
+
const getDensityModel = (search, localStorageDensity, setLocalStorageDensity, initialState, isNewVersion) => {
|
|
1917
|
+
// Default density: honour initialState.density if valid, otherwise fall back to 'compact'
|
|
1918
|
+
const initialDensity = initialState === null || initialState === void 0 ? void 0 : initialState.density;
|
|
1919
|
+
const defaultValue = initialDensity && VALID_DENSITIES.includes(initialDensity) ? initialDensity : 'compact';
|
|
1920
|
+
|
|
1921
|
+
// Persist initialState-derived density to localStorage so all three sources stay in sync
|
|
1922
|
+
const persistDefaultDensity = () => {
|
|
1923
|
+
const searchFromDefault = getSearchParamsFromDensity(defaultValue);
|
|
1924
|
+
const searchString = urlSearchParamsToString(searchFromDefault);
|
|
1925
|
+
if (searchString !== localStorageDensity) {
|
|
1926
|
+
setLocalStorageDensity(searchString);
|
|
1927
|
+
}
|
|
1928
|
+
};
|
|
1929
|
+
if (isNewVersion) {
|
|
1930
|
+
persistDefaultDensity();
|
|
1931
|
+
return defaultValue;
|
|
1932
|
+
}
|
|
1933
|
+
const density = getDensityFromString(search);
|
|
1934
|
+
if (density !== 'invalid') {
|
|
1935
|
+
const searchFromDensity = getSearchParamsFromDensity(density);
|
|
1936
|
+
const searchString = urlSearchParamsToString(searchFromDensity);
|
|
1937
|
+
if (searchString !== localStorageDensity) {
|
|
1938
|
+
setLocalStorageDensity(searchString);
|
|
1939
|
+
}
|
|
1940
|
+
return density;
|
|
1941
|
+
}
|
|
1942
|
+
const densityFromLocalStorage = getDensityFromString(localStorageDensity);
|
|
1943
|
+
if (densityFromLocalStorage !== 'invalid') {
|
|
1944
|
+
return densityFromLocalStorage;
|
|
1945
|
+
}
|
|
1946
|
+
persistDefaultDensity();
|
|
1947
|
+
return defaultValue;
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
/** COLUMN ORDER */
|
|
1951
|
+
|
|
1952
|
+
const getColumnOrderFromString = searchString => {
|
|
1953
|
+
if (!searchString) return 'invalid';
|
|
1954
|
+
const searchParams = new URLSearchParams(searchString);
|
|
1955
|
+
const value = searchParams.get('_columnOrder');
|
|
1956
|
+
if (value === '' || value === null || value === '[]') return 'invalid';
|
|
1957
|
+
|
|
1958
|
+
// Handle bracket notation [a,b,c]
|
|
1959
|
+
const inner = value.startsWith('[') && value.endsWith(']') ? value.slice(1, -1) : value;
|
|
1960
|
+
if (!inner) return 'invalid';
|
|
1961
|
+
return inner.split(',').filter(Boolean);
|
|
1962
|
+
};
|
|
1963
|
+
const getSearchParamsFromColumnOrder = columnOrder => {
|
|
1964
|
+
const searchParams = new URLSearchParams();
|
|
1965
|
+
if (columnOrder.length > 0) {
|
|
1966
|
+
searchParams.set('_columnOrder', `[${columnOrder.join(',')}]`);
|
|
1967
|
+
}
|
|
1968
|
+
return searchParams;
|
|
1969
|
+
};
|
|
1970
|
+
const getColumnOrder = (search, columns, localStorageColumnOrder, setLocalStorageColumnOrder, initialState, isNewVersion) => {
|
|
1971
|
+
var _initialState$columns4, _initialState$columns5;
|
|
1972
|
+
const defaultValue = (_initialState$columns4 = initialState === null || initialState === void 0 ? void 0 : (_initialState$columns5 = initialState.columns) === null || _initialState$columns5 === void 0 ? void 0 : _initialState$columns5.orderedFields) !== null && _initialState$columns4 !== void 0 ? _initialState$columns4 : columns.map(c => c.field);
|
|
1973
|
+
const persistDefault = () => {
|
|
1974
|
+
const searchFromDefault = getSearchParamsFromColumnOrder(defaultValue);
|
|
1975
|
+
const searchString = urlSearchParamsToString(searchFromDefault);
|
|
1976
|
+
if (searchString !== localStorageColumnOrder) {
|
|
1977
|
+
setLocalStorageColumnOrder(searchString);
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
if (isNewVersion) {
|
|
1981
|
+
persistDefault();
|
|
1982
|
+
return defaultValue;
|
|
1983
|
+
}
|
|
1984
|
+
const fromUrl = getColumnOrderFromString(search);
|
|
1985
|
+
if (fromUrl !== 'invalid') {
|
|
1986
|
+
const searchFromModel = getSearchParamsFromColumnOrder(fromUrl);
|
|
1987
|
+
const searchString = urlSearchParamsToString(searchFromModel);
|
|
1988
|
+
if (searchString !== localStorageColumnOrder) {
|
|
1989
|
+
setLocalStorageColumnOrder(searchString);
|
|
1990
|
+
}
|
|
1991
|
+
return fromUrl;
|
|
1992
|
+
}
|
|
1993
|
+
const fromLocalStorage = getColumnOrderFromString(localStorageColumnOrder);
|
|
1994
|
+
if (fromLocalStorage !== 'invalid') {
|
|
1995
|
+
return fromLocalStorage;
|
|
1996
|
+
}
|
|
1997
|
+
persistDefault();
|
|
1998
|
+
return defaultValue;
|
|
1999
|
+
};
|
|
2000
|
+
const getFinalSearch = _ref => {
|
|
2001
|
+
let {
|
|
2002
|
+
search,
|
|
2003
|
+
localStorageVersion,
|
|
2004
|
+
filterModel,
|
|
2005
|
+
sortModel,
|
|
2006
|
+
paginationModel,
|
|
2007
|
+
columnsVisibilityModel,
|
|
2008
|
+
pinnedColumnsModel,
|
|
2009
|
+
density,
|
|
2010
|
+
columnOrderModel,
|
|
2011
|
+
defaultColumnOrder,
|
|
2012
|
+
columns
|
|
2013
|
+
} = _ref;
|
|
2014
|
+
const filterModelSearch = getSearchParamsFromFilterModel(filterModel);
|
|
2015
|
+
const sortModelSearch = getSearchParamsFromSorting(sortModel);
|
|
2016
|
+
const paginationModelSearch = getSearchParamsFromPagination(paginationModel);
|
|
2017
|
+
const columnVisibilityModelSearch = getSearchParamsFromColumnVisibility(columnsVisibilityModel, columns);
|
|
2018
|
+
const pinnedColumnsModelSearch = getSearchParamsFromPinnedColumns(pinnedColumnsModel);
|
|
2019
|
+
const densitySearch = getSearchParamsFromDensity(density);
|
|
2020
|
+
// Only include _columnOrder in URL when it differs from the default
|
|
2021
|
+
const columnOrderSearch = columnOrderModel.length !== defaultColumnOrder.length || columnOrderModel.some((field, i) => field !== defaultColumnOrder[i]) ? getSearchParamsFromColumnOrder(columnOrderModel) : new URLSearchParams();
|
|
2022
|
+
const tabSearch = getSearchParamsFromTab(search);
|
|
2023
|
+
const searchParams = new URLSearchParams();
|
|
2024
|
+
for (const [key, value] of new URLSearchParams(search)) {
|
|
2025
|
+
if (!key.startsWith('_')) {
|
|
2026
|
+
searchParams.set(key, value);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
searchParams.set('v', `${localStorageVersion}`);
|
|
2030
|
+
|
|
2031
|
+
// Add quickFilterValues explicitly if present in filterModel
|
|
2032
|
+
if (filterModel.quickFilterValues && filterModel.quickFilterValues.length > 0) {
|
|
2033
|
+
// Encode array as JSON string to preserve all values in one param
|
|
2034
|
+
searchParams.set('_quickFilterValues', encodeURIComponent(JSON.stringify(filterModel.quickFilterValues)));
|
|
2035
|
+
}
|
|
2036
|
+
return new URLSearchParams([...searchParams, ...filterModelSearch, ...sortModelSearch, ...paginationModelSearch, ...tabSearch, ...pinnedColumnsModelSearch, ...columnVisibilityModelSearch, ...densitySearch, ...columnOrderSearch]);
|
|
2037
|
+
};
|
|
2038
|
+
/** Return the state of the table given the URL and the local storage state */
|
|
2039
|
+
const getModelsParsedOrUpdateLocalStorage = (search, localStorageVersion, columns, initialState, localStorage) => {
|
|
2040
|
+
var _initialState$columns6, _initialState$columns7;
|
|
2041
|
+
// Decompress any compressed params in the search string before processing
|
|
2042
|
+
const decompressedSearch = decompressSearchParams(search);
|
|
2043
|
+
// Convert from display format (dot notation) to internal format (bracket notation) if needed
|
|
2044
|
+
const decodedSearch = getDecodedSearchFromUrl(decompressedSearch, columns);
|
|
2045
|
+
const decodedParams = new URLSearchParams(decodedSearch);
|
|
2046
|
+
const currentVersion = decodedParams.get('v');
|
|
2047
|
+
// Only treat as "new version reset" when the URL has params with a stale/missing version.
|
|
2048
|
+
// An empty URL should fall through to the localStorage branch of each getter so persisted
|
|
2049
|
+
// state is restored instead of being clobbered by defaults.
|
|
2050
|
+
const hasUrlState = Array.from(decodedParams.keys()).length > 0;
|
|
2051
|
+
const isNewVersion = hasUrlState && (!currentVersion || Number(currentVersion) !== localStorageVersion);
|
|
2052
|
+
const {
|
|
2053
|
+
localStorageFilters,
|
|
2054
|
+
setLocalStorageFilters,
|
|
2055
|
+
localStorageSorting,
|
|
2056
|
+
setLocalStorageSorting,
|
|
2057
|
+
localStoragePagination,
|
|
2058
|
+
setLocalStoragePagination,
|
|
2059
|
+
localStorageColumnsVisibility,
|
|
2060
|
+
setLocalStorageColumnsVisibility,
|
|
2061
|
+
localStoragePinnedColumns,
|
|
2062
|
+
setLocalStoragePinnedColumns,
|
|
2063
|
+
localStorageDensity,
|
|
2064
|
+
setLocalStorageDensity,
|
|
2065
|
+
localStorageColumnOrder,
|
|
2066
|
+
setLocalStorageColumnOrder
|
|
2067
|
+
} = localStorage;
|
|
2068
|
+
const filterModel = getFilterModel(decodedSearch, columns, localStorageFilters, setLocalStorageFilters, initialState, isNewVersion);
|
|
2069
|
+
const sortModel = getSortModel(decodedSearch, columns, localStorageSorting, setLocalStorageSorting, initialState, isNewVersion);
|
|
2070
|
+
const paginationModel = getPaginationModel(decodedSearch, localStoragePagination, setLocalStoragePagination, initialState, isNewVersion);
|
|
2071
|
+
const columnVisibilityModel = getColumnsVisibility(decodedSearch, columns, localStorageColumnsVisibility, setLocalStorageColumnsVisibility, initialState, isNewVersion);
|
|
2072
|
+
const pinnedColumnsModel = getPinnedColumns(decodedSearch, columns, localStoragePinnedColumns, setLocalStoragePinnedColumns, initialState, isNewVersion);
|
|
2073
|
+
const density = getDensityModel(decodedSearch, localStorageDensity, setLocalStorageDensity, initialState, isNewVersion);
|
|
2074
|
+
const columnOrderModel = getColumnOrder(decodedSearch, columns, localStorageColumnOrder, setLocalStorageColumnOrder, initialState, isNewVersion);
|
|
2075
|
+
const defaultColumnOrder = (_initialState$columns6 = initialState === null || initialState === void 0 ? void 0 : (_initialState$columns7 = initialState.columns) === null || _initialState$columns7 === void 0 ? void 0 : _initialState$columns7.orderedFields) !== null && _initialState$columns6 !== void 0 ? _initialState$columns6 : columns.map(c => c.field);
|
|
2076
|
+
const finalSearch = getFinalSearch({
|
|
2077
|
+
localStorageVersion,
|
|
2078
|
+
search: decodedSearch,
|
|
2079
|
+
filterModel,
|
|
2080
|
+
sortModel,
|
|
2081
|
+
paginationModel,
|
|
2082
|
+
columnsVisibilityModel: columnVisibilityModel,
|
|
2083
|
+
pinnedColumnsModel,
|
|
2084
|
+
density,
|
|
2085
|
+
columnOrderModel,
|
|
2086
|
+
defaultColumnOrder,
|
|
2087
|
+
columns
|
|
2088
|
+
});
|
|
2089
|
+
const internalSearchString = urlSearchParamsToString(finalSearch);
|
|
2090
|
+
// Convert to display format for the URL
|
|
2091
|
+
const displaySearchString = buildQueryParamsString(internalSearchString);
|
|
2092
|
+
// Apply budget compression to the display format string
|
|
2093
|
+
const compressedSearchString = applyCompressionToDisplaySearch(displaySearchString);
|
|
2094
|
+
const compressedWithoutQuestion = compressedSearchString.startsWith('?') ? compressedSearchString.slice(1) : compressedSearchString;
|
|
2095
|
+
|
|
2096
|
+
// Compare with original search (without leading ?)
|
|
2097
|
+
const originalSearchWithoutQuestion = search.startsWith('?') ? search.slice(1) : search;
|
|
2098
|
+
const pendingSearch = !areSearchStringsEqual(compressedWithoutQuestion, originalSearchWithoutQuestion) ? compressedWithoutQuestion : null;
|
|
2099
|
+
return {
|
|
2100
|
+
filterModel,
|
|
2101
|
+
sortModel,
|
|
2102
|
+
paginationModel,
|
|
2103
|
+
columnVisibilityModel,
|
|
2104
|
+
pinnedColumnsModel,
|
|
2105
|
+
density,
|
|
2106
|
+
columnOrderModel,
|
|
2107
|
+
pendingSearch
|
|
2108
|
+
};
|
|
2109
|
+
};
|
|
2110
|
+
const updateUrl = (_ref2, search, localStorageVersion, historyReplace, columns) => {
|
|
2111
|
+
let {
|
|
2112
|
+
filterModel,
|
|
2113
|
+
sortModel,
|
|
2114
|
+
paginationModel,
|
|
2115
|
+
columnsModel: columnsVisibilityModel,
|
|
2116
|
+
pinnedColumnsModel,
|
|
2117
|
+
density,
|
|
2118
|
+
columnOrderModel,
|
|
2119
|
+
defaultColumnOrder
|
|
2120
|
+
} = _ref2;
|
|
2121
|
+
// Convert from display format to internal format if needed
|
|
2122
|
+
const decodedSearch = getDecodedSearchFromUrl(search, columns);
|
|
2123
|
+
const newSearch = getFinalSearch({
|
|
2124
|
+
search: decodedSearch,
|
|
2125
|
+
localStorageVersion,
|
|
2126
|
+
filterModel,
|
|
2127
|
+
sortModel,
|
|
2128
|
+
paginationModel,
|
|
2129
|
+
columnsVisibilityModel,
|
|
2130
|
+
pinnedColumnsModel,
|
|
2131
|
+
density,
|
|
2132
|
+
columnOrderModel,
|
|
2133
|
+
defaultColumnOrder,
|
|
2134
|
+
columns
|
|
2135
|
+
});
|
|
2136
|
+
const internalSearchString = urlSearchParamsToString(newSearch);
|
|
2137
|
+
// Convert to display format for the URL
|
|
2138
|
+
const displaySearchString = buildQueryParamsString(internalSearchString);
|
|
2139
|
+
// Apply budget compression to the display format string
|
|
2140
|
+
const compressedSearchString = applyCompressionToDisplaySearch(displaySearchString);
|
|
2141
|
+
const compressedWithoutQuestion = compressedSearchString.startsWith('?') ? compressedSearchString.slice(1) : compressedSearchString;
|
|
2142
|
+
|
|
2143
|
+
// Compare with original search (without leading ?)
|
|
2144
|
+
const originalSearchWithoutQuestion = search.startsWith('?') ? search.slice(1) : search;
|
|
2145
|
+
if (!areSearchStringsEqual(compressedWithoutQuestion, originalSearchWithoutQuestion)) {
|
|
2146
|
+
historyReplace(compressedWithoutQuestion);
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
|
|
2150
|
+
// Note: this is a comparator to sort the filters, not pure comparison
|
|
2151
|
+
// do not use it for equivalence (e.g. with value `3` and undefined we
|
|
2152
|
+
// will get 0).
|
|
2153
|
+
const compareFilters = (firstFilter, secondFilter) => {
|
|
2154
|
+
if (firstFilter.field < secondFilter.field) {
|
|
2155
|
+
return -1;
|
|
2156
|
+
} else if (firstFilter.field > secondFilter.field) {
|
|
2157
|
+
return 1;
|
|
2158
|
+
}
|
|
2159
|
+
if (firstFilter.operator === undefined || secondFilter.operator === undefined) {
|
|
2160
|
+
return 0;
|
|
2161
|
+
}
|
|
2162
|
+
if (firstFilter.operator < secondFilter.operator) {
|
|
2163
|
+
return -1;
|
|
2164
|
+
} else if (firstFilter.operator > secondFilter.operator) {
|
|
2165
|
+
return 1;
|
|
2166
|
+
}
|
|
2167
|
+
if (firstFilter.value < secondFilter.value) {
|
|
2168
|
+
return -1;
|
|
2169
|
+
} else if (firstFilter.value > secondFilter.value) {
|
|
2170
|
+
return 1;
|
|
2171
|
+
}
|
|
2172
|
+
return 0;
|
|
2173
|
+
};
|
|
2174
|
+
const areFiltersEquivalent = (firstFilter, secondFilter) => {
|
|
2175
|
+
return firstFilter.field === secondFilter.field && firstFilter.operator === secondFilter.operator && firstFilter.value === secondFilter.value;
|
|
2176
|
+
};
|
|
2177
|
+
const areFilterModelsEquivalent = (filterModel, filterModelToMatch) => {
|
|
2178
|
+
const {
|
|
2179
|
+
items,
|
|
2180
|
+
logicOperator
|
|
2181
|
+
} = filterModel;
|
|
2182
|
+
const {
|
|
2183
|
+
items: itemsToMatch,
|
|
2184
|
+
logicOperator: logicOperatorToMatch
|
|
2185
|
+
} = filterModelToMatch;
|
|
2186
|
+
if (logicOperator !== logicOperatorToMatch) {
|
|
2187
|
+
return false;
|
|
2188
|
+
}
|
|
2189
|
+
if (items.length !== itemsToMatch.length) {
|
|
2190
|
+
return false;
|
|
2191
|
+
}
|
|
2192
|
+
items.sort(compareFilters);
|
|
2193
|
+
itemsToMatch.sort(compareFilters);
|
|
2194
|
+
for (let i = 0; i < items.length; i++) {
|
|
2195
|
+
const filter = items[i];
|
|
2196
|
+
const filterToCompare = itemsToMatch[i];
|
|
2197
|
+
|
|
2198
|
+
// compareFilters return 0 if and only if the filters have the same
|
|
2199
|
+
// field, operator, and value
|
|
2200
|
+
if (!areFiltersEquivalent(filter, filterToCompare)) {
|
|
2201
|
+
return false;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
return true;
|
|
2205
|
+
};
|
|
2206
|
+
|
|
2207
|
+
/**
|
|
2208
|
+
* Decompress any `~`-prefixed param values in a search string.
|
|
2209
|
+
* Also handles the `_filters` aggregate param: expands it back into individual filter params.
|
|
2210
|
+
*/
|
|
2211
|
+
const decompressSearchParams = search => {
|
|
2212
|
+
if (!search || !search.includes('~')) return search;
|
|
2213
|
+
const cleanSearch = search.startsWith('?') ? search.slice(1) : search;
|
|
2214
|
+
const params = new URLSearchParams(cleanSearch);
|
|
2215
|
+
const result = new URLSearchParams();
|
|
2216
|
+
for (const [key, value] of params) {
|
|
2217
|
+
if (key === '_filters' && isCompressed(value)) {
|
|
2218
|
+
// Aggregate filter: decompress JSON back into individual filter params
|
|
2219
|
+
try {
|
|
2220
|
+
const json = decompressValue(value);
|
|
2221
|
+
const filterRecord = JSON.parse(json);
|
|
2222
|
+
for (const [filterKey, filterValue] of Object.entries(filterRecord)) {
|
|
2223
|
+
result.set(filterKey, filterValue);
|
|
2224
|
+
}
|
|
2225
|
+
} catch {
|
|
2226
|
+
// If decompression fails, keep the original
|
|
2227
|
+
result.set(key, value);
|
|
2228
|
+
}
|
|
2229
|
+
} else {
|
|
2230
|
+
result.set(key, decompressValue(value));
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
return '?' + result.toString();
|
|
2234
|
+
};
|
|
2235
|
+
|
|
2236
|
+
/**
|
|
2237
|
+
* Apply budget compression to a display-format search string.
|
|
2238
|
+
* Parses the string into a Map, runs applyBudgetCompression, and reassembles.
|
|
2239
|
+
*/
|
|
2240
|
+
const applyCompressionToDisplaySearch = displaySearch => {
|
|
2241
|
+
const cleanSearch = displaySearch.startsWith('?') ? displaySearch.slice(1) : displaySearch;
|
|
2242
|
+
if (!cleanSearch) return displaySearch;
|
|
2243
|
+
const params = new Map();
|
|
2244
|
+
for (const part of cleanSearch.split('&')) {
|
|
2245
|
+
const eqIndex = part.indexOf('=');
|
|
2246
|
+
if (eqIndex === -1) continue;
|
|
2247
|
+
const key = part.slice(0, eqIndex);
|
|
2248
|
+
const value = part.slice(eqIndex + 1);
|
|
2249
|
+
params.set(key, value);
|
|
2250
|
+
}
|
|
2251
|
+
const compressed = applyBudgetCompression(params);
|
|
2252
|
+
const parts = [];
|
|
2253
|
+
for (const [key, value] of compressed) {
|
|
2254
|
+
parts.push(`${key}=${value}`);
|
|
2255
|
+
}
|
|
2256
|
+
return '?' + parts.join('&');
|
|
2257
|
+
};
|
|
2258
|
+
|
|
2259
|
+
// Get and Set data from LocalStorage WITHOUT useState
|
|
2260
|
+
const useFetchState = (defaultValue, key) => {
|
|
2261
|
+
let stickyValue = null;
|
|
2262
|
+
try {
|
|
2263
|
+
stickyValue = window.localStorage.getItem(key);
|
|
2264
|
+
} catch (e) {
|
|
2265
|
+
console.error('StatefulDataGrid: error getting item from local storage: ', e);
|
|
2266
|
+
}
|
|
2267
|
+
const parsedValue = stickyValue !== null && stickyValue !== undefined && stickyValue !== 'undefined' ? JSON.parse(stickyValue) : defaultValue;
|
|
2268
|
+
const updateValue = useCallback(value => {
|
|
2269
|
+
try {
|
|
2270
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
2271
|
+
} catch (e) {
|
|
2272
|
+
console.error('StatefulDataGrid: error setting item into local storage: ', e);
|
|
2273
|
+
}
|
|
2274
|
+
}, [key]);
|
|
2275
|
+
return [parsedValue, updateValue];
|
|
2276
|
+
};
|
|
2277
|
+
|
|
2278
|
+
const useTableStates = (id, version) => {
|
|
2279
|
+
const [paginationModel, setPaginationModel] = useFetchState('', buildStorageKey({
|
|
2280
|
+
id,
|
|
2281
|
+
version,
|
|
2282
|
+
category: PAGINATION_MODEL_KEY
|
|
2283
|
+
}));
|
|
2284
|
+
const [sortModel, setSortModel] = useFetchState('', buildStorageKey({
|
|
2285
|
+
id,
|
|
2286
|
+
version,
|
|
2287
|
+
category: SORT_MODEL_KEY
|
|
2288
|
+
}));
|
|
2289
|
+
const [localStorageFilters, setLocalStorageFilters] = useFetchState('', buildStorageKey({
|
|
2290
|
+
id,
|
|
2291
|
+
version,
|
|
2292
|
+
category: FILTER_SEARCH_KEY
|
|
2293
|
+
}));
|
|
2294
|
+
const [visibilityModelLocalStorage, setVisibilityModelLocalStorage] = useFetchState('', buildStorageKey({
|
|
2295
|
+
id,
|
|
2296
|
+
version,
|
|
2297
|
+
category: VISIBILITY_MODEL_KEY
|
|
2298
|
+
}));
|
|
2299
|
+
const [pinnedColumns, setPinnedColumns] = useFetchState('_pinnedColumnsLeft=[]&_pinnedColumnsRight=[]', buildStorageKey({
|
|
2300
|
+
id,
|
|
2301
|
+
version,
|
|
2302
|
+
category: PINNED_COLUMNS
|
|
2303
|
+
}));
|
|
2304
|
+
const [dimensionModel, setDimensionModel] = useFetchState({}, buildStorageKey({
|
|
2305
|
+
id,
|
|
2306
|
+
version,
|
|
2307
|
+
category: DIMENSION_MODEL_KEY
|
|
2308
|
+
}));
|
|
2309
|
+
const [densityModel, setDensityModel] = useFetchState('', buildStorageKey({
|
|
2310
|
+
id,
|
|
2311
|
+
version,
|
|
2312
|
+
category: DENSITY_MODEL_KEY
|
|
2313
|
+
}));
|
|
2314
|
+
const [columnOrderModel, setColumnOrderModel] = useFetchState('', buildStorageKey({
|
|
2315
|
+
id,
|
|
2316
|
+
version,
|
|
2317
|
+
category: COLUMN_ORDER_MODEL_KEY
|
|
2318
|
+
}));
|
|
2319
|
+
return {
|
|
2320
|
+
paginationModel,
|
|
2321
|
+
setPaginationModel,
|
|
2322
|
+
sortModel,
|
|
2323
|
+
setSortModel,
|
|
2324
|
+
localStorageFilters,
|
|
2325
|
+
setLocalStorageFilters,
|
|
2326
|
+
visibilityModelLocalStorage,
|
|
2327
|
+
setVisibilityModelLocalStorage,
|
|
2328
|
+
pinnedColumns,
|
|
2329
|
+
setPinnedColumns,
|
|
2330
|
+
dimensionModel,
|
|
2331
|
+
setDimensionModel,
|
|
2332
|
+
densityModel,
|
|
2333
|
+
setDensityModel,
|
|
2334
|
+
columnOrderModel,
|
|
2335
|
+
setColumnOrderModel
|
|
2336
|
+
};
|
|
2337
|
+
};
|
|
2338
|
+
|
|
2339
|
+
/**
|
|
2340
|
+
* Deep-equal comparison for plain objects / arrays.
|
|
2341
|
+
* Used to stabilise parsed model references so that MUI v8 does not
|
|
2342
|
+
* reset pagination on every render.
|
|
2343
|
+
*/
|
|
2344
|
+
function isDeepEqual(a, b) {
|
|
2345
|
+
if (a === b) return true;
|
|
2346
|
+
if (a == null || b == null) return false;
|
|
2347
|
+
if (typeof a !== typeof b) return false;
|
|
2348
|
+
if (typeof a !== 'object') return false;
|
|
2349
|
+
const aObj = a;
|
|
2350
|
+
const bObj = b;
|
|
2351
|
+
const aKeys = Object.keys(aObj);
|
|
2352
|
+
const bKeys = Object.keys(bObj);
|
|
2353
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
2354
|
+
return aKeys.every(key => isDeepEqual(aObj[key], bObj[key]));
|
|
2355
|
+
}
|
|
2356
|
+
const useStatefulTable = props => {
|
|
2357
|
+
var _initialState$columns, _initialState$columns2;
|
|
2358
|
+
const {
|
|
2359
|
+
apiRef,
|
|
2360
|
+
initialState,
|
|
2361
|
+
columns: propsColumns,
|
|
2362
|
+
onColumnVisibilityModelChange: propsOnColumnVisibilityModelChange,
|
|
2363
|
+
onColumnWidthChange: propsOnColumnWidthChange,
|
|
2364
|
+
onFilterModelChange: propsOnFilterModelChange,
|
|
2365
|
+
onPaginationModelChange: propsOnPaginationModelChange,
|
|
2366
|
+
onPinnedColumnsChange: propsOnPinnedColumnsChange,
|
|
2367
|
+
onSortModelChange: propsOnSortModelChange,
|
|
2368
|
+
useRouter,
|
|
2369
|
+
localStorageVersion = 1,
|
|
2370
|
+
previousLocalStorageVersions = []
|
|
2371
|
+
} = props;
|
|
2372
|
+
const {
|
|
2373
|
+
search,
|
|
2374
|
+
pathname,
|
|
2375
|
+
historyReplace
|
|
2376
|
+
} = useRouter();
|
|
2377
|
+
const id = pathname;
|
|
2378
|
+
|
|
2379
|
+
// States and setters persisted in the local storage for this table
|
|
2380
|
+
const {
|
|
2381
|
+
paginationModel,
|
|
2382
|
+
setPaginationModel,
|
|
2383
|
+
sortModel,
|
|
2384
|
+
setSortModel,
|
|
2385
|
+
localStorageFilters,
|
|
2386
|
+
setLocalStorageFilters,
|
|
2387
|
+
visibilityModelLocalStorage,
|
|
2388
|
+
setVisibilityModelLocalStorage,
|
|
2389
|
+
pinnedColumns,
|
|
2390
|
+
setPinnedColumns,
|
|
2391
|
+
dimensionModel,
|
|
2392
|
+
setDimensionModel,
|
|
2393
|
+
densityModel,
|
|
2394
|
+
setDensityModel,
|
|
2395
|
+
columnOrderModel: localStorageColumnOrder,
|
|
2396
|
+
setColumnOrderModel: setLocalStorageColumnOrder
|
|
2397
|
+
} = useTableStates(id, localStorageVersion);
|
|
2398
|
+
|
|
2399
|
+
// clearing up old version keys, triggering only on first render
|
|
2400
|
+
useEffect(() => clearPreviousVersionStorage(id, previousLocalStorageVersions), [id, previousLocalStorageVersions]);
|
|
2401
|
+
const onColumnDimensionChange = useCallback(_ref => {
|
|
2402
|
+
let {
|
|
2403
|
+
newWidth,
|
|
2404
|
+
field
|
|
2405
|
+
} = _ref;
|
|
2406
|
+
setDimensionModel(_objectSpread2(_objectSpread2({}, dimensionModel), {}, {
|
|
2407
|
+
[field]: newWidth
|
|
2408
|
+
}));
|
|
2409
|
+
}, [dimensionModel, setDimensionModel]);
|
|
2410
|
+
const {
|
|
2411
|
+
filterModel: filterParsed,
|
|
2412
|
+
sortModel: sortModelParsed,
|
|
2413
|
+
paginationModel: paginationModelParsed,
|
|
2414
|
+
columnVisibilityModel: visibilityModel,
|
|
2415
|
+
pinnedColumnsModel,
|
|
2416
|
+
density: densityParsed,
|
|
2417
|
+
columnOrderModel: columnOrderParsed,
|
|
2418
|
+
pendingSearch
|
|
2419
|
+
} = getModelsParsedOrUpdateLocalStorage(search || '', localStorageVersion, propsColumns, initialState, {
|
|
2420
|
+
localStorageFilters,
|
|
2421
|
+
setLocalStorageFilters,
|
|
2422
|
+
localStorageSorting: sortModel,
|
|
2423
|
+
setLocalStorageSorting: setSortModel,
|
|
2424
|
+
localStoragePagination: paginationModel,
|
|
2425
|
+
setLocalStoragePagination: setPaginationModel,
|
|
2426
|
+
localStorageColumnsVisibility: visibilityModelLocalStorage,
|
|
2427
|
+
setLocalStorageColumnsVisibility: setVisibilityModelLocalStorage,
|
|
2428
|
+
localStoragePinnedColumns: pinnedColumns,
|
|
2429
|
+
setLocalStoragePinnedColumns: setPinnedColumns,
|
|
2430
|
+
localStorageDensity: densityModel,
|
|
2431
|
+
setLocalStorageDensity: setDensityModel,
|
|
2432
|
+
localStorageColumnOrder,
|
|
2433
|
+
setLocalStorageColumnOrder
|
|
2434
|
+
});
|
|
2435
|
+
|
|
2436
|
+
// Sync URL in an effect rather than during render to comply with React rules
|
|
2437
|
+
useEffect(() => {
|
|
2438
|
+
if (pendingSearch !== null) {
|
|
2439
|
+
historyReplace(pendingSearch);
|
|
2440
|
+
}
|
|
2441
|
+
}, [pendingSearch, historyReplace]);
|
|
2442
|
+
|
|
2443
|
+
// Stabilise parsed model references to prevent MUI v8 from resetting
|
|
2444
|
+
// pagination on every render due to new object identity.
|
|
2445
|
+
const filterParsedRef = useRef(filterParsed);
|
|
2446
|
+
if (!isDeepEqual(filterParsedRef.current, filterParsed)) {
|
|
2447
|
+
filterParsedRef.current = filterParsed;
|
|
2448
|
+
}
|
|
2449
|
+
const sortModelParsedRef = useRef(sortModelParsed);
|
|
2450
|
+
if (!isDeepEqual(sortModelParsedRef.current, sortModelParsed)) {
|
|
2451
|
+
sortModelParsedRef.current = sortModelParsed;
|
|
2452
|
+
}
|
|
2453
|
+
const paginationModelParsedRef = useRef(paginationModelParsed);
|
|
2454
|
+
if (!isDeepEqual(paginationModelParsedRef.current, paginationModelParsed)) {
|
|
2455
|
+
paginationModelParsedRef.current = paginationModelParsed;
|
|
2456
|
+
}
|
|
2457
|
+
const visibilityModelRef = useRef(visibilityModel);
|
|
2458
|
+
if (!isDeepEqual(visibilityModelRef.current, visibilityModel)) {
|
|
2459
|
+
visibilityModelRef.current = visibilityModel;
|
|
2460
|
+
}
|
|
2461
|
+
const pinnedColumnsModelRef = useRef(pinnedColumnsModel);
|
|
2462
|
+
if (!isDeepEqual(pinnedColumnsModelRef.current, pinnedColumnsModel)) {
|
|
2463
|
+
pinnedColumnsModelRef.current = pinnedColumnsModel;
|
|
2464
|
+
}
|
|
2465
|
+
const columnOrderParsedRef = useRef(columnOrderParsed);
|
|
2466
|
+
if (!isDeepEqual(columnOrderParsedRef.current, columnOrderParsed)) {
|
|
2467
|
+
columnOrderParsedRef.current = columnOrderParsed;
|
|
2468
|
+
}
|
|
2469
|
+
const columns = useMemo(() => propsColumns.map(column => {
|
|
2470
|
+
return _objectSpread2(_objectSpread2({}, column), {}, {
|
|
2471
|
+
width: dimensionModel[column.field] || column.width || 100
|
|
2472
|
+
});
|
|
2473
|
+
}), [propsColumns, dimensionModel]);
|
|
2474
|
+
if (apiRef.current) {
|
|
2475
|
+
/** Add resetPage method to apiRef. */
|
|
2476
|
+
apiRef.current.resetPage = () => {
|
|
2477
|
+
var _apiRef$current;
|
|
2478
|
+
(_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 ? void 0 : _apiRef$current.setPage(0);
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
const defaultColumnOrder = (_initialState$columns = initialState === null || initialState === void 0 ? void 0 : (_initialState$columns2 = initialState.columns) === null || _initialState$columns2 === void 0 ? void 0 : _initialState$columns2.orderedFields) !== null && _initialState$columns !== void 0 ? _initialState$columns : propsColumns.map(c => c.field);
|
|
2482
|
+
|
|
2483
|
+
// Subscribe to density changes via stateChange event (MUI v6 has no densityChange event)
|
|
2484
|
+
useEffect(() => {
|
|
2485
|
+
const api = apiRef.current;
|
|
2486
|
+
if (!(api !== null && api !== void 0 && api.subscribeEvent)) return;
|
|
2487
|
+
let prevDensity = densityParsed;
|
|
2488
|
+
const unsub = api.subscribeEvent('stateChange', () => {
|
|
2489
|
+
const currentDensity = api.state.density;
|
|
2490
|
+
if (currentDensity !== prevDensity) {
|
|
2491
|
+
prevDensity = currentDensity;
|
|
2492
|
+
updateUrl({
|
|
2493
|
+
filterModel: filterParsed,
|
|
2494
|
+
sortModel: sortModelParsed,
|
|
2495
|
+
paginationModel: paginationModelParsed,
|
|
2496
|
+
columnsModel: api.state.columns.columnVisibilityModel,
|
|
2497
|
+
pinnedColumnsModel: pinnedColumnsModel,
|
|
2498
|
+
density: currentDensity,
|
|
2499
|
+
columnOrderModel: columnOrderParsed,
|
|
2500
|
+
defaultColumnOrder
|
|
2501
|
+
}, search, localStorageVersion, historyReplace, columns);
|
|
2502
|
+
}
|
|
2503
|
+
});
|
|
2504
|
+
return unsub;
|
|
2505
|
+
}, [apiRef, densityParsed, filterParsed, sortModelParsed, paginationModelParsed, pinnedColumnsModel, columnOrderParsed, defaultColumnOrder, search, localStorageVersion, historyReplace, columns]);
|
|
2506
|
+
|
|
2507
|
+
// Subscribe to column order changes via columnOrderChange (drag-drop) and columnIndexChange (programmatic setColumnIndex)
|
|
2508
|
+
useEffect(() => {
|
|
2509
|
+
const api = apiRef.current;
|
|
2510
|
+
if (!(api !== null && api !== void 0 && api.subscribeEvent)) return;
|
|
2511
|
+
const handleColumnOrderChange = () => {
|
|
2512
|
+
const orderedFields = api.state.columns.orderedFields;
|
|
2513
|
+
if (orderedFields && !isDeepEqual(orderedFields, columnOrderParsed)) {
|
|
2514
|
+
updateUrl({
|
|
2515
|
+
filterModel: filterParsed,
|
|
2516
|
+
sortModel: sortModelParsed,
|
|
2517
|
+
paginationModel: paginationModelParsed,
|
|
2518
|
+
columnsModel: api.state.columns.columnVisibilityModel,
|
|
2519
|
+
pinnedColumnsModel,
|
|
2520
|
+
density: densityParsed,
|
|
2521
|
+
columnOrderModel: orderedFields,
|
|
2522
|
+
defaultColumnOrder
|
|
2523
|
+
}, search, localStorageVersion, historyReplace, columns);
|
|
2524
|
+
}
|
|
2525
|
+
};
|
|
2526
|
+
const unsub1 = api.subscribeEvent('columnOrderChange', handleColumnOrderChange);
|
|
2527
|
+
const unsub2 = api.subscribeEvent('columnIndexChange', handleColumnOrderChange);
|
|
2528
|
+
return () => {
|
|
2529
|
+
unsub1();
|
|
2530
|
+
unsub2();
|
|
2531
|
+
};
|
|
2532
|
+
}, [apiRef, columnOrderParsed, defaultColumnOrder, filterParsed, sortModelParsed, paginationModelParsed, pinnedColumnsModel, densityParsed, search, localStorageVersion, historyReplace, columns]);
|
|
2533
|
+
|
|
2534
|
+
// Helper to build the current DataGridModel for updateUrl calls
|
|
2535
|
+
const buildModel = function () {
|
|
2536
|
+
var _apiRef$current$state, _apiRef$current2;
|
|
2537
|
+
let overrides = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
2538
|
+
return _objectSpread2({
|
|
2539
|
+
filterModel: filterParsed,
|
|
2540
|
+
sortModel: sortModelParsed,
|
|
2541
|
+
paginationModel: paginationModelParsed,
|
|
2542
|
+
columnsModel: (_apiRef$current$state = (_apiRef$current2 = apiRef.current) === null || _apiRef$current2 === void 0 ? void 0 : _apiRef$current2.state.columns.columnVisibilityModel) !== null && _apiRef$current$state !== void 0 ? _apiRef$current$state : {},
|
|
2543
|
+
pinnedColumnsModel: pinnedColumnsModel,
|
|
2544
|
+
density: densityParsed,
|
|
2545
|
+
columnOrderModel: columnOrderParsed,
|
|
2546
|
+
defaultColumnOrder
|
|
2547
|
+
}, overrides);
|
|
2548
|
+
};
|
|
2549
|
+
|
|
2550
|
+
// Track last emitted values for deep-equal guards to avoid feedback loops.
|
|
2551
|
+
// Initialised from the current parsed values; updated only when we actually fire.
|
|
2552
|
+
const lastEmittedFilterRef = useRef(filterParsed);
|
|
2553
|
+
const lastEmittedSortRef = useRef(sortModelParsed);
|
|
2554
|
+
const lastEmittedPaginationRef = useRef(paginationModelParsed);
|
|
2555
|
+
return {
|
|
2556
|
+
apiRef,
|
|
2557
|
+
columns,
|
|
2558
|
+
density: densityParsed,
|
|
2559
|
+
columnOrderModel: columnOrderParsedRef.current,
|
|
2560
|
+
onFilterModelChange: (model, details) => {
|
|
2561
|
+
const filterModel = _objectSpread2(_objectSpread2({}, model), {}, {
|
|
2562
|
+
items: model.items.map(item => {
|
|
2563
|
+
var _apiRef$current3;
|
|
2564
|
+
const column = (_apiRef$current3 = apiRef.current) === null || _apiRef$current3 === void 0 ? void 0 : _apiRef$current3.getColumn(item.field);
|
|
2565
|
+
item.type = (column === null || column === void 0 ? void 0 : column.type) || 'string';
|
|
2566
|
+
return item;
|
|
2567
|
+
}),
|
|
2568
|
+
quickFilterValues: model.quickFilterValues || []
|
|
2569
|
+
});
|
|
2570
|
+
if (isDeepEqual(filterModel, lastEmittedFilterRef.current)) return;
|
|
2571
|
+
lastEmittedFilterRef.current = filterModel;
|
|
2572
|
+
updateUrl(buildModel({
|
|
2573
|
+
filterModel
|
|
2574
|
+
}), search, localStorageVersion, historyReplace, columns);
|
|
2575
|
+
propsOnFilterModelChange === null || propsOnFilterModelChange === void 0 ? void 0 : propsOnFilterModelChange(filterModel, details);
|
|
2576
|
+
},
|
|
2577
|
+
filterModel: filterParsedRef.current,
|
|
2578
|
+
onSortModelChange: (model, details) => {
|
|
2579
|
+
if (isDeepEqual(model, lastEmittedSortRef.current)) return;
|
|
2580
|
+
lastEmittedSortRef.current = model;
|
|
2581
|
+
updateUrl(buildModel({
|
|
2582
|
+
sortModel: model
|
|
2583
|
+
}), search, localStorageVersion, historyReplace, columns);
|
|
2584
|
+
propsOnSortModelChange === null || propsOnSortModelChange === void 0 ? void 0 : propsOnSortModelChange(model, details);
|
|
2585
|
+
},
|
|
2586
|
+
sortModel: sortModelParsedRef.current,
|
|
2587
|
+
onPinnedColumnsChange: (pinnedColumns, details) => {
|
|
2588
|
+
updateUrl(buildModel({
|
|
2589
|
+
pinnedColumnsModel: pinnedColumns
|
|
2590
|
+
}), search, localStorageVersion, historyReplace, columns);
|
|
2591
|
+
propsOnPinnedColumnsChange === null || propsOnPinnedColumnsChange === void 0 ? void 0 : propsOnPinnedColumnsChange(pinnedColumns, details);
|
|
2592
|
+
},
|
|
2593
|
+
pinnedColumns: pinnedColumnsModelRef.current,
|
|
2594
|
+
paginationModel: paginationModelParsedRef.current,
|
|
2595
|
+
onPaginationModelChange: (model, details) => {
|
|
2596
|
+
const paginationModel = _objectSpread2(_objectSpread2({}, model), {}, {
|
|
2597
|
+
direction: paginationModelParsed.page < model.page ? 'next' : 'back'
|
|
2598
|
+
});
|
|
2599
|
+
if (isDeepEqual(paginationModel, lastEmittedPaginationRef.current)) return;
|
|
2600
|
+
lastEmittedPaginationRef.current = paginationModel;
|
|
2601
|
+
updateUrl(buildModel({
|
|
2602
|
+
paginationModel
|
|
2603
|
+
}), search, localStorageVersion, historyReplace, columns);
|
|
2604
|
+
propsOnPaginationModelChange === null || propsOnPaginationModelChange === void 0 ? void 0 : propsOnPaginationModelChange(paginationModel, details);
|
|
2605
|
+
},
|
|
2606
|
+
columnVisibilityModel: visibilityModelRef.current,
|
|
2607
|
+
onColumnVisibilityModelChange: (columnsVisibilityModel, details) => {
|
|
2608
|
+
updateUrl(buildModel({
|
|
2609
|
+
columnsModel: columnsVisibilityModel
|
|
2610
|
+
}), search, localStorageVersion, historyReplace, columns);
|
|
2611
|
+
propsOnColumnVisibilityModelChange === null || propsOnColumnVisibilityModelChange === void 0 ? void 0 : propsOnColumnVisibilityModelChange(columnsVisibilityModel, details);
|
|
2612
|
+
},
|
|
2613
|
+
onColumnWidthChange: (params, event, details) => {
|
|
2614
|
+
propsOnColumnWidthChange === null || propsOnColumnWidthChange === void 0 ? void 0 : propsOnColumnWidthChange(params, event, details);
|
|
2615
|
+
onColumnDimensionChange({
|
|
2616
|
+
newWidth: params.width,
|
|
2617
|
+
field: params.colDef.field
|
|
2618
|
+
});
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
};
|
|
2622
|
+
|
|
2623
|
+
const _excluded = ["apiRef", "autoHeight", "className", "columns", "slots", "slotProps", "filterModel", "columnVisibilityModel", "pinnedColumns", "sortModel", "paginationModel", "height", "hideToolbar", "initialState", "isRowSelectable", "license", "localStorageVersion", "previousLocalStorageVersions", "onFilterModelChange", "rowSelectionModel", "onColumnWidthChange", "onPaginationModelChange", "onRowSelectionModelChange", "onColumnVisibilityModelChange", "onPinnedColumnsChange", "onSortModelChange", "pagination", "paginationPlacement", "paginationProps", "rows", "pageSizeOptions", "sx", "theme", "useRouter", "paginationMode", "rowCount", "density", "dataSource", "filterMode", "sortingMode"];
|
|
2624
|
+
const COMPONENT_NAME = 'DataGrid';
|
|
2625
|
+
const CLASSNAME = 'redsift-datagrid';
|
|
2626
|
+
|
|
2627
|
+
/**
|
|
2628
|
+
* StatefulDataGrid extends DataGrid with automatic state persistence to localStorage.
|
|
2629
|
+
* Preserves filters, sorting, pagination, column visibility, and column widths
|
|
2630
|
+
* across page refreshes.
|
|
2631
|
+
*
|
|
2632
|
+
* Use when users need persistent table preferences. Requires unique `localStorageKey`.
|
|
2633
|
+
*
|
|
2634
|
+
* @example
|
|
2635
|
+
* // Basic stateful grid (persists to localStorage)
|
|
2636
|
+
* <StatefulDataGrid
|
|
2637
|
+
* localStorageKey="users-table"
|
|
2638
|
+
* columns={columns}
|
|
2639
|
+
* rows={users}
|
|
2640
|
+
* pagination
|
|
2641
|
+
* />
|
|
2642
|
+
*
|
|
2643
|
+
* @example
|
|
2644
|
+
* // With version migration (clears old preferences)
|
|
2645
|
+
* <StatefulDataGrid
|
|
2646
|
+
* localStorageKey="reports-table"
|
|
2647
|
+
* localStorageVersion="v2"
|
|
2648
|
+
* previousLocalStorageVersions={['v1']}
|
|
2649
|
+
* columns={columns}
|
|
2650
|
+
* rows={reports}
|
|
2651
|
+
* />
|
|
2652
|
+
*
|
|
2653
|
+
* @example
|
|
2654
|
+
* // With URL state sync (shareable filtered views)
|
|
2655
|
+
* <StatefulDataGrid
|
|
2656
|
+
* localStorageKey="domains-table"
|
|
2657
|
+
* useRouter={true}
|
|
2658
|
+
* columns={columns}
|
|
2659
|
+
* rows={domains}
|
|
2660
|
+
* pagination
|
|
2661
|
+
* />
|
|
2662
|
+
*
|
|
2663
|
+
* @example
|
|
2664
|
+
* // All features combined
|
|
2665
|
+
* <StatefulDataGrid
|
|
2666
|
+
* localStorageKey="audit-log"
|
|
2667
|
+
* localStorageVersion="v3"
|
|
2668
|
+
* previousLocalStorageVersions={['v1', 'v2']}
|
|
2669
|
+
* useRouter={true}
|
|
2670
|
+
* columns={columns}
|
|
2671
|
+
* rows={logs}
|
|
2672
|
+
* pagination
|
|
2673
|
+
* pageSize={50}
|
|
2674
|
+
* checkboxSelection
|
|
2675
|
+
* />
|
|
2676
|
+
*/
|
|
2677
|
+
|
|
2678
|
+
const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
|
|
2679
|
+
const datagridRef = ref || useRef();
|
|
2680
|
+
const {
|
|
2681
|
+
apiRef: propsApiRef,
|
|
2682
|
+
autoHeight,
|
|
2683
|
+
className,
|
|
2684
|
+
columns,
|
|
2685
|
+
slots,
|
|
2686
|
+
slotProps,
|
|
2687
|
+
filterModel: propsFilterModel,
|
|
2688
|
+
columnVisibilityModel: propsColumnVisibilityModel,
|
|
2689
|
+
pinnedColumns: propsPinnedColumns,
|
|
2690
|
+
sortModel: propsSortModel,
|
|
2691
|
+
paginationModel: propsPaginationModel,
|
|
2692
|
+
height: propsHeight,
|
|
2693
|
+
hideToolbar,
|
|
2694
|
+
initialState,
|
|
2695
|
+
isRowSelectable,
|
|
2696
|
+
license = process.env.MUI_LICENSE_KEY,
|
|
2697
|
+
localStorageVersion,
|
|
2698
|
+
previousLocalStorageVersions,
|
|
2699
|
+
onFilterModelChange: propsOnFilterModelChange,
|
|
2700
|
+
rowSelectionModel: propsRowSelectionModel,
|
|
2701
|
+
onColumnWidthChange: propsOnColumnWidthChange,
|
|
2702
|
+
onPaginationModelChange: propsOnPaginationModelChange,
|
|
2703
|
+
onRowSelectionModelChange: propsOnRowSelectionModelChange,
|
|
2704
|
+
onColumnVisibilityModelChange: propsOnColumnVisibilityModelChange,
|
|
2705
|
+
onPinnedColumnsChange: propsOnPinnedColumnsChange,
|
|
2706
|
+
onSortModelChange: propsOnSortModelChange,
|
|
2707
|
+
pagination,
|
|
2708
|
+
paginationPlacement = 'both',
|
|
2709
|
+
paginationProps,
|
|
2710
|
+
rows,
|
|
2711
|
+
pageSizeOptions,
|
|
2712
|
+
sx,
|
|
2713
|
+
theme: propsTheme,
|
|
2714
|
+
useRouter,
|
|
2715
|
+
paginationMode = 'client',
|
|
2716
|
+
rowCount,
|
|
2717
|
+
density: _density,
|
|
2718
|
+
dataSource,
|
|
2719
|
+
filterMode: propsFilterMode,
|
|
2720
|
+
sortingMode: propsSortingMode
|
|
2721
|
+
} = props,
|
|
2722
|
+
forwardedProps = _objectWithoutProperties(props, _excluded);
|
|
2723
|
+
const theme = useTheme(propsTheme);
|
|
2724
|
+
const _apiRef = useGridApiRef();
|
|
2725
|
+
const apiRef = propsApiRef !== null && propsApiRef !== void 0 ? propsApiRef : _apiRef;
|
|
2726
|
+
LicenseInfo.setLicenseKey(license);
|
|
2727
|
+
const height = propsHeight !== null && propsHeight !== void 0 ? propsHeight : autoHeight ? undefined : '500px';
|
|
2728
|
+
|
|
2729
|
+
// When dataSource is present, MUI manages filter/sort/pagination internally.
|
|
2730
|
+
// We must not pass controlled models — only initialState (one-time) and
|
|
2731
|
+
// write-only onChange handlers for URL/localStorage persistence.
|
|
2732
|
+
const isDataSourceMode = Boolean(dataSource);
|
|
2733
|
+
const effectivePaginationMode = isDataSourceMode ? 'server' : paginationMode;
|
|
2734
|
+
const effectiveFilterMode = isDataSourceMode ? 'server' : propsFilterMode;
|
|
2735
|
+
const effectiveSortingMode = isDataSourceMode ? 'server' : propsSortingMode;
|
|
2736
|
+
const {
|
|
2737
|
+
onColumnVisibilityModelChange: controlledOnColumnVisibilityModelChange,
|
|
2738
|
+
onFilterModelChange: controlledOnFilterModelChange,
|
|
2739
|
+
onPaginationModelChange: controlledOnPaginationModelChange,
|
|
2740
|
+
onPinnedColumnsChange: controlledOnPinnedColumnsChange,
|
|
2741
|
+
onSortModelChange: controlledOnSortModelChange
|
|
2742
|
+
} = useControlledDatagridState({
|
|
2743
|
+
initialState,
|
|
2744
|
+
pageSizeOptions,
|
|
2745
|
+
propsColumnVisibilityModel,
|
|
2746
|
+
propsFilterModel,
|
|
2747
|
+
propsOnColumnVisibilityModelChange,
|
|
2748
|
+
propsOnFilterModelChange,
|
|
2749
|
+
propsOnPinnedColumnsChange,
|
|
2750
|
+
propsOnSortModelChange,
|
|
2751
|
+
propsPaginationModel,
|
|
2752
|
+
propsPinnedColumns,
|
|
2753
|
+
propsSortModel,
|
|
2754
|
+
propsOnPaginationModelChange
|
|
2755
|
+
});
|
|
2756
|
+
const {
|
|
2757
|
+
columnVisibilityModel,
|
|
2758
|
+
density: controlledDensity,
|
|
2759
|
+
filterModel,
|
|
2760
|
+
onColumnVisibilityModelChange,
|
|
2761
|
+
onFilterModelChange,
|
|
2762
|
+
onPaginationModelChange,
|
|
2763
|
+
onPinnedColumnsChange,
|
|
2764
|
+
onSortModelChange,
|
|
2765
|
+
paginationModel,
|
|
2766
|
+
pinnedColumns,
|
|
2767
|
+
sortModel,
|
|
2768
|
+
onColumnWidthChange,
|
|
2769
|
+
columnOrderModel
|
|
2770
|
+
} = useStatefulTable({
|
|
2771
|
+
apiRef: apiRef,
|
|
2772
|
+
initialState,
|
|
2773
|
+
columns,
|
|
2774
|
+
onColumnVisibilityModelChange: controlledOnColumnVisibilityModelChange,
|
|
2775
|
+
onColumnWidthChange: propsOnColumnWidthChange,
|
|
2776
|
+
onFilterModelChange: controlledOnFilterModelChange,
|
|
2777
|
+
onPaginationModelChange: controlledOnPaginationModelChange,
|
|
2778
|
+
onPinnedColumnsChange: controlledOnPinnedColumnsChange,
|
|
2779
|
+
onSortModelChange: controlledOnSortModelChange,
|
|
2780
|
+
useRouter: useRouter,
|
|
2781
|
+
localStorageVersion,
|
|
2782
|
+
previousLocalStorageVersions
|
|
2783
|
+
});
|
|
2784
|
+
|
|
2785
|
+
// In dataSource mode, track pagination locally for the custom pagination slots
|
|
2786
|
+
// (rendered outside DataGridPro). MUI owns the actual pagination state internally.
|
|
2787
|
+
const [dataSourcePaginationModel, setDataSourcePaginationModel] = useState(paginationModel);
|
|
2788
|
+
|
|
2789
|
+
// The pagination model to use for display in pagination slots
|
|
2790
|
+
const activePaginationModel = isDataSourceMode ? dataSourcePaginationModel : paginationModel;
|
|
2791
|
+
|
|
2792
|
+
// Wrap onPaginationModelChange to also track state locally in dataSource mode
|
|
2793
|
+
const wrappedOnPaginationModelChange = useCallback((model, details) => {
|
|
2794
|
+
if (isDataSourceMode) {
|
|
2795
|
+
setDataSourcePaginationModel(model);
|
|
2796
|
+
}
|
|
2797
|
+
onPaginationModelChange(model, details);
|
|
2798
|
+
}, [isDataSourceMode, onPaginationModelChange]);
|
|
2799
|
+
|
|
2800
|
+
// In dataSource mode, pagination changes from our custom pagination slots
|
|
2801
|
+
// (rendered outside MUI's pagination state) route through apiRef so MUI's
|
|
2802
|
+
// internal page state updates and dataSource.getRows() refetches with the
|
|
2803
|
+
// new params. The `paginationModelChange` subscription below picks up the
|
|
2804
|
+
// resulting state change and propagates it to URL/localStorage and local
|
|
2805
|
+
// React state via wrappedOnPaginationModelChange.
|
|
2806
|
+
const dataSourcePaginationChange = useCallback(model => {
|
|
2807
|
+
var _apiRef$current;
|
|
2808
|
+
(_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 ? void 0 : _apiRef$current.setPaginationModel(model);
|
|
2809
|
+
}, [apiRef]);
|
|
2810
|
+
|
|
2811
|
+
// In dataSource mode, subscribe to MUI's `paginationModelChange` event so
|
|
2812
|
+
// URL state stays in sync with MUI's internal pagination regardless of how
|
|
2813
|
+
// it changed (slot click, apiRef.setPaginationModel from consumer code,
|
|
2814
|
+
// MUI internal updates, etc.). Relying on MUI's `onPaginationModelChange`
|
|
2815
|
+
// prop callback alone is not sufficient: in pivot/GroupedData strategy mode
|
|
2816
|
+
// and with `paginationModel` seeded via `initialState` (rather than as a
|
|
2817
|
+
// controlled prop), the prop callback can be missed under certain
|
|
2818
|
+
// re-render orderings. The event fires reliably whenever the internal
|
|
2819
|
+
// state changes via `setState('setPaginationModel')`, see
|
|
2820
|
+
// `useGridStateInitialization.setState` → `publishEvent(changeEvent, …)`.
|
|
2821
|
+
// The deep-equal guard inside `useStatefulTable.onPaginationModelChange`
|
|
2822
|
+
// dedupes any duplicate emits, so overlap with the prop callback is safe.
|
|
2823
|
+
useEffect(() => {
|
|
2824
|
+
if (!isDataSourceMode) return;
|
|
2825
|
+
const api = apiRef.current;
|
|
2826
|
+
if (!(api !== null && api !== void 0 && api.subscribeEvent)) return;
|
|
2827
|
+
return api.subscribeEvent('paginationModelChange', model => {
|
|
2828
|
+
wrappedOnPaginationModelChange({
|
|
2829
|
+
page: model.page,
|
|
2830
|
+
pageSize: model.pageSize
|
|
2831
|
+
}, {
|
|
2832
|
+
reason: 'paginationModelChange'
|
|
2833
|
+
});
|
|
2834
|
+
});
|
|
2835
|
+
}, [isDataSourceMode, apiRef, wrappedOnPaginationModelChange]);
|
|
2836
|
+
const [rowSelectionModel, setRowSelectionModel] = useState(() => normalizeRowSelectionModel(propsRowSelectionModel));
|
|
2837
|
+
useEffect(() => {
|
|
2838
|
+
setRowSelectionModel(normalizeRowSelectionModel(propsRowSelectionModel));
|
|
2839
|
+
}, [propsRowSelectionModel]);
|
|
2840
|
+
const onRowSelectionModelChange = (selectionModel, details) => {
|
|
2841
|
+
setRowSelectionModel(selectionModel);
|
|
2842
|
+
propsOnRowSelectionModelChange === null || propsOnRowSelectionModelChange === void 0 ? void 0 : propsOnRowSelectionModelChange(selectionModel, details);
|
|
2843
|
+
};
|
|
2844
|
+
const selectionStatusRef = useRef({
|
|
2845
|
+
type: 'none',
|
|
2846
|
+
numberOfSelectedRows: 0,
|
|
2847
|
+
numberOfSelectedRowsInPage: 0,
|
|
2848
|
+
page: paginationModel.page,
|
|
2849
|
+
pageSize: paginationModel.pageSize
|
|
2850
|
+
});
|
|
2851
|
+
|
|
2852
|
+
// Version counter to force re-renders when selectionStatus ref changes
|
|
2853
|
+
const [, forceSelectionUpdate] = useState(0);
|
|
2854
|
+
|
|
2855
|
+
// The checkboxSelectionVisibleOnly should only be applied to client-side pagination,
|
|
2856
|
+
// for server-side pagination it produces inconsistent behavior when selecting all rows in pages 2 and beyond
|
|
2857
|
+
const checkboxSelectionVisibleOnly = Boolean(pagination) && Boolean(effectivePaginationMode != 'server');
|
|
2858
|
+
|
|
2859
|
+
// Track when the grid API is ready to ensure top pagination renders correctly
|
|
2860
|
+
const [gridReady, setGridReady] = useState(false);
|
|
2861
|
+
|
|
2862
|
+
// Force re-render when the grid API becomes ready (for top pagination)
|
|
2863
|
+
useEffect(() => {
|
|
2864
|
+
if (apiRef.current && !gridReady) {
|
|
2865
|
+
setGridReady(true);
|
|
2866
|
+
}
|
|
2867
|
+
});
|
|
2868
|
+
|
|
2869
|
+
// Sync persisted density via apiRef — initialState only applies on mount,
|
|
2870
|
+
// so this handles SPA back/forward navigation where controlledDensity changes after mount
|
|
2871
|
+
useEffect(() => {
|
|
2872
|
+
if (apiRef.current) {
|
|
2873
|
+
apiRef.current.setDensity(controlledDensity);
|
|
2874
|
+
}
|
|
2875
|
+
}, [controlledDensity, apiRef]);
|
|
2876
|
+
|
|
2877
|
+
// in server-side pagination we want to update the selection status
|
|
2878
|
+
// every time we navigate between pages, resize our page or select something
|
|
2879
|
+
useEffect(() => {
|
|
2880
|
+
if (effectivePaginationMode == 'server') {
|
|
2881
|
+
onServerSideSelectionStatusChange(rowSelectionModel, apiRef, selectionStatusRef, forceSelectionUpdate, isRowSelectable, activePaginationModel.page, activePaginationModel.pageSize);
|
|
2882
|
+
}
|
|
2883
|
+
}, [rowSelectionModel, activePaginationModel.page, activePaginationModel.pageSize, rows]);
|
|
2884
|
+
|
|
2885
|
+
// In dataSource mode MUI provides rows internally; skip the guard.
|
|
2886
|
+
if (!isDataSourceMode && !Array.isArray(rows)) {
|
|
2887
|
+
return null;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// Compute selection status synchronously during render for client-side pagination.
|
|
2891
|
+
// This value is passed directly to ToolbarWrapper and ControlledPagination, so slots
|
|
2892
|
+
// receive the fresh value in the same render cycle — no extra re-render needed.
|
|
2893
|
+
// The ref is kept in sync for the onRowSelectionModelChange callback's deselect logic.
|
|
2894
|
+
let selectionStatus = selectionStatusRef.current;
|
|
2895
|
+
if (pagination && effectivePaginationMode !== 'server' && getSelectionCount(rowSelectionModel) > 0) {
|
|
2896
|
+
try {
|
|
2897
|
+
// Use manual page slicing instead of gridPaginatedVisibleSorted* selectors.
|
|
2898
|
+
// MUI's paginated selectors use apiRef internal state which may be stale when
|
|
2899
|
+
// paginationModel prop changes — our React state is always up to date.
|
|
2900
|
+
const allFilteredEntries = gridFilteredSortedRowEntriesSelector(apiRef);
|
|
2901
|
+
const pageStart = activePaginationModel.page * activePaginationModel.pageSize;
|
|
2902
|
+
const pageEntries = allFilteredEntries.slice(pageStart, pageStart + activePaginationModel.pageSize);
|
|
2903
|
+
const selectableRowsInPage = isRowSelectable ? pageEntries.filter(_ref => {
|
|
2904
|
+
let {
|
|
2905
|
+
model
|
|
2906
|
+
} = _ref;
|
|
2907
|
+
return isRowSelectable({
|
|
2908
|
+
row: model
|
|
2909
|
+
});
|
|
2910
|
+
}).map(_ref2 => {
|
|
2911
|
+
let {
|
|
2912
|
+
id
|
|
2913
|
+
} = _ref2;
|
|
2914
|
+
return id;
|
|
2915
|
+
}) : pageEntries.map(_ref3 => {
|
|
2916
|
+
let {
|
|
2917
|
+
id
|
|
2918
|
+
} = _ref3;
|
|
2919
|
+
return id;
|
|
2920
|
+
});
|
|
2921
|
+
const numberOfSelectableRowsInPage = selectableRowsInPage.length;
|
|
2922
|
+
const selectableRowsInTable = isRowSelectable ? allFilteredEntries.filter(_ref4 => {
|
|
2923
|
+
let {
|
|
2924
|
+
model
|
|
2925
|
+
} = _ref4;
|
|
2926
|
+
return isRowSelectable({
|
|
2927
|
+
row: model
|
|
2928
|
+
});
|
|
2929
|
+
}).map(_ref5 => {
|
|
2930
|
+
let {
|
|
2931
|
+
id
|
|
2932
|
+
} = _ref5;
|
|
2933
|
+
return id;
|
|
2934
|
+
}) : gridFilteredSortedRowIdsSelector(apiRef);
|
|
2935
|
+
const numberOfSelectableRowsInTable = selectableRowsInTable.length;
|
|
2936
|
+
const numberOfSelectedRows = getSelectionCount(rowSelectionModel);
|
|
2937
|
+
const selectedOnCurrentPage = selectableRowsInPage.filter(id => isRowSelected(rowSelectionModel, id));
|
|
2938
|
+
if (numberOfSelectedRows === numberOfSelectableRowsInTable && numberOfSelectableRowsInPage < numberOfSelectableRowsInTable) {
|
|
2939
|
+
selectionStatus = {
|
|
2940
|
+
type: 'table',
|
|
2941
|
+
numberOfSelectedRows
|
|
2942
|
+
};
|
|
2943
|
+
} else if (selectedOnCurrentPage.length === numberOfSelectableRowsInPage && numberOfSelectableRowsInPage > 0 && numberOfSelectableRowsInPage < numberOfSelectableRowsInTable) {
|
|
2944
|
+
selectionStatus = {
|
|
2945
|
+
type: 'page',
|
|
2946
|
+
numberOfSelectedRows: selectedOnCurrentPage.length
|
|
2947
|
+
};
|
|
2948
|
+
} else if (numberOfSelectedRows > 0) {
|
|
2949
|
+
selectionStatus = {
|
|
2950
|
+
type: 'other',
|
|
2951
|
+
numberOfSelectedRows
|
|
2952
|
+
};
|
|
2953
|
+
} else {
|
|
2954
|
+
selectionStatus = {
|
|
2955
|
+
type: 'none',
|
|
2956
|
+
numberOfSelectedRows: 0
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2959
|
+
} catch {
|
|
2960
|
+
// apiRef may not be initialized on first render
|
|
2961
|
+
}
|
|
2962
|
+
} else if (pagination && effectivePaginationMode !== 'server') {
|
|
2963
|
+
selectionStatus = {
|
|
2964
|
+
type: 'none',
|
|
2965
|
+
numberOfSelectedRows: 0
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
selectionStatusRef.current = selectionStatus;
|
|
2969
|
+
const muiTheme = useMemo(() => createTheme({
|
|
2970
|
+
palette: {
|
|
2971
|
+
mode: theme,
|
|
2972
|
+
primary: {
|
|
2973
|
+
main: RedsiftColorBlueN
|
|
2974
|
+
},
|
|
2975
|
+
background: {
|
|
2976
|
+
default: theme === 'dark' ? RedsiftColorNeutralXDarkGrey : RedsiftColorNeutralWhite,
|
|
2977
|
+
paper: theme === 'dark' ? RedsiftColorNeutralXDarkGrey : RedsiftColorNeutralWhite
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
}), [theme]);
|
|
2981
|
+
return /*#__PURE__*/React__default.createElement(ThemeProvider, {
|
|
2982
|
+
value: {
|
|
2983
|
+
theme
|
|
2984
|
+
}
|
|
2985
|
+
}, /*#__PURE__*/React__default.createElement(ThemeProvider$1, {
|
|
2986
|
+
theme: muiTheme
|
|
2987
|
+
}, /*#__PURE__*/React__default.createElement(StyledDataGrid, {
|
|
2988
|
+
ref: datagridRef,
|
|
2989
|
+
className: classNames(StatefulDataGrid.className, className),
|
|
2990
|
+
$height: height
|
|
2991
|
+
}, pagination && ['top', 'both'].includes(paginationPlacement) && gridReady ? effectivePaginationMode == 'server' ? /*#__PURE__*/React__default.createElement(ServerSideControlledPagination, {
|
|
2992
|
+
displaySelection: true,
|
|
2993
|
+
displayRowsPerPage: ['top', 'both'].includes(paginationPlacement),
|
|
2994
|
+
displayPagination: ['top', 'both'].includes(paginationPlacement),
|
|
2995
|
+
selectionStatus: selectionStatus,
|
|
2996
|
+
paginationModel: activePaginationModel,
|
|
2997
|
+
onPaginationModelChange: isDataSourceMode ? dataSourcePaginationChange : onPaginationModelChange,
|
|
2998
|
+
pageSizeOptions: pageSizeOptions,
|
|
2999
|
+
paginationProps: paginationProps,
|
|
3000
|
+
rowCount: rowCount
|
|
3001
|
+
}) : /*#__PURE__*/React__default.createElement(ControlledPagination, {
|
|
3002
|
+
displaySelection: true,
|
|
3003
|
+
displayRowsPerPage: ['top', 'both'].includes(paginationPlacement),
|
|
3004
|
+
displayPagination: ['top', 'both'].includes(paginationPlacement),
|
|
3005
|
+
selectionStatus: selectionStatus,
|
|
3006
|
+
apiRef: apiRef,
|
|
3007
|
+
isRowSelectable: isRowSelectable,
|
|
3008
|
+
paginationModel: activePaginationModel,
|
|
3009
|
+
onPaginationModelChange: onPaginationModelChange,
|
|
3010
|
+
pageSizeOptions: pageSizeOptions,
|
|
3011
|
+
paginationProps: paginationProps
|
|
3012
|
+
}) : null, /*#__PURE__*/React__default.createElement(DataGridPro, _extends({}, forwardedProps, {
|
|
3013
|
+
apiRef: apiRef,
|
|
3014
|
+
dataSource: dataSource,
|
|
3015
|
+
columns: columns,
|
|
3016
|
+
onColumnVisibilityModelChange: onColumnVisibilityModelChange,
|
|
3017
|
+
onPinnedColumnsChange: onPinnedColumnsChange,
|
|
3018
|
+
pageSizeOptions: pageSizeOptions,
|
|
3019
|
+
onColumnWidthChange: onColumnWidthChange
|
|
3020
|
+
// In dataSource mode: models are uncontrolled (MUI owns them),
|
|
3021
|
+
// onChange handlers are write-only for URL/localStorage persistence,
|
|
3022
|
+
// and initialState seeds MUI on mount from the persisted URL state.
|
|
3023
|
+
// columnVisibilityModel / pinnedColumns are also uncontrolled here
|
|
3024
|
+
// to avoid a controlled re-render race with consumer-side
|
|
3025
|
+
// microtask-deferred history updates (otherwise user toggles flip
|
|
3026
|
+
// back when MUI re-emits with the stale controlled value).
|
|
3027
|
+
// Consumers needing programmatic changes should use the apiRef
|
|
3028
|
+
// imperative API.
|
|
3029
|
+
}, isDataSourceMode ? {
|
|
3030
|
+
onFilterModelChange: onFilterModelChange,
|
|
3031
|
+
onSortModelChange: onSortModelChange,
|
|
3032
|
+
onPaginationModelChange: wrappedOnPaginationModelChange,
|
|
3033
|
+
initialState: _objectSpread2(_objectSpread2({}, initialState), {}, {
|
|
3034
|
+
density: controlledDensity,
|
|
3035
|
+
columns: _objectSpread2(_objectSpread2({}, initialState === null || initialState === void 0 ? void 0 : initialState.columns), {}, {
|
|
3036
|
+
orderedFields: columnOrderModel,
|
|
3037
|
+
columnVisibilityModel
|
|
3038
|
+
}),
|
|
3039
|
+
pinnedColumns,
|
|
3040
|
+
filter: {
|
|
3041
|
+
filterModel
|
|
3042
|
+
},
|
|
3043
|
+
sorting: {
|
|
3044
|
+
sortModel
|
|
3045
|
+
},
|
|
3046
|
+
pagination: {
|
|
3047
|
+
paginationModel
|
|
3048
|
+
}
|
|
3049
|
+
})
|
|
3050
|
+
} : {
|
|
3051
|
+
columnVisibilityModel,
|
|
3052
|
+
pinnedColumns,
|
|
3053
|
+
filterModel,
|
|
3054
|
+
sortModel,
|
|
3055
|
+
paginationModel,
|
|
3056
|
+
onFilterModelChange: onFilterModelChange,
|
|
3057
|
+
onSortModelChange: onSortModelChange,
|
|
3058
|
+
onPaginationModelChange: wrappedOnPaginationModelChange,
|
|
3059
|
+
initialState: _objectSpread2(_objectSpread2({}, initialState), {}, {
|
|
3060
|
+
density: controlledDensity,
|
|
3061
|
+
columns: _objectSpread2(_objectSpread2({}, initialState === null || initialState === void 0 ? void 0 : initialState.columns), {}, {
|
|
3062
|
+
orderedFields: columnOrderModel
|
|
3063
|
+
})
|
|
3064
|
+
})
|
|
3065
|
+
}, {
|
|
3066
|
+
isRowSelectable: isRowSelectable,
|
|
3067
|
+
pagination: pagination,
|
|
3068
|
+
paginationMode: effectivePaginationMode,
|
|
3069
|
+
filterMode: effectiveFilterMode,
|
|
3070
|
+
sortingMode: effectiveSortingMode,
|
|
3071
|
+
keepNonExistentRowsSelected: effectivePaginationMode == 'server',
|
|
3072
|
+
rows: isDataSourceMode ? [] : rows,
|
|
3073
|
+
rowCount: rowCount,
|
|
3074
|
+
autoHeight: autoHeight,
|
|
3075
|
+
checkboxSelectionVisibleOnly: checkboxSelectionVisibleOnly,
|
|
3076
|
+
disableRowSelectionExcludeModel: true,
|
|
3077
|
+
showToolbar: !hideToolbar,
|
|
3078
|
+
slots: _objectSpread2(_objectSpread2({
|
|
3079
|
+
baseButton: BaseButton,
|
|
3080
|
+
baseCheckbox: BaseCheckbox,
|
|
3081
|
+
baseIconButton: BaseIconButton,
|
|
3082
|
+
columnFilteredIcon: props => /*#__PURE__*/React__default.createElement(BaseIcon, _extends({}, props, {
|
|
3083
|
+
displayName: "columnFilteredIcon"
|
|
3084
|
+
})),
|
|
3085
|
+
columnSelectorIcon: props => /*#__PURE__*/React__default.createElement(BaseIcon, _extends({}, props, {
|
|
3086
|
+
displayName: "columnSelectorIcon"
|
|
3087
|
+
})),
|
|
3088
|
+
columnSortedAscendingIcon: props => /*#__PURE__*/React__default.createElement(BaseIcon, _extends({}, props, {
|
|
3089
|
+
displayName: "columnSortedAscendingIcon"
|
|
3090
|
+
})),
|
|
3091
|
+
columnSortedDescendingIcon: props => /*#__PURE__*/React__default.createElement(BaseIcon, _extends({}, props, {
|
|
3092
|
+
displayName: "columnSortedDescendingIcon"
|
|
3093
|
+
})),
|
|
3094
|
+
densityCompactIcon: props => /*#__PURE__*/React__default.createElement(BaseIcon, _extends({}, props, {
|
|
3095
|
+
displayName: "densityCompactIcon"
|
|
3096
|
+
})),
|
|
3097
|
+
densityStandardIcon: props => /*#__PURE__*/React__default.createElement(BaseIcon, _extends({}, props, {
|
|
3098
|
+
displayName: "densityStandardIcon"
|
|
3099
|
+
})),
|
|
3100
|
+
densityComfortableIcon: props => /*#__PURE__*/React__default.createElement(BaseIcon, _extends({}, props, {
|
|
3101
|
+
displayName: "densityComfortableIcon"
|
|
3102
|
+
})),
|
|
3103
|
+
detailPanelCollapseIcon: props => /*#__PURE__*/React__default.createElement(BaseIcon, _extends({}, props, {
|
|
3104
|
+
displayName: "detailPanelCollapseIcon"
|
|
3105
|
+
})),
|
|
3106
|
+
detailPanelExpandIcon: props => /*#__PURE__*/React__default.createElement(BaseIcon, _extends({}, props, {
|
|
3107
|
+
displayName: "detailPanelExpandIcon"
|
|
3108
|
+
})),
|
|
3109
|
+
exportIcon: props => /*#__PURE__*/React__default.createElement(BaseIcon, _extends({}, props, {
|
|
3110
|
+
displayName: "exportIcon"
|
|
3111
|
+
})),
|
|
3112
|
+
openFilterButtonIcon: props => /*#__PURE__*/React__default.createElement(BaseIcon, _extends({}, props, {
|
|
3113
|
+
displayName: "openFilterButtonIcon"
|
|
3114
|
+
}))
|
|
3115
|
+
}, slots), {}, {
|
|
3116
|
+
pagination: props => {
|
|
3117
|
+
return pagination ? effectivePaginationMode == 'server' ? /*#__PURE__*/React__default.createElement(ServerSideControlledPagination, _extends({}, props, {
|
|
3118
|
+
displaySelection: false,
|
|
3119
|
+
displayRowsPerPage: ['bottom', 'both'].includes(paginationPlacement),
|
|
3120
|
+
displayPagination: ['bottom', 'both'].includes(paginationPlacement),
|
|
3121
|
+
selectionStatus: selectionStatus,
|
|
3122
|
+
paginationModel: activePaginationModel
|
|
3123
|
+
// In dataSource mode route through apiRef so MUI's internal pagination
|
|
3124
|
+
// state stays in sync with the displayed model and dataSource.getRows()
|
|
3125
|
+
// re-fetches with the new params. MUI then fires onPaginationModelChange
|
|
3126
|
+
// via the prop, which updates URL/localStorage via useStatefulTable.
|
|
3127
|
+
// Without this the bottom slot only updated local React state + URL,
|
|
3128
|
+
// leaving MUI on its previous internal page (no refetch, stale display).
|
|
3129
|
+
,
|
|
3130
|
+
onPaginationModelChange: isDataSourceMode ? dataSourcePaginationChange : wrappedOnPaginationModelChange,
|
|
3131
|
+
pageSizeOptions: pageSizeOptions,
|
|
3132
|
+
paginationProps: paginationProps,
|
|
3133
|
+
rowCount: rowCount
|
|
3134
|
+
})) : /*#__PURE__*/React__default.createElement(ControlledPagination, _extends({}, props, {
|
|
3135
|
+
displaySelection: false,
|
|
3136
|
+
displayRowsPerPage: ['bottom', 'both'].includes(paginationPlacement),
|
|
3137
|
+
displayPagination: ['bottom', 'both'].includes(paginationPlacement),
|
|
3138
|
+
selectionStatus: selectionStatus,
|
|
3139
|
+
apiRef: apiRef,
|
|
3140
|
+
isRowSelectable: isRowSelectable,
|
|
3141
|
+
paginationModel: activePaginationModel,
|
|
3142
|
+
onPaginationModelChange: wrappedOnPaginationModelChange,
|
|
3143
|
+
pageSizeOptions: pageSizeOptions,
|
|
3144
|
+
paginationProps: paginationProps
|
|
3145
|
+
})) : null;
|
|
3146
|
+
}
|
|
3147
|
+
}),
|
|
3148
|
+
slotProps: _objectSpread2({}, slotProps),
|
|
3149
|
+
rowSelectionModel: rowSelectionModel,
|
|
3150
|
+
onRowSelectionModelChange: (newSelectionModel, details) => {
|
|
3151
|
+
if (pagination && effectivePaginationMode != 'server') {
|
|
3152
|
+
// Use manual page slicing instead of gridPaginatedVisibleSorted* selectors
|
|
3153
|
+
// to avoid stale apiRef pagination state.
|
|
3154
|
+
const allFilteredEntries = gridFilteredSortedRowEntriesSelector(apiRef);
|
|
3155
|
+
const pageStart = activePaginationModel.page * activePaginationModel.pageSize;
|
|
3156
|
+
const pageEntries = allFilteredEntries.slice(pageStart, pageStart + activePaginationModel.pageSize);
|
|
3157
|
+
const selectableRowsInPage = isRowSelectable ? pageEntries.filter(_ref6 => {
|
|
3158
|
+
let {
|
|
3159
|
+
model
|
|
3160
|
+
} = _ref6;
|
|
3161
|
+
return isRowSelectable({
|
|
3162
|
+
row: model
|
|
3163
|
+
});
|
|
3164
|
+
}).map(_ref7 => {
|
|
3165
|
+
let {
|
|
3166
|
+
id
|
|
3167
|
+
} = _ref7;
|
|
3168
|
+
return id;
|
|
3169
|
+
}) : pageEntries.map(_ref8 => {
|
|
3170
|
+
let {
|
|
3171
|
+
id
|
|
3172
|
+
} = _ref8;
|
|
3173
|
+
return id;
|
|
3174
|
+
});
|
|
3175
|
+
const numberOfSelectableRowsInPage = selectableRowsInPage.length;
|
|
3176
|
+
const selectableRowsInTable = isRowSelectable ? allFilteredEntries.filter(_ref9 => {
|
|
3177
|
+
let {
|
|
3178
|
+
model
|
|
3179
|
+
} = _ref9;
|
|
3180
|
+
return isRowSelectable({
|
|
3181
|
+
row: model
|
|
3182
|
+
});
|
|
3183
|
+
}).map(_ref10 => {
|
|
3184
|
+
let {
|
|
3185
|
+
id
|
|
3186
|
+
} = _ref10;
|
|
3187
|
+
return id;
|
|
3188
|
+
}) : gridFilteredSortedRowIdsSelector(apiRef);
|
|
3189
|
+
const numberOfSelectableRowsInTable = selectableRowsInTable.length;
|
|
3190
|
+
const numberOfSelectedRows = getSelectionCount(newSelectionModel);
|
|
3191
|
+
if (selectionStatusRef.current.type === 'table' && numberOfSelectedRows === numberOfSelectableRowsInTable - numberOfSelectableRowsInPage || selectionStatusRef.current.type === 'table' && numberOfSelectedRows === numberOfSelectableRowsInTable || selectionStatusRef.current.type === 'page' && numberOfSelectedRows === numberOfSelectableRowsInPage) {
|
|
3192
|
+
setTimeout(() => {
|
|
3193
|
+
var _apiRef$current2;
|
|
3194
|
+
(_apiRef$current2 = apiRef.current) === null || _apiRef$current2 === void 0 ? void 0 : _apiRef$current2.selectRows([], true, true);
|
|
3195
|
+
}, 0);
|
|
3196
|
+
}
|
|
3197
|
+
if (numberOfSelectedRows === numberOfSelectableRowsInPage && numberOfSelectableRowsInPage < numberOfSelectableRowsInTable) {
|
|
3198
|
+
selectionStatusRef.current = {
|
|
3199
|
+
type: 'page',
|
|
3200
|
+
numberOfSelectedRows
|
|
3201
|
+
};
|
|
3202
|
+
} else if (numberOfSelectedRows === numberOfSelectableRowsInTable && numberOfSelectableRowsInPage < numberOfSelectableRowsInTable) {
|
|
3203
|
+
selectionStatusRef.current = {
|
|
3204
|
+
type: 'table',
|
|
3205
|
+
numberOfSelectedRows
|
|
3206
|
+
};
|
|
3207
|
+
} else if (numberOfSelectedRows > 0) {
|
|
3208
|
+
selectionStatusRef.current = {
|
|
3209
|
+
type: 'other',
|
|
3210
|
+
numberOfSelectedRows
|
|
3211
|
+
};
|
|
3212
|
+
} else {
|
|
3213
|
+
selectionStatusRef.current = {
|
|
3214
|
+
type: 'none',
|
|
3215
|
+
numberOfSelectedRows
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
forceSelectionUpdate(v => v + 1);
|
|
3219
|
+
}
|
|
3220
|
+
onRowSelectionModelChange === null || onRowSelectionModelChange === void 0 ? void 0 : onRowSelectionModelChange(newSelectionModel, details);
|
|
3221
|
+
},
|
|
3222
|
+
sx: _objectSpread2(_objectSpread2({}, sx), {}, {
|
|
3223
|
+
'.MuiDataGrid-columnHeaders': {
|
|
3224
|
+
flexDirection: 'column',
|
|
3225
|
+
alignItems: 'normal'
|
|
3226
|
+
},
|
|
3227
|
+
'.MuiDataGrid-selectedRowCount': {
|
|
3228
|
+
margin: 'none'
|
|
3229
|
+
}
|
|
3230
|
+
})
|
|
3231
|
+
})))));
|
|
3232
|
+
});
|
|
3233
|
+
StatefulDataGrid.className = CLASSNAME;
|
|
3234
|
+
StatefulDataGrid.displayName = COMPONENT_NAME;
|
|
3235
|
+
|
|
3236
|
+
export { convertToDisplayFormat as $, ARRAY_IS_EMPTY as A, IS as B, CONTAINS_ANY_OF as C, DOES_NOT_CONTAIN as D, ENDS_WITH_ANY_OF as E, IS_NOT as F, getGridStringOperators as G, HAS_WITH_SELECT as H, IS_ANY_OF as I, getGridStringArrayOperators as J, getGridStringArrayOperatorsWithSelect as K, getGridStringArrayOperatorsWithSelectOnStringArrayColumns as L, FILTER_MODEL_KEY as M, SORT_MODEL_KEY as N, PINNED_COLUMNS as O, PAGINATION_MODEL_KEY as P, DIMENSION_MODEL_KEY as Q, FILTER_SEARCH_KEY as R, STARTS_WITH_ANY_OF as S, DENSITY_MODEL_KEY as T, COLUMN_ORDER_MODEL_KEY as U, VISIBILITY_MODEL_KEY as V, CATEGORIES as W, buildStorageKey as X, clearPreviousVersionStorage as Y, clearAllVersionStorage as Z, resetStatefulDataGridState as _, DOES_NOT_EQUAL as a, convertFromDisplayFormat as a0, getDecodedSearchFromUrl as a1, buildQueryParamsString as a2, areSearchStringsEqual as a3, decodeValue as a4, encodeValue as a5, urlSearchParamsToString as a6, numberOperatorEncoder as a7, numberOperatorDecoder as a8, isOperatorValueValid as a9, isValueValid as aa, getFilterModelFromString as ab, getSearchParamsFromFilterModel as ac, getSortingFromString as ad, getSearchParamsFromSorting as ae, getPaginationFromString as af, getSearchParamsFromPagination as ag, getColumnVisibilityFromString as ah, getSearchParamsFromColumnVisibility as ai, getPinnedColumnsFromString as aj, getSearchParamsFromPinnedColumns as ak, getSearchParamsFromTab as al, getDensityFromString as am, getSearchParamsFromDensity as an, getDensityModel as ao, getColumnOrderFromString as ap, getSearchParamsFromColumnOrder as aq, getFinalSearch as ar, getModelsParsedOrUpdateLocalStorage as as, updateUrl as at, areFilterModelsEquivalent as au, StatefulDataGrid as av, DOES_NOT_START_WITH as b, DOES_NOT_END_WITH as c, IS_NOT_ANY_OF as d, DOES_NOT_CONTAIN_ANY_OF as e, DOES_NOT_START_WITH_ANY_OF as f, DOES_NOT_END_WITH_ANY_OF as g, IS_BETWEEN as h, IS_WITH_SELECT as i, IS_NOT_WITH_SELECT as j, IS_ANY_OF_WITH_SELECT as k, IS_NOT_ANY_OF_WITH_SELECT as l, ARRAY_IS_NOT_EMPTY as m, DOES_NOT_HAVE_WITH_SELECT as n, operatorList as o, HAS_ANY_OF_WITH_SELECT as p, HAS_ALL_OF_WITH_SELECT as q, DOES_NOT_HAVE_ANY_OF_WITH_SELECT as r, HAS_ONLY_WITH_SELECT as s, HAS as t, DOES_NOT_HAVE as u, HAS_ANY_OF as v, HAS_ALL_OF as w, DOES_NOT_HAVE_ANY_OF as x, HAS_ONLY as y, getGridNumericOperators as z };
|
|
3237
|
+
//# sourceMappingURL=StatefulDataGrid2.js.map
|