@olenbetong/appframe-ds 1.2.0 → 1.3.1

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.
Files changed (177) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +7 -7
  3. package/scripts/copyAssets.mjs +27 -27
  4. package/src/AfLookup/Lookup.css +129 -129
  5. package/src/AfLookup/Lookup.tsx +184 -184
  6. package/src/AfLookup/LookupCombobox.css +78 -78
  7. package/src/AfLookup/LookupCombobox.tsx +237 -237
  8. package/src/AfLookup/LookupEdit.tsx +130 -130
  9. package/src/AfLookup/LookupGrid.tsx +124 -124
  10. package/src/AfLookup/index.ts +5 -5
  11. package/src/autocomplete/Autocomplete.css +256 -256
  12. package/src/autocomplete/Autocomplete.tsx +320 -320
  13. package/src/autocomplete/AutocompleteFrame.tsx +168 -168
  14. package/src/autocomplete/Combobox.tsx +286 -286
  15. package/src/autocomplete/index.ts +4 -4
  16. package/src/autocomplete/types.ts +204 -204
  17. package/src/autocomplete/utils.ts +183 -183
  18. package/src/binding/BoundNumericTextField.tsx +102 -102
  19. package/src/binding/BoundTextField.tsx +49 -49
  20. package/src/binding/CancelButton.tsx +30 -30
  21. package/src/binding/CancelIconButton.tsx +30 -30
  22. package/src/binding/DataEditToolbar.css +23 -0
  23. package/src/binding/DataEditToolbar.tsx +84 -92
  24. package/src/binding/DeleteButton.tsx +35 -35
  25. package/src/binding/DeleteIconButton.tsx +35 -35
  26. package/src/binding/RefreshButton.tsx +31 -31
  27. package/src/binding/RefreshIconButton.tsx +33 -33
  28. package/src/binding/RefreshRowIconButton.tsx +33 -33
  29. package/src/binding/SaveButton.tsx +37 -37
  30. package/src/binding/SaveIconButton.tsx +38 -38
  31. package/src/container/Container.tsx +49 -49
  32. package/src/container/index.ts +1 -1
  33. package/src/cutoff/CutOffPicker.css +12 -12
  34. package/src/cutoff/CutOffPicker.tsx +165 -165
  35. package/src/cutoff/context.tsx +194 -194
  36. package/src/cutoff/dsCutOffDates.ts +21 -21
  37. package/src/cutoff/index.ts +4 -4
  38. package/src/cutoff/utils.test.ts +59 -59
  39. package/src/cutoff/utils.ts +276 -276
  40. package/src/filter/ChipListInput.tsx +79 -79
  41. package/src/filter/FieldFilterPanel.css +150 -150
  42. package/src/filter/FieldFilterPanel.tsx +309 -309
  43. package/src/filter/FilterBuilder.css +152 -152
  44. package/src/filter/FilterBuilder.test.tsx +153 -153
  45. package/src/filter/FilterBuilder.tsx +435 -435
  46. package/src/filter/FilterEditor.css +231 -231
  47. package/src/filter/FilterEditor.test.tsx +259 -259
  48. package/src/filter/FilterEditor.tsx +75 -75
  49. package/src/filter/FilterGroupEditor.tsx +160 -160
  50. package/src/filter/FilterNameDialog.css +22 -22
  51. package/src/filter/FilterNameDialog.tsx +81 -81
  52. package/src/filter/FilterRow.tsx +117 -117
  53. package/src/filter/FilterShareDialog.css +28 -28
  54. package/src/filter/FilterShareDialog.tsx +201 -201
  55. package/src/filter/FilterStringField.tsx +78 -78
  56. package/src/filter/FilterValueEditor.tsx +220 -220
  57. package/src/filter/SavedFilterTree.css +107 -107
  58. package/src/filter/SavedFilterTree.test.tsx +94 -94
  59. package/src/filter/SavedFilterTree.tsx +218 -218
  60. package/src/filter/fieldFilter.test.ts +158 -158
  61. package/src/filter/fieldFilter.ts +139 -139
  62. package/src/filter/index.ts +27 -27
  63. package/src/filter/types.ts +38 -38
  64. package/src/filter/useDistinctValues.test.ts +102 -102
  65. package/src/filter/useDistinctValues.ts +230 -230
  66. package/src/global.d.ts +11 -11
  67. package/src/grid/AfGridColumnsPanel.tsx +118 -118
  68. package/src/grid/AfGridContext.tsx +51 -51
  69. package/src/grid/AfGridError.tsx +46 -46
  70. package/src/grid/AfHeaderFilterCell.tsx +147 -147
  71. package/src/grid/AfHeaderFilterPanel.tsx +111 -111
  72. package/src/grid/GridEditLookup.css +12 -12
  73. package/src/grid/GridEditLookup.tsx +121 -121
  74. package/src/grid/Toolbar.tsx +233 -233
  75. package/src/grid/editing.ts +100 -100
  76. package/src/grid/filter.ts +78 -78
  77. package/src/grid/formatters.ts +48 -48
  78. package/src/grid/index.css +245 -245
  79. package/src/grid/index.tsx +503 -489
  80. package/src/grid/license.ts +48 -48
  81. package/src/grid/localization.ts +369 -369
  82. package/src/grid/slots/index.tsx +307 -307
  83. package/src/grid/slots/slots.css +64 -64
  84. package/src/grid/theme.tsx +138 -138
  85. package/src/grid/useAfColumns.tsx +371 -371
  86. package/src/grid/useAfCurrentIndex.ts +63 -63
  87. package/src/grid/useAfData.ts +20 -20
  88. package/src/grid/useAfFilter.ts +160 -160
  89. package/src/grid/useAfFilterFields.ts +55 -55
  90. package/src/grid/useAfGridApi.ts +71 -71
  91. package/src/grid/useAfKeyBindings.ts +36 -36
  92. package/src/grid/useAfNewItemRow.ts +258 -258
  93. package/src/grid/useAfPagination.ts +53 -53
  94. package/src/grid/useAfPersistedState.ts +230 -230
  95. package/src/grid/useAfRowEditModel.ts +145 -145
  96. package/src/grid/useAfRowGrouping.ts +47 -47
  97. package/src/grid/useAfServerAggregation.test.ts +68 -0
  98. package/src/grid/useAfServerAggregation.ts +158 -145
  99. package/src/grid/useAfSortModel.ts +84 -84
  100. package/src/i18n.ts +13 -13
  101. package/src/index.ts +14 -14
  102. package/src/input/InputAdornments.css +84 -75
  103. package/src/input/InputAdornments.tsx +102 -102
  104. package/src/input/index.ts +1 -1
  105. package/src/layout/AppMenu.tsx +74 -74
  106. package/src/layout/AppMenuDrawer.css +70 -70
  107. package/src/layout/AppMenuDrawer.tsx +122 -122
  108. package/src/layout/ErrorBoundary.tsx +162 -162
  109. package/src/layout/PageContainer.css +21 -2
  110. package/src/layout/PageContainer.tsx +18 -2
  111. package/src/layout/ResponsiveDialog.css +86 -86
  112. package/src/layout/ResponsiveDialog.tsx +55 -55
  113. package/src/layout/index.tsx +69 -69
  114. package/src/link/Link.tsx +23 -23
  115. package/src/link/index.ts +1 -1
  116. package/src/mdi/Mdi.css +138 -138
  117. package/src/mdi/Mdi.test.tsx +91 -91
  118. package/src/mdi/Mdi.tsx +141 -141
  119. package/src/mdi/MdiConfirmCloseDialog.tsx +47 -47
  120. package/src/mdi/MdiContext.tsx +59 -59
  121. package/src/mdi/MdiDocumentContext.tsx +60 -60
  122. package/src/mdi/MdiPanel.tsx +61 -61
  123. package/src/mdi/MdiRestore.test.tsx +86 -86
  124. package/src/mdi/MdiTab.tsx +30 -30
  125. package/src/mdi/controller.test.ts +382 -382
  126. package/src/mdi/controller.ts +656 -656
  127. package/src/mdi/dockviewHost.ts +132 -132
  128. package/src/mdi/fakeHost.ts +200 -200
  129. package/src/mdi/host.ts +44 -44
  130. package/src/mdi/ids.test.ts +20 -20
  131. package/src/mdi/ids.ts +18 -18
  132. package/src/mdi/index.ts +31 -31
  133. package/src/mdi/layoutRestore.test.ts +156 -156
  134. package/src/mdi/layoutRestore.ts +311 -311
  135. package/src/mdi/layoutSpec.test.ts +143 -143
  136. package/src/mdi/layoutSpec.ts +214 -214
  137. package/src/mdi/layoutStore.test.ts +93 -93
  138. package/src/mdi/layoutStore.ts +99 -99
  139. package/src/mdi/memorySettingsStore.ts +21 -21
  140. package/src/mdi/registry.ts +34 -34
  141. package/src/mdi/testTypes.ts +37 -37
  142. package/src/mdi/theme.ts +22 -22
  143. package/src/mdi/types.ts +214 -214
  144. package/src/page/Page.css +53 -53
  145. package/src/page/PageBlock.css +64 -64
  146. package/src/page/PageBlock.tsx +92 -92
  147. package/src/page/PageHeader.css +88 -88
  148. package/src/page/PageHeader.tsx +115 -115
  149. package/src/page/PageTitle.css +5 -5
  150. package/src/page/PageTitle.tsx +21 -21
  151. package/src/page/index.ts +4 -4
  152. package/src/paper/Paper.tsx +52 -52
  153. package/src/progress/LinearProgress.css +93 -93
  154. package/src/progress/LinearProgress.tsx +104 -104
  155. package/src/progress/index.ts +1 -1
  156. package/src/report-downloader/ReportDownloader.css +67 -67
  157. package/src/report-downloader/components/StatusItem.tsx +87 -87
  158. package/src/report-downloader/components/StatusList.tsx +28 -28
  159. package/src/report-downloader/index.ts +41 -41
  160. package/src/report-downloader/status.tsx +92 -92
  161. package/src/sortable/DropIndicator.css +51 -51
  162. package/src/sortable/DropIndicator.tsx +27 -27
  163. package/src/sortable/SortableItemList.css +21 -21
  164. package/src/sortable/SortableItemList.tsx +264 -264
  165. package/src/sortable/index.ts +8 -8
  166. package/src/sortable/reorderSortOrder.ts +126 -126
  167. package/src/sortable/sortOrder.ts +101 -101
  168. package/src/sortable/types.ts +9 -9
  169. package/src/sortable/useSortableContainer.ts +53 -53
  170. package/src/sortable/useSortableItem.ts +95 -95
  171. package/src/sortable/useSortableList.ts +117 -117
  172. package/src/test/happy-dom-document-class.ts +24 -24
  173. package/src/test/setup.ts +12 -12
  174. package/src/theme/useDsColorScheme.ts +53 -53
  175. package/tsconfig.build.json +9 -9
  176. package/tsconfig.json +8 -8
  177. package/vitest.config.ts +10 -10
