@ibanzajoe/uploader 0.1.0 → 0.3.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,495 @@ 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
+
1085
+ // src/react/theme.ts
1086
+ var VAR_MAP = {
1087
+ accent: "--uploader-accent",
1088
+ accentHover: "--uploader-accent-hover",
1089
+ accentSoft: "--uploader-accent-soft",
1090
+ accentRing: "--uploader-accent-ring",
1091
+ radius: "--uploader-radius",
1092
+ background: "--uploader-bg",
1093
+ surface: "--uploader-surface",
1094
+ surfaceHover: "--uploader-surface-hover",
1095
+ border: "--uploader-border",
1096
+ borderSubtle: "--uploader-border-subtle",
1097
+ text: "--uploader-text",
1098
+ textMuted: "--uploader-text-muted",
1099
+ textXMuted: "--uploader-text-xmuted",
1100
+ error: "--uploader-error",
1101
+ errorSoft: "--uploader-error-soft",
1102
+ success: "--uploader-success",
1103
+ shadowSm: "--uploader-shadow-sm",
1104
+ shadow: "--uploader-shadow",
1105
+ shadowLg: "--uploader-shadow-lg",
1106
+ font: "--uploader-font",
1107
+ transition: "--uploader-transition",
1108
+ zBackdrop: "--uploader-z-backdrop",
1109
+ zPanel: "--uploader-z-panel"
1110
+ };
1111
+ function themeToVars(theme) {
1112
+ if (!theme) return {};
1113
+ const vars = {};
1114
+ for (const key of Object.keys(theme)) {
1115
+ const value = theme[key];
1116
+ if (value === void 0 || value === null) continue;
1117
+ vars[VAR_MAP[key]] = String(value);
1118
+ }
1119
+ return vars;
1120
+ }
1121
+
643
1122
  // src/react/PickerOverlay.tsx
