@ibanzajoe/uploader 0.1.0 → 0.2.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/index.js CHANGED
@@ -12,10 +12,10 @@ import {
12
12
 
13
13
  // src/react/PickerOverlay.tsx
14
14
  import {
15
- useRef as useRef3,
16
- useState as useState3,
17
- useCallback as useCallback3,
18
- useEffect as useEffect2,
15
+ useRef as useRef5,
16
+ useState as useState4,
17
+ useCallback as useCallback5,
18
+ useEffect as useEffect3,
19
19
  useId
20
20
  } from "react";
21
21
 
@@ -640,8 +640,458 @@ function ImageEditor({
640
640
  );
641
641
  }
642
642
 
643
+ // src/react/Dropzone.tsx
644
+ import { useRef as useRef3, useCallback as useCallback3 } from "react";
645
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
646
+ function Dropzone({
647
+ onFiles,
648
+ isDragOver,
649
+ onDragEnter,
650
+ onDragLeave,
651
+ onDragOver,
652
+ onDrop,
653
+ accept,
654
+ multiple = true,
655
+ onCamera,
656
+ hint,
657
+ className,
658
+ style
659
+ }) {
660
+ const inputRef = useRef3(null);
661
+ const openFileDialog = useCallback3(() => inputRef.current?.click(), []);
662
+ const handleInputChange = useCallback3(
663
+ (e) => {
664
+ if (e.target.files && e.target.files.length > 0) {
665
+ onFiles(e.target.files);
666
+ e.target.value = "";
667
+ }
668
+ },
669
+ [onFiles]
670
+ );
671
+ return /* @__PURE__ */ jsxs2(
672
+ "div",
673
+ {
674
+ className: ["uploader-dropzone", className ?? ""].filter(Boolean).join(" "),
675
+ "data-drag-over": isDragOver ? "true" : void 0,
676
+ role: "button",
677
+ tabIndex: 0,
678
+ "aria-label": "Drop files here or click to browse",
679
+ style,
680
+ onDragEnter,
681
+ onDragLeave,
682
+ onDragOver,
683
+ onDrop,
684
+ onClick: openFileDialog,
685
+ onKeyDown: (e) => {
686
+ if (e.key === "Enter" || e.key === " ") {
687
+ e.preventDefault();
688
+ openFileDialog();
689
+ }
690
+ },
691
+ children: [
692
+ /* @__PURE__ */ jsx2("div", { className: "uploader-dropzone-icon-wrap", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("div", { className: "uploader-dropzone-icon" }) }),
693
+ /* @__PURE__ */ jsx2("p", { className: "uploader-dropzone-label", children: "Drag & drop files here" }),
694
+ /* @__PURE__ */ jsx2("p", { className: "uploader-dropzone-sublabel", children: "or" }),
695
+ /* @__PURE__ */ jsxs2("div", { className: "uploader-dropzone-actions", children: [
696
+ /* @__PURE__ */ jsx2(
697
+ "button",
698
+ {
699
+ type: "button",
700
+ className: "uploader-btn-browse",
701
+ onClick: (e) => {
702
+ e.stopPropagation();
703
+ openFileDialog();
704
+ },
705
+ children: "Browse files"
706
+ }
707
+ ),
708
+ onCamera && /* @__PURE__ */ jsxs2(
709
+ "button",
710
+ {
711
+ type: "button",
712
+ className: "uploader-btn-camera",
713
+ onClick: (e) => {
714
+ e.stopPropagation();
715
+ onCamera();
716
+ },
717
+ children: [
718
+ /* @__PURE__ */ jsxs2(
719
+ "svg",
720
+ {
721
+ className: "uploader-btn-camera-icon",
722
+ width: "16",
723
+ height: "16",
724
+ viewBox: "0 0 24 24",
725
+ fill: "none",
726
+ "aria-hidden": "true",
727
+ children: [
728
+ /* @__PURE__ */ jsx2(
729
+ "path",
730
+ {
731
+ d: "M4 8.5A2.5 2.5 0 0 1 6.5 6h1.2a1 1 0 0 0 .83-.45l.74-1.1A1 1 0 0 1 10.1 4h3.8a1 1 0 0 1 .83.45l.74 1.1a1 1 0 0 0 .83.45h1.2A2.5 2.5 0 0 1 20 8.5v8A2.5 2.5 0 0 1 17.5 19h-11A2.5 2.5 0 0 1 4 16.5v-8Z",
732
+ stroke: "currentColor",
733
+ strokeWidth: "1.6",
734
+ strokeLinejoin: "round"
735
+ }
736
+ ),
737
+ /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12.5", r: "3.2", stroke: "currentColor", strokeWidth: "1.6" })
738
+ ]
739
+ }
740
+ ),
741
+ "Take photo"
742
+ ]
743
+ }
744
+ )
745
+ ] }),
746
+ hint && /* @__PURE__ */ jsx2("p", { className: "uploader-dropzone-hint", children: hint }),
747
+ /* @__PURE__ */ jsx2(
748
+ "input",
749
+ {
750
+ ref: inputRef,
751
+ type: "file",
752
+ multiple,
753
+ accept,
754
+ className: "uploader-file-input",
755
+ "aria-hidden": "true",
756
+ tabIndex: -1,
757
+ onChange: handleInputChange
758
+ }
759
+ )
760
+ ]
761
+ }
762
+ );
763
+ }
764
+
765
+ // src/react/CameraCapture.tsx
766
+ import { useCallback as useCallback4, useEffect as useEffect2, useRef as useRef4, useState as useState3 } from "react";
767
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
768
+ function isCameraSupported() {
769
+ return typeof navigator !== "undefined" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === "function" && typeof document !== "undefined" && typeof HTMLCanvasElement !== "undefined";
770
+ }
771
+ var IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp|svg|avif|heic|heif)$/i;
772
+ function acceptsImages(accept) {
773
+ if (!accept || accept.length === 0) return true;
774
+ return accept.some((a) => a.startsWith("image/") || a === "image/*" || IMAGE_EXT.test(a.trim()));
775
+ }
776
+ function shouldOfferCamera(options) {
777
+ if (!isCameraSupported()) return false;
778
+ if (!acceptsImages(options?.accept)) return false;
779
+ const sources = options?.fromSources;
780
+ if (sources && !sources.includes("camera")) return false;
781
+ return true;
782
+ }
783
+ function extForType(mime) {
784
+ switch (mime) {
785
+ case "image/png":
786
+ return "png";
787
+ case "image/webp":
788
+ return "webp";
789
+ default:
790
+ return "jpg";
791
+ }
792
+ }
793
+ function describeCameraError(err) {
794
+ const name = err instanceof DOMException ? err.name : "";
795
+ switch (name) {
796
+ case "NotAllowedError":
797
+ case "SecurityError":
798
+ return "Camera access was blocked. Allow camera permission and try again.";
799
+ case "NotFoundError":
800
+ case "OverconstrainedError":
801
+ return "No camera was found on this device.";
802
+ case "NotReadableError":
803
+ return "The camera is already in use by another application.";
804
+ default:
805
+ return "Could not start the camera.";
806
+ }
807
+ }
808
+ function CameraCapture({
809
+ onCapture,
810
+ onCancel,
811
+ facingMode = "user",
812
+ outputType = "image/jpeg",
813
+ quality: quality2 = 0.92,
814
+ fileNamePrefix = "camera-photo",
815
+ className,
816
+ style
817
+ }) {
818
+ const videoRef = useRef4(null);
819
+ const streamRef = useRef4(null);
820
+ const blobRef = useRef4(null);
821
+ const [status, setStatus] = useState3("initializing");
822
+ const [error, setError] = useState3(null);
823
+ const [stillUrl, setStillUrl] = useState3("");
824
+ const mirror = facingMode === "user";
825
+ const stopStream = useCallback4(() => {
826
+ streamRef.current?.getTracks().forEach((t) => t.stop());
827
+ streamRef.current = null;
828
+ if (videoRef.current) videoRef.current.srcObject = null;
829
+ }, []);
830
+ const startStream = useCallback4(async () => {
831
+ setError(null);
832
+ setStatus("initializing");
833
+ if (!isCameraSupported()) {
834
+ setError("Camera capture is not supported in this browser.");
835
+ setStatus("error");
836
+ return;
837
+ }
838
+ try {
839
+ const stream = await navigator.mediaDevices.getUserMedia({
840
+ video: { facingMode },
841
+ audio: false
842
+ });
843
+ if (videoRef.current == null) {
844
+ stream.getTracks().forEach((t) => t.stop());
845
+ return;
846
+ }
847
+ streamRef.current = stream;
848
+ videoRef.current.srcObject = stream;
849
+ setStatus("live");
850
+ } catch (err) {
851
+ setError(describeCameraError(err));
852
+ setStatus("error");
853
+ }
854
+ }, [facingMode]);
855
+ useEffect2(() => {
856
+ void startStream();
857
+ return () => {
858
+ stopStream();
859
+ };
860
+ }, [startStream, stopStream]);
861
+ useEffect2(() => {
862
+ if (!stillUrl) return;
863
+ return () => URL.revokeObjectURL(stillUrl);
864
+ }, [stillUrl]);
865
+ const capture = useCallback4(() => {
866
+ const video = videoRef.current;
867
+ if (!video) return;
868
+ const w = video.videoWidth;
869
+ const h = video.videoHeight;
870
+ if (!w || !h) {
871
+ setError("The camera is still warming up \u2014 try again in a moment.");
872
+ return;
873
+ }
874
+ const canvas = document.createElement("canvas");
875
+ canvas.width = w;
876
+ canvas.height = h;
877
+ const ctx = canvas.getContext("2d");
878
+ if (!ctx) {
879
+ setError("Canvas is unavailable for capturing.");
880
+ return;
881
+ }
882
+ if (mirror) {
883
+ ctx.translate(w, 0);
884
+ ctx.scale(-1, 1);
885
+ }
886
+ ctx.drawImage(video, 0, 0, w, h);
887
+ canvas.toBlob(
888
+ (blob) => {
889
+ if (!blob) {
890
+ setError("Failed to capture the photo.");
891
+ return;
892
+ }
893
+ blobRef.current = blob;
894
+ setStillUrl(URL.createObjectURL(blob));
895
+ setStatus("captured");
896
+ stopStream();
897
+ },
898
+ outputType,
899
+ quality2
900
+ );
901
+ }, [mirror, outputType, quality2, stopStream]);
902
+ const retake = useCallback4(() => {
903
+ blobRef.current = null;
904
+ setStillUrl("");
905
+ void startStream();
906
+ }, [startStream]);
907
+ const confirm = useCallback4(() => {
908
+ const blob = blobRef.current;
909
+ if (!blob) return;
910
+ const name = `${fileNamePrefix}-${Date.now()}.${extForType(outputType)}`;
911
+ const file = new File([blob], name, { type: outputType, lastModified: Date.now() });
912
+ onCapture(file);
913
+ }, [fileNamePrefix, outputType, onCapture]);
914
+ return /* @__PURE__ */ jsxs3(
915
+ "div",
916
+ {
917
+ className: ["uploader-camera", className ?? ""].filter(Boolean).join(" "),
918
+ style,
919
+ "data-testid": "camera-capture",
920
+ children: [
921
+ /* @__PURE__ */ jsxs3("div", { className: "uploader-camera-stage", children: [
922
+ status === "error" ? /* @__PURE__ */ jsx3("div", { className: "uploader-camera-error", role: "alert", children: error }) : status === "captured" ? (
923
+ /* eslint-disable-next-line @next/next/no-img-element */
924
+ /* @__PURE__ */ jsx3("img", { src: stillUrl, alt: "Captured preview", className: "uploader-camera-still" })
925
+ ) : /* @__PURE__ */ jsx3(
926
+ "video",
927
+ {
928
+ ref: videoRef,
929
+ className: "uploader-camera-video",
930
+ autoPlay: true,
931
+ playsInline: true,
932
+ muted: true,
933
+ "data-mirror": mirror ? "true" : void 0
934
+ }
935
+ ),
936
+ status === "initializing" && /* @__PURE__ */ jsx3("div", { className: "uploader-camera-status", "aria-live": "polite", children: "Starting camera\u2026" })
937
+ ] }),
938
+ status === "live" && /* @__PURE__ */ jsx3("p", { className: "uploader-camera-hint", children: "Frame your shot, then tap the shutter to capture." }),
939
+ /* @__PURE__ */ jsx3("div", { className: "uploader-camera-actions", children: status === "captured" ? /* @__PURE__ */ jsxs3(Fragment, { children: [
940
+ /* @__PURE__ */ jsx3("button", { type: "button", className: "uploader-btn-secondary", onClick: retake, children: "Retake" }),
941
+ /* @__PURE__ */ jsx3("button", { type: "button", className: "uploader-btn-primary", onClick: confirm, children: "Use photo" })
942
+ ] }) : status === "error" ? /* @__PURE__ */ jsxs3(Fragment, { children: [
943
+ /* @__PURE__ */ jsx3("button", { type: "button", className: "uploader-btn-secondary", onClick: onCancel, children: "Back" }),
944
+ /* @__PURE__ */ jsx3("button", { type: "button", className: "uploader-btn-primary", onClick: () => void startStream(), children: "Try again" })
945
+ ] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
946
+ /* @__PURE__ */ jsx3("button", { type: "button", className: "uploader-btn-secondary", onClick: onCancel, children: "Back" }),
947
+ /* @__PURE__ */ jsx3(
948
+ "button",
949
+ {
950
+ type: "button",
951
+ className: "uploader-camera-shutter",
952
+ "aria-label": "Capture photo",
953
+ disabled: status !== "live",
954
+ onClick: capture,
955
+ children: /* @__PURE__ */ jsx3("span", { className: "uploader-camera-shutter-ring", "aria-hidden": "true" })
956
+ }
957
+ ),
958
+ /* @__PURE__ */ jsx3("div", { className: "uploader-camera-actions-spacer", "aria-hidden": "true", children: /* @__PURE__ */ jsx3("span", { className: "uploader-btn-secondary", children: "Back" }) })
959
+ ] }) })
960
+ ]
961
+ }
962
+ );
963
+ }
964
+
965
+ // src/react/ProgressBar.tsx
966
+ import "react";
967
+ import { jsx as jsx4 } from "react/jsx-runtime";
968
+ function ProgressBar({ value, label, size = "sm", className, style }) {
969
+ const clamped = Math.max(0, Math.min(100, value));
970
+ return /* @__PURE__ */ jsx4(
971
+ "div",
972
+ {
973
+ role: "progressbar",
974
+ "aria-valuenow": clamped,
975
+ "aria-valuemin": 0,
976
+ "aria-valuemax": 100,
977
+ "aria-label": label,
978
+ "data-size": size,
979
+ className: ["uploader-progress-bar", className ?? ""].filter(Boolean).join(" "),
980
+ style,
981
+ children: /* @__PURE__ */ jsx4(
982
+ "div",
983
+ {
984
+ className: "uploader-progress-bar-fill",
985
+ style: { width: `${clamped}%` }
986
+ }
987
+ )
988
+ }
989
+ );
990
+ }
991
+
992
+ // src/react/FileQueueRow.tsx
993
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
994
+ function statusLabel(entry) {
995
+ switch (entry.state) {
996
+ case "uploading":
997
+ return entry.progress != null ? `${entry.progress}%` : "Uploading\u2026";
998
+ case "done":
999
+ return "Done";
1000
+ case "error":
1001
+ return entry.error ?? "Failed";
1002
+ default:
1003
+ return "Ready";
1004
+ }
1005
+ }
1006
+ function FileQueueRow({ entry, onRemove, onRetry, onEdit }) {
1007
+ const { id, file, state, progress, previewUrl } = entry;
1008
+ const isUploading = state === "uploading";
1009
+ return /* @__PURE__ */ jsxs4("li", { className: `uploader-file-item uploader-file-${state}`, children: [
1010
+ previewUrl ? /* @__PURE__ */ jsx5(
1011
+ "img",
1012
+ {
1013
+ src: previewUrl,
1014
+ alt: "",
1015
+ "aria-hidden": "true",
1016
+ className: "uploader-file-thumb"
1017
+ }
1018
+ ) : /* @__PURE__ */ jsx5("div", { className: "uploader-file-thumb-placeholder", "aria-hidden": "true" }),
1019
+ /* @__PURE__ */ jsxs4("div", { className: "uploader-file-info", children: [
1020
+ /* @__PURE__ */ jsxs4("div", { className: "uploader-file-info-row", children: [
1021
+ /* @__PURE__ */ jsx5("span", { className: "uploader-file-name", children: file.name }),
1022
+ /* @__PURE__ */ jsx5(
1023
+ "span",
1024
+ {
1025
+ className: "uploader-file-status",
1026
+ "data-state": state,
1027
+ role: state === "error" ? "alert" : void 0,
1028
+ children: statusLabel(entry)
1029
+ }
1030
+ )
1031
+ ] }),
1032
+ /* @__PURE__ */ jsx5(
1033
+ ProgressBar,
1034
+ {
1035
+ value: isUploading && progress != null ? progress : state === "done" ? 100 : 0,
1036
+ label: `${file.name} upload progress`
1037
+ }
1038
+ )
1039
+ ] }),
1040
+ state === "done" && /* @__PURE__ */ jsx5("div", { className: "uploader-file-done-badge", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("svg", { width: "9", height: "7", viewBox: "0 0 9 7", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(
1041
+ "path",
1042
+ {
1043
+ d: "M1 3.5L3.5 6L8 1",
1044
+ stroke: "currentColor",
1045
+ strokeWidth: "1.8",
1046
+ strokeLinecap: "round",
1047
+ strokeLinejoin: "round"
1048
+ }
1049
+ ) }) }),
1050
+ !isUploading && /* @__PURE__ */ jsxs4("div", { className: "uploader-file-actions", children: [
1051
+ onEdit && previewUrl && (state === "pending" || state === "error") && /* @__PURE__ */ jsx5(
1052
+ "button",
1053
+ {
1054
+ type: "button",
1055
+ className: "uploader-btn-edit",
1056
+ "aria-label": `Edit ${file.name}`,
1057
+ onClick: () => onEdit(id),
1058
+ children: "Edit"
1059
+ }
1060
+ ),
1061
+ state === "error" && /* @__PURE__ */ jsx5(
1062
+ "button",
1063
+ {
1064
+ type: "button",
1065
+ className: "uploader-btn-retry",
1066
+ "aria-label": `Retry uploading ${file.name}`,
1067
+ onClick: () => onRetry(id),
1068
+ children: "Retry"
1069
+ }
1070
+ ),
1071
+ /* @__PURE__ */ jsx5(
1072
+ "button",
1073
+ {
1074
+ type: "button",
1075
+ className: "uploader-btn-remove",
1076
+ "aria-label": `Remove ${file.name}`,
1077
+ onClick: () => onRemove(id),
1078
+ children: "\xD7"
1079
+ }
1080
+ )
1081
+ ] })
1082
+ ] });
1083
+ }
1084
+
643
1085
  // src/react/PickerOverlay.tsx
