@etsoo/materialui 1.2.53 → 1.2.54
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/ListChooser.d.ts +7 -3
- package/lib/ListChooser.js +25 -11
- package/package.json +1 -1
- package/src/ListChooser.tsx +203 -176
package/lib/ListChooser.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { DataTypes, DelayedExecutorType, IdDefaultType } from
|
|
2
|
-
import { ListItemButtonProps, ListProps } from
|
|
3
|
-
import React from
|
|
1
|
+
import { DataTypes, DelayedExecutorType, IdDefaultType } from "@etsoo/shared";
|
|
2
|
+
import { ListItemButtonProps, ListProps } from "@mui/material";
|
|
3
|
+
import React from "react";
|
|
4
4
|
type QueryData = {
|
|
5
5
|
title?: string;
|
|
6
6
|
};
|
|
@@ -46,6 +46,10 @@ export type ListChooserProps<T extends object, D extends DataTypes.Keys<T>, Q ex
|
|
|
46
46
|
* Title
|
|
47
47
|
*/
|
|
48
48
|
title: string;
|
|
49
|
+
/**
|
|
50
|
+
* Double click enabled
|
|
51
|
+
*/
|
|
52
|
+
doubleClickEnabled?: boolean;
|
|
49
53
|
};
|
|
50
54
|
/**
|
|
51
55
|
* List chooser
|
package/lib/ListChooser.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { useDelayedExecutor } from
|
|
2
|
-
import { List, ListItem, ListItemButton, ListItemText, TextField } from
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import { useDelayedExecutor } from "@etsoo/react";
|
|
2
|
+
import { List, ListItem, ListItemButton, ListItemText, TextField } from "@mui/material";
|
|
3
|
+
import CheckBoxIcon from "@mui/icons-material/CheckBox";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { VBox } from "./FlexBox";
|
|
5
6
|
/**
|
|
6
7
|
* List chooser
|
|
7
8
|
* @param props Props
|
|
@@ -29,19 +30,20 @@ export function ListChooser(props) {
|
|
|
29
30
|
});
|
|
30
31
|
// Destruct
|
|
31
32
|
const { conditionRenderer = (rq, delayed) => (React.createElement(TextField, { autoFocus: true, margin: "dense", name: "title", label: title, fullWidth: true, variant: "standard", inputProps: { maxLength: 128 }, onChange: (event) => {
|
|
32
|
-
Reflect.set(rq,
|
|
33
|
+
Reflect.set(rq, "title", event.target.value);
|
|
33
34
|
delayed.call();
|
|
34
35
|
} })), itemRenderer = (item, selectProps) => {
|
|
35
36
|
const id = item[idField];
|
|
36
|
-
const
|
|
37
|
+
const sp = selectProps(id);
|
|
38
|
+
const label = typeof labelField === "function"
|
|
37
39
|
? labelField(item)
|
|
38
40
|
: Reflect.get(item, labelField);
|
|
39
|
-
return (React.createElement(ListItem, { disableGutters: true, key: `${id}
|
|
40
|
-
React.createElement(ListItemButton, { ...
|
|
41
|
+
return (React.createElement(ListItem, { disableGutters: true, key: `${id}`, secondaryAction: sp.selected ? React.createElement(CheckBoxIcon, { fontSize: "small" }) : undefined },
|
|
42
|
+
React.createElement(ListItemButton, { ...sp },
|
|
41
43
|
React.createElement(ListItemText, { primary: label }))));
|
|
42
|
-
}, idField =
|
|
44
|
+
}, idField = "id", labelField = "label", loadData, multiple = false, onItemChange, title, doubleClickEnabled = false, onDoubleClick, ...rest } = props;
|
|
43
45
|
// Default minimum height
|
|
44
|
-
(_a = rest.sx) !== null && _a !== void 0 ? _a : (rest.sx = { minHeight:
|
|
46
|
+
(_a = rest.sx) !== null && _a !== void 0 ? _a : (rest.sx = { minHeight: "220px" });
|
|
45
47
|
// State
|
|
46
48
|
const [items, setItems] = React.useState([]);
|
|
47
49
|
// Query request data
|
|
@@ -72,8 +74,20 @@ export function ListChooser(props) {
|
|
|
72
74
|
delayed.clear();
|
|
73
75
|
};
|
|
74
76
|
}, [delayed]);
|
|
77
|
+
const onDoubleClickLocal = (event) => {
|
|
78
|
+
var _a;
|
|
79
|
+
if (onDoubleClick)
|
|
80
|
+
onDoubleClick(event);
|
|
81
|
+
if (doubleClickEnabled) {
|
|
82
|
+
const button = (_a = event.currentTarget
|
|
83
|
+
.closest("form")) === null || _a === void 0 ? void 0 : _a.elements.namedItem("okButton");
|
|
84
|
+
if (button) {
|
|
85
|
+
button.click();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
75
89
|
// Layout
|
|
76
90
|
return (React.createElement(VBox, null,
|
|
77
91
|
conditionRenderer(rq.current, delayed),
|
|
78
|
-
React.createElement(List, { disablePadding: true, dense: true, ...rest }, items.map((item) => itemRenderer(item, selectProps)))));
|
|
92
|
+
React.createElement(List, { onDoubleClick: onDoubleClickLocal, disablePadding: true, dense: true, ...rest }, items.map((item) => itemRenderer(item, selectProps)))));
|
|
79
93
|
}
|
package/package.json
CHANGED
package/src/ListChooser.tsx
CHANGED
|
@@ -1,84 +1,90 @@
|
|
|
1
|
-
import { useDelayedExecutor } from
|
|
2
|
-
import { DataTypes, DelayedExecutorType, IdDefaultType } from
|
|
1
|
+
import { useDelayedExecutor } from "@etsoo/react";
|
|
2
|
+
import { DataTypes, DelayedExecutorType, IdDefaultType } from "@etsoo/shared";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from
|
|
12
|
-
import
|
|
13
|
-
import
|
|
4
|
+
List,
|
|
5
|
+
ListItem,
|
|
6
|
+
ListItemButton,
|
|
7
|
+
ListItemButtonProps,
|
|
8
|
+
ListItemText,
|
|
9
|
+
ListProps,
|
|
10
|
+
TextField
|
|
11
|
+
} from "@mui/material";
|
|
12
|
+
import CheckBoxIcon from "@mui/icons-material/CheckBox";
|
|
13
|
+
import React from "react";
|
|
14
|
+
import { VBox } from "./FlexBox";
|
|
14
15
|
|
|
15
16
|
type QueryData = {
|
|
16
|
-
|
|
17
|
+
title?: string;
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* List chooser button props
|
|
21
22
|
*/
|
|
22
23
|
export interface ListChooserButtonProps<
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
T extends object,
|
|
25
|
+
D extends DataTypes.Keys<T>
|
|
25
26
|
> {
|
|
26
|
-
|
|
27
|
+
(id: T[D]): ListItemButtonProps;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* List chooser props
|
|
31
32
|
*/
|
|
32
33
|
export type ListChooserProps<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
T extends object,
|
|
35
|
+
D extends DataTypes.Keys<T>,
|
|
36
|
+
Q extends object
|
|
36
37
|
> = ListProps & {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Condition renderer
|
|
40
|
+
*/
|
|
41
|
+
conditionRenderer?: (
|
|
42
|
+
rq: Partial<Q>,
|
|
43
|
+
delayed: DelayedExecutorType
|
|
44
|
+
) => React.ReactNode;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* List item renderer
|
|
48
|
+
*/
|
|
49
|
+
itemRenderer?: (
|
|
50
|
+
data: T,
|
|
51
|
+
props: ListChooserButtonProps<T, D>
|
|
52
|
+
) => React.ReactNode;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Label field
|
|
56
|
+
*/
|
|
57
|
+
labelField?: DataTypes.Keys<T, string> | ((data: T) => string);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Id field
|
|
61
|
+
*/
|
|
62
|
+
idField?: D;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Load data callback
|
|
66
|
+
*/
|
|
67
|
+
loadData: (rq: Partial<Q>) => PromiseLike<T[] | null | undefined>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Multiple selected
|
|
71
|
+
*/
|
|
72
|
+
multiple?: boolean;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Item onchange callback
|
|
76
|
+
*/
|
|
77
|
+
onItemChange: (items: T[], ids: T[D][]) => void;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Title
|
|
81
|
+
*/
|
|
82
|
+
title: string;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Double click enabled
|
|
86
|
+
*/
|
|
87
|
+
doubleClickEnabled?: boolean;
|
|
82
88
|
};
|
|
83
89
|
|
|
84
90
|
/**
|
|
@@ -87,118 +93,139 @@ export type ListChooserProps<
|
|
|
87
93
|
* @returns Component
|
|
88
94
|
*/
|
|
89
95
|
export function ListChooser<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
T extends object,
|
|
97
|
+
D extends DataTypes.Keys<T> = IdDefaultType<T>,
|
|
98
|
+
Q extends object = QueryData
|
|
93
99
|
>(props: ListChooserProps<T, D, Q>) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
};
|
|
193
|
-
}, [delayed]);
|
|
194
|
-
|
|
195
|
-
// Layout
|
|
196
|
-
return (
|
|
197
|
-
<VBox>
|
|
198
|
-
{conditionRenderer(rq.current, delayed)}
|
|
199
|
-
<List disablePadding dense {...rest}>
|
|
200
|
-
{items.map((item) => itemRenderer(item, selectProps))}
|
|
201
|
-
</List>
|
|
202
|
-
</VBox>
|
|
100
|
+
// Selected ids state
|
|
101
|
+
const [selectedIds, setSelectedIds] = React.useState<T[D][]>([]);
|
|
102
|
+
|
|
103
|
+
const selectProps: ListChooserButtonProps<T, D> = (id: T[D]) => ({
|
|
104
|
+
selected: selectedIds.includes(id),
|
|
105
|
+
onClick: () => {
|
|
106
|
+
if (multiple) {
|
|
107
|
+
const index = selectedIds.indexOf(id);
|
|
108
|
+
if (index === -1) selectedIds.push(id);
|
|
109
|
+
else selectedIds.splice(index, 1);
|
|
110
|
+
setSelectedIds([...selectedIds]);
|
|
111
|
+
} else {
|
|
112
|
+
setSelectedIds([id]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Destruct
|
|
118
|
+
const {
|
|
119
|
+
conditionRenderer = (rq: Partial<Q>, delayed: DelayedExecutorType) => (
|
|
120
|
+
<TextField
|
|
121
|
+
autoFocus
|
|
122
|
+
margin="dense"
|
|
123
|
+
name="title"
|
|
124
|
+
label={title}
|
|
125
|
+
fullWidth
|
|
126
|
+
variant="standard"
|
|
127
|
+
inputProps={{ maxLength: 128 }}
|
|
128
|
+
onChange={(event) => {
|
|
129
|
+
Reflect.set(rq, "title", event.target.value);
|
|
130
|
+
delayed.call();
|
|
131
|
+
}}
|
|
132
|
+
/>
|
|
133
|
+
),
|
|
134
|
+
itemRenderer = (item, selectProps) => {
|
|
135
|
+
const id = item[idField];
|
|
136
|
+
const sp = selectProps(id);
|
|
137
|
+
const label =
|
|
138
|
+
typeof labelField === "function"
|
|
139
|
+
? labelField(item)
|
|
140
|
+
: (Reflect.get(item, labelField) as React.ReactNode);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<ListItem
|
|
144
|
+
disableGutters
|
|
145
|
+
key={`${id}`}
|
|
146
|
+
secondaryAction={
|
|
147
|
+
sp.selected ? <CheckBoxIcon fontSize="small" /> : undefined
|
|
148
|
+
}
|
|
149
|
+
>
|
|
150
|
+
<ListItemButton {...sp}>
|
|
151
|
+
<ListItemText primary={label} />
|
|
152
|
+
</ListItemButton>
|
|
153
|
+
</ListItem>
|
|
154
|
+
);
|
|
155
|
+
},
|
|
156
|
+
idField = "id" as D,
|
|
157
|
+
labelField = "label",
|
|
158
|
+
loadData,
|
|
159
|
+
multiple = false,
|
|
160
|
+
onItemChange,
|
|
161
|
+
title,
|
|
162
|
+
doubleClickEnabled = false,
|
|
163
|
+
onDoubleClick,
|
|
164
|
+
...rest
|
|
165
|
+
} = props;
|
|
166
|
+
|
|
167
|
+
// Default minimum height
|
|
168
|
+
rest.sx ??= { minHeight: "220px" };
|
|
169
|
+
|
|
170
|
+
// State
|
|
171
|
+
const [items, setItems] = React.useState<T[]>([]);
|
|
172
|
+
|
|
173
|
+
// Query request data
|
|
174
|
+
const mounted = React.useRef<boolean>(false);
|
|
175
|
+
const rq = React.useRef<Partial<Q>>({});
|
|
176
|
+
|
|
177
|
+
// Delayed execution
|
|
178
|
+
const delayed = useDelayedExecutor(async () => {
|
|
179
|
+
const result = await loadData(rq.current);
|
|
180
|
+
if (result == null || !mounted.current) return;
|
|
181
|
+
|
|
182
|
+
if (
|
|
183
|
+
!multiple &&
|
|
184
|
+
selectedIds.length > 0 &&
|
|
185
|
+
!result.some((item) => selectedIds.includes(item[idField]))
|
|
186
|
+
) {
|
|
187
|
+
setSelectedIds([]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
setItems(result);
|
|
191
|
+
}, 480);
|
|
192
|
+
|
|
193
|
+
React.useEffect(() => {
|
|
194
|
+
if (!mounted.current) return;
|
|
195
|
+
onItemChange(
|
|
196
|
+
items.filter((item) => selectedIds.includes(item[idField])),
|
|
197
|
+
selectedIds
|
|
203
198
|
);
|
|
199
|
+
}, [selectedIds]);
|
|
200
|
+
|
|
201
|
+
React.useEffect(() => {
|
|
202
|
+
mounted.current = true;
|
|
203
|
+
delayed.call(0);
|
|
204
|
+
return () => {
|
|
205
|
+
mounted.current = false;
|
|
206
|
+
delayed.clear();
|
|
207
|
+
};
|
|
208
|
+
}, [delayed]);
|
|
209
|
+
|
|
210
|
+
const onDoubleClickLocal = (event: React.MouseEvent<HTMLUListElement>) => {
|
|
211
|
+
if (onDoubleClick) onDoubleClick(event);
|
|
212
|
+
if (doubleClickEnabled) {
|
|
213
|
+
const button = event.currentTarget
|
|
214
|
+
.closest("form")
|
|
215
|
+
?.elements.namedItem("okButton") as HTMLButtonElement | undefined;
|
|
216
|
+
if (button) {
|
|
217
|
+
button.click();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Layout
|
|
223
|
+
return (
|
|
224
|
+
<VBox>
|
|
225
|
+
{conditionRenderer(rq.current, delayed)}
|
|
226
|
+
<List onDoubleClick={onDoubleClickLocal} disablePadding dense {...rest}>
|
|
227
|
+
{items.map((item) => itemRenderer(item, selectProps))}
|
|
228
|
+
</List>
|
|
229
|
+
</VBox>
|
|
230
|
+
);
|
|
204
231
|
}
|