644
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1123
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1124
+ function dialogTitle(isUploading, isDone, totalFiles, doneCount, errorCount, editingId, cameraOpen) {
1125
+ if (editingId) return "Edit Image";
1126
+ if (cameraOpen) return "Take a photo";
1127
+ if (isDone && errorCount === 0) return "All files uploaded";
1128
+ if (isDone && errorCount > 0) return `${doneCount} of ${totalFiles} uploaded`;
1129
+ if (isUploading) return `Uploading ${totalFiles} file${totalFiles !== 1 ? "s" : ""}`;
1130
+ return "Upload files";
1131
+ }
645
1132
  function PickerOverlay({
646
1133
  open,
647
1134
  onClose,
@@ -653,14 +1140,15 @@ function PickerOverlay({
653
1140
  onFileUploadFinished,
654
1141
  onFileUploadFailed,
655
1142
  onCancel,
1143
+ theme,
656
1144
  className,
657
1145
  style
658
1146
  }) {
659
1147
  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);
1148
+ const panelRef = useRef5(null);
1149
+ const [isDragOver, setIsDragOver] = useState4(false);
1150
+ const [editingId, setEditingId] = useState4(null);
1151
+ const [cameraOpen, setCameraOpen] = useState4(false);
664
1152
  const { files, addFiles, removeFile, editFile, retryFile, upload, progress, isUploading, isDone } = usePicker({
665
1153
  apikey,
666
1154
  apiUrl,
@@ -670,7 +1158,7 @@ function PickerOverlay({
670
1158
  onFileUploadFinished,
671
1159
  onFileUploadFailed
672
1160
  });
673
- useEffect2(() => {
1161
+ useEffect3(() => {
674
1162
  if (!open) return;
675
1163
  const handler = (e) => {
676
1164
  if (e.key === "Escape") {
@@ -681,7 +1169,7 @@ function PickerOverlay({
681
1169
  document.addEventListener("keydown", handler);
682
1170
  return () => document.removeEventListener("keydown", handler);
683
1171
  }, [open, onClose, onCancel]);
684
- useEffect2(() => {
1172
+ useEffect3(() => {
685
1173
  if (!open || !panelRef.current) return;
686
1174
  const panel = panelRef.current;
687
1175
  const focusable = panel.querySelectorAll(
@@ -707,8 +1195,8 @@ function PickerOverlay({
707
1195
  };
708
1196
  document.addEventListener("keydown", trap);
709
1197
  return () => document.removeEventListener("keydown", trap);
710
- }, [open, files, editingId]);
711
- useEffect2(() => {
1198
+ }, [open, files, editingId, cameraOpen]);
1199
+ useEffect3(() => {
712
1200
  if (!open) return;
713
1201
  const prev = document.body.style.overflow;
714
1202
  document.body.style.overflow = "hidden";
@@ -716,18 +1204,18 @@ function PickerOverlay({
716
1204
  document.body.style.overflow = prev;
717
1205
  };
718
1206
  }, [open]);
719
- const handleDragEnter = useCallback3((e) => {
1207
+ const handleDragEnter = useCallback5((e) => {
720
1208
  e.preventDefault();
721
1209
  setIsDragOver(true);
722
1210
  }, []);
723
- const handleDragLeave = useCallback3((e) => {
1211
+ const handleDragLeave = useCallback5((e) => {
724
1212
  e.preventDefault();
725
1213
  setIsDragOver(false);
726
1214
  }, []);
727
- const handleDragOver = useCallback3((e) => {
1215
+ const handleDragOver = useCallback5((e) => {
728
1216
  e.preventDefault();
729
1217
  }, []);
730
- const handleDrop = useCallback3(
1218
+ const handleDrop = useCallback5(
731
1219
  (e) => {
732
1220
  e.preventDefault();
733
1221
  setIsDragOver(false);
@@ -737,58 +1225,64 @@ function PickerOverlay({
737
1225
  },
738
1226
  [addFiles]
739
1227
  );
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(() => {
1228
+ const handleClose = useCallback5(() => {
751
1229
  onCancel?.();
752
1230
  onClose();
753
1231
  }, [onCancel, onClose]);
754
1232
  const hasPending = files.some((f) => f.state === "pending");
1233
+ const pendingCount = files.filter((f) => f.state === "pending").length;
1234
+ const doneCount = files.filter((f) => f.state === "done").length;
1235
+ const errorCount = files.filter((f) => f.state === "error").length;
755
1236
  const accept = pickerOptions?.accept?.join(",");
1237
+ const multiple = !pickerOptions?.maxFiles || pickerOptions.maxFiles > 1;
756
1238
  const editingEntry = files.find((f) => f.id === editingId) ?? null;
1239
+ const offerCamera = shouldOfferCamera(pickerOptions);
1240
+ const title = dialogTitle(
1241
+ isUploading,
1242
+ isDone,
1243
+ files.length,
1244
+ doneCount,
1245
+ errorCount,
1246
+ editingId,
1247
+ cameraOpen
1248
+ );
757
1249
  if (!open) return null;
758
1250
  return (
759
1251
  /* Backdrop — clicking outside the panel dismisses the modal */
760
- /* @__PURE__ */ jsx2(
1252
+ /* @__PURE__ */ jsx6(
761
1253
  "div",
762
1254
  {
763
1255
  className: "uploader-backdrop",
764
1256
  "data-testid": "picker-backdrop",
1257
+ style: themeToVars(theme),
765
1258
  onClick: handleClose,
766
- children: /* @__PURE__ */ jsxs2(
1259
+ children: /* @__PURE__ */ jsxs5(
767
1260
  "div",
768
1261
  {
769
1262
  ref: panelRef,
770
1263
  role: "dialog",
771
1264
  "aria-modal": "true",
772
1265
  "aria-labelledby": titleId,
1266
+ "data-drag-over": isDragOver && !editingEntry && files.length === 0 ? "true" : void 0,
773
1267
  className: ["uploader-picker-panel", className ?? ""].filter(Boolean).join(" "),
774
1268
  style,
775
1269
  "data-testid": "picker-panel",
776
1270
  onClick: (e) => e.stopPropagation(),
777
1271
  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(
1272
+ /* @__PURE__ */ jsxs5("div", { className: "uploader-picker-header", children: [
1273
+ /* @__PURE__ */ jsx6("h2", { id: titleId, className: "uploader-picker-title", children: title }),
1274
+ /* @__PURE__ */ jsx6(
781
1275
  "button",
782
1276
  {
783
1277
  type: "button",
784
1278
  className: "uploader-btn-close",
785
1279
  "aria-label": "Close picker",
786
1280
  onClick: handleClose,
787
- children: "\xD7"
1281
+ children: "\u2715"
788
1282
  }
789
1283
  )
790
1284
  ] }),
791
- editingEntry ? /* @__PURE__ */ jsx2(
1285
+ editingEntry ? /* @__PURE__ */ jsx6(
792
1286
  ImageEditor,
793
1287
  {
794
1288
  file: editingEntry.file,
@@ -798,160 +1292,134 @@ function PickerOverlay({
798
1292
  setEditingId(null);
799
1293
  }
800
1294
  }
801
- ) : /* @__PURE__ */ jsxs2(Fragment, { children: [
802
- /* @__PURE__ */ jsxs2(
803
- "div",
1295
+ ) : cameraOpen ? (
1296
+ /* ── Camera capture view ───────────────────────────────────────── */
1297
+ /* @__PURE__ */ jsx6(
1298
+ CameraCapture,
804
1299
  {
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"
1300
+ facingMode: pickerOptions?.cameraFacingMode,
1301
+ onCancel: () => setCameraOpen(false),
1302
+ onCapture: (file) => {
1303
+ addFiles([file]);
1304
+ setCameraOpen(false);
862
1305
  }
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" })
1306
+ }
1307
+ )
1308
+ ) : isDone ? (
1309
+ /* ── Done state ─────────────────────────────────────────────────── */
1310
+ /* @__PURE__ */ jsxs5("div", { className: "uploader-done-body", children: [
1311
+ /* @__PURE__ */ jsxs5("p", { className: "uploader-done-subtitle", children: [
1312
+ files.length,
1313
+ " file",
1314
+ files.length !== 1 ? "s" : "",
1315
+ " delivered to your bucket"
868
1316
  ] }),
869
- entry.state === "uploading" && entry.progress != null && /* @__PURE__ */ jsx2(
870
- "progress",
1317
+ /* @__PURE__ */ jsx6("ul", { className: "uploader-file-list uploader-done-list", role: "list", "aria-label": "Uploaded files", children: files.map((entry) => /* @__PURE__ */ jsx6(
1318
+ FileQueueRow,
871
1319
  {
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(
1320
+ entry,
1321
+ onRemove: removeFile,
1322
+ onRetry: retryFile
1323
+ },
1324
+ entry.id
1325
+ )) }),
1326
+ /* @__PURE__ */ jsx6(
920
1327
  "button",
921
1328
  {
922
1329
  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
- ]
1330
+ className: "uploader-btn-primary uploader-btn-done-full",
1331
+ onClick: onClose,
1332
+ children: "Done"
1333
+ }
1334
+ )
1335
+ ] })
1336
+ ) : (
1337
+ /* ── Normal (idle / uploading / error) view ─────────────────────── */
1338
+ /* @__PURE__ */ jsxs5(Fragment2, { children: [
1339
+ files.length === 0 && /* @__PURE__ */ jsx6(
1340
+ Dropzone,
1341
+ {
1342
+ onFiles: addFiles,
1343
+ isDragOver,
1344
+ onDragEnter: handleDragEnter,
1345
+ onDragLeave: handleDragLeave,
1346
+ onDragOver: handleDragOver,
1347
+ onDrop: handleDrop,
1348
+ accept,
1349
+ multiple,
1350
+ onCamera: offerCamera ? () => setCameraOpen(true) : void 0,
1351
+ hint: "PNG, JPG, PDF, ZIP \xB7 up to 50 MB"
932
1352
  }
933
1353
  ),
934
- isDone && /* @__PURE__ */ jsx2(
935
- "button",
1354
+ files.length > 0 && /* @__PURE__ */ jsx6(
1355
+ "ul",
936
1356
  {
937
- type: "button",
938
- className: "uploader-btn-done",
939
- onClick: onClose,
940
- children: "Done"
1357
+ className: "uploader-file-list",
1358
+ role: "list",
1359
+ "aria-label": "Selected files",
1360
+ "aria-live": "polite",
1361
+ children: files.map((entry) => /* @__PURE__ */ jsx6(
1362
+ FileQueueRow,
1363
+ {
1364
+ entry,
1365
+ onRemove: removeFile,
1366
+ onRetry: retryFile,
1367
+ onEdit: setEditingId
1368
+ },
1369
+ entry.id
1370
+ ))
941
1371
  }
942
1372
  ),
943
- !isDone && /* @__PURE__ */ jsx2(
944
- "button",
1373
+ isUploading && /* @__PURE__ */ jsxs5(
1374
+ "div",
945
1375
  {
946
- type: "button",
947
- className: "uploader-btn-cancel",
948
- disabled: isUploading,
949
- onClick: handleClose,
950
- children: "Cancel"
1376
+ className: "uploader-aggregate-progress",
1377
+ role: "status",
1378
+ "aria-live": "polite",
1379
+ children: [
1380
+ /* @__PURE__ */ jsx6("span", { className: "uploader-aggregate-progress-label", children: "Total progress" }),
1381
+ /* @__PURE__ */ jsx6(
1382
+ ProgressBar,
1383
+ {
1384
+ value: progress,
1385
+ label: "Overall upload progress",
1386
+ size: "lg"
1387
+ }
1388
+ ),
1389
+ /* @__PURE__ */ jsxs5("span", { className: "uploader-aggregate-progress-pct", children: [
1390
+ progress,
1391
+ "%"
1392
+ ] })
1393
+ ]
951
1394
  }
952
- )
1395
+ ),
1396
+ /* @__PURE__ */ jsxs5("div", { className: "uploader-picker-footer", children: [
1397
+ /* @__PURE__ */ jsx6("span", { className: "uploader-picker-footer-esc", children: "Esc to close" }),
1398
+ /* @__PURE__ */ jsxs5("div", { className: "uploader-picker-footer-actions", children: [
1399
+ /* @__PURE__ */ jsx6(
1400
+ "button",
1401
+ {
1402
+ type: "button",
1403
+ className: "uploader-btn-secondary uploader-btn-cancel",
1404
+ disabled: isUploading,
1405
+ onClick: handleClose,
1406
+ children: "Cancel"
1407
+ }
1408
+ ),
1409
+ /* @__PURE__ */ jsx6(
1410
+ "button",
1411
+ {
1412
+ type: "button",
1413
+ className: "uploader-btn-primary uploader-btn-upload",
1414
+ disabled: isUploading || !hasPending,
1415
+ onClick: () => void upload(),
1416
+ children: isUploading ? "Uploading\u2026" : `Upload${pendingCount > 0 ? ` ${pendingCount} file${pendingCount !== 1 ? "s" : ""}` : ""}`
1417
+ }
1418
+ )
1419
+ ] })
1420
+ ] })
953
1421
  ] })
954
- ] })
1422
+ )
955
1423
  ]
956
1424
  }
957
1425
  )
@@ -961,8 +1429,8 @@ function PickerOverlay({
961
1429
  }
962
1430
 
963
1431
  // 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";
1432
+ import { useState as useState5, useCallback as useCallback6 } from "react";
1433
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
966
1434
  function DropPane({
967
1435
  apikey,
968
1436
  apiUrl,
@@ -972,12 +1440,14 @@ function DropPane({
972
1440
  onFileUploadFinished,
973
1441
  onFileUploadFailed,
974
1442
  onCancel,
1443
+ hint = "or click to browse \xB7 max 50 MB",
1444
+ theme,
975
1445
  className,
976
1446
  style
977
1447
  }) {
978
- const inputId = useId2();
979
- const inputRef = useRef4(null);
980
- const [isDragOver, setIsDragOver] = useState4(false);
1448
+ const [isDragOver, setIsDragOver] = useState5(false);
1449
+ const [cameraOpen, setCameraOpen] = useState5(false);
1450
+ const rootStyle = { ...themeToVars(theme), ...style };
981
1451
  const { files, addFiles, removeFile, retryFile, upload, progress, isUploading, isDone } = usePicker({
982
1452
  apikey,
983
1453
  apiUrl,
@@ -987,21 +1457,21 @@ function DropPane({
987
1457
  onFileUploadFinished,
988
1458
  onFileUploadFailed
989
1459
  });
990
- const handleDragEnter = useCallback4((e) => {
1460
+ const handleDragEnter = useCallback6((e) => {
991
1461
  e.preventDefault();
992
1462
  e.stopPropagation();
993
1463
  setIsDragOver(true);
994
1464
  }, []);
995
- const handleDragLeave = useCallback4((e) => {
1465
+ const handleDragLeave = useCallback6((e) => {
996
1466
  e.preventDefault();
997
1467
  e.stopPropagation();
998
1468
  setIsDragOver(false);
999
1469
  }, []);
1000
- const handleDragOver = useCallback4((e) => {
1470
+ const handleDragOver = useCallback6((e) => {
1001
1471
  e.preventDefault();
1002
1472
  e.stopPropagation();
1003
1473
  }, []);
1004
- const handleDrop = useCallback4(
1474
+ const handleDrop = useCallback6(
1005
1475
  (e) => {
1006
1476
  e.preventDefault();
1007
1477
  e.stopPropagation();
@@ -1012,141 +1482,126 @@ function DropPane({
1012
1482
  },
1013
1483
  [addFiles]
1014
1484
  );
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
1485
  const hasPending = files.some((f) => f.state === "pending");
1486
+ const pendingCount = files.filter((f) => f.state === "pending").length;
1028
1487
  const accept = pickerOptions?.accept?.join(",");
1029
- return /* @__PURE__ */ jsxs3(
1488
+ const multiple = !pickerOptions?.maxFiles || pickerOptions.maxFiles > 1;
1489
+ const offerCamera = shouldOfferCamera(pickerOptions);
1490
+ if (cameraOpen) {
1491
+ return /* @__PURE__ */ jsx7(
1492
+ "div",
1493
+ {
1494
+ className: ["uploader-drop-pane", className ?? ""].filter(Boolean).join(" "),
1495
+ "data-testid": "drop-pane",
1496
+ style: rootStyle,
1497
+ children: /* @__PURE__ */ jsx7(
1498
+ CameraCapture,
1499
+ {
1500
+ facingMode: pickerOptions?.cameraFacingMode,
1501
+ onCancel: () => setCameraOpen(false),
1502
+ onCapture: (file) => {
1503
+ addFiles([file]);
1504
+ setCameraOpen(false);
1505
+ }
1506
+ }
1507
+ )
1508
+ }
1509
+ );
1510
+ }
1511
+ return /* @__PURE__ */ jsxs6(
1030
1512
  "div",
1031
1513
  {
1032
- className: ["uploader-drop-pane", isDragOver ? "uploader-drag-over" : "", className ?? ""].filter(Boolean).join(" "),
1033
- style,
1514
+ className: ["uploader-drop-pane", className ?? ""].filter(Boolean).join(" "),
1515
+ "data-drag-over": isDragOver ? "true" : void 0,
1034
1516
  "data-testid": "drop-pane",
1517
+ style: rootStyle,
1035
1518
  children: [
1036
- /* @__PURE__ */ jsxs3(
1037
- "div",
1519
+ files.length === 0 && /* @__PURE__ */ jsx7(
1520
+ Dropzone,
1038
1521
  {
1039
- className: "uploader-dropzone",
1040
- role: "button",
1041
- tabIndex: 0,
1042
- "aria-label": "Drop files here or click to browse",
1522
+ onFiles: addFiles,
1523
+ isDragOver,
1043
1524
  onDragEnter: handleDragEnter,
1044
1525
  onDragLeave: handleDragLeave,
1045
1526
  onDragOver: handleDragOver,
1046
1527
  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
- ]
1528
+ accept,
1529
+ multiple,
1530
+ onCamera: offerCamera ? () => setCameraOpen(true) : void 0,
1531
+ hint
1062
1532
  }
1063
1533
  ),
1064
- /* @__PURE__ */ jsx3(
1065
- "input",
1534
+ files.length > 0 && isDragOver && /* @__PURE__ */ jsx7(
1535
+ "div",
1066
1536
  {
1067
- id: inputId,
1068
- ref: inputRef,
1069
- type: "file",
1070
- multiple: !pickerOptions?.maxFiles || pickerOptions.maxFiles > 1,
1071
- accept,
1072
- className: "uploader-file-input",
1537
+ className: "uploader-drop-pane-drag-overlay",
1538
+ onDragEnter: handleDragEnter,
1539
+ onDragLeave: handleDragLeave,
1540
+ onDragOver: handleDragOver,
1541
+ onDrop: handleDrop,
1073
1542
  "aria-hidden": "true",
1074
- tabIndex: -1,
1075
- onChange: handleInputChange
1543
+ children: /* @__PURE__ */ jsx7("span", { className: "uploader-drop-pane-drag-label", children: "Drop to add files" })
1076
1544
  }
1077
1545
  ),
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",
1546
+ files.length > 0 && /* @__PURE__ */ jsx7(
1547
+ "ul",
1548
+ {
1549
+ className: "uploader-file-list uploader-drop-pane-file-list",
1550
+ role: "list",
1551
+ "aria-label": "Selected files",
1552
+ "aria-live": "polite",
1553
+ children: files.map((entry) => /* @__PURE__ */ jsx7(
1554
+ FileQueueRow,
1115
1555
  {
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(
1556
+ entry,
1557
+ onRemove: removeFile,
1558
+ onRetry: retryFile
1559
+ },
1560
+ entry.id
1561
+ ))
1562
+ }
1563
+ ),
1564
+ isUploading && /* @__PURE__ */ jsxs6(
1565
+ "div",
1566
+ {
1567
+ className: "uploader-aggregate-progress",
1568
+ role: "status",
1569
+ "aria-live": "polite",
1570
+ children: [
1571
+ /* @__PURE__ */ jsx7(
1572
+ ProgressBar,
1573
+ {
1574
+ value: progress,
1575
+ label: "Overall upload progress",
1576
+ size: "lg"
1577
+ }
1578
+ ),
1579
+ /* @__PURE__ */ jsxs6("span", { className: "uploader-aggregate-progress-pct", children: [
1580
+ progress,
1581
+ "%"
1582
+ ] })
1583
+ ]
1584
+ }
1585
+ ),
1586
+ files.length > 0 && !isDone && /* @__PURE__ */ jsxs6("div", { className: "uploader-drop-pane-actions", children: [
1587
+ /* @__PURE__ */ jsx7(
1134
1588
  "button",
1135
1589
  {
1136
1590
  type: "button",
1137
- className: "uploader-btn-upload",
1591
+ className: "uploader-btn-secondary uploader-btn-cancel",
1138
1592
  disabled: isUploading,
1139
- onClick: () => void upload(),
1140
- children: "Upload"
1593
+ onClick: onCancel,
1594
+ children: "Cancel"
1141
1595
  }
1142
1596
  ),
1143
- !isDone && !isUploading && /* @__PURE__ */ jsx3(
1597
+ hasPending && /* @__PURE__ */ jsx7(
1144
1598
  "button",
1145
1599
  {
1146
1600
  type: "button",
1147
- className: "uploader-btn-cancel",
1148
- onClick: onCancel,
1149
- children: "Cancel"
1601
+ className: "uploader-btn-primary uploader-btn-upload",
1602
+ disabled: isUploading,
1603
+ onClick: () => void upload(),
1604
+ children: isUploading ? "Uploading\u2026" : `Upload${pendingCount > 0 ? ` ${pendingCount} file${pendingCount !== 1 ? "s" : ""}` : ""}`
1150
1605
  }
1151
1606
  )
1152
1607
  ] })
@@ -1155,6 +1610,7 @@ function DropPane({
1155
1610
  );
1156
1611
  }
1157
1612
  export {
1613
+ CameraCapture,
1158
1614
  DropPane,
1159
1615
  ImageEditor,
1160
1616
  PickerOverlay,
@@ -1163,10 +1619,13 @@ export {
1163
1619
  editImage,
1164
1620
  flip,
1165
1621
  flop,
1622
+ isCameraSupported,
1166
1623
  output,
1167
1624
  quality,
1168
1625
  resize,
1169
1626
  rotate,
1627
+ shouldOfferCamera,
1628
+ themeToVars,
1170
1629
  transformUrl,
1171
1630
  usePicker
1172
1631
  };