644
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1086
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1087
+ function dialogTitle(isUploading, isDone, totalFiles, doneCount, errorCount, editingId, cameraOpen) {
1088
+ if (editingId) return "Edit Image";
1089
+ if (cameraOpen) return "Take a photo";
1090
+ if (isDone && errorCount === 0) return "All files uploaded";
1091
+ if (isDone && errorCount > 0) return `${doneCount} of ${totalFiles} uploaded`;
1092
+ if (isUploading) return `Uploading ${totalFiles} file${totalFiles !== 1 ? "s" : ""}`;
1093
+ return "Upload files";
1094
+ }
645
1095
  function PickerOverlay({
646
1096
  open,
647
1097
  onClose,
@@ -657,10 +1107,10 @@ function PickerOverlay({
657
1107
  style
658
1108
  }) {
659
1109
  const titleId = useId();
660
- const inputRef = useRef3(null);
661
- const panelRef = useRef3(null);
662
- const [isDragOver, setIsDragOver] = useState3(false);
663
- const [editingId, setEditingId] = useState3(null);
1110
+ const panelRef = useRef5(null);
1111
+ const [isDragOver, setIsDragOver] = useState4(false);
1112
+ const [editingId, setEditingId] = useState4(null);
1113
+ const [cameraOpen, setCameraOpen] = useState4(false);
664
1114
  const { files, addFiles, removeFile, editFile, retryFile, upload, progress, isUploading, isDone } = usePicker({
665
1115
  apikey,
666
1116
  apiUrl,
@@ -670,7 +1120,7 @@ function PickerOverlay({
670
1120
  onFileUploadFinished,
671
1121
  onFileUploadFailed
672
1122
  });
673
- useEffect2(() => {
1123
+ useEffect3(() => {
674
1124
  if (!open) return;
675
1125
  const handler = (e) => {
676
1126
  if (e.key === "Escape") {
@@ -681,7 +1131,7 @@ function PickerOverlay({
681
1131
  document.addEventListener("keydown", handler);
682
1132
  return () => document.removeEventListener("keydown", handler);
683
1133
  }, [open, onClose, onCancel]);
684
- useEffect2(() => {
1134
+ useEffect3(() => {
685
1135
  if (!open || !panelRef.current) return;
686
1136
  const panel = panelRef.current;
687
1137
  const focusable = panel.querySelectorAll(
@@ -707,8 +1157,8 @@ function PickerOverlay({
707
1157
  };
708
1158
  document.addEventListener("keydown", trap);
709
1159
  return () => document.removeEventListener("keydown", trap);
710
- }, [open, files, editingId]);
711
- useEffect2(() => {
1160
+ }, [open, files, editingId, cameraOpen]);
1161
+ useEffect3(() => {
712
1162
  if (!open) return;
713
1163
  const prev = document.body.style.overflow;
714
1164
  document.body.style.overflow = "hidden";
@@ -716,18 +1166,18 @@ function PickerOverlay({
716
1166
  document.body.style.overflow = prev;
717
1167
  };
718
1168
  }, [open]);
719
- const handleDragEnter = useCallback3((e) => {
1169
+ const handleDragEnter = useCallback5((e) => {
720
1170
  e.preventDefault();
721
1171
  setIsDragOver(true);
722
1172
  }, []);
723
- const handleDragLeave = useCallback3((e) => {
1173
+ const handleDragLeave = useCallback5((e) => {
724
1174
  e.preventDefault();
725
1175
  setIsDragOver(false);
726
1176
  }, []);
727
- const handleDragOver = useCallback3((e) => {
1177
+ const handleDragOver = useCallback5((e) => {
728
1178
  e.preventDefault();
729
1179
  }, []);
730
- const handleDrop = useCallback3(
1180
+ const handleDrop = useCallback5(
731
1181
  (e) => {
732
1182
  e.preventDefault();
733
1183
  setIsDragOver(false);
@@ -737,58 +1187,63 @@ function PickerOverlay({
737
1187
  },
738
1188
  [addFiles]
739
1189
  );
740
- const openFileDialog = useCallback3(() => inputRef.current?.click(), []);
741
- const handleInputChange = useCallback3(
742
- (e) => {
743
- if (e.target.files && e.target.files.length > 0) {
744
- addFiles(e.target.files);
745
- e.target.value = "";
746
- }
747
- },
748
- [addFiles]
749
- );
750
- const handleClose = useCallback3(() => {
1190
+ const handleClose = useCallback5(() => {
751
1191
  onCancel?.();
752
1192
  onClose();
753
1193
  }, [onCancel, onClose]);
754
1194
  const hasPending = files.some((f) => f.state === "pending");
1195
+ const pendingCount = files.filter((f) => f.state === "pending").length;
1196
+ const doneCount = files.filter((f) => f.state === "done").length;
1197
+ const errorCount = files.filter((f) => f.state === "error").length;
755
1198
  const accept = pickerOptions?.accept?.join(",");
1199
+ const multiple = !pickerOptions?.maxFiles || pickerOptions.maxFiles > 1;
756
1200
  const editingEntry = files.find((f) => f.id === editingId) ?? null;
1201
+ const offerCamera = shouldOfferCamera(pickerOptions);
1202
+ const title = dialogTitle(
1203
+ isUploading,
1204
+ isDone,
1205
+ files.length,
1206
+ doneCount,
1207
+ errorCount,
1208
+ editingId,
1209
+ cameraOpen
1210
+ );
757
1211
  if (!open) return null;
758
1212
  return (
759
1213
  /* Backdrop — clicking outside the panel dismisses the modal */
760
- /* @__PURE__ */ jsx2(
1214
+ /* @__PURE__ */ jsx6(
761
1215
  "div",
762
1216
  {
763
1217
  className: "uploader-backdrop",
764
1218
  "data-testid": "picker-backdrop",
765
1219
  onClick: handleClose,
766
- children: /* @__PURE__ */ jsxs2(
1220
+ children: /* @__PURE__ */ jsxs5(
767
1221
  "div",
768
1222
  {
769
1223
  ref: panelRef,
770
1224
  role: "dialog",
771
1225
  "aria-modal": "true",
772
1226
  "aria-labelledby": titleId,
1227
+ "data-drag-over": isDragOver && !editingEntry && files.length === 0 ? "true" : void 0,
773
1228
  className: ["uploader-picker-panel", className ?? ""].filter(Boolean).join(" "),
774
1229
  style,
775
1230
  "data-testid": "picker-panel",
776
1231
  onClick: (e) => e.stopPropagation(),
777
1232
  children: [
778
- /* @__PURE__ */ jsxs2("div", { className: "uploader-picker-header", children: [
779
- /* @__PURE__ */ jsx2("h2", { id: titleId, className: "uploader-picker-title", children: editingEntry ? "Edit Image" : "Upload Files" }),
780
- /* @__PURE__ */ jsx2(
1233
+ /* @__PURE__ */ jsxs5("div", { className: "uploader-picker-header", children: [
1234
+ /* @__PURE__ */ jsx6("h2", { id: titleId, className: "uploader-picker-title", children: title }),
1235
+ /* @__PURE__ */ jsx6(
781
1236
  "button",
782
1237
  {
783
1238
  type: "button",
784
1239
  className: "uploader-btn-close",
785
1240
  "aria-label": "Close picker",
786
1241
  onClick: handleClose,
787
- children: "\xD7"
1242
+ children: "\u2715"
788
1243
  }
789
1244
  )
790
1245
  ] }),
791
- editingEntry ? /* @__PURE__ */ jsx2(
1246
+ editingEntry ? /* @__PURE__ */ jsx6(
792
1247
  ImageEditor,
793
1248
  {
794
1249
  file: editingEntry.file,
@@ -798,160 +1253,134 @@ function PickerOverlay({
798
1253
  setEditingId(null);
799
1254
  }
800
1255
  }
801
- ) : /* @__PURE__ */ jsxs2(Fragment, { children: [
802
- /* @__PURE__ */ jsxs2(
803
- "div",
1256
+ ) : cameraOpen ? (
1257
+ /* ── Camera capture view ───────────────────────────────────────── */
1258
+ /* @__PURE__ */ jsx6(
1259
+ CameraCapture,
804
1260
  {
805
- className: [
806
- "uploader-dropzone",
807
- isDragOver ? "uploader-drag-over" : ""
808
- ].filter(Boolean).join(" "),
809
- role: "button",
810
- tabIndex: 0,
811
- "aria-label": "Drop files here or click to browse",
812
- onDragEnter: handleDragEnter,
813
- onDragLeave: handleDragLeave,
814
- onDragOver: handleDragOver,
815
- onDrop: handleDrop,
816
- onClick: openFileDialog,
817
- onKeyDown: (e) => {
818
- if (e.key === "Enter" || e.key === " ") {
819
- e.preventDefault();
820
- openFileDialog();
821
- }
822
- },
823
- children: [
824
- /* @__PURE__ */ jsx2("span", { className: "uploader-dropzone-icon", "aria-hidden": "true", children: "\u2191" }),
825
- /* @__PURE__ */ jsx2("p", { className: "uploader-dropzone-label", children: "Drag & drop files here" }),
826
- /* @__PURE__ */ jsx2(
827
- "button",
828
- {
829
- type: "button",
830
- className: "uploader-btn-browse",
831
- onClick: (e) => {
832
- e.stopPropagation();
833
- openFileDialog();
834
- },
835
- children: "Browse files"
836
- }
837
- )
838
- ]
839
- }
840
- ),
841
- /* @__PURE__ */ jsx2(
842
- "input",
843
- {
844
- ref: inputRef,
845
- type: "file",
846
- multiple: !pickerOptions?.maxFiles || pickerOptions.maxFiles > 1,
847
- accept,
848
- className: "uploader-file-input",
849
- "aria-hidden": "true",
850
- tabIndex: -1,
851
- onChange: handleInputChange
852
- }
853
- ),
854
- files.length > 0 && /* @__PURE__ */ jsx2("ul", { className: "uploader-file-list", role: "list", "aria-label": "Selected files", children: files.map((entry) => /* @__PURE__ */ jsxs2("li", { className: `uploader-file-item uploader-file-${entry.state}`, children: [
855
- entry.previewUrl && /* @__PURE__ */ jsx2(
856
- "img",
857
- {
858
- src: entry.previewUrl,
859
- alt: "",
860
- "aria-hidden": "true",
861
- className: "uploader-file-thumb"
1261
+ facingMode: pickerOptions?.cameraFacingMode,
1262
+ onCancel: () => setCameraOpen(false),
1263
+ onCapture: (file) => {
1264
+ addFiles([file]);
1265
+ setCameraOpen(false);
862
1266
  }
863
- ),
864
- /* @__PURE__ */ jsxs2("div", { className: "uploader-file-info", children: [
865
- /* @__PURE__ */ jsx2("span", { className: "uploader-file-name", children: entry.file.name }),
866
- entry.state === "error" && /* @__PURE__ */ jsx2("span", { className: "uploader-file-error", role: "alert", children: entry.error ?? "Upload failed" }),
867
- entry.state === "done" && /* @__PURE__ */ jsx2("span", { className: "uploader-file-done", children: "Uploaded" })
1267
+ }
1268
+ )
1269
+ ) : isDone ? (
1270
+ /* ── Done state ─────────────────────────────────────────────────── */
1271
+ /* @__PURE__ */ jsxs5("div", { className: "uploader-done-body", children: [
1272
+ /* @__PURE__ */ jsxs5("p", { className: "uploader-done-subtitle", children: [
1273
+ files.length,
1274
+ " file",
1275
+ files.length !== 1 ? "s" : "",
1276
+ " delivered to your bucket"
868
1277
  ] }),
869
- entry.state === "uploading" && entry.progress != null && /* @__PURE__ */ jsx2(
870
- "progress",
1278
+ /* @__PURE__ */ jsx6("ul", { className: "uploader-file-list uploader-done-list", role: "list", "aria-label": "Uploaded files", children: files.map((entry) => /* @__PURE__ */ jsx6(
1279
+ FileQueueRow,
871
1280
  {
872
- className: "uploader-file-progress",
873
- value: entry.progress,
874
- max: 100,
875
- "aria-label": `${entry.file.name} upload progress`
876
- }
877
- ),
878
- /* @__PURE__ */ jsxs2("div", { className: "uploader-file-actions", children: [
879
- entry.previewUrl && (entry.state === "pending" || entry.state === "error") && /* @__PURE__ */ jsx2(
880
- "button",
881
- {
882
- type: "button",
883
- className: "uploader-btn-edit",
884
- "aria-label": `Edit ${entry.file.name}`,
885
- onClick: () => setEditingId(entry.id),
886
- children: "Edit"
887
- }
888
- ),
889
- entry.state === "error" && /* @__PURE__ */ jsx2(
890
- "button",
891
- {
892
- type: "button",
893
- className: "uploader-btn-retry",
894
- "aria-label": `Retry uploading ${entry.file.name}`,
895
- onClick: () => retryFile(entry.id),
896
- children: "Retry"
897
- }
898
- ),
899
- entry.state !== "uploading" && /* @__PURE__ */ jsx2(
900
- "button",
901
- {
902
- type: "button",
903
- className: "uploader-btn-remove",
904
- "aria-label": `Remove ${entry.file.name}`,
905
- onClick: () => removeFile(entry.id),
906
- children: "\xD7"
907
- }
908
- )
909
- ] })
910
- ] }, entry.id)) }),
911
- isUploading && /* @__PURE__ */ jsxs2("div", { className: "uploader-aggregate-progress", role: "status", "aria-live": "polite", children: [
912
- /* @__PURE__ */ jsx2("progress", { value: progress, max: 100, "aria-label": "Overall upload progress" }),
913
- /* @__PURE__ */ jsxs2("span", { "aria-live": "polite", children: [
914
- progress,
915
- "%"
916
- ] })
917
- ] }),
918
- /* @__PURE__ */ jsxs2("div", { className: "uploader-picker-footer", children: [
919
- hasPending && !isUploading && /* @__PURE__ */ jsxs2(
1281
+ entry,
1282
+ onRemove: removeFile,
1283
+ onRetry: retryFile
1284
+ },
1285
+ entry.id
1286
+ )) }),
1287
+ /* @__PURE__ */ jsx6(
920
1288
  "button",
921
1289
  {
922
1290
  type: "button",
923
- className: "uploader-btn-upload",
924
- disabled: isUploading || !hasPending,
925
- onClick: () => void upload(),
926
- children: [
927
- "Upload ",
928
- files.filter((f) => f.state === "pending").length,
929
- " file",
930
- files.filter((f) => f.state === "pending").length !== 1 ? "s" : ""
931
- ]
1291
+ className: "uploader-btn-primary uploader-btn-done-full",
1292
+ onClick: onClose,
1293
+ children: "Done"
1294
+ }
1295
+ )
1296
+ ] })
1297
+ ) : (
1298
+ /* ── Normal (idle / uploading / error) view ─────────────────────── */
1299
+ /* @__PURE__ */ jsxs5(Fragment2, { children: [
1300
+ files.length === 0 && /* @__PURE__ */ jsx6(
1301
+ Dropzone,
1302
+ {
1303
+ onFiles: addFiles,
1304
+ isDragOver,
1305
+ onDragEnter: handleDragEnter,
1306
+ onDragLeave: handleDragLeave,
1307
+ onDragOver: handleDragOver,
1308
+ onDrop: handleDrop,
1309
+ accept,
1310
+ multiple,
1311
+ onCamera: offerCamera ? () => setCameraOpen(true) : void 0,
1312
+ hint: "PNG, JPG, PDF, ZIP \xB7 up to 50 MB"
932
1313
  }
933
1314
  ),
934
- isDone && /* @__PURE__ */ jsx2(
935
- "button",
1315
+ files.length > 0 && /* @__PURE__ */ jsx6(
1316
+ "ul",
936
1317
  {
937
- type: "button",
938
- className: "uploader-btn-done",
939
- onClick: onClose,
940
- children: "Done"
1318
+ className: "uploader-file-list",
1319
+ role: "list",
1320
+ "aria-label": "Selected files",
1321
+ "aria-live": "polite",
1322
+ children: files.map((entry) => /* @__PURE__ */ jsx6(
1323
+ FileQueueRow,
1324
+ {
1325
+ entry,
1326
+ onRemove: removeFile,
1327
+ onRetry: retryFile,
1328
+ onEdit: setEditingId
1329
+ },
1330
+ entry.id
1331
+ ))
941
1332
  }
942
1333
  ),
943
- !isDone && /* @__PURE__ */ jsx2(
944
- "button",
1334
+ isUploading && /* @__PURE__ */ jsxs5(
1335
+ "div",
945
1336
  {
946
- type: "button",
947
- className: "uploader-btn-cancel",
948
- disabled: isUploading,
949
- onClick: handleClose,
950
- children: "Cancel"
1337
+ className: "uploader-aggregate-progress",
1338
+ role: "status",
1339
+ "aria-live": "polite",
1340
+ children: [
1341
+ /* @__PURE__ */ jsx6("span", { className: "uploader-aggregate-progress-label", children: "Total progress" }),
1342
+ /* @__PURE__ */ jsx6(
1343
+ ProgressBar,
1344
+ {
1345
+ value: progress,
1346
+ label: "Overall upload progress",
1347
+ size: "lg"
1348
+ }
1349
+ ),
1350
+ /* @__PURE__ */ jsxs5("span", { className: "uploader-aggregate-progress-pct", children: [
1351
+ progress,
1352
+ "%"
1353
+ ] })
1354
+ ]
951
1355
  }
952
- )
1356
+ ),
1357
+ /* @__PURE__ */ jsxs5("div", { className: "uploader-picker-footer", children: [
1358
+ /* @__PURE__ */ jsx6("span", { className: "uploader-picker-footer-esc", children: "Esc to close" }),
1359
+ /* @__PURE__ */ jsxs5("div", { className: "uploader-picker-footer-actions", children: [
1360
+ /* @__PURE__ */ jsx6(
1361
+ "button",
1362
+ {
1363
+ type: "button",
1364
+ className: "uploader-btn-secondary uploader-btn-cancel",
1365
+ disabled: isUploading,
1366
+ onClick: handleClose,
1367
+ children: "Cancel"
1368
+ }
1369
+ ),
1370
+ /* @__PURE__ */ jsx6(
1371
+ "button",
1372
+ {
1373
+ type: "button",
1374
+ className: "uploader-btn-primary uploader-btn-upload",
1375
+ disabled: isUploading || !hasPending,
1376
+ onClick: () => void upload(),
1377
+ children: isUploading ? "Uploading\u2026" : `Upload${pendingCount > 0 ? ` ${pendingCount} file${pendingCount !== 1 ? "s" : ""}` : ""}`
1378
+ }
1379
+ )
1380
+ ] })
1381
+ ] })
953
1382
  ] })
954
- ] })
1383
+ )
955
1384
  ]
956
1385
  }
957
1386
  )
@@ -961,8 +1390,8 @@ function PickerOverlay({
961
1390
  }
962
1391
 
963
1392
  // src/react/DropPane.tsx
964
- import { useRef as useRef4, useState as useState4, useCallback as useCallback4, useId as useId2 } from "react";
965
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1393
+ import { useState as useState5, useCallback as useCallback6 } from "react";
1394
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
966
1395
  function DropPane({
967
1396
  apikey,
968
1397
  apiUrl,
@@ -972,12 +1401,12 @@ function DropPane({
972
1401
  onFileUploadFinished,
973
1402
  onFileUploadFailed,
974
1403
  onCancel,
1404
+ hint = "or click to browse \xB7 max 50 MB",
975
1405
  className,
976
1406
  style
977
1407
  }) {
978
- const inputId = useId2();
979
- const inputRef = useRef4(null);
980
- const [isDragOver, setIsDragOver] = useState4(false);
1408
+ const [isDragOver, setIsDragOver] = useState5(false);
1409
+ const [cameraOpen, setCameraOpen] = useState5(false);
981
1410
  const { files, addFiles, removeFile, retryFile, upload, progress, isUploading, isDone } = usePicker({
982
1411
  apikey,
983
1412
  apiUrl,
@@ -987,21 +1416,21 @@ function DropPane({
987
1416
  onFileUploadFinished,
988
1417
  onFileUploadFailed
989
1418
  });
990
- const handleDragEnter = useCallback4((e) => {
1419
+ const handleDragEnter = useCallback6((e) => {
991
1420
  e.preventDefault();
992
1421
  e.stopPropagation();
993
1422
  setIsDragOver(true);
994
1423
  }, []);
995
- const handleDragLeave = useCallback4((e) => {
1424
+ const handleDragLeave = useCallback6((e) => {
996
1425
  e.preventDefault();
997
1426
  e.stopPropagation();
998
1427
  setIsDragOver(false);
999
1428
  }, []);
1000
- const handleDragOver = useCallback4((e) => {
1429
+ const handleDragOver = useCallback6((e) => {
1001
1430
  e.preventDefault();
1002
1431
  e.stopPropagation();
1003
1432
  }, []);
1004
- const handleDrop = useCallback4(
1433
+ const handleDrop = useCallback6(
1005
1434
  (e) => {
1006
1435
  e.preventDefault();
1007
1436
  e.stopPropagation();
@@ -1012,141 +1441,126 @@ function DropPane({
1012
1441
  },
1013
1442
  [addFiles]
1014
1443
  );
1015
- const handleInputChange = useCallback4(
1016
- (e) => {
1017
- if (e.target.files && e.target.files.length > 0) {
1018
- addFiles(e.target.files);
1019
- e.target.value = "";
1020
- }
1021
- },
1022
- [addFiles]
1023
- );
1024
- const openFileDialog = useCallback4(() => {
1025
- inputRef.current?.click();
1026
- }, []);
1027
1444
  const hasPending = files.some((f) => f.state === "pending");
1445
+ const pendingCount = files.filter((f) => f.state === "pending").length;
1028
1446
  const accept = pickerOptions?.accept?.join(",");
1029
- return /* @__PURE__ */ jsxs3(
1447
+ const multiple = !pickerOptions?.maxFiles || pickerOptions.maxFiles > 1;
1448
+ const offerCamera = shouldOfferCamera(pickerOptions);
1449
+ if (cameraOpen) {
1450
+ return /* @__PURE__ */ jsx7(
1451
+ "div",
1452
+ {
1453
+ className: ["uploader-drop-pane", className ?? ""].filter(Boolean).join(" "),
1454
+ "data-testid": "drop-pane",
1455
+ style,
1456
+ children: /* @__PURE__ */ jsx7(
1457
+ CameraCapture,
1458
+ {
1459
+ facingMode: pickerOptions?.cameraFacingMode,
1460
+ onCancel: () => setCameraOpen(false),
1461
+ onCapture: (file) => {
1462
+ addFiles([file]);
1463
+ setCameraOpen(false);
1464
+ }
1465
+ }
1466
+ )
1467
+ }
1468
+ );
1469
+ }
1470
+ return /* @__PURE__ */ jsxs6(
1030
1471
  "div",
1031
1472
  {
1032
- className: ["uploader-drop-pane", isDragOver ? "uploader-drag-over" : "", className ?? ""].filter(Boolean).join(" "),
1033
- style,
1473
+ className: ["uploader-drop-pane", className ?? ""].filter(Boolean).join(" "),
1474
+ "data-drag-over": isDragOver ? "true" : void 0,
1034
1475
  "data-testid": "drop-pane",
1476
+ style,
1035
1477
  children: [
1036
- /* @__PURE__ */ jsxs3(
1037
- "div",
1478
+ files.length === 0 && /* @__PURE__ */ jsx7(
1479
+ Dropzone,
1038
1480
  {
1039
- className: "uploader-dropzone",
1040
- role: "button",
1041
- tabIndex: 0,
1042
- "aria-label": "Drop files here or click to browse",
1481
+ onFiles: addFiles,
1482
+ isDragOver,
1043
1483
  onDragEnter: handleDragEnter,
1044
1484
  onDragLeave: handleDragLeave,
1045
1485
  onDragOver: handleDragOver,
1046
1486
  onDrop: handleDrop,
1047
- onClick: openFileDialog,
1048
- onKeyDown: (e) => {
1049
- if (e.key === "Enter" || e.key === " ") {
1050
- e.preventDefault();
1051
- openFileDialog();
1052
- }
1053
- },
1054
- children: [
1055
- /* @__PURE__ */ jsx3("span", { className: "uploader-dropzone-icon", "aria-hidden": "true", children: "\u2191" }),
1056
- /* @__PURE__ */ jsxs3("span", { className: "uploader-dropzone-label", children: [
1057
- "Drag & drop files here or",
1058
- " ",
1059
- /* @__PURE__ */ jsx3("label", { htmlFor: inputId, className: "uploader-browse-link", children: "browse" })
1060
- ] })
1061
- ]
1487
+ accept,
1488
+ multiple,
1489
+ onCamera: offerCamera ? () => setCameraOpen(true) : void 0,
1490
+ hint
1062
1491
  }
1063
1492
  ),
1064
- /* @__PURE__ */ jsx3(
1065
- "input",
1493
+ files.length > 0 && isDragOver && /* @__PURE__ */ jsx7(
1494
+ "div",
1066
1495
  {
1067
- id: inputId,
1068
- ref: inputRef,
1069
- type: "file",
1070
- multiple: !pickerOptions?.maxFiles || pickerOptions.maxFiles > 1,
1071
- accept,
1072
- className: "uploader-file-input",
1496
+ className: "uploader-drop-pane-drag-overlay",
1497
+ onDragEnter: handleDragEnter,
1498
+ onDragLeave: handleDragLeave,
1499
+ onDragOver: handleDragOver,
1500
+ onDrop: handleDrop,
1073
1501
  "aria-hidden": "true",
1074
- tabIndex: -1,
1075
- onChange: handleInputChange
1502
+ children: /* @__PURE__ */ jsx7("span", { className: "uploader-drop-pane-drag-label", children: "Drop to add files" })
1076
1503
  }
1077
1504
  ),
1078
- files.length > 0 && /* @__PURE__ */ jsx3("ul", { className: "uploader-file-list", role: "list", "aria-label": "Selected files", children: files.map((entry) => /* @__PURE__ */ jsxs3("li", { className: `uploader-file-item uploader-file-${entry.state}`, children: [
1079
- entry.previewUrl && /* @__PURE__ */ jsx3(
1080
- "img",
1081
- {
1082
- src: entry.previewUrl,
1083
- alt: "",
1084
- "aria-hidden": "true",
1085
- className: "uploader-file-thumb"
1086
- }
1087
- ),
1088
- /* @__PURE__ */ jsxs3("div", { className: "uploader-file-info", children: [
1089
- /* @__PURE__ */ jsx3("span", { className: "uploader-file-name", children: entry.file.name }),
1090
- entry.state === "error" && /* @__PURE__ */ jsx3("span", { className: "uploader-file-error", role: "alert", children: entry.error ?? "Upload failed" }),
1091
- entry.state === "done" && /* @__PURE__ */ jsx3("span", { className: "uploader-file-done", children: "Uploaded" })
1092
- ] }),
1093
- entry.state === "uploading" && entry.progress != null && /* @__PURE__ */ jsx3(
1094
- "progress",
1095
- {
1096
- className: "uploader-file-progress",
1097
- value: entry.progress,
1098
- max: 100,
1099
- "aria-label": `${entry.file.name} upload progress`
1100
- }
1101
- ),
1102
- /* @__PURE__ */ jsxs3("div", { className: "uploader-file-actions", children: [
1103
- entry.state === "error" && /* @__PURE__ */ jsx3(
1104
- "button",
1105
- {
1106
- type: "button",
1107
- className: "uploader-btn-retry",
1108
- "aria-label": `Retry uploading ${entry.file.name}`,
1109
- onClick: () => retryFile(entry.id),
1110
- children: "Retry"
1111
- }
1112
- ),
1113
- entry.state !== "uploading" && /* @__PURE__ */ jsx3(
1114
- "button",
1505
+ files.length > 0 && /* @__PURE__ */ jsx7(
1506
+ "ul",
1507
+ {
1508
+ className: "uploader-file-list uploader-drop-pane-file-list",
1509
+ role: "list",
1510
+ "aria-label": "Selected files",
1511
+ "aria-live": "polite",
1512
+ children: files.map((entry) => /* @__PURE__ */ jsx7(
1513
+ FileQueueRow,
1115
1514
  {
1116
- type: "button",
1117
- className: "uploader-btn-remove",
1118
- "aria-label": `Remove ${entry.file.name}`,
1119
- onClick: () => removeFile(entry.id),
1120
- children: "\xD7"
1121
- }
1122
- )
1123
- ] })
1124
- ] }, entry.id)) }),
1125
- isUploading && /* @__PURE__ */ jsxs3("div", { className: "uploader-aggregate-progress", role: "status", "aria-live": "polite", children: [
1126
- /* @__PURE__ */ jsx3("progress", { value: progress, max: 100, "aria-label": "Overall upload progress" }),
1127
- /* @__PURE__ */ jsxs3("span", { children: [
1128
- progress,
1129
- "%"
1130
- ] })
1131
- ] }),
1132
- /* @__PURE__ */ jsxs3("div", { className: "uploader-actions", children: [
1133
- hasPending && !isUploading && /* @__PURE__ */ jsx3(
1515
+ entry,
1516
+ onRemove: removeFile,
1517
+ onRetry: retryFile
1518
+ },
1519
+ entry.id
1520
+ ))
1521
+ }
1522
+ ),
1523
+ isUploading && /* @__PURE__ */ jsxs6(
1524
+ "div",
1525
+ {
1526
+ className: "uploader-aggregate-progress",
1527
+ role: "status",
1528
+ "aria-live": "polite",
1529
+ children: [
1530
+ /* @__PURE__ */ jsx7(
1531
+ ProgressBar,
1532
+ {
1533
+ value: progress,
1534
+ label: "Overall upload progress",
1535
+ size: "lg"
1536
+ }
1537
+ ),
1538
+ /* @__PURE__ */ jsxs6("span", { className: "uploader-aggregate-progress-pct", children: [
1539
+ progress,
1540
+ "%"
1541
+ ] })
1542
+ ]
1543
+ }
1544
+ ),
1545
+ files.length > 0 && !isDone && /* @__PURE__ */ jsxs6("div", { className: "uploader-drop-pane-actions", children: [
1546
+ /* @__PURE__ */ jsx7(
1134
1547
  "button",
1135
1548
  {
1136
1549
  type: "button",
1137
- className: "uploader-btn-upload",
1550
+ className: "uploader-btn-secondary uploader-btn-cancel",
1138
1551
  disabled: isUploading,
1139
- onClick: () => void upload(),
1140
- children: "Upload"
1552
+ onClick: onCancel,
1553
+ children: "Cancel"
1141
1554
  }
1142
1555
  ),
1143
- !isDone && !isUploading && /* @__PURE__ */ jsx3(
1556
+ hasPending && /* @__PURE__ */ jsx7(
1144
1557
  "button",
1145
1558
  {
1146
1559
  type: "button",
1147
- className: "uploader-btn-cancel",
1148
- onClick: onCancel,
1149
- children: "Cancel"
1560
+ className: "uploader-btn-primary uploader-btn-upload",
1561
+ disabled: isUploading,
1562
+ onClick: () => void upload(),
1563
+ children: isUploading ? "Uploading\u2026" : `Upload${pendingCount > 0 ? ` ${pendingCount} file${pendingCount !== 1 ? "s" : ""}` : ""}`
1150
1564
  }
1151
1565
  )
1152
1566
  ] })
@@ -1155,6 +1569,7 @@ function DropPane({
1155
1569
  );
1156
1570
  }
1157
1571
  export {
1572
+ CameraCapture,
1158
1573
  DropPane,
1159
1574
  ImageEditor,
1160
1575
  PickerOverlay,
@@ -1163,10 +1578,12 @@ export {
1163
1578
  editImage,
1164
1579
  flip,
1165
1580
  flop,
1581
+ isCameraSupported,
1166
1582
  output,
1167
1583
  quality,
1168
1584
  resize,
1169
1585
  rotate,
1586
+ shouldOfferCamera,
1170
1587
  transformUrl,
1171
1588
  usePicker
1172
1589
  };