@redsift/table 12.5.9-muiv7 → 12.5.9

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.
@@ -1,1500 +1,19 @@
1
1
  import { _ as _objectSpread2, a as _objectWithoutProperties, b as _extends } from './_rollupPluginBabelHelpers.js';
2
2
  import * as React from 'react';
3
3
  import React__default, { useCallback, useEffect, useRef, useMemo, forwardRef, useState } from 'react';
4
+ import { createTheme, ThemeProvider as ThemeProvider$1 } from '@mui/material/styles';
4
5
  import classNames from 'classnames';
5
- import { Icon, useTheme as useTheme$1, RedsiftColorBlueN, RedsiftColorNeutralXDarkGrey, RedsiftColorNeutralWhite, ThemeProvider } from '@redsift/design-system';
6
- 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';
7
- import { L as LicenseInfo, u as useControlledDatagridState, T as ThemeProvider$1, S as StyledDataGrid, B as BelowToolbar, a as BottomPagination, b as baseGridSlots } from './useControlledDatagridState.js';
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, DataGridPremium } from '@mui/x-data-grid-premium';
9
+ import { u as useControlledDatagridState, S as StyledDataGrid, B as BottomPagination, b as baseGridSlots, a as BelowToolbar } from './useControlledDatagridState.js';
10
+ import Box from '@mui/material/Box';
11
+ import TextField from '@mui/material/TextField';
8
12
  import { mdiSync } from '@redsift/icons';
9
- import { d as defaultSxConfig, i as isPlainObject, s as styled, a as styleFunctionSx, c as clsx, g as generateUtilityClasses, b as createTheme, T as THEME_ID, C as ClassNameGenerator, P as PropTypes, e as generateUtilityClass, f as styled$1, u as useDefaultProps, h as composeClasses, r as rootShouldForwardProp, j as refType } from './Portal.js';
10
- import { j as jsxRuntimeExports } from './jsx-runtime.js';
11
- import { u as useTheme, m as memoTheme, c as createSimplePaletteValueFilter, a as useFormControl, b as formControlState, d as capitalize, i as isAdornedStart, e as isFilled, F as FormControlContext, h as useId, j as useSlot, k as Select, I as Input, l as FilledInput, O as OutlinedInput, o as onServerSideSelectionStatusChange } from './ServerSideControlledPagination.js';
12
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';
13
15
  import { T as Toolbar } from './Toolbar2.js';
14
16
 
