@shopify/shop-minis-react 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/index4.js +2 -2
- package/dist/_virtual/index5.js +2 -3
- package/dist/_virtual/index5.js.map +1 -1
- package/dist/_virtual/index6.js +2 -2
- package/dist/_virtual/index7.js +3 -2
- package/dist/_virtual/index7.js.map +1 -1
- package/dist/components/atoms/image.js +35 -33
- package/dist/components/atoms/image.js.map +1 -1
- package/dist/providers/ImagePickerProvider.js +54 -55
- package/dist/providers/ImagePickerProvider.js.map +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.1.6_react@19.1.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/@videojs_xhr@2.7.0/node_modules/@videojs/xhr/lib/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/mpd-parser@1.3.1/node_modules/mpd-parser/dist/mpd-parser.es.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/querystringify@2.2.0/node_modules/querystringify/index.js +1 -1
- package/generated-hook-maps/hook-scopes-map.json +16 -16
- package/package.json +2 -2
- package/src/components/atoms/image.test.tsx +41 -1
- package/src/components/atoms/image.tsx +8 -3
- package/src/providers/ImagePickerProvider.test.tsx +82 -155
- package/src/providers/ImagePickerProvider.tsx +21 -33
package/dist/_virtual/index4.js
CHANGED
package/dist/_virtual/index5.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index5.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index5.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
package/dist/_virtual/index6.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { __require as r } from "../shop-minis-react/node_modules/.pnpm
|
|
1
|
+
import { __require as r } from "../shop-minis-react/node_modules/.pnpm/@xmldom_xmldom@0.8.10/node_modules/@xmldom/xmldom/lib/index.js";
|
|
2
2
|
var i = r();
|
|
3
3
|
export {
|
|
4
|
-
i as
|
|
4
|
+
i as l
|
|
5
5
|
};
|
|
6
6
|
//# sourceMappingURL=index6.js.map
|
package/dist/_virtual/index7.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { __require as r } from "../shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/shim/index.js";
|
|
2
|
+
var i = r();
|
|
2
3
|
export {
|
|
3
|
-
|
|
4
|
+
i as s
|
|
4
5
|
};
|
|
5
6
|
//# sourceMappingURL=index7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index7.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index7.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
|
|
@@ -1,58 +1,60 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { memo as
|
|
3
|
-
import { cn as
|
|
4
|
-
import { getThumbhashDataURL as
|
|
5
|
-
const O =
|
|
1
|
+
import { jsx as u } from "react/jsx-runtime";
|
|
2
|
+
import { memo as j, useState as i, useEffect as k, useMemo as m, useCallback as y } from "react";
|
|
3
|
+
import { cn as d } from "../../lib/utils.js";
|
|
4
|
+
import { getThumbhashDataURL as I, getResizedImageUrl as z } from "../../utils/image.js";
|
|
5
|
+
const O = j(function(b) {
|
|
6
6
|
const {
|
|
7
7
|
src: r,
|
|
8
8
|
file: t,
|
|
9
|
-
thumbhash:
|
|
10
|
-
onLoad:
|
|
11
|
-
className:
|
|
12
|
-
style:
|
|
13
|
-
aspectRatio:
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
thumbhash: s,
|
|
10
|
+
onLoad: c,
|
|
11
|
+
className: f,
|
|
12
|
+
style: h,
|
|
13
|
+
aspectRatio: o = "auto",
|
|
14
|
+
objectFit: g = "contain",
|
|
15
|
+
...L
|
|
16
|
+
} = b, [p, U] = i(!1), [a, l] = i(null);
|
|
16
17
|
k(() => {
|
|
17
18
|
if (!t) {
|
|
18
|
-
|
|
19
|
+
l(null);
|
|
19
20
|
return;
|
|
20
21
|
}
|
|
21
22
|
const e = URL.createObjectURL(t);
|
|
22
|
-
return
|
|
23
|
+
return l(e), () => {
|
|
23
24
|
URL.revokeObjectURL(e);
|
|
24
25
|
};
|
|
25
26
|
}, [t]);
|
|
26
|
-
const
|
|
27
|
-
() =>
|
|
28
|
-
[
|
|
29
|
-
),
|
|
27
|
+
const n = m(
|
|
28
|
+
() => I(s ?? void 0),
|
|
29
|
+
[s]
|
|
30
|
+
), R = y(
|
|
30
31
|
(e) => {
|
|
31
|
-
|
|
32
|
+
U(!0), c?.(e);
|
|
32
33
|
},
|
|
33
|
-
[
|
|
34
|
-
),
|
|
35
|
-
return /* @__PURE__ */
|
|
34
|
+
[c]
|
|
35
|
+
), v = m(() => a || z(r), [a, r]);
|
|
36
|
+
return /* @__PURE__ */ u(
|
|
36
37
|
"div",
|
|
37
38
|
{
|
|
38
|
-
className:
|
|
39
|
+
className: d("relative w-full", f),
|
|
39
40
|
style: {
|
|
40
|
-
...
|
|
41
|
-
aspectRatio:
|
|
42
|
-
backgroundImage:
|
|
41
|
+
...h,
|
|
42
|
+
...o !== "auto" && { aspectRatio: o },
|
|
43
|
+
backgroundImage: n ? `url(${n})` : void 0,
|
|
43
44
|
backgroundSize: "cover",
|
|
44
45
|
backgroundPosition: "center"
|
|
45
46
|
},
|
|
46
|
-
children: /* @__PURE__ */
|
|
47
|
+
children: /* @__PURE__ */ u(
|
|
47
48
|
"img",
|
|
48
49
|
{
|
|
49
|
-
className:
|
|
50
|
-
"absolute inset-0 opacity-0 size-full
|
|
51
|
-
|
|
50
|
+
className: d(
|
|
51
|
+
o === "auto" ? "opacity-0 w-full h-auto" : "absolute inset-0 opacity-0 size-full",
|
|
52
|
+
`object-${g}`,
|
|
53
|
+
p && "opacity-100"
|
|
52
54
|
),
|
|
53
|
-
src:
|
|
54
|
-
onLoad:
|
|
55
|
-
...
|
|
55
|
+
src: v,
|
|
56
|
+
onLoad: R,
|
|
57
|
+
...L
|
|
56
58
|
}
|
|
57
59
|
)
|
|
58
60
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.js","sources":["../../../src/components/atoms/image.tsx"],"sourcesContent":["/* eslint-disable jsx-a11y/alt-text */\n/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */\nimport {\n ImgHTMLAttributes,\n useCallback,\n useMemo,\n memo,\n useState,\n useEffect,\n} from 'react'\n\nimport {cn} from '../../lib/utils'\nimport {getThumbhashDataURL, getResizedImageUrl} from '../../utils'\n\ntype ImageProps = ImgHTMLAttributes<HTMLImageElement> & {\n src?: string\n file?: File\n thumbhash?: string | null\n aspectRatio?: number | string\n}\n\nexport const Image = memo(function Image(props: ImageProps) {\n const {\n src,\n file,\n thumbhash,\n onLoad,\n className,\n style,\n aspectRatio = 'auto',\n ...restProps\n } = props\n\n const [isLoaded, setIsLoaded] = useState(false)\n const [blobUrl, setBlobUrl] = useState<string | null>(null)\n\n // Create and manage blob URL for File objects\n useEffect(() => {\n if (!file) {\n setBlobUrl(null)\n return\n }\n\n const url = URL.createObjectURL(file)\n setBlobUrl(url)\n\n // Cleanup on unmount or when file changes\n return () => {\n URL.revokeObjectURL(url)\n }\n }, [file])\n\n const thumbhashDataURL = useMemo(\n () => getThumbhashDataURL(thumbhash ?? undefined),\n [thumbhash]\n )\n\n const handleLoad = useCallback(\n (event: React.SyntheticEvent<HTMLImageElement, Event>) => {\n setIsLoaded(true)\n onLoad?.(event)\n },\n [onLoad]\n )\n\n // Use blob URL if file is provided, otherwise use src\n const imageSrc = useMemo(() => {\n if (blobUrl) return blobUrl\n return getResizedImageUrl(src)\n }, [blobUrl, src])\n\n return (\n <div\n className={cn('relative w-full
|
|
1
|
+
{"version":3,"file":"image.js","sources":["../../../src/components/atoms/image.tsx"],"sourcesContent":["/* eslint-disable jsx-a11y/alt-text */\n/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */\nimport {\n ImgHTMLAttributes,\n useCallback,\n useMemo,\n memo,\n useState,\n useEffect,\n} from 'react'\n\nimport {cn} from '../../lib/utils'\nimport {getThumbhashDataURL, getResizedImageUrl} from '../../utils'\n\ntype ImageProps = ImgHTMLAttributes<HTMLImageElement> & {\n src?: string\n file?: File\n thumbhash?: string | null\n aspectRatio?: number | string\n objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | 'none'\n}\n\nexport const Image = memo(function Image(props: ImageProps) {\n const {\n src,\n file,\n thumbhash,\n onLoad,\n className,\n style,\n aspectRatio = 'auto',\n objectFit = 'contain',\n ...restProps\n } = props\n\n const [isLoaded, setIsLoaded] = useState(false)\n const [blobUrl, setBlobUrl] = useState<string | null>(null)\n\n // Create and manage blob URL for File objects\n useEffect(() => {\n if (!file) {\n setBlobUrl(null)\n return\n }\n\n const url = URL.createObjectURL(file)\n setBlobUrl(url)\n\n // Cleanup on unmount or when file changes\n return () => {\n URL.revokeObjectURL(url)\n }\n }, [file])\n\n const thumbhashDataURL = useMemo(\n () => getThumbhashDataURL(thumbhash ?? undefined),\n [thumbhash]\n )\n\n const handleLoad = useCallback(\n (event: React.SyntheticEvent<HTMLImageElement, Event>) => {\n setIsLoaded(true)\n onLoad?.(event)\n },\n [onLoad]\n )\n\n // Use blob URL if file is provided, otherwise use src\n const imageSrc = useMemo(() => {\n if (blobUrl) return blobUrl\n return getResizedImageUrl(src)\n }, [blobUrl, src])\n\n return (\n <div\n className={cn('relative w-full', className)}\n style={{\n ...style,\n ...(aspectRatio !== 'auto' && {aspectRatio}),\n backgroundImage: thumbhashDataURL\n ? `url(${thumbhashDataURL})`\n : undefined,\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n }}\n >\n <img\n className={cn(\n aspectRatio === 'auto'\n ? 'opacity-0 w-full h-auto'\n : 'absolute inset-0 opacity-0 size-full',\n `object-${objectFit}`,\n isLoaded && 'opacity-100'\n )}\n src={imageSrc}\n onLoad={handleLoad}\n {...restProps}\n />\n </div>\n )\n})\n"],"names":["Image","memo","props","src","file","thumbhash","onLoad","className","style","aspectRatio","objectFit","restProps","isLoaded","setIsLoaded","useState","blobUrl","setBlobUrl","useEffect","url","thumbhashDataURL","useMemo","getThumbhashDataURL","handleLoad","useCallback","event","imageSrc","getResizedImageUrl","jsx","cn"],"mappings":";;;;AAsBO,MAAMA,IAAQC,EAAK,SAAeC,GAAmB;AACpD,QAAA;AAAA,IACJ,KAAAC;AAAA,IACA,MAAAC;AAAA,IACA,WAAAC;AAAA,IACA,QAAAC;AAAA,IACA,WAAAC;AAAA,IACA,OAAAC;AAAA,IACA,aAAAC,IAAc;AAAA,IACd,WAAAC,IAAY;AAAA,IACZ,GAAGC;AAAA,EAAA,IACDT,GAEE,CAACU,GAAUC,CAAW,IAAIC,EAAS,EAAK,GACxC,CAACC,GAASC,CAAU,IAAIF,EAAwB,IAAI;AAG1D,EAAAG,EAAU,MAAM;AACd,QAAI,CAACb,GAAM;AACT,MAAAY,EAAW,IAAI;AACf;AAAA,IAAA;AAGI,UAAAE,IAAM,IAAI,gBAAgBd,CAAI;AACpC,WAAAY,EAAWE,CAAG,GAGP,MAAM;AACX,UAAI,gBAAgBA,CAAG;AAAA,IACzB;AAAA,EAAA,GACC,CAACd,CAAI,CAAC;AAET,QAAMe,IAAmBC;AAAA,IACvB,MAAMC,EAAoBhB,KAAa,MAAS;AAAA,IAChD,CAACA,CAAS;AAAA,EACZ,GAEMiB,IAAaC;AAAA,IACjB,CAACC,MAAyD;AACxD,MAAAX,EAAY,EAAI,GAChBP,IAASkB,CAAK;AAAA,IAChB;AAAA,IACA,CAAClB,CAAM;AAAA,EACT,GAGMmB,IAAWL,EAAQ,MACnBL,KACGW,EAAmBvB,CAAG,GAC5B,CAACY,GAASZ,CAAG,CAAC;AAGf,SAAA,gBAAAwB;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,EAAG,mBAAmBrB,CAAS;AAAA,MAC1C,OAAO;AAAA,QACL,GAAGC;AAAA,QACH,GAAIC,MAAgB,UAAU,EAAC,aAAAA,EAAW;AAAA,QAC1C,iBAAiBU,IACb,OAAOA,CAAgB,MACvB;AAAA,QACJ,gBAAgB;AAAA,QAChB,oBAAoB;AAAA,MACtB;AAAA,MAEA,UAAA,gBAAAQ;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAWC;AAAA,YACTnB,MAAgB,SACZ,4BACA;AAAA,YACJ,UAAUC,CAAS;AAAA,YACnBE,KAAY;AAAA,UACd;AAAA,UACA,KAAKa;AAAA,UACL,QAAQH;AAAA,UACP,GAAGX;AAAA,QAAA;AAAA,MAAA;AAAA,IACN;AAAA,EACF;AAEJ,CAAC;"}
|
|
@@ -1,109 +1,108 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useRef as
|
|
3
|
-
import { useRequestPermissions as
|
|
4
|
-
const
|
|
5
|
-
function
|
|
6
|
-
const f =
|
|
1
|
+
import { jsxs as x, jsx as m } from "react/jsx-runtime";
|
|
2
|
+
import { useRef as i, useCallback as s, useEffect as R, useMemo as I, createContext as b, useContext as A } from "react";
|
|
3
|
+
import { useRequestPermissions as L } from "../hooks/util/useRequestPermissions.js";
|
|
4
|
+
const k = b(null);
|
|
5
|
+
function H() {
|
|
6
|
+
const f = A(k);
|
|
7
7
|
if (!f)
|
|
8
8
|
throw new Error(
|
|
9
9
|
"useImagePickerContext must be used within an ImagePickerProvider"
|
|
10
10
|
);
|
|
11
11
|
return f;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const C = o(null), h = o(null), g = o(null), n = o(null), e = o(null), a = o(null), { requestPermission: d } = M(), t = s(() => {
|
|
13
|
+
function U({ children: f }) {
|
|
14
|
+
const C = i(null), h = i(null), g = i(null), n = i(null), e = i(null), a = i(null), { requestPermission: p } = L(), r = s(() => {
|
|
16
15
|
if (a.current) {
|
|
17
|
-
const { input:
|
|
18
|
-
|
|
16
|
+
const { input: u, handler: c } = a.current;
|
|
17
|
+
u.removeEventListener("cancel", c), a.current = null;
|
|
19
18
|
}
|
|
20
|
-
}, []),
|
|
19
|
+
}, []), o = s(() => {
|
|
21
20
|
e.current && (e.current(
|
|
22
21
|
new Error("New file picker opened before previous completed")
|
|
23
22
|
), n.current = null, e.current = null);
|
|
24
|
-
}, []),
|
|
25
|
-
(
|
|
26
|
-
const c =
|
|
27
|
-
c && n.current && (n.current(c), n.current = null, e.current = null,
|
|
23
|
+
}, []), d = s(
|
|
24
|
+
(u) => {
|
|
25
|
+
const c = u.target.files?.[0];
|
|
26
|
+
c && n.current && (n.current(c), n.current = null, e.current = null, r()), u.target.value = "";
|
|
28
27
|
},
|
|
29
|
-
[
|
|
30
|
-
),
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
28
|
+
[r]
|
|
29
|
+
), E = s(() => new Promise((u, c) => {
|
|
30
|
+
o(), r(), n.current = u, e.current = c;
|
|
31
|
+
const t = C.current;
|
|
32
|
+
if (!t) {
|
|
34
33
|
c(new Error("Gallery input not found")), n.current = null, e.current = null;
|
|
35
34
|
return;
|
|
36
35
|
}
|
|
37
|
-
const
|
|
38
|
-
e.current && (e.current(new Error("User cancelled file selection")), n.current = null, e.current = null),
|
|
36
|
+
const l = () => {
|
|
37
|
+
e.current && (e.current(new Error("User cancelled file selection")), n.current = null, e.current = null), r();
|
|
39
38
|
};
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
t.addEventListener("cancel", l), a.current = { input: t, handler: l }, p({ permission: "CAMERA" }).then(() => {
|
|
40
|
+
t.click();
|
|
42
41
|
}).catch(() => {
|
|
43
|
-
|
|
44
|
-
})
|
|
45
|
-
}), [
|
|
46
|
-
(
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
if (!
|
|
50
|
-
|
|
42
|
+
t.click();
|
|
43
|
+
});
|
|
44
|
+
}), [o, r, p]), P = s(
|
|
45
|
+
(u = "back") => new Promise((c, t) => {
|
|
46
|
+
o(), r(), n.current = c, e.current = t;
|
|
47
|
+
const l = u === "front" ? h.current : g.current;
|
|
48
|
+
if (!l) {
|
|
49
|
+
t(new Error("Camera input not found")), n.current = null, e.current = null;
|
|
51
50
|
return;
|
|
52
51
|
}
|
|
53
|
-
const
|
|
54
|
-
e.current && (e.current(new Error("User cancelled camera")), n.current = null, e.current = null),
|
|
52
|
+
const w = () => {
|
|
53
|
+
e.current && (e.current(new Error("User cancelled camera")), n.current = null, e.current = null), r();
|
|
55
54
|
};
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
l.addEventListener("cancel", w), a.current = { input: l, handler: w }, p({ permission: "CAMERA" }).then(({ granted: y }) => {
|
|
56
|
+
y ? l.click() : (t(new Error("Camera permission not granted")), n.current = null, e.current = null);
|
|
58
57
|
}).catch(() => {
|
|
59
|
-
|
|
60
|
-
})
|
|
58
|
+
t(new Error("Camera permission not granted")), n.current = null, e.current = null;
|
|
59
|
+
});
|
|
61
60
|
}),
|
|
62
|
-
[
|
|
61
|
+
[o, r, p]
|
|
63
62
|
);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}, [
|
|
67
|
-
const
|
|
63
|
+
R(() => () => {
|
|
64
|
+
o(), r();
|
|
65
|
+
}, [o, r]);
|
|
66
|
+
const v = I(
|
|
68
67
|
() => ({
|
|
69
68
|
openCamera: P,
|
|
70
|
-
openGallery:
|
|
69
|
+
openGallery: E
|
|
71
70
|
}),
|
|
72
|
-
[P,
|
|
71
|
+
[P, E]
|
|
73
72
|
);
|
|
74
|
-
return /* @__PURE__ */
|
|
73
|
+
return /* @__PURE__ */ x(k.Provider, { value: v, children: [
|
|
75
74
|
f,
|
|
76
|
-
/* @__PURE__ */
|
|
75
|
+
/* @__PURE__ */ m(
|
|
77
76
|
"input",
|
|
78
77
|
{
|
|
79
78
|
ref: C,
|
|
80
79
|
type: "file",
|
|
81
80
|
accept: "image/*",
|
|
82
|
-
onChange:
|
|
81
|
+
onChange: d,
|
|
83
82
|
style: { display: "none" },
|
|
84
83
|
"aria-hidden": "true"
|
|
85
84
|
}
|
|
86
85
|
),
|
|
87
|
-
/* @__PURE__ */
|
|
86
|
+
/* @__PURE__ */ m(
|
|
88
87
|
"input",
|
|
89
88
|
{
|
|
90
89
|
ref: h,
|
|
91
90
|
type: "file",
|
|
92
91
|
accept: "image/*",
|
|
93
92
|
capture: "user",
|
|
94
|
-
onChange:
|
|
93
|
+
onChange: d,
|
|
95
94
|
style: { display: "none" },
|
|
96
95
|
"aria-hidden": "true"
|
|
97
96
|
}
|
|
98
97
|
),
|
|
99
|
-
/* @__PURE__ */
|
|
98
|
+
/* @__PURE__ */ m(
|
|
100
99
|
"input",
|
|
101
100
|
{
|
|
102
101
|
ref: g,
|
|
103
102
|
type: "file",
|
|
104
103
|
accept: "image/*",
|
|
105
104
|
capture: "environment",
|
|
106
|
-
onChange:
|
|
105
|
+
onChange: d,
|
|
107
106
|
style: { display: "none" },
|
|
108
107
|
"aria-hidden": "true"
|
|
109
108
|
}
|
|
@@ -111,7 +110,7 @@ function F({ children: f }) {
|
|
|
111
110
|
] });
|
|
112
111
|
}
|
|
113
112
|
export {
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
U as ImagePickerProvider,
|
|
114
|
+
H as useImagePickerContext
|
|
116
115
|
};
|
|
117
116
|
//# sourceMappingURL=ImagePickerProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImagePickerProvider.js","sources":["../../src/providers/ImagePickerProvider.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n useRef,\n useCallback,\n useEffect,\n useMemo,\n} from 'react'\n\nimport {useRequestPermissions} from '../hooks/util/useRequestPermissions'\n\nexport type CameraFacing = 'front' | 'back'\n\ninterface ImagePickerContextValue {\n openCamera: (cameraFacing?: CameraFacing) => Promise<File>\n openGallery: () => Promise<File>\n}\n\nconst ImagePickerContext = createContext<ImagePickerContextValue | null>(null)\n\nexport function useImagePickerContext() {\n const context = useContext(ImagePickerContext)\n if (!context) {\n throw new Error(\n 'useImagePickerContext must be used within an ImagePickerProvider'\n )\n }\n return context\n}\n\ninterface ImagePickerProviderProps {\n children: React.ReactNode\n}\n\nconst isAndroid = () => window?.minisParams?.platform === 'android'\n\nexport function ImagePickerProvider({children}: ImagePickerProviderProps) {\n const galleryInputRef = useRef<HTMLInputElement>(null)\n const frontCameraInputRef = useRef<HTMLInputElement>(null)\n const backCameraInputRef = useRef<HTMLInputElement>(null)\n const resolveRef = useRef<((file: File) => void) | null>(null)\n const rejectRef = useRef<((reason: Error) => void) | null>(null)\n const activeCancelHandlerRef = useRef<{\n input: HTMLInputElement\n handler: () => void\n } | null>(null)\n\n const {requestPermission} = useRequestPermissions()\n\n const cleanupCancelHandler = useCallback(() => {\n if (activeCancelHandlerRef.current) {\n const {input, handler} = activeCancelHandlerRef.current\n input.removeEventListener('cancel', handler)\n activeCancelHandlerRef.current = null\n }\n }, [])\n\n const rejectPendingPromise = useCallback(() => {\n if (rejectRef.current) {\n rejectRef.current(\n new Error('New file picker opened before previous completed')\n )\n resolveRef.current = null\n rejectRef.current = null\n }\n }, [])\n\n const handleFileChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0]\n\n if (file && resolveRef.current) {\n resolveRef.current(file)\n\n resolveRef.current = null\n rejectRef.current = null\n\n cleanupCancelHandler()\n }\n\n event.target.value = ''\n },\n [cleanupCancelHandler]\n )\n\n const openGallery = useCallback(() => {\n return new Promise<File>((resolve, reject) => {\n rejectPendingPromise()\n cleanupCancelHandler()\n\n resolveRef.current = resolve\n rejectRef.current = reject\n\n const input = galleryInputRef.current\n\n if (!input) {\n reject(new Error('Gallery input not found'))\n resolveRef.current = null\n rejectRef.current = null\n return\n }\n\n const handleCancel = () => {\n if (rejectRef.current) {\n rejectRef.current(new Error('User cancelled file selection'))\n resolveRef.current = null\n rejectRef.current = null\n }\n cleanupCancelHandler()\n }\n\n input.addEventListener('cancel', handleCancel)\n activeCancelHandlerRef.current = {input, handler: handleCancel}\n\n if (isAndroid()) {\n // Android requires explicit camera permission for camera picker\n requestPermission({permission: 'CAMERA'})\n .then(() => {\n // This will show both Camera and Gallery\n input.click()\n })\n .catch(() => {\n // Show only Gallery\n input.click()\n })\n } else {\n input.click()\n }\n })\n }, [rejectPendingPromise, cleanupCancelHandler, requestPermission])\n\n const openCamera = useCallback(\n (cameraFacing: CameraFacing = 'back') => {\n return new Promise<File>((resolve, reject) => {\n rejectPendingPromise()\n cleanupCancelHandler()\n\n resolveRef.current = resolve\n rejectRef.current = reject\n\n const input =\n cameraFacing === 'front'\n ? frontCameraInputRef.current\n : backCameraInputRef.current\n\n if (!input) {\n reject(new Error('Camera input not found'))\n resolveRef.current = null\n rejectRef.current = null\n return\n }\n\n const handleCancel = () => {\n if (rejectRef.current) {\n rejectRef.current(new Error('User cancelled camera'))\n resolveRef.current = null\n rejectRef.current = null\n }\n cleanupCancelHandler()\n }\n\n input.addEventListener('cancel', handleCancel)\n activeCancelHandlerRef.current = {input, handler: handleCancel}\n\n if (isAndroid()) {\n // Android requires explicit camera permission\n requestPermission({permission: 'CAMERA'})\n .then(({granted}) => {\n if (granted) {\n input.click()\n } else {\n reject(new Error('Camera permission not granted'))\n resolveRef.current = null\n rejectRef.current = null\n }\n })\n .catch(() => {\n reject(new Error('Camera permission not granted'))\n resolveRef.current = null\n rejectRef.current = null\n })\n } else {\n input.click()\n }\n })\n },\n [rejectPendingPromise, cleanupCancelHandler, requestPermission]\n )\n\n useEffect(() => {\n return () => {\n rejectPendingPromise()\n cleanupCancelHandler()\n }\n }, [rejectPendingPromise, cleanupCancelHandler])\n\n const contextValue: ImagePickerContextValue = useMemo(\n () => ({\n openCamera,\n openGallery,\n }),\n [openCamera, openGallery]\n )\n\n return (\n <ImagePickerContext.Provider value={contextValue}>\n {children}\n <input\n ref={galleryInputRef}\n type=\"file\"\n accept=\"image/*\"\n onChange={handleFileChange}\n style={{display: 'none'}}\n aria-hidden=\"true\"\n />\n <input\n ref={frontCameraInputRef}\n type=\"file\"\n accept=\"image/*\"\n capture=\"user\"\n onChange={handleFileChange}\n style={{display: 'none'}}\n aria-hidden=\"true\"\n />\n <input\n ref={backCameraInputRef}\n type=\"file\"\n accept=\"image/*\"\n capture=\"environment\"\n onChange={handleFileChange}\n style={{display: 'none'}}\n aria-hidden=\"true\"\n />\n </ImagePickerContext.Provider>\n )\n}\n"],"names":["ImagePickerContext","createContext","useImagePickerContext","context","useContext","isAndroid","ImagePickerProvider","children","galleryInputRef","useRef","frontCameraInputRef","backCameraInputRef","resolveRef","rejectRef","activeCancelHandlerRef","requestPermission","useRequestPermissions","cleanupCancelHandler","useCallback","input","handler","rejectPendingPromise","handleFileChange","event","file","openGallery","resolve","reject","handleCancel","openCamera","cameraFacing","granted","useEffect","contextValue","useMemo","jsxs","jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ImagePickerProvider.js","sources":["../../src/providers/ImagePickerProvider.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n useRef,\n useCallback,\n useEffect,\n useMemo,\n} from 'react'\n\nimport {useRequestPermissions} from '../hooks/util/useRequestPermissions'\n\nexport type CameraFacing = 'front' | 'back'\n\ninterface ImagePickerContextValue {\n openCamera: (cameraFacing?: CameraFacing) => Promise<File>\n openGallery: () => Promise<File>\n}\n\nconst ImagePickerContext = createContext<ImagePickerContextValue | null>(null)\n\nexport function useImagePickerContext() {\n const context = useContext(ImagePickerContext)\n if (!context) {\n throw new Error(\n 'useImagePickerContext must be used within an ImagePickerProvider'\n )\n }\n return context\n}\n\ninterface ImagePickerProviderProps {\n children: React.ReactNode\n}\n\nexport function ImagePickerProvider({children}: ImagePickerProviderProps) {\n const galleryInputRef = useRef<HTMLInputElement>(null)\n const frontCameraInputRef = useRef<HTMLInputElement>(null)\n const backCameraInputRef = useRef<HTMLInputElement>(null)\n const resolveRef = useRef<((file: File) => void) | null>(null)\n const rejectRef = useRef<((reason: Error) => void) | null>(null)\n const activeCancelHandlerRef = useRef<{\n input: HTMLInputElement\n handler: () => void\n } | null>(null)\n\n const {requestPermission} = useRequestPermissions()\n\n const cleanupCancelHandler = useCallback(() => {\n if (activeCancelHandlerRef.current) {\n const {input, handler} = activeCancelHandlerRef.current\n input.removeEventListener('cancel', handler)\n activeCancelHandlerRef.current = null\n }\n }, [])\n\n const rejectPendingPromise = useCallback(() => {\n if (rejectRef.current) {\n rejectRef.current(\n new Error('New file picker opened before previous completed')\n )\n resolveRef.current = null\n rejectRef.current = null\n }\n }, [])\n\n const handleFileChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0]\n\n if (file && resolveRef.current) {\n resolveRef.current(file)\n\n resolveRef.current = null\n rejectRef.current = null\n\n cleanupCancelHandler()\n }\n\n event.target.value = ''\n },\n [cleanupCancelHandler]\n )\n\n const openGallery = useCallback(() => {\n return new Promise<File>((resolve, reject) => {\n rejectPendingPromise()\n cleanupCancelHandler()\n\n resolveRef.current = resolve\n rejectRef.current = reject\n\n const input = galleryInputRef.current\n\n if (!input) {\n reject(new Error('Gallery input not found'))\n resolveRef.current = null\n rejectRef.current = null\n return\n }\n\n const handleCancel = () => {\n if (rejectRef.current) {\n rejectRef.current(new Error('User cancelled file selection'))\n resolveRef.current = null\n rejectRef.current = null\n }\n cleanupCancelHandler()\n }\n\n input.addEventListener('cancel', handleCancel)\n activeCancelHandlerRef.current = {input, handler: handleCancel}\n\n requestPermission({permission: 'CAMERA'})\n .then(() => {\n // This will show both Camera and Gallery\n input.click()\n })\n .catch(() => {\n // Show only Gallery\n input.click()\n })\n })\n }, [rejectPendingPromise, cleanupCancelHandler, requestPermission])\n\n const openCamera = useCallback(\n (cameraFacing: CameraFacing = 'back') => {\n return new Promise<File>((resolve, reject) => {\n rejectPendingPromise()\n cleanupCancelHandler()\n\n resolveRef.current = resolve\n rejectRef.current = reject\n\n const input =\n cameraFacing === 'front'\n ? frontCameraInputRef.current\n : backCameraInputRef.current\n\n if (!input) {\n reject(new Error('Camera input not found'))\n resolveRef.current = null\n rejectRef.current = null\n return\n }\n\n const handleCancel = () => {\n if (rejectRef.current) {\n rejectRef.current(new Error('User cancelled camera'))\n resolveRef.current = null\n rejectRef.current = null\n }\n cleanupCancelHandler()\n }\n\n input.addEventListener('cancel', handleCancel)\n activeCancelHandlerRef.current = {input, handler: handleCancel}\n\n requestPermission({permission: 'CAMERA'})\n .then(({granted}) => {\n if (granted) {\n input.click()\n } else {\n reject(new Error('Camera permission not granted'))\n resolveRef.current = null\n rejectRef.current = null\n }\n })\n .catch(() => {\n reject(new Error('Camera permission not granted'))\n resolveRef.current = null\n rejectRef.current = null\n })\n })\n },\n [rejectPendingPromise, cleanupCancelHandler, requestPermission]\n )\n\n useEffect(() => {\n return () => {\n rejectPendingPromise()\n cleanupCancelHandler()\n }\n }, [rejectPendingPromise, cleanupCancelHandler])\n\n const contextValue: ImagePickerContextValue = useMemo(\n () => ({\n openCamera,\n openGallery,\n }),\n [openCamera, openGallery]\n )\n\n return (\n <ImagePickerContext.Provider value={contextValue}>\n {children}\n <input\n ref={galleryInputRef}\n type=\"file\"\n accept=\"image/*\"\n onChange={handleFileChange}\n style={{display: 'none'}}\n aria-hidden=\"true\"\n />\n <input\n ref={frontCameraInputRef}\n type=\"file\"\n accept=\"image/*\"\n capture=\"user\"\n onChange={handleFileChange}\n style={{display: 'none'}}\n aria-hidden=\"true\"\n />\n <input\n ref={backCameraInputRef}\n type=\"file\"\n accept=\"image/*\"\n capture=\"environment\"\n onChange={handleFileChange}\n style={{display: 'none'}}\n aria-hidden=\"true\"\n />\n </ImagePickerContext.Provider>\n )\n}\n"],"names":["ImagePickerContext","createContext","useImagePickerContext","context","useContext","ImagePickerProvider","children","galleryInputRef","useRef","frontCameraInputRef","backCameraInputRef","resolveRef","rejectRef","activeCancelHandlerRef","requestPermission","useRequestPermissions","cleanupCancelHandler","useCallback","input","handler","rejectPendingPromise","handleFileChange","event","file","openGallery","resolve","reject","handleCancel","openCamera","cameraFacing","granted","useEffect","contextValue","useMemo","jsxs","jsx"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __module as q } from "../../../../../../../../_virtual/
|
|
1
|
+
import { __module as q } from "../../../../../../../../_virtual/index5.js";
|
|
2
2
|
import { __require as F } from "../../../../../global@4.4.0/node_modules/global/window.js";
|
|
3
3
|
import { __require as N } from "../../../../../@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/extends.js";
|
|
4
4
|
import { __require as J } from "../../../../../is-function@1.0.2/node_modules/is-function/index.js";
|
|
@@ -2,7 +2,7 @@ import L from "../../../../@videojs_vhs-utils@4.1.1/node_modules/@videojs/vhs-ut
|
|
|
2
2
|
import T from "../../../../../../../_virtual/window.js";
|
|
3
3
|
import { forEachMediaGroup as Z } from "../../../../@videojs_vhs-utils@4.1.1/node_modules/@videojs/vhs-utils/es/media-groups.js";
|
|
4
4
|
import J from "../../../../@videojs_vhs-utils@4.1.1/node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js";
|
|
5
|
-
import { l as Q } from "../../../../../../../_virtual/
|
|
5
|
+
import { l as Q } from "../../../../../../../_virtual/index6.js";
|
|
6
6
|
/*! @name mpd-parser @version 1.3.1 @license Apache-2.0 */
|
|
7
7
|
const w = (e) => !!e && typeof e == "object", E = (...e) => e.reduce((n, t) => (typeof t != "object" || Object.keys(t).forEach((r) => {
|
|
8
8
|
Array.isArray(n[r]) && Array.isArray(t[r]) ? n[r] = n[r].concat(t[r]) : w(n[r]) && w(t[r]) ? n[r] = E(n[r], t[r]) : n[r] = t[r];
|
|
@@ -1,48 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"useBuyerAttributes": [
|
|
3
|
-
"
|
|
3
|
+
"profile"
|
|
4
4
|
],
|
|
5
5
|
"useCurrentUser": [
|
|
6
|
-
"
|
|
6
|
+
"user_settings:read"
|
|
7
7
|
],
|
|
8
8
|
"useFollowedShops": [
|
|
9
|
-
"
|
|
9
|
+
"shops:follows:read"
|
|
10
10
|
],
|
|
11
11
|
"useFollowedShopsActions": [
|
|
12
|
-
"
|
|
12
|
+
"shops:follows:write"
|
|
13
13
|
],
|
|
14
14
|
"useGenerateUserToken": [
|
|
15
|
-
"
|
|
15
|
+
"openid"
|
|
16
16
|
],
|
|
17
17
|
"useOrders": [
|
|
18
|
-
"
|
|
18
|
+
"orders"
|
|
19
19
|
],
|
|
20
20
|
"useProductList": [
|
|
21
|
-
"
|
|
21
|
+
"product_list:read"
|
|
22
22
|
],
|
|
23
23
|
"useProductListActions": [
|
|
24
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"product_list:write",
|
|
25
|
+
"product_list_item:write"
|
|
26
26
|
],
|
|
27
27
|
"useProductLists": [
|
|
28
|
-
"
|
|
28
|
+
"product_list:read"
|
|
29
29
|
],
|
|
30
30
|
"useRecentProducts": [
|
|
31
|
-
"
|
|
31
|
+
"products:recent:read"
|
|
32
32
|
],
|
|
33
33
|
"useRecentShops": [
|
|
34
|
-
"
|
|
34
|
+
"shops:recent:read"
|
|
35
35
|
],
|
|
36
36
|
"useRecommendedProducts": [
|
|
37
|
-
"
|
|
37
|
+
"products:recommendations:read"
|
|
38
38
|
],
|
|
39
39
|
"useRecommendedShops": [
|
|
40
|
-
"
|
|
40
|
+
"shops:recommendations:read"
|
|
41
41
|
],
|
|
42
42
|
"useSavedProducts": [
|
|
43
|
-
"
|
|
43
|
+
"product_list:read"
|
|
44
44
|
],
|
|
45
45
|
"useSavedProductsActions": [
|
|
46
|
-
"
|
|
46
|
+
"product_list:write"
|
|
47
47
|
]
|
|
48
48
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shopify/shop-minis-react",
|
|
3
3
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"typescript": ">=5.0.0"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@shopify/shop-minis-platform": "0.
|
|
46
|
+
"@shopify/shop-minis-platform": "0.6.0",
|
|
47
47
|
"@tailwindcss/vite": "4.1.8",
|
|
48
48
|
"@types/color": "3.0.6",
|
|
49
49
|
"@types/lodash": "4.17.20",
|
|
@@ -76,12 +76,13 @@ describe('Image', () => {
|
|
|
76
76
|
expect(wrapper.style.aspectRatio).toBe('16/9')
|
|
77
77
|
})
|
|
78
78
|
|
|
79
|
-
it('uses thumbhash as background when provided', () => {
|
|
79
|
+
it('uses thumbhash as background when provided with fixed aspect ratio', () => {
|
|
80
80
|
const {container} = render(
|
|
81
81
|
<Image
|
|
82
82
|
src="https://example.com/image.jpg"
|
|
83
83
|
alt="Test image"
|
|
84
84
|
thumbhash="testThumbhash"
|
|
85
|
+
aspectRatio="1"
|
|
85
86
|
/>
|
|
86
87
|
)
|
|
87
88
|
|
|
@@ -91,6 +92,45 @@ describe('Image', () => {
|
|
|
91
92
|
)
|
|
92
93
|
})
|
|
93
94
|
|
|
95
|
+
it('renders with natural sizing when aspectRatio is auto', () => {
|
|
96
|
+
const {container} = render(
|
|
97
|
+
<Image
|
|
98
|
+
src="https://example.com/image.jpg"
|
|
99
|
+
alt="Test image"
|
|
100
|
+
aspectRatio="auto"
|
|
101
|
+
className="custom-class"
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const wrapper = container.firstChild as HTMLElement
|
|
106
|
+
const img = screen.getByRole('img')
|
|
107
|
+
|
|
108
|
+
expect(wrapper.tagName).toBe('DIV')
|
|
109
|
+
expect(wrapper).toHaveClass('custom-class')
|
|
110
|
+
expect(wrapper.style.aspectRatio).toBe('')
|
|
111
|
+
expect(img).toHaveClass('w-full', 'h-auto')
|
|
112
|
+
expect(img).toHaveAttribute('src', 'https://example.com/image.jpg')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('preserves thumbhash with natural sizing', () => {
|
|
116
|
+
const {container} = render(
|
|
117
|
+
<Image
|
|
118
|
+
src="https://example.com/image.jpg"
|
|
119
|
+
alt="Test image"
|
|
120
|
+
aspectRatio="auto"
|
|
121
|
+
thumbhash="testThumbhash"
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const wrapper = container.firstChild as HTMLElement
|
|
126
|
+
const img = screen.getByRole('img')
|
|
127
|
+
|
|
128
|
+
expect(wrapper.style.backgroundImage).toContain(
|
|
129
|
+
'data:image/png;base64,testThumbhash'
|
|
130
|
+
)
|
|
131
|
+
expect(img).toHaveClass('w-full', 'h-auto')
|
|
132
|
+
})
|
|
133
|
+
|
|
94
134
|
it('passes additional props to img element', () => {
|
|
95
135
|
render(
|
|
96
136
|
<Image
|
|
@@ -17,6 +17,7 @@ type ImageProps = ImgHTMLAttributes<HTMLImageElement> & {
|
|
|
17
17
|
file?: File
|
|
18
18
|
thumbhash?: string | null
|
|
19
19
|
aspectRatio?: number | string
|
|
20
|
+
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | 'none'
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export const Image = memo(function Image(props: ImageProps) {
|
|
@@ -28,6 +29,7 @@ export const Image = memo(function Image(props: ImageProps) {
|
|
|
28
29
|
className,
|
|
29
30
|
style,
|
|
30
31
|
aspectRatio = 'auto',
|
|
32
|
+
objectFit = 'contain',
|
|
31
33
|
...restProps
|
|
32
34
|
} = props
|
|
33
35
|
|
|
@@ -71,10 +73,10 @@ export const Image = memo(function Image(props: ImageProps) {
|
|
|
71
73
|
|
|
72
74
|
return (
|
|
73
75
|
<div
|
|
74
|
-
className={cn('relative w-full
|
|
76
|
+
className={cn('relative w-full', className)}
|
|
75
77
|
style={{
|
|
76
78
|
...style,
|
|
77
|
-
aspectRatio,
|
|
79
|
+
...(aspectRatio !== 'auto' && {aspectRatio}),
|
|
78
80
|
backgroundImage: thumbhashDataURL
|
|
79
81
|
? `url(${thumbhashDataURL})`
|
|
80
82
|
: undefined,
|
|
@@ -84,7 +86,10 @@ export const Image = memo(function Image(props: ImageProps) {
|
|
|
84
86
|
>
|
|
85
87
|
<img
|
|
86
88
|
className={cn(
|
|
87
|
-
|
|
89
|
+
aspectRatio === 'auto'
|
|
90
|
+
? 'opacity-0 w-full h-auto'
|
|
91
|
+
: 'absolute inset-0 opacity-0 size-full',
|
|
92
|
+
`object-${objectFit}`,
|
|
88
93
|
isLoaded && 'opacity-100'
|
|
89
94
|
)}
|
|
90
95
|
src={imageSrc}
|
|
@@ -89,7 +89,9 @@ describe('ImagePickerProvider', () => {
|
|
|
89
89
|
})
|
|
90
90
|
|
|
91
91
|
describe('openGallery', () => {
|
|
92
|
-
it('
|
|
92
|
+
it('requests CAMERA permission before showing picker', async () => {
|
|
93
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
94
|
+
|
|
93
95
|
const TestComponent = () => {
|
|
94
96
|
const {openGallery} = useImagePickerContext()
|
|
95
97
|
|
|
@@ -129,7 +131,17 @@ describe('ImagePickerProvider', () => {
|
|
|
129
131
|
const button = screen.getByText('Open Gallery')
|
|
130
132
|
fireEvent.click(button)
|
|
131
133
|
|
|
132
|
-
|
|
134
|
+
// Wait for permission request to complete
|
|
135
|
+
await vi.waitFor(() => {
|
|
136
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
137
|
+
permission: 'CAMERA',
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Input should be clicked after permission request
|
|
142
|
+
await vi.waitFor(() => {
|
|
143
|
+
expect(clickSpy).toHaveBeenCalledTimes(1)
|
|
144
|
+
})
|
|
133
145
|
|
|
134
146
|
// Simulate file selection
|
|
135
147
|
const file = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
|
|
@@ -152,6 +164,8 @@ describe('ImagePickerProvider', () => {
|
|
|
152
164
|
})
|
|
153
165
|
|
|
154
166
|
it('handles cancel event', async () => {
|
|
167
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
168
|
+
|
|
155
169
|
const TestComponent = () => {
|
|
156
170
|
const {openGallery} = useImagePickerContext()
|
|
157
171
|
|
|
@@ -185,6 +199,13 @@ describe('ImagePickerProvider', () => {
|
|
|
185
199
|
const button = screen.getByText('Open Gallery')
|
|
186
200
|
fireEvent.click(button)
|
|
187
201
|
|
|
202
|
+
// Wait for permission request to complete
|
|
203
|
+
await vi.waitFor(() => {
|
|
204
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
205
|
+
permission: 'CAMERA',
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
188
209
|
// Simulate cancel event
|
|
189
210
|
await act(async () => {
|
|
190
211
|
const cancelEvent = new Event('cancel', {bubbles: true})
|
|
@@ -199,56 +220,7 @@ describe('ImagePickerProvider', () => {
|
|
|
199
220
|
})
|
|
200
221
|
})
|
|
201
222
|
|
|
202
|
-
it('
|
|
203
|
-
window.minisParams = {...window.minisParams, platform: 'android'} as any
|
|
204
|
-
mockRequestPermission.mockResolvedValue({granted: true})
|
|
205
|
-
|
|
206
|
-
const TestComponent = () => {
|
|
207
|
-
const {openGallery} = useImagePickerContext()
|
|
208
|
-
|
|
209
|
-
return (
|
|
210
|
-
<button
|
|
211
|
-
type="button"
|
|
212
|
-
onClick={() =>
|
|
213
|
-
openGallery().catch(() => {
|
|
214
|
-
// Ignore errors from cleanup
|
|
215
|
-
})
|
|
216
|
-
}
|
|
217
|
-
>
|
|
218
|
-
Open Gallery
|
|
219
|
-
</button>
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const {container} = render(
|
|
224
|
-
<ImagePickerProvider>
|
|
225
|
-
<TestComponent />
|
|
226
|
-
</ImagePickerProvider>
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
const galleryInput = container.querySelector(
|
|
230
|
-
'input[type="file"]:not([capture])'
|
|
231
|
-
) as HTMLInputElement
|
|
232
|
-
const clickSpy = vi.spyOn(galleryInput, 'click')
|
|
233
|
-
|
|
234
|
-
const button = screen.getByText('Open Gallery')
|
|
235
|
-
fireEvent.click(button)
|
|
236
|
-
|
|
237
|
-
// Wait for permission request to complete
|
|
238
|
-
await vi.waitFor(() => {
|
|
239
|
-
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
240
|
-
permission: 'CAMERA',
|
|
241
|
-
})
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
// Input should be clicked after permission request
|
|
245
|
-
await vi.waitFor(() => {
|
|
246
|
-
expect(clickSpy).toHaveBeenCalledTimes(1)
|
|
247
|
-
})
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
it('still opens picker on Android even if CAMERA permission is denied', async () => {
|
|
251
|
-
window.minisParams = {...window.minisParams, platform: 'android'} as any
|
|
223
|
+
it('still opens picker even if CAMERA permission is denied', async () => {
|
|
252
224
|
mockRequestPermission.mockRejectedValue(new Error('Permission denied'))
|
|
253
225
|
|
|
254
226
|
const TestComponent = () => {
|
|
@@ -297,7 +269,9 @@ describe('ImagePickerProvider', () => {
|
|
|
297
269
|
})
|
|
298
270
|
|
|
299
271
|
describe('openCamera', () => {
|
|
300
|
-
it('
|
|
272
|
+
it('requests CAMERA permission and opens camera if granted', async () => {
|
|
273
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
274
|
+
|
|
301
275
|
const TestComponent = () => {
|
|
302
276
|
const {openCamera} = useImagePickerContext()
|
|
303
277
|
|
|
@@ -329,10 +303,22 @@ describe('ImagePickerProvider', () => {
|
|
|
329
303
|
const button = screen.getByText('Open Camera')
|
|
330
304
|
fireEvent.click(button)
|
|
331
305
|
|
|
332
|
-
|
|
306
|
+
// Wait for permission request to complete
|
|
307
|
+
await vi.waitFor(() => {
|
|
308
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
309
|
+
permission: 'CAMERA',
|
|
310
|
+
})
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
// Input should be clicked after permission is granted
|
|
314
|
+
await vi.waitFor(() => {
|
|
315
|
+
expect(clickSpy).toHaveBeenCalledTimes(1)
|
|
316
|
+
})
|
|
333
317
|
})
|
|
334
318
|
|
|
335
319
|
it('triggers front camera input click when specified', async () => {
|
|
320
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
321
|
+
|
|
336
322
|
const TestComponent = () => {
|
|
337
323
|
const {openCamera} = useImagePickerContext()
|
|
338
324
|
|
|
@@ -364,10 +350,22 @@ describe('ImagePickerProvider', () => {
|
|
|
364
350
|
const button = screen.getByText('Open Front Camera')
|
|
365
351
|
fireEvent.click(button)
|
|
366
352
|
|
|
367
|
-
|
|
353
|
+
// Wait for permission request to complete
|
|
354
|
+
await vi.waitFor(() => {
|
|
355
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
356
|
+
permission: 'CAMERA',
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
// Front camera input should be clicked after permission is granted
|
|
361
|
+
await vi.waitFor(() => {
|
|
362
|
+
expect(clickSpy).toHaveBeenCalledTimes(1)
|
|
363
|
+
})
|
|
368
364
|
})
|
|
369
365
|
|
|
370
366
|
it('resolves with selected file from camera', async () => {
|
|
367
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
368
|
+
|
|
371
369
|
const TestComponent = () => {
|
|
372
370
|
const {openCamera} = useImagePickerContext()
|
|
373
371
|
|
|
@@ -405,6 +403,13 @@ describe('ImagePickerProvider', () => {
|
|
|
405
403
|
const button = screen.getByText('Open Camera')
|
|
406
404
|
fireEvent.click(button)
|
|
407
405
|
|
|
406
|
+
// Wait for permission request to complete
|
|
407
|
+
await vi.waitFor(() => {
|
|
408
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
409
|
+
permission: 'CAMERA',
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
|
|
408
413
|
// Simulate file capture
|
|
409
414
|
const file = new File(['photo'], 'photo.jpg', {type: 'image/jpeg'})
|
|
410
415
|
Object.defineProperty(cameraInput, 'files', {
|
|
@@ -423,6 +428,8 @@ describe('ImagePickerProvider', () => {
|
|
|
423
428
|
})
|
|
424
429
|
|
|
425
430
|
it('handles cancel event for camera', async () => {
|
|
431
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
432
|
+
|
|
426
433
|
const TestComponent = () => {
|
|
427
434
|
const {openCamera} = useImagePickerContext()
|
|
428
435
|
|
|
@@ -456,6 +463,13 @@ describe('ImagePickerProvider', () => {
|
|
|
456
463
|
const button = screen.getByText('Open Camera')
|
|
457
464
|
fireEvent.click(button)
|
|
458
465
|
|
|
466
|
+
// Wait for permission request to complete
|
|
467
|
+
await vi.waitFor(() => {
|
|
468
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
469
|
+
permission: 'CAMERA',
|
|
470
|
+
})
|
|
471
|
+
})
|
|
472
|
+
|
|
459
473
|
// Simulate cancel event
|
|
460
474
|
await act(async () => {
|
|
461
475
|
const cancelEvent = new Event('cancel', {bubbles: true})
|
|
@@ -470,56 +484,7 @@ describe('ImagePickerProvider', () => {
|
|
|
470
484
|
})
|
|
471
485
|
})
|
|
472
486
|
|
|
473
|
-
it('
|
|
474
|
-
window.minisParams = {...window.minisParams, platform: 'android'} as any
|
|
475
|
-
mockRequestPermission.mockResolvedValue({granted: true})
|
|
476
|
-
|
|
477
|
-
const TestComponent = () => {
|
|
478
|
-
const {openCamera} = useImagePickerContext()
|
|
479
|
-
|
|
480
|
-
return (
|
|
481
|
-
<button
|
|
482
|
-
type="button"
|
|
483
|
-
onClick={() =>
|
|
484
|
-
openCamera().catch(() => {
|
|
485
|
-
// Ignore errors from cleanup
|
|
486
|
-
})
|
|
487
|
-
}
|
|
488
|
-
>
|
|
489
|
-
Open Camera
|
|
490
|
-
</button>
|
|
491
|
-
)
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
const {container} = render(
|
|
495
|
-
<ImagePickerProvider>
|
|
496
|
-
<TestComponent />
|
|
497
|
-
</ImagePickerProvider>
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
const cameraInput = container.querySelector(
|
|
501
|
-
'input[capture="environment"]'
|
|
502
|
-
) as HTMLInputElement
|
|
503
|
-
const clickSpy = vi.spyOn(cameraInput, 'click')
|
|
504
|
-
|
|
505
|
-
const button = screen.getByText('Open Camera')
|
|
506
|
-
fireEvent.click(button)
|
|
507
|
-
|
|
508
|
-
// Wait for permission request to complete
|
|
509
|
-
await vi.waitFor(() => {
|
|
510
|
-
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
511
|
-
permission: 'CAMERA',
|
|
512
|
-
})
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
// Input should be clicked after permission is granted
|
|
516
|
-
await vi.waitFor(() => {
|
|
517
|
-
expect(clickSpy).toHaveBeenCalledTimes(1)
|
|
518
|
-
})
|
|
519
|
-
})
|
|
520
|
-
|
|
521
|
-
it('rejects with error on Android if CAMERA permission is not granted', async () => {
|
|
522
|
-
window.minisParams = {...window.minisParams, platform: 'android'} as any
|
|
487
|
+
it('rejects with error if CAMERA permission is not granted', async () => {
|
|
523
488
|
mockRequestPermission.mockResolvedValue({granted: false})
|
|
524
489
|
|
|
525
490
|
const TestComponent = () => {
|
|
@@ -575,8 +540,7 @@ describe('ImagePickerProvider', () => {
|
|
|
575
540
|
})
|
|
576
541
|
})
|
|
577
542
|
|
|
578
|
-
it('rejects with error
|
|
579
|
-
window.minisParams = {...window.minisParams, platform: 'android'} as any
|
|
543
|
+
it('rejects with error if permission request fails', async () => {
|
|
580
544
|
mockRequestPermission.mockRejectedValue(
|
|
581
545
|
new Error('Permission request failed')
|
|
582
546
|
)
|
|
@@ -633,58 +597,12 @@ describe('ImagePickerProvider', () => {
|
|
|
633
597
|
expect(errorMessage?.textContent).toBe('Camera permission not granted')
|
|
634
598
|
})
|
|
635
599
|
})
|
|
636
|
-
|
|
637
|
-
it('requests permission for front camera on Android', async () => {
|
|
638
|
-
window.minisParams = {...window.minisParams, platform: 'android'} as any
|
|
639
|
-
mockRequestPermission.mockResolvedValue({granted: true})
|
|
640
|
-
|
|
641
|
-
const TestComponent = () => {
|
|
642
|
-
const {openCamera} = useImagePickerContext()
|
|
643
|
-
|
|
644
|
-
return (
|
|
645
|
-
<button
|
|
646
|
-
type="button"
|
|
647
|
-
onClick={() =>
|
|
648
|
-
openCamera('front').catch(() => {
|
|
649
|
-
// Ignore errors from cleanup
|
|
650
|
-
})
|
|
651
|
-
}
|
|
652
|
-
>
|
|
653
|
-
Open Front Camera
|
|
654
|
-
</button>
|
|
655
|
-
)
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const {container} = render(
|
|
659
|
-
<ImagePickerProvider>
|
|
660
|
-
<TestComponent />
|
|
661
|
-
</ImagePickerProvider>
|
|
662
|
-
)
|
|
663
|
-
|
|
664
|
-
const frontCameraInput = container.querySelector(
|
|
665
|
-
'input[capture="user"]'
|
|
666
|
-
) as HTMLInputElement
|
|
667
|
-
const clickSpy = vi.spyOn(frontCameraInput, 'click')
|
|
668
|
-
|
|
669
|
-
const button = screen.getByText('Open Front Camera')
|
|
670
|
-
fireEvent.click(button)
|
|
671
|
-
|
|
672
|
-
// Wait for permission request to complete
|
|
673
|
-
await vi.waitFor(() => {
|
|
674
|
-
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
675
|
-
permission: 'CAMERA',
|
|
676
|
-
})
|
|
677
|
-
})
|
|
678
|
-
|
|
679
|
-
// Front camera input should be clicked after permission is granted
|
|
680
|
-
await vi.waitFor(() => {
|
|
681
|
-
expect(clickSpy).toHaveBeenCalledTimes(1)
|
|
682
|
-
})
|
|
683
|
-
})
|
|
684
600
|
})
|
|
685
601
|
|
|
686
602
|
describe('Multiple Picker Handling', () => {
|
|
687
603
|
it('rejects previous promise when new picker is opened', async () => {
|
|
604
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
605
|
+
|
|
688
606
|
const TestComponent = () => {
|
|
689
607
|
const {openCamera, openGallery} = useImagePickerContext()
|
|
690
608
|
|
|
@@ -744,6 +662,8 @@ describe('ImagePickerProvider', () => {
|
|
|
744
662
|
|
|
745
663
|
describe('Cleanup', () => {
|
|
746
664
|
it('clears input value after file selection', async () => {
|
|
665
|
+
mockRequestPermission.mockResolvedValue({granted: true})
|
|
666
|
+
|
|
747
667
|
const TestComponent = () => {
|
|
748
668
|
const {openGallery} = useImagePickerContext()
|
|
749
669
|
|
|
@@ -774,6 +694,13 @@ describe('ImagePickerProvider', () => {
|
|
|
774
694
|
const button = screen.getByText('Open Gallery')
|
|
775
695
|
fireEvent.click(button)
|
|
776
696
|
|
|
697
|
+
// Wait for permission request to complete
|
|
698
|
+
await vi.waitFor(() => {
|
|
699
|
+
expect(mockRequestPermission).toHaveBeenCalledWith({
|
|
700
|
+
permission: 'CAMERA',
|
|
701
|
+
})
|
|
702
|
+
})
|
|
703
|
+
|
|
777
704
|
// Set file and trigger change
|
|
778
705
|
const file = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
|
|
779
706
|
Object.defineProperty(galleryInput, 'files', {
|
|
@@ -32,8 +32,6 @@ interface ImagePickerProviderProps {
|
|
|
32
32
|
children: React.ReactNode
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const isAndroid = () => window?.minisParams?.platform === 'android'
|
|
36
|
-
|
|
37
35
|
export function ImagePickerProvider({children}: ImagePickerProviderProps) {
|
|
38
36
|
const galleryInputRef = useRef<HTMLInputElement>(null)
|
|
39
37
|
const frontCameraInputRef = useRef<HTMLInputElement>(null)
|
|
@@ -112,20 +110,15 @@ export function ImagePickerProvider({children}: ImagePickerProviderProps) {
|
|
|
112
110
|
input.addEventListener('cancel', handleCancel)
|
|
113
111
|
activeCancelHandlerRef.current = {input, handler: handleCancel}
|
|
114
112
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
.
|
|
123
|
-
|
|
124
|
-
input.click()
|
|
125
|
-
})
|
|
126
|
-
} else {
|
|
127
|
-
input.click()
|
|
128
|
-
}
|
|
113
|
+
requestPermission({permission: 'CAMERA'})
|
|
114
|
+
.then(() => {
|
|
115
|
+
// This will show both Camera and Gallery
|
|
116
|
+
input.click()
|
|
117
|
+
})
|
|
118
|
+
.catch(() => {
|
|
119
|
+
// Show only Gallery
|
|
120
|
+
input.click()
|
|
121
|
+
})
|
|
129
122
|
})
|
|
130
123
|
}, [rejectPendingPromise, cleanupCancelHandler, requestPermission])
|
|
131
124
|
|
|
@@ -162,26 +155,21 @@ export function ImagePickerProvider({children}: ImagePickerProviderProps) {
|
|
|
162
155
|
input.addEventListener('cancel', handleCancel)
|
|
163
156
|
activeCancelHandlerRef.current = {input, handler: handleCancel}
|
|
164
157
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
input.click()
|
|
171
|
-
} else {
|
|
172
|
-
reject(new Error('Camera permission not granted'))
|
|
173
|
-
resolveRef.current = null
|
|
174
|
-
rejectRef.current = null
|
|
175
|
-
}
|
|
176
|
-
})
|
|
177
|
-
.catch(() => {
|
|
158
|
+
requestPermission({permission: 'CAMERA'})
|
|
159
|
+
.then(({granted}) => {
|
|
160
|
+
if (granted) {
|
|
161
|
+
input.click()
|
|
162
|
+
} else {
|
|
178
163
|
reject(new Error('Camera permission not granted'))
|
|
179
164
|
resolveRef.current = null
|
|
180
165
|
rejectRef.current = null
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
.catch(() => {
|
|
169
|
+
reject(new Error('Camera permission not granted'))
|
|
170
|
+
resolveRef.current = null
|
|
171
|
+
rejectRef.current = null
|
|
172
|
+
})
|
|
185
173
|
})
|
|
186
174
|
},
|
|
187
175
|
[rejectPendingPromise, cleanupCancelHandler, requestPermission]
|