@etsoo/react 1.4.18 → 1.4.22

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,17 @@ import {
16
17
  DataGridExProps
17
18
  } from './DataGridEx';
18
19
  import { GridMethodRef } from './GridMethodRef';
20
+ import { MUGlobal } from './MUGlobal';
21
+ import { PullToRefreshUI } from './PullToRefreshUI';
19
22
  import {
20
23
  ScrollerListEx,
21
24
  ScrollerListExInnerItemRendererProps
22
25
  } from './ScrollerListEx';
23
26
  import { SearchBar } from './SearchBar';
24
27
 
28
+ /**
29
+ * ResponsibleContainer props
30
+ */
25
31
  export interface ResponsibleContainerProps<
26
32
  T extends {},
27
33
  F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
@@ -99,6 +105,16 @@ export interface ResponsibleContainerProps<
99
105
  */
100
106
  mRef?: React.MutableRefObject<GridMethodRef | undefined>;
101
107
 
108
+ /**
109
+ * Paddings
110
+ */
111
+ paddings?: {};
112
+
113
+ /**
114
+ * Pull to refresh data
115
+ */
116
+ pullToRefresh?: boolean;
117
+
102
118
  /**
103
119
  * Searchbox SX
104
120
  */
@@ -111,10 +127,16 @@ export interface ResponsibleContainerProps<
111
127
  }
112
128
 
113
129
  interface LocalRefs {
114
- height?: number;
130
+ rect?: DOMRect;
115
131
  ref?: GridMethodRef;
132
+ mounted?: boolean;
116
133
  }
117
134
 
135
+ /**
136
+ * Responsible container
137
+ * @param props Props
138
+ * @returns Layout
139
+ */
118
140
  export function ResponsibleContainer<
119
141
  T extends {},
120
142
  F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
@@ -130,72 +152,125 @@ export function ResponsibleContainer<
130
152
  listBoxSx,
131
153
  loadData,
132
154
  mRef,
133
- searchBoxSx,
155
+ paddings = MUGlobal.pagePaddings,
156
+ pullToRefresh = true,
157
+ searchBoxSx = { marginBottom: MUGlobal.half(paddings) },
134
158
  sizeReadyMiliseconds = 0,
135
159
  ...rest
136
160
  } = props;
137
161
 
162
+ // Labels
163
+ const labels = Labels.CommonPage;
164
+
138
165
  // Refs
139
166
  const refs = React.useRef<LocalRefs>({});
167
+ const state = refs.current;
140
168
 
141
169
  const mRefs = useCombinedRefs(mRef, (ref: GridMethodRef) => {
142
170
  if (ref == null) return;
143
- refs.current.ref = ref;
171
+ state.ref = ref;
144
172
  });
145
173
 
174
+ // Update mounted state
175
+ React.useEffect(() => {
176
+ return () => {
177
+ state.mounted = false;
178
+ };
179
+ }, []);
180
+
146
181
  // Has fields
147
182
  const hasFields = fields != null && fields.length > 0;
148
183
 
184
+ // Load data
185
+ const localLoadData = (props: GridLoadDataProps) => {
186
+ state.mounted = true;
187
+ const data = GridDataGet(props, fieldTemplate);
188
+ return loadData(data);
189
+ };
190
+
191
+ // On submit callback
192
+ const onSubmit = (data: FormData, _reset: boolean) => {
193
+ if (data == null || state.ref == null) return;
194
+ state.ref.reset({ data });
195
+ };
196
+
149
197
  // Watch container
150
- const { dimensions } = useDimensions(1, undefined, sizeReadyMiliseconds);
198
+ const { dimensions } = useDimensions(
199
+ 1,
200
+ undefined,
201
+ sizeReadyMiliseconds,
202
+ (_preRect, rect) => {
203
+ // Check
204
+ if (rect == null) return true;
205
+
206
+ // Last rect
207
+ const lastRect = state.rect;
208
+
209
+ // 32 = scroll bar width
210
+ if (
211
+ lastRect != null &&
212
+ state.mounted !== true &&
213
+ Math.abs(rect.width - lastRect.width) <= 32 &&
214
+ Math.abs(rect.height - lastRect.height) <= 32
215
+ )
216
+ return true;
217
+
218
+ // Hold the new rect
219
+ state.rect = rect;
220
+
221
+ return false;
222
+ }
223
+ );
224
+
225
+ // Rect
151
226
  const rect = dimensions[0][2];
152
- const showDataGrid = (rect?.width ?? 0) >= dataGridMinWidth;
153
227
 