15
- const splitProps = props => {
16
- const result = {
17
- systemProps: {},
18
- otherProps: {}
19
- };
20
- const config = props?.theme?.unstable_sxConfig ?? defaultSxConfig;
21
- Object.keys(props).forEach(prop => {
22
- if (config[prop]) {
23
- result.systemProps[prop] = props[prop];
24
- } else {
25
- result.otherProps[prop] = props[prop];
26
- }
27
- });
28
- return result;
29
- };
30
- function extendSxProp(props) {
31
- const {
32
- sx: inSx,
33
- ...other
34
- } = props;
35
- const {
36
- systemProps,
37
- otherProps
38
- } = splitProps(other);
39
- let finalSx;
40
- if (Array.isArray(inSx)) {
41
- finalSx = [systemProps, ...inSx];
42
- } else if (typeof inSx === 'function') {
43
- finalSx = (...args) => {
44
- const result = inSx(...args);
45
- if (!isPlainObject(result)) {
46
- return systemProps;
47
- }
48
- return {
49
- ...systemProps,
50
- ...result
51
- };
52
- };
53
- } else {
54
- finalSx = {
55
- ...systemProps,
56
- ...inSx
57
- };
58
- }
59
- return {
60
- ...otherProps,
61
- sx: finalSx
62
- };
63
- }
64
-
65
- function createBox(options = {}) {
66
- const {
67
- themeId,
68
- defaultTheme,
69
- defaultClassName = 'MuiBox-root',
70
- generateClassName
71
- } = options;
72
- const BoxRoot = styled('div', {
73
- shouldForwardProp: prop => prop !== 'theme' && prop !== 'sx' && prop !== 'as'
74
- })(styleFunctionSx);
75
- const Box = /*#__PURE__*/React.forwardRef(function Box(inProps, ref) {
76
- const theme = useTheme(defaultTheme);
77
- const {
78
- className,
79
- component = 'div',
80
- ...other
81
- } = extendSxProp(inProps);
82
- return /*#__PURE__*/jsxRuntimeExports.jsx(BoxRoot, {
83
- as: component,
84
- ref: ref,
85
- className: clsx(className, generateClassName ? generateClassName(defaultClassName) : defaultClassName),
86
- theme: themeId ? theme[themeId] || theme : theme,
87
- ...other
88
- });
89
- });
90
- return Box;
91
- }
92
-
93
- function isMuiElement(element, muiNames) {
94
- return /*#__PURE__*/React.isValidElement(element) && muiNames.indexOf(
95
- // For server components `muiName` is avaialble in element.type._payload.value.muiName
96
- // relevant info - https://github.com/facebook/react/blob/2807d781a08db8e9873687fccc25c0f12b4fb3d4/packages/react/src/ReactLazy.js#L45
97
- // eslint-disable-next-line no-underscore-dangle
98
- element.type.muiName ?? element.type?._payload?.value?.muiName) !== -1;
99
- }
100
-
101
- const boxClasses = generateUtilityClasses('MuiBox', ['root']);
102
- var boxClasses$1 = boxClasses;
103
-
104
- const defaultTheme = createTheme();
105
- const Box = createBox({
106
- themeId: THEME_ID,
107
- defaultTheme,
108
- defaultClassName: boxClasses$1.root,
109
- generateClassName: ClassNameGenerator.generate
110
- });
111
- process.env.NODE_ENV !== "production" ? Box.propTypes /* remove-proptypes */ = {
112
- // ┌────────────────────────────── Warning ──────────────────────────────┐
113
- // │ These PropTypes are generated from the TypeScript type definitions. │
114
- // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
115
- // └─────────────────────────────────────────────────────────────────────┘
116
- /**
117
- * @ignore
118
- */
119
- children: PropTypes.node,
120
- /**
121
- * The component used for the root node.
122
- * Either a string to use a HTML element or a component.
123
- */
124
- component: PropTypes.elementType,
125
- /**
126
- * The system prop that allows defining system overrides as well as additional CSS styles.
127
- */
128
- sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object])
129
- } : void 0;
130
- var Box$1 = Box;
131
-
132
- function getFormLabelUtilityClasses(slot) {
133
- return generateUtilityClass('MuiFormLabel', slot);
134
- }
135
- const formLabelClasses = generateUtilityClasses('MuiFormLabel', ['root', 'colorSecondary', 'focused', 'disabled', 'error', 'filled', 'required', 'asterisk']);
136
- var formLabelClasses$1 = formLabelClasses;
137
-
138
- const useUtilityClasses$4 = ownerState => {
139
- const {
140
- classes,
141
- color,
142
- focused,
143
- disabled,
144
- error,
145
- filled,
146
- required
147
- } = ownerState;
148
- const slots = {
149
- root: ['root', `color${capitalize(color)}`, disabled && 'disabled', error && 'error', filled && 'filled', focused && 'focused', required && 'required'],
150
- asterisk: ['asterisk', error && 'error']
151
- };
152
- return composeClasses(slots, getFormLabelUtilityClasses, classes);
153
- };
154
- const FormLabelRoot = styled$1('label', {
155
- name: 'MuiFormLabel',
156
- slot: 'Root',
157
- overridesResolver: (props, styles) => {
158
- const {
159
- ownerState
160
- } = props;
161
- return [styles.root, ownerState.color === 'secondary' && styles.colorSecondary, ownerState.filled && styles.filled];
162
- }
163
- })(memoTheme(({
164
- theme
165
- }) => ({
166
- color: (theme.vars || theme).palette.text.secondary,
167
- ...theme.typography.body1,
168
- lineHeight: '1.4375em',
169
- padding: 0,
170
- position: 'relative',
171
- variants: [...Object.entries(theme.palette).filter(createSimplePaletteValueFilter()).map(([color]) => ({
172
- props: {
173
- color
174
- },
175
- style: {
176
- [`&.${formLabelClasses$1.focused}`]: {
177
- color: (theme.vars || theme).palette[color].main
178
- }
179
- }
180
- })), {
181
- props: {},
182
- style: {
183
- [`&.${formLabelClasses$1.disabled}`]: {
184
- color: (theme.vars || theme).palette.text.disabled
185
- },
186
- [`&.${formLabelClasses$1.error}`]: {
187
- color: (theme.vars || theme).palette.error.main
188
- }
189
- }
190
- }]
191
- })));
192
- const AsteriskComponent = styled$1('span', {
193
- name: 'MuiFormLabel',
194
- slot: 'Asterisk',
195
- overridesResolver: (props, styles) => styles.asterisk
196
- })(memoTheme(({
197
- theme
198
- }) => ({
199
- [`&.${formLabelClasses$1.error}`]: {
200
- color: (theme.vars || theme).palette.error.main
201
- }
202
- })));
203
- const FormLabel = /*#__PURE__*/React.forwardRef(function FormLabel(inProps, ref) {
204
- const props = useDefaultProps({
205
- props: inProps,
206
- name: 'MuiFormLabel'
207
- });
208
- const {
209
- children,
210
- className,
211
- color,
212
- component = 'label',
213
- disabled,
214
- error,
215
- filled,
216
- focused,
217
- required,
218
- ...other
219
- } = props;
220
- const muiFormControl = useFormControl();
221
- const fcs = formControlState({
222
- props,
223
- muiFormControl,
224
- states: ['color', 'required', 'focused', 'disabled', 'error', 'filled']
225
- });
226
- const ownerState = {
227
- ...props,
228
- color: fcs.color || 'primary',
229
- component,
230
- disabled: fcs.disabled,
231
- error: fcs.error,
232
- filled: fcs.filled,
233
- focused: fcs.focused,
234
- required: fcs.required
235
- };
236
- const classes = useUtilityClasses$4(ownerState);
237
- return /*#__PURE__*/jsxRuntimeExports.jsxs(FormLabelRoot, {
238
- as: component,
239
- ownerState: ownerState,
240
- className: clsx(classes.root, className),
241
- ref: ref,
242
- ...other,
243
- children: [children, fcs.required && /*#__PURE__*/jsxRuntimeExports.jsxs(AsteriskComponent, {
244
- ownerState: ownerState,
245
- "aria-hidden": true,
246
- className: classes.asterisk,
247
- children: ["\u2009", '*']
248
- })]
249
- });
250
- });
251
- process.env.NODE_ENV !== "production" ? FormLabel.propTypes /* remove-proptypes */ = {
252
- // ┌────────────────────────────── Warning ──────────────────────────────┐
253
- // │ These PropTypes are generated from the TypeScript type definitions. │
254
- // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
255
- // └─────────────────────────────────────────────────────────────────────┘
256
- /**
257
- * The content of the component.
258
- */
259
- children: PropTypes.node,
260
- /**
261
- * Override or extend the styles applied to the component.
262
- */
263
- classes: PropTypes.object,
264
- /**
265
- * @ignore
266
- */
267
- className: PropTypes.string,
268
- /**
269
- * The color of the component.
270
- * It supports both default and custom theme colors, which can be added as shown in the
271
- * [palette customization guide](https://mui.com/material-ui/customization/palette/#custom-colors).
272
- */
273
- color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['error', 'info', 'primary', 'secondary', 'success', 'warning']), PropTypes.string]),
274
- /**
275
- * The component used for the root node.
276
- * Either a string to use a HTML element or a component.
277
- */
278
- component: PropTypes.elementType,
279
- /**
280
- * If `true`, the label should be displayed in a disabled state.
281
- */
282
- disabled: PropTypes.bool,
283
- /**
284
- * If `true`, the label is displayed in an error state.
285
- */
286
- error: PropTypes.bool,
287
- /**
288
- * If `true`, the label should use filled classes key.
289
- */
290
- filled: PropTypes.bool,
291
- /**
292
- * If `true`, the input of this label is focused (used by `FormGroup` components).
293
- */
294
- focused: PropTypes.bool,
295
- /**
296
- * If `true`, the label will indicate that the `input` is required.
297
- */
298
- required: PropTypes.bool,
299
- /**
300
- * The system prop that allows defining system overrides as well as additional CSS styles.
301
- */
302
- sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object])
303
- } : void 0;
304
- var FormLabel$1 = FormLabel;
305
-
306
- function getInputLabelUtilityClasses(slot) {
307
- return generateUtilityClass('MuiInputLabel', slot);
308
- }
309
- generateUtilityClasses('MuiInputLabel', ['root', 'focused', 'disabled', 'error', 'required', 'asterisk', 'formControl', 'sizeSmall', 'shrink', 'animated', 'standard', 'filled', 'outlined']);
310
-
311
- const useUtilityClasses$3 = ownerState => {
312
- const {
313
- classes,
314
- formControl,
315
- size,
316
- shrink,
317
- disableAnimation,
318
- variant,
319
- required
320
- } = ownerState;
321
- const slots = {
322
- root: ['root', formControl && 'formControl', !disableAnimation && 'animated', shrink && 'shrink', size && size !== 'normal' && `size${capitalize(size)}`, variant],
323
- asterisk: [required && 'asterisk']
324
- };
325
- const composedClasses = composeClasses(slots, getInputLabelUtilityClasses, classes);
326
- return {
327
- ...classes,
328
- // forward the focused, disabled, etc. classes to the FormLabel
329
- ...composedClasses
330
- };
331
- };
332
- const InputLabelRoot = styled$1(FormLabel$1, {
333
- shouldForwardProp: prop => rootShouldForwardProp(prop) || prop === 'classes',
334
- name: 'MuiInputLabel',
335
- slot: 'Root',
336
- overridesResolver: (props, styles) => {
337
- const {
338
- ownerState
339
- } = props;
340
- return [{
341
- [`& .${formLabelClasses$1.asterisk}`]: styles.asterisk
342
- }, styles.root, ownerState.formControl && styles.formControl, ownerState.size === 'small' && styles.sizeSmall, ownerState.shrink && styles.shrink, !ownerState.disableAnimation && styles.animated, ownerState.focused && styles.focused, styles[ownerState.variant]];
343
- }
344
- })(memoTheme(({
345
- theme
346
- }) => ({
347
- display: 'block',
348
- transformOrigin: 'top left',
349
- whiteSpace: 'nowrap',
350
- overflow: 'hidden',
351
- textOverflow: 'ellipsis',
352
- maxWidth: '100%',
353
- variants: [{
354
- props: ({
355
- ownerState
356
- }) => ownerState.formControl,
357
- style: {
358
- position: 'absolute',
359
- left: 0,
360
- top: 0,
361
- // slight alteration to spec spacing to match visual spec result
362
- transform: 'translate(0, 20px) scale(1)'
363
- }
364
- }, {
365
- props: {
366
- size: 'small'
367
- },
368
- style: {
369
- // Compensation for the `Input.inputSizeSmall` style.
370
- transform: 'translate(0, 17px) scale(1)'
371
- }
372
- }, {
373
- props: ({
374
- ownerState
375
- }) => ownerState.shrink,
376
- style: {
377
- transform: 'translate(0, -1.5px) scale(0.75)',
378
- transformOrigin: 'top left',
379
- maxWidth: '133%'
380
- }
381
- }, {
382
- props: ({
383
- ownerState
384
- }) => !ownerState.disableAnimation,
385
- style: {
386
- transition: theme.transitions.create(['color', 'transform', 'max-width'], {
387
- duration: theme.transitions.duration.shorter,
388
- easing: theme.transitions.easing.easeOut
389
- })
390
- }
391
- }, {
392
- props: {
393
- variant: 'filled'
394
- },
395
- style: {
396
- // Chrome's autofill feature gives the input field a yellow background.
397
- // Since the input field is behind the label in the HTML tree,
398
- // the input field is drawn last and hides the label with an opaque background color.
399
- // zIndex: 1 will raise the label above opaque background-colors of input.
400
- zIndex: 1,
401
- pointerEvents: 'none',
402
- transform: 'translate(12px, 16px) scale(1)',
403
- maxWidth: 'calc(100% - 24px)'
404
- }
405
- }, {
406
- props: {
407
- variant: 'filled',
408
- size: 'small'
409
- },
410
- style: {
411
- transform: 'translate(12px, 13px) scale(1)'
412
- }
413
- }, {
414
- props: ({
415
- variant,
416
- ownerState
417
- }) => variant === 'filled' && ownerState.shrink,
418
- style: {
419
- userSelect: 'none',
420
- pointerEvents: 'auto',
421
- transform: 'translate(12px, 7px) scale(0.75)',
422
- maxWidth: 'calc(133% - 24px)'
423
- }
424
- }, {
425
- props: ({
426
- variant,
427
- ownerState,
428
- size
429
- }) => variant === 'filled' && ownerState.shrink && size === 'small',
430
- style: {
431
- transform: 'translate(12px, 4px) scale(0.75)'
432
- }
433
- }, {
434
- props: {
435
- variant: 'outlined'
436
- },
437
- style: {
438
- // see comment above on filled.zIndex
439
- zIndex: 1,
440
- pointerEvents: 'none',
441
- transform: 'translate(14px, 16px) scale(1)',
442
- maxWidth: 'calc(100% - 24px)'
443
- }
444
- }, {
445
- props: {
446
- variant: 'outlined',
447
- size: 'small'
448
- },
449
- style: {
450
- transform: 'translate(14px, 9px) scale(1)'
451
- }
452
- }, {
453
- props: ({
454
- variant,
455
- ownerState
456
- }) => variant === 'outlined' && ownerState.shrink,
457
- style: {
458
- userSelect: 'none',
459
- pointerEvents: 'auto',
460
- // Theoretically, we should have (8+5)*2/0.75 = 34px
461
- // but it feels a better when it bleeds a bit on the left, so 32px.
462
- maxWidth: 'calc(133% - 32px)',
463
- transform: 'translate(14px, -9px) scale(0.75)'
464
- }
465
- }]
466
- })));
467
- const InputLabel = /*#__PURE__*/React.forwardRef(function InputLabel(inProps, ref) {
468
- const props = useDefaultProps({
469
- name: 'MuiInputLabel',
470
- props: inProps
471
- });
472
- const {
473
- disableAnimation = false,
474
- margin,
475
- shrink: shrinkProp,
476
- variant,
477
- className,
478
- ...other
479
- } = props;
480
- const muiFormControl = useFormControl();
481
- let shrink = shrinkProp;
482
- if (typeof shrink === 'undefined' && muiFormControl) {
483
- shrink = muiFormControl.filled || muiFormControl.focused || muiFormControl.adornedStart;
484
- }
485
- const fcs = formControlState({
486
- props,
487
- muiFormControl,
488
- states: ['size', 'variant', 'required', 'focused']
489
- });
490
- const ownerState = {
491
- ...props,
492
- disableAnimation,
493
- formControl: muiFormControl,
494
- shrink,
495
- size: fcs.size,
496
- variant: fcs.variant,
497
- required: fcs.required,
498
- focused: fcs.focused
499
- };
500
- const classes = useUtilityClasses$3(ownerState);
501
- return /*#__PURE__*/jsxRuntimeExports.jsx(InputLabelRoot, {
502
- "data-shrink": shrink,
503
- ref: ref,
504
- className: clsx(classes.root, className),
505
- ...other,
506
- ownerState: ownerState,
507
- classes: classes
508
- });
509
- });
510
- process.env.NODE_ENV !== "production" ? InputLabel.propTypes /* remove-proptypes */ = {
511
- // ┌────────────────────────────── Warning ──────────────────────────────┐
512
- // │ These PropTypes are generated from the TypeScript type definitions. │
513
- // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
514
- // └─────────────────────────────────────────────────────────────────────┘
515
- /**
516
- * The content of the component.
517
- */
518
- children: PropTypes.node,
519
- /**
520
- * Override or extend the styles applied to the component.
521
- */
522
- classes: PropTypes.object,
523
- /**
524
- * @ignore
525
- */
526
- className: PropTypes.string,
527
- /**
528
- * The color of the component.
529
- * It supports both default and custom theme colors, which can be added as shown in the
530
- * [palette customization guide](https://mui.com/material-ui/customization/palette/#custom-colors).
531
- */
532
- color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['error', 'info', 'primary', 'secondary', 'success', 'warning']), PropTypes.string]),
533
- /**
534
- * If `true`, the transition animation is disabled.
535
- * @default false
536
- */
537
- disableAnimation: PropTypes.bool,
538
- /**
539
- * If `true`, the component is disabled.
540
- */
541
- disabled: PropTypes.bool,
542
- /**
543
- * If `true`, the label is displayed in an error state.
544
- */
545
- error: PropTypes.bool,
546
- /**
547
- * If `true`, the `input` of this label is focused.
548
- */
549
- focused: PropTypes.bool,
550
- /**
551
- * If `dense`, will adjust vertical spacing. This is normally obtained via context from
552
- * FormControl.
553
- */
554
- margin: PropTypes.oneOf(['dense']),
555
- /**
556
- * if `true`, the label will indicate that the `input` is required.
557
- */
558
- required: PropTypes.bool,
559
- /**
560
- * If `true`, the label is shrunk.
561
- */
562
- shrink: PropTypes.bool,
563
- /**
564
- * The size of the component.
565
- * @default 'normal'
566
- */
567
- size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['normal', 'small']), PropTypes.string]),
568
- /**
569
- * The system prop that allows defining system overrides as well as additional CSS styles.
570
- */
571
- sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
572
- /**
573
- * The variant to use.
574
- */
575
- variant: PropTypes.oneOf(['filled', 'outlined', 'standard'])
576
- } : void 0;
577
- var InputLabel$1 = InputLabel;
578
-
579
- function getFormControlUtilityClasses(slot) {
580
- return generateUtilityClass('MuiFormControl', slot);
581
- }
582
- generateUtilityClasses('MuiFormControl', ['root', 'marginNone', 'marginNormal', 'marginDense', 'fullWidth', 'disabled']);
583
-
584
- const useUtilityClasses$2 = ownerState => {
585
- const {
586
- classes,
587
- margin,
588
- fullWidth
589
- } = ownerState;
590
- const slots = {
591
- root: ['root', margin !== 'none' && `margin${capitalize(margin)}`, fullWidth && 'fullWidth']
592
- };
593
- return composeClasses(slots, getFormControlUtilityClasses, classes);
594
- };
595
- const FormControlRoot = styled$1('div', {
596
- name: 'MuiFormControl',
597
- slot: 'Root',
598
- overridesResolver: (props, styles) => {
599
- const {
600
- ownerState
601
- } = props;
602
- return [styles.root, styles[`margin${capitalize(ownerState.margin)}`], ownerState.fullWidth && styles.fullWidth];
603
- }
604
- })({
605
- display: 'inline-flex',
606
- flexDirection: 'column',
607
- position: 'relative',
608
- // Reset fieldset default style.
609
- minWidth: 0,
610
- padding: 0,
611
- margin: 0,
612
- border: 0,
613
- verticalAlign: 'top',
614
- // Fix alignment issue on Safari.
615
- variants: [{
616
- props: {
617
- margin: 'normal'
618
- },
619
- style: {
620
- marginTop: 16,
621
- marginBottom: 8
622
- }
623
- }, {
624
- props: {
625
- margin: 'dense'
626
- },
627
- style: {
628
- marginTop: 8,
629
- marginBottom: 4
630
- }
631
- }, {
632
- props: {
633
- fullWidth: true
634
- },
635
- style: {
636
- width: '100%'
637
- }
638
- }]
639
- });
640
-
641
- /**
642
- * Provides context such as filled/focused/error/required for form inputs.
643
- * Relying on the context provides high flexibility and ensures that the state always stays
644
- * consistent across the children of the `FormControl`.
645
- * This context is used by the following components:
646
- *
647
- * - FormLabel
648
- * - FormHelperText
649
- * - Input
650
- * - InputLabel
651
- *
652
- * You can find one composition example below and more going to [the demos](/material-ui/react-text-field/#components).
653
- *
654
- * ```jsx
655
- * <FormControl>
656
- * <InputLabel htmlFor="my-input">Email address</InputLabel>
657
- * <Input id="my-input" aria-describedby="my-helper-text" />
658
- * <FormHelperText id="my-helper-text">We'll never share your email.</FormHelperText>
659
- * </FormControl>
660
- * ```
661
- *
662
- * ⚠️ Only one `InputBase` can be used within a FormControl because it creates visual inconsistencies.
663
- * For instance, only one input can be focused at the same time, the state shouldn't be shared.
664
- */
665
- const FormControl = /*#__PURE__*/React.forwardRef(function FormControl(inProps, ref) {
666
- const props = useDefaultProps({
667
- props: inProps,
668
- name: 'MuiFormControl'
669
- });
670
- const {
671
- children,
672
- className,
673
- color = 'primary',
674
- component = 'div',
675
- disabled = false,
676
- error = false,
677
- focused: visuallyFocused,
678
- fullWidth = false,
679
- hiddenLabel = false,
680
- margin = 'none',
681
- required = false,
682
- size = 'medium',
683
- variant = 'outlined',
684
- ...other
685
- } = props;
686
- const ownerState = {
687
- ...props,
688
- color,
689
- component,
690
- disabled,
691
- error,
692
- fullWidth,
693
- hiddenLabel,
694
- margin,
695
- required,
696
- size,
697
- variant
698
- };
699
- const classes = useUtilityClasses$2(ownerState);
700
- const [adornedStart, setAdornedStart] = React.useState(() => {
701
- // We need to iterate through the children and find the Input in order
702
- // to fully support server-side rendering.
703
- let initialAdornedStart = false;
704
- if (children) {
705
- React.Children.forEach(children, child => {
706
- if (!isMuiElement(child, ['Input', 'Select'])) {
707
- return;
708
- }
709
- const input = isMuiElement(child, ['Select']) ? child.props.input : child;
710
- if (input && isAdornedStart(input.props)) {
711
- initialAdornedStart = true;
712
- }
713
- });
714
- }
715
- return initialAdornedStart;
716
- });
717
- const [filled, setFilled] = React.useState(() => {
718
- // We need to iterate through the children and find the Input in order
719
- // to fully support server-side rendering.
720
- let initialFilled = false;
721
- if (children) {
722
- React.Children.forEach(children, child => {
723
- if (!isMuiElement(child, ['Input', 'Select'])) {
724
- return;
725
- }
726
- if (isFilled(child.props, true) || isFilled(child.props.inputProps, true)) {
727
- initialFilled = true;
728
- }
729
- });
730
- }
731
- return initialFilled;
732
- });
733
- const [focusedState, setFocused] = React.useState(false);
734
- if (disabled && focusedState) {
735
- setFocused(false);
736
- }
737
- const focused = visuallyFocused !== undefined && !disabled ? visuallyFocused : focusedState;
738
- let registerEffect;
739
- const registeredInput = React.useRef(false);
740
- if (process.env.NODE_ENV !== 'production') {
741
- registerEffect = () => {
742
- if (registeredInput.current) {
743
- console.error(['MUI: There are multiple `InputBase` components inside a FormControl.', 'This creates visual inconsistencies, only use one `InputBase`.'].join('\n'));
744
- }
745
- registeredInput.current = true;
746
- return () => {
747
- registeredInput.current = false;
748
- };
749
- };
750
- }
751
- const onFilled = React.useCallback(() => {
752
- setFilled(true);
753
- }, []);
754
- const onEmpty = React.useCallback(() => {
755
- setFilled(false);
756
- }, []);
757
- const childContext = React.useMemo(() => {
758
- return {
759
- adornedStart,
760
- setAdornedStart,
761
- color,
762
- disabled,
763
- error,
764
- filled,
765
- focused,
766
- fullWidth,
767
- hiddenLabel,
768
- size,
769
- onBlur: () => {
770
- setFocused(false);
771
- },
772
- onFocus: () => {
773
- setFocused(true);
774
- },
775
- onEmpty,
776
- onFilled,
777
- registerEffect,
778
- required,
779
- variant
780
- };
781
- }, [adornedStart, color, disabled, error, filled, focused, fullWidth, hiddenLabel, registerEffect, onEmpty, onFilled, required, size, variant]);
782
- return /*#__PURE__*/jsxRuntimeExports.jsx(FormControlContext.Provider, {
783
- value: childContext,
784
- children: /*#__PURE__*/jsxRuntimeExports.jsx(FormControlRoot, {
785
- as: component,
786
- ownerState: ownerState,
787
- className: clsx(classes.root, className),
788
- ref: ref,
789
- ...other,
790
- children: children
791
- })
792
- });
793
- });
794
- process.env.NODE_ENV !== "production" ? FormControl.propTypes /* remove-proptypes */ = {
795
- // ┌────────────────────────────── Warning ──────────────────────────────┐
796
- // │ These PropTypes are generated from the TypeScript type definitions. │
797
- // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
798
- // └─────────────────────────────────────────────────────────────────────┘
799
- /**
800
- * The content of the component.
801
- */
802
- children: PropTypes.node,
803
- /**
804
- * Override or extend the styles applied to the component.
805
- */
806
- classes: PropTypes.object,
807
- /**
808
- * @ignore
809
- */
810
- className: PropTypes.string,
811
- /**
812
- * The color of the component.
813
- * It supports both default and custom theme colors, which can be added as shown in the
814
- * [palette customization guide](https://mui.com/material-ui/customization/palette/#custom-colors).
815
- * @default 'primary'
816
- */
817
- color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['primary', 'secondary', 'error', 'info', 'success', 'warning']), PropTypes.string]),
818
- /**
819
- * The component used for the root node.
820
- * Either a string to use a HTML element or a component.
821
- */
822
- component: PropTypes.elementType,
823
- /**
824
- * If `true`, the label, input and helper text should be displayed in a disabled state.
825
- * @default false
826
- */
827
- disabled: PropTypes.bool,
828
- /**
829
- * If `true`, the label is displayed in an error state.
830
- * @default false
831
- */
832
- error: PropTypes.bool,
833
- /**
834
- * If `true`, the component is displayed in focused state.
835
- */
836
- focused: PropTypes.bool,
837
- /**
838
- * If `true`, the component will take up the full width of its container.
839
- * @default false
840
- */
841
- fullWidth: PropTypes.bool,
842
- /**
843
- * If `true`, the label is hidden.
844
- * This is used to increase density for a `FilledInput`.
845
- * Be sure to add `aria-label` to the `input` element.
846
- * @default false
847
- */
848
- hiddenLabel: PropTypes.bool,
849
- /**
850
- * If `dense` or `normal`, will adjust vertical spacing of this and contained components.
851
- * @default 'none'
852
- */
853
- margin: PropTypes.oneOf(['dense', 'none', 'normal']),
854
- /**
855
- * If `true`, the label will indicate that the `input` is required.
856
- * @default false
857
- */
858
- required: PropTypes.bool,
859
- /**
860
- * The size of the component.
861
- * @default 'medium'
862
- */
863
- size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['medium', 'small']), PropTypes.string]),
864
- /**
865
- * The system prop that allows defining system overrides as well as additional CSS styles.
866
- */
867
- sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
868
- /**
869
- * The variant to use.
870
- * @default 'outlined'
871
- */
872
- variant: PropTypes.oneOf(['filled', 'outlined', 'standard'])
873
- } : void 0;
874
- var FormControl$1 = FormControl;
875
-
876
- function getFormHelperTextUtilityClasses(slot) {
877
- return generateUtilityClass('MuiFormHelperText', slot);
878
- }
879
- const formHelperTextClasses = generateUtilityClasses('MuiFormHelperText', ['root', 'error', 'disabled', 'sizeSmall', 'sizeMedium', 'contained', 'focused', 'filled', 'required']);
880
- var formHelperTextClasses$1 = formHelperTextClasses;
881
-
882
- var _span;
883
- const useUtilityClasses$1 = ownerState => {
884
- const {
885
- classes,
886
- contained,
887
- size,
888
- disabled,
889
- error,
890
- filled,
891
- focused,
892
- required
893
- } = ownerState;
894
- const slots = {
895
- root: ['root', disabled && 'disabled', error && 'error', size && `size${capitalize(size)}`, contained && 'contained', focused && 'focused', filled && 'filled', required && 'required']
896
- };
897
- return composeClasses(slots, getFormHelperTextUtilityClasses, classes);
898
- };
899
- const FormHelperTextRoot = styled$1('p', {
900
- name: 'MuiFormHelperText',
901
- slot: 'Root',
902
- overridesResolver: (props, styles) => {
903
- const {
904
- ownerState
905
- } = props;
906
- return [styles.root, ownerState.size && styles[`size${capitalize(ownerState.size)}`], ownerState.contained && styles.contained, ownerState.filled && styles.filled];
907
- }
908
- })(memoTheme(({
909
- theme
910
- }) => ({
911
- color: (theme.vars || theme).palette.text.secondary,
912
- ...theme.typography.caption,
913
- textAlign: 'left',
914
- marginTop: 3,
915
- marginRight: 0,
916
- marginBottom: 0,
917
- marginLeft: 0,
918
- [`&.${formHelperTextClasses$1.disabled}`]: {
919
- color: (theme.vars || theme).palette.text.disabled
920
- },
921
- [`&.${formHelperTextClasses$1.error}`]: {
922
- color: (theme.vars || theme).palette.error.main
923
- },
924
- variants: [{
925
- props: {
926
- size: 'small'
927
- },
928
- style: {
929
- marginTop: 4
930
- }
931
- }, {
932
- props: ({
933
- ownerState
934
- }) => ownerState.contained,
935
- style: {
936
- marginLeft: 14,
937
- marginRight: 14
938
- }
939
- }]
940
- })));
941
- const FormHelperText = /*#__PURE__*/React.forwardRef(function FormHelperText(inProps, ref) {
942
- const props = useDefaultProps({
943
- props: inProps,
944
- name: 'MuiFormHelperText'
945
- });
946
- const {
947
- children,
948
- className,
949
- component = 'p',
950
- disabled,
951
- error,
952
- filled,
953
- focused,
954
- margin,
955
- required,
956
- variant,
957
- ...other
958
- } = props;
959
- const muiFormControl = useFormControl();
960
- const fcs = formControlState({
961
- props,
962
- muiFormControl,
963
- states: ['variant', 'size', 'disabled', 'error', 'filled', 'focused', 'required']
964
- });
965
- const ownerState = {
966
- ...props,
967
- component,
968
- contained: fcs.variant === 'filled' || fcs.variant === 'outlined',
969
- variant: fcs.variant,
970
- size: fcs.size,
971
- disabled: fcs.disabled,
972
- error: fcs.error,
973
- filled: fcs.filled,
974
- focused: fcs.focused,
975
- required: fcs.required
976
- };
977
-
978
- // This issue explains why this is required: https://github.com/mui/material-ui/issues/42184
979
- delete ownerState.ownerState;
980
- const classes = useUtilityClasses$1(ownerState);
981
- return /*#__PURE__*/jsxRuntimeExports.jsx(FormHelperTextRoot, {
982
- as: component,
983
- className: clsx(classes.root, className),
984
- ref: ref,
985
- ...other,
986
- ownerState: ownerState,
987
- children: children === ' ' ? // notranslate needed while Google Translate will not fix zero-width space issue
988
- _span || (_span = /*#__PURE__*/jsxRuntimeExports.jsx("span", {
989
- className: "notranslate",
990
- "aria-hidden": true,
991
- children: "\u200B"
992
- })) : children
993
- });
994
- });
995
- process.env.NODE_ENV !== "production" ? FormHelperText.propTypes /* remove-proptypes */ = {
996
- // ┌────────────────────────────── Warning ──────────────────────────────┐
997
- // │ These PropTypes are generated from the TypeScript type definitions. │
998
- // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
999
- // └─────────────────────────────────────────────────────────────────────┘
1000
- /**
1001
- * The content of the component.
1002
- *
1003
- * If `' '` is provided, the component reserves one line height for displaying a future message.
1004
- */
1005
- children: PropTypes.node,
1006
- /**
1007
- * Override or extend the styles applied to the component.
1008
- */
1009
- classes: PropTypes.object,
1010
- /**
1011
- * @ignore
1012
- */
1013
- className: PropTypes.string,
1014
- /**
1015
- * The component used for the root node.
1016
- * Either a string to use a HTML element or a component.
1017
- */
1018
- component: PropTypes.elementType,
1019
- /**
1020
- * If `true`, the helper text should be displayed in a disabled state.
1021
- */
1022
- disabled: PropTypes.bool,
1023
- /**
1024
- * If `true`, helper text should be displayed in an error state.
1025
- */
1026
- error: PropTypes.bool,
1027
- /**
1028
- * If `true`, the helper text should use filled classes key.
1029
- */
1030
- filled: PropTypes.bool,
1031
- /**
1032
- * If `true`, the helper text should use focused classes key.
1033
- */
1034
- focused: PropTypes.bool,
1035
- /**
1036
- * If `dense`, will adjust vertical spacing. This is normally obtained via context from
1037
- * FormControl.
1038
- */
1039
- margin: PropTypes.oneOf(['dense']),
1040
- /**
1041
- * If `true`, the helper text should use required classes key.
1042
- */
1043
- required: PropTypes.bool,
1044
- /**
1045
- * The system prop that allows defining system overrides as well as additional CSS styles.
1046
- */
1047
- sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
1048
- /**
1049
- * The variant to use.
1050
- */
1051
- variant: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['filled', 'outlined', 'standard']), PropTypes.string])
1052
- } : void 0;
1053
- var FormHelperText$1 = FormHelperText;
1054
-
1055
- function getTextFieldUtilityClass(slot) {
1056
- return generateUtilityClass('MuiTextField', slot);
1057
- }
1058
- generateUtilityClasses('MuiTextField', ['root']);
1059
-
1060
- const variantComponent = {
1061
- standard: Input,
1062
- filled: FilledInput,
1063
- outlined: OutlinedInput
1064
- };
1065
- const useUtilityClasses = ownerState => {
1066
- const {
1067
- classes
1068
- } = ownerState;
1069
- const slots = {
1070
- root: ['root']
1071
- };
1072
- return composeClasses(slots, getTextFieldUtilityClass, classes);
1073
- };
1074
- const TextFieldRoot = styled$1(FormControl$1, {
1075
- name: 'MuiTextField',
1076
- slot: 'Root',
1077
- overridesResolver: (props, styles) => styles.root
1078
- })({});
1079
-
1080
- /**
1081
- * The `TextField` is a convenience wrapper for the most common cases (80%).
1082
- * It cannot be all things to all people, otherwise the API would grow out of control.
1083
- *
1084
- * ## Advanced Configuration
1085
- *
1086
- * It's important to understand that the text field is a simple abstraction
1087
- * on top of the following components:
1088
- *
1089
- * - [FormControl](/material-ui/api/form-control/)
1090
- * - [InputLabel](/material-ui/api/input-label/)
1091
- * - [FilledInput](/material-ui/api/filled-input/)
1092
- * - [OutlinedInput](/material-ui/api/outlined-input/)
1093
- * - [Input](/material-ui/api/input/)
1094
- * - [FormHelperText](/material-ui/api/form-helper-text/)
1095
- *
1096
- * If you wish to alter the props applied to the `input` element, you can do so as follows:
1097
- *
1098
- * ```jsx
1099
- * const inputProps = {
1100
- * step: 300,
1101
- * };
1102
- *
1103
- * return <TextField id="time" type="time" inputProps={inputProps} />;
1104
- * ```
1105
- *
1106
- * For advanced cases, please look at the source of TextField by clicking on the
1107
- * "Edit this page" button above. Consider either:
1108
- *
1109
- * - using the upper case props for passing values directly to the components
1110
- * - using the underlying components directly as shown in the demos
1111
- */
1112
- const TextField = /*#__PURE__*/React.forwardRef(function TextField(inProps, ref) {
1113
- const props = useDefaultProps({
1114
- props: inProps,
1115
- name: 'MuiTextField'
1116
- });
1117
- const {
1118
- autoComplete,
1119
- autoFocus = false,
1120
- children,
1121
- className,
1122
- color = 'primary',
1123
- defaultValue,
1124
- disabled = false,
1125
- error = false,
1126
- FormHelperTextProps: FormHelperTextPropsProp,
1127
- fullWidth = false,
1128
- helperText,
1129
- id: idOverride,
1130
- InputLabelProps: InputLabelPropsProp,
1131
- inputProps: inputPropsProp,
1132
- InputProps: InputPropsProp,
1133
- inputRef,
1134
- label,
1135
- maxRows,
1136
- minRows,
1137
- multiline = false,
1138
- name,
1139
- onBlur,
1140
- onChange,
1141
- onFocus,
1142
- placeholder,
1143
- required = false,
1144
- rows,
1145
- select = false,
1146
- SelectProps: SelectPropsProp,
1147
- slots = {},
1148
- slotProps = {},
1149
- type,
1150
- value,
1151
- variant = 'outlined',
1152
- ...other
1153
- } = props;
1154
- const ownerState = {
1155
- ...props,
1156
- autoFocus,
1157
- color,
1158
- disabled,
1159
- error,
1160
- fullWidth,
1161
- multiline,
1162
- required,
1163
- select,
1164
- variant
1165
- };
1166
- const classes = useUtilityClasses(ownerState);
1167
- if (process.env.NODE_ENV !== 'production') {
1168
- if (select && !children) {
1169
- console.error('MUI: `children` must be passed when using the `TextField` component with `select`.');
1170
- }
1171
- }
1172
- const id = useId(idOverride);
1173
- const helperTextId = helperText && id ? `${id}-helper-text` : undefined;
1174
- const inputLabelId = label && id ? `${id}-label` : undefined;
1175
- const InputComponent = variantComponent[variant];
1176
- const externalForwardedProps = {
1177
- slots,
1178
- slotProps: {
1179
- input: InputPropsProp,
1180
- inputLabel: InputLabelPropsProp,
1181
- htmlInput: inputPropsProp,
1182
- formHelperText: FormHelperTextPropsProp,
1183
- select: SelectPropsProp,
1184
- ...slotProps
1185
- }
1186
- };
1187
- const inputAdditionalProps = {};
1188
- const inputLabelSlotProps = externalForwardedProps.slotProps.inputLabel;
1189
- if (variant === 'outlined') {
1190
- if (inputLabelSlotProps && typeof inputLabelSlotProps.shrink !== 'undefined') {
1191
- inputAdditionalProps.notched = inputLabelSlotProps.shrink;
1192
- }
1193
- inputAdditionalProps.label = label;
1194
- }
1195
- if (select) {
1196
- // unset defaults from textbox inputs
1197
- if (!SelectPropsProp || !SelectPropsProp.native) {
1198
- inputAdditionalProps.id = undefined;
1199
- }
1200
- inputAdditionalProps['aria-describedby'] = undefined;
1201
- }
1202
- const [RootSlot, rootProps] = useSlot('root', {
1203
- elementType: TextFieldRoot,
1204
- shouldForwardComponentProp: true,
1205
- externalForwardedProps: {
1206
- ...externalForwardedProps,
1207
- ...other
1208
- },
1209
- ownerState,
1210
- className: clsx(classes.root, className),
1211
- ref,
1212
- additionalProps: {
1213
- disabled,
1214
- error,
1215
- fullWidth,
1216
- required,
1217
- color,
1218
- variant
1219
- }
1220
- });
1221
- const [InputSlot, inputProps] = useSlot('input', {
1222
- elementType: InputComponent,
1223
- externalForwardedProps,
1224
- additionalProps: inputAdditionalProps,
1225
- ownerState
1226
- });
1227
- const [InputLabelSlot, inputLabelProps] = useSlot('inputLabel', {
1228
- elementType: InputLabel$1,
1229
- externalForwardedProps,
1230
- ownerState
1231
- });
1232
- const [HtmlInputSlot, htmlInputProps] = useSlot('htmlInput', {
1233
- elementType: 'input',
1234
- externalForwardedProps,
1235
- ownerState
1236
- });
1237
- const [FormHelperTextSlot, formHelperTextProps] = useSlot('formHelperText', {
1238
- elementType: FormHelperText$1,
1239
- externalForwardedProps,
1240
- ownerState
1241
- });
1242
- const [SelectSlot, selectProps] = useSlot('select', {
1243
- elementType: Select,
1244
- externalForwardedProps,
1245
- ownerState
1246
- });
1247
- const InputElement = /*#__PURE__*/jsxRuntimeExports.jsx(InputSlot, {
1248
- "aria-describedby": helperTextId,
1249
- autoComplete: autoComplete,
1250
- autoFocus: autoFocus,
1251
- defaultValue: defaultValue,
1252
- fullWidth: fullWidth,
1253
- multiline: multiline,
1254
- name: name,
1255
- rows: rows,
1256
- maxRows: maxRows,
1257
- minRows: minRows,
1258
- type: type,
1259
- value: value,
1260
- id: id,
1261
- inputRef: inputRef,
1262
- onBlur: onBlur,
1263
- onChange: onChange,
1264
- onFocus: onFocus,
1265
- placeholder: placeholder,
1266
- inputProps: htmlInputProps,
1267
- slots: {
1268
- input: slots.htmlInput ? HtmlInputSlot : undefined
1269
- },
1270
- ...inputProps
1271
- });
1272
- return /*#__PURE__*/jsxRuntimeExports.jsxs(RootSlot, {
1273
- ...rootProps,
1274
- children: [label != null && label !== '' && /*#__PURE__*/jsxRuntimeExports.jsx(InputLabelSlot, {
1275
- htmlFor: id,
1276
- id: inputLabelId,
1277
- ...inputLabelProps,
1278
- children: label
1279
- }), select ? /*#__PURE__*/jsxRuntimeExports.jsx(SelectSlot, {
1280
- "aria-describedby": helperTextId,
1281
- id: id,
1282
- labelId: inputLabelId,
1283
- value: value,
1284
- input: InputElement,
1285
- ...selectProps,
1286
- children: children
1287
- }) : InputElement, helperText && /*#__PURE__*/jsxRuntimeExports.jsx(FormHelperTextSlot, {
1288
- id: helperTextId,
1289
- ...formHelperTextProps,
1290
- children: helperText
1291
- })]
1292
- });
1293
- });
1294
- process.env.NODE_ENV !== "production" ? TextField.propTypes /* remove-proptypes */ = {
1295
- // ┌────────────────────────────── Warning ──────────────────────────────┐
1296
- // │ These PropTypes are generated from the TypeScript type definitions. │
1297
- // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
1298
- // └─────────────────────────────────────────────────────────────────────┘
1299
- /**
1300
- * This prop helps users to fill forms faster, especially on mobile devices.
1301
- * The name can be confusing, as it's more like an autofill.
1302
- * You can learn more about it [following the specification](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill).
1303
- */
1304
- autoComplete: PropTypes.string,
1305
- /**
1306
- * If `true`, the `input` element is focused during the first mount.
1307
- * @default false
1308
- */
1309
- autoFocus: PropTypes.bool,
1310
- /**
1311
- * @ignore
1312
- */
1313
- children: PropTypes.node,
1314
- /**
1315
- * Override or extend the styles applied to the component.
1316
- */
1317
- classes: PropTypes.object,
1318
- /**
1319
- * @ignore
1320
- */
1321
- className: PropTypes.string,
1322
- /**
1323
- * The color of the component.
1324
- * It supports both default and custom theme colors, which can be added as shown in the
1325
- * [palette customization guide](https://mui.com/material-ui/customization/palette/#custom-colors).
1326
- * @default 'primary'
1327
- */
1328
- color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['primary', 'secondary', 'error', 'info', 'success', 'warning']), PropTypes.string]),
1329
- /**
1330
- * The default value. Use when the component is not controlled.
1331
- */
1332
- defaultValue: PropTypes.any,
1333
- /**
1334
- * If `true`, the component is disabled.
1335
- * @default false
1336
- */
1337
- disabled: PropTypes.bool,
1338
- /**
1339
- * If `true`, the label is displayed in an error state.
1340
- * @default false
1341
- */
1342
- error: PropTypes.bool,
1343
- /**
1344
- * Props applied to the [`FormHelperText`](https://mui.com/material-ui/api/form-helper-text/) element.
1345
- * @deprecated Use `slotProps.formHelperText` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details.
1346
- */
1347
- FormHelperTextProps: PropTypes.object,
1348
- /**
1349
- * If `true`, the input will take up the full width of its container.
1350
- * @default false
1351
- */
1352
- fullWidth: PropTypes.bool,
1353
- /**
1354
- * The helper text content.
1355
- */
1356
- helperText: PropTypes.node,
1357
- /**
1358
- * The id of the `input` element.
1359
- * Use this prop to make `label` and `helperText` accessible for screen readers.
1360
- */
1361
- id: PropTypes.string,
1362
- /**
1363
- * Props applied to the [`InputLabel`](https://mui.com/material-ui/api/input-label/) element.
1364
- * Pointer events like `onClick` are enabled if and only if `shrink` is `true`.
1365
- * @deprecated Use `slotProps.inputLabel` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details.
1366
- */
1367
- InputLabelProps: PropTypes.object,
1368
- /**
1369
- * [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Attributes) applied to the `input` element.
1370
- * @deprecated Use `slotProps.htmlInput` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details.
1371
- */
1372
- inputProps: PropTypes.object,
1373
- /**
1374
- * Props applied to the Input element.
1375
- * It will be a [`FilledInput`](https://mui.com/material-ui/api/filled-input/),
1376
- * [`OutlinedInput`](https://mui.com/material-ui/api/outlined-input/) or [`Input`](https://mui.com/material-ui/api/input/)
1377
- * component depending on the `variant` prop value.
1378
- * @deprecated Use `slotProps.input` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details.
1379
- */
1380
- InputProps: PropTypes.object,
1381
- /**
1382
- * Pass a ref to the `input` element.
1383
- */
1384
- inputRef: refType,
1385
- /**
1386
- * The label content.
1387
- */
1388
- label: PropTypes.node,
1389
- /**
1390
- * If `dense` or `normal`, will adjust vertical spacing of this and contained components.
1391
- * @default 'none'
1392
- */
1393
- margin: PropTypes.oneOf(['dense', 'none', 'normal']),
1394
- /**
1395
- * Maximum number of rows to display when multiline option is set to true.
1396
- */
1397
- maxRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
1398
- /**
1399
- * Minimum number of rows to display when multiline option is set to true.
1400
- */
1401
- minRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
1402
- /**
1403
- * If `true`, a `textarea` element is rendered instead of an input.
1404
- * @default false
1405
- */
1406
- multiline: PropTypes.bool,
1407
- /**
1408
- * Name attribute of the `input` element.
1409
- */
1410
- name: PropTypes.string,
1411
- /**
1412
- * @ignore
1413
- */
1414
- onBlur: PropTypes.func,
1415
- /**
1416
- * Callback fired when the value is changed.
1417
- *
1418
- * @param {object} event The event source of the callback.
1419
- * You can pull out the new value by accessing `event.target.value` (string).
1420
- */
1421
- onChange: PropTypes.func,
1422
- /**
1423
- * @ignore
1424
- */
1425
- onFocus: PropTypes.func,
1426
- /**
1427
- * The short hint displayed in the `input` before the user enters a value.
1428
- */
1429
- placeholder: PropTypes.string,
1430
- /**
1431
- * If `true`, the label is displayed as required and the `input` element is required.
1432
- * @default false
1433
- */
1434
- required: PropTypes.bool,
1435
- /**
1436
- * Number of rows to display when multiline option is set to true.
1437
- */
1438
- rows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
1439
- /**
1440
- * Render a [`Select`](https://mui.com/material-ui/api/select/) element while passing the Input element to `Select` as `input` parameter.
1441
- * If this option is set you must pass the options of the select as children.
1442
- * @default false
1443
- */
1444
- select: PropTypes.bool,
1445
- /**
1446
- * Props applied to the [`Select`](https://mui.com/material-ui/api/select/) element.
1447
- * @deprecated Use `slotProps.select` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details.
1448
- */
1449
- SelectProps: PropTypes.object,
1450
- /**
1451
- * The size of the component.
1452
- * @default 'medium'
1453
- */
1454
- size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['medium', 'small']), PropTypes.string]),
1455
- /**
1456
- * The props used for each slot inside.
1457
- * @default {}
1458
- */
1459
- slotProps: PropTypes /* @typescript-to-proptypes-ignore */.shape({
1460
- formHelperText: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
1461
- htmlInput: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
1462
- input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
1463
- inputLabel: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
1464
- select: PropTypes.oneOfType([PropTypes.func, PropTypes.object])
1465
- }),
1466
- /**
1467
- * The components used for each slot inside.
1468
- * @default {}
1469
- */
1470
- slots: PropTypes.shape({
1471
- formHelperText: PropTypes.elementType,
1472
- htmlInput: PropTypes.elementType,
1473
- input: PropTypes.elementType,
1474
- inputLabel: PropTypes.elementType,
1475
- root: PropTypes.elementType,
1476
- select: PropTypes.elementType
1477
- }),
1478
- /**
1479
- * The system prop that allows defining system overrides as well as additional CSS styles.
1480
- */
1481
- sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
1482
- /**
1483
- * Type of the `input` element. It should be [a valid HTML5 input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types).
1484
- */
1485
- type: PropTypes /* @typescript-to-proptypes-ignore */.string,
1486
- /**
1487
- * The value of the `input` element, required for a controlled component.
1488
- */
1489
- value: PropTypes.any,
1490
- /**
1491
- * The variant to use.
1492
- * @default 'outlined'
1493
- */
1494
- variant: PropTypes.oneOf(['filled', 'outlined', 'standard'])
1495
- } : void 0;
1496
- var TextField$1 = TextField;
1497
-
1498
17
  const SUBMIT_FILTER_STROKE_TIME = 500;
