@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.
@@ -2,6 +2,7 @@ import { DataTypes } from '@etsoo/shared';
2
2
  import { Box, Stack, SxProps, Theme } from '@mui/material';
3
3
  import React from 'react';
4
4
  import { ListChildComponentProps } from 'react-window';
5
+ import { Labels } from '../app/Labels';
5
6
  import { GridColumn } from '../components/GridColumn';
6
7
  import {
7
8
  GridDataGet,
@@ -16,12 +17,16 @@ import {
16
17
  DataGridExProps
17
18
  } from './DataGridEx';
18
19
  import { GridMethodRef } from './GridMethodRef';
20
+ import { PullToRefreshUI } from './PullToRefreshUI';
19
21
  import {
20
22
  ScrollerListEx,
21
23
  ScrollerListExInnerItemRendererProps
22
24
  } from './ScrollerListEx';
23
25
  import { SearchBar } from './SearchBar';
24
26
 
27
+ /**
28
+ * ResponsibleContainer props
29
+ */
25
30
  export interface ResponsibleContainerProps<
26
31
  T extends {},
27
32
  F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
@@ -99,6 +104,11 @@ export interface ResponsibleContainerProps<
99
104
  */
100
105
  mRef?: React.MutableRefObject<GridMethodRef | undefined>;
101
106
 
107
+ /**
108
+ * Pull to refresh data
109
+ */
110
+ pullToRefresh?: boolean;
111
+
102
112
  /**
103
113
  * Searchbox SX
104
114
  */
@@ -111,10 +121,16 @@ export interface ResponsibleContainerProps<
111
121
  }
112
122
 
113
123
  interface LocalRefs {
114
- height?: number;
124
+ rect?: DOMRect;
115
125
  ref?: GridMethodRef;
126
+ mounted?: boolean;
116
127
  }
117
128
 
129
+ /**
130
+ * Responsible container
131
+ * @param props Props
132
+ * @returns Layout
133
+ */
118
134
  export function ResponsibleContainer<
119
135
  T extends {},
120
136
  F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
@@ -130,11 +146,15 @@ export function ResponsibleContainer<
130
146
  listBoxSx,
131
147
  loadData,
132
148
  mRef,
149
+ pullToRefresh = true,
133
150
  searchBoxSx,
134
151
  sizeReadyMiliseconds = 0,
135
152
  ...rest
136
153
  } = props;
137
154
 
155
+ // Labels
156
+ const labels = Labels.CommonPage;
157
+
138
158
  // Refs
139
159
  const refs = React.useRef<LocalRefs>({});
140
160
 
@@ -143,23 +163,72 @@ export function ResponsibleContainer<
143
163
  refs.current.ref = ref;
144
164
  });
145
165
 
166
+ // Update mounted state
167
+ React.useEffect(() => {
168
+ return () => {
169
+ refs.current.mounted = false;
170
+ };
171
+ }, []);
172
+
146
173
  // Has fields
147
174
  const hasFields = fields != null && fields.length > 0;
148
175
 
149
- // Watch container
150
- const { dimensions } = useDimensions(1, undefined, sizeReadyMiliseconds);
151
- const rect = dimensions[0][2];
152
-
176
+ // Load data
153
177
  const localLoadData = (props: GridLoadDataProps) => {
178
+ refs.current.mounted = true;
154
179
  const data = GridDataGet(props, fieldTemplate);
155
180
  return loadData(data);
156
181
  };
157
182
 
