@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/README.md +83 -0
- package/dist/index.cjs +752 -289
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +193 -9
- package/dist/index.d.ts +193 -9
- package/dist/index.js +750 -291
- package/dist/index.js.map +1 -1
- package/dist/styles.css +561 -129
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
|
|
13
13
|
// src/react/PickerOverlay.tsx
|
|
14
14
|
import {
|
|
15
|
-
useRef as
|
|
16
|
-
useState as
|
|
17
|
-
useCallback as
|
|
18
|
-
useEffect as
|
|
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
|
|
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
|
|
661
|
-
const
|
|
662
|
-
const [
|
|
663
|
-
const [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1207
|
+
const handleDragEnter = useCallback5((e) => {
|
|
720
1208
|
e.preventDefault();
|
|
721
1209
|
setIsDragOver(true);
|
|
722
1210
|
}, []);
|
|
723
|
-
const handleDragLeave =
|
|
1211
|
+
const handleDragLeave = useCallback5((e) => {
|
|
724
1212
|
e.preventDefault();
|
|
725
1213
|
setIsDragOver(false);
|
|
726
1214
|
}, []);
|
|
727
|
-
const handleDragOver =
|
|
1215
|
+
const handleDragOver = useCallback5((e) => {
|
|
728
1216
|
e.preventDefault();
|
|
729
1217
|
}, []);
|
|
730
|
-
const handleDrop =
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
779
|
-
/* @__PURE__ */
|
|
780
|
-
/* @__PURE__ */
|
|
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: "\
|
|
1281
|
+
children: "\u2715"
|
|
788
1282
|
}
|
|
789
1283
|
)
|
|
790
1284
|
] }),
|
|
791
|
-
editingEntry ? /* @__PURE__ */
|
|
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
|
-
) :
|
|
802
|
-
/*
|
|
803
|
-
|
|
1295
|
+
) : cameraOpen ? (
|
|
1296
|
+
/* ── Camera capture view ───────────────────────────────────────── */
|
|
1297
|
+
/* @__PURE__ */ jsx6(
|
|
1298
|
+
CameraCapture,
|
|
804
1299
|
{
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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
|
-
|
|
870
|
-
|
|
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
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
),
|
|
878
|
-
/* @__PURE__ */
|
|
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-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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
|
-
|
|
935
|
-
"
|
|
1354
|
+
files.length > 0 && /* @__PURE__ */ jsx6(
|
|
1355
|
+
"ul",
|
|
936
1356
|
{
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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
|
-
|
|
944
|
-
"
|
|
1373
|
+
isUploading && /* @__PURE__ */ jsxs5(
|
|
1374
|
+
"div",
|
|
945
1375
|
{
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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 {
|
|
965
|
-
import { jsx as
|
|
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
|
|
979
|
-
const
|
|
980
|
-
const
|
|
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 =
|
|
1460
|
+
const handleDragEnter = useCallback6((e) => {
|
|
991
1461
|
e.preventDefault();
|
|
992
1462
|
e.stopPropagation();
|
|
993
1463
|
setIsDragOver(true);
|
|
994
1464
|
}, []);
|
|
995
|
-
const handleDragLeave =
|
|
1465
|
+
const handleDragLeave = useCallback6((e) => {
|
|
996
1466
|
e.preventDefault();
|
|
997
1467
|
e.stopPropagation();
|
|
998
1468
|
setIsDragOver(false);
|
|
999
1469
|
}, []);
|
|
1000
|
-
const handleDragOver =
|
|
1470
|
+
const handleDragOver = useCallback6((e) => {
|
|
1001
1471
|
e.preventDefault();
|
|
1002
1472
|
e.stopPropagation();
|
|
1003
1473
|
}, []);
|
|
1004
|
-
const handleDrop =
|
|
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
|
-
|
|
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",
|
|
1033
|
-
|
|
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__ */
|
|
1037
|
-
|
|
1519
|
+
files.length === 0 && /* @__PURE__ */ jsx7(
|
|
1520
|
+
Dropzone,
|
|
1038
1521
|
{
|
|
1039
|
-
|
|
1040
|
-
|
|
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
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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__ */
|
|
1065
|
-
"
|
|
1534
|
+
files.length > 0 && isDragOver && /* @__PURE__ */ jsx7(
|
|
1535
|
+
"div",
|
|
1066
1536
|
{
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
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
|
-
|
|
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__ */
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
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
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
"
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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-
|
|
1591
|
+
className: "uploader-btn-secondary uploader-btn-cancel",
|
|
1138
1592
|
disabled: isUploading,
|
|
1139
|
-
onClick:
|
|
1140
|
-
children: "
|
|
1593
|
+
onClick: onCancel,
|
|
1594
|
+
children: "Cancel"
|
|
1141
1595
|
}
|
|
1142
1596
|
),
|
|
1143
|
-
|
|
1597
|
+
hasPending && /* @__PURE__ */ jsx7(
|
|
1144
1598
|
"button",
|
|
1145
1599
|
{
|
|
1146
1600
|
type: "button",
|
|
1147
|
-
className: "uploader-btn-
|
|
1148
|
-
|
|
1149
|
-
|
|
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
|
};
|