1499
18
  const InputNumberInterval = props => {
1500
19
  var _item$value;
@@ -1535,7 +54,7 @@ const InputNumberInterval = props => {
1535
54
  const newLowerBound = event.target.value;
1536
55
  updateFilterValue(newLowerBound, filterValueState[1]);
1537
56
  };
1538
- return /*#__PURE__*/React.createElement(Box$1, {
57
+ return /*#__PURE__*/React.createElement(Box, {
1539
58
  sx: {
1540
59
  display: 'inline-flex',
1541
60
  flexDirection: 'row',
@@ -1543,7 +62,7 @@ const InputNumberInterval = props => {
1543
62
  height: 48,
1544
63
  pl: '20px'
1545
64
  }
1546
- }, /*#__PURE__*/React.createElement(TextField$1, {
65
+ }, /*#__PURE__*/React.createElement(TextField, {
1547
66
  name: "lower-bound-input",
1548
67
  placeholder: "From",
1549
68
  label: "From",
@@ -1555,7 +74,7 @@ const InputNumberInterval = props => {
1555
74
  sx: {
1556
75
  mr: 2
1557
76
  }
1558
- }), /*#__PURE__*/React.createElement(TextField$1, {
77
+ }), /*#__PURE__*/React.createElement(TextField, {
1559
78
  name: "upper-bound-input",
1560
79
  placeholder: "To",
1561
80
  label: "To",
@@ -2184,14 +703,20 @@ const DIMENSION_MODEL_KEY = 'dimension';
2184
703
  const FILTER_SEARCH_KEY = 'searchModel';
2185
704
  const DENSITY_MODEL_KEY = 'densityModel';
2186
705
  const COLUMN_ORDER_MODEL_KEY = 'columnOrderModel';
2187
- 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];
706
+ const ROW_GROUPING_MODEL_KEY = 'rowGroupingModel';
707
+ const AGGREGATION_MODEL_KEY = 'aggregationModel';
708
+ /** Storage category key for the pivot column/row/value configuration. Consumer interop — use with `buildStorageKey`. */
709
+ const PIVOT_MODEL_KEY = 'pivotModel';
710
+ /** Storage category key for whether pivoting is active. Consumer interop — use with `buildStorageKey`. */
711
+ const PIVOT_ACTIVE_KEY = 'pivotActive';
712
+ 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, ROW_GROUPING_MODEL_KEY, AGGREGATION_MODEL_KEY, PIVOT_MODEL_KEY, PIVOT_ACTIVE_KEY];
2188
713
  /**
2189
714
  * Build the localStorage key for a specific grid state category.
2190
715
  * Consumers can use this to read or clear individual state entries directly.
2191
716
  *
2192
717
  * @example
2193
718
  * ```ts
2194
- * const key = buildStorageKey({ id: pathname, version: 2, category: SORT_MODEL_KEY });
719
+ * const key = buildStorageKey({ id: pathname, version: 2, category: PIVOT_ACTIVE_KEY });
2195
720
  * localStorage.removeItem(key);
2196
721
  * ```
2197
722
  */
@@ -2241,6 +766,22 @@ const clearPreviousVersionStorage = (id, previousLocalStorageVersions) => {
2241
766
  id,
2242
767
  version,
2243
768
  category: COLUMN_ORDER_MODEL_KEY
769
+ }), buildStorageKey({
770
+ id,
771
+ version,
772
+ category: ROW_GROUPING_MODEL_KEY
773
+ }), buildStorageKey({
774
+ id,
775
+ version,
776
+ category: AGGREGATION_MODEL_KEY
777
+ }), buildStorageKey({
778
+ id,
779
+ version,
780
+ category: PIVOT_MODEL_KEY
781
+ }), buildStorageKey({
782
+ id,
783
+ version,
784
+ category: PIVOT_ACTIVE_KEY
2244
785
  })];
2245
786
  for (const keyToDelete of keysToDelete) {
2246
787
  try {
@@ -2315,13 +856,14 @@ const resetStatefulDataGridState = _ref2 => {
2315
856
  * default (all columns visible). Clears the persisted `visibilityModel`
2316
857
  * localStorage entry for the given versions and strips the `_columnVisibility`
2317
858
  * param from the live URL, leaving every other piece of grid state (filters,
2318
- * sort, pagination, pinned columns, …) untouched.
859
+ * sort, pivot, pagination, pinned columns, …) untouched.
2319
860
  *
2320
861
  * This is the visibility-scoped counterpart to `resetStatefulDataGridState`.
2321
- * Reach for it at transition points where a stale visibility snapshot would
2322
- * otherwise re-seed and keep base columns hidden. Because it leaves the other
2323
- * params alone it will not clobber a filter the caller is writing in the same
2324
- * transition.
862
+ * Reach for it at transition points — for example drilling down from pivot
863
+ * mode to a flat record view — where a stale pivot-era visibility snapshot
864
+ * would otherwise re-seed and keep base columns hidden. Because it leaves the
865
+ * other params alone it will not clobber a filter the caller is writing in the
866
+ * same transition.
2325
867
  *
2326
868
  * Like `resetStatefulDataGridState`, the URL strip reads `window.location.search`
2327
869
  * directly so it operates on the live URL (the caller's captured snapshot may
@@ -2371,7 +913,7 @@ const COMPRESSED_PREFIX = '~';
2371
913
  * Params listed first are compressed first (least valuable to read in the URL).
2372
914
  * The filter aggregate step uses the special key `_filters_aggregate`.
2373
915
  */
2374
- const COMPRESSION_PRIORITY = ['_columnOrder', '_columnVisibility', '_pinnedColumnsLeft', '_pinnedColumnsRight', '_filters_aggregate', '_quickFilterValues'];
916
+ const COMPRESSION_PRIORITY = ['_columnOrder', '_columnVisibility', '_pinnedColumnsLeft', '_pinnedColumnsRight', '_filters_aggregate', '_aggregation', '_rowGrouping', '_quickFilterValues', '_pivot'];
2375
917
 
2376
918
  /** Params that are always short and should never be compressed. */
2377
919
  const NEVER_COMPRESS = new Set(['_sortColumn', '_pagination', '_density', '_logicOperator', 'v', 'tab']);
@@ -2449,7 +991,7 @@ const tryAggregateFilters = (params, filterKeys) => {
2449
991
  * Filter params are those that start with `_` but are not well-known system params.
2450
992
  */
2451
993
  const getFilterParamKeys = params => {
2452
- const systemPrefixes = ['_sortColumn', '_pagination', '_density', '_logicOperator', '_quickFilterValues', '_columnVisibility', '_columnOrder', '_pinnedColumnsLeft', '_pinnedColumnsRight', '_filters'];
994
+ const systemPrefixes = ['_sortColumn', '_pagination', '_density', '_logicOperator', '_quickFilterValues', '_columnVisibility', '_columnOrder', '_pinnedColumnsLeft', '_pinnedColumnsRight', '_rowGrouping', '_aggregation', '_pivot', '_filters'];
2453
995
  const filterKeys = [];
2454
996
  for (const key of params.keys()) {
2455
997
  if (!key.startsWith('_')) continue;
@@ -2680,6 +1222,21 @@ const convertToDisplayFormat = search => {
2680
1222
  return param;
2681
1223
  }
2682
1224
 
1225
+ // Handle _rowGrouping=[a,b,c]
1226
+ if (param.startsWith('_rowGrouping=')) {
1227
+ const value = param.slice('_rowGrouping='.length);
1228
+ if (value.startsWith('[') && value.endsWith(']')) {
1229
+ const inner = value.slice(1, -1);
1230
+ return `_rowGrouping=${inner}`;
1231
+ }
1232
+ return param;
1233
+ }
1234
+
1235
+ // _aggregation and _pivot do not use bracket notation — pass through
1236
+ if (param.startsWith('_aggregation=') || param.startsWith('_pivot=')) {
1237
+ return param;
1238
+ }
1239
+
2683
1240
  // Handle _field[operator,type]=value or _field[operator,type]=list[a,b,c]
2684
1241
  const bracketMatch = param.match(/^_([^[]+)\[([^\]]+)\]=(.*)$/);
2685
1242
  if (bracketMatch) {
@@ -2793,8 +1350,17 @@ const convertFromDisplayFormat = (search, columns) => {
2793
1350
  return `_columnOrder=[${value}]`;
2794
1351
  }
2795
1352
 
2796
- // _filters — pass through (no bracket conversion needed)
2797
- if (param.startsWith('_filters=')) {
1353
+ // Handle _rowGrouping=a,b,c
1354
+ if (param.startsWith('_rowGrouping=')) {
1355
+ const value = param.slice('_rowGrouping='.length);
1356
+ if (value.startsWith('[')) {
1357
+ return param;
1358
+ }
1359
+ return `_rowGrouping=[${value}]`;
1360
+ }
1361
+
1362
+ // _aggregation, _pivot, _filters — pass through (no bracket conversion needed)
1363
+ if (param.startsWith('_aggregation=') || param.startsWith('_pivot=') || param.startsWith('_filters=')) {
2798
1364
  return param;
2799
1365
  }
2800
1366
 
@@ -2841,8 +1407,9 @@ const getDecodedSearchFromUrl = (search, columns) => {
2841
1407
  const hasPinnedWithoutBrackets = /(_pinnedColumnsLeft|_pinnedColumnsRight)=[^&[]*(&|$)/.test(searchWithoutLeadingQuestion);
2842
1408
  const hasVisibilityWithoutBrackets = /_columnVisibility=[^&[]*(&|$)/.test(searchWithoutLeadingQuestion);
2843
1409
  const hasColumnOrderWithoutBrackets = /_columnOrder=[^&[]*(&|$)/.test(searchWithoutLeadingQuestion);
1410
+ const hasRowGroupingWithoutBrackets = /_rowGrouping=[^&[]*(&|$)/.test(searchWithoutLeadingQuestion);
2844
1411
  const hasBracketNotation = /\[.*\]=/.test(searchWithoutLeadingQuestion);
2845
- const isDisplayFormat = (hasDotNotationFilter || hasEmptySortColumn || hasSortDotNotation || hasPaginationDotNotation || hasPinnedWithoutBrackets || hasVisibilityWithoutBrackets || hasColumnOrderWithoutBrackets) && !hasBracketNotation;
1412
+ const isDisplayFormat = (hasDotNotationFilter || hasEmptySortColumn || hasSortDotNotation || hasPaginationDotNotation || hasPinnedWithoutBrackets || hasVisibilityWithoutBrackets || hasColumnOrderWithoutBrackets || hasRowGroupingWithoutBrackets) && !hasBracketNotation;
2846
1413
  if (isDisplayFormat) {
2847
1414
  return '?' + convertFromDisplayFormat(searchWithoutLeadingQuestion, columns);
2848
1415
  }
@@ -2967,12 +1534,17 @@ const isValueValid = (value, field, columns, operator) => {
2967
1534
  }
2968
1535
  const type = (_column$type = column['type']) !== null && _column$type !== void 0 ? _column$type : 'string';
2969
1536
 
2970
- // Only date and dateTime need value validation; other types either accept
2971
- // any string or reset themselves to undefined.
2972
- if (type !== 'date' && type !== 'dateTime') {
1537
+ // Only date, dateTime and rating need value validation; other types either
1538
+ // accept any string or reset themselves to undefined.
1539
+ if (type !== 'date' && type !== 'dateTime' && type !== 'rating') {
2973
1540
  return true;
2974
1541
  }
2975
1542
 
1543
+ // just checking that rating is a number.
1544
+ if (type === 'rating') {
1545
+ return !isNaN(Number(value));
1546
+ }
1547
+
2976
1548
  // format: YYYY-MM-DD — strict, so corrupt/locale/ISO strings (e.g. a
2977
1549
  // stringified Date, or an ISO value with a `T` suffix) are rejected rather
2978
1550
  // than reaching a server consumer. Canonical values come from
@@ -2993,7 +1565,7 @@ const isValueValid = (value, field, columns, operator) => {
2993
1565
 
2994
1566
  // example:
2995
1567
  // unicodeDomain[contains]=a&unicodeDomain[contains]=dsa&logicOperator=and&tab=ignored
2996
- const getFilterModelFromString = (searchString, columns) => {
1568
+ const getFilterModelFromString = (searchString, columns, previousModel) => {
2997
1569
  if (!searchString) {
2998
1570
  return 'invalid';
2999
1571
  }
@@ -3006,7 +1578,7 @@ const getFilterModelFromString = (searchString, columns) => {
3006
1578
  let hasFilterMeta = false;
3007
1579
  const searchParams = new URLSearchParams();
3008
1580
  for (const [key, value] of new URLSearchParams(searchString)) {
3009
- if (key.startsWith('_') && !['_logicOperator', '_sortColumn', '_pinnedColumnsLeft', '_pinnedColumnsRight', '_columnVisibility', '_pagination', '_quickFilterValues', '_columnOrder', '_density', '_filters'].includes(key)) {
1581
+ if (key.startsWith('_') && !['_logicOperator', '_sortColumn', '_pinnedColumnsLeft', '_pinnedColumnsRight', '_columnVisibility', '_pagination', '_quickFilterValues', '_columnOrder', '_rowGrouping', '_aggregation', '_pivot', '_density', '_filters'].includes(key)) {
3010
1582
  searchParams.set(key, value);
3011
1583
  }
3012
1584
  if (key === '_logicOperator') {
@@ -3023,6 +1595,21 @@ const getFilterModelFromString = (searchString, columns) => {
3023
1595
  }
3024
1596
  }
3025
1597
  let id = 5000;
1598
+
1599
+ // Positional id-reuse map keyed by `${field}|${operator}` (decoded operator, matching
1600
+ // the item we push below). FIFO per key so duplicate field+operator items map by order.
1601
+ // Reusing the id of a previously parsed/emitted item keeps `item.id` stable across the
1602
+ // URL write→parse roundtrip (the URL never carries the id), so MUI's GridFilterForm
1603
+ // React key does not change and the filter panel input keeps focus. (DS-81)
1604
+ const previousIdsByKey = new Map();
1605
+ if (previousModel) {
1606
+ for (const prev of previousModel.items) {
1607
+ if (prev.id === undefined) continue;
1608
+ const key = `${prev.field}|${prev.operator}`;
1609
+ const bucket = previousIdsByKey.get(key);
1610
+ if (bucket) bucket.push(prev.id);else previousIdsByKey.set(key, [prev.id]);
1611
+ }
1612
+ }
3026
1613
  const fields = columns.map(column => column.field);
3027
1614
  let isInvalid = false;
3028
1615
  const items = [];
@@ -3058,10 +1645,13 @@ const getFilterModelFromString = (searchString, columns) => {
3058
1645
  isInvalid = true;
3059
1646
  return;
3060
1647
  }
1648
+ const finalOperator = columnType === 'number' && Object.keys(numberOperatorDecoder).includes(operator) ? numberOperatorDecoder[operator] : operator;
1649
+ const reuseBucket = previousIdsByKey.get(`${field}|${finalOperator}`);
1650
+ const stableId = reuseBucket && reuseBucket.length > 0 ? reuseBucket.shift() : id;
3061
1651
  items.push({
3062
1652
  field,
3063
- operator: columnType === 'number' && Object.keys(numberOperatorDecoder).includes(operator) ? numberOperatorDecoder[operator] : operator,
3064
- id,
1653
+ operator: finalOperator,
1654
+ id: stableId,
3065
1655
  value: listOperators.includes(operator) && decodedValue === '' ? [] : decodedValue,
3066
1656
  type
3067
1657
  });
@@ -3101,7 +1691,7 @@ const getFilterModelFromString = (searchString, columns) => {
3101
1691
  * - `dateTime` → `YYYY-MM-DDTHH:mm:ss` (local wall-clock, matching the
3102
1692
  * datetime-local input)
3103
1693
  * Non-date types, empty values, arrays of dates (e.g. `isBetween`) and
3104
- * unparseable values are handled so the existing string / number / list
1694
+ * unparseable values are handled so the existing string / number / list / rating
3105
1695
  * paths are untouched and invalid values still fall through to `isValueValid`.
3106
1696
  */
3107
1697
  const normalizeDateValue = (value, type) => {
@@ -3155,7 +1745,7 @@ const getSearchParamsFromFilterModel = filterModel => {
3155
1745
  // - if we have something in the URL, use that info
3156
1746
  // - if we don't have that, use the localStorage and update the URL
3157
1747
  // - if we don't have that, return an empty FilterModel
3158
- const getFilterModel = (search, columns, localStorageFilters, setLocalStorageFilters, initialState, isNewVersion) => {
1748
+ const getFilterModel = (search, columns, localStorageFilters, setLocalStorageFilters, initialState, isNewVersion, previousModel) => {
3159
1749
  const defaultValue = initialState && initialState.filter && initialState.filter.filterModel ? initialState.filter.filterModel : {
3160
1750
  items: [],
3161
1751
  logicOperator: GridLogicOperator.And
@@ -3173,7 +1763,7 @@ const getFilterModel = (search, columns, localStorageFilters, setLocalStorageFil
3173
1763
  persistDefaultFilters();
3174
1764
  return defaultValue;
3175
1765
  }
3176
- const filterModelFromSearch = getFilterModelFromString(search, columns);
1766
+ const filterModelFromSearch = getFilterModelFromString(search, columns, previousModel);
3177
1767
  if (filterModelFromSearch !== 'invalid') {
3178
1768
  const searchFromFilterModel = getSearchParamsFromFilterModel(filterModelFromSearch);
3179
1769
  const searchString = urlSearchParamsToString(searchFromFilterModel);
@@ -3356,8 +1946,8 @@ const getColumnVisibilityFromString = (searchString, columns) => {
3356
1946
  // by 12.5.5-muiv8-alpha.5/alpha.6 (replaced by the URL-safe `!` form below — see
3357
1947
  // getSearchParamsFromColumnVisibility). Still parsed so any localStorage entry
3358
1948
  // persisted by those alphas keeps working; never written anymore. The two sets are
3359
- // split on the fixed `];h:[` separator, so a `[` or `]` inside a field name does
3360
- // not break parsing.
1949
+ // split on the fixed `];h:[` separator, so a `[` or `]` inside a pivot field name
1950
+ // (e.g. `["gmail"]>->email_volume`) does not break parsing.
3361
1951
  if (value.startsWith('v:[') && value.endsWith(']') && value.includes('];h:[')) {
3362
1952
  const inner = value.slice('v:['.length, -1);
3363
1953
  const separatorIndex = inner.indexOf('];h:[');
@@ -3447,14 +2037,14 @@ const getSearchParamsFromColumnVisibility = (columnVisibility, columns) => {
3447
2037
  }, columnVisibility);
3448
2038
 
3449
2039
  // Serialise a single comma list over the static columns plus any extra keys present
3450
- // in the model (dynamically-generated fields, preserved in model order). Hidden
3451
- // fields are prefixed `!`; visible fields are bare. This URL-safe form round-trips
3452
- // idempotently through `URLSearchParams.toString()` percent-encoding — the earlier
3453
- // `v:[..];h:[..]` form did NOT (its `:`/`;` were percent-encoded and mis-parsed on
3454
- // read-back, driving an unbounded `history.replace` loop, ODM-3033). `!` (not `~`,
3455
- // the compression sentinel — compression.ts COMPRESSED_PREFIX) is used so a leading
3456
- // hidden field can't be mistaken for a compressed value. The hidden set is still
3457
- // explicit, so deselected dynamic columns survive reloads.
2040
+ // in the model (dynamically-generated pivot fields, preserved in model order).
2041
+ // Hidden fields are prefixed `!`; visible fields are bare. This URL-safe form
2042
+ // round-trips idempotently through `URLSearchParams.toString()` percent-encoding —
2043
+ // the earlier `v:[..];h:[..]` form did NOT (its `:`/`;` were percent-encoded and
2044
+ // mis-parsed on read-back, driving an unbounded `history.replace` loop, ODM-3033).
2045
+ // `!` (not `~`, which is the compression sentinel — compression.ts COMPRESSED_PREFIX)
2046
+ // is used so a leading hidden field can't be mistaken for a compressed value.
2047
+ // The hidden set is still explicit, so deselected dynamic columns survive reloads.
3458
2048
  const allFields = [...fields];
3459
2049
  for (const field of Object.keys(finalColumnVisibility)) {
3460
2050
  if (!allFields.includes(field)) {
@@ -3670,29 +2260,6 @@ const getSearchParamsFromColumnOrder = columnOrder => {
3670
2260
  }
3671
2261
  return searchParams;
3672
2262
  };
3673
-
3674
- /**
3675
- * Builds the `v=<version>` search param the grid uses to detect stale URLs.
3676
- *
3677
- * Use this when constructing a deep link or share URL outside the grid (e.g. via
3678
- * `getSearchParamsFromFilterModel` + `buildQueryParamsString`). The grid itself
3679
- * always includes `v` in URLs it writes via `getFinalSearch`, but external helpers
3680
- * do not — include this when you want a stale `localStorageVersion` bump to invalidate
3681
- * the link. Omitting `v` is also safe: a missing `v` is treated as "current version"
3682
- * and the URL state is applied as-is.
3683
- *
3684
- * @example
3685
- * const params = new URLSearchParams([
3686
- * ...getSearchParamsFromFilterModel(filterModel),
3687
- * ...getSearchParamsFromVersion(1),
3688
- * ]);
3689
- * const url = `/my-grid${buildQueryParamsString(urlSearchParamsToString(params))}`;
3690
- */
3691
- const getSearchParamsFromVersion = version => {
3692
- const searchParams = new URLSearchParams();
3693
- searchParams.set('v', String(version));
3694
- return searchParams;
3695
- };
3696
2263
  const getColumnOrder = (search, columns, localStorageColumnOrder, setLocalStorageColumnOrder, initialState, isNewVersion) => {
3697
2264
  var _initialState$columns4, _initialState$columns5;
3698
2265
  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);
@@ -3723,7 +2290,298 @@ const getColumnOrder = (search, columns, localStorageColumnOrder, setLocalStorag
3723
2290
  persistDefault();
3724
2291
  return defaultValue;
3725
2292
  };
3726
- const getFinalSearch = _ref => {
2293
+
2294
+ /** ROW GROUPING */
2295
+
2296
+ const getRowGroupingFromString = searchString => {
2297
+ if (!searchString) return 'invalid';
2298
+ const searchParams = new URLSearchParams(searchString);
2299
+ const value = searchParams.get('_rowGrouping');
2300
+ if (value === '' || value === null || value === '[]') return 'invalid';
2301
+ const inner = value.startsWith('[') && value.endsWith(']') ? value.slice(1, -1) : value;
2302
+ if (!inner) return 'invalid';
2303
+ return inner.split(',').filter(Boolean);
2304
+ };
2305
+ const getSearchParamsFromRowGrouping = rowGrouping => {
2306
+ const searchParams = new URLSearchParams();
2307
+ if (rowGrouping.length > 0) {
2308
+ searchParams.set('_rowGrouping', `[${rowGrouping.join(',')}]`);
2309
+ }
2310
+ return searchParams;
2311
+ };
2312
+ const getRowGroupingModel = (search, localStorageRowGrouping, setLocalStorageRowGrouping, initialState, isNewVersion) => {
2313
+ var _initialState$rowGrou, _initialState$rowGrou2;
2314
+ const defaultValue = (_initialState$rowGrou = initialState === null || initialState === void 0 ? void 0 : (_initialState$rowGrou2 = initialState.rowGrouping) === null || _initialState$rowGrou2 === void 0 ? void 0 : _initialState$rowGrou2.model) !== null && _initialState$rowGrou !== void 0 ? _initialState$rowGrou : [];
2315
+ const persistDefault = () => {
2316
+ const searchFromDefault = getSearchParamsFromRowGrouping(defaultValue);
2317
+ const searchString = urlSearchParamsToString(searchFromDefault);
2318
+ if (searchString !== localStorageRowGrouping) {
2319
+ setLocalStorageRowGrouping(searchString);
2320
+ }
2321
+ };
2322
+ if (isNewVersion) {
2323
+ persistDefault();
2324
+ return defaultValue;
2325
+ }
2326
+ const fromUrl = getRowGroupingFromString(search);
2327
+ if (fromUrl !== 'invalid') {
2328
+ const searchFromModel = getSearchParamsFromRowGrouping(fromUrl);
2329
+ const searchString = urlSearchParamsToString(searchFromModel);
2330
+ if (searchString !== localStorageRowGrouping) {
2331
+ setLocalStorageRowGrouping(searchString);
2332
+ }
2333
+ return fromUrl;
2334
+ }
2335
+ const fromLocalStorage = getRowGroupingFromString(localStorageRowGrouping);
2336
+ if (fromLocalStorage !== 'invalid') {
2337
+ return fromLocalStorage;
2338
+ }
2339
+ persistDefault();
2340
+ return defaultValue;
2341
+ };
2342
+
2343
+ /** AGGREGATION */
2344
+
2345
+ const getAggregationFromString = searchString => {
2346
+ if (!searchString) return 'invalid';
2347
+ const searchParams = new URLSearchParams(searchString);
2348
+ const value = searchParams.get('_aggregation');
2349
+ if (value === '' || value === null) return 'invalid';
2350
+
2351
+ // Format: field1.sum,field2.avg or [field1.sum,field2.avg]
2352
+ const inner = value.startsWith('[') && value.endsWith(']') ? value.slice(1, -1) : value;
2353
+ if (!inner) return 'invalid';
2354
+ const model = {};
2355
+ for (const entry of inner.split(',')) {
2356
+ const dotIndex = entry.lastIndexOf('.');
2357
+ if (dotIndex <= 0) return 'invalid';
2358
+ const field = entry.slice(0, dotIndex);
2359
+ const aggFunc = entry.slice(dotIndex + 1);
2360
+ if (!field || !aggFunc) return 'invalid';
2361
+ model[field] = aggFunc;
2362
+ }
2363
+ return Object.keys(model).length > 0 ? model : 'invalid';
2364
+ };
2365
+ const getSearchParamsFromAggregation = aggregation => {
2366
+ const searchParams = new URLSearchParams();
2367
+ const entries = Object.entries(aggregation);
2368
+ if (entries.length > 0) {
2369
+ const value = entries.map(_ref => {
2370
+ let [field, aggFunc] = _ref;
2371
+ return `${field}.${aggFunc}`;
2372
+ }).join(',');
2373
+ searchParams.set('_aggregation', value);
2374
+ }
2375
+ return searchParams;
2376
+ };
2377
+ const getAggregationModel = (search, localStorageAggregation, setLocalStorageAggregation, initialState, isNewVersion) => {
2378
+ var _initialState$aggrega, _initialState$aggrega2;
2379
+ const defaultValue = (_initialState$aggrega = initialState === null || initialState === void 0 ? void 0 : (_initialState$aggrega2 = initialState.aggregation) === null || _initialState$aggrega2 === void 0 ? void 0 : _initialState$aggrega2.model) !== null && _initialState$aggrega !== void 0 ? _initialState$aggrega : {};
2380
+ const persistDefault = () => {
2381
+ const searchFromDefault = getSearchParamsFromAggregation(defaultValue);
2382
+ const searchString = urlSearchParamsToString(searchFromDefault);
2383
+ if (searchString !== localStorageAggregation) {
2384
+ setLocalStorageAggregation(searchString);
2385
+ }
2386
+ };
2387
+ if (isNewVersion) {
2388
+ persistDefault();
2389
+ return defaultValue;
2390
+ }
2391
+ const fromUrl = getAggregationFromString(search);
2392
+ if (fromUrl !== 'invalid') {
2393
+ const searchFromModel = getSearchParamsFromAggregation(fromUrl);
2394
+ const searchString = urlSearchParamsToString(searchFromModel);
2395
+ if (searchString !== localStorageAggregation) {
2396
+ setLocalStorageAggregation(searchString);
2397
+ }
2398
+ return fromUrl;
2399
+ }
2400
+ const fromLocalStorage = getAggregationFromString(localStorageAggregation);
2401
+ if (fromLocalStorage !== 'invalid') {
2402
+ return fromLocalStorage;
2403
+ }
2404
+ persistDefault();
2405
+ return defaultValue;
2406
+ };
2407
+
2408
+ /** PIVOT */
2409
+
2410
+ /** Convert MUI's GridPivotModel → our simplified PivotModel */
2411
+ const fromGridPivotModel = model => ({
2412
+ columns: model.columns.map(c => c.field),
2413
+ rows: model.rows.map(r => r.field),
2414
+ values: model.values.map(_ref2 => {
2415
+ let {
2416
+ field,
2417
+ aggFunc
2418
+ } = _ref2;
2419
+ return {
2420
+ field,
2421
+ aggFunc
2422
+ };
2423
+ })
2424
+ });
2425
+
2426
+ /**
2427
+ * Pivot format: `cols:f1,f2;rows:f3;vals:f4.sum,f5.avg`
2428
+ */
2429
+ const getPivotFromString = searchString => {
2430
+ if (!searchString) return 'invalid';
2431
+ const searchParams = new URLSearchParams(searchString);
2432
+ const value = searchParams.get('_pivot');
2433
+ if (value === '' || value === null) return 'invalid';
2434
+ const model = {
2435
+ columns: [],
2436
+ rows: [],
2437
+ values: []
2438
+ };
2439
+ for (const segment of value.split(';')) {
2440
+ const colonIndex = segment.indexOf(':');
2441
+ if (colonIndex <= 0) return 'invalid';
2442
+ const key = segment.slice(0, colonIndex);
2443
+ const content = segment.slice(colonIndex + 1);
2444
+ if (key === 'cols') {
2445
+ model.columns = content ? content.split(',').filter(Boolean) : [];
2446
+ } else if (key === 'rows') {
2447
+ model.rows = content ? content.split(',').filter(Boolean) : [];
2448
+ } else if (key === 'vals') {
2449
+ if (!content) continue;
2450
+ for (const entry of content.split(',')) {
2451
+ const dotIndex = entry.lastIndexOf('.');
2452
+ if (dotIndex <= 0) return 'invalid';
2453
+ model.values.push({
2454
+ field: entry.slice(0, dotIndex),
2455
+ aggFunc: entry.slice(dotIndex + 1)
2456
+ });
2457
+ }
2458
+ }
2459
+ }
2460
+
2461
+ // At least one section must have content
2462
+ if (model.columns.length === 0 && model.rows.length === 0 && model.values.length === 0) {
2463
+ return 'invalid';
2464
+ }
2465
+ return model;
2466
+ };
2467
+ const getSearchParamsFromPivot = pivot => {
2468
+ const searchParams = new URLSearchParams();
2469
+ const hasContent = pivot.columns.length > 0 || pivot.rows.length > 0 || pivot.values.length > 0;
2470
+ if (hasContent) {
2471
+ const parts = [];
2472
+ parts.push(`cols:${pivot.columns.join(',')}`);
2473
+ parts.push(`rows:${pivot.rows.join(',')}`);
2474
+ if (pivot.values.length > 0) {
2475
+ parts.push(`vals:${pivot.values.map(v => `${v.field}.${v.aggFunc}`).join(',')}`);
2476
+ }
2477
+ searchParams.set('_pivot', parts.join(';'));
2478
+ }
2479
+ return searchParams;
2480
+ };
2481
+ const getPivotModel = (search, localStoragePivot, setLocalStoragePivot, initialState, isNewVersion) => {
2482
+ var _initialState$pivotin;
2483
+ const defaultValue = initialState !== null && initialState !== void 0 && (_initialState$pivotin = initialState.pivoting) !== null && _initialState$pivotin !== void 0 && _initialState$pivotin.model ? fromGridPivotModel(initialState.pivoting.model) : {
2484
+ columns: [],
2485
+ rows: [],
2486
+ values: []
2487
+ };
2488
+ const persistDefault = () => {
2489
+ const searchFromDefault = getSearchParamsFromPivot(defaultValue);
2490
+ const searchString = urlSearchParamsToString(searchFromDefault);
2491
+ if (searchString !== localStoragePivot) {
2492
+ setLocalStoragePivot(searchString);
2493
+ }
2494
+ };
2495
+ if (isNewVersion) {
2496
+ persistDefault();
2497
+ return defaultValue;
2498
+ }
2499
+ const fromUrl = getPivotFromString(search);
2500
+ if (fromUrl !== 'invalid') {
2501
+ const searchFromModel = getSearchParamsFromPivot(fromUrl);
2502
+ const searchString = urlSearchParamsToString(searchFromModel);
2503
+ if (searchString !== localStoragePivot) {
2504
+ setLocalStoragePivot(searchString);
2505
+ }
2506
+ return fromUrl;
2507
+ }
2508
+ const fromLocalStorage = getPivotFromString(localStoragePivot);
2509
+ if (fromLocalStorage !== 'invalid') {
2510
+ return fromLocalStorage;
2511
+ }
2512
+ persistDefault();
2513
+ return defaultValue;
2514
+ };
2515
+
2516
+ /** PIVOT ACTIVE */
2517
+
2518
+ const getPivotActiveFromString = searchString => {
2519
+ if (!searchString) return 'invalid';
2520
+ const searchParams = new URLSearchParams(searchString);
2521
+ const value = searchParams.get('_pivotActive');
2522
+ if (value === 'true') return true;
2523
+ if (value === 'false') return false;
2524
+ return 'invalid';
2525
+ };
2526
+ const getSearchParamsFromPivotActive = active => {
2527
+ const searchParams = new URLSearchParams();
2528
+ searchParams.set('_pivotActive', String(active));
2529
+ return searchParams;
2530
+ };
2531
+
2532
+ /**
2533
+ * Builds the `v=<version>` search param the grid uses to detect stale URLs.
2534
+ *
2535
+ * Use this when constructing a deep link or share URL outside the grid (e.g. via
2536
+ * `getSearchParamsFromFilterModel` + `buildQueryParamsString`). The grid itself
2537
+ * always includes `v` in URLs it writes via `getFinalSearch`, but external helpers
2538
+ * do not — include this when you want a stale `localStorageVersion` bump to invalidate
2539
+ * the link. Omitting `v` is also safe: a missing `v` is treated as "current version"
2540
+ * and the URL state is applied as-is.
2541
+ *
2542
+ * @example
2543
+ * const params = new URLSearchParams([
2544
+ * ...getSearchParamsFromFilterModel(filterModel),
2545
+ * ...getSearchParamsFromVersion(1),
2546
+ * ]);
2547
+ * const url = `/my-grid${buildQueryParamsString(urlSearchParamsToString(params))}`;
2548
+ */
2549
+ const getSearchParamsFromVersion = version => {
2550
+ const searchParams = new URLSearchParams();
2551
+ searchParams.set('v', String(version));
2552
+ return searchParams;
2553
+ };
2554
+ const getPivotActive = (search, localStoragePivotActive, setLocalStoragePivotActive, initialState, isNewVersion) => {
2555
+ var _initialState$pivotin2, _initialState$pivotin3;
2556
+ const defaultValue = (_initialState$pivotin2 = initialState === null || initialState === void 0 ? void 0 : (_initialState$pivotin3 = initialState.pivoting) === null || _initialState$pivotin3 === void 0 ? void 0 : _initialState$pivotin3.enabled) !== null && _initialState$pivotin2 !== void 0 ? _initialState$pivotin2 : false;
2557
+ const persistDefault = () => {
2558
+ const searchFromDefault = getSearchParamsFromPivotActive(defaultValue);
2559
+ const searchString = urlSearchParamsToString(searchFromDefault);
2560
+ if (searchString !== localStoragePivotActive) {
2561
+ setLocalStoragePivotActive(searchString);
2562
+ }
2563
+ };
2564
+ if (isNewVersion) {
2565
+ persistDefault();
2566
+ return defaultValue;
2567
+ }
2568
+ const fromUrl = getPivotActiveFromString(search);
2569
+ if (fromUrl !== 'invalid') {
2570
+ const searchFromModel = getSearchParamsFromPivotActive(fromUrl);
2571
+ const searchString = urlSearchParamsToString(searchFromModel);
2572
+ if (searchString !== localStoragePivotActive) {
2573
+ setLocalStoragePivotActive(searchString);
2574
+ }
2575
+ return fromUrl;
2576
+ }
2577
+ const fromLocalStorage = getPivotActiveFromString(localStoragePivotActive);
2578
+ if (fromLocalStorage !== 'invalid') {
2579
+ return fromLocalStorage;
2580
+ }
2581
+ persistDefault();
2582
+ return defaultValue;
2583
+ };
2584
+ const getFinalSearch = _ref3 => {
3727
2585
  let {
3728
2586
  search,
3729
2587
  localStorageVersion,
@@ -3735,8 +2593,12 @@ const getFinalSearch = _ref => {
3735
2593
  density,
3736
2594
  columnOrderModel,
3737
2595
  defaultColumnOrder,
2596
+ rowGroupingModel,
2597
+ aggregationModel,
2598
+ pivotModel,
2599
+ pivotActive,
3738
2600
  columns
3739
- } = _ref;
2601
+ } = _ref3;
3740
2602
  const filterModelSearch = getSearchParamsFromFilterModel(filterModel);
3741
2603
  const sortModelSearch = getSearchParamsFromSorting(sortModel);
3742
2604
  const paginationModelSearch = getSearchParamsFromPagination(paginationModel);
@@ -3745,6 +2607,10 @@ const getFinalSearch = _ref => {
3745
2607
  const densitySearch = getSearchParamsFromDensity(density);
3746
2608
  // Only include _columnOrder in URL when it differs from the default
3747
2609
  const columnOrderSearch = columnOrderModel.length !== defaultColumnOrder.length || columnOrderModel.some((field, i) => field !== defaultColumnOrder[i]) ? getSearchParamsFromColumnOrder(columnOrderModel) : new URLSearchParams();
2610
+ const rowGroupingSearch = getSearchParamsFromRowGrouping(rowGroupingModel);
2611
+ const aggregationSearch = getSearchParamsFromAggregation(aggregationModel);
2612
+ const pivotSearch = getSearchParamsFromPivot(pivotModel);
2613
+ const pivotActiveSearch = getSearchParamsFromPivotActive(pivotActive);
3748
2614
  const tabSearch = getSearchParamsFromTab(search);
3749
2615
  const searchParams = new URLSearchParams();
3750
2616
  for (const [key, value] of new URLSearchParams(search)) {
@@ -3759,10 +2625,10 @@ const getFinalSearch = _ref => {
3759
2625
  // Encode array as JSON string to preserve all values in one param
3760
2626
  searchParams.set('_quickFilterValues', encodeURIComponent(JSON.stringify(filterModel.quickFilterValues)));
3761
2627
  }
3762
- return new URLSearchParams([...searchParams, ...filterModelSearch, ...sortModelSearch, ...paginationModelSearch, ...tabSearch, ...pinnedColumnsModelSearch, ...columnVisibilityModelSearch, ...densitySearch, ...columnOrderSearch]);
2628
+ return new URLSearchParams([...searchParams, ...filterModelSearch, ...sortModelSearch, ...paginationModelSearch, ...tabSearch, ...pinnedColumnsModelSearch, ...columnVisibilityModelSearch, ...densitySearch, ...columnOrderSearch, ...rowGroupingSearch, ...aggregationSearch, ...pivotSearch, ...pivotActiveSearch]);
3763
2629
  };
3764
2630
  /** Return the state of the table given the URL and the local storage state */
3765
- const getModelsParsedOrUpdateLocalStorage = (search, localStorageVersion, columns, initialState, localStorage) => {
2631
+ const getModelsParsedOrUpdateLocalStorage = (search, localStorageVersion, columns, initialState, localStorage, previousFilterModel) => {
3766
2632
  var _initialState$columns6, _initialState$columns7;
3767
2633
  // Decompress any compressed params in the search string before processing
3768
2634
  const decompressedSearch = decompressSearchParams(search);
@@ -3799,15 +2665,27 @@ const getModelsParsedOrUpdateLocalStorage = (search, localStorageVersion, column
3799
2665
  localStorageDensity,
3800
2666
  setLocalStorageDensity,
3801
2667
  localStorageColumnOrder,
3802
- setLocalStorageColumnOrder
2668
+ setLocalStorageColumnOrder,
2669
+ localStorageRowGrouping,
2670
+ setLocalStorageRowGrouping,
2671
+ localStorageAggregation,
2672
+ setLocalStorageAggregation,
2673
+ localStoragePivot,
2674
+ setLocalStoragePivot,
2675
+ localStoragePivotActive,
2676
+ setLocalStoragePivotActive
3803
2677
  } = localStorage;
3804
- const filterModel = getFilterModel(decodedSearch, columns, localStorageFilters, setLocalStorageFilters, initialState, isNewVersion);
2678
+ const filterModel = getFilterModel(decodedSearch, columns, localStorageFilters, setLocalStorageFilters, initialState, isNewVersion, previousFilterModel);
3805
2679
  const sortModel = getSortModel(decodedSearch, columns, localStorageSorting, setLocalStorageSorting, initialState, isNewVersion);
3806
2680
  const paginationModel = getPaginationModel(decodedSearch, localStoragePagination, setLocalStoragePagination, initialState, isNewVersion);
3807
2681
  const columnVisibilityModel = getColumnsVisibility(decodedSearch, columns, localStorageColumnsVisibility, setLocalStorageColumnsVisibility, initialState, isNewVersion);
3808
2682
  const pinnedColumnsModel = getPinnedColumns(decodedSearch, columns, localStoragePinnedColumns, setLocalStoragePinnedColumns, initialState, isNewVersion);
3809
2683
  const density = getDensityModel(decodedSearch, localStorageDensity, setLocalStorageDensity, initialState, isNewVersion);
3810
2684
  const columnOrderModel = getColumnOrder(decodedSearch, columns, localStorageColumnOrder, setLocalStorageColumnOrder, initialState, isNewVersion);
2685
+ const rowGroupingModel = getRowGroupingModel(decodedSearch, localStorageRowGrouping, setLocalStorageRowGrouping, initialState, isNewVersion);
2686
+ const aggregationModel = getAggregationModel(decodedSearch, localStorageAggregation, setLocalStorageAggregation, initialState, isNewVersion);
2687
+ const pivotModel = getPivotModel(decodedSearch, localStoragePivot, setLocalStoragePivot, initialState, isNewVersion);
2688
+ const pivotActive = getPivotActive(decodedSearch, localStoragePivotActive, setLocalStoragePivotActive, initialState, isNewVersion);
3811
2689
  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);
3812
2690
  const finalSearch = getFinalSearch({
3813
2691
  localStorageVersion,
@@ -3820,6 +2698,10 @@ const getModelsParsedOrUpdateLocalStorage = (search, localStorageVersion, column
3820
2698
  density,
3821
2699
  columnOrderModel,
3822
2700
  defaultColumnOrder,
2701
+ rowGroupingModel,
2702
+ aggregationModel,
2703
+ pivotModel,
2704
+ pivotActive,
3823
2705
  columns
3824
2706
  });
3825
2707
  const internalSearchString = urlSearchParamsToString(finalSearch);
@@ -3840,10 +2722,14 @@ const getModelsParsedOrUpdateLocalStorage = (search, localStorageVersion, column
3840
2722
  pinnedColumnsModel,
3841
2723
  density,
3842
2724
  columnOrderModel,
2725
+ rowGroupingModel,
2726
+ aggregationModel,
2727
+ pivotModel,
2728
+ pivotActive,
3843
2729
  pendingSearch
3844
2730
  };
3845
2731
  };
3846
- const updateUrl = (_ref2, search, localStorageVersion, historyReplace, columns) => {
2732
+ const updateUrl = (_ref4, search, localStorageVersion, historyReplace, columns) => {
3847
2733
  let {
3848
2734
  filterModel,
3849
2735
  sortModel,
@@ -3852,8 +2738,12 @@ const updateUrl = (_ref2, search, localStorageVersion, historyReplace, columns)
3852
2738
  pinnedColumnsModel,
3853
2739
  density,
3854
2740
  columnOrderModel,
3855
- defaultColumnOrder
3856
- } = _ref2;
2741
+ defaultColumnOrder,
2742
+ rowGroupingModel,
2743
+ aggregationModel,
2744
+ pivotModel,
2745
+ pivotActive
2746
+ } = _ref4;
3857
2747
  // Convert from display format to internal format if needed
3858
2748
  const decodedSearch = getDecodedSearchFromUrl(search, columns);
3859
2749
  const newSearch = getFinalSearch({
@@ -3867,6 +2757,10 @@ const updateUrl = (_ref2, search, localStorageVersion, historyReplace, columns)
3867
2757
  density,
3868
2758
  columnOrderModel,
3869
2759
  defaultColumnOrder,
2760
+ rowGroupingModel,
2761
+ aggregationModel,
2762
+ pivotModel,
2763
+ pivotActive,
3870
2764
  columns
3871
2765
  });
3872
2766
  const internalSearchString = urlSearchParamsToString(newSearch);
@@ -4059,6 +2953,26 @@ const useTableStates = (id, version) => {
4059
2953
  version,
4060
2954
  category: COLUMN_ORDER_MODEL_KEY
4061
2955
  }));
2956
+ const [rowGroupingModel, setRowGroupingModel] = useFetchState('', buildStorageKey({
2957
+ id,
2958
+ version,
2959
+ category: ROW_GROUPING_MODEL_KEY
2960
+ }));
2961
+ const [aggregationModel, setAggregationModel] = useFetchState('', buildStorageKey({
2962
+ id,
2963
+ version,
2964
+ category: AGGREGATION_MODEL_KEY
2965
+ }));
2966
+ const [pivotModel, setPivotModel] = useFetchState('', buildStorageKey({
2967
+ id,
2968
+ version,
2969
+ category: PIVOT_MODEL_KEY
2970
+ }));
2971
+ const [pivotActive, setPivotActive] = useFetchState('', buildStorageKey({
2972
+ id,
2973
+ version,
2974
+ category: PIVOT_ACTIVE_KEY
2975
+ }));
4062
2976
  return {
4063
2977
  paginationModel,
4064
2978
  setPaginationModel,
@@ -4075,13 +2989,41 @@ const useTableStates = (id, version) => {
4075
2989
  densityModel,
4076
2990
  setDensityModel,
4077
2991
  columnOrderModel,
4078
- setColumnOrderModel
2992
+ setColumnOrderModel,
2993
+ rowGroupingModel,
2994
+ setRowGroupingModel,
2995
+ aggregationModel,
2996
+ setAggregationModel,
2997
+ pivotModel,
2998
+ setPivotModel,
2999
+ pivotActive,
3000
+ setPivotActive
4079
3001
  };
4080
3002
  };
4081
3003
 
3004
+ /** Convert our simplified PivotModel → MUI's GridPivotModel */
3005
+ const toGridPivotModel = model => ({
3006
+ columns: model.columns.map(field => ({
3007
+ field
3008
+ })),
3009
+ rows: model.rows.map(field => ({
3010
+ field
3011
+ })),
3012
+ values: model.values.map(_ref => {
3013
+ let {
3014
+ field,
3015
+ aggFunc
3016
+ } = _ref;
3017
+ return {
3018
+ field,
3019
+ aggFunc
3020
+ };
3021
+ })
3022
+ });
3023
+
4082
3024
  /**
4083
3025
  * Deep-equal comparison for plain objects / arrays.
4084
- * Used to stabilise parsed model references so that MUI does not
3026
+ * Used to stabilise parsed model references so that MUI v8 does not
4085
3027
  * reset pagination on every render.
4086
3028
  */
4087
3029
  function isDeepEqual(a, b) {
@@ -4108,6 +3050,9 @@ const useStatefulTable = props => {
4108
3050
  onPaginationModelChange: propsOnPaginationModelChange,
4109
3051
  onPinnedColumnsChange: propsOnPinnedColumnsChange,
4110
3052
  onSortModelChange: propsOnSortModelChange,
3053
+ onRowGroupingModelChange: propsOnRowGroupingModelChange,
3054
+ onAggregationModelChange: propsOnAggregationModelChange,
3055
+ onPivotModelChange: propsOnPivotModelChange,
4111
3056
  useRouter,
4112
3057
  localStorageVersion = 1,
4113
3058
  previousLocalStorageVersions = []
@@ -4136,20 +3081,33 @@ const useStatefulTable = props => {
4136
3081
  densityModel,
4137
3082
  setDensityModel,
4138
3083
  columnOrderModel: localStorageColumnOrder,
4139
- setColumnOrderModel: setLocalStorageColumnOrder
3084
+ setColumnOrderModel: setLocalStorageColumnOrder,
3085
+ rowGroupingModel: localStorageRowGrouping,
3086
+ setRowGroupingModel: setLocalStorageRowGrouping,
3087
+ aggregationModel: localStorageAggregation,
3088
+ setAggregationModel: setLocalStorageAggregation,
3089
+ pivotModel: localStoragePivot,
3090
+ setPivotModel: setLocalStoragePivot,
3091
+ pivotActive: localStoragePivotActive,
3092
+ setPivotActive: setLocalStoragePivotActive
4140
3093
  } = useTableStates(id, localStorageVersion);
4141
3094
 
4142
3095
  // clearing up old version keys, triggering only on first render
4143
3096
  useEffect(() => clearPreviousVersionStorage(id, previousLocalStorageVersions), [id, previousLocalStorageVersions]);
4144
- const onColumnDimensionChange = useCallback(_ref => {
3097
+ const onColumnDimensionChange = useCallback(_ref2 => {
4145
3098
  let {
4146
3099
  newWidth,
4147
3100
  field
4148
- } = _ref;
3101
+ } = _ref2;
4149
3102
  setDimensionModel(_objectSpread2(_objectSpread2({}, dimensionModel), {}, {
4150
3103
  [field]: newWidth
4151
3104
  }));
4152
3105
  }, [dimensionModel, setDimensionModel]);
3106
+
3107
+ // Source of ids for content-matching in the parser, so item.id is stable across the
3108
+ // URL write→parse roundtrip (DS-81). Holds MUI's just-emitted model (random placeholder
3109
+ // id) after onFilterModelChange, then the stabilised parsed model each render.
3110
+ const filterIdSourceRef = useRef(undefined);
4153
3111
  const {
4154
3112
  filterModel: filterParsed,
4155
3113
  sortModel: sortModelParsed,
@@ -4158,6 +3116,10 @@ const useStatefulTable = props => {
4158
3116
  pinnedColumnsModel,
4159
3117
  density: densityParsed,
4160
3118
  columnOrderModel: columnOrderParsed,
3119
+ rowGroupingModel: rowGroupingParsed,
3120
+ aggregationModel: aggregationParsed,
3121
+ pivotModel: pivotParsed,
3122
+ pivotActive: pivotActiveParsed,
4161
3123
  pendingSearch
4162
3124
  } = getModelsParsedOrUpdateLocalStorage(search || '', localStorageVersion, propsColumns, initialState, {
4163
3125
  localStorageFilters,
@@ -4173,8 +3135,16 @@ const useStatefulTable = props => {
4173
3135
  localStorageDensity: densityModel,
4174
3136
  setLocalStorageDensity: setDensityModel,
4175
3137
  localStorageColumnOrder,
4176
- setLocalStorageColumnOrder
4177
- });
3138
+ setLocalStorageColumnOrder,
3139
+ localStorageRowGrouping,
3140
+ setLocalStorageRowGrouping,
3141
+ localStorageAggregation,
3142
+ setLocalStorageAggregation,
3143
+ localStoragePivot,
3144
+ setLocalStoragePivot,
3145
+ localStoragePivotActive: localStoragePivotActive,
3146
+ setLocalStoragePivotActive: setLocalStoragePivotActive
3147
+ }, filterIdSourceRef.current);
4178
3148
 
4179
3149
  // Sync URL in an effect rather than during render to comply with React rules
4180
3150
  useEffect(() => {
@@ -4183,12 +3153,16 @@ const useStatefulTable = props => {
4183
3153
  }
4184
3154
  }, [pendingSearch, historyReplace]);
4185
3155
 
4186
- // Stabilise parsed model references to prevent MUI from resetting
3156
+ // Stabilise parsed model references to prevent MUI v8 from resetting
4187
3157
  // pagination on every render due to new object identity.
4188
3158
  const filterParsedRef = useRef(filterParsed);
4189
3159
  if (!isDeepEqual(filterParsedRef.current, filterParsed)) {
4190
3160
  filterParsedRef.current = filterParsed;
4191
3161
  }
3162
+ // Keep the id source in sync with the stabilised parsed model so re-parses triggered
3163
+ // by unrelated state (pagination/sort/visibility) reuse the same ids (DS-81). The parse
3164
+ // call above reads the ref before this write (read-before-write within the render).
3165
+ filterIdSourceRef.current = filterParsed;
4192
3166
  const sortModelParsedRef = useRef(sortModelParsed);
4193
3167
  if (!isDeepEqual(sortModelParsedRef.current, sortModelParsed)) {
4194
3168
  sortModelParsedRef.current = sortModelParsed;
@@ -4209,6 +3183,18 @@ const useStatefulTable = props => {
4209
3183
  if (!isDeepEqual(columnOrderParsedRef.current, columnOrderParsed)) {
4210
3184
  columnOrderParsedRef.current = columnOrderParsed;
4211
3185
  }
3186
+ const rowGroupingParsedRef = useRef(rowGroupingParsed);
3187
+ if (!isDeepEqual(rowGroupingParsedRef.current, rowGroupingParsed)) {
3188
+ rowGroupingParsedRef.current = rowGroupingParsed;
3189
+ }
3190
+ const aggregationParsedRef = useRef(aggregationParsed);
3191
+ if (!isDeepEqual(aggregationParsedRef.current, aggregationParsed)) {
3192
+ aggregationParsedRef.current = aggregationParsed;
3193
+ }
3194
+ const pivotParsedRef = useRef(pivotParsed);
3195
+ if (!isDeepEqual(pivotParsedRef.current, pivotParsed)) {
3196
+ pivotParsedRef.current = pivotParsed;
3197
+ }
4212
3198
  const columns = useMemo(() => propsColumns.map(column => {
4213
3199
  return _objectSpread2(_objectSpread2({}, column), {}, {
4214
3200
  width: dimensionModel[column.field] || column.width || 100
@@ -4217,29 +3203,13 @@ const useStatefulTable = props => {
4217
3203
  if (apiRef.current) {
4218
3204
  /** Add resetPage method to apiRef. */
4219
3205
  apiRef.current.resetPage = () => {
4220
- apiRef.current.setPage(0);
3206
+ var _apiRef$current;
3207
+ (_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 ? void 0 : _apiRef$current.setPage(0);
4221
3208
  };
4222
3209
  }
4223
3210
  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);
4224
3211
 
4225
- // Helper to build the current DataGridModel for updateUrl calls
4226
- const buildModel = function () {
4227
- var _apiRef$current$state, _apiRef$current;
4228
- let overrides = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
4229
- return _objectSpread2({
4230
- filterModel: filterParsed,
4231
- sortModel: sortModelParsed,
4232
- paginationModel: paginationModelParsed,
4233
- columnsModel: (_apiRef$current$state = (_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 ? void 0 : _apiRef$current.state.columns.columnVisibilityModel) !== null && _apiRef$current$state !== void 0 ? _apiRef$current$state : {},
4234
- pinnedColumnsModel: pinnedColumnsModel,
4235
- density: densityParsed,
4236
- columnOrderModel: columnOrderParsed,
4237
- defaultColumnOrder
4238
- }, overrides);
4239
- };
4240
-
4241
- // Subscribe to density changes via stateChange event
4242
- // (MUI v7 supports onDensityChange, but stateChange works and avoids a larger refactor)
3212
+ // Subscribe to density changes via stateChange event (MUI v6 has no densityChange event)
4243
3213
  useEffect(() => {
4244
3214
  const api = apiRef.current;
4245
3215
  if (!(api !== null && api !== void 0 && api.subscribeEvent)) return;
@@ -4248,14 +3218,24 @@ const useStatefulTable = props => {
4248
3218
  const currentDensity = api.state.density;
4249
3219
  if (currentDensity !== prevDensity) {
4250
3220
  prevDensity = currentDensity;
4251
- updateUrl(buildModel({
3221
+ updateUrl({
3222
+ filterModel: filterParsed,
3223
+ sortModel: sortModelParsed,
3224
+ paginationModel: paginationModelParsed,
4252
3225
  columnsModel: api.state.columns.columnVisibilityModel,
4253
- density: currentDensity
4254
- }), search, localStorageVersion, historyReplace, columns);
3226
+ pinnedColumnsModel: pinnedColumnsModel,
3227
+ density: currentDensity,
3228
+ columnOrderModel: columnOrderParsed,
3229
+ defaultColumnOrder,
3230
+ rowGroupingModel: rowGroupingParsed,
3231
+ aggregationModel: aggregationParsed,
3232
+ pivotModel: pivotParsed,
3233
+ pivotActive: pivotActiveParsed
3234
+ }, search, localStorageVersion, historyReplace, columns);
4255
3235
  }
4256
3236
  });
4257
3237
  return unsub;
4258
- }, [apiRef, densityParsed, filterParsed, sortModelParsed, paginationModelParsed, pinnedColumnsModel, columnOrderParsed, defaultColumnOrder, search, localStorageVersion, historyReplace, columns]);
3238
+ }, [apiRef, densityParsed, filterParsed, sortModelParsed, paginationModelParsed, pinnedColumnsModel, columnOrderParsed, defaultColumnOrder, rowGroupingParsed, aggregationParsed, pivotParsed, pivotActiveParsed, search, localStorageVersion, historyReplace, columns]);
4259
3239
 
4260
3240
  // Subscribe to column order changes via columnOrderChange (drag-drop) and columnIndexChange (programmatic setColumnIndex)
4261
3241
  useEffect(() => {
@@ -4264,9 +3244,20 @@ const useStatefulTable = props => {
4264
3244
  const handleColumnOrderChange = () => {
4265
3245
  const orderedFields = api.state.columns.orderedFields;
4266
3246
  if (orderedFields && !isDeepEqual(orderedFields, columnOrderParsed)) {
4267
- updateUrl(buildModel({
4268
- columnOrderModel: orderedFields
4269
- }), search, localStorageVersion, historyReplace, columns);
3247
+ updateUrl({
3248
+ filterModel: filterParsed,
3249
+ sortModel: sortModelParsed,
3250
+ paginationModel: paginationModelParsed,
3251
+ columnsModel: api.state.columns.columnVisibilityModel,
3252
+ pinnedColumnsModel,
3253
+ density: densityParsed,
3254
+ columnOrderModel: orderedFields,
3255
+ defaultColumnOrder,
3256
+ rowGroupingModel: rowGroupingParsed,
3257
+ aggregationModel: aggregationParsed,
3258
+ pivotModel: pivotParsed,
3259
+ pivotActive: pivotActiveParsed
3260
+ }, search, localStorageVersion, historyReplace, columns);
4270
3261
  }
4271
3262
  };
4272
3263
  const unsub1 = api.subscribeEvent('columnOrderChange', handleColumnOrderChange);
@@ -4275,34 +3266,63 @@ const useStatefulTable = props => {
4275
3266
  unsub1();
4276
3267
  unsub2();
4277
3268
  };
4278
- }, [apiRef, columnOrderParsed, defaultColumnOrder, filterParsed, sortModelParsed, paginationModelParsed, pinnedColumnsModel, densityParsed, search, localStorageVersion, historyReplace, columns]);
3269
+ }, [apiRef, columnOrderParsed, defaultColumnOrder, filterParsed, sortModelParsed, paginationModelParsed, pinnedColumnsModel, densityParsed, rowGroupingParsed, aggregationParsed, pivotParsed, pivotActiveParsed, search, localStorageVersion, historyReplace, columns]);
3270
+
3271
+ // Helper to build the current DataGridModel for updateUrl calls
3272
+ const buildModel = function () {
3273
+ var _apiRef$current$state, _apiRef$current2;
3274
+ let overrides = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
3275
+ return _objectSpread2({
3276
+ filterModel: filterParsed,
3277
+ sortModel: sortModelParsed,
3278
+ paginationModel: paginationModelParsed,
3279
+ 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 : {},
3280
+ pinnedColumnsModel: pinnedColumnsModel,
3281
+ density: densityParsed,
3282
+ columnOrderModel: columnOrderParsed,
3283
+ defaultColumnOrder,
3284
+ rowGroupingModel: rowGroupingParsed,
3285
+ aggregationModel: aggregationParsed,
3286
+ pivotModel: pivotParsed,
3287
+ pivotActive: pivotActiveParsed
3288
+ }, overrides);
3289
+ };
3290
+
3291
+ // Stable GridPivotModel identity — only recompute when the simplified value changes.
3292
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3293
+ const pivotModelMui = useMemo(() => toGridPivotModel(pivotParsed), [JSON.stringify(pivotParsed)]);
4279
3294
 
4280
3295
  // Track last emitted values for deep-equal guards to avoid feedback loops.
4281
3296
  // Initialised from the current parsed values; updated only when we actually fire.
4282
3297
  const lastEmittedFilterRef = useRef(filterParsed);
4283
3298
  const lastEmittedSortRef = useRef(sortModelParsed);
4284
3299
  const lastEmittedPaginationRef = useRef(paginationModelParsed);
3300
+ const lastEmittedPivotRef = useRef(pivotParsed);
4285
3301
  return {
4286
3302
  apiRef,
4287
3303
  columns,
4288
3304
  density: densityParsed,
4289
- onDensityChange: newDensity => {
4290
- updateUrl(buildModel({
4291
- density: newDensity
4292
- }), search, localStorageVersion, historyReplace, columns);
4293
- },
4294
3305
  columnOrderModel: columnOrderParsedRef.current,
3306
+ rowGroupingModel: rowGroupingParsedRef.current,
3307
+ aggregationModel: aggregationParsedRef.current,
3308
+ pivotModel: pivotModelMui,
3309
+ pivotActive: pivotActiveParsed,
4295
3310
  onFilterModelChange: (model, details) => {
4296
3311
  const filterModel = _objectSpread2(_objectSpread2({}, model), {}, {
4297
3312
  items: model.items.map(item => {
4298
- const column = apiRef.current.getColumn(item.field);
4299
- item.type = column.type || 'string';
3313
+ var _apiRef$current3;
3314
+ const column = (_apiRef$current3 = apiRef.current) === null || _apiRef$current3 === void 0 ? void 0 : _apiRef$current3.getColumn(item.field);
3315
+ item.type = (column === null || column === void 0 ? void 0 : column.type) || 'string';
4300
3316
  return item;
4301
3317
  }),
4302
3318
  quickFilterValues: model.quickFilterValues || []
4303
3319
  });
4304
3320
  if (isDeepEqual(filterModel, lastEmittedFilterRef.current)) return;
4305
3321
  lastEmittedFilterRef.current = filterModel;
3322
+ // Capture MUI's just-emitted model (carrying the placeholder's random id) so the
3323
+ // next render's URL re-parse reuses that id and the GridFilterForm key — and thus
3324
+ // the value input's focus — survives the write→parse roundtrip (DS-81).
3325
+ filterIdSourceRef.current = filterModel;
4306
3326
  updateUrl(buildModel({
4307
3327
  filterModel
4308
3328
  }), search, localStorageVersion, historyReplace, columns);
@@ -4319,10 +3339,20 @@ const useStatefulTable = props => {
4319
3339
  },
4320
3340
  sortModel: sortModelParsedRef.current,
4321
3341
  onPinnedColumnsChange: (pinnedColumns, details) => {
3342
+ var _apiRef$current$state2, _apiRef$current4, _apiRef$current4$stat, _apiRef$current4$stat2;
3343
+ // While pivot mode is active, MUI Premium emits synthetic pinned-column
3344
+ // models (e.g. `__row_group_by_columns_group__` for the grouping column)
3345
+ // that must not be persisted to the URL / localStorage. Read the live
3346
+ // grid state from apiRef rather than the parsed URL value because the
3347
+ // URL lags by a tick during pivot enable/disable transitions. Consumer
3348
+ // callbacks are always forwarded so observers can still react.
3349
+ const pivotActiveLive = (_apiRef$current$state2 = (_apiRef$current4 = apiRef.current) === null || _apiRef$current4 === void 0 ? void 0 : (_apiRef$current4$stat = _apiRef$current4.state) === null || _apiRef$current4$stat === void 0 ? void 0 : (_apiRef$current4$stat2 = _apiRef$current4$stat.pivoting) === null || _apiRef$current4$stat2 === void 0 ? void 0 : _apiRef$current4$stat2.active) !== null && _apiRef$current$state2 !== void 0 ? _apiRef$current$state2 : pivotActiveParsed;
3350
+ if (!pivotActiveLive) {
3351
+ updateUrl(buildModel({
3352
+ pinnedColumnsModel: pinnedColumns
3353
+ }), search, localStorageVersion, historyReplace, columns);
3354
+ }
4322
3355
  propsOnPinnedColumnsChange === null || propsOnPinnedColumnsChange === void 0 ? void 0 : propsOnPinnedColumnsChange(pinnedColumns, details);
4323
- updateUrl(buildModel({
4324
- pinnedColumnsModel: pinnedColumns
4325
- }), search, localStorageVersion, historyReplace, columns);
4326
3356
  },
4327
3357
  pinnedColumns: pinnedColumnsModelRef.current,
4328
3358
  paginationModel: paginationModelParsedRef.current,
@@ -4339,10 +3369,21 @@ const useStatefulTable = props => {
4339
3369
  },
4340
3370
  columnVisibilityModel: visibilityModelRef.current,
4341
3371
  onColumnVisibilityModelChange: (columnsVisibilityModel, details) => {
3372
+ var _apiRef$current$state3, _apiRef$current5, _apiRef$current5$stat, _apiRef$current5$stat2;
3373
+ // While pivot mode is active, MUI Premium emits synthetic visibility
3374
+ // models that whitelist only the pivot value fields (hiding every base
3375
+ // column). Persisting that to the URL would re-hide all base columns
3376
+ // on the next load (see getColumnVisibilityFromString whitelist logic).
3377
+ // Read the live grid state rather than the parsed URL value because the
3378
+ // URL lags by a tick during pivot enable/disable transitions. Consumer
3379
+ // callbacks are always forwarded.
3380
+ const pivotActiveLive = (_apiRef$current$state3 = (_apiRef$current5 = apiRef.current) === null || _apiRef$current5 === void 0 ? void 0 : (_apiRef$current5$stat = _apiRef$current5.state) === null || _apiRef$current5$stat === void 0 ? void 0 : (_apiRef$current5$stat2 = _apiRef$current5$stat.pivoting) === null || _apiRef$current5$stat2 === void 0 ? void 0 : _apiRef$current5$stat2.active) !== null && _apiRef$current$state3 !== void 0 ? _apiRef$current$state3 : pivotActiveParsed;
3381
+ if (!pivotActiveLive) {
3382
+ updateUrl(buildModel({
3383
+ columnsModel: columnsVisibilityModel
3384
+ }), search, localStorageVersion, historyReplace, columns);
3385
+ }
4342
3386
  propsOnColumnVisibilityModelChange === null || propsOnColumnVisibilityModelChange === void 0 ? void 0 : propsOnColumnVisibilityModelChange(columnsVisibilityModel, details);
4343
- updateUrl(buildModel({
4344
- columnsModel: columnsVisibilityModel
4345
- }), search, localStorageVersion, historyReplace, columns);
4346
3387
  },
4347
3388
  onColumnWidthChange: (params, event, details) => {
4348
3389
  propsOnColumnWidthChange === null || propsOnColumnWidthChange === void 0 ? void 0 : propsOnColumnWidthChange(params, event, details);
@@ -4350,11 +3391,38 @@ const useStatefulTable = props => {
4350
3391
  newWidth: params.width,
4351
3392
  field: params.colDef.field
4352
3393
  });
3394
+ },
3395
+ onRowGroupingModelChange: (model, details) => {
3396
+ updateUrl(buildModel({
3397
+ rowGroupingModel: model
3398
+ }), search, localStorageVersion, historyReplace, columns);
3399
+ propsOnRowGroupingModelChange === null || propsOnRowGroupingModelChange === void 0 ? void 0 : propsOnRowGroupingModelChange(model, details);
3400
+ },
3401
+ onAggregationModelChange: (model, details) => {
3402
+ updateUrl(buildModel({
3403
+ aggregationModel: model
3404
+ }), search, localStorageVersion, historyReplace, columns);
3405
+ propsOnAggregationModelChange === null || propsOnAggregationModelChange === void 0 ? void 0 : propsOnAggregationModelChange(model, details);
3406
+ },
3407
+ onPivotModelChange: model => {
3408
+ const simplified = fromGridPivotModel(model);
3409
+ if (isDeepEqual(simplified, lastEmittedPivotRef.current)) return;
3410
+ lastEmittedPivotRef.current = simplified;
3411
+ updateUrl(buildModel({
3412
+ pivotModel: simplified
3413
+ }), search, localStorageVersion, historyReplace, columns);
3414
+ propsOnPivotModelChange === null || propsOnPivotModelChange === void 0 ? void 0 : propsOnPivotModelChange(model);
3415
+ },
3416
+ onPivotActiveChange: active => {
3417
+ if (active === pivotActiveParsed) return;
3418
+ updateUrl(buildModel({
3419
+ pivotActive: active
3420
+ }), search, localStorageVersion, historyReplace, columns);
4353
3421
  }
4354
3422
  };
4355
3423
  };
4356
3424
 
4357
- 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", "selectionBannerPlacement", "paginationProps", "rows", "pageSizeOptions", "sx", "theme", "useRouter", "paginationMode", "rowCount"];
3425
+ 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", "onRowGroupingModelChange", "onAggregationModelChange", "onPivotModelChange", "pagination", "paginationPlacement", "selectionBannerPlacement", "paginationProps", "rows", "pageSizeOptions", "sx", "theme", "useRouter", "paginationMode", "rowCount", "density", "dataSource", "filterMode", "sortingMode"];
4358
3426
  const COMPONENT_NAME = 'DataGrid';
4359
3427
  const CLASSNAME = 'redsift-datagrid';
4360
3428
 
@@ -4410,6 +3478,7 @@ const CLASSNAME = 'redsift-datagrid';
4410
3478
  */
4411
3479
 
4412
3480
  const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
3481
+ var _ref7;
4413
3482
  const datagridRef = ref || useRef();
4414
3483
  const {
4415
3484
  apiRef: propsApiRef,
@@ -4438,6 +3507,9 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4438
3507
  onColumnVisibilityModelChange: propsOnColumnVisibilityModelChange,
4439
3508
  onPinnedColumnsChange: propsOnPinnedColumnsChange,
4440
3509
  onSortModelChange: propsOnSortModelChange,
3510
+ onRowGroupingModelChange: propsOnRowGroupingModelChange,
3511
+ onAggregationModelChange: propsOnAggregationModelChange,
3512
+ onPivotModelChange: propsOnPivotModelChange,
4441
3513
  pagination,
4442
3514
  paginationPlacement = 'both',
4443
3515
  selectionBannerPlacement = 'top',
@@ -4448,15 +3520,26 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4448
3520
  theme: propsTheme,
4449
3521
  useRouter,
4450
3522
  paginationMode = 'client',
4451
- rowCount
3523
+ rowCount,
3524
+ density: _density,
3525
+ dataSource,
3526
+ filterMode: propsFilterMode,
3527
+ sortingMode: propsSortingMode
4452
3528
  } = props,
4453
3529
  forwardedProps = _objectWithoutProperties(props, _excluded);
4454
- const theme = useTheme$1(propsTheme);
3530
+ const theme = useTheme(propsTheme);
4455
3531
  const _apiRef = useGridApiRef();
4456
3532
  const apiRef = propsApiRef !== null && propsApiRef !== void 0 ? propsApiRef : _apiRef;
4457
- const RenderedToolbar = slots !== null && slots !== void 0 && slots.toolbar ? slots.toolbar : Toolbar;
4458
3533
  LicenseInfo.setLicenseKey(license);
4459
3534
  const height = propsHeight !== null && propsHeight !== void 0 ? propsHeight : autoHeight ? undefined : '500px';
3535
+
3536
+ // When dataSource is present, MUI manages filter/sort/pagination internally.
3537
+ // We must not pass controlled models — only initialState (one-time) and
3538
+ // write-only onChange handlers for URL/localStorage persistence.
3539
+ const isDataSourceMode = Boolean(dataSource);
3540
+ const effectivePaginationMode = isDataSourceMode ? 'server' : paginationMode;
3541
+ const effectiveFilterMode = isDataSourceMode ? 'server' : propsFilterMode;
3542
+ const effectiveSortingMode = isDataSourceMode ? 'server' : propsSortingMode;
4460
3543
  const {
4461
3544
  onColumnVisibilityModelChange: controlledOnColumnVisibilityModelChange,
4462
3545
  onFilterModelChange: controlledOnFilterModelChange,
@@ -4482,7 +3565,6 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4482
3565
  density: controlledDensity,
4483
3566
  filterModel,
4484
3567
  onColumnVisibilityModelChange,
4485
- onDensityChange,
4486
3568
  onFilterModelChange,
4487
3569
  onPaginationModelChange,
4488
3570
  onPinnedColumnsChange,
@@ -4491,7 +3573,15 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4491
3573
  pinnedColumns,
4492
3574
  sortModel,
4493
3575
  onColumnWidthChange,
4494
- columnOrderModel
3576
+ columnOrderModel,
3577
+ rowGroupingModel,
3578
+ aggregationModel,
3579
+ pivotModel,
3580
+ pivotActive,
3581
+ onRowGroupingModelChange,
3582
+ onAggregationModelChange,
3583
+ onPivotModelChange,
3584
+ onPivotActiveChange
4495
3585
  } = useStatefulTable({
4496
3586
  apiRef: apiRef,
4497
3587
  initialState,
@@ -4502,6 +3592,9 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4502
3592
  onPaginationModelChange: controlledOnPaginationModelChange,
4503
3593
  onPinnedColumnsChange: controlledOnPinnedColumnsChange,
4504
3594
  onSortModelChange: controlledOnSortModelChange,
3595
+ onRowGroupingModelChange: propsOnRowGroupingModelChange,
3596
+ onAggregationModelChange: propsOnAggregationModelChange,
3597
+ onPivotModelChange: propsOnPivotModelChange,
4505
3598
  useRouter: useRouter,
4506
3599
  localStorageVersion,
4507
3600
  previousLocalStorageVersions
@@ -4537,9 +3630,61 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4537
3630
  return column;
4538
3631
  });
4539
3632
  }, [columns, columnOrderModel]);
4540
- const [rowSelectionModel, setRowSelectionModel] = useState(propsRowSelectionModel !== null && propsRowSelectionModel !== void 0 ? propsRowSelectionModel : []);
3633
+
3634
+ // In dataSource mode, track pagination locally for the custom pagination slots
3635
+ // (rendered outside DataGridPremium). MUI owns the actual pagination state internally.
3636
+ const [dataSourcePaginationModel, setDataSourcePaginationModel] = useState(paginationModel);
3637
+
3638
+ // The pagination model to use for display in pagination slots
3639
+ const activePaginationModel = isDataSourceMode ? dataSourcePaginationModel : paginationModel;
3640
+
3641
+ // Wrap onPaginationModelChange to also track state locally in dataSource mode
3642
+ const wrappedOnPaginationModelChange = useCallback((model, details) => {
3643
+ if (isDataSourceMode) {
3644
+ setDataSourcePaginationModel(model);
3645
+ }
3646
+ onPaginationModelChange(model, details);
3647
+ }, [isDataSourceMode, onPaginationModelChange]);
3648
+
3649
+ // In dataSource mode, pagination changes from our custom pagination slots
3650
+ // (rendered outside MUI's pagination state) route through apiRef so MUI's
3651
+ // internal page state updates and dataSource.getRows() refetches with the
3652
+ // new params. The `paginationModelChange` subscription below picks up the
3653
+ // resulting state change and propagates it to URL/localStorage and local
3654
+ // React state via wrappedOnPaginationModelChange.
3655
+ const dataSourcePaginationChange = useCallback(model => {
3656
+ var _apiRef$current;
3657
+ (_apiRef$current = apiRef.current) === null || _apiRef$current === void 0 ? void 0 : _apiRef$current.setPaginationModel(model);
3658
+ }, [apiRef]);
3659
+
3660
+ // In dataSource mode, subscribe to MUI's `paginationModelChange` event so
3661
+ // URL state stays in sync with MUI's internal pagination regardless of how
3662
+ // it changed (slot click, apiRef.setPaginationModel from consumer code,
3663
+ // MUI internal updates, etc.). Relying on MUI's `onPaginationModelChange`
3664
+ // prop callback alone is not sufficient: in pivot/GroupedData strategy mode
3665
+ // and with `paginationModel` seeded via `initialState` (rather than as a
3666
+ // controlled prop), the prop callback can be missed under certain
3667
+ // re-render orderings. The event fires reliably whenever the internal
3668
+ // state changes via `setState('setPaginationModel')`, see
3669
+ // `useGridStateInitialization.setState` → `publishEvent(changeEvent, …)`.
3670
+ // The deep-equal guard inside `useStatefulTable.onPaginationModelChange`
3671
+ // dedupes any duplicate emits, so overlap with the prop callback is safe.
4541
3672
  useEffect(() => {
4542
- setRowSelectionModel(propsRowSelectionModel !== null && propsRowSelectionModel !== void 0 ? propsRowSelectionModel : []);
3673
+ if (!isDataSourceMode) return;
3674
+ const api = apiRef.current;
3675
+ if (!(api !== null && api !== void 0 && api.subscribeEvent)) return;
3676
+ return api.subscribeEvent('paginationModelChange', model => {
3677
+ wrappedOnPaginationModelChange({
3678
+ page: model.page,
3679
+ pageSize: model.pageSize
3680
+ }, {
3681
+ reason: 'paginationModelChange'
3682
+ });
3683
+ });
3684
+ }, [isDataSourceMode, apiRef, wrappedOnPaginationModelChange]);
3685
+ const [rowSelectionModel, setRowSelectionModel] = useState(() => normalizeRowSelectionModel(propsRowSelectionModel));
3686
+ useEffect(() => {
3687
+ setRowSelectionModel(normalizeRowSelectionModel(propsRowSelectionModel));
4543
3688
  }, [propsRowSelectionModel]);
4544
3689
  const onRowSelectionModelChange = (selectionModel, details) => {
4545
3690
  setRowSelectionModel(selectionModel);
@@ -4558,23 +3703,44 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4558
3703
 
4559
3704
  // The checkboxSelectionVisibleOnly should only be applied to client-side pagination,
4560
3705
  // for server-side pagination it produces inconsistent behavior when selecting all rows in pages 2 and beyond
4561
- const checkboxSelectionVisibleOnly = Boolean(pagination) && Boolean(paginationMode != 'server');
3706
+ const checkboxSelectionVisibleOnly = Boolean(pagination) && Boolean(effectivePaginationMode != 'server');
4562
3707
 
4563
3708
  // Banner and pager placements are independent. `belowToolbar` (for either) renders
4564
- // in the toolbar-slot row, and only applies when pagination is on.
3709
+ // in a row inside the toolbar slot, and only applies when pagination is on.
4565
3710
  const bannerAtTop = selectionBannerPlacement === 'top';
4566
3711
  const bannerAtBottom = selectionBannerPlacement === 'bottom';
4567
3712
  const bannerBelowToolbar = selectionBannerPlacement === 'belowToolbar' && Boolean(pagination);
4568
3713
  const pagerBelowToolbar = paginationPlacement === 'belowToolbar' && Boolean(pagination);
3714
+ const belowToolbarActive = bannerBelowToolbar || pagerBelowToolbar;
3715
+
3716
+ // Track when the grid API is ready to ensure top pagination renders correctly
3717
+ const [gridReady, setGridReady] = useState(false);
3718
+
3719
+ // Force re-render when the grid API becomes ready (for top pagination)
3720
+ useEffect(() => {
3721
+ if (apiRef.current && !gridReady) {
3722
+ setGridReady(true);
3723
+ }
3724
+ });
3725
+
3726
+ // Sync persisted density via apiRef — initialState only applies on mount,
3727
+ // so this handles SPA back/forward navigation where controlledDensity changes after mount
3728
+ useEffect(() => {
3729
+ if (apiRef.current) {
3730
+ apiRef.current.setDensity(controlledDensity);
3731
+ }
3732
+ }, [controlledDensity, apiRef]);
4569
3733
 
4570
3734
  // in server-side pagination we want to update the selection status
4571
3735
  // every time we navigate between pages, resize our page or select something
4572
3736
  useEffect(() => {
4573
- if (paginationMode == 'server') {
4574
- onServerSideSelectionStatusChange(Array.isArray(rowSelectionModel) ? rowSelectionModel : [rowSelectionModel], apiRef, selectionStatusRef, forceSelectionUpdate, isRowSelectable, paginationModel.page, paginationModel.pageSize);
3737
+ if (effectivePaginationMode == 'server') {
3738
+ onServerSideSelectionStatusChange(rowSelectionModel, apiRef, selectionStatusRef, forceSelectionUpdate, isRowSelectable, activePaginationModel.page, activePaginationModel.pageSize);
4575
3739
  }
4576
- }, [rowSelectionModel, paginationModel.page, paginationModel.pageSize, rows]);
4577
- if (!Array.isArray(rows)) {
3740
+ }, [rowSelectionModel, activePaginationModel.page, activePaginationModel.pageSize, rows]);
3741
+
3742
+ // In dataSource mode MUI provides rows internally; skip the guard.
3743
+ if (!isDataSourceMode && !Array.isArray(rows)) {
4578
3744
  return null;
4579
3745
  }
4580
3746
 
@@ -4583,15 +3749,15 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4583
3749
  // receive the fresh value in the same render cycle — no extra re-render needed.
4584
3750
  // The ref is kept in sync for the onRowSelectionModelChange callback's deselect logic.
4585
3751
  let selectionStatus = selectionStatusRef.current;
4586
- if (pagination && paginationMode !== 'server' && Array.isArray(rowSelectionModel) && rowSelectionModel.length > 0) {
3752
+ if (pagination && effectivePaginationMode !== 'server' && getSelectionCount(rowSelectionModel) > 0) {
4587
3753
  try {
4588
- // Use manual page slicing with our React state's paginationModel instead of
4589
- // gridPaginatedVisibleSortedGridRow*Selector(apiRef). In MUI v7, the apiRef's
4590
- // internal pagination state can lag behind the React state after a page change,
4591
- // causing the selection status to be one page behind.
4592
- const pageStart = paginationModel.page * paginationModel.pageSize;
4593
- const pageEnd = pageStart + paginationModel.pageSize;
4594
- const selectableRowsInPage = isRowSelectable ? gridFilteredSortedRowEntriesSelector(apiRef).slice(pageStart, pageEnd).filter(_ref2 => {
3754
+ // Use manual page slicing instead of gridPaginatedVisibleSorted* selectors.
3755
+ // MUI's paginated selectors use apiRef internal state which may be stale when
3756
+ // paginationModel prop changes — our React state is always up to date.
3757
+ const allFilteredEntries = gridFilteredSortedRowEntriesSelector(apiRef);
3758
+ const pageStart = activePaginationModel.page * activePaginationModel.pageSize;
3759
+ const pageEntries = allFilteredEntries.slice(pageStart, pageStart + activePaginationModel.pageSize);
3760
+ const selectableRowsInPage = isRowSelectable ? pageEntries.filter(_ref2 => {
4595
3761
  let {
4596
3762
  model
4597
3763
  } = _ref2;
@@ -4603,24 +3769,29 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4603
3769
  id
4604
3770
  } = _ref3;
4605
3771
  return id;
4606
- }) : gridFilteredSortedRowIdsSelector(apiRef).slice(pageStart, pageEnd);
3772
+ }) : pageEntries.map(_ref4 => {
3773
+ let {
3774
+ id
3775
+ } = _ref4;
3776
+ return id;
3777
+ });
4607
3778
  const numberOfSelectableRowsInPage = selectableRowsInPage.length;
4608
- const selectableRowsInTable = isRowSelectable ? gridFilteredSortedRowEntriesSelector(apiRef).filter(_ref4 => {
3779
+ const selectableRowsInTable = isRowSelectable ? allFilteredEntries.filter(_ref5 => {
4609
3780
  let {
4610
3781
  model
4611
- } = _ref4;
3782
+ } = _ref5;
4612
3783
  return isRowSelectable({
4613
3784
  row: model
4614
3785
  });
4615
- }).map(_ref5 => {
3786
+ }).map(_ref6 => {
4616
3787
  let {
4617
3788
  id
4618
- } = _ref5;
3789
+ } = _ref6;
4619
3790
  return id;
4620
3791
  }) : gridFilteredSortedRowIdsSelector(apiRef);
4621
3792
  const numberOfSelectableRowsInTable = selectableRowsInTable.length;
4622
- const numberOfSelectedRows = rowSelectionModel.length;
4623
- const selectedOnCurrentPage = selectableRowsInPage.filter(id => rowSelectionModel.includes(id));
3793
+ const numberOfSelectedRows = getSelectionCount(rowSelectionModel);
3794
+ const selectedOnCurrentPage = selectableRowsInPage.filter(id => isRowSelected(rowSelectionModel, id));
4624
3795
  if (numberOfSelectedRows === numberOfSelectableRowsInTable && numberOfSelectableRowsInPage < numberOfSelectableRowsInTable) {
4625
3796
  selectionStatus = {
4626
3797
  type: 'table',
@@ -4645,7 +3816,7 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4645
3816
  } catch {
4646
3817
  // apiRef may not be initialized on first render
4647
3818
  }
4648
- } else if (pagination && paginationMode !== 'server') {
3819
+ } else if (pagination && effectivePaginationMode !== 'server') {
4649
3820
  selectionStatus = {
4650
3821
  type: 'none',
4651
3822
  numberOfSelectedRows: 0
@@ -4670,37 +3841,39 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4670
3841
  // remounted (remounting the toolbar dropped quick-search focus on every keystroke). Typed as
4671
3842
  // ToolbarWrapper props so mistakes are caught here; cast to MUI's slot types at the injection
4672
3843
  // sites below. See ../DataGrid/defaultSlots.
4673
- const toolbarSlotProps = {
3844
+ const belowToolbarSlotProps = {
3845
+ RenderedToolbar: (_ref7 = slots === null || slots === void 0 ? void 0 : slots.toolbar) !== null && _ref7 !== void 0 ? _ref7 : Toolbar,
4674
3846
  hideToolbar,
4675
- RenderedToolbar,
4676
3847
  filterModel,
4677
3848
  onFilterModelChange,
4678
3849
  pagination,
4679
- displaySelection: bannerAtTop || bannerBelowToolbar,
4680
- displayPagination: ['top', 'both'].includes(paginationPlacement) || pagerBelowToolbar,
3850
+ paginationMode: effectivePaginationMode,
3851
+ displaySelection: bannerBelowToolbar,
3852
+ displayPagination: pagerBelowToolbar,
4681
3853
  displayRowsPerPage: pagerBelowToolbar,
4682
3854
  selectionStatus,
4683
3855
  apiRef,
4684
3856
  isRowSelectable,
4685
- paginationModel,
4686
- onPaginationModelChange,
4687
- pageSizeOptions,
3857
+ paginationModel: activePaginationModel,
3858
+ onPaginationModelChange: isDataSourceMode ? dataSourcePaginationChange : onPaginationModelChange,
3859
+ pageSizeOptions: pageSizeOptions,
4688
3860
  paginationProps,
4689
- paginationMode,
4690
3861
  rowCount
4691
3862
  };
4692
3863
  const bottomPaginationSlotProps = {
4693
3864
  pagination,
4694
- paginationMode,
3865
+ paginationMode: effectivePaginationMode,
4695
3866
  displaySelection: bannerAtBottom,
4696
3867
  displayRowsPerPage: ['bottom', 'both'].includes(paginationPlacement),
4697
3868
  displayPagination: ['bottom', 'both'].includes(paginationPlacement),
4698
3869
  selectionStatus,
4699
- paginationModel,
4700
- onPaginationModelChange,
3870
+ paginationModel: activePaginationModel,
3871
+ // Bottom pager routes non-dataSource changes through the wrapped handler (the
3872
+ // belowToolbar/top pagers use the unwrapped one) — preserved from the previous inline slot.
3873
+ onPaginationModelChange: isDataSourceMode ? dataSourcePaginationChange : wrappedOnPaginationModelChange,
4701
3874
  apiRef,
4702
3875
  isRowSelectable,
4703
- pageSizeOptions,
3876
+ pageSizeOptions: pageSizeOptions,
4704
3877
  paginationProps,
4705
3878
  rowCount
4706
3879
  };
@@ -4714,90 +3887,172 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4714
3887
  ref: datagridRef,
4715
3888
  className: classNames(StatefulDataGrid.className, className),
4716
3889
  $height: height
4717
- }, /*#__PURE__*/React__default.createElement(DataGridPro, _extends({}, forwardedProps, {
3890
+ }, pagination && gridReady && (bannerAtTop || ['top', 'both'].includes(paginationPlacement)) ? effectivePaginationMode == 'server' ? /*#__PURE__*/React__default.createElement(ServerSideControlledPagination, {
3891
+ displaySelection: bannerAtTop,
3892
+ displayRowsPerPage: ['top', 'both'].includes(paginationPlacement),
3893
+ displayPagination: ['top', 'both'].includes(paginationPlacement),
3894
+ selectionStatus: selectionStatus,
3895
+ paginationModel: activePaginationModel,
3896
+ onPaginationModelChange: isDataSourceMode ? dataSourcePaginationChange : onPaginationModelChange,
3897
+ pageSizeOptions: pageSizeOptions,
3898
+ paginationProps: paginationProps,
3899
+ rowCount: rowCount
3900
+ }) : /*#__PURE__*/React__default.createElement(ControlledPagination, {
3901
+ displaySelection: bannerAtTop,
3902
+ displayRowsPerPage: ['top', 'both'].includes(paginationPlacement),
3903
+ displayPagination: ['top', 'both'].includes(paginationPlacement),
3904
+ selectionStatus: selectionStatus,
4718
3905
  apiRef: apiRef,
3906
+ isRowSelectable: isRowSelectable,
3907
+ paginationModel: activePaginationModel,
3908
+ onPaginationModelChange: onPaginationModelChange,
3909
+ pageSizeOptions: pageSizeOptions,
3910
+ paginationProps: paginationProps
3911
+ }) : null, /*#__PURE__*/React__default.createElement(DataGridPremium, _extends({}, forwardedProps, {
3912
+ apiRef: apiRef,
3913
+ dataSource: dataSource,
4719
3914
  columns: orderedColumns,
4720
- columnVisibilityModel: columnVisibilityModel,
4721
- density: controlledDensity,
4722
- filterModel: filterModel,
4723
3915
  onColumnVisibilityModelChange: onColumnVisibilityModelChange,
4724
- onDensityChange: onDensityChange,
4725
- onFilterModelChange: onFilterModelChange,
4726
- onPaginationModelChange: onPaginationModelChange,
4727
3916
  onPinnedColumnsChange: onPinnedColumnsChange,
4728
- onSortModelChange: onSortModelChange,
4729
- paginationModel: paginationModel,
4730
- pinnedColumns: pinnedColumns,
4731
- sortModel: sortModel,
4732
3917
  pageSizeOptions: pageSizeOptions,
4733
3918
  onColumnWidthChange: onColumnWidthChange,
3919
+ onRowGroupingModelChange: onRowGroupingModelChange,
3920
+ onAggregationModelChange: onAggregationModelChange,
3921
+ onPivotModelChange: onPivotModelChange,
3922
+ pivotActive: pivotActive,
3923
+ onPivotActiveChange: onPivotActiveChange
3924
+ // In dataSource mode: models are uncontrolled (MUI owns them),
3925
+ // onChange handlers are write-only for URL/localStorage persistence,
3926
+ // and initialState seeds MUI on mount from the persisted URL state.
3927
+ // columnVisibilityModel / pinnedColumns / rowGroupingModel /
3928
+ // aggregationModel / pivotModel are also uncontrolled here to
3929
+ // avoid a controlled re-render race with consumer-side
3930
+ // microtask-deferred history updates (otherwise user toggles
3931
+ // flip back when MUI re-emits with the stale controlled value).
3932
+ // pivotModel specifically also carries `hidden`/`sort` field
3933
+ // metadata that our simplified URL representation strips — so
3934
+ // controlling it would prevent users from unchecking fields in
3935
+ // the pivot panel (the controlled prop would immediately re-add
3936
+ // them). Consumers needing programmatic changes should use the
3937
+ // apiRef imperative API.
3938
+ }, isDataSourceMode ? {
3939
+ onFilterModelChange: onFilterModelChange,
3940
+ onSortModelChange: onSortModelChange,
3941
+ onPaginationModelChange: wrappedOnPaginationModelChange,
3942
+ initialState: _objectSpread2(_objectSpread2({}, initialState), {}, {
3943
+ density: controlledDensity,
3944
+ columns: _objectSpread2(_objectSpread2({}, initialState === null || initialState === void 0 ? void 0 : initialState.columns), {}, {
3945
+ orderedFields: columnOrderModel,
3946
+ columnVisibilityModel
3947
+ }),
3948
+ pinnedColumns,
3949
+ rowGrouping: _objectSpread2(_objectSpread2({}, initialState === null || initialState === void 0 ? void 0 : initialState.rowGrouping), {}, {
3950
+ model: rowGroupingModel
3951
+ }),
3952
+ aggregation: _objectSpread2(_objectSpread2({}, initialState === null || initialState === void 0 ? void 0 : initialState.aggregation), {}, {
3953
+ model: aggregationModel
3954
+ }),
3955
+ filter: {
3956
+ filterModel
3957
+ },
3958
+ sorting: {
3959
+ sortModel
3960
+ },
3961
+ pagination: {
3962
+ paginationModel
3963
+ },
3964
+ pivoting: _objectSpread2(_objectSpread2({}, initialState === null || initialState === void 0 ? void 0 : initialState.pivoting), {}, {
3965
+ model: pivotModel,
3966
+ enabled: pivotActive
3967
+ })
3968
+ })
3969
+ } : {
3970
+ columnVisibilityModel,
3971
+ pinnedColumns,
3972
+ rowGroupingModel,
3973
+ aggregationModel,
3974
+ filterModel,
3975
+ sortModel,
3976
+ paginationModel,
3977
+ pivotModel,
3978
+ onFilterModelChange: onFilterModelChange,
3979
+ onSortModelChange: onSortModelChange,
3980
+ onPaginationModelChange: wrappedOnPaginationModelChange,
4734
3981
  initialState: _objectSpread2(_objectSpread2({}, initialState), {}, {
3982
+ density: controlledDensity,
4735
3983
  columns: _objectSpread2(_objectSpread2({}, initialState === null || initialState === void 0 ? void 0 : initialState.columns), {}, {
4736
3984
  orderedFields: columnOrderModel
4737
3985
  })
4738
- }),
3986
+ })
3987
+ }, {
4739
3988
  isRowSelectable: isRowSelectable,
4740
3989
  pagination: pagination,
4741
- paginationMode: paginationMode,
4742
- keepNonExistentRowsSelected: paginationMode == 'server',
4743
- rows: rows,
3990
+ paginationMode: effectivePaginationMode,
3991
+ filterMode: effectiveFilterMode,
3992
+ sortingMode: effectiveSortingMode,
3993
+ keepNonExistentRowsSelected: effectivePaginationMode == 'server',
3994
+ rows: isDataSourceMode ? [] : rows,
4744
3995
  rowCount: rowCount,
4745
3996
  autoHeight: autoHeight,
4746
3997
  checkboxSelectionVisibleOnly: checkboxSelectionVisibleOnly,
4747
- slots: _objectSpread2(_objectSpread2(_objectSpread2({}, baseGridSlots), slots), {}, {
4748
- // `BelowToolbar` and `BottomPagination` are stable module-level components; their
4749
- // per-render data is injected via `slotProps` below. A fresh inline function here
4750
- // would remount the slot's subtree every render — for the toolbar that dropped
4751
- // quick-search focus on every keystroke. The consumer's `slots.toolbar` (or the
4752
- // default DS Toolbar) is preserved via `RenderedToolbar` inside ToolbarWrapper.
4753
- toolbar: BelowToolbar,
3998
+ disableRowSelectionExcludeModel: true,
3999
+ showToolbar: !hideToolbar || belowToolbarActive,
4000
+ slots: _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, baseGridSlots), slots), belowToolbarActive ? {
4001
+ toolbar: BelowToolbar
4002
+ } : {}), {}, {
4754
4003
  pagination: BottomPagination
4755
4004
  }),
4756
- slotProps: _objectSpread2(_objectSpread2({}, slotProps), {}, {
4757
- // Inject the per-render slot data (built above) here rather than closing over it
4758
- // in inline slots, so the slot identities stay stable. Consumer slot props are
4759
- // spread first; the DS layout data wins. Cast at the MUI boundary because these
4760
- // are ToolbarWrapper props, not the MUI slot prop types.
4761
- toolbar: _objectSpread2(_objectSpread2({}, slotProps === null || slotProps === void 0 ? void 0 : slotProps.toolbar), toolbarSlotProps),
4005
+ slotProps: _objectSpread2(_objectSpread2(_objectSpread2({}, slotProps), belowToolbarActive ? {
4006
+ toolbar: _objectSpread2(_objectSpread2({}, slotProps === null || slotProps === void 0 ? void 0 : slotProps.toolbar), belowToolbarSlotProps)
4007
+ } : {}), {}, {
4762
4008
  pagination: _objectSpread2(_objectSpread2({}, slotProps === null || slotProps === void 0 ? void 0 : slotProps.pagination), bottomPaginationSlotProps)
4763
4009
  }),
4764
4010
  rowSelectionModel: rowSelectionModel,
4765
4011
  onRowSelectionModelChange: (newSelectionModel, details) => {
4766
- if (pagination && paginationMode != 'server') {
4767
- const cbPageStart = paginationModel.page * paginationModel.pageSize;
4768
- const cbPageEnd = cbPageStart + paginationModel.pageSize;
4769
- const selectableRowsInPage = isRowSelectable ? gridFilteredSortedRowEntriesSelector(apiRef).slice(cbPageStart, cbPageEnd).filter(_ref6 => {
4012
+ if (pagination && effectivePaginationMode != 'server') {
4013
+ // Use manual page slicing instead of gridPaginatedVisibleSorted* selectors
4014
+ // to avoid stale apiRef pagination state.
4015
+ const allFilteredEntries = gridFilteredSortedRowEntriesSelector(apiRef);
4016
+ const pageStart = activePaginationModel.page * activePaginationModel.pageSize;
4017
+ const pageEntries = allFilteredEntries.slice(pageStart, pageStart + activePaginationModel.pageSize);
4018
+ const selectableRowsInPage = isRowSelectable ? pageEntries.filter(_ref8 => {
4770
4019
  let {
4771
4020
  model
4772
- } = _ref6;
4021
+ } = _ref8;
4773
4022
  return isRowSelectable({
4774
4023
  row: model
4775
4024
  });
4776
- }).map(_ref7 => {
4025
+ }).map(_ref9 => {
4026
+ let {
4027
+ id
4028
+ } = _ref9;
4029
+ return id;
4030
+ }) : pageEntries.map(_ref10 => {
4777
4031
  let {
4778
4032
  id
4779
- } = _ref7;
4033
+ } = _ref10;
4780
4034
  return id;
4781
- }) : gridFilteredSortedRowIdsSelector(apiRef).slice(cbPageStart, cbPageEnd);
4035
+ });
4782
4036
  const numberOfSelectableRowsInPage = selectableRowsInPage.length;
4783
- const selectableRowsInTable = isRowSelectable ? gridFilteredSortedRowEntriesSelector(apiRef).filter(_ref8 => {
4037
+ const selectableRowsInTable = isRowSelectable ? allFilteredEntries.filter(_ref11 => {
4784
4038
  let {
4785
4039
  model
4786
- } = _ref8;
4040
+ } = _ref11;
4787
4041
  return isRowSelectable({
4788
4042
  row: model
4789
4043
  });
4790
- }).map(_ref9 => {
4044
+ }).map(_ref12 => {
4791
4045
  let {
4792
4046
  id
4793
- } = _ref9;
4047
+ } = _ref12;
4794
4048
  return id;
4795
4049
  }) : gridFilteredSortedRowIdsSelector(apiRef);
4796
4050
  const numberOfSelectableRowsInTable = selectableRowsInTable.length;
4797
- const numberOfSelectedRows = newSelectionModel.length;
4051
+ const numberOfSelectedRows = getSelectionCount(newSelectionModel);
4798
4052
  if (selectionStatusRef.current.type === 'table' && numberOfSelectedRows === numberOfSelectableRowsInTable - numberOfSelectableRowsInPage || selectionStatusRef.current.type === 'table' && numberOfSelectedRows === numberOfSelectableRowsInTable || selectionStatusRef.current.type === 'page' && numberOfSelectedRows === numberOfSelectableRowsInPage) {
4799
4053
  setTimeout(() => {
4800
- apiRef.current.selectRows([], true, true);
4054
+ var _apiRef$current2;
4055
+ (_apiRef$current2 = apiRef.current) === null || _apiRef$current2 === void 0 ? void 0 : _apiRef$current2.selectRows([], true, true);
4801
4056
  }, 0);
4802
4057
  }
4803
4058
  if (numberOfSelectedRows === numberOfSelectableRowsInPage && numberOfSelectableRowsInPage < numberOfSelectableRowsInTable) {
@@ -4839,5 +4094,5 @@ const StatefulDataGrid = /*#__PURE__*/forwardRef((props, ref) => {
4839
4094
  StatefulDataGrid.className = CLASSNAME;
4840
4095
  StatefulDataGrid.displayName = COMPONENT_NAME;
4841
4096
 
4842
- export { clearAllVersionStorage as $, ARRAY_IS_EMPTY as A, Box$1 as B, CONTAINS_ANY_OF as C, DOES_NOT_CONTAIN as D, ENDS_WITH_ANY_OF as E, IS as F, IS_NOT as G, HAS_WITH_SELECT as H, IS_ANY_OF as I, getGridStringOperators as J, getGridStringArrayOperators as K, getGridStringArrayOperatorsWithSelect as L, getGridStringArrayOperatorsWithSelectOnStringArrayColumns as M, FILTER_MODEL_KEY as N, SORT_MODEL_KEY as O, PAGINATION_MODEL_KEY as P, PINNED_COLUMNS as Q, DIMENSION_MODEL_KEY as R, STARTS_WITH_ANY_OF as S, TextField$1 as T, FILTER_SEARCH_KEY as U, VISIBILITY_MODEL_KEY as V, DENSITY_MODEL_KEY as W, COLUMN_ORDER_MODEL_KEY as X, CATEGORIES as Y, buildStorageKey as Z, clearPreviousVersionStorage as _, DOES_NOT_EQUAL as a, resetStatefulDataGridState as a0, resetColumnVisibility as a1, convertToDisplayFormat as a2, convertFromDisplayFormat as a3, getDecodedSearchFromUrl as a4, buildQueryParamsString as a5, areSearchStringsEqual as a6, decodeValue as a7, encodeValue as a8, urlSearchParamsToString as a9, StatefulDataGrid as aA, numberOperatorEncoder as aa, numberOperatorDecoder as ab, isOperatorValueValid as ac, isValueValid as ad, getFilterModelFromString as ae, normalizeDateValue as af, getSearchParamsFromFilterModel as ag, getSortingFromString as ah, getSearchParamsFromSorting as ai, getPaginationFromString as aj, getSearchParamsFromPagination as ak, getColumnVisibilityFromString as al, getSearchParamsFromColumnVisibility as am, getPinnedColumnsFromString as an, getSearchParamsFromPinnedColumns as ao, getSearchParamsFromTab as ap, getDensityFromString as aq, getSearchParamsFromDensity as ar, getDensityModel as as, getColumnOrderFromString as at, getSearchParamsFromColumnOrder as au, getSearchParamsFromVersion as av, getFinalSearch as aw, getModelsParsedOrUpdateLocalStorage as ax, updateUrl as ay, areFilterModelsEquivalent as az, 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 };
4097
+ export { buildStorageKey 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, ROW_GROUPING_MODEL_KEY as W, AGGREGATION_MODEL_KEY as X, PIVOT_MODEL_KEY as Y, PIVOT_ACTIVE_KEY as Z, CATEGORIES as _, DOES_NOT_EQUAL as a, clearPreviousVersionStorage as a0, clearAllVersionStorage as a1, resetStatefulDataGridState as a2, resetColumnVisibility as a3, convertToDisplayFormat as a4, convertFromDisplayFormat as a5, getDecodedSearchFromUrl as a6, buildQueryParamsString as a7, areSearchStringsEqual as a8, decodeValue as a9, getSearchParamsFromAggregation as aA, fromGridPivotModel as aB, getPivotFromString as aC, getSearchParamsFromPivot as aD, getPivotActiveFromString as aE, getSearchParamsFromPivotActive as aF, getSearchParamsFromVersion as aG, getFinalSearch as aH, getModelsParsedOrUpdateLocalStorage as aI, updateUrl as aJ, areFilterModelsEquivalent as aK, StatefulDataGrid as aL, encodeValue as aa, urlSearchParamsToString as ab, numberOperatorEncoder as ac, numberOperatorDecoder as ad, isOperatorValueValid as ae, isValueValid as af, getFilterModelFromString as ag, normalizeDateValue as ah, getSearchParamsFromFilterModel as ai, getSortingFromString as aj, getSearchParamsFromSorting as ak, getPaginationFromString as al, getSearchParamsFromPagination as am, getColumnVisibilityFromString as an, getSearchParamsFromColumnVisibility as ao, getPinnedColumnsFromString as ap, getSearchParamsFromPinnedColumns as aq, getSearchParamsFromTab as ar, getDensityFromString as as, getSearchParamsFromDensity as at, getDensityModel as au, getColumnOrderFromString as av, getSearchParamsFromColumnOrder as aw, getRowGroupingFromString as ax, getSearchParamsFromRowGrouping as ay, getAggregationFromString as az, 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 };
4843
4098
  //# sourceMappingURL=StatefulDataGrid2.js.map