@ibanzajoe/uploader 1.4.0 → 1.6.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 +25 -3
- package/dist/index.cjs +93 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -5
- package/dist/index.d.ts +38 -5
- package/dist/index.js +91 -5
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +3 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +35 -6
- package/dist/server.d.ts +35 -6
- package/dist/server.js +3 -1
- package/dist/server.js.map +1 -1
- package/dist/styles.css +42 -0
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -144,9 +144,14 @@ type PickerOptions = {
|
|
|
144
144
|
*/
|
|
145
145
|
fromSources?: Array<'local_file_system' | 'camera'>;
|
|
146
146
|
/**
|
|
147
|
-
*
|
|
148
|
-
* 'user' — front / selfie camera (mirrored).
|
|
147
|
+
* Which camera opens first when the 'camera' source is used.
|
|
148
|
+
* 'user' — front / selfie camera (mirrored).
|
|
149
149
|
* 'environment' — rear camera.
|
|
150
|
+
*
|
|
151
|
+
* Omitted (recommended) → chosen per device: **rear on phones and tablets**,
|
|
152
|
+
* front on desktops. The user can flip between them in the camera view
|
|
153
|
+
* whenever the device exposes more than one camera, so this only sets the
|
|
154
|
+
* starting side.
|
|
150
155
|
*/
|
|
151
156
|
cameraFacingMode?: 'user' | 'environment';
|
|
152
157
|
};
|
|
@@ -302,6 +307,12 @@ declare function DropPane({ apikey, apiUrl, security, deliveryProtection, allowe
|
|
|
302
307
|
* unmount, on capture (to release the camera indicator while reviewing), and on
|
|
303
308
|
* cancel.
|
|
304
309
|
*
|
|
310
|
+
* Which camera opens first is device-dependent: phones and tablets default to
|
|
311
|
+
* the REAR camera (people photograph the world, not themselves, from an upload
|
|
312
|
+
* form), desktops to the only camera they have. A flip control appears whenever
|
|
313
|
+
* the device actually exposes more than one camera, so the default is a starting
|
|
314
|
+
* point rather than a constraint. `facingMode` overrides the default outright.
|
|
315
|
+
*
|
|
305
316
|
* All colors resolve from --uploader-* CSS variables — no hard-coded brand
|
|
306
317
|
* colors. Used by PickerOverlay and DropPane.
|
|
307
318
|
*
|
|
@@ -310,6 +321,24 @@ declare function DropPane({ apikey, apiUrl, security, deliveryProtection, allowe
|
|
|
310
321
|
|
|
311
322
|
/** True when the current environment can open a camera stream. */
|
|
312
323
|
declare function isCameraSupported(): boolean;
|
|
324
|
+
/** Which physical camera to open. Mirrors the getUserMedia `facingMode` values. */
|
|
325
|
+
type CameraFacingMode = 'user' | 'environment';
|
|
326
|
+
/**
|
|
327
|
+
* Best-effort "is this a phone or tablet?" check, used only to pick which camera
|
|
328
|
+
* opens first — never to gate a feature, so a wrong answer costs one tap on the
|
|
329
|
+
* flip control.
|
|
330
|
+
*
|
|
331
|
+
* `userAgentData.mobile` is the reliable signal where it exists (Chromium). The
|
|
332
|
+
* UA regex covers everything else, and the `maxTouchPoints` clause catches
|
|
333
|
+
* iPadOS 13+, which reports a desktop Macintosh UA and would otherwise open the
|
|
334
|
+
* front camera on a tablet.
|
|
335
|
+
*/
|
|
336
|
+
declare function isMobileDevice(): boolean;
|
|
337
|
+
/**
|
|
338
|
+
* The camera to open when the caller expresses no preference: rear on mobile,
|
|
339
|
+
* front on everything else.
|
|
340
|
+
*/
|
|
341
|
+
declare function defaultCameraFacingMode(): CameraFacingMode;
|
|
313
342
|
/**
|
|
314
343
|
* Decide whether the picker should offer the camera source, given the picker
|
|
315
344
|
* options and the current environment. The camera is offered when:
|
|
@@ -323,8 +352,12 @@ type CameraCaptureProps = {
|
|
|
323
352
|
onCapture: (file: File) => void;
|
|
324
353
|
/** Called when the user backs out without capturing. */
|
|
325
354
|
onCancel: () => void;
|
|
326
|
-
/**
|
|
327
|
-
|
|
355
|
+
/**
|
|
356
|
+
* Which camera to open first. 'user' = front (mirrored), 'environment' = rear.
|
|
357
|
+
* Omitted → `defaultCameraFacingMode()`: rear on mobile, front elsewhere. The
|
|
358
|
+
* user can still flip when the device has more than one camera.
|
|
359
|
+
*/
|
|
360
|
+
facingMode?: CameraFacingMode;
|
|
328
361
|
/** Output MIME type for the still. Default 'image/jpeg'. */
|
|
329
362
|
outputType?: string;
|
|
330
363
|
/** Quality 0–1 for lossy output. Default 0.92. */
|
|
@@ -401,4 +434,4 @@ type EditImageOptions = {
|
|
|
401
434
|
};
|
|
402
435
|
declare function editImage(file: File, opts?: EditImageOptions): Promise<File>;
|
|
403
436
|
|
|
404
|
-
export { CameraCapture, type CameraCaptureProps, type CropArea, DeliveryProtection, DropPane, type DropPaneProps, type EditImageOptions, FileResult, type Flip, ImageEditor, type ImageEditorProps, type PickerFile, type PickerFileState, type PickerOptions, PickerOverlay, type PickerOverlayProps, PickerResponse, type SharedPickerProps, UploaderClientOptions, type UploaderTheme, type UsePickerOptions, type UsePickerResult, editImage, isCameraSupported, shouldOfferCamera, themeToVars, usePicker };
|
|
437
|
+
export { CameraCapture, type CameraCaptureProps, type CameraFacingMode, type CropArea, DeliveryProtection, DropPane, type DropPaneProps, type EditImageOptions, FileResult, type Flip, ImageEditor, type ImageEditorProps, type PickerFile, type PickerFileState, type PickerOptions, PickerOverlay, type PickerOverlayProps, PickerResponse, type SharedPickerProps, UploaderClientOptions, type UploaderTheme, type UsePickerOptions, type UsePickerResult, defaultCameraFacingMode, editImage, isCameraSupported, isMobileDevice, shouldOfferCamera, themeToVars, usePicker };
|
package/dist/index.d.ts
CHANGED
|
@@ -144,9 +144,14 @@ type PickerOptions = {
|
|
|
144
144
|
*/
|
|
145
145
|
fromSources?: Array<'local_file_system' | 'camera'>;
|
|
146
146
|
/**
|
|
147
|
-
*
|
|
148
|
-
* 'user' — front / selfie camera (mirrored).
|
|
147
|
+
* Which camera opens first when the 'camera' source is used.
|
|
148
|
+
* 'user' — front / selfie camera (mirrored).
|
|
149
149
|
* 'environment' — rear camera.
|
|
150
|
+
*
|
|
151
|
+
* Omitted (recommended) → chosen per device: **rear on phones and tablets**,
|
|
152
|
+
* front on desktops. The user can flip between them in the camera view
|
|
153
|
+
* whenever the device exposes more than one camera, so this only sets the
|
|
154
|
+
* starting side.
|
|
150
155
|
*/
|
|
151
156
|
cameraFacingMode?: 'user' | 'environment';
|
|
152
157
|
};
|
|
@@ -302,6 +307,12 @@ declare function DropPane({ apikey, apiUrl, security, deliveryProtection, allowe
|
|
|
302
307
|
* unmount, on capture (to release the camera indicator while reviewing), and on
|
|
303
308
|
* cancel.
|
|
304
309
|
*
|
|
310
|
+
* Which camera opens first is device-dependent: phones and tablets default to
|
|
311
|
+
* the REAR camera (people photograph the world, not themselves, from an upload
|
|
312
|
+
* form), desktops to the only camera they have. A flip control appears whenever
|
|
313
|
+
* the device actually exposes more than one camera, so the default is a starting
|
|
314
|
+
* point rather than a constraint. `facingMode` overrides the default outright.
|
|
315
|
+
*
|
|
305
316
|
* All colors resolve from --uploader-* CSS variables — no hard-coded brand
|
|
306
317
|
* colors. Used by PickerOverlay and DropPane.
|
|
307
318
|
*
|
|
@@ -310,6 +321,24 @@ declare function DropPane({ apikey, apiUrl, security, deliveryProtection, allowe
|
|
|
310
321
|
|
|
311
322
|
/** True when the current environment can open a camera stream. */
|
|
312
323
|
declare function isCameraSupported(): boolean;
|
|
324
|
+
/** Which physical camera to open. Mirrors the getUserMedia `facingMode` values. */
|
|
325
|
+
type CameraFacingMode = 'user' | 'environment';
|
|
326
|
+
/**
|
|
327
|
+
* Best-effort "is this a phone or tablet?" check, used only to pick which camera
|
|
328
|
+
* opens first — never to gate a feature, so a wrong answer costs one tap on the
|
|
329
|
+
* flip control.
|
|
330
|
+
*
|
|
331
|
+
* `userAgentData.mobile` is the reliable signal where it exists (Chromium). The
|
|
332
|
+
* UA regex covers everything else, and the `maxTouchPoints` clause catches
|
|
333
|
+
* iPadOS 13+, which reports a desktop Macintosh UA and would otherwise open the
|
|
334
|
+
* front camera on a tablet.
|
|
335
|
+
*/
|
|
336
|
+
declare function isMobileDevice(): boolean;
|
|
337
|
+
/**
|
|
338
|
+
* The camera to open when the caller expresses no preference: rear on mobile,
|
|
339
|
+
* front on everything else.
|
|
340
|
+
*/
|
|
341
|
+
declare function defaultCameraFacingMode(): CameraFacingMode;
|
|
313
342
|
/**
|
|
314
343
|
* Decide whether the picker should offer the camera source, given the picker
|
|
315
344
|
* options and the current environment. The camera is offered when:
|
|
@@ -323,8 +352,12 @@ type CameraCaptureProps = {
|
|
|
323
352
|
onCapture: (file: File) => void;
|
|
324
353
|
/** Called when the user backs out without capturing. */
|
|
325
354
|
onCancel: () => void;
|
|
326
|
-
/**
|
|
327
|
-
|
|
355
|
+
/**
|
|
356
|
+
* Which camera to open first. 'user' = front (mirrored), 'environment' = rear.
|
|
357
|
+
* Omitted → `defaultCameraFacingMode()`: rear on mobile, front elsewhere. The
|
|
358
|
+
* user can still flip when the device has more than one camera.
|
|
359
|
+
*/
|
|
360
|
+
facingMode?: CameraFacingMode;
|
|
328
361
|
/** Output MIME type for the still. Default 'image/jpeg'. */
|
|
329
362
|
outputType?: string;
|
|
330
363
|
/** Quality 0–1 for lossy output. Default 0.92. */
|
|
@@ -401,4 +434,4 @@ type EditImageOptions = {
|
|
|
401
434
|
};
|
|
402
435
|
declare function editImage(file: File, opts?: EditImageOptions): Promise<File>;
|
|
403
436
|
|
|
404
|
-
export { CameraCapture, type CameraCaptureProps, type CropArea, DeliveryProtection, DropPane, type DropPaneProps, type EditImageOptions, FileResult, type Flip, ImageEditor, type ImageEditorProps, type PickerFile, type PickerFileState, type PickerOptions, PickerOverlay, type PickerOverlayProps, PickerResponse, type SharedPickerProps, UploaderClientOptions, type UploaderTheme, type UsePickerOptions, type UsePickerResult, editImage, isCameraSupported, shouldOfferCamera, themeToVars, usePicker };
|
|
437
|
+
export { CameraCapture, type CameraCaptureProps, type CameraFacingMode, type CropArea, DeliveryProtection, DropPane, type DropPaneProps, type EditImageOptions, FileResult, type Flip, ImageEditor, type ImageEditorProps, type PickerFile, type PickerFileState, type PickerOptions, PickerOverlay, type PickerOverlayProps, PickerResponse, type SharedPickerProps, UploaderClientOptions, type UploaderTheme, type UsePickerOptions, type UsePickerResult, defaultCameraFacingMode, editImage, isCameraSupported, isMobileDevice, shouldOfferCamera, themeToVars, usePicker };
|
package/dist/index.js
CHANGED
|
@@ -782,6 +782,19 @@ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
|
782
782
|
function isCameraSupported() {
|
|
783
783
|
return typeof navigator !== "undefined" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === "function" && typeof document !== "undefined" && typeof HTMLCanvasElement !== "undefined";
|
|
784
784
|
}
|
|
785
|
+
function isMobileDevice() {
|
|
786
|
+
if (typeof navigator === "undefined") return false;
|
|
787
|
+
const uaData = navigator.userAgentData;
|
|
788
|
+
if (typeof uaData?.mobile === "boolean") return uaData.mobile;
|
|
789
|
+
const ua = navigator.userAgent ?? "";
|
|
790
|
+
if (/Android|iPhone|iPad|iPod|Windows Phone|webOS|BlackBerry|Opera Mini|Mobile/i.test(ua)) {
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
return /Macintosh/.test(ua) && (navigator.maxTouchPoints ?? 0) > 1;
|
|
794
|
+
}
|
|
795
|
+
function defaultCameraFacingMode() {
|
|
796
|
+
return isMobileDevice() ? "environment" : "user";
|
|
797
|
+
}
|
|
785
798
|
var IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp|svg|avif|heic|heif)$/i;
|
|
786
799
|
function acceptsImages(accept) {
|
|
787
800
|
if (!accept || accept.length === 0) return true;
|
|
@@ -822,7 +835,7 @@ function describeCameraError(err) {
|
|
|
822
835
|
function CameraCapture({
|
|
823
836
|
onCapture,
|
|
824
837
|
onCancel,
|
|
825
|
-
facingMode
|
|
838
|
+
facingMode,
|
|
826
839
|
outputType = "image/jpeg",
|
|
827
840
|
quality: quality2 = 0.92,
|
|
828
841
|
fileNamePrefix = "camera-photo",
|
|
@@ -835,12 +848,32 @@ function CameraCapture({
|
|
|
835
848
|
const [status, setStatus] = useState3("initializing");
|
|
836
849
|
const [error, setError] = useState3(null);
|
|
837
850
|
const [stillUrl, setStillUrl] = useState3("");
|
|
838
|
-
const
|
|
851
|
+
const [facing, setFacing] = useState3(
|
|
852
|
+
() => facingMode ?? defaultCameraFacingMode()
|
|
853
|
+
);
|
|
854
|
+
const [canFlip, setCanFlip] = useState3(false);
|
|
855
|
+
const mirror = facing === "user";
|
|
856
|
+
useEffect2(() => {
|
|
857
|
+
if (facingMode) setFacing(facingMode);
|
|
858
|
+
}, [facingMode]);
|
|
839
859
|
const stopStream = useCallback4(() => {
|
|
840
860
|
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
841
861
|
streamRef.current = null;
|
|
842
862
|
if (videoRef.current) videoRef.current.srcObject = null;
|
|
843
863
|
}, []);
|
|
864
|
+
const probeCameraCount = useCallback4(async () => {
|
|
865
|
+
const media = typeof navigator !== "undefined" ? navigator.mediaDevices : void 0;
|
|
866
|
+
if (!media || typeof media.enumerateDevices !== "function") {
|
|
867
|
+
setCanFlip(isMobileDevice());
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
try {
|
|
871
|
+
const devices = await media.enumerateDevices();
|
|
872
|
+
setCanFlip(devices.filter((d) => d.kind === "videoinput").length > 1);
|
|
873
|
+
} catch {
|
|
874
|
+
setCanFlip(isMobileDevice());
|
|
875
|
+
}
|
|
876
|
+
}, []);
|
|
844
877
|
const startStream = useCallback4(async () => {
|
|
845
878
|
setError(null);
|
|
846
879
|
setStatus("initializing");
|
|
@@ -849,9 +882,12 @@ function CameraCapture({
|
|
|
849
882
|
setStatus("error");
|
|
850
883
|
return;
|
|
851
884
|
}
|
|
885
|
+
stopStream();
|
|
852
886
|
try {
|
|
853
887
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
854
|
-
|
|
888
|
+
// Non-exact: a device with no camera on the requested side falls back to
|
|
889
|
+
// the one it has rather than throwing OverconstrainedError.
|
|
890
|
+
video: { facingMode: facing },
|
|
855
891
|
audio: false
|
|
856
892
|
});
|
|
857
893
|
if (videoRef.current == null) {
|
|
@@ -861,11 +897,12 @@ function CameraCapture({
|
|
|
861
897
|
streamRef.current = stream;
|
|
862
898
|
videoRef.current.srcObject = stream;
|
|
863
899
|
setStatus("live");
|
|
900
|
+
void probeCameraCount();
|
|
864
901
|
} catch (err) {
|
|
865
902
|
setError(describeCameraError(err));
|
|
866
903
|
setStatus("error");
|
|
867
904
|
}
|
|
868
|
-
}, [
|
|
905
|
+
}, [facing, probeCameraCount, stopStream]);
|
|
869
906
|
useEffect2(() => {
|
|
870
907
|
void startStream();
|
|
871
908
|
return () => {
|
|
@@ -913,6 +950,9 @@ function CameraCapture({
|
|
|
913
950
|
quality2
|
|
914
951
|
);
|
|
915
952
|
}, [mirror, outputType, quality2, stopStream]);
|
|
953
|
+
const flipCamera = useCallback4(() => {
|
|
954
|
+
setFacing((current) => current === "user" ? "environment" : "user");
|
|
955
|
+
}, []);
|
|
916
956
|
const retake = useCallback4(() => {
|
|
917
957
|
blobRef.current = null;
|
|
918
958
|
setStillUrl("");
|
|
@@ -931,6 +971,7 @@ function CameraCapture({
|
|
|
931
971
|
className: ["uploader-camera", className ?? ""].filter(Boolean).join(" "),
|
|
932
972
|
style,
|
|
933
973
|
"data-testid": "camera-capture",
|
|
974
|
+
"data-facing": facing,
|
|
934
975
|
children: [
|
|
935
976
|
/* @__PURE__ */ jsxs3("div", { className: "uploader-camera-stage", children: [
|
|
936
977
|
status === "error" ? /* @__PURE__ */ jsx3("div", { className: "uploader-camera-error", role: "alert", children: error }) : status === "captured" ? (
|
|
@@ -947,7 +988,50 @@ function CameraCapture({
|
|
|
947
988
|
"data-mirror": mirror ? "true" : void 0
|
|
948
989
|
}
|
|
949
990
|
),
|
|
950
|
-
status === "initializing" && /* @__PURE__ */ jsx3("div", { className: "uploader-camera-status", "aria-live": "polite", children: "Starting camera\u2026" })
|
|
991
|
+
status === "initializing" && /* @__PURE__ */ jsx3("div", { className: "uploader-camera-status", "aria-live": "polite", children: "Starting camera\u2026" }),
|
|
992
|
+
canFlip && (status === "live" || status === "initializing") && /* @__PURE__ */ jsx3(
|
|
993
|
+
"button",
|
|
994
|
+
{
|
|
995
|
+
type: "button",
|
|
996
|
+
className: "uploader-camera-flip",
|
|
997
|
+
"aria-label": facing === "user" ? "Switch to rear camera" : "Switch to front camera",
|
|
998
|
+
disabled: status !== "live",
|
|
999
|
+
onClick: flipCamera,
|
|
1000
|
+
children: /* @__PURE__ */ jsxs3("svg", { viewBox: "0 0 24 24", width: "20", height: "20", "aria-hidden": "true", focusable: "false", children: [
|
|
1001
|
+
/* @__PURE__ */ jsx3(
|
|
1002
|
+
"path",
|
|
1003
|
+
{
|
|
1004
|
+
d: "M4 8.5A2.5 2.5 0 0 1 6.5 6h1.2l1-1.7a1 1 0 0 1 .87-.5h4.86a1 1 0 0 1 .86.5l1 1.7h1.21A2.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",
|
|
1005
|
+
fill: "none",
|
|
1006
|
+
stroke: "currentColor",
|
|
1007
|
+
strokeWidth: "1.6",
|
|
1008
|
+
strokeLinejoin: "round"
|
|
1009
|
+
}
|
|
1010
|
+
),
|
|
1011
|
+
/* @__PURE__ */ jsx3(
|
|
1012
|
+
"path",
|
|
1013
|
+
{
|
|
1014
|
+
d: "M9.4 12.6a2.6 2.6 0 0 0 4.45 1.6M14.6 11.4a2.6 2.6 0 0 0-4.45-1.6",
|
|
1015
|
+
fill: "none",
|
|
1016
|
+
stroke: "currentColor",
|
|
1017
|
+
strokeWidth: "1.6",
|
|
1018
|
+
strokeLinecap: "round"
|
|
1019
|
+
}
|
|
1020
|
+
),
|
|
1021
|
+
/* @__PURE__ */ jsx3(
|
|
1022
|
+
"path",
|
|
1023
|
+
{
|
|
1024
|
+
d: "m9.4 10.2-.1 2.1 2.1-.6M14.6 13.8l.1-2.1-2.1.6",
|
|
1025
|
+
fill: "none",
|
|
1026
|
+
stroke: "currentColor",
|
|
1027
|
+
strokeWidth: "1.6",
|
|
1028
|
+
strokeLinecap: "round",
|
|
1029
|
+
strokeLinejoin: "round"
|
|
1030
|
+
}
|
|
1031
|
+
)
|
|
1032
|
+
] })
|
|
1033
|
+
}
|
|
1034
|
+
)
|
|
951
1035
|
] }),
|
|
952
1036
|
status === "live" && /* @__PURE__ */ jsx3("p", { className: "uploader-camera-hint", children: "Frame your shot, then tap the shutter to capture." }),
|
|
953
1037
|
/* @__PURE__ */ jsx3("div", { className: "uploader-camera-actions", children: status === "captured" ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
@@ -1638,10 +1722,12 @@ export {
|
|
|
1638
1722
|
PickerOverlay,
|
|
1639
1723
|
UploaderClient,
|
|
1640
1724
|
crop,
|
|
1725
|
+
defaultCameraFacingMode,
|
|
1641
1726
|
editImage,
|
|
1642
1727
|
flip,
|
|
1643
1728
|
flop,
|
|
1644
1729
|
isCameraSupported,
|
|
1730
|
+
isMobileDevice,
|
|
1645
1731
|
output,
|
|
1646
1732
|
quality,
|
|
1647
1733
|
resize,
|