@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.
- package/dist/components/ErrorBoundary.js +19 -0
- package/dist/components/ErrorBoundary.js.map +1 -0
- package/dist/components/MinisContainer.js +27 -16
- package/dist/components/MinisContainer.js.map +1 -1
- package/dist/internal/useReportInteraction.js +21 -0
- package/dist/internal/useReportInteraction.js.map +1 -0
- package/dist/mocks.js +32 -31
- package/dist/mocks.js.map +1 -1
- package/dist/providers/ImagePickerProvider.js +122 -63
- package/dist/providers/ImagePickerProvider.js.map +1 -1
- package/dist/utils/getWindowLocationPathname.js +7 -0
- package/dist/utils/getWindowLocationPathname.js.map +1 -0
- package/package.json +2 -3
- package/src/components/ErrorBoundary.tsx +25 -0
- package/src/components/MinisContainer.tsx +24 -2
- package/src/hooks/storage/useAsyncStorage.test.ts +3 -2
- package/src/internal/useReportImpression.ts +33 -0
- package/src/internal/useReportInteraction.ts +33 -0
- package/src/mocks.ts +3 -2
- package/src/providers/ImagePickerProvider.test.tsx +391 -0
- package/src/providers/ImagePickerProvider.tsx +93 -12
- package/src/utils/getWindowLocationPathname.ts +6 -0
|
@@ -1,108 +1,167 @@
|
|
|
1
|
-
import { jsxs as x, jsx as
|
|
2
|
-
import { useRef as
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
12
|
+
return g;
|
|
12
13
|
}
|
|
13
|
-
function
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
16
|
-
const { input:
|
|
17
|
-
|
|
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
|
-
}, []),
|
|
20
|
-
|
|
21
|
-
new Error(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
),
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
if (!
|
|
33
|
-
|
|
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
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
69
|
+
u.click();
|
|
43
70
|
});
|
|
44
|
-
}), [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
115
|
+
[
|
|
116
|
+
m,
|
|
117
|
+
a,
|
|
118
|
+
y,
|
|
119
|
+
r
|
|
120
|
+
]
|
|
62
121
|
);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}, [
|
|
66
|
-
const
|
|
122
|
+
I(() => () => {
|
|
123
|
+
m(), a();
|
|
124
|
+
}, [m, a]);
|
|
125
|
+
const T = V(
|
|
67
126
|
() => ({
|
|
68
|
-
openCamera:
|
|
69
|
-
openGallery:
|
|
127
|
+
openCamera: v,
|
|
128
|
+
openGallery: P
|
|
70
129
|
}),
|
|
71
|
-
[
|
|
130
|
+
[v, P]
|
|
72
131
|
);
|
|
73
|
-
return /* @__PURE__ */ x(
|
|
74
|
-
|
|
75
|
-
/* @__PURE__ */
|
|
132
|
+
return /* @__PURE__ */ x(R.Provider, { value: T, children: [
|
|
133
|
+
g,
|
|
134
|
+
/* @__PURE__ */ h(
|
|
76
135
|
"input",
|
|
77
136
|
{
|
|
78
|
-
ref:
|
|
137
|
+
ref: k,
|
|
79
138
|
type: "file",
|
|
80
139
|
accept: "image/*",
|
|
81
|
-
onChange:
|
|
140
|
+
onChange: C,
|
|
82
141
|
style: { display: "none" },
|
|
83
142
|
"aria-hidden": "true"
|
|
84
143
|
}
|
|
85
144
|
),
|
|
86
|
-
/* @__PURE__ */
|
|
145
|
+
/* @__PURE__ */ h(
|
|
87
146
|
"input",
|
|
88
147
|
{
|
|
89
|
-
ref:
|
|
148
|
+
ref: _,
|
|
90
149
|
type: "file",
|
|
91
150
|
accept: "image/*",
|
|
92
151
|
capture: "user",
|
|
93
|
-
onChange:
|
|
152
|
+
onChange: C,
|
|
94
153
|
style: { display: "none" },
|
|
95
154
|
"aria-hidden": "true"
|
|
96
155
|
}
|
|
97
156
|
),
|
|
98
|
-
/* @__PURE__ */
|
|
157
|
+
/* @__PURE__ */ h(
|
|
99
158
|
"input",
|
|
100
159
|
{
|
|
101
|
-
ref:
|
|
160
|
+
ref: E,
|
|
102
161
|
type: "file",
|
|
103
162
|
accept: "image/*",
|
|
104
163
|
capture: "environment",
|
|
105
|
-
onChange:
|
|
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
|
-
|
|
114
|
-
|
|
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":";;;;AAmBA,MAAMA,IAAqBC,EAA8C,IAAI;AAEtE,SAASC,IAAwB;AAChC,QAAAC,IAAUC,EAAWJ,CAAkB;AAC7C,MAAI,CAACG;AACH,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAEK,SAAAA;AACT;AAMgB,SAAAE,EAAoB,EAAC,UAAAC,KAAqC;AAClE,QAAAC,IAAkBC,EAAyB,IAAI,GAC/CC,IAAsBD,EAAyB,IAAI,GACnDE,IAAqBF,EAAyB,IAAI,GAClDG,IAAaH,EAAsC,IAAI,GACvDI,IAAYJ,EAAyC,IAAI,GACzDK,IAAyBL,EAGrB,IAAI,GACRM,IAAqBN,EAAoC,IAAI,GAE7D,EAAC,mBAAAO,EAAiB,IAAIC,EAAsB,GAC5C,EAAC,mBAAAC,EAAiB,IAAIC,EAAqB,GAE3CC,IAAuBC,EAAY,MAAM;AAC7C,QAAIP,EAAuB,SAAS;AAClC,YAAM,EAAC,OAAAQ,GAAO,SAAAC,EAAO,IAAIT,EAAuB;AAC1C,MAAAQ,EAAA,oBAAoB,UAAUC,CAAO,GAC3CT,EAAuB,UAAU;AAAA,IAAA;AAAA,EAErC,GAAG,EAAE,GAECU,IAAuBH,EAAY,MAAM;AAC7C,QAAIR,EAAU,SAAS;AACrB,YAAMY,IAAQ,IAAI;AAAA,QAChB;AAAA,MACF;AACI,MAAAV,EAAmB,YAAY,YACfG,EAAA;AAAA,QAChB,iBAAiB;AAAA,QACjB,kBAAkBO,EAAM;AAAA,MAAA,CACzB,IACQV,EAAmB,YAAY,YACtBG,EAAA;AAAA,QAChB,iBAAiB;AAAA,QACjB,kBAAkBO,EAAM;AAAA,MAAA,CACzB,GAGHZ,EAAU,QAAQY,CAAK,GACvBb,EAAW,UAAU,MACrBC,EAAU,UAAU,MACpBE,EAAmB,UAAU;AAAA,IAAA;AAAA,EAC/B,GACC,CAACG,CAAiB,CAAC,GAEhBQ,IAAmBL;AAAA,IACvB,CAACM,MAA+C;AAC9C,YAAMC,IAAOD,EAAM,OAAO,QAAQ,CAAC;AAE/B,MAAAC,KAAQhB,EAAW,YAEjBG,EAAmB,YAAY,YACfG,EAAA;AAAA,QAChB,iBAAiB;AAAA,MAAA,CAClB,IACQH,EAAmB,YAAY,YACtBG,EAAA;AAAA,QAChB,iBAAiB;AAAA,MAAA,CAClB,GAGHN,EAAW,QAAQgB,CAAI,GAEvBhB,EAAW,UAAU,MACrBC,EAAU,UAAU,MACpBE,EAAmB,UAAU,MAERK,EAAA,IAGvBO,EAAM,OAAO,QAAQ;AAAA,IACvB;AAAA,IACA,CAACP,GAAsBF,CAAiB;AAAA,EAC1C,GAEMW,IAAcR,EAAY,MACvB,IAAI,QAAc,CAACS,GAASC,MAAW;AACvB,IAAAP,EAAA,GACAJ,EAAA,GAErBR,EAAW,UAAUkB,GACrBjB,EAAU,UAAUkB,GACpBhB,EAAmB,UAAU;AAE7B,UAAMO,IAAQd,EAAgB;AAE9B,QAAI,CAACc,GAAO;AACJ,YAAAG,IAAQ,IAAI,MAAM,yBAAyB;AAC/B,MAAAP,EAAA;AAAA,QAChB,iBAAiB;AAAA,QACjB,kBAAkBO,EAAM;AAAA,MAAA,CACzB,GACDM,EAAON,CAAK,GACZb,EAAW,UAAU,MACrBC,EAAU,UAAU,MACpBE,EAAmB,UAAU;AAC7B;AAAA,IAAA;AAGF,UAAMiB,IAAe,MAAM;AACzB,UAAInB,EAAU,SAAS;AACf,cAAAY,IAAQ,IAAI,MAAM,+BAA+B;AACrC,QAAAP,EAAA;AAAA,UAChB,iBAAiB;AAAA,UACjB,kBAAkBO,EAAM;AAAA,QAAA,CACzB,GACDZ,EAAU,QAAQY,CAAK,GACvBb,EAAW,UAAU,MACrBC,EAAU,UAAU,MACpBE,EAAmB,UAAU;AAAA,MAAA;AAEV,MAAAK,EAAA;AAAA,IACvB;AAEM,IAAAE,EAAA,iBAAiB,UAAUU,CAAY,GAC7ClB,EAAuB,UAAU,EAAC,OAAAQ,GAAO,SAASU,EAAY,GAE5Cd,EAAA;AAAA,MAChB,iBAAiB;AAAA,IAAA,CAClB,GAEDF,EAAkB,EAAC,YAAY,SAAA,CAAS,EACrC,KAAK,MAAM;AAEV,MAAAM,EAAM,MAAM;AAAA,IAAA,CACb,EACA,MAAM,MAAM;AAEX,MAAAA,EAAM,MAAM;AAAA,IAAA,CACb;AAAA,EAAA,CACJ,GACA;AAAA,IACDE;AAAA,IACAJ;AAAA,IACAJ;AAAA,IACAE;AAAA,EAAA,CACD,GAEKe,IAAaZ;AAAA,IACjB,CAACa,IAA6B,WACrB,IAAI,QAAc,CAACJ,GAASC,MAAW;AACvB,MAAAP,EAAA,GACAJ,EAAA,GAErBR,EAAW,UAAUkB,GACrBjB,EAAU,UAAUkB,GACpBhB,EAAmB,UAAU;AAE7B,YAAMO,IACJY,MAAiB,UACbxB,EAAoB,UACpBC,EAAmB;AAEzB,UAAI,CAACW,GAAO;AACJ,cAAAG,IAAQ,IAAI,MAAM,wBAAwB;AAC9B,QAAAP,EAAA;AAAA,UAChB,iBAAiB;AAAA,UACjB,kBAAkBO,EAAM;AAAA,QAAA,CACzB,GACDM,EAAON,CAAK,GACZb,EAAW,UAAU,MACrBC,EAAU,UAAU,MACpBE,EAAmB,UAAU;AAC7B;AAAA,MAAA;AAGF,YAAMiB,IAAe,MAAM;AACzB,YAAInB,EAAU,SAAS;AACf,gBAAAY,IAAQ,IAAI,MAAM,uBAAuB;AAC7B,UAAAP,EAAA;AAAA,YAChB,iBAAiB;AAAA,YACjB,kBAAkBO,EAAM;AAAA,UAAA,CACzB,GACDZ,EAAU,QAAQY,CAAK,GACvBb,EAAW,UAAU,MACrBC,EAAU,UAAU,MACpBE,EAAmB,UAAU;AAAA,QAAA;AAEV,QAAAK,EAAA;AAAA,MACvB;AAEM,MAAAE,EAAA,iBAAiB,UAAUU,CAAY,GAC7ClB,EAAuB,UAAU,EAAC,OAAAQ,GAAO,SAASU,EAAY,GAE5Cd,EAAA;AAAA,QAChB,iBAAiB;AAAA,MAAA,CAClB,GAEiBF,EAAA,EAAC,YAAY,SAAQ,CAAC,EACrC,KAAK,CAAC,EAAC,SAAAmB,QAAa;AACnB,YAAIA;AACF,UAAAb,EAAM,MAAM;AAAA,aACP;AACC,gBAAAG,IAAQ,IAAI,MAAM,+BAA+B;AACrC,UAAAP,EAAA;AAAA,YAChB,iBAAiB;AAAA,YACjB,kBAAkBO,EAAM;AAAA,UAAA,CACzB,GACDM,EAAON,CAAK,GACZb,EAAW,UAAU,MACrBC,EAAU,UAAU,MACpBE,EAAmB,UAAU;AAAA,QAAA;AAAA,MAC/B,CACD,EACA,MAAM,MAAM;AACL,cAAAU,IAAQ,IAAI,MAAM,+BAA+B;AACvD,QAAAM,EAAON,CAAK,GACZb,EAAW,UAAU,MACrBC,EAAU,UAAU,MACpBE,EAAmB,UAAU;AAAA,MAAA,CAC9B;AAAA,IAAA,CACJ;AAAA,IAEH;AAAA,MACES;AAAA,MACAJ;AAAA,MACAJ;AAAA,MACAE;AAAA,IAAA;AAAA,EAEJ;AAEA,EAAAkB,EAAU,MACD,MAAM;AACU,IAAAZ,EAAA,GACAJ,EAAA;AAAA,EACvB,GACC,CAACI,GAAsBJ,CAAoB,CAAC;AAE/C,QAAMiB,IAAwCC;AAAA,IAC5C,OAAO;AAAA,MACL,YAAAL;AAAA,MACA,aAAAJ;AAAA,IAAA;AAAA,IAEF,CAACI,GAAYJ,CAAW;AAAA,EAC1B;AAEA,SACG,gBAAAU,EAAAtC,EAAmB,UAAnB,EAA4B,OAAOoC,GACjC,UAAA;AAAA,IAAA9B;AAAA,IACD,gBAAAiC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAKhC;AAAA,QACL,MAAK;AAAA,QACL,QAAO;AAAA,QACP,UAAUkB;AAAA,QACV,OAAO,EAAC,SAAS,OAAM;AAAA,QACvB,eAAY;AAAA,MAAA;AAAA,IACd;AAAA,IACA,gBAAAc;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK9B;AAAA,QACL,MAAK;AAAA,QACL,QAAO;AAAA,QACP,SAAQ;AAAA,QACR,UAAUgB;AAAA,QACV,OAAO,EAAC,SAAS,OAAM;AAAA,QACvB,eAAY;AAAA,MAAA;AAAA,IACd;AAAA,IACA,gBAAAc;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK7B;AAAA,QACL,MAAK;AAAA,QACL,QAAO;AAAA,QACP,SAAQ;AAAA,QACR,UAAUe;AAAA,QACV,OAAO,EAAC,SAAS,OAAM;AAAA,QACvB,eAAY;AAAA,MAAA;AAAA,IAAA;AAAA,EACd,GACF;AAEJ;"}
|
|
@@ -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.
|
|
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.
|
|
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
|
|
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(
|
|
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('
|
|
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:
|
|
252
|
+
getPersistedItem: null,
|
|
253
253
|
setPersistedItem: undefined,
|
|
254
254
|
removePersistedItem: undefined,
|
|
255
255
|
getAllPersistedKeys: ['key1', 'key2', 'key3'],
|
|
256
256
|
clearPersistedItems: undefined,
|
|
257
|
-
getInternalPersistedItem:
|
|
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> = {}
|