@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.
- package/lib/components/GridLoader.d.ts +6 -2
- package/lib/components/ScrollerGrid.d.ts +1 -1
- package/lib/components/ScrollerGrid.js +50 -43
- package/lib/components/ScrollerList.js +30 -32
- package/lib/mu/DataGridEx.d.ts +1 -1
- package/lib/mu/DataGridEx.js +23 -49
- package/lib/mu/DataGridRenderers.d.ts +1 -1
- package/lib/mu/DataGridRenderers.js +4 -4
- package/lib/mu/GridMethodRef.d.ts +2 -1
- package/lib/mu/ResponsibleContainer.d.ts +16 -0
- package/lib/mu/ResponsibleContainer.js +94 -40
- package/lib/mu/ScrollerListEx.js +1 -2
- package/lib/mu/TableEx.js +27 -25
- package/lib/mu/pages/CommonPage.d.ts +1 -2
- package/lib/uses/useDimensions.js +0 -8
- package/package.json +8 -8
- package/src/components/GridLoader.ts +7 -2
- package/src/components/ScrollerGrid.tsx +69 -63
- package/src/components/ScrollerList.tsx +44 -48
- package/src/mu/DataGridEx.tsx +51 -91
- package/src/mu/DataGridRenderers.tsx +7 -8
- package/src/mu/GridMethodRef.ts +3 -1
- package/src/mu/ResponsibleContainer.tsx +141 -47
- package/src/mu/ScrollerListEx.tsx +1 -2
- package/src/mu/TableEx.tsx +40 -46
- package/src/uses/useDimensions.ts +0 -11
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
161
|
-
if (!isNaN(
|
|
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
|
-
|
|
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={
|
|
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
|
-
|
|
290
|
+
width={rect.width}
|
|
291
|
+
height={heightLocal}
|
|
216
292
|
loadData={localLoadData}
|
|
217
293
|
mRef={mRefs}
|
|
218
294
|
{...rest}
|
|
219
295
|
/>
|
|
220
|
-
</Box
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
<
|
|
233
|
-
|
|
310
|
+
<React.Fragment>
|
|
311
|
+
<Stack>
|
|
234
312
|
<Box ref={dimensions[0][0]} sx={searchBoxSx}>
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
selectedDiv.classList.remove(selectedClassName);
|
|
177
|
+
selectedDiv?.classList.remove(selectedClassName);
|
|
179
178
|
|
|
180
179
|
div.classList.add(selectedClassName);
|
|
181
180
|
|
package/src/mu/TableEx.tsx
CHANGED
|
@@ -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
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
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
|
-
|
|
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 } =
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
|
250
|
-
reset({
|
|
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 =
|
|
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 =
|
|
270
|
+
const selectedItems = state.selectedItems;
|
|
274
271
|
|
|
275
|
-
|
|
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
|
|
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[];
|