@etsoo/react 1.4.9 → 1.4.13

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.
@@ -96,7 +96,7 @@ export class ReactApp extends CoreApp {
96
96
  if (startPos > 0) {
97
97
  const main = message.substring(0, startPos).trim();
98
98
  const tip = message.substring(startPos);
99
- const titleNode = React.createElement(React.Fragment, null, main, React.createElement('div', { style: { fontSize: '9px' } }, tip));
99
+ const titleNode = React.createElement(React.Fragment, null, main, React.createElement('br'), React.createElement('span', { style: { fontSize: '9px' } }, tip));
100
100
  this.notifier.alert(titleNode, callback);
101
101
  return;
102
102
  }
package/lib/index.d.ts CHANGED
@@ -48,6 +48,7 @@ export * from './mu/ItemList';
48
48
  export * from './mu/ListItemRightIcon';
49
49
  export * from './mu/LoadingButton';
50
50
  export * from './mu/MaskInput';
51
+ export * from './mu/MobileListItemRenderer';
51
52
  export * from './mu/MoreFab';
52
53
  export * from './mu/MUGlobal';
53
54
  export * from './mu/NotifierMU';
package/lib/index.js CHANGED
@@ -51,6 +51,7 @@ export * from './mu/ItemList';
51
51
  export * from './mu/ListItemRightIcon';
52
52
  export * from './mu/LoadingButton';
53
53
  export * from './mu/MaskInput';
54
+ export * from './mu/MobileListItemRenderer';
54
55
  export * from './mu/MoreFab';
55
56
  export * from './mu/MUGlobal';
56
57
  export * from './mu/NotifierMU';
@@ -36,10 +36,14 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
36
36
  * Data change handler
37
37
  */
38
38
  onChange?: (items: D[]) => void;
39
+ /**
40
+ * Drag end handler
41
+ */
42
+ onDragEnd?: (items: D[]) => void;
39
43
  /**
40
44
  * Top and bottom sides renderer
41
45
  */
42
- sideRenderer?: (top: boolean, addItem: (item: D) => boolean, addItems: (items: D[]) => number) => React.ReactNode;
46
+ sideRenderer?: (top: boolean, addItem: (item: D) => boolean, addItems: (items: D[]) => number, reloadItems: () => void) => React.ReactNode;
43
47
  /**
44
48
  * Name for hidden form input
45
49
  */
package/lib/mu/DnDList.js CHANGED
@@ -9,7 +9,7 @@ import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
9
9
  */
10
10
  export function DnDList(props) {
11
11
  // Destruct
12
- const { children, Component = 'div', componentProps, getItemStyle = (_isDragging) => ({}), getListStyle = () => undefined, labelField, loadData, name, onChange, sideRenderer } = props;
12
+ const { children, Component = 'div', componentProps, getItemStyle = (_isDragging) => ({}), getListStyle = () => undefined, labelField, loadData, name, onChange, onDragEnd, sideRenderer } = props;
13
13
  // State
14
14
  const [items, setItems] = React.useState([]);
15
15
  const changeItems = (items) => {
@@ -20,7 +20,7 @@ export function DnDList(props) {
20
20
  setItems(items);
21
21
  };
22
22
  // Drag end handler
23
- const onDragEnd = (result) => {
23
+ const onDragEndLocal = (result) => {
24
24
  // Dropped outside the list
25
25
  if (!result.destination) {
26
26
  return;
@@ -33,6 +33,9 @@ export function DnDList(props) {
33
33
  newItems.splice(result.destination.index, 0, removed);
34
34
  // Update the state
35
35
  changeItems(newItems);
36
+ // Drag end handler
37
+ if (onDragEnd)
38
+ onDragEnd(newItems);
36
39
  };
37
40
  // Add item
38
41
  const addItem = (newItem) => {
@@ -49,7 +52,9 @@ export function DnDList(props) {
49
52
  // Edit item
50
53
  const editItem = (newItem, index) => {
51
54
  // Existence check
52
- if (items.some((item) => item[labelField] === newItem[labelField])) {
55
+ const newIndex = items.findIndex((item) => item[labelField] === newItem[labelField]);
56
+ if (newIndex >= 0 && newIndex != index) {
57
+ // Label field is the same with a different item
53
58
  return false;
54
59
  }
55
60
  // Clone
@@ -85,15 +90,20 @@ export function DnDList(props) {
85
90
  // Update the state
86
91
  changeItems(newItems);
87
92
  };
88
- React.useEffect(() => {
93
+ // Reload items
94
+ const reloadItems = () => {
89
95
  // Load data
90
96
  loadData(name).then((items) => setItems(items));
97
+ };
98
+ React.useEffect(() => {
99
+ // Load data
100
+ reloadItems();
91
101
  }, [name]);
92
102
  // Layout
93
103
  return (React.createElement(React.Fragment, null,
94
- sideRenderer && sideRenderer(true, addItem, addItems),
104
+ sideRenderer && sideRenderer(true, addItem, addItems, reloadItems),
95
105
  React.createElement(Component, { ...componentProps },
96
- React.createElement(DragDropContext, { onDragEnd: onDragEnd },
106
+ React.createElement(DragDropContext, { onDragEnd: onDragEndLocal },
97
107
  React.createElement(Droppable, { droppableId: name }, (provided, snapshot) => (React.createElement("div", { ...provided.droppableProps, ref: provided.innerRef, style: getListStyle(snapshot.isDraggingOver) },
98
108
  items.map((item, index) => {
99
109
  // Id
@@ -108,5 +118,6 @@ export function DnDList(props) {
108
118
  } }, children(item, index, deleteItem, editItem)))));
109
119
  }),
110
120
  provided.placeholder))))),
111
- sideRenderer && sideRenderer(false, addItem, addItems)));
121
+ sideRenderer &&
122
+ sideRenderer(false, addItem, addItems, reloadItems)));
112
123
  }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { ListItemReact } from '../components/ListItemReact';
3
+ import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
4
+ export declare function MobileListItemRenderer<T>({ data, itemHeight }: ScrollerListExInnerItemRendererProps<T>, margin: {}, renderer: (data: T) => [
5
+ string,
6
+ string | undefined,
7
+ React.ReactNode | (ListItemReact | boolean)[],
8
+ React.ReactNode
9
+ ]): JSX.Element;
@@ -0,0 +1,63 @@
1
+ import { Card, CardContent, CardHeader, LinearProgress } from '@mui/material';
2
+ import React from 'react';
3
+ import { MoreFab } from './MoreFab';
4
+ import { MUGlobal } from './MUGlobal';
5
+ function getActions(input) {
6
+ // Actions
7
+ const actions = [];
8
+ input.forEach((action) => {
9
+ if (typeof action === 'boolean')
10
+ return;
11
+ actions.push(action);
12
+ });
13
+ return actions;
14
+ }
15
+ export function MobileListItemRenderer({ data, itemHeight }, margin, renderer) {
16
+ // Loading
17
+ if (data == null)
18
+ return React.createElement(LinearProgress, null);
19
+ // Elements
20
+ const [title, subheader, actions, children] = renderer(data);
21
+ // Half
22
+ const halfMargin = MUGlobal.half(margin);
23
+ return (React.createElement(Card, { sx: {
24
+ height: (theme) => MUGlobal.adjustWithTheme(itemHeight, margin, theme.spacing),
25
+ marginLeft: margin,
26
+ marginRight: margin,
27
+ marginTop: halfMargin,
28
+ marginBottom: halfMargin
29
+ } },
30
+ React.createElement(CardHeader, { sx: { paddingBottom: 0.5 }, action: Array.isArray(actions) ? (React.createElement(MoreFab, { iconButton: true, size: "small", anchorOrigin: {
31
+ vertical: 'bottom',
32
+ horizontal: 'right'
33
+ }, transformOrigin: {
34
+ vertical: 'top',
35
+ horizontal: 'right'
36
+ }, actions: getActions(actions), PaperProps: {
37
+ elevation: 0,
38
+ sx: {
39
+ overflow: 'visible',
40
+ filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
41
+ mt: -0.4,
42
+ '& .MuiAvatar-root': {
43
+ width: 32,
44
+ height: 32,
45
+ ml: -0.5,
46
+ mr: 1
47
+ },
48
+ '&:before': {
49
+ content: '""',
50
+ display: 'block',
51
+ position: 'absolute',
52
+ top: 0,
53
+ right: 14,
54
+ width: 10,
55
+ height: 10,
56
+ bgcolor: 'background.paper',
57
+ transform: 'translateY(-50%) rotate(45deg)',
58
+ zIndex: 0
59
+ }
60
+ }
61
+ } })) : (actions), title: title, titleTypographyProps: { variant: 'body2' }, subheader: subheader, subheaderTypographyProps: { variant: 'caption' } }),
62
+ React.createElement(CardContent, { sx: { paddingTop: 0 } }, children)));
63
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/react",
3
- "version": "1.4.9",
3
+ "version": "1.4.13",
4
4
  "description": "TypeScript ReactJs framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -50,7 +50,7 @@
50
50
  "@emotion/react": "^11.7.1",
51
51
  "@emotion/style": "^0.8.0",
52
52
  "@emotion/styled": "^11.6.0",
53
- "@etsoo/appscript": "^1.2.18",
53
+ "@etsoo/appscript": "^1.2.20",
54
54
  "@etsoo/notificationbase": "^1.1.0",
55
55
  "@etsoo/shared": "^1.1.0",
56
56
  "@mui/icons-material": "^5.2.5",
@@ -89,7 +89,7 @@
89
89
  "eslint-config-airbnb-base": "^15.0.0",
90
90
  "eslint-plugin-import": "^2.25.4",
91
91
  "eslint-plugin-react": "^7.28.0",
92
- "jest": "^27.4.5",
92
+ "jest": "^27.4.7",
93
93
  "react-test-renderer": "^17.0.2",
94
94
  "ts-jest": "^27.1.2",
95
95
  "typescript": "^4.5.4"
@@ -223,8 +223,9 @@ export class ReactApp<
223
223
  React.Fragment,
224
224
  null,
225
225
  main,
226
+ React.createElement('br'),
226
227
  React.createElement(
227
- 'div',
228
+ 'span',
228
229
  { style: { fontSize: '9px' } },
229
230
  tip
230
231
  )
package/src/index.ts CHANGED
@@ -55,6 +55,7 @@ export * from './mu/ItemList';
55
55
  export * from './mu/ListItemRightIcon';
56
56
  export * from './mu/LoadingButton';
57
57
  export * from './mu/MaskInput';
58
+ export * from './mu/MobileListItemRenderer';
58
59
  export * from './mu/MoreFab';
59
60
  export * from './mu/MUGlobal';
60
61
  export * from './mu/NotifierMU';
@@ -57,13 +57,19 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
57
57
  */
58
58
  onChange?: (items: D[]) => void;
59
59
 
60
+ /**
61
+ * Drag end handler
62
+ */
63
+ onDragEnd?: (items: D[]) => void;
64
+
60
65
  /**
61
66
  * Top and bottom sides renderer
62
67
  */
63
68
  sideRenderer?: (
64
69
  top: boolean,
65
70
  addItem: (item: D) => boolean,
66
- addItems: (items: D[]) => number
71
+ addItems: (items: D[]) => number,
72
+ reloadItems: () => void
67
73
  ) => React.ReactNode;
68
74
 
69
75
  /**
@@ -93,6 +99,7 @@ export function DnDList<
93
99
  loadData,
94
100
  name,
95
101
  onChange,
102
+ onDragEnd,
96
103
  sideRenderer
97
104
  } = props;
98
105
 
@@ -108,7 +115,7 @@ export function DnDList<
108
115
  };
109
116
 
110
117
  // Drag end handler
111
- const onDragEnd = (result: DropResult) => {
118
+ const onDragEndLocal = (result: DropResult) => {
112
119
  // Dropped outside the list
113
120
  if (!result.destination) {
114
121
  return;
@@ -125,6 +132,9 @@ export function DnDList<
125
132
 
126
133
  // Update the state
127
134
  changeItems(newItems);
135
+
136
+ // Drag end handler
137
+ if (onDragEnd) onDragEnd(newItems);
128
138
  };
129
139
 
130
140
  // Add item
@@ -146,7 +156,11 @@ export function DnDList<
146
156
  // Edit item
147
157
  const editItem = (newItem: D, index: number) => {
148
158
  // Existence check
149
- if (items.some((item) => item[labelField] === newItem[labelField])) {
159
+ const newIndex = items.findIndex(
160
+ (item) => item[labelField] === newItem[labelField]
161
+ );
162
+ if (newIndex >= 0 && newIndex != index) {
163
+ // Label field is the same with a different item
150
164
  return false;
151
165
  }
152
166
 
@@ -197,17 +211,23 @@ export function DnDList<
197
211
  changeItems(newItems);
198
212
  };
199
213
 
200
- React.useEffect(() => {
214
+ // Reload items
215
+ const reloadItems = () => {
201
216
  // Load data
202
217
  loadData(name).then((items) => setItems(items));
218
+ };
219
+
220
+ React.useEffect(() => {
221
+ // Load data
222
+ reloadItems();
203
223
  }, [name]);
204
224
 
205
225
  // Layout
206
226
  return (
207
227
  <React.Fragment>
208
- {sideRenderer && sideRenderer(true, addItem, addItems)}
228
+ {sideRenderer && sideRenderer(true, addItem, addItems, reloadItems)}
209
229
  <Component {...componentProps}>
210
- <DragDropContext onDragEnd={onDragEnd}>
230
+ <DragDropContext onDragEnd={onDragEndLocal}>
211
231
  <Droppable droppableId={name}>
212
232
  {(provided, snapshot) => (
213
233
  <div
@@ -261,7 +281,8 @@ export function DnDList<
261
281
  </Droppable>
262
282
  </DragDropContext>
263
283
  </Component>
264
- {sideRenderer && sideRenderer(false, addItem, addItems)}
284
+ {sideRenderer &&
285
+ sideRenderer(false, addItem, addItems, reloadItems)}
265
286
  </React.Fragment>
266
287
  );
267
288
  }
@@ -0,0 +1,106 @@
1
+ import { Card, CardContent, CardHeader, LinearProgress } from '@mui/material';
2
+ import React from 'react';
3
+ import { ListItemReact } from '../components/ListItemReact';
4
+ import { MoreFab } from './MoreFab';
5
+ import { MUGlobal } from './MUGlobal';
6
+ import { ScrollerListExInnerItemRendererProps } from './ScrollerListEx';
7
+
8
+ function getActions(input: (ListItemReact | boolean)[]): ListItemReact[] {
9
+ // Actions
10
+ const actions: ListItemReact[] = [];
11
+ input.forEach((action) => {
12
+ if (typeof action === 'boolean') return;
13
+ actions.push(action);
14
+ });
15
+ return actions;
16
+ }
17
+
18
+ export function MobileListItemRenderer<T>(
19
+ { data, itemHeight }: ScrollerListExInnerItemRendererProps<T>,
20
+ margin: {},
21
+ renderer: (
22
+ data: T
23
+ ) => [
24
+ string,
25
+ string | undefined,
26
+ React.ReactNode | (ListItemReact | boolean)[],
27
+ React.ReactNode
28
+ ]
29
+ ) {
30
+ // Loading
31
+ if (data == null) return <LinearProgress />;
32
+
33
+ // Elements
34
+ const [title, subheader, actions, children] = renderer(data);
35
+
36
+ // Half
37
+ const halfMargin = MUGlobal.half(margin);
38
+
39
+ return (
40
+ <Card
41
+ sx={{
42
+ height: (theme) =>
43
+ MUGlobal.adjustWithTheme(itemHeight, margin, theme.spacing),
44
+ marginLeft: margin,
45
+ marginRight: margin,
46
+ marginTop: halfMargin,
47
+ marginBottom: halfMargin
48
+ }}
49
+ >
50
+ <CardHeader
51
+ sx={{ paddingBottom: 0.5 }}
52
+ action={
53
+ Array.isArray(actions) ? (
54
+ <MoreFab
55
+ iconButton
56
+ size="small"
57
+ anchorOrigin={{
58
+ vertical: 'bottom',
59
+ horizontal: 'right'
60
+ }}
61
+ transformOrigin={{
62
+ vertical: 'top',
63
+ horizontal: 'right'
64
+ }}
65
+ actions={getActions(actions)}
66
+ PaperProps={{
67
+ elevation: 0,
68
+ sx: {
69
+ overflow: 'visible',
70
+ filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
71
+ mt: -0.4,
72
+ '& .MuiAvatar-root': {
73
+ width: 32,
74
+ height: 32,
75
+ ml: -0.5,
76
+ mr: 1
77
+ },
78
+ '&:before': {
79
+ content: '""',
80
+ display: 'block',
81
+ position: 'absolute',
82
+ top: 0,
83
+ right: 14,
84
+ width: 10,
85
+ height: 10,
86
+ bgcolor: 'background.paper',
87
+ transform:
88
+ 'translateY(-50%) rotate(45deg)',
89
+ zIndex: 0
90
+ }
91
+ }
92
+ }}
93
+ />
94
+ ) : (
95
+ actions
96
+ )
97
+ }
98
+ title={title}
99
+ titleTypographyProps={{ variant: 'body2' }}
100
+ subheader={subheader}
101
+ subheaderTypographyProps={{ variant: 'caption' }}
102
+ />
103
+ <CardContent sx={{ paddingTop: 0 }}>{children}</CardContent>
104
+ </Card>
105
+ );
106
+ }