@shopify/shop-minis-react 0.2.0 → 0.2.2

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,108 +1,167 @@
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";
1
+ import { jsxs as x, jsx as h } from "react/jsx-runtime";
2
+ import { useRef as p, useCallback as d, useEffect as I, useMemo as V, createContext as b, useContext as A } from "react";
3
3
  import { useRequestPermissions as L } from "../hooks/util/useRequestPermissions.js";
4
- const k = b(null);
5
- function H() {
6
- const f = A(k);
7
- if (!f)
4
+ import { useReportInteraction as M } from "../internal/useReportInteraction.js";
5
+ const R = b(null);
6
+ function F() {
7
+ const g = A(R);
8
+ if (!g)
8
9
  throw new Error(
9
10
  "useImagePickerContext must be used within an ImagePickerProvider"
10
11
  );
11
- return f;
12
+ return g;
12
13
  }
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(() => {
15
- if (a.current) {
16
- const { input: u, handler: c } = a.current;
17
- u.removeEventListener("cancel", c), a.current = null;
14
+ function N({ children: g }) {
15
+ const k = p(null), _ = p(null), E = p(null), t = p(null), e = p(null), f = p(null), n = p(null), { requestPermission: y } = L(), { reportInteraction: r } = M(), a = d(() => {
16
+ if (f.current) {
17
+ const { input: c, handler: i } = f.current;
18
+ c.removeEventListener("cancel", i), f.current = null;
18
19
  }
19
- }, []), o = s(() => {
20
- e.current && (e.current(
21
- new Error("New file picker opened before previous completed")
22
- ), 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 = "";
20
+ }, []), m = d(() => {
21
+ if (e.current) {
22
+ const c = new Error(
23
+ "New file picker opened before previous completed"
24
+ );
25
+ n.current === "gallery" ? r({
26
+ interactionType: "image_picker_error",
27
+ interactionValue: c.message
28
+ }) : n.current === "camera" && r({
29
+ interactionType: "camera_error",
30
+ interactionValue: c.message
31
+ }), e.current(c), t.current = null, e.current = null, n.current = null;
32
+ }
33
+ }, [r]), C = d(
34
+ (c) => {
35
+ const i = c.target.files?.[0];
36
+ i && t.current && (n.current === "gallery" ? r({
37
+ interactionType: "image_picker_success"
38
+ }) : n.current === "camera" && r({
39
+ interactionType: "camera_success"
40
+ }), t.current(i), t.current = null, e.current = null, n.current = null, a()), c.target.value = "";
27
41
  },
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) {
33
- c(new Error("Gallery input not found")), n.current = null, e.current = null;
42
+ [a, r]
43
+ ), P = d(() => new Promise((c, i) => {
44
+ m(), a(), t.current = c, e.current = i, n.current = "gallery";
45
+ const u = k.current;
46
+ if (!u) {
47
+ const o = new Error("Gallery input not found");
48
+ r({
49
+ interactionType: "image_picker_error",
50
+ interactionValue: o.message
51
+ }), i(o), t.current = null, e.current = null, n.current = null;
34
52
  return;
35
53
  }
36
- const l = () => {
37
- e.current && (e.current(new Error("User cancelled file selection")), n.current = null, e.current = null), r();
54
+ const s = () => {
55
+ if (e.current) {
56
+ const o = new Error("User cancelled file selection");
57
+ r({
58
+ interactionType: "image_picker_error",
59
+ interactionValue: o.message
60
+ }), e.current(o), t.current = null, e.current = null, n.current = null;
61
+ }
62
+ a();
38
63
  };
39
- t.addEventListener("cancel", l), a.current = { input: t, handler: l }, p({ permission: "CAMERA" }).then(() => {
40
- t.click();
64
+ u.addEventListener("cancel", s), f.current = { input: u, handler: s }, r({
65
+ interactionType: "image_picker_open"
66
+ }), y({ permission: "CAMERA" }).then(() => {
67
+ u.click();
41
68
  }).catch(() => {
42
- t.click();
69
+ u.click();
43
70
  });
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;
71
+ }), [
72
+ m,
73
+ a,
74
+ y,
75
+ r
76
+ ]), v = d(
77
+ (c = "back") => new Promise((i, u) => {
78
+ m(), a(), t.current = i, e.current = u, n.current = "camera";
79
+ const s = c === "front" ? _.current : E.current;
80
+ if (!s) {
81
+ const l = new Error("Camera input not found");
82
+ r({
83
+ interactionType: "camera_error",
84
+ interactionValue: l.message
85
+ }), u(l), t.current = null, e.current = null, n.current = null;
50
86
  return;
51
87
  }
52
- const w = () => {
53
- e.current && (e.current(new Error("User cancelled camera")), n.current = null, e.current = null), r();
88
+ const o = () => {
89
+ if (e.current) {
90
+ const l = new Error("User cancelled camera");
91
+ r({
92
+ interactionType: "camera_error",
93
+ interactionValue: l.message
94
+ }), e.current(l), t.current = null, e.current = null, n.current = null;
95
+ }
96
+ a();
54
97
  };
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);
98
+ s.addEventListener("cancel", o), f.current = { input: s, handler: o }, r({
99
+ interactionType: "camera_open"
100
+ }), y({ permission: "CAMERA" }).then(({ granted: l }) => {
101
+ if (l)
102
+ s.click();
103
+ else {
104
+ const w = new Error("Camera permission not granted");
105
+ r({
106
+ interactionType: "camera_error",
107
+ interactionValue: w.message
108
+ }), u(w), t.current = null, e.current = null, n.current = null;
109
+ }
57
110
  }).catch(() => {
58
- t(new Error("Camera permission not granted")), n.current = null, e.current = null;
111
+ const l = new Error("Camera permission not granted");
112
+ u(l), t.current = null, e.current = null, n.current = null;
59
113
  });
60
114
  }),
61
- [o, r, p]
115
+ [
116
+ m,
117
+ a,
118
+ y,
119
+ r
120
+ ]
62
121
  );
63
- R(() => () => {
64
- o(), r();
65
- }, [o, r]);
66
- const v = I(
122
+ I(() => () => {
123
+ m(), a();
124
+ }, [m, a]);
125
+ const T = V(
67
126
  () => ({
68
- openCamera: P,
69
- openGallery: E
127
+ openCamera: v,
128
+ openGallery: P
70
129
  }),
71
- [P, E]
130
+ [v, P]
72
131
  );
73
- return /* @__PURE__ */ x(k.Provider, { value: v, children: [
74
- f,
75
- /* @__PURE__ */ m(
132
+ return /* @__PURE__ */ x(R.Provider, { value: T, children: [
133
+ g,
134
+ /* @__PURE__ */ h(
76
135
  "input",
77
136
  {
78
- ref: C,
137
+ ref: k,
79
138
  type: "file",
80
139
  accept: "image/*",
81
- onChange: d,
140
+ onChange: C,
82
141
  style: { display: "none" },
83
142
  "aria-hidden": "true"
84
143
  }
85
144
  ),
86
- /* @__PURE__ */ m(
145
+ /* @__PURE__ */ h(
87
146
  "input",
88
147
  {
89
- ref: h,
148
+ ref: _,
90
149
  type: "file",
91
150
  accept: "image/*",
92
151
  capture: "user",
93
- onChange: d,
152
+ onChange: C,
94
153
  style: { display: "none" },
95
154
  "aria-hidden": "true"
96
155
  }
97
156
  ),
98
- /* @__PURE__ */ m(
157
+ /* @__PURE__ */ h(
99
158
  "input",
100
159
  {
101
- ref: g,
160
+ ref: E,
102
161
  type: "file",
103
162
  accept: "image/*",
104
163
  capture: "environment",
105
- onChange: d,
164
+ onChange: C,
106
165
  style: { display: "none" },
107
166
  "aria-hidden": "true"
108
167
  }
@@ -110,7 +169,7 @@ function U({ children: f }) {
110
169
  ] });
111
170
  }
112
171
  export {
113
- U as ImagePickerProvider,
114
- H as useImagePickerContext
172
+ N as ImagePickerProvider,
173
+ F as useImagePickerContext
115
174
  };
116
175
  //# 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\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
+ {"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'\nimport {useReportInteraction} from '../internal/useReportInteraction'\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 const activeOperationRef = useRef<'gallery' | 'camera' | null>(null)\n\n const {requestPermission} = useRequestPermissions()\n const {reportInteraction} = useReportInteraction()\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 const error = new Error(\n 'New file picker opened before previous completed'\n )\n if (activeOperationRef.current === 'gallery') {\n reportInteraction({\n interactionType: 'image_picker_error',\n interactionValue: error.message,\n })\n } else if (activeOperationRef.current === 'camera') {\n reportInteraction({\n interactionType: 'camera_error',\n interactionValue: error.message,\n })\n }\n\n rejectRef.current(error)\n resolveRef.current = null\n rejectRef.current = null\n activeOperationRef.current = null\n }\n }, [reportInteraction])\n\n const handleFileChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0]\n\n if (file && resolveRef.current) {\n // Report success based on the active operation\n if (activeOperationRef.current === 'gallery') {\n reportInteraction({\n interactionType: 'image_picker_success',\n })\n } else if (activeOperationRef.current === 'camera') {\n reportInteraction({\n interactionType: 'camera_success',\n })\n }\n\n resolveRef.current(file)\n\n resolveRef.current = null\n rejectRef.current = null\n activeOperationRef.current = null\n\n cleanupCancelHandler()\n }\n\n event.target.value = ''\n },\n [cleanupCancelHandler, reportInteraction]\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 activeOperationRef.current = 'gallery'\n\n const input = galleryInputRef.current\n\n if (!input) {\n const error = new Error('Gallery input not found')\n reportInteraction({\n interactionType: 'image_picker_error',\n interactionValue: error.message,\n })\n reject(error)\n resolveRef.current = null\n rejectRef.current = null\n activeOperationRef.current = null\n return\n }\n\n const handleCancel = () => {\n if (rejectRef.current) {\n const error = new Error('User cancelled file selection')\n reportInteraction({\n interactionType: 'image_picker_error',\n interactionValue: error.message,\n })\n rejectRef.current(error)\n resolveRef.current = null\n rejectRef.current = null\n activeOperationRef.current = null\n }\n cleanupCancelHandler()\n }\n\n input.addEventListener('cancel', handleCancel)\n activeCancelHandlerRef.current = {input, handler: handleCancel}\n\n reportInteraction({\n interactionType: 'image_picker_open',\n })\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 }, [\n rejectPendingPromise,\n cleanupCancelHandler,\n requestPermission,\n reportInteraction,\n ])\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 activeOperationRef.current = 'camera'\n\n const input =\n cameraFacing === 'front'\n ? frontCameraInputRef.current\n : backCameraInputRef.current\n\n if (!input) {\n const error = new Error('Camera input not found')\n reportInteraction({\n interactionType: 'camera_error',\n interactionValue: error.message,\n })\n reject(error)\n resolveRef.current = null\n rejectRef.current = null\n activeOperationRef.current = null\n return\n }\n\n const handleCancel = () => {\n if (rejectRef.current) {\n const error = new Error('User cancelled camera')\n reportInteraction({\n interactionType: 'camera_error',\n interactionValue: error.message,\n })\n rejectRef.current(error)\n resolveRef.current = null\n rejectRef.current = null\n activeOperationRef.current = null\n }\n cleanupCancelHandler()\n }\n\n input.addEventListener('cancel', handleCancel)\n activeCancelHandlerRef.current = {input, handler: handleCancel}\n\n reportInteraction({\n interactionType: 'camera_open',\n })\n\n requestPermission({permission: 'CAMERA'})\n .then(({granted}) => {\n if (granted) {\n input.click()\n } else {\n const error = new Error('Camera permission not granted')\n reportInteraction({\n interactionType: 'camera_error',\n interactionValue: error.message,\n })\n reject(error)\n resolveRef.current = null\n rejectRef.current = null\n activeOperationRef.current = null\n }\n })\n .catch(() => {\n const error = new Error('Camera permission not granted')\n reject(error)\n resolveRef.current = null\n rejectRef.current = null\n activeOperationRef.current = null\n })\n })\n },\n [\n rejectPendingPromise,\n cleanupCancelHandler,\n requestPermission,\n reportInteraction,\n ]\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","activeOperationRef","requestPermission","useRequestPermissions","reportInteraction","useReportInteraction","cleanupCancelHandler","useCallback","input","handler","rejectPendingPromise","error","handleFileChange","event","file","openGallery","resolve","reject","handleCancel","openCamera","cameraFacing","granted","useEffect","contextValue","useMemo","jsxs","jsx"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ function n() {
2
+ return typeof window < "u" && window.location ? window.location.pathname || "/" : "";
3
+ }
4
+ export {
5
+ n as getWindowLocationPathname
6
+ };
7
+ //# sourceMappingURL=getWindowLocationPathname.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getWindowLocationPathname.js","sources":["../../src/utils/getWindowLocationPathname.ts"],"sourcesContent":["export function getWindowLocationPathname(): string {\n if (typeof window !== 'undefined' && window.location) {\n return window.location.pathname || '/'\n }\n return ''\n}\n"],"names":["getWindowLocationPathname"],"mappings":"AAAO,SAASA,IAAoC;AAClD,SAAI,OAAO,SAAW,OAAe,OAAO,WACnC,OAAO,SAAS,YAAY,MAE9B;AACT;"}
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.2.0",
4
+ "version": "0.2.2",
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.6.0",
46
+ "@shopify/shop-minis-platform": "0.7.0",
47
47
  "@tailwindcss/vite": "4.1.8",
48
48
  "@types/color": "3.0.6",
49
49
  "@types/lodash": "4.17.20",
@@ -79,7 +79,6 @@
79
79
  },
80
80
  "devDependencies": {
81
81
  "@playwright/test": "^1.54.2",
82
- "@shopify/generate-docs": "^0.16.6",
83
82
  "@storybook/addon-docs": "^9.0.16",
84
83
  "@storybook/react-vite": "^9.0.16",
85
84
  "@testing-library/jest-dom": "^6.6.4",
@@ -0,0 +1,25 @@
1
+ import React, {Component} from 'react'
2
+
3
+ import {ReportErrorParams} from '@shopify/shop-minis-platform/actions'
4
+
5
+ interface ErrorBoundaryProps {
6
+ children: React.ReactNode
7
+ onError: (params: ReportErrorParams) => void
8
+ }
9
+
10
+ export class ErrorBoundary extends Component<ErrorBoundaryProps> {
11
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
12
+ // Report the error to the app
13
+ this.props.onError({
14
+ message: error.message,
15
+ stack: error.stack,
16
+ additionalContext: {
17
+ componentStack: errorInfo.componentStack,
18
+ },
19
+ })
20
+ }
21
+
22
+ render() {
23
+ return this.props.children
24
+ }
25
+ }
@@ -1,12 +1,30 @@
1
- import React, {useEffect, useState} from 'react'
1
+ import React, {useCallback, useEffect, useState} from 'react'
2
2
 
3
+ import {ReportErrorParams} from '@shopify/shop-minis-platform/actions'
4
+
5
+ import {useShopActions} from '../internal/useShopActions'
3
6
  import {injectMocks} from '../mocks'
4
7
  import {ImagePickerProvider} from '../providers/ImagePickerProvider'
5
8
 
9
+ import {ErrorBoundary} from './ErrorBoundary'
10
+
6
11
  injectMocks()
7
12
 
8
13
  export function MinisContainer({children}: {children: React.ReactNode}) {
9
14
  const [isSDKReady, setIsSDKReady] = useState(false)
15
+ const {reportError} = useShopActions()
16
+
17
+ const handleError = useCallback(
18
+ async (params: ReportErrorParams) => {
19
+ try {
20
+ await reportError(params)
21
+ } catch (error) {
22
+ // If reporting fails, at least log to console
23
+ console.error('Failed to report error to app:', error)
24
+ }
25
+ },
26
+ [reportError]
27
+ )
10
28
 
11
29
  useEffect(() => {
12
30
  // Function to check if SDK is ready
@@ -63,5 +81,9 @@ export function MinisContainer({children}: {children: React.ReactNode}) {
63
81
  )
64
82
  }
65
83
 
66
- return <ImagePickerProvider>{children}</ImagePickerProvider>
84
+ return (
85
+ <ErrorBoundary onError={handleError}>
86
+ <ImagePickerProvider>{children}</ImagePickerProvider>
87
+ </ErrorBoundary>
88
+ )
67
89
  }
@@ -29,7 +29,7 @@ describe('useAsyncStorage', () => {
29
29
 
30
30
  // Set up mock actions with proper implementations
31
31
  mockActions = {
32
- getPersistedItem: vi.fn().mockResolvedValue('stored-value'),
32
+ getPersistedItem: vi.fn().mockResolvedValue(null),
33
33
  setPersistedItem: vi.fn().mockResolvedValue(undefined),
34
34
  removePersistedItem: vi.fn().mockResolvedValue(undefined),
35
35
  getAllPersistedKeys: vi.fn().mockResolvedValue(['key1', 'key2', 'key3']),
@@ -65,11 +65,12 @@ describe('useAsyncStorage', () => {
65
65
 
66
66
  describe('getItem', () => {
67
67
  it('calls getPersistedItem with correct parameters', async () => {
68
+ mockActions.getPersistedItem.mockResolvedValue('test-value')
68
69
  const {result} = renderHook(() => useAsyncStorage())
69
70
 
70
71
  await act(async () => {
71
72
  const value = await result.current.getItem({key: 'test-key'})
72
- expect(value).toBe('stored-value')
73
+ expect(value).toBe('test-value')
73
74
  })
74
75
 
75
76
  expect(mockActions.getPersistedItem).toHaveBeenCalledWith({
@@ -0,0 +1,33 @@
1
+ import {useCallback} from 'react'
2
+
3
+ import {ReportImpressionParams} from '@shopify/shop-minis-platform/actions'
4
+
5
+ import {getWindowLocationPathname} from '../utils/getWindowLocationPathname'
6
+
7
+ import {useHandleAction} from './useHandleAction'
8
+ import {useShopActions} from './useShopActions'
9
+
10
+ interface UseReportImpressionReturns {
11
+ /**
12
+ * Report an impression event for analytics.
13
+ */
14
+ reportImpression: (params: ReportImpressionParams) => Promise<void>
15
+ }
16
+
17
+ export const useReportImpression = (): UseReportImpressionReturns => {
18
+ const {reportImpression} = useShopActions()
19
+ const handleAction = useHandleAction(reportImpression)
20
+
21
+ const report = useCallback(
22
+ (params: ReportImpressionParams) => {
23
+ const enrichedParams = {
24
+ ...params,
25
+ pageValue: params.pageValue || getWindowLocationPathname(),
26
+ }
27
+ return handleAction(enrichedParams)
28
+ },
29
+ [handleAction]
30
+ )
31
+
32
+ return {reportImpression: report}
33
+ }
@@ -0,0 +1,33 @@
1
+ import {useCallback} from 'react'
2
+
3
+ import {ReportInteractionParams} from '@shopify/shop-minis-platform/actions'
4
+
5
+ import {getWindowLocationPathname} from '../utils/getWindowLocationPathname'
6
+
7
+ import {useHandleAction} from './useHandleAction'
8
+ import {useShopActions} from './useShopActions'
9
+
10
+ interface UseReportInteractionReturns {
11
+ /**
12
+ * Report a user interaction event for analytics.
13
+ */
14
+ reportInteraction: (params: ReportInteractionParams) => Promise<void>
15
+ }
16
+
17
+ export const useReportInteraction = (): UseReportInteractionReturns => {
18
+ const {reportInteraction} = useShopActions()
19
+ const handleAction = useHandleAction(reportInteraction)
20
+
21
+ const report = useCallback(
22
+ (params: ReportInteractionParams) => {
23
+ const enrichedParams = {
24
+ ...params,
25
+ pageValue: params.pageValue || getWindowLocationPathname(),
26
+ }
27
+ return handleAction(enrichedParams)
28
+ },
29
+ [handleAction]
30
+ )
31
+
32
+ return {reportInteraction: report}
33
+ }
package/src/mocks.ts CHANGED
@@ -249,12 +249,12 @@ export function makeMockActions(): ShopActions {
249
249
  },
250
250
  ],
251
251
  },
252
- getPersistedItem: 'stored-value',
252
+ getPersistedItem: null,
253
253
  setPersistedItem: undefined,
254
254
  removePersistedItem: undefined,
255
255
  getAllPersistedKeys: ['key1', 'key2', 'key3'],
256
256
  clearPersistedItems: undefined,
257
- getInternalPersistedItem: 'internal-value',
257
+ getInternalPersistedItem: null,
258
258
  setInternalPersistedItem: undefined,
259
259
  removeInternalPersistedItem: undefined,
260
260
  getAllInternalPersistedKeys: ['internal-key1', 'internal-key2'],
@@ -461,6 +461,7 @@ export function makeMockActions(): ShopActions {
461
461
  requestPermission: {
462
462
  granted: true,
463
463
  },
464
+ reportError: undefined,
464
465
  } as const
465
466
 
466
467
  const mock: Partial<ShopActions> = {}