uplofile 2.2.1 → 2.2.2

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/index.d.ts CHANGED
@@ -34,6 +34,7 @@ type UplofileRootRef<TMeta = any> = {
34
34
  onDragOver: (e: DragEvent) => void;
35
35
  openFileDialog: () => void;
36
36
  actions: ItemActions;
37
+ onLoadingChange?: (isLoading: boolean) => void;
37
38
  };
38
39
  type BeforeUploadResult<TMeta = any> = boolean | Array<{
39
40
  valid: boolean;
@@ -56,6 +57,7 @@ type RootProps<TMeta = any> = PropsWithChildren<{
56
57
  accept?: string;
57
58
  beforeUpload?: (items: UploadFileItem<TMeta>[]) => BeforeUploadResult<TMeta> | Promise<BeforeUploadResult<TMeta>>;
58
59
  onChange?: (items: UploadFileItem<TMeta>[]) => Promise<void> | void;
60
+ onLoadingChange?: (isLoading: boolean) => void;
59
61
  upload: (file: File, signal: AbortSignal, setProgress?: (pct: number) => void) => Promise<UploadResult<TMeta>>;
60
62
  onRemove?: (item: UploadFileItem<TMeta>, signal: AbortSignal) => Promise<void | any>;
61
63
  }>;
@@ -111,8 +113,9 @@ type PreviewRenderProps<TMeta = any> = {
111
113
 
112
114
  type Props<TMeta = any> = {
113
115
  render?: (api: PreviewRenderProps<TMeta>) => React.ReactNode;
116
+ className?: string;
114
117
  };
115
- declare const Preview: <TMeta = any>({ render }: Props<TMeta>) => string | number | boolean | react_jsx_runtime.JSX.Element | Iterable<React.ReactNode> | null | undefined;
118
+ declare const Preview: <TMeta = any>({ render, className, }: Props<TMeta>) => string | number | boolean | react_jsx_runtime.JSX.Element | Iterable<React.ReactNode> | null | undefined;
116
119
  declare const HiddenInput: ({ name }: {
117
120
  name?: string;
118
121
  }) => react_jsx_runtime.JSX.Element;
@@ -135,13 +138,14 @@ declare const Root: React.ForwardRefExoticComponent<{
135
138
  accept?: string;
136
139
  beforeUpload?: ((items: UploadFileItem<unknown>[]) => BeforeUploadResult<unknown> | Promise<BeforeUploadResult<unknown>>) | undefined;
137
140
  onChange?: ((items: UploadFileItem<unknown>[]) => Promise<void> | void) | undefined;
141
+ onLoadingChange?: (isLoading: boolean) => void;
138
142
  upload: (file: File, signal: AbortSignal, setProgress?: (pct: number) => void) => Promise<UploadResult<unknown>>;
139
143
  onRemove?: ((item: UploadFileItem<unknown>, signal: AbortSignal) => Promise<void | any>) | undefined;
140
144
  } & {
141
145
  children?: React.ReactNode | undefined;
142
146
  } & React.RefAttributes<UplofileRootRef<unknown>>>;
143
147
 
144
- declare const Trigger: <TMeta = any>({ asChild, children, render, ...rest }: PropsWithChildren<{
148
+ declare const Trigger: <TMeta = any>({ asChild, children, render, onClick, ...rest }: PropsWithChildren<{
145
149
  asChild?: boolean;
146
150
  render?: (api: TriggerRenderProps<TMeta>) => React.ReactNode;
147
151
  children?: React.ReactNode | ((api: TriggerRenderProps<TMeta>) => React.ReactNode);
package/dist/index.mjs CHANGED
@@ -115,13 +115,24 @@ 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/*", beforeUpload, name = "image", maxCount, disabled, children }, ref)=>{
118
+ const Root = /*#__PURE__*/ forwardRef(({ multiple = true, initial = [], onChange, onLoadingChange, upload, removeMode = "optimistic", onRemove, accept = "image/*", beforeUpload, name = "image", maxCount, disabled, children }, ref)=>{
119
119
  const [items, setItems] = useState([]);
120
120
  const [isLoading, setIsLoading] = useState(Array.isArray(initial) ? initial.length > 0 : !!initial);
121
121
  const controllers = useRef(new Map());
122
122
  const removeControllers = useRef(new Map());
123
123
  const inputRef = useRef(null);
124
124
  const hasHydratedInitialRef = useRef(false);
125
+ const onLoadingChangeRef = useRef(onLoadingChange);
126
+ useEffect(()=>{
127
+ onLoadingChangeRef.current = onLoadingChange;
128
+ }, [
129
+ onLoadingChange
130
+ ]);
131
+ useEffect(()=>{
132
+ onLoadingChangeRef.current?.(isLoading);
133
+ }, [
134
+ isLoading
135
+ ]);
125
136
  // Hydrate initial items from the server and keep them marked as done
126
137
  useEffect(()=>{
127
138
  if (hasHydratedInitialRef.current) return;
@@ -139,8 +150,17 @@ const Root = /*#__PURE__*/ forwardRef(({ multiple = true, initial = [], onChange
139
150
  meta: it.meta
140
151
  };
141
152
  });
142
- // Only hydrate if the user hasn't already added/modified items locally
143
- setItems((prev)=>prev.length === 0 ? mapped : prev);
153
+ // Append server items if user already added files while loading; avoid replacing
154
+ setItems((prev)=>{
155
+ if (prev.length === 0) return mapped;
156
+ const existing = new Set(prev.map((i)=>i.uid));
157
+ const toAppend = mapped.filter((m)=>!existing.has(m.uid));
158
+ if (toAppend.length === 0) return prev;
159
+ return [
160
+ ...prev,
161
+ ...toAppend
162
+ ];
163
+ });
144
164
  hasHydratedInitialRef.current = true;
145
165
  } finally{
146
166
  setIsLoading(false);
@@ -419,7 +439,13 @@ const Root = /*#__PURE__*/ forwardRef(({ multiple = true, initial = [], onChange
419
439
  onDrop,
420
440
  onDragOver,
421
441
  openFileDialog: ()=>inputRef.current?.click(),
422
- actions
442
+ actions,
443
+ get onLoadingChange () {
444
+ return onLoadingChangeRef.current;
445
+ },
446
+ set onLoadingChange (callback){
447
+ onLoadingChangeRef.current = callback;
448
+ }
423
449
  }), [
424
450
  emitChange,
425
451
  items,
@@ -460,7 +486,7 @@ const Dropzone = ({ asChild, ...rest })=>{
460
486
  });
461
487
  };
462
488
 
463
- const Preview = ({ render })=>{
489
+ const Preview = ({ render, className = "" })=>{
464
490
  const { items, actions, setItems, isLoading } = useUplofile();
465
491
  if (render && typeof render === "function") return render({
466
492
  items,
@@ -473,7 +499,10 @@ const Preview = ({ render })=>{
473
499
  "data-part": "preview",
474
500
  className: "uplofile-preview",
475
501
  children: /*#__PURE__*/ jsx("div", {
476
- className: "uplofile-preview__wrapper grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5",
502
+ className: [
503
+ "uplofile-preview__wrapper grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5",
504
+ className
505
+ ].join(" ").trim(),
477
506
  children: items.map((item)=>/*#__PURE__*/ jsxs("div", {
478
507
  onClick: (e)=>e.stopPropagation(),
479
508
  className: `uplofile-preview__item group relative aspect-square overflow-hidden rounded-xl border bg-muted/5 transition-all ${item.status === "error" ? "border-red-200 bg-red-50/30 hover:shadow-md" : "hover:shadow-md hover:ring-2 hover:ring-primary/20"}`,
@@ -773,7 +802,7 @@ const Remove = ({ uid, asChild, ...rest })=>{
773
802
  });
774
803
  };
775
804
 
776
- const Trigger = ({ asChild, children, render, ...rest })=>{
805
+ const Trigger = ({ asChild, children, render, onClick, ...rest })=>{
777
806
  const { openFileDialog, disabled, items, isLoading } = useUplofile();
778
807
  const Comp = asChild ? Slot : "button";
779
808
  const uploading = items.filter((i)=>i.status === "uploading");
@@ -795,12 +824,13 @@ const Trigger = ({ asChild, children, render, ...rest })=>{
795
824
  type: asChild ? undefined : "button",
796
825
  "aria-disabled": disabled,
797
826
  "data-part": "trigger",
827
+ ...rest,
798
828
  onClick: (e)=>{
799
829
  if (disabled) return;
800
- rest.onClick?.(e);
830
+ onClick?.(e);
831
+ if (e.defaultPrevented) return;
801
832
  openFileDialog();
802
833
  },
803
- ...rest,
804
834
  children: render ? render(api) : children
805
835
  });
806
836
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uplofile",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "description": "Composable file‑upload components for React.",
5
5
  "license": "MIT",
6
6
  "author": "Chris Josh <KristofaJosh>",