@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.
@@ -1,5 +1,5 @@
1
- var e = { exports: {} };
1
+ var r = {};
2
2
  export {
3
- e as __module
3
+ r as __exports
4
4
  };
5
5
  //# sourceMappingURL=index4.js.map
@@ -1,6 +1,5 @@
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
- var i = r();
1
+ var e = { exports: {} };
3
2
  export {
4
- i as l
3
+ e as __module
5
4
  };
6
5
  //# sourceMappingURL=index5.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index5.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
1
+ {"version":3,"file":"index5.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1,6 +1,6 @@
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";
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 s
4
+ i as l
5
5
  };
6
6
  //# sourceMappingURL=index6.js.map
@@ -1,5 +1,6 @@
1
- var r = {};
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
- r as __exports
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 n } from "react/jsx-runtime";
2
- import { memo as v, useState as i, useEffect as k, useMemo as u, useCallback as I } from "react";
3
- import { cn as m } from "../../lib/utils.js";
4
- import { getThumbhashDataURL as j, getResizedImageUrl as y } from "../../utils/image.js";
5
- const O = v(function(d) {
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: a,
10
- onLoad: s,
11
- className: b,
12
- style: f,
13
- aspectRatio: g = "auto",
14
- ...h
15
- } = d, [L, p] = i(!1), [o, c] = i(null);
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
- c(null);
19
+ l(null);
19
20
  return;
20
21
  }
21
22
  const e = URL.createObjectURL(t);
22
- return c(e), () => {
23
+ return l(e), () => {
23
24
  URL.revokeObjectURL(e);
24
25
  };
25
26
  }, [t]);
