@shopify/shop-minis-react 0.0.9 → 0.0.10

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,6 @@
1
- var r = {};
1
+ import { __require as r } from "../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=index2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index2.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
1
+ {"version":3,"file":"index2.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
@@ -1,6 +1,5 @@
1
- import { __require as r } from "../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();
1
+ var r = {};
3
2
  export {
4
- i as s
3
+ r as __exports
5
4
  };
6
5
  //# sourceMappingURL=index3.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index3.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
1
+ {"version":3,"file":"index3.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1,14 +1,15 @@
1
- import { jsx as t, jsxs as c } from "react/jsx-runtime";
1
+ import { jsx as r, jsxs as c } from "react/jsx-runtime";
2
2
  import { useState as m, useEffect as l } from "react";
3
- function v({ children: s }) {
4
- const [a, r] = m(!1);
3
+ import { ImagePickerProvider as u } from "../providers/ImagePickerProvider.js";
4
+ function p({ children: s }) {
5
+ const [a, t] = m(!1);
5
6
  return l(() => {
6
- const n = () => window.minisSDK ? (r(!0), !0) : !1;
7
+ const n = () => window.minisSDK ? (t(!0), !0) : !1;
7
8
  if (n())
8
9
  return;
9
- const e = (d) => {
10
- const { type: o } = JSON.parse(d.data);
11
- o === "MINIS_SDK_READY" && r(!0);
10
+ const e = (o) => {
11
+ const { type: d } = JSON.parse(o.data);
12
+ d === "MINIS_SDK_READY" && t(!0);
12
13
  };
13
14
  window.addEventListener("message", e), document.addEventListener("message", e);
14
15
  const i = setInterval(() => {
@@ -17,12 +18,12 @@ function v({ children: s }) {
17
18
  return () => {
18
19
  window.removeEventListener("message", e), document.removeEventListener("message", e);
19
20
  };
20
- }, []), a ? s : /* @__PURE__ */ t("div", { className: "h-screen bg-gray-50 flex items-center justify-center", children: /* @__PURE__ */ c("div", { className: "text-center", children: [
21
- /* @__PURE__ */ t("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto mb-4" }),
22
- /* @__PURE__ */ t("p", { className: "text-gray-600", children: "Loading..." })
21
+ }, []), a ? /* @__PURE__ */ r(u, { children: s }) : /* @__PURE__ */ r("div", { className: "h-screen bg-gray-50 flex items-center justify-center", children: /* @__PURE__ */ c("div", { className: "text-center", children: [
22
+ /* @__PURE__ */ r("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto mb-4" }),
23
+ /* @__PURE__ */ r("p", { className: "text-gray-600", children: "Loading..." })
23
24
  ] }) });
24
25
  }
25
26
  export {
26
- v as MinisContainer
27
+ p as MinisContainer
27
28
  };
28
29
  //# sourceMappingURL=MinisContainer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MinisContainer.js","sources":["../../src/components/MinisContainer.tsx"],"sourcesContent":["import React, {useEffect, useState} from 'react'\n\nexport function MinisContainer({children}: {children: React.ReactNode}) {\n const [isSDKReady, setIsSDKReady] = useState(false)\n\n useEffect(() => {\n // Function to check if SDK is ready\n const checkSDKReady = () => {\n if (window.minisSDK) {\n setIsSDKReady(true)\n return true\n }\n return false\n }\n\n // Check immediately\n if (checkSDKReady()) {\n return\n }\n\n // If not ready, set up a listener for the MINIS_SDK_READY event\n const handleSDKReady = (event: any) => {\n const {type} = JSON.parse(event.data)\n\n if (type === 'MINIS_SDK_READY') {\n setIsSDKReady(true)\n }\n }\n\n // Listen for the MINIS_SDK_READY event\n window.addEventListener('message', handleSDKReady)\n document.addEventListener('message', handleSDKReady)\n\n // Also poll for SDK availability as a fallback\n const pollInterval = setInterval(() => {\n if (checkSDKReady()) {\n clearInterval(pollInterval)\n }\n }, 100)\n\n // Cleanup\n return () => {\n // clearInterval(pollInterval);\n window.removeEventListener('message', handleSDKReady)\n document.removeEventListener('message', handleSDKReady)\n }\n }, [])\n\n // Don't render anything until SDK is ready\n if (!isSDKReady) {\n return (\n <div className=\"h-screen bg-gray-50 flex items-center justify-center\">\n <div className=\"text-center\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto mb-4\" />\n <p className=\"text-gray-600\">Loading...</p>\n </div>\n </div>\n )\n }\n\n return children\n}\n"],"names":["MinisContainer","children","isSDKReady","setIsSDKReady","useState","useEffect","checkSDKReady","handleSDKReady","event","type","pollInterval","jsxs","jsx"],"mappings":";;AAEgB,SAAAA,EAAe,EAAC,UAAAC,KAAwC;AACtE,QAAM,CAACC,GAAYC,CAAa,IAAIC,EAAS,EAAK;AA8ClD,SA5CAC,EAAU,MAAM;AAEd,UAAMC,IAAgB,MAChB,OAAO,YACTH,EAAc,EAAI,GACX,MAEF;AAIT,QAAIG;AACF;AAII,UAAAC,IAAiB,CAACC,MAAe;AACrC,YAAM,EAAC,MAAAC,EAAI,IAAI,KAAK,MAAMD,EAAM,IAAI;AAEpC,MAAIC,MAAS,qBACXN,EAAc,EAAI;AAAA,IAEtB;AAGO,WAAA,iBAAiB,WAAWI,CAAc,GACxC,SAAA,iBAAiB,WAAWA,CAAc;AAG7C,UAAAG,IAAe,YAAY,MAAM;AACrC,MAAIJ,OACF,cAAcI,CAAY;AAAA,OAE3B,GAAG;AAGN,WAAO,MAAM;AAEJ,aAAA,oBAAoB,WAAWH,CAAc,GAC3C,SAAA,oBAAoB,WAAWA,CAAc;AAAA,IACxD;AAAA,EACF,GAAG,EAAE,GAGAL,IAWED,sBATF,OAAI,EAAA,WAAU,wDACb,UAAC,gBAAAU,EAAA,OAAA,EAAI,WAAU,eACb,UAAA;AAAA,IAAC,gBAAAC,EAAA,OAAA,EAAI,WAAU,4EAA4E,CAAA;AAAA,IAC1F,gBAAAA,EAAA,KAAA,EAAE,WAAU,iBAAgB,UAAU,aAAA,CAAA;AAAA,EAAA,EAAA,CACzC,EACF,CAAA;AAKN;"}
1
+ {"version":3,"file":"MinisContainer.js","sources":["../../src/components/MinisContainer.tsx"],"sourcesContent":["import React, {useEffect, useState} from 'react'\n\nimport {ImagePickerProvider} from '../providers/ImagePickerProvider'\n\nexport function MinisContainer({children}: {children: React.ReactNode}) {\n const [isSDKReady, setIsSDKReady] = useState(false)\n\n useEffect(() => {\n // Function to check if SDK is ready\n const checkSDKReady = () => {\n if (window.minisSDK) {\n setIsSDKReady(true)\n return true\n }\n return false\n }\n\n // Check immediately\n if (checkSDKReady()) {\n return\n }\n\n // If not ready, set up a listener for the MINIS_SDK_READY event\n const handleSDKReady = (event: any) => {\n const {type} = JSON.parse(event.data)\n\n if (type === 'MINIS_SDK_READY') {\n setIsSDKReady(true)\n }\n }\n\n // Listen for the MINIS_SDK_READY event\n window.addEventListener('message', handleSDKReady)\n document.addEventListener('message', handleSDKReady)\n\n // Also poll for SDK availability as a fallback\n const pollInterval = setInterval(() => {\n if (checkSDKReady()) {\n clearInterval(pollInterval)\n }\n }, 100)\n\n // Cleanup\n return () => {\n // clearInterval(pollInterval);\n window.removeEventListener('message', handleSDKReady)\n document.removeEventListener('message', handleSDKReady)\n }\n }, [])\n\n // Don't render anything until SDK is ready\n if (!isSDKReady) {\n return (\n <div className=\"h-screen bg-gray-50 flex items-center justify-center\">\n <div className=\"text-center\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto mb-4\" />\n <p className=\"text-gray-600\">Loading...</p>\n </div>\n </div>\n )\n }\n\n return <ImagePickerProvider>{children}</ImagePickerProvider>\n}\n"],"names":["MinisContainer","children","isSDKReady","setIsSDKReady","useState","useEffect","checkSDKReady","handleSDKReady","event","type","pollInterval","jsx","ImagePickerProvider","jsxs"],"mappings":";;;AAIgB,SAAAA,EAAe,EAAC,UAAAC,KAAwC;AACtE,QAAM,CAACC,GAAYC,CAAa,IAAIC,EAAS,EAAK;AA8ClD,SA5CAC,EAAU,MAAM;AAEd,UAAMC,IAAgB,MAChB,OAAO,YACTH,EAAc,EAAI,GACX,MAEF;AAIT,QAAIG;AACF;AAII,UAAAC,IAAiB,CAACC,MAAe;AACrC,YAAM,EAAC,MAAAC,EAAI,IAAI,KAAK,MAAMD,EAAM,IAAI;AAEpC,MAAIC,MAAS,qBACXN,EAAc,EAAI;AAAA,IAEtB;AAGO,WAAA,iBAAiB,WAAWI,CAAc,GACxC,SAAA,iBAAiB,WAAWA,CAAc;AAG7C,UAAAG,IAAe,YAAY,MAAM;AACrC,MAAIJ,OACF,cAAcI,CAAY;AAAA,OAE3B,GAAG;AAGN,WAAO,MAAM;AAEJ,aAAA,oBAAoB,WAAWH,CAAc,GAC3C,SAAA,oBAAoB,WAAWA,CAAc;AAAA,IACxD;AAAA,EACF,GAAG,EAAE,GAGAL,IAWE,gBAAAS,EAACC,KAAqB,UAAAX,GAAS,sBATjC,OAAI,EAAA,WAAU,wDACb,UAAC,gBAAAY,EAAA,OAAA,EAAI,WAAU,eACb,UAAA;AAAA,IAAC,gBAAAF,EAAA,OAAA,EAAI,WAAU,4EAA4E,CAAA;AAAA,IAC1F,gBAAAA,EAAA,KAAA,EAAE,WAAU,iBAAgB,UAAU,aAAA,CAAA;AAAA,EAAA,EAAA,CACzC,EACF,CAAA;AAKN;"}
@@ -0,0 +1,12 @@
1
+ import { useImagePickerContext as o } from "../../providers/ImagePickerProvider.js";
2
+ function t() {
3
+ const { openCamera: e, openGallery: r } = o();
4
+ return {
5
+ openCamera: e,
6
+ openGallery: r
7
+ };
8
+ }
9
+ export {
10
+ t as useImagePicker
11
+ };
12
+ //# sourceMappingURL=useImagePicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useImagePicker.js","sources":["../../../src/hooks/util/useImagePicker.tsx"],"sourcesContent":["import {\n CameraFacing,\n useImagePickerContext,\n} from '../../providers/ImagePickerProvider'\n\ninterface UseImagePickerReturn {\n openCamera: (cameraFacing?: CameraFacing) => Promise<File>\n openGallery: () => Promise<File>\n}\n\nexport function useImagePicker(): UseImagePickerReturn {\n const {openCamera, openGallery} = useImagePickerContext()\n\n return {\n openCamera,\n openGallery,\n }\n}\n"],"names":["useImagePicker","openCamera","openGallery","useImagePickerContext"],"mappings":";AAUO,SAASA,IAAuC;AACrD,QAAM,EAAC,YAAAC,GAAY,aAAAC,EAAW,IAAIC,EAAsB;AAEjD,SAAA;AAAA,IACL,YAAAF;AAAA,IACA,aAAAC;AAAA,EACF;AACF;"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { MinisContainer as o } from "./components/MinisContainer.js";
2
2
  import { ProductCard as a, ProductCardBadge as i, ProductCardCurrentPrice as l, ProductCardFavoriteButton as n, ProductCardImage as c, ProductCardImageContainer as u, ProductCardInfo as d, ProductCardOriginalPrice as p, ProductCardPrice as s, ProductCardRoot as m, ProductCardTitle as f } from "./components/commerce/product-card.js";
3
- import { ProductLink as g, ProductLinkActions as C, ProductLinkCurrentPrice as P, ProductLinkDiscountPrice as D, ProductLinkImage as S, ProductLinkInfo as A, ProductLinkOriginalPrice as L, ProductLinkPrice as T, ProductLinkRating as k, ProductLinkRoot as h, ProductLinkTitle as w } from "./components/commerce/product-link.js";
3
+ import { ProductLink as g, ProductLinkActions as C, ProductLinkCurrentPrice as P, ProductLinkDiscountPrice as D, ProductLinkImage as S, ProductLinkInfo as A, ProductLinkOriginalPrice as k, ProductLinkPrice as L, ProductLinkRating as T, ProductLinkRoot as h, ProductLinkTitle as w } from "./components/commerce/product-link.js";
4
4
  import { Accordion as b, AccordionContent as v, AccordionItem as R, AccordionTrigger as F } from "./components/atoms/accordion.js";
5
5
  import { Alert as y, AlertDescription as E, AlertTitle as H } from "./components/atoms/alert.js";
6
6
  import { AlertDialog as G, AlertDialogAction as M, AlertDialogCancel as N, AlertDialogContent as U, AlertDialogDescription as z, AlertDialogFooter as V, AlertDialogHeader as j, AlertDialogOverlay as q, AlertDialogPortal as J, AlertDialogTitle as K, AlertDialogTrigger as Q } from "./components/atoms/alert-dialog.js";
@@ -10,7 +10,7 @@ import { Button as or, buttonVariants as tr } from "./components/atoms/button.js
10
10
  import { Card as ir, CardAction as lr, CardContent as nr, CardDescription as cr, CardFooter as ur, CardHeader as dr, CardTitle as pr } from "./components/atoms/card.js";
11
11
  import { Carousel as mr, CarouselContent as fr, CarouselItem as xr, CarouselNext as gr, CarouselPrevious as Cr } from "./components/atoms/carousel.js";
12
12
  import { Checkbox as Dr } from "./components/atoms/checkbox.js";
13
- import { Dialog as Ar, DialogClose as Lr, DialogContent as Tr, DialogDescription as kr, DialogFooter as hr, DialogHeader as wr, DialogOverlay as Ir, DialogPortal as br, DialogTitle as vr, DialogTrigger as Rr } from "./components/atoms/dialog.js";
13
+ import { Dialog as Ar, DialogClose as kr, DialogContent as Lr, DialogDescription as Tr, DialogFooter as hr, DialogHeader as wr, DialogOverlay as Ir, DialogPortal as br, DialogTitle as vr, DialogTrigger as Rr } from "./components/atoms/dialog.js";
14
14
  import { Drawer as Br, DrawerClose as yr, DrawerContent as Er, DrawerDescription as Hr, DrawerFooter as Or, DrawerHeader as Gr, DrawerOverlay as Mr, DrawerPortal as Nr, DrawerTitle as Ur, DrawerTrigger as zr } from "./components/atoms/drawer.js";
15
15
  import { Input as jr } from "./components/atoms/input.js";
16
16
  import { Label as Jr } from "./components/atoms/label.js";
@@ -20,7 +20,7 @@ import { ResizableHandle as _r, ResizablePanel as $r, ResizablePanelGroup as re
20
20
  import { ScrollArea as oe, ScrollBar as te } from "./components/atoms/scroll-area.js";
21
21
  import { Select as ie, SelectContent as le, SelectGroup as ne, SelectItem as ce, SelectLabel as ue, SelectScrollDownButton as de, SelectScrollUpButton as pe, SelectSeparator as se, SelectTrigger as me, SelectValue as fe } from "./components/atoms/select.js";
22
22
  import { Separator as ge } from "./components/atoms/separator.js";
23
- import { Sheet as Pe, SheetClose as De, SheetContent as Se, SheetDescription as Ae, SheetFooter as Le, SheetHeader as Te, SheetTitle as ke, SheetTrigger as he } from "./components/atoms/sheet.js";
23
+ import { Sheet as Pe, SheetClose as De, SheetContent as Se, SheetDescription as Ae, SheetFooter as ke, SheetHeader as Le, SheetTitle as Te, SheetTrigger as he } from "./components/atoms/sheet.js";
24
24
  import { Toaster as Ie } from "./components/atoms/sonner.js";
25
25
  import { useSavedProductsActions as ve } from "./hooks/user/useSavedProductsActions.js";
26
26
  import { useFollowedShopsActions as Fe } from "./hooks/user/useFollowedShopsActions.js";
@@ -43,9 +43,10 @@ import { useRecommendedShops as uo } from "./hooks/shop/useRecommendedShops.js";
43
43
  import { useErrorToast as so } from "./hooks/util/useErrorToast.js";
44
44
  import { useErrorScreen as fo } from "./hooks/util/useErrorScreen.js";
45
45
  import { useShare as go } from "./hooks/util/useShare.js";
46
- import { MiniEntityNotFoundError as Po, MiniError as Do, MiniNetworkError as So, formatError as Ao } from "./utils/errors.js";
47
- import { parseUrl as To } from "./utils/parseUrl.js";
48
- import { Consent as ho, ConsentStatus as wo, CurrencyCode as Io, Gender as bo } from "./types/minisSDK.generated.d.js";
46
+ import { useImagePicker as Po } from "./hooks/util/useImagePicker.js";
47
+ import { MiniEntityNotFoundError as So, MiniError as Ao, MiniNetworkError as ko, formatError as Lo } from "./utils/errors.js";
48
+ import { parseUrl as ho } from "./utils/parseUrl.js";
49
+ import { Consent as Io, ConsentStatus as bo, CurrencyCode as vo, Gender as Ro } from "./types/minisSDK.generated.d.js";
49
50
  export {
50
51
  b as Accordion,
51
52
  v as AccordionContent,
@@ -83,13 +84,13 @@ export {
83
84
  gr as CarouselNext,
84
85
  Cr as CarouselPrevious,
85
86
  Dr as Checkbox,
86
- ho as Consent,
87
- wo as ConsentStatus,
88
- Io as CurrencyCode,
87
+ Io as Consent,
88
+ bo as ConsentStatus,
89
+ vo as CurrencyCode,
89
90
  Ar as Dialog,
90
- Lr as DialogClose,
91
- Tr as DialogContent,
92
- kr as DialogDescription,
91
+ kr as DialogClose,
92
+ Lr as DialogContent,
93
+ Tr as DialogDescription,
93
94
  hr as DialogFooter,
94
95
  wr as DialogHeader,
95
96
  Ir as DialogOverlay,
@@ -106,12 +107,12 @@ export {
106
107
  Nr as DrawerPortal,
107
108
  Ur as DrawerTitle,
108
109
  zr as DrawerTrigger,
109
- bo as Gender,
110
+ Ro as Gender,
110
111
  jr as Input,
111
112
  Jr as Label,
112
- Po as MiniEntityNotFoundError,
113
- Do as MiniError,
114
- So as MiniNetworkError,
113
+ So as MiniEntityNotFoundError,
114
+ Ao as MiniError,
115
+ ko as MiniNetworkError,
115
116
  o as MinisContainer,
116
117
  a as ProductCard,
117
118
  i as ProductCardBadge,
@@ -130,9 +131,9 @@ export {
130
131
  D as ProductLinkDiscountPrice,
131
132
  S as ProductLinkImage,
132
133
  A as ProductLinkInfo,
133
- L as ProductLinkOriginalPrice,
134
- T as ProductLinkPrice,
135
- k as ProductLinkRating,
134
+ k as ProductLinkOriginalPrice,
135
+ L as ProductLinkPrice,
136
+ T as ProductLinkRating,
136
137
  h as ProductLinkRoot,
137
138
  w as ProductLinkTitle,
138
139
  Qr as Progress,
@@ -158,15 +159,15 @@ export {
158
159
  De as SheetClose,
159
160
  Se as SheetContent,
160
161
  Ae as SheetDescription,
161
- Le as SheetFooter,
162
- Te as SheetHeader,
163
- ke as SheetTitle,
162
+ ke as SheetFooter,
163
+ Le as SheetHeader,
164
+ Te as SheetTitle,
164
165
  he as SheetTrigger,
165
166
  Ie as Toaster,
166
167
  rr as badgeVariants,
167
168
  tr as buttonVariants,
168
- Ao as formatError,
169
- To as parseUrl,
169
+ Lo as formatError,
170
+ ho as parseUrl,
170
171
  Xe as useAsyncStorage,
171
172
  Ge as useBuyerAttributes,
172
173
  to as useCloseMini,
@@ -175,6 +176,7 @@ export {
175
176
  fo as useErrorScreen,
176
177
  so as useErrorToast,
177
178
  Fe as useFollowedShopsActions,
179
+ Po as useImagePicker,
178
180
  $e as useImageUpload,
179
181
  He as useOrders,
180
182
  Qe as usePopularProducts,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,4 +1,4 @@
1
- import { s as r } from "../../../../../../../_virtual/index3.js";
1
+ import { s as r } from "../../../../../../../_virtual/index2.js";
2
2
  function s() {
3
3
  return r.useSyncExternalStore(
4
4
  e,
@@ -1,4 +1,4 @@
1
- import { __exports as i } from "../../../../../_virtual/index2.js";
1
+ import { __exports as i } from "../../../../../_virtual/index3.js";
2
2
  var c;
3
3
  function d() {
4
4
  if (c) return i;
@@ -0,0 +1,107 @@
1
+ import { jsxs as w, jsx as p } from "react/jsx-runtime";
2
+ import { useRef as o, useCallback as s, useEffect as x, useMemo as E, createContext as I, useContext as R } from "react";
3
+ const P = I(null);
4
+ function j() {
5
+ const f = R(P);
6
+ if (!f)
7
+ throw new Error(
8
+ "useImagePickerContext must be used within an ImagePickerProvider"
9
+ );
10
+ return f;
11
+ }
12
+ function G({ children: f }) {
13
+ const m = o(null), C = o(null), g = o(null), n = o(null), e = o(null), i = o(null), r = s(() => {
14
+ if (i.current) {
15
+ const { input: c, handler: t } = i.current;
16
+ c.removeEventListener("cancel", t), i.current = null;
17
+ }
18
+ }, []), a = s(() => {
19
+ e.current && (e.current(
20
+ new Error("New file picker opened before previous completed")
21
+ ), n.current = null, e.current = null);
22
+ }, []), d = s(
23
+ (c) => {
24
+ const t = c.target.files?.[0];
25
+ t && n.current && (n.current(t), n.current = null, e.current = null, r()), c.target.value = "";
26
+ },
27
+ [r]
28
+ ), h = s(() => new Promise((c, t) => {
29
+ a(), r(), n.current = c, e.current = t;
30
+ const u = m.current;
31
+ if (!u) {
32
+ t(new Error("Gallery input not found")), n.current = null, e.current = null;
33
+ return;
34
+ }
35
+ const l = () => {
36
+ e.current && (e.current(new Error("User cancelled file selection")), n.current = null, e.current = null), r();
37
+ };
38
+ u.addEventListener("cancel", l), i.current = { input: u, handler: l }, u.click();
39
+ }), [a, r]), v = s(
40
+ (c = "back") => new Promise((t, u) => {
41
+ a(), r(), n.current = t, e.current = u;
42
+ const l = c === "front" ? C.current : g.current;
43
+ if (!l) {
44
+ u(new Error("Camera input not found")), n.current = null, e.current = null;
45
+ return;
46
+ }
47
+ const y = () => {
48
+ e.current && (e.current(new Error("User cancelled camera")), n.current = null, e.current = null), r();
49
+ };
50
+ l.addEventListener("cancel", y), i.current = { input: l, handler: y }, l.click();
51
+ }),
52
+ [a, r]
53
+ );
54
+ x(() => () => {
55
+ a(), r();
56
+ }, [a, r]);
57
+ const k = E(
58
+ () => ({
59
+ openCamera: v,
60
+ openGallery: h
61
+ }),
62
+ [v, h]
63
+ );
64
+ return /* @__PURE__ */ w(P.Provider, { value: k, children: [
65
+ f,
66
+ /* @__PURE__ */ p(
67
+ "input",
68
+ {
69
+ ref: m,
70
+ type: "file",
71
+ accept: "image/*",
72
+ onChange: d,
73
+ style: { display: "none" },
74
+ "aria-hidden": "true"
75
+ }
76
+ ),
77
+ /* @__PURE__ */ p(
78
+ "input",
79
+ {
80
+ ref: C,
81
+ type: "file",
82
+ accept: "image/*",
83
+ capture: "user",
84
+ onChange: d,
85
+ style: { display: "none" },
86
+ "aria-hidden": "true"
87
+ }
88
+ ),
89
+ /* @__PURE__ */ p(
90
+ "input",
91
+ {
92
+ ref: g,
93
+ type: "file",
94
+ accept: "image/*",
95
+ capture: "environment",
96
+ onChange: d,
97
+ style: { display: "none" },
98
+ "aria-hidden": "true"
99
+ }
100
+ )
101
+ ] });
102
+ }
103
+ export {
104
+ G as ImagePickerProvider,
105
+ j as useImagePickerContext
106
+ };
107
+ //# sourceMappingURL=ImagePickerProvider.js.map
@@ -0,0 +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\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 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 input.click()\n })\n }, [rejectPendingPromise, cleanupCancelHandler])\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 input.click()\n })\n },\n [rejectPendingPromise, cleanupCancelHandler]\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","cleanupCancelHandler","useCallback","input","handler","rejectPendingPromise","handleFileChange","event","file","openGallery","resolve","reject","handleCancel","openCamera","cameraFacing","useEffect","contextValue","useMemo","jsxs","jsx"],"mappings":"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"}
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.0.9",
4
+ "version": "0.0.10",
5
5
  "sideEffects": false,
6
6
  "type": "module",
7
7
  "engines": {
@@ -1,5 +1,7 @@
1
1
  import React, {useEffect, useState} from 'react'
2
2
 
3
+ import {ImagePickerProvider} from '../providers/ImagePickerProvider'
4
+
3
5
  export function MinisContainer({children}: {children: React.ReactNode}) {
4
6
  const [isSDKReady, setIsSDKReady] = useState(false)
5
7
 
@@ -58,5 +60,5 @@ export function MinisContainer({children}: {children: React.ReactNode}) {
58
60
  )
59
61
  }
60
62
 
61
- return children
63
+ return <ImagePickerProvider>{children}</ImagePickerProvider>
62
64
  }
@@ -32,3 +32,4 @@ export * from './shop/useRecommendedShops'
32
32
  export * from './util/useErrorToast'
33
33
  export * from './util/useErrorScreen'
34
34
  export * from './util/useShare'
35
+ export * from './util/useImagePicker'
@@ -0,0 +1,41 @@
1
+ export const useImagePickerDoc = {
2
+ name: 'useImagePicker',
3
+ category: 'Utility',
4
+ description:
5
+ 'Hook for selecting images from camera or gallery in web-based mini apps',
6
+ params: [
7
+ {
8
+ name: 'options',
9
+ type: 'UseImagePickerOptions',
10
+ description: 'Configuration options for the image picker',
11
+ optional: true,
12
+ properties: [
13
+ {
14
+ name: 'cameraFacing',
15
+ type: "'user' | 'environment'",
16
+ description: 'Which camera to use when opening camera',
17
+ optional: true,
18
+ default: "'environment'",
19
+ },
20
+ ],
21
+ },
22
+ ],
23
+ returns: [
24
+ {
25
+ name: 'openCamera',
26
+ type: '() => Promise<File>',
27
+ description: 'Function to open the camera for capturing an image',
28
+ },
29
+ {
30
+ name: 'openGallery',
31
+ type: '() => Promise<File>',
32
+ description: 'Function to open the gallery for selecting an image',
33
+ },
34
+ {
35
+ name: 'ImagePickerInputs',
36
+ type: 'React.FC',
37
+ description:
38
+ 'Component that renders hidden file inputs. Must be rendered in your component for the picker to work',
39
+ },
40
+ ],
41
+ }
@@ -0,0 +1,85 @@
1
+ import React, {useState} from 'react'
2
+
3
+ import {Camera, Image} from 'lucide-react'
4
+
5
+ import {Alert, AlertDescription, AlertTitle} from '../../components/ui/alert'
6
+ import {Button} from '../../components/ui/button'
7
+ import {
8
+ Card,
9
+ CardContent,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from '../../components/ui/card'
13
+
14
+ import {useImagePicker} from './useImagePicker'
15
+
16
+ export const UseImagePickerExample = () => {
17
+ const [selectedImage, setSelectedImage] = useState<string | null>(null)
18
+ const [error, setError] = useState<string | null>(null)
19
+ const {openCamera, openGallery, ImagePickerInputs} = useImagePicker({
20
+ cameraFacing: 'user',
21
+ })
22
+
23
+ const handleCameraCapture = async () => {
24
+ try {
25
+ setError(null)
26
+ const file = await openCamera()
27
+ const url = URL.createObjectURL(file)
28
+ setSelectedImage(url)
29
+ } catch (err) {
30
+ setError(err instanceof Error ? err.message : 'Failed to capture image')
31
+ }
32
+ }
33
+
34
+ const handleGallerySelect = async () => {
35
+ try {
36
+ setError(null)
37
+ const file = await openGallery()
38
+ const url = URL.createObjectURL(file)
39
+ setSelectedImage(url)
40
+ } catch (err) {
41
+ setError(err instanceof Error ? err.message : 'Failed to select image')
42
+ }
43
+ }
44
+
45
+ return (
46
+ <Card>
47
+ <CardHeader>
48
+ <CardTitle>Image Picker Example</CardTitle>
49
+ </CardHeader>
50
+ <CardContent className="space-y-4">
51
+ <ImagePickerInputs />
52
+
53
+ <div className="flex gap-2">
54
+ <Button onClick={handleCameraCapture} variant="primary">
55
+ <Camera className="mr-2 h-4 w-4" />
56
+ Open Camera
57
+ </Button>
58
+
59
+ <Button onClick={handleGallerySelect} variant="secondary">
60
+ <Image className="mr-2 h-4 w-4" />
61
+ Open Gallery
62
+ </Button>
63
+ </div>
64
+
65
+ {error && (
66
+ <Alert variant="destructive">
67
+ <AlertTitle>Error</AlertTitle>
68
+ <AlertDescription>{error}</AlertDescription>
69
+ </Alert>
70
+ )}
71
+
72
+ {selectedImage && (
73
+ <div className="space-y-2">
74
+ <h3 className="text-sm font-medium">Selected Image:</h3>
75
+ <img
76
+ src={selectedImage}
77
+ alt="Selected"
78
+ className="w-full rounded-md border"
79
+ />
80
+ </div>
81
+ )}
82
+ </CardContent>
83
+ </Card>
84
+ )
85
+ }
@@ -0,0 +1,18 @@
1
+ import {
2
+ CameraFacing,
3
+ useImagePickerContext,
4
+ } from '../../providers/ImagePickerProvider'
5
+
6
+ interface UseImagePickerReturn {
7
+ openCamera: (cameraFacing?: CameraFacing) => Promise<File>
8
+ openGallery: () => Promise<File>
9
+ }
10
+
11
+ export function useImagePicker(): UseImagePickerReturn {
12
+ const {openCamera, openGallery} = useImagePickerContext()
13
+
14
+ return {
15
+ openCamera,
16
+ openGallery,
17
+ }
18
+ }
@@ -0,0 +1,198 @@
1
+ import React, {
2
+ createContext,
3
+ useContext,
4
+ useRef,
5
+ useCallback,
6
+ useEffect,
7
+ useMemo,
8
+ } from 'react'
9
+
10
+ export type CameraFacing = 'front' | 'back'
11
+
12
+ interface ImagePickerContextValue {
13
+ openCamera: (cameraFacing?: CameraFacing) => Promise<File>
14
+ openGallery: () => Promise<File>
15
+ }
16
+
17
+ const ImagePickerContext = createContext<ImagePickerContextValue | null>(null)
18
+
19
+ export function useImagePickerContext() {
20
+ const context = useContext(ImagePickerContext)
21
+ if (!context) {
22
+ throw new Error(
23
+ 'useImagePickerContext must be used within an ImagePickerProvider'
24
+ )
25
+ }
26
+ return context
27
+ }
28
+
29
+ interface ImagePickerProviderProps {
30
+ children: React.ReactNode
31
+ }
32
+
33
+ export function ImagePickerProvider({children}: ImagePickerProviderProps) {
34
+ const galleryInputRef = useRef<HTMLInputElement>(null)
35
+ const frontCameraInputRef = useRef<HTMLInputElement>(null)
36
+ const backCameraInputRef = useRef<HTMLInputElement>(null)
37
+ const resolveRef = useRef<((file: File) => void) | null>(null)
38
+ const rejectRef = useRef<((reason: Error) => void) | null>(null)
39
+ const activeCancelHandlerRef = useRef<{
40
+ input: HTMLInputElement
41
+ handler: () => void
42
+ } | null>(null)
43
+
44
+ const cleanupCancelHandler = useCallback(() => {
45
+ if (activeCancelHandlerRef.current) {
46
+ const {input, handler} = activeCancelHandlerRef.current
47
+ input.removeEventListener('cancel', handler)
48
+ activeCancelHandlerRef.current = null
49
+ }
50
+ }, [])
51
+
52
+ const rejectPendingPromise = useCallback(() => {
53
+ if (rejectRef.current) {
54
+ rejectRef.current(
55
+ new Error('New file picker opened before previous completed')
56
+ )
57
+ resolveRef.current = null
58
+ rejectRef.current = null
59
+ }
60
+ }, [])
61
+
62
+ const handleFileChange = useCallback(
63
+ (event: React.ChangeEvent<HTMLInputElement>) => {
64
+ const file = event.target.files?.[0]
65
+
66
+ if (file && resolveRef.current) {
67
+ resolveRef.current(file)
68
+
69
+ resolveRef.current = null
70
+ rejectRef.current = null
71
+
72
+ cleanupCancelHandler()
73
+ }
74
+
75
+ event.target.value = ''
76
+ },
77
+ [cleanupCancelHandler]
78
+ )
79
+
80
+ const openGallery = useCallback(() => {
81
+ return new Promise<File>((resolve, reject) => {
82
+ rejectPendingPromise()
83
+ cleanupCancelHandler()
84
+
85
+ resolveRef.current = resolve
86
+ rejectRef.current = reject
87
+
88
+ const input = galleryInputRef.current
89
+
90
+ if (!input) {
91
+ reject(new Error('Gallery input not found'))
92
+ resolveRef.current = null
93
+ rejectRef.current = null
94
+ return
95
+ }
96
+
97
+ const handleCancel = () => {
98
+ if (rejectRef.current) {
99
+ rejectRef.current(new Error('User cancelled file selection'))
100
+ resolveRef.current = null
101
+ rejectRef.current = null
102
+ }
103
+ cleanupCancelHandler()
104
+ }
105
+
106
+ input.addEventListener('cancel', handleCancel)
107
+ activeCancelHandlerRef.current = {input, handler: handleCancel}
108
+
109
+ input.click()
110
+ })
111
+ }, [rejectPendingPromise, cleanupCancelHandler])
112
+
113
+ const openCamera = useCallback(
114
+ (cameraFacing: CameraFacing = 'back') => {
115
+ return new Promise<File>((resolve, reject) => {
116
+ rejectPendingPromise()
117
+ cleanupCancelHandler()
118
+
119
+ resolveRef.current = resolve
120
+ rejectRef.current = reject
121
+
122
+ const input =
123
+ cameraFacing === 'front'
124
+ ? frontCameraInputRef.current
125
+ : backCameraInputRef.current
126
+
127
+ if (!input) {
128
+ reject(new Error('Camera input not found'))
129
+ resolveRef.current = null
130
+ rejectRef.current = null
131
+ return
132
+ }
133
+
134
+ const handleCancel = () => {
135
+ if (rejectRef.current) {
136
+ rejectRef.current(new Error('User cancelled camera'))
137
+ resolveRef.current = null
138
+ rejectRef.current = null
139
+ }
140
+ cleanupCancelHandler()
141
+ }
142
+
143
+ input.addEventListener('cancel', handleCancel)
144
+ activeCancelHandlerRef.current = {input, handler: handleCancel}
145
+
146
+ input.click()
147
+ })
148
+ },
149
+ [rejectPendingPromise, cleanupCancelHandler]
150
+ )
151
+
152
+ useEffect(() => {
153
+ return () => {
154
+ rejectPendingPromise()
155
+ cleanupCancelHandler()
156
+ }
157
+ }, [rejectPendingPromise, cleanupCancelHandler])
158
+
159
+ const contextValue: ImagePickerContextValue = useMemo(
160
+ () => ({
161
+ openCamera,
162
+ openGallery,
163
+ }),
164
+ [openCamera, openGallery]
165
+ )
166
+
167
+ return (
168
+ <ImagePickerContext.Provider value={contextValue}>
169
+ {children}
170
+ <input
171
+ ref={galleryInputRef}
172
+ type="file"
173
+ accept="image/*"
174
+ onChange={handleFileChange}
175
+ style={{display: 'none'}}
176
+ aria-hidden="true"
177
+ />
178
+ <input
179
+ ref={frontCameraInputRef}
180
+ type="file"
181
+ accept="image/*"
182
+ capture="user"
183
+ onChange={handleFileChange}
184
+ style={{display: 'none'}}
185
+ aria-hidden="true"
186
+ />
187
+ <input
188
+ ref={backCameraInputRef}
189
+ type="file"
190
+ accept="image/*"
191
+ capture="environment"
192
+ onChange={handleFileChange}
193
+ style={{display: 'none'}}
194
+ aria-hidden="true"
195
+ />
196
+ </ImagePickerContext.Provider>
197
+ )
198
+ }