158
- const list = React.useMemo(() => {
159
- // No rect
160
- if (rect == null) return;
183
+ // On submit callback
184
+ const onSubmit = (data: FormData, _reset: boolean) => {
185
+ if (data == null || rect == null || refs.current.ref == null) return;
186
+ refs.current.ref.reset({ data });
187
+ };
188
+
189
+ // Watch container
190
+ const { dimensions } = useDimensions(
191
+ 1,
192
+ undefined,
193
+ sizeReadyMiliseconds,
194
+ (_preRect, rect) => {
195
+ // Check
196
+ if (rect == null) return true;
197
+
198
+ // Last rect
199
+ const lastRect = refs.current.rect;
200
+
201
+ // 32 = scroll bar width
202
+ if (
203
+ lastRect != null &&
204
+ refs.current.mounted !== true &&
205
+ Math.abs(rect.width - lastRect.width) <= 32 &&
206
+ Math.abs(rect.height - lastRect.height) <= 32
207
+ )
208
+ return true;
209
+
210
+ // Hold the new rect
211
+ refs.current.rect = rect;
212
+
213
+ return false;
214
+ }
215
+ );
216
+
217
+ // Rect
218
+ const rect = dimensions[0][2];
219
+
220
+ // Create list
221
+ const [list, showDataGrid] = (() => {
222
+ // No layout
223
+ if (rect == null) return [null, false];
161
224
 
162
- // If height is the same
225
+ // Width
226
+ const width = rect.width;
227
+
228
+ // Show DataGrid or List dependng on width
229
+ const showDataGrid = width >= dataGridMinWidth;
230
+
231
+ // Height
163
232
  let heightLocal: number;
164
233
  if (height != null) {
165
234
  heightLocal = height;
@@ -177,31 +246,24 @@ export function ResponsibleContainer<
177
246
  }
178
247
  }
179
248
 
180
- console.log(rect, heightLocal, refs.current.height);
181
-
182
- // Same height
183
- if (heightLocal === refs.current.height) return;
184
- refs.current.height = heightLocal;
185
-
186
- // Show DataGrid or List dependng on width
187
- const showDataGrid = rect.width >= dataGridMinWidth;
188
-
189
249
  if (showDataGrid) {
190
250
  // Delete
191
251
  delete rest.itemRenderer;
192
252
 
193
- return (
253
+ return [
194
254
  <Box sx={listBoxSx == null ? undefined : listBoxSx(true)}>
195
255
  <DataGridEx<T>
196
256
  autoLoad={!hasFields}
197
257
  height={heightLocal}
258
+ width={rect.width}
198
259
  loadData={localLoadData}
199
260
  mRef={mRefs}
200
261
  columns={columns}
201
262
  {...rest}
202
263
  />
203
- </Box>
204
- );
264
+ </Box>,
265
+ true
266
+ ];
205
267
  }
206
268
 
207
269
  // Delete
@@ -214,34 +276,53 @@ export function ResponsibleContainer<
214
276
  delete rest.hoverColor;
215
277
  delete rest.selectable;
216
278
 
217
- return (
279
+ return [
218
280
  <Box sx={listBoxSx == null ? undefined : listBoxSx(false)}>
219
281
  <ScrollerListEx<T>
220
282
  autoLoad={!hasFields}
283
+ width={rect.width}
221
284
  height={heightLocal}
222
285
  loadData={localLoadData}
223
286
  mRef={mRefs}
224
287
  {...rest}
225
288
  />
226
- </Box>
227
- );
228
- }, [rect, height]);
229
-
230
- // On submit callback
231
- const onSubmit = (data: FormData, _reset: boolean) => {
232
- if (data == null || rect == null || refs.current.ref == null) return;
233
- refs.current.ref.reset({ data });
234
- };
289
+ </Box>,
290
+ false
291
+ ];
292
+ })();
293
+
294
+ // Pull container
295
+ const pullContainer = list
296
+ ? showDataGrid
297
+ ? '.DataGridEx-Body'
298
+ : '.ScrollerListEx-Body'
299
+ : undefined;
235
300
 
236
301
  // Layout