26
- const l = u(
27
- () => j(a ?? void 0),
28
- [a]
29
- ), U = I(
27
+ const n = m(
28
+ () => I(s ?? void 0),
29
+ [s]
30
+ ), R = y(
30
31
  (e) => {
31
- p(!0), s?.(e);
32
+ U(!0), c?.(e);
32
33
  },
33
- [s]
34
- ), R = u(() => o || y(r), [o, r]);
35
- return /* @__PURE__ */ n(
34
+ [c]
35
+ ), v = m(() => a || z(r), [a, r]);
36
+ return /* @__PURE__ */ u(
36
37
  "div",
37
38
  {
38
- className: m("relative w-full ", b),
39
+ className: d("relative w-full", f),
39
40
  style: {
40
- ...f,
41
- aspectRatio: g,
42
- backgroundImage: l ? `url(${l})` : void 0,
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__ */ n(
47
+ children: /* @__PURE__ */ u(
47
48
  "img",
48
49
  {
49
- className: m(
50
- "absolute inset-0 opacity-0 size-full object-cover",
51
- L && "opacity-100"
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: R,
54
- onLoad: U,
55
- ...h
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 ', className)}\n style={{\n ...style,\n aspectRatio,\n backgroundImage: thumbhashDataURL\n ? `url(${thumbhashDataURL})`\n : undefined,\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n }}\n >\n <img\n className={cn(\n 'absolute inset-0 opacity-0 size-full object-cover',\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","restProps","isLoaded","setIsLoaded","useState","blobUrl","setBlobUrl","useEffect","url","thumbhashDataURL","useMemo","getThumbhashDataURL","handleLoad","useCallback","event","imageSrc","getResizedImageUrl","jsx","cn"],"mappings":";;;;AAqBO,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,GAAGC;AAAA,EAAA,IACDR,GAEE,CAACS,GAAUC,CAAW,IAAIC,EAAS,EAAK,GACxC,CAACC,GAASC,CAAU,IAAIF,EAAwB,IAAI;AAG1D,EAAAG,EAAU,MAAM;AACd,QAAI,CAACZ,GAAM;AACT,MAAAW,EAAW,IAAI;AACf;AAAA,IAAA;AAGI,UAAAE,IAAM,IAAI,gBAAgBb,CAAI;AACpC,WAAAW,EAAWE,CAAG,GAGP,MAAM;AACX,UAAI,gBAAgBA,CAAG;AAAA,IACzB;AAAA,EAAA,GACC,CAACb,CAAI,CAAC;AAET,QAAMc,IAAmBC;AAAA,IACvB,MAAMC,EAAoBf,KAAa,MAAS;AAAA,IAChD,CAACA,CAAS;AAAA,EACZ,GAEMgB,IAAaC;AAAA,IACjB,CAACC,MAAyD;AACxD,MAAAX,EAAY,EAAI,GAChBN,IAASiB,CAAK;AAAA,IAChB;AAAA,IACA,CAACjB,CAAM;AAAA,EACT,GAGMkB,IAAWL,EAAQ,MACnBL,KACGW,EAAmBtB,CAAG,GAC5B,CAACW,GAASX,CAAG,CAAC;AAGf,SAAA,gBAAAuB;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,EAAG,oBAAoBpB,CAAS;AAAA,MAC3C,OAAO;AAAA,QACL,GAAGC;AAAA,QACH,aAAAC;AAAA,QACA,iBAAiBS,IACb,OAAOA,CAAgB,MACvB;AAAA,QACJ,gBAAgB;AAAA,QAChB,oBAAoB;AAAA,MACtB;AAAA,MAEA,UAAA,gBAAAQ;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAWC;AAAA,YACT;AAAA,YACAhB,KAAY;AAAA,UACd;AAAA,UACA,KAAKa;AAAA,UACL,QAAQH;AAAA,UACP,GAAGX;AAAA,QAAA;AAAA,MAAA;AAAA,IACN;AAAA,EACF;AAEJ,CAAC;"}
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 R, jsx as p } from "react/jsx-runtime";
2
- import { useRef as o, useCallback as s, useEffect as I, useMemo as b, createContext as A, useContext as L } from "react";
3
- import { useRequestPermissions as M } from "../hooks/util/useRequestPermissions.js";
4
- const v = A(null);
5
- function U() {
6
- const f = L(v);
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
- const E = () => window?.minisParams?.platform === "android";
14
- function F({ children: f }) {
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: l, handler: c } = a.current;
18
- l.removeEventListener("cancel", c), a.current = null;
16
+ const { input: u, handler: c } = a.current;
17
+ u.removeEventListener("cancel", c), a.current = null;
19
18
  }
20
- }, []), i = s(() => {
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
- }, []), m = s(
25
- (l) => {
26
- const c = l.target.files?.[0];
27
- c && n.current && (n.current(c), n.current = null, e.current = null, t()), l.target.value = "";
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
- [t]
30
- ), w = s(() => new Promise((l, c) => {
31
- i(), t(), n.current = l, e.current = c;
32
- const r = C.current;
33
- if (!r) {
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 u = () => {
38
- e.current && (e.current(new Error("User cancelled file selection")), n.current = null, e.current = null), t();
36
+ const l = () => {
37
+ e.current && (e.current(new Error("User cancelled file selection")), n.current = null, e.current = null), r();
39
38
  };
40
- r.addEventListener("cancel", u), a.current = { input: r, handler: u }, E() ? d({ permission: "CAMERA" }).then(() => {
41
- r.click();
39
+ t.addEventListener("cancel", l), a.current = { input: t, handler: l }, p({ permission: "CAMERA" }).then(() => {
40
+ t.click();
42
41
  }).catch(() => {
43
- r.click();
44
- }) : r.click();
45
- }), [i, t, d]), P = s(
46
- (l = "back") => new Promise((c, r) => {
47
- i(), t(), n.current = c, e.current = r;
48
- const u = l === "front" ? h.current : g.current;
49
- if (!u) {
50
- r(new Error("Camera input not found")), n.current = null, e.current = null;
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 k = () => {
54
- e.current && (e.current(new Error("User cancelled camera")), n.current = null, e.current = null), t();
52
+ const w = () => {
53
+ e.current && (e.current(new Error("User cancelled camera")), n.current = null, e.current = null), r();
55
54
  };
56
- u.addEventListener("cancel", k), a.current = { input: u, handler: k }, E() ? d({ permission: "CAMERA" }).then(({ granted: x }) => {
57
- x ? u.click() : (r(new Error("Camera permission not granted")), n.current = null, e.current = null);
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
- r(new Error("Camera permission not granted")), n.current = null, e.current = null;
60
- }) : u.click();
58
+ t(new Error("Camera permission not granted")), n.current = null, e.current = null;
59
+ });
61
60
  }),
62
- [i, t, d]
61
+ [o, r, p]
63
62
  );
64
- I(() => () => {
65
- i(), t();
66
- }, [i, t]);
67
- const y = b(
63
+ R(() => () => {
64
+ o(), r();
65
+ }, [o, r]);
66
+ const v = I(
68
67
  () => ({
69
68
  openCamera: P,
70
- openGallery: w
69
+ openGallery: E
71
70
  }),
72
- [P, w]
71
+ [P, E]
73
72
  );
74
- return /* @__PURE__ */ R(v.Provider, { value: y, children: [
73
+ return /* @__PURE__ */ x(k.Provider, { value: v, children: [
75
74
  f,
76
- /* @__PURE__ */ p(
75
+ /* @__PURE__ */ m(
77
76
  "input",
78
77
  {
79
78
  ref: C,
80
79
  type: "file",
81
80
  accept: "image/*",
82
- onChange: m,
81
+ onChange: d,
83
82
  style: { display: "none" },
84
83
  "aria-hidden": "true"
85
84
  }
86
85
  ),
87
- /* @__PURE__ */ p(
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: m,
93
+ onChange: d,
95
94
  style: { display: "none" },
96
95
  "aria-hidden": "true"
97
96
  }
98
97
  ),
99
- /* @__PURE__ */ p(
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: m,
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
- F as ImagePickerProvider,
115
- U as useImagePickerContext
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 { s as r } from "../../../../../../../../_virtual/index6.js";
1
+ import { s as r } from "../../../../../../../../_virtual/index7.js";
2
2
  function s() {
3
3
  return r.useSyncExternalStore(
4
4
  e,
@@ -1,4 +1,4 @@
1
- import { __module as q } from "../../../../../../../../_virtual/index4.js";
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/index5.js";
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,4 +1,4 @@
1
- import { __exports as i } from "../../../../../../_virtual/index7.js";
1
+ import { __exports as i } from "../../../../../../_virtual/index4.js";
2
2
  var c;
3
3
  function d() {
4
4
  if (c) return i;
@@ -1,48 +1,48 @@
1
1
  {
2
2
  "useBuyerAttributes": [
3
- "PROFILE"
3
+ "profile"
4
4
  ],
5
5
  "useCurrentUser": [
6
- "USER_SETTINGS_READ"
6
+ "user_settings:read"
7
7
  ],
8
8
  "useFollowedShops": [
9
- "SHOPS_FOLLOWS_READ"
9
+ "shops:follows:read"
10
10
  ],
11
11
  "useFollowedShopsActions": [
12
- "SHOPS_FOLLOWS_WRITE"
12
+ "shops:follows:write"
13
13
  ],
14
14
  "useGenerateUserToken": [
15
- "OPENID"
15
+ "openid"
16
16
  ],
17
17
  "useOrders": [
18
- "ORDERS"
18
+ "orders"
19
19
  ],
20
20
  "useProductList": [
21
- "PRODUCT_LIST_READ"
21
+ "product_list:read"
22
22
  ],
23
23
  "useProductListActions": [
24
- "PRODUCT_LIST_ITEM_WRITE",
25
- "PRODUCT_LIST_WRITE"
24
+ "product_list:write",
25
+ "product_list_item:write"
26
26
  ],
27
27
  "useProductLists": [
28
- "PRODUCT_LIST_READ"
28
+ "product_list:read"
29
29
  ],
30
30
  "useRecentProducts": [
31
- "PRODUCTS_RECENT_READ"
31
+ "products:recent:read"
32
32
  ],
33
33
  "useRecentShops": [
34
- "SHOPS_RECENT_READ"
34
+ "shops:recent:read"
35
35
  ],
36
36
  "useRecommendedProducts": [
37
- "PRODUCTS_RECOMMENDATIONS_READ"
37
+ "products:recommendations:read"
38
38
  ],
39
39
  "useRecommendedShops": [
40
- "SHOPS_RECOMMENDATIONS_READ"
40
+ "shops:recommendations:read"
41
41
  ],
42
42
  "useSavedProducts": [
43
- "FAVORITES"
43
+ "product_list:read"
44
44
  ],
45
45
  "useSavedProductsActions": [
46
- "FAVORITES_WRITE"
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.1.8",
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.5.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 ', className)}
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
- 'absolute inset-0 opacity-0 size-full object-cover',
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('triggers gallery input click directly on non-Android platforms', async () => {
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
- expect(clickSpy).toHaveBeenCalledTimes(1)
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('requests CAMERA permission on Android before showing picker', async () => {
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('triggers back camera input click directly on non-Android platforms', async () => {
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
- expect(clickSpy).toHaveBeenCalledTimes(1)
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
- expect(clickSpy).toHaveBeenCalledTimes(1)
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('requests CAMERA permission on Android and opens camera if granted', async () => {
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 on Android if permission request fails', async () => {
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
- if (isAndroid()) {
116
- // Android requires explicit camera permission for camera picker
117
- requestPermission({permission: 'CAMERA'})
118
- .then(() => {
119
- // This will show both Camera and Gallery
120
- input.click()
121
- })
122
- .catch(() => {
123
- // Show only Gallery
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
- if (isAndroid()) {
166
- // Android requires explicit camera permission
167
- requestPermission({permission: 'CAMERA'})
168
- .then(({granted}) => {
169
- if (granted) {
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
- } else {
183
- input.click()
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]