154
- React.useEffect(() => {
155
- if (rect != null && rect.height > 50 && height == null) {
156
- let gridHeight =
157
- window.innerHeight - Math.round(rect.top + rect.height + 1);
228
+ // Create list
229
+ const [list, showDataGrid] = (() => {
230
+ // No layout
231
+ if (rect == null) return [null, false];
232
+
233
+ // Width
234
+ const width = rect.width;
235
+
236
+ // Show DataGrid or List dependng on width
237
+ const showDataGrid = width >= dataGridMinWidth;
238
+
239
+ // Height
240
+ let heightLocal: number;
241
+ if (height != null) {
242
+ heightLocal = height;
243
+ } else {
244
+ // Auto calculation
245
+ heightLocal = window.innerHeight - Math.round(rect.bottom + 1);
158
246
 
159
247
  const style = window.getComputedStyle(dimensions[0][1]!);
160
- const boxPadding = parseFloat(style.paddingLeft);
161
- if (!isNaN(boxPadding)) gridHeight -= 2 * boxPadding;
248
+ const boxMargin = parseFloat(style.marginBottom);
249
+ if (!isNaN(boxMargin)) heightLocal -= 3 * boxMargin; // 1 - Box, 2 - Page bottom
162
250
 
163
251
  if (adjustHeight != null) {
164
- gridHeight -= adjustHeight(gridHeight);
165
- }
166
-
167
- if (gridHeight !== refs.current.height) {
168
- refs.current.height = gridHeight;
252
+ heightLocal -= adjustHeight(heightLocal);
169
253
  }
170
254
  }
171
- }, [rect]);
172
-
173
- const localLoadData = (props: GridLoadDataProps) => {
174
- const data = GridDataGet(props, fieldTemplate);
175
- return loadData(data);
176
- };
177
-
178
- const gridHeight = refs.current.height;
179
- console.log(rect, gridHeight, showDataGrid);
180
- const list = React.useMemo(() => {
181
- if (gridHeight == null) return;
182
255
 
183
256
  if (showDataGrid) {
184
257
  // Delete
185
258
  delete rest.itemRenderer;
186
259
 
187
- return (
260
+ return [
188
261
  <Box sx={listBoxSx == null ? undefined : listBoxSx(true)}>
189
262
  <DataGridEx<T>
190
263
  autoLoad={!hasFields}
191
- height={gridHeight}
264
+ height={heightLocal}
265
+ width={rect.width}
192
266
  loadData={localLoadData}
193
267
  mRef={mRefs}
194
268
  columns={columns}
195
269
  {...rest}
196
270
  />
197
- </Box>
198
- );
271
+ </Box>,
272
+ true
273
+ ];
199
274
  }
200
275
 
201
276
  // Delete
@@ -208,34 +283,53 @@ export function ResponsibleContainer<
208
283
  delete rest.hoverColor;
209
284
  delete rest.selectable;
210
285
 
211
- return (
286
+ return [
212
287
  <Box sx={listBoxSx == null ? undefined : listBoxSx(false)}>
213
288
  <ScrollerListEx<T>
214
289
  autoLoad={!hasFields}
215
- height={gridHeight}
290
+ width={rect.width}
291
+ height={heightLocal}
216
292
  loadData={localLoadData}
217
293
  mRef={mRefs}
218
294
  {...rest}
219
295
  />
220
- </Box>
221
- );
222
- }, [gridHeight, showDataGrid]);
223
-
224
- // On submit callback
225
- const onSubmit = (data: FormData, _reset: boolean) => {
226
- if (data == null || rect == null || refs.current.ref == null) return;
227
- refs.current.ref.reset({ data });
228
- };
296
+ </Box>,
297
+ false
298
+ ];
299
+ })();
300
+
301
+ // Pull container
302
+ const pullContainer = list
303
+ ? showDataGrid
304
+ ? '.DataGridEx-Body'
305
+ : '.ScrollerListEx-Body'
306
+ : undefined;
229
307
 
230
308
  // Layout
231
309
  return (
232
- <Stack>
233
- {hasFields && (
310
+ <React.Fragment>
311
+ <Stack>
234
312
  <Box ref={dimensions[0][0]} sx={searchBoxSx}>
235
- <SearchBar fields={fields} onSubmit={onSubmit} />
313
+ {hasFields && (
314
+ <SearchBar fields={fields} onSubmit={onSubmit} />
315
+ )}
236
316
  </Box>
317
+ {list}
318
+ </Stack>
319
+ {pullToRefresh && pullContainer && (
320
+ <PullToRefreshUI
321
+ mainElement={pullContainer}
322
+ triggerElement={pullContainer}
323
+ instructionsPullToRefresh={labels.pullToRefresh}
324
+ instructionsReleaseToRefresh={labels.releaseToRefresh}
325
+ instructionsRefreshing={labels.refreshing}
326
+ onRefresh={() => state.ref?.reset()}
327
+ shouldPullToRefresh={() => {
328
+ const container = document.querySelector(pullContainer);
329
+ return !container?.scrollTop;
330
+ }}
331
+ />
237
332
  )}
238
- {list}
239
- </Stack>
333
+ </React.Fragment>
240
334
  );
241
335
  }
@@ -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[];