@etsoo/react 1.4.20 → 1.4.21

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,14 +1,23 @@
1
1
  import { Box, Stack } from '@mui/material';
2
2
  import React from 'react';
3
+ import { Labels } from '../app/Labels';
3
4
  import { GridDataGet } from '../components/GridLoader';
4
5
  import useCombinedRefs from '../uses/useCombinedRefs';
5
6
  import { useDimensions } from '../uses/useDimensions';
6
7
  import { DataGridEx, DataGridExCalColumns } from './DataGridEx';
8
+ import { PullToRefreshUI } from './PullToRefreshUI';
7
9
  import { ScrollerListEx } from './ScrollerListEx';
8
10
  import { SearchBar } from './SearchBar';
11
+ /**
12
+ * Responsible container
13
+ * @param props Props
14
+ * @returns Layout
15
+ */
9
16
  export function ResponsibleContainer(props) {
10
17
  // Destruct
11
- const { adjustHeight, columns, dataGridMinWidth = DataGridExCalColumns(columns).total, fields, fieldTemplate, height, listBoxSx, loadData, mRef, searchBoxSx, sizeReadyMiliseconds = 0, ...rest } = props;
18
+ const { adjustHeight, columns, dataGridMinWidth = DataGridExCalColumns(columns).total, fields, fieldTemplate, height, listBoxSx, loadData, mRef, pullToRefresh = true, searchBoxSx, sizeReadyMiliseconds = 0, ...rest } = props;
19
+ // Labels
20
+ const labels = Labels.CommonPage;
12
21
  // Refs
13
22
  const refs = React.useRef({});
14
23
  const mRefs = useCombinedRefs(mRef, (ref) => {
@@ -16,20 +25,55 @@ export function ResponsibleContainer(props) {
16
25
  return;
17
26
  refs.current.ref = ref;
18
27
  });
28
+ // Update mounted state
29
+ React.useEffect(() => {
30
+ return () => {
31
+ refs.current.mounted = false;
32
+ };
33
+ }, []);
19
34
  // Has fields
20
35
  const hasFields = fields != null && fields.length > 0;
21
- // Watch container
22
- const { dimensions } = useDimensions(1, undefined, sizeReadyMiliseconds);
23
- const rect = dimensions[0][2];
36
+ // Load data
24
37
  const localLoadData = (props) => {
38
+ refs.current.mounted = true;
25
39
  const data = GridDataGet(props, fieldTemplate);
26
40
  return loadData(data);
27
41
  };
28
- const list = React.useMemo(() => {
29
- // No rect
30
- if (rect == null)
42
+ // On submit callback
43
+ const onSubmit = (data, _reset) => {
44
+ if (data == null || rect == null || refs.current.ref == null)
31
45
  return;
32
- // If height is the same
46
+ refs.current.ref.reset({ data });
47
+ };
48
+ // Watch container
49
+ const { dimensions } = useDimensions(1, undefined, sizeReadyMiliseconds, (_preRect, rect) => {
50
+ // Check
51
+ if (rect == null)
52
+ return true;
53
+ // Last rect
54
+ const lastRect = refs.current.rect;
55
+ // 32 = scroll bar width
56
+ if (lastRect != null &&
57
+ refs.current.mounted !== true &&
58
+ Math.abs(rect.width - lastRect.width) <= 32 &&
59
+ Math.abs(rect.height - lastRect.height) <= 32)
60
+ return true;
61
+ // Hold the new rect
62
+ refs.current.rect = rect;
63
+ return false;
64
+ });
65
+ // Rect
66
+ const rect = dimensions[0][2];
67
+ // Create list
68
+ const [list, showDataGrid] = (() => {
69
+ // No layout
70
+ if (rect == null)
71
+ return [null, false];
72
+ // Width
73
+ const width = rect.width;
74
+ // Show DataGrid or List dependng on width
75
+ const showDataGrid = width >= dataGridMinWidth;
76
+ // Height
33
77
  let heightLocal;
34
78
  if (height != null) {
35
79
  heightLocal = height;
@@ -46,18 +90,14 @@ export function ResponsibleContainer(props) {
46
90
  heightLocal -= adjustHeight(heightLocal);
47
91
  }
48
92
  }
49
- console.log(rect, heightLocal, refs.current.height);
50
- // Same height
51
- if (heightLocal === refs.current.height)
52
- return;
53
- refs.current.height = heightLocal;
54
- // Show DataGrid or List dependng on width
55
- const showDataGrid = rect.width >= dataGridMinWidth;
56
93
  if (showDataGrid) {
57
94
  // Delete
58
95
  delete rest.itemRenderer;
59
- return (React.createElement(Box, { sx: listBoxSx == null ? undefined : listBoxSx(true) },
60
- React.createElement(DataGridEx, { autoLoad: !hasFields, height: heightLocal, loadData: localLoadData, mRef: mRefs, columns: columns, ...rest })));
96
+ return [
97
+ React.createElement(Box, { sx: listBoxSx == null ? undefined : listBoxSx(true) },
98
+ React.createElement(DataGridEx, { autoLoad: !hasFields, height: heightLocal, width: rect.width, loadData: localLoadData, mRef: mRefs, columns: columns, ...rest })),
99
+ true
100
+ ];
61
101
  }
62
102
  // Delete
63
103
  delete rest.checkable;
@@ -68,18 +108,25 @@ export function ResponsibleContainer(props) {
68
108
  delete rest.hideFooter;
69
109
  delete rest.hoverColor;
70
110
  delete rest.selectable;
71
- return (React.createElement(Box, { sx: listBoxSx == null ? undefined : listBoxSx(false) },
72
- React.createElement(ScrollerListEx, { autoLoad: !hasFields, height: heightLocal, loadData: localLoadData, mRef: mRefs, ...rest })));
73
- }, [rect, height]);
74
- // On submit callback
75
- const onSubmit = (data, _reset) => {
76
- if (data == null || rect == null || refs.current.ref == null)
77
- return;
78
- refs.current.ref.reset({ data });
79
- };
111
+ return [
112
+ React.createElement(Box, { sx: listBoxSx == null ? undefined : listBoxSx(false) },
113
+ React.createElement(ScrollerListEx, { autoLoad: !hasFields, width: rect.width, height: heightLocal, loadData: localLoadData, mRef: mRefs, ...rest })),
114
+ false
115
+ ];
116
+ })();
117
+ // Pull container
118
+ const pullContainer = list
119
+ ? showDataGrid
120
+ ? '.DataGridEx-Body'
121
+ : '.ScrollerListEx-Body'
122
+ : undefined;
80
123
  // Layout
81
- return (React.createElement(Stack, { ref: hasFields ? undefined : dimensions[0][0] },
82
- hasFields && (React.createElement(Box, { ref: dimensions[0][0], sx: searchBoxSx },
83
- React.createElement(SearchBar, { fields: fields, onSubmit: onSubmit }))),
84
- list));
124
+ return (React.createElement(React.Fragment, null,
125
+ React.createElement(Stack, null,
126
+ React.createElement(Box, { ref: dimensions[0][0], sx: searchBoxSx }, hasFields && (React.createElement(SearchBar, { fields: fields, onSubmit: onSubmit }))),
127
+ list),
128
+ pullToRefresh && pullContainer && (React.createElement(PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: () => { var _a; return (_a = refs.current.ref) === null || _a === void 0 ? void 0 : _a.reset(); }, shouldPullToRefresh: () => {
129
+ const container = document.querySelector(pullContainer);
130
+ return !(container === null || container === void 0 ? void 0 : container.scrollTop);
131
+ } }))));
85
132
  }
@@ -66,8 +66,7 @@ export function ScrollerListEx(props) {
66
66
  const [selectedDiv, selectedData] = (_a = selectedItem.current) !== null && _a !== void 0 ? _a : [];
67
67
  if (selectedData != null && selectedData[idField] === data[idField])
68
68
  return;
69
- if (selectedDiv != null)
70
- selectedDiv.classList.remove(selectedClassName);
69
+ selectedDiv === null || selectedDiv === void 0 ? void 0 : selectedDiv.classList.remove(selectedClassName);
71
70
  div.classList.add(selectedClassName);
72
71
  selectedItem.current = [div, data];
73
72
  if (onSelectChange)
package/lib/mu/TableEx.js CHANGED
@@ -34,35 +34,39 @@ export function TableEx(props) {
34
34
  else {
35
35
  rowsPerPageLocal = 10;
36
36
  }
37
+ // Rows
38
+ const [rows, updateRows] = React.useState([]);
39
+ const setRows = (rows) => {
40
+ state.loadedItems = rows.length;
41
+ updateRows(rows);
42
+ };
37
43
  // States
38
- const [state, stateUpdate] = React.useReducer((currentState, newState) => {
39
- return { ...currentState, ...newState };
40
- }, {
44
+ const stateRefs = React.useRef({
41
45
  autoLoad,
42
46
  currentPage: 0,
47
+ loadedItems: 0,
43
48
  hasNextPage: true,
44
49
  isNextPageLoading: false,
45
50
  orderBy: defaultOrderBy,
46
51
  orderByAsc: defaultOrderBy
47
52
  ? (_a = columns.find((column) => column.field === defaultOrderBy)) === null || _a === void 0 ? void 0 : _a.sortAsc
48
53
  : undefined,
49
- rows: [],
50
54
  batchSize: rowsPerPageLocal,
51
55
  selectedItems: []
52
56
  });
53
- const isMounted = React.useRef(true);
57
+ const state = stateRefs.current;
54
58
  // Reset the state and load again
55
59
  const reset = (add) => {
56
- const state = {
60
+ const resetState = {
57
61
  autoLoad: true,
58
62
  currentPage: 0,
63
+ loadedItems: 0,
59
64
  hasNextPage: true,
60
65
  isNextPageLoading: false,
61
66
  lastLoadedItems: undefined,
62
- rows: [],
63
67
  ...add
64
68
  };
65
- stateUpdate(state);
69
+ Object.assign(state, resetState);
66
70
  };
67
71
  React.useImperativeHandle(mRef, () => ({
68
72
  /**
@@ -84,7 +88,7 @@ export function TableEx(props) {
84
88
  // Update state
85
89
  state.isNextPageLoading = true;
86
90
  // Parameters
87
- const { currentPage, batchSize, orderBy, orderByAsc, data } = state;
91
+ const { currentPage, batchSize, orderBy, orderByAsc, data, isMounted } = state;
88
92
  const loadProps = {
89
93
  currentPage,
90
94
  batchSize,
@@ -93,16 +97,16 @@ export function TableEx(props) {
93
97
  data
94
98
  };
95
99
  loadData(loadProps).then((result) => {
96
- if (!isMounted.current || result == null) {
100
+ state.isMounted = true;
101
+ if (result == null || isMounted === false) {
97
102
  return;
98
103
  }
99
104
  const newItems = result.length;
100
- stateUpdate({
101
- rows: result,
102
- lastLoadedItems: newItems,
103
- hasNextPage: newItems >= batchSize,
104
- isNextPageLoading: false
105
- });
105
+ state.lastLoadedItems = newItems;
106
+ state.hasNextPage = newItems >= batchSize;
107
+ state.isNextPageLoading = false;
108
+ // Update rows
109
+ setRows(result);
106
110
  });
107
111
  };
108
112
  const handleChangePage = (_event, newPage) => {
@@ -111,11 +115,11 @@ export function TableEx(props) {
111
115
  loadDataLocal();
112
116
  };
113
117
  const handleChangeRowsPerPage = (event) => {
114
- const rowsPerPage = parseInt(event.target.value);
115
- reset({ rowsPerPage });
118
+ const batchSize = parseInt(event.target.value);
119
+ reset({ batchSize });
116
120
  };
117
121
  const handleSelect = (item, checked) => {
118
- const selectedItems = [...state.selectedItems];
122
+ const selectedItems = state.selectedItems;
119
123
  const index = selectedItems.findIndex((selectedItem) => selectedItem[idField] === item[idField]);
120
124
  if (checked) {
121
125
  if (index === -1)
@@ -128,11 +132,10 @@ export function TableEx(props) {
128
132
  if (onSelectChange != null) {
129
133
  onSelectChange(selectedItems);
130
134
  }
131
- stateUpdate({ selectedItems });
132
135
  };
133
136
  const handleSelectAll = (checked) => {
134
- const selectedItems = [...state.selectedItems];
135
- state.rows.forEach((row) => {
137
+ const selectedItems = state.selectedItems;
138
+ rows.forEach((row) => {
136
139
  const index = selectedItems.findIndex((selectedItem) => selectedItem[idField] === row[idField]);
137
140
  if (checked) {
138
141
  if (index === -1)
@@ -145,14 +148,13 @@ export function TableEx(props) {
145
148
  if (onSelectChange != null) {
146
149
  onSelectChange(selectedItems);
147
150
  }
148
- stateUpdate({ selectedItems });
149
151
  };
150
152
  // New sort
151
153
  const handleSort = (field, asc) => {
152
154
  reset({ orderBy: field, orderByAsc: asc });
153
155
  };
154
156
  // Destruct states
155
- const { autoLoad: stateAutoLoad, currentPage, hasNextPage, lastLoadedItems, orderBy, rows, batchSize, selectedItems } = state;
157
+ const { autoLoad: stateAutoLoad, currentPage, hasNextPage, lastLoadedItems, orderBy, batchSize, selectedItems } = state;
156
158
  // Current page selected items
157
159
  const pageSelectedItems = selectable
158
160
  ? rows.reduce((previousValue, currentItem) => {
@@ -170,7 +172,7 @@ export function TableEx(props) {
170
172
  loadDataLocal();
171
173
  React.useEffect(() => {
172
174
  return () => {
173
- isMounted.current = false;
175
+ state.isMounted = false;
174
176
  };
175
177
  }, []);
176
178
  // Layout
@@ -1,13 +1,5 @@
1
1
  import { DomUtils } from '@etsoo/shared';
2
2
  import React from 'react';
3
- const createRef = (source, index) => {
4
- return [
5
- (instance) => {
6
- if (instance != null)
7
- source[index][1] = instance;
8
- }
9
- ];
10
- };
11
3
  /**
12
4
  * Calculate element(s) dimensions
13
5
  * @param elements Observed elments count
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/react",
3
- "version": "1.4.20",
3
+ "version": "1.4.21",
4
4
  "description": "TypeScript ReactJs framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -126,6 +126,11 @@ export interface GridLoaderStates<T> extends GridLoadDataProps {
126
126
  */
127
127
  lastLoadedItems?: number;
128
128
 
129
+ /**
130
+ * All loaded items count
131
+ */
132
+ loadedItems: number;
133
+
129
134
  /**
130
135
  * Has next page?
131
136
  */
@@ -137,9 +142,9 @@ export interface GridLoaderStates<T> extends GridLoadDataProps {
137
142
  isNextPageLoading: boolean;
138
143
 
139
144
  /**
140
- * Current rows
145
+ * Is mounted
141
146
  */
142
- rows: T[];
147
+ isMounted?: boolean;
143
148
 
144
149
  /**
145
150
  * Selected items of id
@@ -40,7 +40,10 @@ export interface ScrollerGridProps<T>
40
40
  /**
41
41
  * Footer renderer
42
42
  */
43
- footerRenderer?: (states: GridLoaderStates<T>) => React.ReactNode;
43
+ footerRenderer?: (
44
+ rows: T[],
45
+ states: GridLoaderStates<T>
46
+ ) => React.ReactNode;
44
47
 
45
48
  /**
46
49
  * Header renderer
@@ -163,29 +166,28 @@ export const ScrollerGrid = <T extends Record<string, unknown>>(
163
166
  ...rest
164
167
  } = props;
165
168
 
166
- // States
167
- const [state, stateUpdate] = React.useReducer(
168
- (
169
- currentState: GridLoaderStates<T>,
170
- newState: Partial<GridLoaderStates<T>>
171
- ) => {
172
- return { ...currentState, ...newState };
173
- },
174
- {
175
- autoLoad,
176
- currentPage: 0,
177
- hasNextPage: true,
178
- isNextPageLoading: false,
179
- orderBy: defaultOrderBy,
180
- orderByAsc: defaultOrderByAsc,
181
- rows: [],
182
- batchSize: 10,
183
- selectedItems: []
184
- }
185
- );
186
- const isMounted = React.useRef(true);
169
+ // Rows
170
+ const [rows, updateRows] = React.useState<T[]>([]);
171
+ const setRows = (rows: T[]) => {
172
+ state.loadedItems = rows.length;
173
+ updateRows(rows);
174
+ };
187
175
 
188
- const ref = React.createRef<VariableSizeGrid<T>>();
176
+ // Refs
177
+ const refs = React.useRef<GridLoaderStates<T>>({
178
+ autoLoad,
179
+ currentPage: 0,
180
+ hasNextPage: true,
181
+ isNextPageLoading: false,
182
+ orderBy: defaultOrderBy,
183
+ orderByAsc: defaultOrderByAsc,
184
+ batchSize: 10,
185
+ loadedItems: 0,
186
+ selectedItems: []
187
+ });
188
+ const state = refs.current;
189
+
190
+ const ref = React.useRef<VariableSizeGrid<T>>(null);
189
191
 
190
192
  // Load data
191
193
  const loadDataLocal = (pageAdd: number = 1) => {
@@ -194,10 +196,10 @@ export const ScrollerGrid = <T extends Record<string, unknown>>(
194
196
 
195
197
  // Update state
196
198
  state.isNextPageLoading = true;
197
- // stateUpdate({ isNextPageLoading: true });
198
199
 
199
200
  // Parameters
200
- const { currentPage, batchSize, orderBy, orderByAsc, data } = state;
201
+ const { currentPage, batchSize, orderBy, orderByAsc, data, isMounted } =
202
+ state;
201
203
 
202
204
  const loadProps: GridLoadDataProps = {
203
205
  currentPage,
@@ -208,38 +210,36 @@ export const ScrollerGrid = <T extends Record<string, unknown>>(
208
210
  };
209
211
 
210
212
  loadData(loadProps).then((result) => {
211
- if (result == null || !isMounted.current) {
213
+ state.isMounted = true;
214
+
215
+ if (result == null || isMounted === false) {
212
216
  return;
213
217
  }
214
218
 
215
219
  const newItems = result.length;
220
+ state.lastLoadedItems = newItems;
221
+ state.isNextPageLoading = false;
222
+ state.hasNextPage = newItems >= batchSize;
216
223
 
217
224
  if (pageAdd === 0) {
218
225
  // New items
219
- const rows = state.lastLoadedItems
220
- ? state.rows
226
+ const newRows = state.lastLoadedItems
227
+ ? [...rows]
221
228
  .splice(
222
- state.rows.length - state.lastLoadedItems,
229
+ rows.length - state.lastLoadedItems,
223
230
  state.lastLoadedItems
224
231
  )
225
232
  .concat(result)
226
233
  : result;
227
234
 
228
- // Refresh current page
229
- stateUpdate({
230
- rows,
231
- lastLoadedItems: newItems,
232
- isNextPageLoading: false,
233
- hasNextPage: newItems >= batchSize
234
- });
235
+ // Update rows
236
+ setRows(newRows);
235
237
  } else {
236
- stateUpdate({
237
- rows: state.rows.concat(result),
238
- lastLoadedItems: newItems,
239
- isNextPageLoading: false,
240
- currentPage: state.currentPage + pageAdd,
241
- hasNextPage: newItems >= batchSize
242
- });
238
+ // Set current page
239
+ state.currentPage = state.currentPage + pageAdd;
240
+
241
+ // Update rows
242
+ setRows([...rows, ...result]);
243
243
  }
244
244
  });
245
245
  };
@@ -251,8 +251,8 @@ export const ScrollerGrid = <T extends Record<string, unknown>>(
251
251
  ) => {
252
252
  // Custom render
253
253
  const data =
254
- itemProps.rowIndex < state.rows.length
255
- ? state.rows[itemProps.rowIndex]
254
+ itemProps.rowIndex < rows.length
255
+ ? rows[itemProps.rowIndex]
256
256
  : undefined;
257
257
  return itemRenderer({
258
258
  ...itemProps,
@@ -264,7 +264,7 @@ export const ScrollerGrid = <T extends Record<string, unknown>>(
264
264
  // Local items renderer callback
265
265
  const onItemsRenderedLocal = (props: GridOnItemsRenderedProps) => {
266
266
  // No items, means no necessary to load more data during reset
267
- const itemCount = state.rows.length;
267
+ const itemCount = rows.length;
268
268
  if (
269
269
  itemCount > 0 &&
270
270
  props.visibleRowStopIndex + threshold > itemCount
@@ -278,17 +278,17 @@ export const ScrollerGrid = <T extends Record<string, unknown>>(
278
278
  };
279
279
 
280
280
  // Reset the state and load again
281
- const reset = (add?: {}) => {
282
- const state = {
281
+ const reset = (add?: Partial<GridLoaderStates<T>>) => {
282
+ const resetState: Partial<GridLoaderStates<T>> = {
283
283
  autoLoad: true,
284
284
  currentPage: 0,
285
+ loadedItems: 0,
285
286
  hasNextPage: true,
286
287
  isNextPageLoading: false,
287
288
  lastLoadedItems: undefined,
288
- rows: [],
289
289
  ...add
290
290
  };
291
- stateUpdate(state);
291
+ Object.assign(state, resetState);
292
292
  };
293
293
 
294
294
  React.useImperativeHandle(
@@ -307,16 +307,14 @@ export const ScrollerGrid = <T extends Record<string, unknown>>(
307
307
  select(rowIndex: number) {
308
308
  // Select only one item
309
309
  const selectedItems = state.selectedItems;
310
- selectedItems[0] = state.rows[rowIndex];
310
+ selectedItems[0] = rows[rowIndex];
311
311
 
312
312
  if (onSelectChange) onSelectChange(selectedItems);
313
-
314
- stateUpdate({ selectedItems });
315
313
  },
316
314
  selectAll(checked: boolean) {
317
315
  const selectedItems = state.selectedItems;
318
316
 
319
- state.rows.forEach((row) => {
317
+ rows.forEach((row) => {
320
318
  const index = selectedItems.findIndex(
321
319
  (selectedItem) => selectedItem[idField] === row[idField]
322
320
  );
@@ -329,8 +327,6 @@ export const ScrollerGrid = <T extends Record<string, unknown>>(
329
327
  });
330
328
 
331
329
  if (onSelectChange) onSelectChange(selectedItems);
332
-
333
- stateUpdate({ selectedItems });
334
330
  },
335
331
  selectItem(item: T, checked: boolean) {
336
332
  const selectedItems = state.selectedItems;
@@ -345,8 +341,6 @@ export const ScrollerGrid = <T extends Record<string, unknown>>(
345
341
  }
346
342
 
347
343
  if (onSelectChange) onSelectChange(selectedItems);
348
-
349
- stateUpdate({ selectedItems });
350
344
  },
351
345
  reset,
352
346
  resetAfterColumnIndex(index: number, shouldForceUpdate?: boolean) {
@@ -363,17 +357,26 @@ export const ScrollerGrid = <T extends Record<string, unknown>>(
363
357
  ref.current?.resetAfterRowIndex(index, shouldForceUpdate);
364
358
  }
365
359
  }),
366
- [state.rows, state.selectedItems]
360
+ [rows]
367
361
  );
368
362
 
369
363
  React.useEffect(() => {
370
364
  return () => {
371
- isMounted.current = false;
365
+ state.isMounted = false;
372
366
  };
373
367
  }, []);
374
368
 
369
+ // Force update to work with the new width
370
+ React.useEffect(() => {
371
+ ref.current?.resetAfterIndices({
372
+ columnIndex: 0,
373
+ rowIndex: 0,
374
+ shouldForceUpdate: true
375
+ });
376
+ }, [width]);
377
+
375
378
  // Destruct state
376
- const { autoLoad: stateAutoLoad, rows, hasNextPage, currentPage } = state;
379
+ const { autoLoad: stateAutoLoad, hasNextPage, currentPage } = state;
377
380
  const rowLength = rows.length;
378
381
 
379
382
  // Row count
@@ -388,7 +391,7 @@ export const ScrollerGrid = <T extends Record<string, unknown>>(
388
391
  {headerRenderer && headerRenderer(state)}
389
392
  <VariableSizeGrid<T>
390
393
  itemKey={({ columnIndex, rowIndex }) => {
391
- const data = state.rows[rowIndex];
394
+ const data = rows[rowIndex];
392
395
  if (data == null) return [rowIndex, columnIndex].join(',');
393
396
  return [data[idField], columnIndex].join(',');
394
397
  }}
@@ -406,7 +409,7 @@ export const ScrollerGrid = <T extends Record<string, unknown>>(
406
409
  >
407
410
  {(props) => itemRendererLocal(props, state)}
408
411
  </VariableSizeGrid>
409
- {footerRenderer && footerRenderer(state)}
412
+ {footerRenderer && footerRenderer(rows, state)}
410
413
  </React.Fragment>
411
414
  );
412
415
  };