uplofile 2.0.0 → 2.1.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/README.md +3 -38
- package/dist/index.d.ts +11 -2
- package/dist/index.mjs +56 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Accessible, unstyled primitives for building your own upload UI — with drag‑
|
|
|
10
10
|
- React 16+ compatible
|
|
11
11
|
- Drag‑and‑drop or click‑to‑upload
|
|
12
12
|
- Upload progress, plus cancel/retry/remove actions
|
|
13
|
+
- **Custom Validation:** Use `beforeUpload` to validate files before they start uploading
|
|
13
14
|
- Hidden input for form submissions
|
|
14
15
|
- Unstyled — bring your own design
|
|
15
16
|
|
|
@@ -29,53 +30,17 @@ pnpm add uplofile
|
|
|
29
30
|
|
|
30
31
|
## Quick Start
|
|
31
32
|
|
|
32
|
-
Import the
|
|
33
|
+
Import and use the components in your React component:
|
|
33
34
|
|
|
34
|
-
```typescript
|
|
35
|
-
import * as Uplofile from "uplofile";
|
|
36
|
-
|
|
37
|
-
const UplofileRoot = Uplofile.Root;
|
|
38
|
-
|
|
39
|
-
const UplofileTrigger = Uplofile.Trigger;
|
|
40
|
-
|
|
41
|
-
const UplofileHiddenInput = Uplofile.HiddenInput;
|
|
42
|
-
|
|
43
|
-
const UplofileDropzone = Uplofile.Dropzone;
|
|
44
|
-
|
|
45
|
-
const UplofilePreview = Uplofile.Preview;
|
|
46
|
-
|
|
47
|
-
const UplofileCancel = Uplofile.Cancel;
|
|
48
|
-
|
|
49
|
-
const UplofileRetry = Uplofile.Retry;
|
|
50
|
-
|
|
51
|
-
const UplofileRemove = Uplofile.Remove;
|
|
52
|
-
|
|
53
|
-
export {
|
|
54
|
-
UplofileRoot,
|
|
55
|
-
UplofileTrigger,
|
|
56
|
-
UplofileHiddenInput,
|
|
57
|
-
UplofileDropzone,
|
|
58
|
-
UplofilePreview,
|
|
59
|
-
UplofileCancel,
|
|
60
|
-
UplofileRetry,
|
|
61
|
-
UplofileRemove,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Then use them in your React component:
|
|
67
35
|
```tsx
|
|
68
36
|
"use client";
|
|
69
37
|
|
|
70
38
|
import {
|
|
71
|
-
UplofileCancel,
|
|
72
39
|
UplofileDropzone,
|
|
73
40
|
UplofilePreview,
|
|
74
|
-
UplofileRemove,
|
|
75
|
-
UplofileRetry,
|
|
76
41
|
UplofileRoot,
|
|
77
42
|
UplofileTrigger,
|
|
78
|
-
} from "
|
|
43
|
+
} from "uplofile";
|
|
79
44
|
|
|
80
45
|
export default function Basic() {
|
|
81
46
|
return (
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,13 @@ type UplofileRootRef<TMeta = any> = {
|
|
|
31
31
|
openFileDialog: () => void;
|
|
32
32
|
actions: ItemActions;
|
|
33
33
|
};
|
|
34
|
+
type BeforeUploadResult<TMeta = any> = boolean | Array<{
|
|
35
|
+
valid: boolean;
|
|
36
|
+
meta?: TMeta;
|
|
37
|
+
id?: string;
|
|
38
|
+
uid: string;
|
|
39
|
+
reason?: string;
|
|
40
|
+
}>;
|
|
34
41
|
type RootProps<TMeta = any> = PropsWithChildren<{
|
|
35
42
|
multiple?: boolean;
|
|
36
43
|
initial?: Array<Pick<UploadFileItem<TMeta>, "uid" | "id" | "name" | "url" | "meta">>;
|
|
@@ -43,6 +50,7 @@ type RootProps<TMeta = any> = PropsWithChildren<{
|
|
|
43
50
|
maxCount?: number;
|
|
44
51
|
disabled?: boolean;
|
|
45
52
|
accept?: string;
|
|
53
|
+
beforeUpload?: (items: UploadFileItem<TMeta>[]) => BeforeUploadResult<TMeta> | Promise<BeforeUploadResult<TMeta>>;
|
|
46
54
|
onChange?: (items: UploadFileItem<TMeta>[]) => Promise<void> | void;
|
|
47
55
|
upload: (file: File, signal: AbortSignal, setProgress?: (pct: number) => void) => Promise<UploadResult>;
|
|
48
56
|
onRemove?: (item: UploadFileItem<TMeta>, signal: AbortSignal) => Promise<void | any>;
|
|
@@ -97,7 +105,7 @@ type PreviewRenderProps<TMeta = any> = {
|
|
|
97
105
|
type Props<TMeta = any> = {
|
|
98
106
|
render?: (api: PreviewRenderProps<TMeta>) => React.ReactNode;
|
|
99
107
|
};
|
|
100
|
-
declare const Preview: <TMeta = any>({ render }: Props<TMeta>) => string | number | boolean | Iterable<React.ReactNode> |
|
|
108
|
+
declare const Preview: <TMeta = any>({ render }: Props<TMeta>) => string | number | boolean | react_jsx_runtime.JSX.Element | Iterable<React.ReactNode> | null | undefined;
|
|
101
109
|
declare const HiddenInput: ({ name }: {
|
|
102
110
|
name?: string;
|
|
103
111
|
}) => react_jsx_runtime.JSX.Element;
|
|
@@ -112,12 +120,13 @@ declare const Remove: ({ uid, asChild, ...rest }: ButtonProps) => react_jsx_runt
|
|
|
112
120
|
|
|
113
121
|
declare const Root: React.ForwardRefExoticComponent<{
|
|
114
122
|
multiple?: boolean;
|
|
115
|
-
initial?: Pick<UploadFileItem<unknown>, "
|
|
123
|
+
initial?: Pick<UploadFileItem<unknown>, "id" | "name" | "uid" | "url" | "meta">[] | undefined;
|
|
116
124
|
removeMode?: "optimistic" | "strict";
|
|
117
125
|
name?: string;
|
|
118
126
|
maxCount?: number;
|
|
119
127
|
disabled?: boolean;
|
|
120
128
|
accept?: string;
|
|
129
|
+
beforeUpload?: ((items: UploadFileItem<unknown>[]) => BeforeUploadResult<unknown> | Promise<BeforeUploadResult<unknown>>) | undefined;
|
|
121
130
|
onChange?: ((items: UploadFileItem<unknown>[]) => Promise<void> | void) | undefined;
|
|
122
131
|
upload: (file: File, signal: AbortSignal, setProgress?: (pct: number) => void) => Promise<UploadResult>;
|
|
123
132
|
onRemove?: ((item: UploadFileItem<unknown>, signal: AbortSignal) => Promise<void | any>) | undefined;
|
package/dist/index.mjs
CHANGED
|
@@ -115,7 +115,7 @@ const acceptsFile = (file, accept)=>{
|
|
|
115
115
|
};
|
|
116
116
|
|
|
117
117
|
const UploaderCtx = /*#__PURE__*/ createContext(null);
|
|
118
|
-
const Root = /*#__PURE__*/ forwardRef(({ multiple = true, initial = [], onChange, upload, removeMode = "optimistic", onRemove, accept = "image/*", name = "image", maxCount, disabled, children }, ref)=>{
|
|
118
|
+
const Root = /*#__PURE__*/ forwardRef(({ multiple = true, initial = [], onChange, upload, removeMode = "optimistic", onRemove, accept = "image/*", beforeUpload, name = "image", maxCount, disabled, children }, ref)=>{
|
|
119
119
|
const [items, setItems] = useState([]);
|
|
120
120
|
const controllers = useRef(new Map());
|
|
121
121
|
const removeControllers = useRef(new Map());
|
|
@@ -188,7 +188,7 @@ const Root = /*#__PURE__*/ forwardRef(({ multiple = true, initial = [], onChange
|
|
|
188
188
|
...it,
|
|
189
189
|
status: "done",
|
|
190
190
|
url: result.url,
|
|
191
|
-
id: result.id,
|
|
191
|
+
id: result.id ?? it.id,
|
|
192
192
|
previewUrl: serverPreview,
|
|
193
193
|
progress: 100
|
|
194
194
|
};
|
|
@@ -207,13 +207,13 @@ const Root = /*#__PURE__*/ forwardRef(({ multiple = true, initial = [], onChange
|
|
|
207
207
|
emitChange,
|
|
208
208
|
upload
|
|
209
209
|
]);
|
|
210
|
-
const selectFiles = useCallback((files)=>{
|
|
210
|
+
const selectFiles = useCallback(async (files)=>{
|
|
211
211
|
if (!files) return;
|
|
212
212
|
const selected = Array.isArray(files) ? files : Array.from(files);
|
|
213
213
|
if (selected.length === 0) return;
|
|
214
214
|
const remaining = maxCount ? Math.max(0, maxCount - items.filter((i)=>i.status !== "canceled").length) : undefined;
|
|
215
215
|
const toUse = typeof remaining === "number" ? selected.slice(0, remaining) : selected;
|
|
216
|
-
|
|
216
|
+
let newItems = toUse.map((file)=>({
|
|
217
217
|
uid: uid(),
|
|
218
218
|
name: file.name,
|
|
219
219
|
file,
|
|
@@ -221,12 +221,59 @@ const Root = /*#__PURE__*/ forwardRef(({ multiple = true, initial = [], onChange
|
|
|
221
221
|
status: "idle",
|
|
222
222
|
progress: 0
|
|
223
223
|
}));
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
224
|
+
if (beforeUpload) {
|
|
225
|
+
const result = await beforeUpload(newItems);
|
|
226
|
+
if (result === false) {
|
|
227
|
+
newItems.forEach((it)=>{
|
|
228
|
+
if (it.previewUrl) URL.revokeObjectURL(it.previewUrl);
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (Array.isArray(result)) {
|
|
233
|
+
const resultMap = new Map(result.map((r)=>[
|
|
234
|
+
r.uid,
|
|
235
|
+
r
|
|
236
|
+
]));
|
|
237
|
+
const processedItems = [];
|
|
238
|
+
for (const item of newItems){
|
|
239
|
+
const validation = resultMap.get(item.uid);
|
|
240
|
+
if (!validation) {
|
|
241
|
+
if (item.previewUrl) URL.revokeObjectURL(item.previewUrl);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (validation.valid) {
|
|
245
|
+
processedItems.push({
|
|
246
|
+
...item,
|
|
247
|
+
meta: validation.meta !== undefined ? validation.meta : item.meta,
|
|
248
|
+
id: validation.id !== undefined ? validation.id : item.id
|
|
249
|
+
});
|
|
250
|
+
} else if (validation.reason) {
|
|
251
|
+
processedItems.push({
|
|
252
|
+
...item,
|
|
253
|
+
status: "error",
|
|
254
|
+
error: validation.reason,
|
|
255
|
+
meta: validation.meta !== undefined ? validation.meta : item.meta,
|
|
256
|
+
id: validation.id !== undefined ? validation.id : item.id
|
|
257
|
+
});
|
|
258
|
+
} else {
|
|
259
|
+
if (item.previewUrl) URL.revokeObjectURL(item.previewUrl);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
newItems = processedItems;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (newItems.length === 0) return;
|
|
266
|
+
emitChange((prev)=>[
|
|
267
|
+
...prev,
|
|
268
|
+
...newItems
|
|
269
|
+
]);
|
|
270
|
+
newItems.forEach((it)=>{
|
|
271
|
+
if (it.status === "idle") {
|
|
272
|
+
startUpload(it);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
229
275
|
}, [
|
|
276
|
+
beforeUpload,
|
|
230
277
|
emitChange,
|
|
231
278
|
items,
|
|
232
279
|
maxCount,
|