@@ -1,489 +1,503 @@
1
- import "./index.css";
2
-
3
- import { Alert } from "@digdir/designsystemet-react";
4
- import {
5
- DataGridPremium as DataGrid,
6
- type DataGridPremiumProps as DataGridProps,
7
- GRID_AGGREGATION_FUNCTIONS,
8
- GRID_ROOT_GROUP_ID,
9
- type GridAggregationFunction,
10
- type GridApi,
11
- type GridFilterModel,
12
- useGridApiRef,
13
- } from "@mui/x-data-grid-premium";
14
- import { type DataObject, uid } from "@olenbetong/appframe-data";
15
- import { DataObjectProvider, useLoading } from "@olenbetong/appframe-react";
16
-
17
- import clsx from "clsx";
18
- import { useEffect, useMemo, useState } from "react";
19
- import { FilterBuilder } from "../filter/index.js";
20
- import { AfGridColumnsPanel } from "./AfGridColumnsPanel.js";
21
- import { type AfGridContextValue, AfGridContextProvider } from "./AfGridContext.js";
22
- import { AfGridError } from "./AfGridError.js";
23
- import { registerLicense } from "./license.js";
24
- import { AF_GRID_LOCALE_TEXT } from "./localization.js";
25
- import { dsBaseSlots } from "./slots/index.js";
26
- import { AfGridThemeProvider } from "./theme.js";
27
- import {
28
- type AfExcelExportOptions,
29
- AfGridExcelExportMenuItem,
30
- type AfGridExcelExportOptions,
31
- AfGridToolbar,
32
- AfGridToolbarExcelExportButton,
33
- GridToolbarAddButton,
34
- GridToolbarAddIfAllowedButton,
35
- GridToolbarColumnsButton,
36
- GridToolbarContainer,
37
- GridToolbarFilterButton,
38
- GridToolbarMoreOptionsButton,
39
- GridToolbarRefreshButton,
40
- GridToolbarResetLayoutButton,
41
- } from "./Toolbar.js";
42
- import type { AfGridColumn } from "./useAfColumns.js";
43
- import { type AfGridColDef, useAfColumns } from "./useAfColumns.js";
44
- import { useAfCurrentIndex } from "./useAfCurrentIndex.js";
45
- import { useAfData } from "./useAfData.js";
46
- import { useAfFilterFields } from "./useAfFilterFields.js";
47
- import { useAfFilter } from "./useAfFilter.js";
48
- import { type AfGridApi, useAfGridApi } from "./useAfGridApi.js";
49
- import { useAfKeyBindings } from "./useAfKeyBindings.js";
50
- import { type AfGridNewItemRowPosition, NEW_ITEM_ROW_ID, isNewItemRow, useAfNewItemRow } from "./useAfNewItemRow.js";
51
- import { useAfPagination } from "./useAfPagination.js";
52
- import {
53
- isPersistedStateLoaded,
54
- type StatePersistenceConfig,
55
- settingsLoaded,
56
- useAfPersistedState,
57
- } from "./useAfPersistedState.js";
58
- import { useAfRowEditModel } from "./useAfRowEditModel.js";
59
- import { useAfRowGrouping } from "./useAfRowGrouping.js";
60
- import { useAfServerAggregation } from "./useAfServerAggregation.js";
61
- import { useAfSortModel } from "./useAfSortModel.js";
62
- import { getLocalizedString } from "../i18n.js";
63
-
64
- export * from "./formatters.js";
65
- export * from "./GridEditLookup.js";
66
- // MUI X as a namespace (`DataGrid.DataGridPremium`, `DataGrid.GridColDef`, `DataGrid.useGridApiRef`,
67
- // ...), for grids that are not bound to a data object - from here rather than the app, so a
68
- // platform app does not bundle a second MUI X. Same shape as appframe-mui exported it.
69
- export * as DataGrid from "@mui/x-data-grid-premium";
70
- export * as MuiDataGrid from "@mui/x-data-grid-premium";
71
-
72
- registerLicense();
73
-
74
- /**
75
- * The grid's criteria live in a `FilterTree` owned by `useAfFilter`, not in
76
- * MUI's filter model, so the model is always empty. It is hoisted so the
77
- * reference is stable and the grid does not treat it as a change.
78
- */
79
- const EMPTY_FILTER_MODEL: GridFilterModel = { items: [] };
80
-
81
- const privSysColumns: AfGridColDef[] = [
82
- { field: "PrimKey", editable: false, hide: true },
83
- { field: "Created", editable: false, hide: true },
84
- { field: "CreatedBy", editable: false, hide: true },
85
- { field: "Updated", editable: false, hide: true },
86
- { field: "UpdatedBy", editable: false, hide: true },
87
- ];
88
-
89
- export const systemColumns = {
90
- [Symbol.iterator]: () => privSysColumns[Symbol.iterator](),
91
- PrimKey: privSysColumns[0],
92
- Created: privSysColumns[1],
93
- CreatedBy: privSysColumns[2],
94
- Updated: privSysColumns[3],
95
- UpdatedBy: privSysColumns[4],
96
- };
97
-
98
- export type {
99
- AfExcelExportOptions,
100
- AfGridApi,
101
- AfGridColDef,
102
- AfGridColumn,
103
- AfGridExcelExportOptions,
104
- AfGridNewItemRowPosition,
105
- };
106
- export type { AfGridGrouping, AfGridGroupingProps } from "./useAfRowGrouping.js";
107
-
108
- export {
109
- AF_GRID_LOCALE_TEXT,
110
- NEW_ITEM_ROW_ID,
111
- isNewItemRow,
112
- useAfNewItemRow,
113
- AfGridColumnsPanel,
114
- AfGridExcelExportMenuItem,
115
- AfGridThemeProvider,
116
- AfGridToolbar,
117
- AfGridToolbarExcelExportButton,
118
- GridToolbarAddButton,
119
- GridToolbarAddIfAllowedButton,
120
- GridToolbarColumnsButton,
121
- GridToolbarContainer,
122
- GridToolbarFilterButton,
123
- GridToolbarMoreOptionsButton,
124
- GridToolbarRefreshButton,
125
- GridToolbarResetLayoutButton,
126
- useAfColumns,
127
- useAfCurrentIndex,
128
- useAfData,
129
- useAfFilter,
130
- useAfFilterFields,
131
- useAfPagination,
132
- useAfPersistedState,
133
- useAfRowEditModel,
134
- useAfRowGrouping,
135
- useAfServerAggregation,
136
- useAfSortModel,
137
- };
138
-
139
- export type AfGridProps = {
140
- apiRef?: React.RefObject<GridApi>;
141
- /**
142
- * If set to true, the grid will automatically load data if the data object has not
143
- * been loaded previously.
144
- */
145
- autoLoad?: boolean;
146
- /**
147
- * Bind the focused/selected row to the current index in the data object.
148
- * Useful if you want child data to load as the user navigates the grid.
149
- */
150
- bindIndex?: boolean;
151
- columns: AfGridColumn[];
152
- dataObject: DataObject<any>;
153
- /**
154
- * The layout version of the grid. Persisted grid state will only be restored
155
- * if the version matches.
156
- */
157
- layoutVersion?: string | number;
158
- /**
159
- * Field that contains a unique identifier for each row. Defaults to PrimKey.
160
- */
161
- idField?: string;
162
- /**
163
- * A function that can be used to transform the data object data before it is
164
- * passed to the grid, for example to create summary rows.
165
- */
166
- getData?: (data: Readonly<Readonly<any>[]>) => any[];
167
- /**
168
- * Override the data object permission, and remove the delete button from the default row actions
169
- */
170
- disableDelete?: boolean;
171
- /**
172
- * Override the data object permission, and remove the edit button from the default row actions
173
- */
174
- disableEdit?: boolean;
175
- /**
176
- * Override the data object permission, and remove the Add button from the default toolbar.
177
- */
178
- disableInsert?: boolean;
179
- /**
180
- * Use to disable server sorting. Required for example when using row grouping.
181
- */
182
- disableServerSorting?: boolean;
183
- /**
184
- * The view the filter builder saves and loads saved filters for, and converts
185
- * its filter against. Without it the filter builder can still be used, but
186
- * saved filters are unavailable.
187
- */
188
- filterViewName?: string;
189
- /**
190
- * Where to show the new item row: a non-data row pinned above or below the data
191
- * that reads "Click here to add a new row" until the user starts editing it.
192
- * Editing it moves the data object to the new record at index -1, and committing
193
- * it saves the record. Only shown when the data object allows insert and
194
- * `disableInsert` is not set. `none` turns it off, in which case the Add button
195
- * appends an unsaved row to the data instead. Defaults to `bottom`.
196
- */
197
- newItemRow?: AfGridNewItemRowPosition;
198
- /**
199
- * A unique ID used to persist the grid state. It is better to use the layoutVersion
200
- * property if you would like to ignore any previously persisted state, as changing
201
- * this property will leave orphan records for the previous id in the Indexed DB
202
- * grid layout store.
203
- */
204
- id: string;
205
- /**
206
- * Object almost matching the grid state, but with all values as booleans. This
207
- * is used to configure which properties should be persisted.
208
- */
209
- persistenceModel?: StatePersistenceConfig;
210
- /**
211
- * Ask the server for the grand total instead of adding up the rows the grid is holding.
212
- *
213
- * MUI aggregates over the loaded rows, which on a dynamically loading data object is the
214
- * current page — a sum that looks like a total and is not one. With this on, the grid runs a
215
- * second retrieve under the same filters that returns only the aggregates, so the footer shows
216
- * the total over the whole result. Subtotals on grouped rows stay client side, since a group
217
- * only exists over the rows the grid has.
218
- *
219
- * `sum`, `avg`, `min` and `max` are resolved server side; `size` and any custom aggregation
220
- * function stay on the client. If the view cannot be aggregated the grid falls back to the
221
- * client total rather than failing.
222
- *
223
- * Defaults to true when the data object loads dynamically, since that is exactly the case
224
- * where the client total is wrong.
225
- */
226
- serverAggregation?: boolean;
227
- } & Omit<
228
- DataGridProps,
229
- | "apiRef"
230
- | "columns"
231
- | "loading"
232
- | "rows"
233
- | "filterMode"
234
- | "filterModel"
235
- | "onFilterModelChange"
236
- | "sortingMode"
237
- | "sortModel"
238
- | "onSortModelChange"
239
- | "rowGroupingModel"
240
- | "onRowGroupingModelChange"
241
- | "aggregationModel"
242
- | "onAggregationModelChange"
243
- | "editRowsModel"
244
- | "editMode"
245
- | "onRowEditCommit"
246
- | "onEditRowsModelChange"
247
- | "pagination"
248
- | "paginationMode"
249
- | "pageSize"
250
- | "rowCount"
251
- | "rowsPerPageOptions"
252
- | "onPageChange"
253
- | "onPageSizeChange"
254
- >;
255
-
256
- // This wrapper component prevents the actual grid from rendering until the persisted state has been loaded.
257
- // This is to prevent the grid from flickering when the persisted state is restored, plus the grid sometimes
258
- // resets the column order if not provided in the initial state
259
- export function AfGrid(props: AfGridProps) {
260
- let [loaded, setLoaded] = useState(isPersistedStateLoaded());
261
-
262
- useEffect(() => {
263
- settingsLoaded.then(() => setLoaded(true));
264
- }, []);
265
-
266
- return (
267
- <AfGridThemeProvider>
268
- {loaded ? <AfGrid_ {...props} /> : <div className="AfGrid-loading">{getLocalizedString("Loading...")}</div>}
269
- </AfGridThemeProvider>
270
- );
271
- }
272
-
273
- function AfGrid_(props: AfGridProps) {
274
- let {
275
- autoLoad = false,
276
- apiRef: apiRefProp,
277
- bindIndex,
278
- columns: columnsProp,
279
- disableServerSorting,
280
- disableInsert,
281
- disableDelete,
282
- disableEdit = false,
283
- dataObject,
284
- filterViewName,
285
- layoutVersion = 0,
286
- getData,
287
- idField = "PrimKey",
288
- id,
289
- // Follows the data object's `newRecordPosition` when it has one (older data
290
- // packages have none and append), so the row sits where the record will land
291
- newItemRow: newItemRowPosition = dataObject.options?.newRecordPosition === "start" ? "top" : "bottom",
292
- persistenceModel,
293
- initialState,
294
- pinnedRows,
295
- getRowClassName,
296
- serverAggregation = dataObject.options?.dynamicLoading === true,
297
- ...other
298
- } = props;
299
- let apiRefPriv = useGridApiRef();
300
- let apiRef = apiRefProp ?? apiRefPriv;
301
- useAfGridApi(props, apiRef);
302
- let newItemRow = useAfNewItemRow({
303
- apiRef,
304
- dataObject,
305
- position: newItemRowPosition,
306
- disableInsert,
307
- pinnedRows,
308
- getRowClassName,
309
- });
310
- let initialState_ = useAfPersistedState({
311
- apiRef,
312
- columns: columnsProp,
313
- id,
314
- layoutVersion,
315
- persistenceModel,
316
- initialState,
317
- });
318
- // Seeded from the restored layout, so a persisted grouping survives a reload.
319
- let grouping = useAfRowGrouping(initialState_);
320
- let loading = useLoading(dataObject);
321
- let paging = useAfPagination(dataObject);
322
- let sortModel = useAfSortModel({
323
- dataObject,
324
- columns: columnsProp,
325
- disableServerSorting,
326
- isGrouping: grouping.isGrouping,
327
- });
328
- let gridFilter = useAfFilter({ autoLoad, dataObject, id });
329
- let [snackbar, setSnackbar] = useState<string | null>(null);
330
- let [isFilterBuilderOpen, setFilterBuilderOpen] = useState(false);
331
- let [isColumnsPanelOpen, setColumnsPanelOpen] = useState(false);
332
- let editProps = useAfRowEditModel({ dataObject, disableEdit, idField, apiRef, newItemRow });
333
- let data = useAfData(dataObject, { getData });
334
- useAfCurrentIndex({
335
- apiRef,
336
- dataObject,
337
- idField,
338
- enabled: !!bindIndex,
339
- newItemRowId: newItemRow.enabled ? NEW_ITEM_ROW_ID : null,
340
- });
341
- let columns = useAfColumns({
342
- apiRef,
343
- dataObject,
344
- disableEdit,
345
- disableDelete,
346
- columnsProp,
347
- idField,
348
- });
349
- let filterFields = useAfFilterFields(dataObject, columnsProp);
350
-
351
- let keyEvents = useAfKeyBindings({ apiRef, dataObject });
352
-
353
- useEffect(() => {
354
- if (!snackbar) return;
355
-
356
- let timeout = setTimeout(() => setSnackbar(null), 6000);
357
-
358
- return () => clearTimeout(timeout);
359
- }, [snackbar]);
360
-
361
- let serverAggregates = useAfServerAggregation(
362
- dataObject,
363
- grouping.gridProps.aggregationModel,
364
- serverAggregation && !grouping.isGrouping,
365
- );
366
-
367
- /*
368
- * The grand total row asks for `groupId === GRID_ROOT_GROUP_ID`; every other call is a group
369
- * subtotal, which only means anything over the rows the grid actually has. So the server value
370
- * is substituted for the root and the built-in function is left to do the rest.
371
- */
372
- let aggregationFunctions = useMemo(() => {
373
- if (!serverAggregation) return undefined;
374
-
375
- let base = other.aggregationFunctions ?? GRID_AGGREGATION_FUNCTIONS;
376
- let wrapped: Record<string, GridAggregationFunction> = {};
377
-
378
- for (let [name, fn] of Object.entries(base) as Array<[string, GridAggregationFunction]>) {
379
- wrapped[name] = {
380
- ...fn,
381
- apply: (params, api) => {
382
- if (params.groupId === GRID_ROOT_GROUP_ID && params.field in serverAggregates.values) {
383
- return serverAggregates.values[params.field];
384
- }
385
-
386
- return fn.apply(params, api);
387
- },
388
- };
389
- }
390
-
391
- return wrapped;
392
- }, [serverAggregation, other.aggregationFunctions, serverAggregates.values]);
393
-
394
- let gridContext = useMemo<AfGridContextValue>(
395
- () => ({
396
- id,
397
- apiRef,
398
- filterFields,
399
- ...gridFilter,
400
- openFilterBuilder: () => setFilterBuilderOpen(true),
401
- openColumnsPanel: () => setColumnsPanelOpen(true),
402
- }),
403
- [id, apiRef, filterFields, gridFilter],
404
- );
405
-
406
- return (
407
- <DataObjectProvider dataObject={dataObject}>
408
- <AfGridContextProvider value={gridContext}>
409
- <div className="AfGrid-root" style={newItemRow.rootStyle} {...keyEvents} tabIndex={-1}>
410
- <DataGrid
411
- apiRef={apiRef}
412
- rows={data}
413
- columns={columns}
414
- loading={loading}
415
- getRowId={(row) => row[uid] ?? row[idField]}
416
- {...editProps}
417
- {...newItemRow.gridProps}
418
- /*
419
- * Our own filter tree drives the data object, so MUI never
420
- * sees a filter model. The header filter cells are replaced
421
- * by `renderHeaderFilter` on every column.
422
- */
423
- filterMode="server"
424
- filterModel={EMPTY_FILTER_MODEL}
425
- {...sortModel}
426
- headerFilters
427
- {...paging}
428
- showToolbar
429
- {...other}
430
- {...grouping.gridProps}
431
- /*
432
- * Spread rather than passed, because the grid merges props over its defaults with
433
- * `Object.assign`: an explicit `undefined` here would wipe out the built-in
434
- * aggregation functions rather than fall back to them, and the grid then throws on
435
- * the first render.
436
- */
437
- {...(aggregationFunctions ? { aggregationFunctions } : {})}
438
- localeText={{ ...other.localeText, ...AF_GRID_LOCALE_TEXT }}
439
- slots={{
440
- ...dsBaseSlots,
441
- toolbar: () => <AfGridToolbar disableInsert={disableInsert} />,
442
- ...other.slots,
443
- }}
444
- initialState={initialState_}
445
- slotProps={{
446
- ...other.slotProps,
447
- headerFilterCell: {
448
- ...other.slotProps?.headerFilterCell,
449
- headerClassName: clsx(other.slotProps?.headerFilterCell?.headerClassName, "AfGrid-headerCell"),
450
- showRightBorder: true,
451
- },
452
- }}
453
- onProcessRowUpdateError={(error) => {
454
- other.onProcessRowUpdateError?.(error);
455
- let message = "message" in (error as any) ? (error as any).message : String(error);
456
- setSnackbar(message);
457
- }}
458
- />
459
- </div>
460
- <AfGridError apiRef={apiRef} dataObject={dataObject} />
461
- {isColumnsPanelOpen && <AfGridColumnsPanel open onClose={() => setColumnsPanelOpen(false)} />}
462
- <FilterBuilder
463
- variant="dialog"
464
- open={isFilterBuilderOpen}
465
- onClose={() => setFilterBuilderOpen(false)}
466
- fields={filterFields}
467
- viewName={filterViewName}
468
- dataObject={dataObject}
469
- /*
470
- * The builder edits the same named slot and the same tree as the
471
- * header filters, so a criterion added in one shows up in the
472
- * other. Applying writes the slot directly, which skips the
473
- * header filters' debounce.
474
- */
475
- slotName={id}
476
- value={gridFilter.filter}
477
- onChange={gridFilter.setFilter}
478
- />
479
- {!!snackbar && (
480
- <div className="AfGrid-snackbar">
481
- <Alert data-color="danger" data-size="sm" onClick={() => setSnackbar(null)}>
482
- {snackbar}
483
- </Alert>
484
- </div>
485
- )}
486
- </AfGridContextProvider>
487
- </DataObjectProvider>
488
- );
489
- }
1
+ import "./index.css";
2
+
3
+ import { Alert } from "@digdir/designsystemet-react";
4
+ import {
5
+ DataGridPremium as DataGrid,
6
+ type DataGridPremiumProps as DataGridProps,
7
+ GRID_AGGREGATION_FUNCTIONS,
8
+ GRID_ROOT_GROUP_ID,
9
+ type GridAggregationFunction,
10
+ type GridApi,
11
+ type GridFilterModel,
12
+ type GridGroupNode,
13
+ useGridApiRef,
14
+ } from "@mui/x-data-grid-premium";
15
+ import { type DataObject, uid } from "@olenbetong/appframe-data";
16
+ import { DataObjectProvider, useLoading } from "@olenbetong/appframe-react";
17
+
18
+ import clsx from "clsx";
19
+ import { useEffect, useMemo, useState } from "react";
20
+ import { FilterBuilder } from "../filter/index.js";
21
+ import { AfGridColumnsPanel } from "./AfGridColumnsPanel.js";
22
+ import { type AfGridContextValue, AfGridContextProvider } from "./AfGridContext.js";
23
+ import { AfGridError } from "./AfGridError.js";
24
+ import { registerLicense } from "./license.js";
25
+ import { AF_GRID_LOCALE_TEXT } from "./localization.js";
26
+ import { dsBaseSlots } from "./slots/index.js";
27
+ import { AfGridThemeProvider } from "./theme.js";
28
+ import {
29
+ type AfExcelExportOptions,
30
+ AfGridExcelExportMenuItem,
31
+ type AfGridExcelExportOptions,
32
+ AfGridToolbar,
33
+ AfGridToolbarExcelExportButton,
34
+ GridToolbarAddButton,
35
+ GridToolbarAddIfAllowedButton,
36
+ GridToolbarColumnsButton,
37
+ GridToolbarContainer,
38
+ GridToolbarFilterButton,
39
+ GridToolbarMoreOptionsButton,
40
+ GridToolbarRefreshButton,
41
+ GridToolbarResetLayoutButton,
42
+ } from "./Toolbar.js";
43
+ import type { AfGridColumn } from "./useAfColumns.js";
44
+ import { type AfGridColDef, useAfColumns } from "./useAfColumns.js";
45
+ import { useAfCurrentIndex } from "./useAfCurrentIndex.js";
46
+ import { useAfData } from "./useAfData.js";
47
+ import { useAfFilterFields } from "./useAfFilterFields.js";
48
+ import { useAfFilter } from "./useAfFilter.js";
49
+ import { type AfGridApi, useAfGridApi } from "./useAfGridApi.js";
50
+ import { useAfKeyBindings } from "./useAfKeyBindings.js";
51
+ import { type AfGridNewItemRowPosition, NEW_ITEM_ROW_ID, isNewItemRow, useAfNewItemRow } from "./useAfNewItemRow.js";
52
+ import { useAfPagination } from "./useAfPagination.js";
53
+ import {
54
+ isPersistedStateLoaded,
55
+ type StatePersistenceConfig,
56
+ settingsLoaded,
57
+ useAfPersistedState,
58
+ } from "./useAfPersistedState.js";
59
+ import { useAfRowEditModel } from "./useAfRowEditModel.js";
60
+ import { useAfRowGrouping } from "./useAfRowGrouping.js";
61
+ import { useAfServerAggregation } from "./useAfServerAggregation.js";
62
+ import { useAfSortModel } from "./useAfSortModel.js";
63
+ import { getLocalizedString } from "../i18n.js";
64
+
65
+ export * from "./formatters.js";
66
+ export * from "./GridEditLookup.js";
67
+ // MUI X as a namespace (`DataGrid.DataGridPremium`, `DataGrid.GridColDef`, `DataGrid.useGridApiRef`,
68
+ // ...), for grids that are not bound to a data object - from here rather than the app, so a
69
+ // platform app does not bundle a second MUI X. Same shape as appframe-mui exported it.
70
+ export * as DataGrid from "@mui/x-data-grid-premium";
71
+ export * as MuiDataGrid from "@mui/x-data-grid-premium";
72
+
73
+ registerLicense();
74
+
75
+ /**
76
+ * The grid's criteria live in a `FilterTree` owned by `useAfFilter`, not in
77
+ * MUI's filter model, so the model is always empty. It is hoisted so the
78
+ * reference is stable and the grid does not treat it as a change.
79
+ */
80
+ const EMPTY_FILTER_MODEL: GridFilterModel = { items: [] };
81
+
82
+ const privSysColumns: AfGridColDef[] = [
83
+ { field: "PrimKey", editable: false, hide: true },
84
+ { field: "Created", editable: false, hide: true },
85
+ { field: "CreatedBy", editable: false, hide: true },
86
+ { field: "Updated", editable: false, hide: true },
87
+ { field: "UpdatedBy", editable: false, hide: true },
88
+ ];
89
+
90
+ export const systemColumns = {
91
+ [Symbol.iterator]: () => privSysColumns[Symbol.iterator](),
92
+ PrimKey: privSysColumns[0],
93
+ Created: privSysColumns[1],
94
+ CreatedBy: privSysColumns[2],
95
+ Updated: privSysColumns[3],
96
+ UpdatedBy: privSysColumns[4],
97
+ };
98
+
99
+ export type {
100
+ AfExcelExportOptions,
101
+ AfGridApi,
102
+ AfGridColDef,
103
+ AfGridColumn,
104
+ AfGridExcelExportOptions,
105
+ AfGridNewItemRowPosition,
106
+ };
107
+ export type { AfGridGrouping, AfGridGroupingProps } from "./useAfRowGrouping.js";
108
+
109
+ export {
110
+ AF_GRID_LOCALE_TEXT,
111
+ NEW_ITEM_ROW_ID,
112
+ isNewItemRow,
113
+ useAfNewItemRow,
114
+ AfGridColumnsPanel,
115
+ AfGridExcelExportMenuItem,
116
+ AfGridThemeProvider,
117
+ AfGridToolbar,
118
+ AfGridToolbarExcelExportButton,
119
+ GridToolbarAddButton,
120
+ GridToolbarAddIfAllowedButton,
121
+ GridToolbarColumnsButton,
122
+ GridToolbarContainer,
123
+ GridToolbarFilterButton,
124
+ GridToolbarMoreOptionsButton,
125
+ GridToolbarRefreshButton,
126
+ GridToolbarResetLayoutButton,
127
+ useAfColumns,
128
+ useAfCurrentIndex,
129
+ useAfData,
130
+ useAfFilter,
131
+ useAfFilterFields,
132
+ useAfPagination,
133
+ useAfPersistedState,
134
+ useAfRowEditModel,
135
+ useAfRowGrouping,
136
+ useAfServerAggregation,
137
+ useAfSortModel,
138
+ };
139
+
140
+ /** MUI's own default: the grand total in the footer, group subtotals inline. */
141
+ function getDefaultAggregationPosition(groupNode: GridGroupNode) {
142
+ return groupNode.depth === -1 ? "footer" : "inline";
143
+ }
144
+
145
+ export type AfGridProps = {
146
+ apiRef?: React.RefObject<GridApi>;
147
+ /**
148
+ * If set to true, the grid will automatically load data if the data object has not
149
+ * been loaded previously.
150
+ */
151
+ autoLoad?: boolean;
152
+ /**
153
+ * Bind the focused/selected row to the current index in the data object.
154
+ * Useful if you want child data to load as the user navigates the grid.
155
+ */
156
+ bindIndex?: boolean;
157
+ columns: AfGridColumn[];
158
+ dataObject: DataObject<any>;
159
+ /**
160
+ * The layout version of the grid. Persisted grid state will only be restored
161
+ * if the version matches.
162
+ */
163
+ layoutVersion?: string | number;
164
+ /**
165
+ * Field that contains a unique identifier for each row. Defaults to PrimKey.
166
+ */
167
+ idField?: string;
168
+ /**
169
+ * A function that can be used to transform the data object data before it is
170
+ * passed to the grid, for example to create summary rows.
171
+ */
172
+ getData?: (data: Readonly<Readonly<any>[]>) => any[];
173
+ /**
174
+ * Override the data object permission, and remove the delete button from the default row actions
175
+ */
176
+ disableDelete?: boolean;
177
+ /**
178
+ * Override the data object permission, and remove the edit button from the default row actions
179
+ */
180
+ disableEdit?: boolean;
181
+ /**
182
+ * Override the data object permission, and remove the Add button from the default toolbar.
183
+ */
184
+ disableInsert?: boolean;
185
+ /**
186
+ * Use to disable server sorting. Required for example when using row grouping.
187
+ */
188
+ disableServerSorting?: boolean;
189
+ /**
190
+ * The view the filter builder saves and loads saved filters for, and converts
191
+ * its filter against. Without it the filter builder can still be used, but
192
+ * saved filters are unavailable.
193
+ */
194
+ filterViewName?: string;
195
+ /**
196
+ * Where to show the new item row: a non-data row pinned above or below the data
197
+ * that reads "Click here to add a new row" until the user starts editing it.
198
+ * Editing it moves the data object to the new record at index -1, and committing
199
+ * it saves the record. Only shown when the data object allows insert and
200
+ * `disableInsert` is not set. `none` turns it off, in which case the Add button
201
+ * appends an unsaved row to the data instead. Defaults to `bottom`.
202
+ */
203
+ newItemRow?: AfGridNewItemRowPosition;
204
+ /**
205
+ * A unique ID used to persist the grid state. It is better to use the layoutVersion
206
+ * property if you would like to ignore any previously persisted state, as changing
207
+ * this property will leave orphan records for the previous id in the Indexed DB
208
+ * grid layout store.
209
+ */
210
+ id: string;
211
+ /**
212
+ * Object almost matching the grid state, but with all values as booleans. This
213
+ * is used to configure which properties should be persisted.
214
+ */
215
+ persistenceModel?: StatePersistenceConfig;
216
+ /**
217
+ * Ask the server for the grand total instead of adding up the rows the grid is holding.
218
+ *
219
+ * MUI aggregates over the loaded rows, which on a dynamically loading data object is the
220
+ * current page — a sum that looks like a total and is not one. With this on, the grid runs a
221
+ * second retrieve under the same filters that returns only the aggregates, so the footer shows
222
+ * the total over the whole result. Subtotals on grouped rows stay client side, since a group
223
+ * only exists over the rows the grid has.
224
+ *
225
+ * `sum`, `avg`, `min` and `max` are resolved server side; `size` and any custom aggregation
226
+ * function stay on the client. If the view cannot be aggregated the grid falls back to the
227
+ * client total rather than failing.
228
+ *
229
+ * Defaults to true when the data object loads dynamically, since that is exactly the case
230
+ * where the client total is wrong.
231
+ */
232
+ serverAggregation?: boolean;
233
+ } & Omit<
234
+ DataGridProps,
235
+ | "apiRef"
236
+ | "columns"
237
+ | "loading"
238
+ | "rows"
239
+ | "filterMode"
240
+ | "filterModel"
241
+ | "onFilterModelChange"
242
+ | "sortingMode"
243
+ | "sortModel"
244
+ | "onSortModelChange"
245
+ | "rowGroupingModel"
246
+ | "onRowGroupingModelChange"
247
+ | "aggregationModel"
248
+ | "onAggregationModelChange"
249
+ | "editRowsModel"
250
+ | "editMode"
251
+ | "onRowEditCommit"
252
+ | "onEditRowsModelChange"
253
+ | "pagination"
254
+ | "paginationMode"
255
+ | "pageSize"
256
+ | "rowCount"
257
+ | "rowsPerPageOptions"
258
+ | "onPageChange"
259
+ | "onPageSizeChange"
260
+ >;
261
+
262
+ // This wrapper component prevents the actual grid from rendering until the persisted state has been loaded.
263
+ // This is to prevent the grid from flickering when the persisted state is restored, plus the grid sometimes
264
+ // resets the column order if not provided in the initial state
265
+ export function AfGrid(props: AfGridProps) {
266
+ let [loaded, setLoaded] = useState(isPersistedStateLoaded());
267
+
268
+ useEffect(() => {
269
+ settingsLoaded.then(() => setLoaded(true));
270
+ }, []);
271
+
272
+ return (
273
+ <AfGridThemeProvider>
274
+ {loaded ? <AfGrid_ {...props} /> : <div className="AfGrid-loading">{getLocalizedString("Loading...")}</div>}
275
+ </AfGridThemeProvider>
276
+ );
277
+ }
278
+
279
+ function AfGrid_(props: AfGridProps) {
280
+ let {
281
+ autoLoad = false,
282
+ apiRef: apiRefProp,
283
+ bindIndex,
284
+ columns: columnsProp,
285
+ disableServerSorting,
286
+ disableInsert,
287
+ disableDelete,
288
+ disableEdit = false,
289
+ dataObject,
290
+ filterViewName,
291
+ layoutVersion = 0,
292
+ getData,
293
+ idField = "PrimKey",
294
+ id,
295
+ // Follows the data object's `newRecordPosition` when it has one (older data
296
+ // packages have none and append), so the row sits where the record will land
297
+ newItemRow: newItemRowPosition = dataObject.options?.newRecordPosition === "start" ? "top" : "bottom",
298
+ persistenceModel,
299
+ initialState,
300
+ pinnedRows,
301
+ getRowClassName,
302
+ serverAggregation = dataObject.options?.dynamicLoading === true,
303
+ ...other
304
+ } = props;
305
+ let apiRefPriv = useGridApiRef();
306
+ let apiRef = apiRefProp ?? apiRefPriv;
307
+ useAfGridApi(props, apiRef);
308
+ let newItemRow = useAfNewItemRow({
309
+ apiRef,
310
+ dataObject,
311
+ position: newItemRowPosition,
312
+ disableInsert,
313
+ pinnedRows,
314
+ getRowClassName,
315
+ });
316
+ let initialState_ = useAfPersistedState({
317
+ apiRef,
318
+ columns: columnsProp,
319
+ id,
320
+ layoutVersion,
321
+ persistenceModel,
322
+ initialState,
323
+ });
324
+ // Seeded from the restored layout, so a persisted grouping survives a reload.
325
+ let grouping = useAfRowGrouping(initialState_);
326
+ let loading = useLoading(dataObject);
327
+ let paging = useAfPagination(dataObject);
328
+ let sortModel = useAfSortModel({
329
+ dataObject,
330
+ columns: columnsProp,
331
+ disableServerSorting,
332
+ isGrouping: grouping.isGrouping,
333
+ });
334
+ let gridFilter = useAfFilter({ autoLoad, dataObject, id });
335
+ let [snackbar, setSnackbar] = useState<string | null>(null);
336
+ let [isFilterBuilderOpen, setFilterBuilderOpen] = useState(false);
337
+ let [isColumnsPanelOpen, setColumnsPanelOpen] = useState(false);
338
+ let editProps = useAfRowEditModel({ dataObject, disableEdit, idField, apiRef, newItemRow });
339
+ let data = useAfData(dataObject, { getData });
340
+ useAfCurrentIndex({
341
+ apiRef,
342
+ dataObject,
343
+ idField,
344
+ enabled: !!bindIndex,
345
+ newItemRowId: newItemRow.enabled ? NEW_ITEM_ROW_ID : null,
346
+ });
347
+ let columns = useAfColumns({
348
+ apiRef,
349
+ dataObject,
350
+ disableEdit,
351
+ disableDelete,
352
+ columnsProp,
353
+ idField,
354
+ });
355
+ let filterFields = useAfFilterFields(dataObject, columnsProp);
356
+
357
+ let keyEvents = useAfKeyBindings({ apiRef, dataObject });
358
+
359
+ useEffect(() => {
360
+ if (!snackbar) return;
361
+
362
+ let timeout = setTimeout(() => setSnackbar(null), 6000);
363
+
364
+ return () => clearTimeout(timeout);
365
+ }, [snackbar]);
366
+
367
+ let serverAggregates = useAfServerAggregation(
368
+ dataObject,
369
+ grouping.gridProps.aggregationModel,
370
+ serverAggregation && !grouping.isGrouping,
371
+ );
372
+
373
+ /*
374
+ * The grand total row asks for `groupId === GRID_ROOT_GROUP_ID`; every other call is a group
375
+ * subtotal, which only means anything over the rows the grid actually has. So the server value
376
+ * is substituted for the root and the built-in function is left to do the rest.
377
+ */
378
+ let aggregationFunctions = useMemo(() => {
379
+ if (!serverAggregation) return undefined;
380
+
381
+ let base = other.aggregationFunctions ?? GRID_AGGREGATION_FUNCTIONS;
382
+ let wrapped: Record<string, GridAggregationFunction> = {};
383
+
384
+ for (let [name, fn] of Object.entries(base) as Array<[string, GridAggregationFunction]>) {
385
+ wrapped[name] = {
386
+ ...fn,
387
+ apply: (params, api) => {
388
+ if (params.groupId === GRID_ROOT_GROUP_ID && params.field in serverAggregates.values) {
389
+ return serverAggregates.values[params.field];
390
+ }
391
+
392
+ return fn.apply(params, api);
393
+ },
394
+ };
395
+ }
396
+
397
+ return wrapped;
398
+ }, [serverAggregation, other.aggregationFunctions, serverAggregates.values]);
399
+
400
+ let gridContext = useMemo<AfGridContextValue>(
401
+ () => ({
402
+ id,
403
+ apiRef,
404
+ filterFields,
405
+ ...gridFilter,
406
+ openFilterBuilder: () => setFilterBuilderOpen(true),
407
+ openColumnsPanel: () => setColumnsPanelOpen(true),
408
+ }),
409
+ [id, apiRef, filterFields, gridFilter],
410
+ );
411
+
412
+ return (
413
+ <DataObjectProvider dataObject={dataObject}>
414
+ <AfGridContextProvider value={gridContext}>
415
+ <div className="AfGrid-root" style={newItemRow.rootStyle} {...keyEvents} tabIndex={-1}>
416
+ <DataGrid
417
+ apiRef={apiRef}
418
+ rows={data}
419
+ columns={columns}
420
+ loading={loading}
421
+ getRowId={(row) => row[uid] ?? row[idField]}
422
+ {...editProps}
423
+ {...newItemRow.gridProps}
424
+ /*
425
+ * Our own filter tree drives the data object, so MUI never
426
+ * sees a filter model. The header filter cells are replaced
427
+ * by `renderHeaderFilter` on every column.
428
+ */
429
+ filterMode="server"
430
+ filterModel={EMPTY_FILTER_MODEL}
431
+ {...sortModel}
432
+ headerFilters
433
+ {...paging}
434
+ showToolbar
435
+ {...other}
436
+ {...grouping.gridProps}
437
+ /*
438
+ * Spread rather than passed, because the grid merges props over its defaults with
439
+ * `Object.assign`: an explicit `undefined` here would wipe out the built-in
440
+ * aggregation functions rather than fall back to them, and the grid then throws on
441
+ * the first render.
442
+ */
443
+ {...(aggregationFunctions ? { aggregationFunctions } : {})}
444
+ /*
445
+ * The server totals arrive after the rows, as new aggregation functions, and the
446
+ * grid only re-aggregates on row, column and model changes: without a nudge the
447
+ * footer kept the totals of the previous load. Its one effect that re-aggregates
448
+ * when the functions change is keyed on this prop and skipped while it is unset,
449
+ * so the default position is passed explicitly.
450
+ */
451
+ getAggregationPosition={other.getAggregationPosition ?? getDefaultAggregationPosition}
452
+ localeText={{ ...other.localeText, ...AF_GRID_LOCALE_TEXT }}
453
+ slots={{
454
+ ...dsBaseSlots,
455
+ toolbar: () => <AfGridToolbar disableInsert={disableInsert} />,
456
+ ...other.slots,
457
+ }}
458
+ initialState={initialState_}
459
+ slotProps={{
460
+ ...other.slotProps,
461
+ headerFilterCell: {
462
+ ...other.slotProps?.headerFilterCell,
463
+ headerClassName: clsx(other.slotProps?.headerFilterCell?.headerClassName, "AfGrid-headerCell"),
464
+ showRightBorder: true,
465
+ },
466
+ }}
467
+ onProcessRowUpdateError={(error) => {
468
+ other.onProcessRowUpdateError?.(error);
469
+ let message = "message" in (error as any) ? (error as any).message : String(error);
470
+ setSnackbar(message);
471
+ }}
472
+ />
473
+ </div>
474
+ <AfGridError apiRef={apiRef} dataObject={dataObject} />
475
+ {isColumnsPanelOpen && <AfGridColumnsPanel open onClose={() => setColumnsPanelOpen(false)} />}
476
+ <FilterBuilder
477
+ variant="dialog"
478
+ open={isFilterBuilderOpen}
479
+ onClose={() => setFilterBuilderOpen(false)}
480
+ fields={filterFields}
481
+ viewName={filterViewName}
482
+ dataObject={dataObject}
483
+ /*
484
+ * The builder edits the same named slot and the same tree as the
485
+ * header filters, so a criterion added in one shows up in the
486
+ * other. Applying writes the slot directly, which skips the
487
+ * header filters' debounce.
488
+ */
489
+ slotName={id}
490
+ value={gridFilter.filter}
491
+ onChange={gridFilter.setFilter}
492
+ />
493
+ {!!snackbar && (
494
+ <div className="AfGrid-snackbar">
495
+ <Alert data-color="danger" data-size="sm" onClick={() => setSnackbar(null)}>
496
+ {snackbar}
497
+ </Alert>
498
+ </div>
499
+ )}
500
+ </AfGridContextProvider>
501
+ </DataObjectProvider>
502
+ );
503
+ }