@rainbow-oh/yee-c 0.9.1 → 0.10.0
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/dist/cjs/Badge/badge.js +5 -3
- package/dist/cjs/Badge/interface.d.ts +4 -0
- package/dist/cjs/Input/style/index.less +1 -0
- package/dist/cjs/Popconfirm/interface.d.ts +0 -4
- package/dist/cjs/Select/select.js +2 -1
- package/dist/cjs/TableSelect/table-select.js +2 -2
- package/dist/cjs/Tabs/demo/overflow-center.d.ts +3 -0
- package/dist/cjs/Tabs/demo/overflow-center.js +54 -0
- package/dist/cjs/Tabs/hooks/useNavListMove.d.ts +2 -1
- package/dist/cjs/Tabs/hooks/useNavListMove.js +51 -16
- package/dist/cjs/Tabs/hooks/useTabScroll.d.ts +1 -0
- package/dist/cjs/Tabs/hooks/useTabScroll.js +5 -1
- package/dist/cjs/Tabs/interface.d.ts +9 -0
- package/dist/cjs/Tabs/style/index.less +5 -0
- package/dist/cjs/Tabs/tab-header/index.d.ts +1 -0
- package/dist/cjs/Tabs/tab-header/index.js +9 -2
- package/dist/cjs/Tabs/tabs.js +4 -1
- package/dist/cjs/TreeSelect/tree-select.js +1 -1
- package/dist/cjs/Upload/demo/auto-upload.d.ts +3 -0
- package/dist/cjs/Upload/demo/auto-upload.js +65 -0
- package/dist/cjs/Upload/demo/basic.js +5 -10
- package/dist/cjs/Upload/demo/controlled.js +4 -4
- package/dist/cjs/Upload/demo/pictureWall.js +14 -13
- package/dist/cjs/Upload/dragger.d.ts +2 -1
- package/dist/cjs/Upload/dragger.js +32 -21
- package/dist/cjs/Upload/index.d.ts +1 -1
- package/dist/cjs/Upload/interface.d.ts +67 -120
- package/dist/cjs/Upload/style/index.less +40 -13
- package/dist/cjs/Upload/upload.d.ts +2 -2
- package/dist/cjs/Upload/upload.js +435 -185
- package/dist/cjs/Upload/uploaded-list/index.d.ts +2 -1
- package/dist/cjs/Upload/uploaded-list/index.js +6 -3
- package/dist/cjs/Upload/uploaded-list/uploaded-item.d.ts +6 -3
- package/dist/cjs/Upload/uploaded-list/uploaded-item.js +127 -138
- package/dist/cjs/style/size.css +1 -1
- package/dist/cjs/style/themes.css +1 -1
- package/dist/cjs/style/variables.css +2 -2
- package/dist/cjs/utils/ax/core.d.ts +3 -1
- package/dist/cjs/utils/ax/core.js +24 -37
- package/dist/cjs/utils/ax/index.d.ts +3 -1
- package/dist/cjs/utils/folder-scanner.d.ts +1 -1
- package/dist/cjs/utils/folder-scanner.js +43 -34
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/Badge/badge.js +3 -2
- package/dist/esm/Badge/interface.d.ts +4 -0
- package/dist/esm/Input/style/index.less +1 -0
- package/dist/esm/Popconfirm/interface.d.ts +0 -4
- package/dist/esm/Select/select.js +2 -1
- package/dist/esm/TableSelect/table-select.js +2 -2
- package/dist/esm/Tabs/demo/overflow-center.d.ts +3 -0
- package/dist/esm/Tabs/demo/overflow-center.js +61 -0
- package/dist/esm/Tabs/hooks/useNavListMove.d.ts +2 -1
- package/dist/esm/Tabs/hooks/useNavListMove.js +62 -25
- package/dist/esm/Tabs/hooks/useTabScroll.d.ts +1 -0
- package/dist/esm/Tabs/hooks/useTabScroll.js +9 -0
- package/dist/esm/Tabs/interface.d.ts +9 -0
- package/dist/esm/Tabs/style/index.less +5 -0
- package/dist/esm/Tabs/tab-header/index.d.ts +1 -0
- package/dist/esm/Tabs/tab-header/index.js +13 -2
- package/dist/esm/Tabs/tabs.js +4 -2
- package/dist/esm/TreeSelect/tree-select.js +1 -1
- package/dist/esm/Upload/demo/auto-upload.d.ts +3 -0
- package/dist/esm/Upload/demo/auto-upload.js +42 -0
- package/dist/esm/Upload/demo/basic.js +5 -11
- package/dist/esm/Upload/demo/controlled.js +8 -6
- package/dist/esm/Upload/demo/pictureWall.js +17 -14
- package/dist/esm/Upload/dragger.d.ts +2 -1
- package/dist/esm/Upload/dragger.js +10 -5
- package/dist/esm/Upload/index.d.ts +1 -1
- package/dist/esm/Upload/interface.d.ts +67 -120
- package/dist/esm/Upload/style/index.less +40 -13
- package/dist/esm/Upload/upload.d.ts +2 -2
- package/dist/esm/Upload/upload.js +573 -271
- package/dist/esm/Upload/uploaded-list/index.d.ts +2 -1
- package/dist/esm/Upload/uploaded-list/index.js +9 -4
- package/dist/esm/Upload/uploaded-list/uploaded-item.d.ts +6 -3
- package/dist/esm/Upload/uploaded-list/uploaded-item.js +145 -150
- package/dist/esm/style/size.css +1 -1
- package/dist/esm/style/themes.css +1 -1
- package/dist/esm/style/variables.css +2 -2
- package/dist/esm/utils/ax/core.d.ts +3 -1
- package/dist/esm/utils/ax/core.js +32 -40
- package/dist/esm/utils/ax/index.d.ts +3 -1
- package/dist/esm/utils/folder-scanner.d.ts +1 -1
- package/dist/esm/utils/folder-scanner.js +76 -75
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/umd/yee-c.min.css +2 -2
- package/dist/umd/yee-c.min.js +1 -1
- package/package.json +1 -1
- package/src/Badge/badge.tsx +2 -1
- package/src/Badge/interface.ts +4 -0
- package/src/Input/style/index.less +1 -0
- package/src/Popconfirm/interface.ts +0 -4
- package/src/Select/select.tsx +2 -1
- package/src/TableSelect/table-select.tsx +1 -0
- package/src/Tabs/demo/overflow-center.tsx +35 -0
- package/src/Tabs/hooks/useNavListMove.ts +60 -18
- package/src/Tabs/hooks/useTabScroll.ts +7 -1
- package/src/Tabs/index.en-US.md +33 -31
- package/src/Tabs/index.zh-CN.md +33 -31
- package/src/Tabs/interface.ts +9 -0
- package/src/Tabs/style/index.less +5 -0
- package/src/Tabs/tab-header/index.tsx +12 -2
- package/src/Tabs/tabs.tsx +2 -0
- package/src/TreeSelect/tree-select.tsx +1 -1
- package/src/Upload/demo/auto-upload.tsx +35 -0
- package/src/Upload/demo/basic.tsx +7 -11
- package/src/Upload/demo/controlled.tsx +12 -8
- package/src/Upload/demo/directory.tsx +3 -4
- package/src/Upload/demo/drag.tsx +5 -12
- package/src/Upload/demo/multiple.tsx +3 -4
- package/src/Upload/demo/pictureWall.tsx +22 -21
- package/src/Upload/dragger.tsx +47 -34
- package/src/Upload/index.en-US.md +63 -43
- package/src/Upload/index.ts +8 -1
- package/src/Upload/index.zh-CN.md +62 -42
- package/src/Upload/interface.ts +68 -120
- package/src/Upload/style/index.less +40 -13
- package/src/Upload/upload.tsx +546 -215
- package/src/Upload/uploaded-list/index.tsx +9 -5
- package/src/Upload/uploaded-list/uploaded-item.tsx +199 -228
- package/src/style/size.css +1 -1
- package/src/utils/ax/core.ts +28 -41
- package/src/utils/folder-scanner.ts +51 -37
- package/src/version.ts +1 -1
package/src/Upload/upload.tsx
CHANGED
|
@@ -1,33 +1,129 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
2
|
import { Plus } from 'lucide-react';
|
|
3
|
-
import React, {
|
|
3
|
+
import React, {
|
|
4
|
+
forwardRef,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useImperativeHandle,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
} from 'react';
|
|
4
11
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
12
|
import Button from '../Button';
|
|
6
13
|
import { GlobalContext } from '../Config-Provider';
|
|
7
|
-
import mergeContextToProps from '../utils/mergeContextToProps';
|
|
8
|
-
import UploadList from './uploaded-list/index';
|
|
9
|
-
|
|
10
14
|
import useMergedState from '../hooks/useMergedState';
|
|
11
15
|
import ax from '../utils/ax';
|
|
12
16
|
import folderScanner from '../utils/folder-scanner';
|
|
13
|
-
import
|
|
17
|
+
import mergeContextToProps from '../utils/mergeContextToProps';
|
|
18
|
+
import type {
|
|
19
|
+
UploadFile,
|
|
20
|
+
UploadInstance,
|
|
21
|
+
UploadProps,
|
|
22
|
+
UploadRequestAbort,
|
|
23
|
+
} from './interface';
|
|
14
24
|
import './style/index.less';
|
|
25
|
+
import UploadList from './uploaded-list/index';
|
|
26
|
+
|
|
27
|
+
type ActiveRequest = {
|
|
28
|
+
token: symbol;
|
|
29
|
+
abort?: () => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getFileExtension = (fileName: string) => {
|
|
33
|
+
const dotIndex = fileName.lastIndexOf('.');
|
|
34
|
+
return dotIndex > -1 ? fileName.slice(dotIndex + 1).toLowerCase() : '';
|
|
35
|
+
};
|
|
15
36
|
|
|
16
37
|
const wrapFile = (file: File): UploadFile => {
|
|
17
38
|
return {
|
|
18
39
|
uid: uuidv4(),
|
|
19
40
|
name: file.name,
|
|
20
41
|
size: file.size,
|
|
21
|
-
type:
|
|
42
|
+
type: file.type || getFileExtension(file.name),
|
|
22
43
|
percent: 0,
|
|
23
44
|
status: 'ready',
|
|
24
45
|
raw: file,
|
|
25
46
|
};
|
|
26
47
|
};
|
|
27
48
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
49
|
+
const isSameFile = (left: UploadFile, right: UploadFile) => {
|
|
50
|
+
if (left.uid && right.uid) {
|
|
51
|
+
return left.uid === right.uid;
|
|
52
|
+
}
|
|
53
|
+
return left === right;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const matchesAccept = (file: File, accept?: string) => {
|
|
57
|
+
if (!accept) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const fileName = file.name.toLowerCase();
|
|
62
|
+
const mimeType = file.type.toLowerCase();
|
|
63
|
+
|
|
64
|
+
return accept
|
|
65
|
+
.split(',')
|
|
66
|
+
.map((item) => item.trim().toLowerCase())
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
.some((rule) => {
|
|
69
|
+
if (rule.startsWith('.')) {
|
|
70
|
+
return fileName.endsWith(rule);
|
|
71
|
+
}
|
|
72
|
+
if (rule.endsWith('/*')) {
|
|
73
|
+
return mimeType.startsWith(rule.slice(0, -1));
|
|
74
|
+
}
|
|
75
|
+
return mimeType === rule;
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const describeErrorDetail = (detail: unknown) => {
|
|
80
|
+
if (typeof detail === 'string' && detail) {
|
|
81
|
+
return detail;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const text = JSON.stringify(detail) ?? '';
|
|
85
|
+
return text.length > 200 ? `${text.slice(0, 200)}…` : text;
|
|
86
|
+
} catch {
|
|
87
|
+
return String(detail);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const toError = (error: unknown) => {
|
|
92
|
+
if (error instanceof Error) {
|
|
93
|
+
return error;
|
|
94
|
+
}
|
|
95
|
+
if (typeof error === 'object' && error !== null && 'error' in error) {
|
|
96
|
+
const detail = (error as { error: unknown }).error;
|
|
97
|
+
if (detail instanceof Error) {
|
|
98
|
+
return detail;
|
|
99
|
+
}
|
|
100
|
+
if (detail !== null && detail !== undefined) {
|
|
101
|
+
// e.g. ax rejects { status: 'error', error: xhr.response }
|
|
102
|
+
const message = describeErrorDetail(detail);
|
|
103
|
+
if (message) {
|
|
104
|
+
return new Error(message);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return new Error(typeof error === 'string' ? error : 'Upload failed');
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// ax rejects aborts as { status: 'abort', error: event }
|
|
112
|
+
const isAbortRejection = (error: unknown) =>
|
|
113
|
+
typeof error === 'object' &&
|
|
114
|
+
error !== null &&
|
|
115
|
+
(error as { status?: unknown }).status === 'abort';
|
|
116
|
+
|
|
117
|
+
const getAbort = (request?: UploadRequestAbort | void) => {
|
|
118
|
+
if (typeof request === 'function') {
|
|
119
|
+
return request;
|
|
120
|
+
}
|
|
121
|
+
return request?.abort;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const Upload = forwardRef<UploadInstance, UploadProps>((baseprops, ref) => {
|
|
125
|
+
const { upload: uploadConfig } = useContext(GlobalContext);
|
|
126
|
+
const props = mergeContextToProps(baseprops, uploadConfig);
|
|
31
127
|
const {
|
|
32
128
|
prefixCls = 'yee-upload',
|
|
33
129
|
name = 'file',
|
|
@@ -48,6 +144,7 @@ const Upload = forwardRef<HTMLDivElement, UploadProps>((baseprops, ref) => {
|
|
|
48
144
|
accept,
|
|
49
145
|
showUploadList = true,
|
|
50
146
|
progress = true,
|
|
147
|
+
autoUpload = true,
|
|
51
148
|
maxFileSize,
|
|
52
149
|
maxCount,
|
|
53
150
|
customRequest,
|
|
@@ -60,255 +157,517 @@ const Upload = forwardRef<HTMLDivElement, UploadProps>((baseprops, ref) => {
|
|
|
60
157
|
} = props;
|
|
61
158
|
|
|
62
159
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
160
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
161
|
+
const activeRequestsRef = useRef<Map<string, ActiveRequest>>(new Map());
|
|
162
|
+
const pendingPreparationsRef = useRef<Map<string, symbol>>(new Map());
|
|
163
|
+
const mountedRef = useRef(true);
|
|
63
164
|
const [dragState, setDragState] = useState<'dragover' | 'drop' | null>(null);
|
|
64
165
|
|
|
65
|
-
const [mergedFileList, setMergedFileList] = useMergedState([], {
|
|
166
|
+
const [mergedFileList, setMergedFileList] = useMergedState<UploadFile[]>([], {
|
|
66
167
|
value: propsFileList,
|
|
67
168
|
defaultValue: defaultFileList,
|
|
68
169
|
});
|
|
170
|
+
const fileListRef = useRef(mergedFileList);
|
|
171
|
+
fileListRef.current = mergedFileList;
|
|
172
|
+
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
return () => {
|
|
175
|
+
mountedRef.current = false;
|
|
176
|
+
pendingPreparationsRef.current.clear();
|
|
177
|
+
const activeRequests = Array.from(activeRequestsRef.current.values());
|
|
178
|
+
activeRequestsRef.current.clear();
|
|
179
|
+
activeRequests.forEach(({ abort }) => abort?.());
|
|
180
|
+
};
|
|
181
|
+
}, []);
|
|
69
182
|
|
|
70
|
-
|
|
183
|
+
const setFileList = (nextFileList: UploadFile[]) => {
|
|
184
|
+
fileListRef.current = nextFileList;
|
|
185
|
+
setMergedFileList(nextFileList);
|
|
186
|
+
};
|
|
71
187
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
188
|
+
const emitChange = (
|
|
189
|
+
file: UploadFile,
|
|
190
|
+
nextFileList: UploadFile[],
|
|
191
|
+
event?: any,
|
|
192
|
+
) => {
|
|
193
|
+
setFileList(nextFileList);
|
|
194
|
+
onChange?.({ file, fileList: nextFileList, event });
|
|
195
|
+
};
|
|
79
196
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return file;
|
|
85
|
-
}
|
|
86
|
-
return item;
|
|
87
|
-
});
|
|
88
|
-
});
|
|
197
|
+
const appendFile = (file: UploadFile, event?: any) => {
|
|
198
|
+
const nextFileList = [...fileListRef.current, file];
|
|
199
|
+
emitChange(file, nextFileList, event);
|
|
200
|
+
return file;
|
|
89
201
|
};
|
|
90
202
|
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
203
|
+
const replaceFile = (
|
|
204
|
+
target: UploadFile,
|
|
205
|
+
replacement: UploadFile,
|
|
206
|
+
event?: any,
|
|
207
|
+
) => {
|
|
208
|
+
const index = fileListRef.current.findIndex((item) =>
|
|
209
|
+
isSameFile(item, target),
|
|
210
|
+
);
|
|
211
|
+
if (index < 0) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const nextFileList = [...fileListRef.current];
|
|
216
|
+
nextFileList[index] = replacement;
|
|
217
|
+
emitChange(replacement, nextFileList, event);
|
|
218
|
+
return replacement;
|
|
219
|
+
};
|
|
94
220
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
221
|
+
const updateFile = (
|
|
222
|
+
target: UploadFile,
|
|
223
|
+
patch: Partial<UploadFile>,
|
|
224
|
+
event?: any,
|
|
225
|
+
) => {
|
|
226
|
+
const current = fileListRef.current.find((item) =>
|
|
227
|
+
isSameFile(item, target),
|
|
228
|
+
);
|
|
229
|
+
if (!current) {
|
|
230
|
+
return undefined;
|
|
100
231
|
}
|
|
232
|
+
return replaceFile(target, { ...current, ...patch }, event);
|
|
233
|
+
};
|
|
101
234
|
|
|
102
|
-
|
|
235
|
+
const abortRequest = (file: UploadFile) => {
|
|
236
|
+
if (!file.uid) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const activeRequest = activeRequestsRef.current.get(file.uid);
|
|
240
|
+
if (activeRequest) {
|
|
241
|
+
activeRequestsRef.current.delete(file.uid);
|
|
242
|
+
activeRequest.abort?.();
|
|
243
|
+
}
|
|
244
|
+
};
|
|
103
245
|
|
|
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
|
-
|
|
246
|
+
const postFile = (sourceFile: UploadFile) => {
|
|
247
|
+
let file = sourceFile;
|
|
248
|
+
if (!file.uid) {
|
|
249
|
+
const withUid = { ...file, uid: uuidv4() };
|
|
250
|
+
file = replaceFile(file, withUid) || withUid;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const requestKey = file.uid as string;
|
|
254
|
+
abortRequest(file);
|
|
255
|
+
const token = Symbol(requestKey);
|
|
256
|
+
activeRequestsRef.current.set(requestKey, { token });
|
|
257
|
+
|
|
258
|
+
const isActive = () =>
|
|
259
|
+
activeRequestsRef.current.get(requestKey)?.token === token;
|
|
260
|
+
const finish = () => {
|
|
261
|
+
if (isActive()) {
|
|
262
|
+
activeRequestsRef.current.delete(requestKey);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
const updateIfActive = (patch: Partial<UploadFile>, event?: any) => {
|
|
266
|
+
if (!isActive()) {
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
return updateFile(file, patch, event);
|
|
270
|
+
};
|
|
271
|
+
const fail = (error: unknown) => {
|
|
272
|
+
// Requests aborted outside of remove/retry/unmount (e.g. by the browser)
|
|
273
|
+
// go back to 'ready' instead of showing an error
|
|
274
|
+
if (isAbortRejection(error)) {
|
|
275
|
+
updateIfActive({ status: 'ready', percent: 0 });
|
|
276
|
+
finish();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const normalizedError = toError(error);
|
|
280
|
+
updateIfActive({ status: 'error', error: normalizedError }, error);
|
|
281
|
+
finish();
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
updateIfActive({ status: 'uploading', percent: 0, error: undefined });
|
|
285
|
+
|
|
286
|
+
if (!file.raw) {
|
|
287
|
+
fail(new Error(`File ${file.name} has no source file to upload`));
|
|
136
288
|
return;
|
|
137
289
|
}
|
|
138
290
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
291
|
+
if (customRequest) {
|
|
292
|
+
try {
|
|
293
|
+
const request = customRequest({
|
|
294
|
+
file: file.raw,
|
|
295
|
+
onProgress: (percent) => {
|
|
296
|
+
const normalizedPercent = Math.min(100, Math.max(0, percent));
|
|
297
|
+
updateIfActive(
|
|
298
|
+
{ status: 'uploading', percent: normalizedPercent },
|
|
299
|
+
{ percent: normalizedPercent },
|
|
300
|
+
);
|
|
301
|
+
},
|
|
302
|
+
onError: fail,
|
|
303
|
+
onSuccess: (response) => {
|
|
304
|
+
updateIfActive(
|
|
305
|
+
{
|
|
306
|
+
status: 'success',
|
|
307
|
+
percent: 100,
|
|
308
|
+
response,
|
|
309
|
+
error: undefined,
|
|
310
|
+
},
|
|
311
|
+
response,
|
|
312
|
+
);
|
|
313
|
+
finish();
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
const activeRequest = activeRequestsRef.current.get(requestKey);
|
|
317
|
+
if (activeRequest?.token === token) {
|
|
318
|
+
activeRequest.abort = getAbort(request);
|
|
155
319
|
}
|
|
320
|
+
} catch (error) {
|
|
321
|
+
fail(error);
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
156
325
|
|
|
157
|
-
|
|
326
|
+
try {
|
|
327
|
+
const url = typeof action === 'function' ? action() : action;
|
|
328
|
+
if (!url) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
'Upload action is required when customRequest is not set',
|
|
331
|
+
);
|
|
332
|
+
}
|
|
158
333
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
})
|
|
166
|
-
.then((res: any) => {
|
|
167
|
-
const current = { ...file, status: 'success' as const, response: res };
|
|
168
|
-
updateFileList(current);
|
|
169
|
-
onChange?.({
|
|
170
|
-
file: current,
|
|
171
|
-
fileList: mergedFileList,
|
|
172
|
-
event: res,
|
|
173
|
-
});
|
|
174
|
-
})
|
|
175
|
-
.catch((err: Error) => {
|
|
176
|
-
const current = { ...file, status: 'error' as const, error: err };
|
|
177
|
-
updateFileList(current);
|
|
178
|
-
|
|
179
|
-
onChange?.({
|
|
180
|
-
file: current,
|
|
181
|
-
fileList: mergedFileList,
|
|
182
|
-
event: err,
|
|
334
|
+
const formData = new FormData();
|
|
335
|
+
formData.append(name, file.raw);
|
|
336
|
+
if (data) {
|
|
337
|
+
const extraData = typeof data === 'function' ? data(file.raw) : data;
|
|
338
|
+
Object.keys(extraData).forEach((key) => {
|
|
339
|
+
formData.append(key, extraData[key]);
|
|
183
340
|
});
|
|
184
|
-
}
|
|
185
|
-
};
|
|
341
|
+
}
|
|
186
342
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
343
|
+
const request = (ax as any).post(url, formData, {
|
|
344
|
+
withCredentials,
|
|
345
|
+
headers: { ...headers },
|
|
346
|
+
onUploadProgress: (event: ProgressEvent) => {
|
|
347
|
+
const percentage = event.total
|
|
348
|
+
? Math.round((event.loaded / event.total) * 100)
|
|
349
|
+
: 0;
|
|
350
|
+
updateIfActive({ status: 'uploading', percent: percentage }, event);
|
|
351
|
+
},
|
|
352
|
+
}) as Promise<any> & { abort?: () => void };
|
|
353
|
+
|
|
354
|
+
const activeRequest = activeRequestsRef.current.get(requestKey);
|
|
355
|
+
if (activeRequest?.token === token) {
|
|
356
|
+
activeRequest.abort = request.abort;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
request
|
|
360
|
+
.then((response) => {
|
|
361
|
+
updateIfActive(
|
|
362
|
+
{
|
|
363
|
+
status: 'success',
|
|
364
|
+
percent: 100,
|
|
365
|
+
response,
|
|
366
|
+
error: undefined,
|
|
367
|
+
},
|
|
368
|
+
response,
|
|
369
|
+
);
|
|
370
|
+
finish();
|
|
371
|
+
})
|
|
372
|
+
.catch(fail);
|
|
373
|
+
} catch (error) {
|
|
374
|
+
fail(error);
|
|
375
|
+
}
|
|
190
376
|
};
|
|
191
377
|
|
|
192
|
-
const isValidFile = (file: File)
|
|
193
|
-
// Check file size
|
|
378
|
+
const isValidFile = (file: File) => {
|
|
194
379
|
if (maxFileSize && file.size > maxFileSize) {
|
|
195
380
|
console.warn(`File ${file.name} exceeds maximum size limit`);
|
|
196
381
|
return false;
|
|
197
382
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (maxCount && mergedFileList.length >= maxCount) {
|
|
201
|
-
console.warn(`Maximum file count (${maxCount}) exceeded`);
|
|
383
|
+
if (!matchesAccept(file, accept)) {
|
|
384
|
+
console.warn(`File ${file.name} does not match accepted file types`);
|
|
202
385
|
return false;
|
|
203
386
|
}
|
|
204
|
-
|
|
205
387
|
return true;
|
|
206
388
|
};
|
|
207
389
|
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
390
|
+
const prepareFile = async (
|
|
391
|
+
file: File,
|
|
392
|
+
wrapped: UploadFile,
|
|
393
|
+
postFiles: File[],
|
|
394
|
+
preparationToken: symbol,
|
|
395
|
+
event?: any,
|
|
396
|
+
) => {
|
|
397
|
+
const preparationKey = wrapped.uid as string;
|
|
398
|
+
const isActive = () =>
|
|
399
|
+
mountedRef.current &&
|
|
400
|
+
pendingPreparationsRef.current.get(preparationKey) === preparationToken &&
|
|
401
|
+
fileListRef.current.some((item) => isSameFile(item, wrapped));
|
|
402
|
+
const finish = () => {
|
|
403
|
+
if (
|
|
404
|
+
pendingPreparationsRef.current.get(preparationKey) === preparationToken
|
|
405
|
+
) {
|
|
406
|
+
pendingPreparationsRef.current.delete(preparationKey);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
try {
|
|
411
|
+
const result = beforeUpload
|
|
412
|
+
? await Promise.resolve(beforeUpload(file, postFiles))
|
|
413
|
+
: true;
|
|
414
|
+
if (!isActive()) {
|
|
415
|
+
finish();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (result === false) {
|
|
419
|
+
// beforeUpload rejected the file: drop it from the list
|
|
420
|
+
setFileList(
|
|
421
|
+
fileListRef.current.filter((item) => !isSameFile(item, wrapped)),
|
|
422
|
+
);
|
|
423
|
+
finish();
|
|
212
424
|
return;
|
|
213
425
|
}
|
|
214
426
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (beforeUpload) {
|
|
223
|
-
const res = beforeUpload(file, postFiles);
|
|
224
|
-
if (res && res instanceof Promise) {
|
|
225
|
-
res.then((progress) => {
|
|
226
|
-
if (progress === false) {
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
post(typeof progress === 'object' ? wrapFile(progress) : wrapped);
|
|
230
|
-
});
|
|
231
|
-
} else if (res !== false) {
|
|
232
|
-
post(wrapped);
|
|
427
|
+
let nextFile = wrapped;
|
|
428
|
+
if (result instanceof File) {
|
|
429
|
+
nextFile = { ...wrapFile(result), uid: wrapped.uid };
|
|
430
|
+
if (!replaceFile(wrapped, nextFile, event)) {
|
|
431
|
+
finish();
|
|
432
|
+
return;
|
|
233
433
|
}
|
|
234
|
-
} else {
|
|
235
|
-
post(wrapped);
|
|
236
434
|
}
|
|
237
|
-
|
|
435
|
+
finish();
|
|
436
|
+
postFile(nextFile);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
if (isActive()) {
|
|
439
|
+
updateFile(wrapped, { status: 'error', error: toError(error) }, error);
|
|
440
|
+
}
|
|
441
|
+
finish();
|
|
442
|
+
}
|
|
238
443
|
};
|
|
239
444
|
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
445
|
+
const uploadFiles = (files: FileList | File[], event?: any) => {
|
|
446
|
+
const selectedFiles = Array.from(files);
|
|
447
|
+
const filesAllowedByMultiple =
|
|
448
|
+
multiple || directory ? selectedFiles : selectedFiles.slice(0, 1);
|
|
449
|
+
const validFiles = filesAllowedByMultiple.filter(isValidFile);
|
|
450
|
+
const remainingCount =
|
|
451
|
+
typeof maxCount === 'number'
|
|
452
|
+
? Math.max(0, maxCount - fileListRef.current.length)
|
|
453
|
+
: validFiles.length;
|
|
454
|
+
const postFiles = validFiles.slice(0, remainingCount);
|
|
455
|
+
|
|
456
|
+
if (postFiles.length < validFiles.length) {
|
|
457
|
+
console.warn(`Maximum file count (${maxCount}) exceeded`);
|
|
246
458
|
}
|
|
459
|
+
|
|
460
|
+
postFiles.forEach((file) => {
|
|
461
|
+
const wrapped = appendFile(wrapFile(file), event);
|
|
462
|
+
|
|
463
|
+
// Manual mode: files only join the list and wait for upload()
|
|
464
|
+
if (!autoUpload) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const preparationToken = Symbol(wrapped.uid);
|
|
469
|
+
pendingPreparationsRef.current.set(
|
|
470
|
+
wrapped.uid as string,
|
|
471
|
+
preparationToken,
|
|
472
|
+
);
|
|
473
|
+
void prepareFile(file, wrapped, postFiles, preparationToken, event);
|
|
474
|
+
});
|
|
247
475
|
};
|
|
248
476
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
477
|
+
// Instance method: upload files waiting in the list (status 'ready')
|
|
478
|
+
const upload = (file?: UploadFile | UploadFile[]) => {
|
|
479
|
+
const requested =
|
|
480
|
+
file === undefined
|
|
481
|
+
? fileListRef.current
|
|
482
|
+
: Array.isArray(file)
|
|
483
|
+
? file
|
|
484
|
+
: [file];
|
|
485
|
+
const readyFiles = requested.filter((item) => item.status === 'ready');
|
|
486
|
+
|
|
487
|
+
readyFiles.forEach((item) => {
|
|
488
|
+
let target = item;
|
|
489
|
+
if (!target.uid) {
|
|
490
|
+
target = replaceFile(item, { ...item, uid: uuidv4() }) ?? item;
|
|
491
|
+
}
|
|
492
|
+
if (!target.raw) {
|
|
493
|
+
updateFile(target, {
|
|
494
|
+
status: 'error',
|
|
495
|
+
error: new Error(`File ${target.name} has no source file to upload`),
|
|
496
|
+
});
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const preparationToken = Symbol(target.uid);
|
|
501
|
+
pendingPreparationsRef.current.set(
|
|
502
|
+
target.uid as string,
|
|
503
|
+
preparationToken,
|
|
504
|
+
);
|
|
505
|
+
void prepareFile(
|
|
506
|
+
target.raw,
|
|
507
|
+
target,
|
|
508
|
+
readyFiles
|
|
509
|
+
.map((ready) => ready.raw)
|
|
510
|
+
.filter((raw): raw is File => raw instanceof File),
|
|
511
|
+
preparationToken,
|
|
512
|
+
);
|
|
513
|
+
});
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
// Expose the root element plus the upload() method on the ref
|
|
517
|
+
useImperativeHandle(
|
|
518
|
+
ref,
|
|
519
|
+
() =>
|
|
520
|
+
Object.assign(rootRef.current ?? document.createElement('div'), {
|
|
521
|
+
upload,
|
|
522
|
+
}),
|
|
523
|
+
[upload],
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
527
|
+
const files = event.target.files;
|
|
528
|
+
if (!files) {
|
|
529
|
+
return;
|
|
252
530
|
}
|
|
531
|
+
uploadFiles(files, event);
|
|
532
|
+
event.target.value = '';
|
|
253
533
|
};
|
|
254
534
|
|
|
255
|
-
const handleDragOver = (
|
|
256
|
-
|
|
257
|
-
if (disabled)
|
|
258
|
-
|
|
535
|
+
const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
|
|
536
|
+
event.preventDefault();
|
|
537
|
+
if (!disabled) {
|
|
538
|
+
setDragState('dragover');
|
|
539
|
+
}
|
|
259
540
|
};
|
|
260
541
|
|
|
261
|
-
const handleDragLeave = (
|
|
262
|
-
|
|
542
|
+
const handleDragLeave = (event: React.DragEvent<HTMLDivElement>) => {
|
|
543
|
+
event.preventDefault();
|
|
263
544
|
setDragState(null);
|
|
264
545
|
};
|
|
265
546
|
|
|
266
|
-
const handleDrop = async (
|
|
267
|
-
|
|
268
|
-
if (disabled)
|
|
547
|
+
const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
|
|
548
|
+
event.preventDefault();
|
|
549
|
+
if (disabled) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
269
552
|
setDragState('drop');
|
|
270
553
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
554
|
+
try {
|
|
555
|
+
const entries = Array.from(event.dataTransfer.items)
|
|
556
|
+
.map((item) => item.webkitGetAsEntry?.())
|
|
557
|
+
.filter(
|
|
558
|
+
(entry): entry is FileSystemEntry =>
|
|
559
|
+
entry !== null && entry !== undefined,
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
if (entries.some((entry) => entry.isDirectory)) {
|
|
563
|
+
const nestedFiles = await Promise.all(
|
|
564
|
+
entries.map((entry) => folderScanner(entry, [])),
|
|
565
|
+
);
|
|
566
|
+
uploadFiles(nestedFiles.flat(), event);
|
|
567
|
+
} else {
|
|
568
|
+
uploadFiles(event.dataTransfer.files, event);
|
|
569
|
+
}
|
|
570
|
+
} catch (error) {
|
|
571
|
+
console.warn('Failed to scan dropped files', error);
|
|
572
|
+
} finally {
|
|
573
|
+
setDragState(null);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const handleRemove = async (file: UploadFile) => {
|
|
578
|
+
if (file.uid) {
|
|
579
|
+
pendingPreparationsRef.current.delete(file.uid);
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
const shouldRemove = onRemove
|
|
583
|
+
? await Promise.resolve(onRemove(file))
|
|
584
|
+
: true;
|
|
585
|
+
if (shouldRemove === false) {
|
|
586
|
+
return;
|
|
282
587
|
}
|
|
283
|
-
|
|
284
|
-
|
|
588
|
+
abortRequest(file);
|
|
589
|
+
setFileList(
|
|
590
|
+
fileListRef.current.filter((item) => !isSameFile(item, file)),
|
|
591
|
+
);
|
|
592
|
+
} catch (error) {
|
|
593
|
+
console.warn(`Failed to remove file ${file.name}`, error);
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
const handleReUpload = (file: UploadFile) => {
|
|
598
|
+
if (file.uid) {
|
|
599
|
+
pendingPreparationsRef.current.delete(file.uid);
|
|
600
|
+
}
|
|
601
|
+
abortRequest(file);
|
|
602
|
+
const nextFile = updateFile(file, {
|
|
603
|
+
uid: file.uid || uuidv4(),
|
|
604
|
+
status: 'ready',
|
|
605
|
+
percent: 0,
|
|
606
|
+
error: undefined,
|
|
607
|
+
response: undefined,
|
|
608
|
+
});
|
|
609
|
+
if (nextFile) {
|
|
610
|
+
postFile(nextFile);
|
|
285
611
|
}
|
|
286
612
|
};
|
|
287
613
|
|
|
614
|
+
const handlePreview = (file: UploadFile) => {
|
|
615
|
+
if (onPreview) {
|
|
616
|
+
onPreview(file);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (file.url) {
|
|
620
|
+
window.open(file.url, '_blank', 'noopener,noreferrer');
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (!file.raw) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const url = URL.createObjectURL(file.raw);
|
|
628
|
+
const previewWindow = window.open(url, '_blank');
|
|
629
|
+
if (!previewWindow) {
|
|
630
|
+
URL.revokeObjectURL(url);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
const cleanup = () => URL.revokeObjectURL(url);
|
|
634
|
+
previewWindow.addEventListener('load', cleanup, { once: true });
|
|
635
|
+
window.setTimeout(cleanup, 60_000);
|
|
636
|
+
};
|
|
637
|
+
|
|
288
638
|
const renderUploadTrigger = () => {
|
|
639
|
+
const acceptDirectory = directory
|
|
640
|
+
? ({ webkitdirectory: '', directory: '' } as Record<string, string>)
|
|
641
|
+
: {};
|
|
642
|
+
|
|
289
643
|
return (
|
|
290
644
|
<span
|
|
291
645
|
className={clsx(`${prefixCls}-input`, {
|
|
292
646
|
[`${prefixCls}-input-disabled`]: disabled,
|
|
293
647
|
})}
|
|
294
|
-
onClick={
|
|
648
|
+
onClick={(event) => {
|
|
649
|
+
if (!disabled && event.target !== inputRef.current) {
|
|
650
|
+
inputRef.current?.click();
|
|
651
|
+
}
|
|
652
|
+
}}
|
|
295
653
|
>
|
|
296
654
|
<input
|
|
297
655
|
type="file"
|
|
656
|
+
aria-label="Upload file"
|
|
657
|
+
className={`${prefixCls}-native-input`}
|
|
298
658
|
disabled={disabled}
|
|
299
659
|
{...acceptDirectory}
|
|
300
|
-
multiple={multiple}
|
|
660
|
+
multiple={Boolean(multiple || directory)}
|
|
301
661
|
accept={accept}
|
|
302
662
|
onChange={handleChange}
|
|
303
|
-
style={{ display: 'none' }}
|
|
304
663
|
ref={inputRef}
|
|
305
664
|
/>
|
|
306
665
|
{children ? (
|
|
307
666
|
children
|
|
308
667
|
) : listType === 'picture-wall' ? (
|
|
309
|
-
<
|
|
668
|
+
<span className={`${prefixCls}-picture-wall-trigger`}>
|
|
310
669
|
<Plus />
|
|
311
|
-
</
|
|
670
|
+
</span>
|
|
312
671
|
) : (
|
|
313
672
|
<Button disabled={disabled}>Upload</Button>
|
|
314
673
|
)}
|
|
@@ -316,35 +675,6 @@ const Upload = forwardRef<HTMLDivElement, UploadProps>((baseprops, ref) => {
|
|
|
316
675
|
);
|
|
317
676
|
};
|
|
318
677
|
|
|
319
|
-
const handleRemove = async (file: UploadFile) => {
|
|
320
|
-
const promise = onRemove?.(file);
|
|
321
|
-
const shouldRemove =
|
|
322
|
-
promise instanceof Promise ? await promise : promise !== false;
|
|
323
|
-
|
|
324
|
-
if (shouldRemove !== false) {
|
|
325
|
-
setMergedFileList((state) =>
|
|
326
|
-
state.filter((item) => item.uid !== file.uid),
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const handleReUpload = (file: UploadFile) => {
|
|
332
|
-
const newFile = { ...file, status: 'ready' as const, percent: 0 };
|
|
333
|
-
updateFileList(newFile);
|
|
334
|
-
postFile(newFile);
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
const handlePreview = (file: UploadFile) => {
|
|
338
|
-
if (onPreview) {
|
|
339
|
-
onPreview(file);
|
|
340
|
-
} else if (file.raw) {
|
|
341
|
-
// Default preview behavior: open in new window
|
|
342
|
-
const url = URL.createObjectURL(file.raw);
|
|
343
|
-
window.open(url);
|
|
344
|
-
setTimeout(() => URL.revokeObjectURL(url), 100);
|
|
345
|
-
}
|
|
346
|
-
};
|
|
347
|
-
|
|
348
678
|
const renderUploadList = (extraProps?: object) => {
|
|
349
679
|
if (!showUploadList) {
|
|
350
680
|
return null;
|
|
@@ -360,6 +690,7 @@ const Upload = forwardRef<HTMLDivElement, UploadProps>((baseprops, ref) => {
|
|
|
360
690
|
onRemove={handleRemove}
|
|
361
691
|
onReUpload={handleReUpload}
|
|
362
692
|
onPreview={handlePreview}
|
|
693
|
+
isPreviewable={(file) => Boolean(onPreview || file.url || file.raw)}
|
|
363
694
|
{...extraProps}
|
|
364
695
|
/>
|
|
365
696
|
);
|
|
@@ -368,7 +699,7 @@ const Upload = forwardRef<HTMLDivElement, UploadProps>((baseprops, ref) => {
|
|
|
368
699
|
if (type === 'drag') {
|
|
369
700
|
return (
|
|
370
701
|
<div
|
|
371
|
-
ref={
|
|
702
|
+
ref={rootRef}
|
|
372
703
|
{...rest}
|
|
373
704
|
className={clsx(
|
|
374
705
|
prefixCls,
|
|
@@ -382,7 +713,7 @@ const Upload = forwardRef<HTMLDivElement, UploadProps>((baseprops, ref) => {
|
|
|
382
713
|
style={style}
|
|
383
714
|
>
|
|
384
715
|
<div
|
|
385
|
-
className={
|
|
716
|
+
className={`${prefixCls}-drag-container`}
|
|
386
717
|
onDrop={handleDrop}
|
|
387
718
|
onDragOver={handleDragOver}
|
|
388
719
|
onDragLeave={handleDragLeave}
|
|
@@ -403,7 +734,7 @@ const Upload = forwardRef<HTMLDivElement, UploadProps>((baseprops, ref) => {
|
|
|
403
734
|
if (listType === 'picture-wall') {
|
|
404
735
|
return (
|
|
405
736
|
<div
|
|
406
|
-
ref={
|
|
737
|
+
ref={rootRef}
|
|
407
738
|
{...rest}
|
|
408
739
|
className={clsx(prefixCls, `${prefixCls}-picture-wall`, className)}
|
|
409
740
|
style={style}
|
|
@@ -415,7 +746,7 @@ const Upload = forwardRef<HTMLDivElement, UploadProps>((baseprops, ref) => {
|
|
|
415
746
|
|
|
416
747
|
return (
|
|
417
748
|
<div
|
|
418
|
-
ref={
|
|
749
|
+
ref={rootRef}
|
|
419
750
|
{...rest}
|
|
420
751
|
className={clsx(
|
|
421
752
|
prefixCls,
|