237
302
  return (
238
- <Stack ref={hasFields ? undefined : dimensions[0][0]}>
239
- {hasFields && (
303
+ <React.Fragment>
304
+ <Stack>
240
305
  <Box ref={dimensions[0][0]} sx={searchBoxSx}>
241
- <SearchBar fields={fields} onSubmit={onSubmit} />
306
+ {hasFields && (
307
+ <SearchBar fields={fields} onSubmit={onSubmit} />
308
+ )}
242
309
  </Box>
310
+ {list}
311
+ </Stack>
312
+ {pullToRefresh && pullContainer && (
313
+ <PullToRefreshUI
314
+ mainElement={pullContainer}
315
+ triggerElement={pullContainer}
316
+ instructionsPullToRefresh={labels.pullToRefresh}
317
+ instructionsReleaseToRefresh={labels.releaseToRefresh}
318
+ instructionsRefreshing={labels.refreshing}
319
+ onRefresh={() => refs.current.ref?.reset()}
320
+ shouldPullToRefresh={() => {
321
+ const container = document.querySelector(pullContainer);
322
+ return !container?.scrollTop;
323
+ }}
324
+ />
243
325
  )}
244
- {list}
245
- </Stack>
326
+ </React.Fragment>
246
327
  );
247
328
  }
@@ -174,8 +174,7 @@ export function ScrollerListEx<T extends Record<string, unknown>>(
174
174
  if (selectedData != null && selectedData[idField] === data[idField])
175
175
  return;
176
176
 
177
- if (selectedDiv != null)
178
- selectedDiv.classList.remove(selectedClassName);
177
+ selectedDiv?.classList.remove(selectedClassName);
179
178
 
180
179
  div.classList.add(selectedClassName);
181
180
 
@@ -145,43 +145,41 @@ export function TableEx<T extends Record<string, unknown>>(
145
145
  rowsPerPageLocal = 10;
146
146
  }
147
147
 
148
+ // Rows
149
+ const [rows, updateRows] = React.useState<T[]>([]);
150
+ const setRows = (rows: T[]) => {
151
+ state.loadedItems = rows.length;
152
+ updateRows(rows);
153
+ };
154
+
148
155
  // States
149
- const [state, stateUpdate] = React.useReducer(
150
- (
151
- currentState: GridLoaderStates<T>,
152
- newState: Partial<GridLoaderStates<T>>
153
- ) => {
154
- return { ...currentState, ...newState };
155
- },
156
- {
157
- autoLoad,
158
- currentPage: 0,
159
- hasNextPage: true,
160
- isNextPageLoading: false,
161
- orderBy: defaultOrderBy,
162
- orderByAsc: defaultOrderBy
163
- ? columns.find((column) => column.field === defaultOrderBy)
164
- ?.sortAsc
165
- : undefined,
166
- rows: [],
167
- batchSize: rowsPerPageLocal,
168
- selectedItems: []
169
- }
170
- );
171
- const isMounted = React.useRef(true);
156
+ const stateRefs = React.useRef<GridLoaderStates<T>>({
157
+ autoLoad,
158
+ currentPage: 0,
159
+ loadedItems: 0,
160
+ hasNextPage: true,
161
+ isNextPageLoading: false,
162
+ orderBy: defaultOrderBy,
163
+ orderByAsc: defaultOrderBy
164
+ ? columns.find((column) => column.field === defaultOrderBy)?.sortAsc
165
+ : undefined,
166
+ batchSize: rowsPerPageLocal,
167
+ selectedItems: []
168
+ });
169
+ const state = stateRefs.current;
172
170
 
173
171
  // Reset the state and load again
174
- const reset = (add?: {}) => {
175
- const state = {
172
+ const reset = (add?: Partial<GridLoaderStates<T>>) => {
173
+ const resetState: Partial<GridLoaderStates<T>> = {
176
174
  autoLoad: true,
177
175
  currentPage: 0,
176
+ loadedItems: 0,
178
177
  hasNextPage: true,
179
178
  isNextPageLoading: false,
180
179
  lastLoadedItems: undefined,
181
- rows: [],
182
180
  ...add
183
181
  };
184
- stateUpdate(state);
182
+ Object.assign(state, resetState);
185
183
  };
186
184
 
187
185
  React.useImperativeHandle(
@@ -211,7 +209,8 @@ export function TableEx<T extends Record<string, unknown>>(
211
209
  state.isNextPageLoading = true;
212
210
 
213
211
  // Parameters
214
- const { currentPage, batchSize, orderBy, orderByAsc, data } = state;
212
+ const { currentPage, batchSize, orderBy, orderByAsc, data, isMounted } =
213
+ state;
215
214
 
216
215
  const loadProps: GridLoadDataProps = {
217
216
  currentPage,
@@ -222,18 +221,18 @@ export function TableEx<T extends Record<string, unknown>>(
222
221
  };
223
222
 
224
223
  loadData(loadProps).then((result) => {
225
- if (!isMounted.current || result == null) {
224
+ state.isMounted = true;
225
+ if (result == null || isMounted === false) {
226
226
  return;
227
227
  }
228
228
 
229
229
  const newItems = result.length;
230
+ state.lastLoadedItems = newItems;
231
+ state.hasNextPage = newItems >= batchSize;
232
+ state.isNextPageLoading = false;
230
233
 
231
- stateUpdate({
232
- rows: result,
233
- lastLoadedItems: newItems,
234
- hasNextPage: newItems >= batchSize,
235
- isNextPageLoading: false
236
- });
234
+ // Update rows
235
+ setRows(result);
237
236
  });
238
237
  };
239
238
 
@@ -246,12 +245,12 @@ export function TableEx<T extends Record<string, unknown>>(
246
245
  const handleChangeRowsPerPage = (
247
246
  event: React.ChangeEvent<HTMLInputElement>
248
247
  ) => {
249
- const rowsPerPage = parseInt(event.target.value);
250
- reset({ rowsPerPage });
248
+ const batchSize = parseInt(event.target.value);
249
+ reset({ batchSize });
251
250
  };
252
251
 
253
252
  const handleSelect = (item: T, checked: Boolean) => {
254
- const selectedItems = [...state.selectedItems];
253
+ const selectedItems = state.selectedItems;
255
254
 
256
255
  const index = selectedItems.findIndex(
257
256
  (selectedItem) => selectedItem[idField] === item[idField]
@@ -265,14 +264,12 @@ export function TableEx<T extends Record<string, unknown>>(
265
264
  if (onSelectChange != null) {
266
265
  onSelectChange(selectedItems);
267
266
  }
268
-
269
- stateUpdate({ selectedItems });
270
267
  };
271
268
 
272
269
  const handleSelectAll = (checked: boolean) => {
273
- const selectedItems = [...state.selectedItems];
270
+ const selectedItems = state.selectedItems;
274
271
 
275
- state.rows.forEach((row) => {
272
+ rows.forEach((row) => {
276
273
  const index = selectedItems.findIndex(
277
274
  (selectedItem) => selectedItem[idField] === row[idField]
278
275
  );
@@ -287,8 +284,6 @@ export function TableEx<T extends Record<string, unknown>>(
287
284
  if (onSelectChange != null) {
288
285
  onSelectChange(selectedItems);
289
286
  }
290
-
291
- stateUpdate({ selectedItems });
292
287
  };
293
288
 
294
289
  // New sort
@@ -303,7 +298,6 @@ export function TableEx<T extends Record<string, unknown>>(
303
298
  hasNextPage,
304
299
  lastLoadedItems,
305
300
  orderBy,
306
- rows,
307
301
  batchSize,
308
302
  selectedItems
309
303
  } = state;
@@ -333,7 +327,7 @@ export function TableEx<T extends Record<string, unknown>>(
333
327
 
334
328
  React.useEffect(() => {
335
329
  return () => {
336
- isMounted.current = false;
330
+ state.isMounted = false;
337
331
  };
338
332
  }, []);
339
333
 
@@ -1,17 +1,6 @@
1
1
  import { DomUtils } from '@etsoo/shared';
2
2
  import React from 'react';
3
3
 
4
- const createRef = (
5
- source: [React.RefCallback<Element>, Element?, DOMRect?][],
6
- index: number
7
- ): [React.RefCallback<Element>] => {
8
- return [
9
- (instance) => {
10
- if (instance != null) source[index][1] = instance;
11
- }
12
- ];
13
- };
14
-
15
4
  interface states {
16
5
  count: number;
17
6
  indices: number[];