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 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 primitives:
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 "./components/ui/uplofile";
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> | react_jsx_runtime.JSX.Element | null | undefined;
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>, "name" | "uid" | "id" | "url" | "meta">[] | undefined;
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
- const newItems = toUse.map((file)=>({
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
- emitChange([
225
- ...items,
226
- ...newItems
227
- ]);
228
- newItems.forEach((it)=>startUpload(it));
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uplofile",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Composable file‑upload components for React.",
5
5
  "license": "MIT",
6
6
  "author": "Chris Josh <KristofaJosh>",