@shopify/shop-minis-react 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,58 +1,60 @@
1
- import { jsx as n } from "react/jsx-runtime";
2
- import { memo as v, useState as i, useEffect as k, useMemo as u, useCallback as I } from "react";
3
- import { cn as m } from "../../lib/utils.js";
4
- import { getThumbhashDataURL as j, getResizedImageUrl as y } from "../../utils/image.js";
5
- const O = v(function(d) {
1
+ import { jsx as u } from "react/jsx-runtime";
2
+ import { memo as j, useState as i, useEffect as k, useMemo as m, useCallback as y } from "react";
3
+ import { cn as d } from "../../lib/utils.js";
4
+ import { getThumbhashDataURL as I, getResizedImageUrl as z } from "../../utils/image.js";
5
+ const O = j(function(b) {
6
6
  const {
7
7
  src: r,
8
8
  file: t,
9
- thumbhash: a,
10
- onLoad: s,
11
- className: b,
12
- style: f,
13
- aspectRatio: g = "auto",
14
- ...h
15
- } = d, [L, p] = i(!1), [o, c] = i(null);
9
+ thumbhash: s,
10
+ onLoad: c,
11
+ className: f,
12
+ style: h,
13
+ aspectRatio: o = "auto",
14
+ objectFit: g = "contain",
15
+ ...L
16
+ } = b, [p, U] = i(!1), [a, l] = i(null);
16
17
  k(() => {
17
18
  if (!t) {
18
- c(null);
19
+ l(null);
19
20
  return;
20
21
  }
21
22
  const e = URL.createObjectURL(t);
22
- return c(e), () => {
23
+ return l(e), () => {
23
24
  URL.revokeObjectURL(e);
24
25
  };
25
26
  }, [t]);
26
- const l = u(
27
- () => j(a ?? void 0),
28
- [a]
29
- ), U = I(
27
+ const n = m(
28
+ () => I(s ?? void 0),
29
+ [s]
30
+ ), R = y(
30
31
  (e) => {
31
- p(!0), s?.(e);
32
+ U(!0), c?.(e);
32
33
  },
33
- [s]
34
- ), R = u(() => o || y(r), [o, r]);
35
- return /* @__PURE__ */ n(
34
+ [c]
35
+ ), v = m(() => a || z(r), [a, r]);
36
+ return /* @__PURE__ */ u(
36
37
  "div",
37
38
  {
38
- className: m("relative w-full ", b),
39
+ className: d("relative w-full", f),
39
40
  style: {
40
- ...f,
41
- aspectRatio: g,
42
- backgroundImage: l ? `url(${l})` : void 0,
41
+ ...h,
42
+ ...o !== "auto" && { aspectRatio: o },
43
+ backgroundImage: n ? `url(${n})` : void 0,
43
44
  backgroundSize: "cover",
44
45
  backgroundPosition: "center"
45
46
  },
46
- children: /* @__PURE__ */ n(
47
+ children: /* @__PURE__ */ u(
47
48
  "img",
48
49
  {
49
- className: m(
50
- "absolute inset-0 opacity-0 size-full object-cover",
51
- L && "opacity-100"
50
+ className: d(
51
+ o === "auto" ? "opacity-0 w-full h-auto" : "absolute inset-0 opacity-0 size-full",
52
+ `object-${g}`,
53
+ p && "opacity-100"
52
54
  ),
53
- src: R,
54
- onLoad: U,
55
- ...h
55
+ src: v,
56
+ onLoad: R,
57
+ ...L
56
58
  }
57
59
  )
58
60
  }
@@ -1 +1 @@
1
- {"version":3,"file":"image.js","sources":["../../../src/components/atoms/image.tsx"],"sourcesContent":["/* eslint-disable jsx-a11y/alt-text */\n/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */\nimport {\n ImgHTMLAttributes,\n useCallback,\n useMemo,\n memo,\n useState,\n useEffect,\n} from 'react'\n\nimport {cn} from '../../lib/utils'\nimport {getThumbhashDataURL, getResizedImageUrl} from '../../utils'\n\ntype ImageProps = ImgHTMLAttributes<HTMLImageElement> & {\n src?: string\n file?: File\n thumbhash?: string | null\n aspectRatio?: number | string\n}\n\nexport const Image = memo(function Image(props: ImageProps) {\n const {\n src,\n file,\n thumbhash,\n onLoad,\n className,\n style,\n aspectRatio = 'auto',\n ...restProps\n } = props\n\n const [isLoaded, setIsLoaded] = useState(false)\n const [blobUrl, setBlobUrl] = useState<string | null>(null)\n\n // Create and manage blob URL for File objects\n useEffect(() => {\n if (!file) {\n setBlobUrl(null)\n return\n }\n\n const url = URL.createObjectURL(file)\n setBlobUrl(url)\n\n // Cleanup on unmount or when file changes\n return () => {\n URL.revokeObjectURL(url)\n }\n }, [file])\n\n const thumbhashDataURL = useMemo(\n () => getThumbhashDataURL(thumbhash ?? undefined),\n [thumbhash]\n )\n\n const handleLoad = useCallback(\n (event: React.SyntheticEvent<HTMLImageElement, Event>) => {\n setIsLoaded(true)\n onLoad?.(event)\n },\n [onLoad]\n )\n\n // Use blob URL if file is provided, otherwise use src\n const imageSrc = useMemo(() => {\n if (blobUrl) return blobUrl\n return getResizedImageUrl(src)\n }, [blobUrl, src])\n\n return (\n <div\n className={cn('relative w-full ', className)}\n style={{\n ...style,\n aspectRatio,\n backgroundImage: thumbhashDataURL\n ? `url(${thumbhashDataURL})`\n : undefined,\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n }}\n >\n <img\n className={cn(\n 'absolute inset-0 opacity-0 size-full object-cover',\n isLoaded && 'opacity-100'\n )}\n src={imageSrc}\n onLoad={handleLoad}\n {...restProps}\n />\n </div>\n )\n})\n"],"names":["Image","memo","props","src","file","thumbhash","onLoad","className","style","aspectRatio","restProps","isLoaded","setIsLoaded","useState","blobUrl","setBlobUrl","useEffect","url","thumbhashDataURL","useMemo","getThumbhashDataURL","handleLoad","useCallback","event","imageSrc","getResizedImageUrl","jsx","cn"],"mappings":";;;;AAqBO,MAAMA,IAAQC,EAAK,SAAeC,GAAmB;AACpD,QAAA;AAAA,IACJ,KAAAC;AAAA,IACA,MAAAC;AAAA,IACA,WAAAC;AAAA,IACA,QAAAC;AAAA,IACA,WAAAC;AAAA,IACA,OAAAC;AAAA,IACA,aAAAC,IAAc;AAAA,IACd,GAAGC;AAAA,EAAA,IACDR,GAEE,CAACS,GAAUC,CAAW,IAAIC,EAAS,EAAK,GACxC,CAACC,GAASC,CAAU,IAAIF,EAAwB,IAAI;AAG1D,EAAAG,EAAU,MAAM;AACd,QAAI,CAACZ,GAAM;AACT,MAAAW,EAAW,IAAI;AACf;AAAA,IAAA;AAGI,UAAAE,IAAM,IAAI,gBAAgBb,CAAI;AACpC,WAAAW,EAAWE,CAAG,GAGP,MAAM;AACX,UAAI,gBAAgBA,CAAG;AAAA,IACzB;AAAA,EAAA,GACC,CAACb,CAAI,CAAC;AAET,QAAMc,IAAmBC;AAAA,IACvB,MAAMC,EAAoBf,KAAa,MAAS;AAAA,IAChD,CAACA,CAAS;AAAA,EACZ,GAEMgB,IAAaC;AAAA,IACjB,CAACC,MAAyD;AACxD,MAAAX,EAAY,EAAI,GAChBN,IAASiB,CAAK;AAAA,IAChB;AAAA,IACA,CAACjB,CAAM;AAAA,EACT,GAGMkB,IAAWL,EAAQ,MACnBL,KACGW,EAAmBtB,CAAG,GAC5B,CAACW,GAASX,CAAG,CAAC;AAGf,SAAA,gBAAAuB;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,EAAG,oBAAoBpB,CAAS;AAAA,MAC3C,OAAO;AAAA,QACL,GAAGC;AAAA,QACH,aAAAC;AAAA,QACA,iBAAiBS,IACb,OAAOA,CAAgB,MACvB;AAAA,QACJ,gBAAgB;AAAA,QAChB,oBAAoB;AAAA,MACtB;AAAA,MAEA,UAAA,gBAAAQ;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAWC;AAAA,YACT;AAAA,YACAhB,KAAY;AAAA,UACd;AAAA,UACA,KAAKa;AAAA,UACL,QAAQH;AAAA,UACP,GAAGX;AAAA,QAAA;AAAA,MAAA;AAAA,IACN;AAAA,EACF;AAEJ,CAAC;"}
1
+ {"version":3,"file":"image.js","sources":["../../../src/components/atoms/image.tsx"],"sourcesContent":["/* eslint-disable jsx-a11y/alt-text */\n/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */\nimport {\n ImgHTMLAttributes,\n useCallback,\n useMemo,\n memo,\n useState,\n useEffect,\n} from 'react'\n\nimport {cn} from '../../lib/utils'\nimport {getThumbhashDataURL, getResizedImageUrl} from '../../utils'\n\ntype ImageProps = ImgHTMLAttributes<HTMLImageElement> & {\n src?: string\n file?: File\n thumbhash?: string | null\n aspectRatio?: number | string\n objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | 'none'\n}\n\nexport const Image = memo(function Image(props: ImageProps) {\n const {\n src,\n file,\n thumbhash,\n onLoad,\n className,\n style,\n aspectRatio = 'auto',\n objectFit = 'contain',\n ...restProps\n } = props\n\n const [isLoaded, setIsLoaded] = useState(false)\n const [blobUrl, setBlobUrl] = useState<string | null>(null)\n\n // Create and manage blob URL for File objects\n useEffect(() => {\n if (!file) {\n setBlobUrl(null)\n return\n }\n\n const url = URL.createObjectURL(file)\n setBlobUrl(url)\n\n // Cleanup on unmount or when file changes\n return () => {\n URL.revokeObjectURL(url)\n }\n }, [file])\n\n const thumbhashDataURL = useMemo(\n () => getThumbhashDataURL(thumbhash ?? undefined),\n [thumbhash]\n )\n\n const handleLoad = useCallback(\n (event: React.SyntheticEvent<HTMLImageElement, Event>) => {\n setIsLoaded(true)\n onLoad?.(event)\n },\n [onLoad]\n )\n\n // Use blob URL if file is provided, otherwise use src\n const imageSrc = useMemo(() => {\n if (blobUrl) return blobUrl\n return getResizedImageUrl(src)\n }, [blobUrl, src])\n\n return (\n <div\n className={cn('relative w-full', className)}\n style={{\n ...style,\n ...(aspectRatio !== 'auto' && {aspectRatio}),\n backgroundImage: thumbhashDataURL\n ? `url(${thumbhashDataURL})`\n : undefined,\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n }}\n >\n <img\n className={cn(\n aspectRatio === 'auto'\n ? 'opacity-0 w-full h-auto'\n : 'absolute inset-0 opacity-0 size-full',\n `object-${objectFit}`,\n isLoaded && 'opacity-100'\n )}\n src={imageSrc}\n onLoad={handleLoad}\n {...restProps}\n />\n </div>\n )\n})\n"],"names":["Image","memo","props","src","file","thumbhash","onLoad","className","style","aspectRatio","objectFit","restProps","isLoaded","setIsLoaded","useState","blobUrl","setBlobUrl","useEffect","url","thumbhashDataURL","useMemo","getThumbhashDataURL","handleLoad","useCallback","event","imageSrc","getResizedImageUrl","jsx","cn"],"mappings":";;;;AAsBO,MAAMA,IAAQC,EAAK,SAAeC,GAAmB;AACpD,QAAA;AAAA,IACJ,KAAAC;AAAA,IACA,MAAAC;AAAA,IACA,WAAAC;AAAA,IACA,QAAAC;AAAA,IACA,WAAAC;AAAA,IACA,OAAAC;AAAA,IACA,aAAAC,IAAc;AAAA,IACd,WAAAC,IAAY;AAAA,IACZ,GAAGC;AAAA,EAAA,IACDT,GAEE,CAACU,GAAUC,CAAW,IAAIC,EAAS,EAAK,GACxC,CAACC,GAASC,CAAU,IAAIF,EAAwB,IAAI;AAG1D,EAAAG,EAAU,MAAM;AACd,QAAI,CAACb,GAAM;AACT,MAAAY,EAAW,IAAI;AACf;AAAA,IAAA;AAGI,UAAAE,IAAM,IAAI,gBAAgBd,CAAI;AACpC,WAAAY,EAAWE,CAAG,GAGP,MAAM;AACX,UAAI,gBAAgBA,CAAG;AAAA,IACzB;AAAA,EAAA,GACC,CAACd,CAAI,CAAC;AAET,QAAMe,IAAmBC;AAAA,IACvB,MAAMC,EAAoBhB,KAAa,MAAS;AAAA,IAChD,CAACA,CAAS;AAAA,EACZ,GAEMiB,IAAaC;AAAA,IACjB,CAACC,MAAyD;AACxD,MAAAX,EAAY,EAAI,GAChBP,IAASkB,CAAK;AAAA,IAChB;AAAA,IACA,CAAClB,CAAM;AAAA,EACT,GAGMmB,IAAWL,EAAQ,MACnBL,KACGW,EAAmBvB,CAAG,GAC5B,CAACY,GAASZ,CAAG,CAAC;AAGf,SAAA,gBAAAwB;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,EAAG,mBAAmBrB,CAAS;AAAA,MAC1C,OAAO;AAAA,QACL,GAAGC;AAAA,QACH,GAAIC,MAAgB,UAAU,EAAC,aAAAA,EAAW;AAAA,QAC1C,iBAAiBU,IACb,OAAOA,CAAgB,MACvB;AAAA,QACJ,gBAAgB;AAAA,QAChB,oBAAoB;AAAA,MACtB;AAAA,MAEA,UAAA,gBAAAQ;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAWC;AAAA,YACTnB,MAAgB,SACZ,4BACA;AAAA,YACJ,UAAUC,CAAS;AAAA,YACnBE,KAAY;AAAA,UACd;AAAA,UACA,KAAKa;AAAA,UACL,QAAQH;AAAA,UACP,GAAGX;AAAA,QAAA;AAAA,MAAA;AAAA,IACN;AAAA,EACF;AAEJ,CAAC;"}
@@ -1,23 +1,23 @@
1
- import { useMemo as e } from "react";
2
- import { parseUrl as s } from "../../utils/parseUrl.js";
3
- const t = () => {
4
- const { initialUrl: r } = window.minisParams;
5
- return e(() => {
1
+ import { useMemo as i } from "react";
2
+ import { parseUrl as n } from "../../utils/parseUrl.js";
3
+ const m = () => {
4
+ const { initialUrl: r, handle: e } = window.minisParams;
5
+ return i(() => {
6
6
  if (!r)
7
7
  return {
8
8
  path: void 0,
9
9
  queryParams: void 0,
10
10
  hash: void 0
11
11
  };
12
- const a = s(r);
12
+ const a = n(r), t = `/mini/${e}`;
13
13
  return {
14
- path: a.pathname,
14
+ path: a.pathname.startsWith(t) ? a.pathname.replace(t, "") : a.pathname,
15
15
  queryParams: a.query,
16
16
  hash: a.hash
17
17
  };
18
- }, [r]);
18
+ }, [e, r]);
19
19
  };
20
20
  export {
21
- t as useDeeplink
21
+ m as useDeeplink
22
22
  };
23
23
  //# sourceMappingURL=useDeeplink.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useDeeplink.js","sources":["../../../src/hooks/navigation/useDeeplink.ts"],"sourcesContent":["import {useMemo} from 'react'\n\nimport {parseUrl} from '../../utils/parseUrl'\n\ninterface UseDeeplinkReturnType {\n /**\n * The path of the deeplink.\n */\n path?: string\n /**\n * The query parameters of the deeplink.\n */\n queryParams?: {[key: string]: string | undefined}\n /**\n * The hash of the deeplink.\n */\n hash?: string\n}\n\nexport const useDeeplink = (): UseDeeplinkReturnType => {\n const {initialUrl} = window.minisParams\n\n return useMemo(() => {\n if (!initialUrl) {\n return {\n path: undefined,\n queryParams: undefined,\n hash: undefined,\n }\n }\n\n const parsedUrl = parseUrl(initialUrl)\n\n return {\n path: parsedUrl.pathname,\n queryParams: parsedUrl.query,\n hash: parsedUrl.hash,\n }\n }, [initialUrl])\n}\n"],"names":["useDeeplink","initialUrl","useMemo","parsedUrl","parseUrl"],"mappings":";;AAmBO,MAAMA,IAAc,MAA6B;AAChD,QAAA,EAAC,YAAAC,MAAc,OAAO;AAE5B,SAAOC,EAAQ,MAAM;AACnB,QAAI,CAACD;AACI,aAAA;AAAA,QACL,MAAM;AAAA,QACN,aAAa;AAAA,QACb,MAAM;AAAA,MACR;AAGI,UAAAE,IAAYC,EAASH,CAAU;AAE9B,WAAA;AAAA,MACL,MAAME,EAAU;AAAA,MAChB,aAAaA,EAAU;AAAA,MACvB,MAAMA,EAAU;AAAA,IAClB;AAAA,EAAA,GACC,CAACF,CAAU,CAAC;AACjB;"}
1
+ {"version":3,"file":"useDeeplink.js","sources":["../../../src/hooks/navigation/useDeeplink.ts"],"sourcesContent":["import {useMemo} from 'react'\n\nimport {parseUrl} from '../../utils/parseUrl'\n\ninterface UseDeeplinkReturnType {\n /**\n * The path of the deeplink.\n */\n path?: string\n /**\n * The query parameters of the deeplink.\n */\n queryParams?: {[key: string]: string | undefined}\n /**\n * The hash of the deeplink.\n */\n hash?: string\n}\n\nexport const useDeeplink = (): UseDeeplinkReturnType => {\n const {initialUrl, handle} = window.minisParams\n\n return useMemo(() => {\n if (!initialUrl) {\n return {\n path: undefined,\n queryParams: undefined,\n hash: undefined,\n }\n }\n\n const parsedUrl = parseUrl(initialUrl)\n const deeplinkPathnamePrefix = `/mini/${handle}`\n\n return {\n path: parsedUrl.pathname.startsWith(deeplinkPathnamePrefix)\n ? parsedUrl.pathname.replace(deeplinkPathnamePrefix, '')\n : parsedUrl.pathname,\n queryParams: parsedUrl.query,\n hash: parsedUrl.hash,\n }\n }, [handle, initialUrl])\n}\n"],"names":["useDeeplink","initialUrl","handle","useMemo","parsedUrl","parseUrl","deeplinkPathnamePrefix"],"mappings":";;AAmBO,MAAMA,IAAc,MAA6B;AACtD,QAAM,EAAC,YAAAC,GAAY,QAAAC,EAAM,IAAI,OAAO;AAEpC,SAAOC,EAAQ,MAAM;AACnB,QAAI,CAACF;AACI,aAAA;AAAA,QACL,MAAM;AAAA,QACN,aAAa;AAAA,QACb,MAAM;AAAA,MACR;AAGI,UAAAG,IAAYC,EAASJ,CAAU,GAC/BK,IAAyB,SAASJ,CAAM;AAEvC,WAAA;AAAA,MACL,MAAME,EAAU,SAAS,WAAWE,CAAsB,IACtDF,EAAU,SAAS,QAAQE,GAAwB,EAAE,IACrDF,EAAU;AAAA,MACd,aAAaA,EAAU;AAAA,MACvB,MAAMA,EAAU;AAAA,IAClB;AAAA,EAAA,GACC,CAACF,GAAQD,CAAU,CAAC;AACzB;"}
@@ -1,109 +1,108 @@
1
- import { jsxs as R, jsx as p } from "react/jsx-runtime";
2
- import { useRef as o, useCallback as s, useEffect as I, useMemo as b, createContext as A, useContext as L } from "react";
3
- import { useRequestPermissions as M } from "../hooks/util/useRequestPermissions.js";
4
- const v = A(null);
5
- function U() {
6
- const f = L(v);
1
+ import { jsxs as x, jsx as m } from "react/jsx-runtime";
2
+ import { useRef as i, useCallback as s, useEffect as R, useMemo as I, createContext as b, useContext as A } from "react";
3
+ import { useRequestPermissions as L } from "../hooks/util/useRequestPermissions.js";
4
+ const k = b(null);
5
+ function H() {
6
+ const f = A(k);
7
7
  if (!f)
8
8
  throw new Error(
9
9
  "useImagePickerContext must be used within an ImagePickerProvider"
10
10
  );
11
11
  return f;
12
12
  }
13
- const E = () => window?.minisParams?.platform === "android";
14
- function F({ children: f }) {
15
- const C = o(null), h = o(null), g = o(null), n = o(null), e = o(null), a = o(null), { requestPermission: d } = M(), t = s(() => {
13
+ function U({ children: f }) {
14
+ const C = i(null), h = i(null), g = i(null), n = i(null), e = i(null), a = i(null), { requestPermission: p } = L(), r = s(() => {
16
15
  if (a.current) {
17
- const { input: l, handler: c } = a.current;
18
- l.removeEventListener("cancel", c), a.current = null;
16
+ const { input: u, handler: c } = a.current;
17
+ u.removeEventListener("cancel", c), a.current = null;
19
18
  }
20
- }, []), i = s(() => {
19
+ }, []), o = s(() => {
21
20
  e.current && (e.current(
22
21
  new Error("New file picker opened before previous completed")
23
22
  ), n.current = null, e.current = null);
24
- }, []), m = s(
25
- (l) => {
26
- const c = l.target.files?.[0];
27
- c && n.current && (n.current(c), n.current = null, e.current = null, t()), l.target.value = "";
23
+ }, []), d = s(
24
+ (u) => {
25
+ const c = u.target.files?.[0];
26
+ c && n.current && (n.current(c), n.current = null, e.current = null, r()), u.target.value = "";
28
27
  },
29
- [t]
30
- ), w = s(() => new Promise((l, c) => {
31
- i(), t(), n.current = l, e.current = c;
32
- const r = C.current;
33
- if (!r) {
28
+ [r]
29
+ ), E = s(() => new Promise((u, c) => {
30
+ o(), r(), n.current = u, e.current = c;
31
+ const t = C.current;
32
+ if (!t) {
34
33
  c(new Error("Gallery input not found")), n.current = null, e.current = null;
35
34
  return;
36
35
  }
37
- const u = () => {
38
- e.current && (e.current(new Error("User cancelled file selection")), n.current = null, e.current = null), t();
36
+ const l = () => {
37
+ e.current && (e.current(new Error("User cancelled file selection")), n.current = null, e.current = null), r();
39
38
  };
40
- r.addEventListener("cancel", u), a.current = { input: r, handler: u }, E() ? d({ permission: "CAMERA" }).then(() => {
41
- r.click();
39
+ t.addEventListener("cancel", l), a.current = { input: t, handler: l }, p({ permission: "CAMERA" }).then(() => {
40
+ t.click();
42
41
  }).catch(() => {
43
- r.click();
44
- }) : r.click();
45
- }), [i, t, d]), P = s(
46
- (l = "back") => new Promise((c, r) => {
47
- i(), t(), n.current = c, e.current = r;
48
- const u = l === "front" ? h.current : g.current;
49
- if (!u) {
50
- r(new Error("Camera input not found")), n.current = null, e.current = null;
42
+ t.click();
43
+ });
44
+ }), [o, r, p]), P = s(
45
+ (u = "back") => new Promise((c, t) => {
46
+ o(), r(), n.current = c, e.current = t;
47
+ const l = u === "front" ? h.current : g.current;
48
+ if (!l) {
49
+ t(new Error("Camera input not found")), n.current = null, e.current = null;
51
50
  return;
52
51
  }
53
- const k = () => {
54
- e.current && (e.current(new Error("User cancelled camera")), n.current = null, e.current = null), t();
52
+ const w = () => {
53
+ e.current && (e.current(new Error("User cancelled camera")), n.current = null, e.current = null), r();
55
54
  };
56
- u.addEventListener("cancel", k), a.current = { input: u, handler: k }, E() ? d({ permission: "CAMERA" }).then(({ granted: x }) => {
57
- x ? u.click() : (r(new Error("Camera permission not granted")), n.current = null, e.current = null);
55
+ l.addEventListener("cancel", w), a.current = { input: l, handler: w }, p({ permission: "CAMERA" }).then(({ granted: y }) => {
56
+ y ? l.click() : (t(new Error("Camera permission not granted")), n.current = null, e.current = null);
58
57
  }).catch(() => {
59
- r(new Error("Camera permission not granted")), n.current = null, e.current = null;
60
- }) : u.click();
58
+ t(new Error("Camera permission not granted")), n.current = null, e.current = null;
59
+ });
61
60
  }),
62
- [i, t, d]
61
+ [o, r, p]
63
62
  );
64
- I(() => () => {
65
- i(), t();
66
- }, [i, t]);
67
- const y = b(
63
+ R(() => () => {
64
+ o(), r();
65
+ }, [o, r]);
66
+ const v = I(
68
67
  () => ({
69
68
  openCamera: P,
70
- openGallery: w
69
+ openGallery: E
71
70
  }),
72
- [P, w]
71
+ [P, E]
73
72
  );
74
- return /* @__PURE__ */ R(v.Provider, { value: y, children: [
73
+ return /* @__PURE__ */ x(k.Provider, { value: v, children: [
75
74
  f,
76
- /* @__PURE__ */ p(
75
+ /* @__PURE__ */ m(
77
76
  "input",
78
77
  {
79
78
  ref: C,
80
79
  type: "file",
81
80
  accept: "image/*",
82
- onChange: m,
81
+ onChange: d,
83
82
  style: { display: "none" },
84
83
  "aria-hidden": "true"
85
84
  }
86
85
  ),
87
- /* @__PURE__ */ p(
86
+ /* @__PURE__ */ m(
88
87
  "input",
89
88
  {
90
89
  ref: h,
91
90
  type: "file",
92
91
  accept: "image/*",
93
92
  capture: "user",
94
- onChange: m,
93
+ onChange: d,
95
94
  style: { display: "none" },
96
95
  "aria-hidden": "true"
97
96
  }
98
97
  ),
99
- /* @__PURE__ */ p(
98
+ /* @__PURE__ */ m(
100
99
  "input",
101
100
  {
102
101
  ref: g,
103
102
  type: "file",
104
103
  accept: "image/*",
105
104
  capture: "environment",
106
- onChange: m,
105
+ onChange: d,
107
106
  style: { display: "none" },
108
107
  "aria-hidden": "true"
109
108
  }
@@ -111,7 +110,7 @@ function F({ children: f }) {
111
110
  ] });
112
111
  }
113
112
  export {
114
- F as ImagePickerProvider,
115
- U as useImagePickerContext
113
+ U as ImagePickerProvider,
114
+ H as useImagePickerContext
116
115
  };
117
116
  //# sourceMappingURL=ImagePickerProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ImagePickerProvider.js","sources":["../../src/providers/ImagePickerProvider.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n useRef,\n useCallback,\n useEffect,\n useMemo,\n} from 'react'\n\nimport {useRequestPermissions} from '../hooks/util/useRequestPermissions'\n\nexport type CameraFacing = 'front' | 'back'\n\ninterface ImagePickerContextValue {\n openCamera: (cameraFacing?: CameraFacing) => Promise<File>\n openGallery: () => Promise<File>\n}\n\nconst ImagePickerContext = createContext<ImagePickerContextValue | null>(null)\n\nexport function useImagePickerContext() {\n const context = useContext(ImagePickerContext)\n if (!context) {\n throw new Error(\n 'useImagePickerContext must be used within an ImagePickerProvider'\n )\n }\n return context\n}\n\ninterface ImagePickerProviderProps {\n children: React.ReactNode\n}\n\nconst isAndroid = () => window?.minisParams?.platform === 'android'\n\nexport function ImagePickerProvider({children}: ImagePickerProviderProps) {\n const galleryInputRef = useRef<HTMLInputElement>(null)\n const frontCameraInputRef = useRef<HTMLInputElement>(null)\n const backCameraInputRef = useRef<HTMLInputElement>(null)\n const resolveRef = useRef<((file: File) => void) | null>(null)\n const rejectRef = useRef<((reason: Error) => void) | null>(null)\n const activeCancelHandlerRef = useRef<{\n input: HTMLInputElement\n handler: () => void\n } | null>(null)\n\n const {requestPermission} = useRequestPermissions()\n\n const cleanupCancelHandler = useCallback(() => {\n if (activeCancelHandlerRef.current) {\n const {input, handler} = activeCancelHandlerRef.current\n input.removeEventListener('cancel', handler)\n activeCancelHandlerRef.current = null\n }\n }, [])\n\n const rejectPendingPromise = useCallback(() => {\n if (rejectRef.current) {\n rejectRef.current(\n new Error('New file picker opened before previous completed')\n )\n resolveRef.current = null\n rejectRef.current = null\n }\n }, [])\n\n const handleFileChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0]\n\n if (file && resolveRef.current) {\n resolveRef.current(file)\n\n resolveRef.current = null\n rejectRef.current = null\n\n cleanupCancelHandler()\n }\n\n event.target.value = ''\n },\n [cleanupCancelHandler]\n )\n\n const openGallery = useCallback(() => {\n return new Promise<File>((resolve, reject) => {\n rejectPendingPromise()\n cleanupCancelHandler()\n\n resolveRef.current = resolve\n rejectRef.current = reject\n\n const input = galleryInputRef.current\n\n if (!input) {\n reject(new Error('Gallery input not found'))\n resolveRef.current = null\n rejectRef.current = null\n return\n }\n\n const handleCancel = () => {\n if (rejectRef.current) {\n rejectRef.current(new Error('User cancelled file selection'))\n resolveRef.current = null\n rejectRef.current = null\n }\n cleanupCancelHandler()\n }\n\n input.addEventListener('cancel', handleCancel)\n activeCancelHandlerRef.current = {input, handler: handleCancel}\n\n if (isAndroid()) {\n // Android requires explicit camera permission for camera picker\n requestPermission({permission: 'CAMERA'})\n .then(() => {\n // This will show both Camera and Gallery\n input.click()\n })\n .catch(() => {\n // Show only Gallery\n input.click()\n })\n } else {\n input.click()\n }\n })\n }, [rejectPendingPromise, cleanupCancelHandler, requestPermission])\n\n const openCamera = useCallback(\n (cameraFacing: CameraFacing = 'back') => {\n return new Promise<File>((resolve, reject) => {\n rejectPendingPromise()\n cleanupCancelHandler()\n\n resolveRef.current = resolve\n rejectRef.current = reject\n\n const input =\n cameraFacing === 'front'\n ? frontCameraInputRef.current\n : backCameraInputRef.current\n\n if (!input) {\n reject(new Error('Camera input not found'))\n resolveRef.current = null\n rejectRef.current = null\n return\n }\n\n const handleCancel = () => {\n if (rejectRef.current) {\n rejectRef.current(new Error('User cancelled camera'))\n resolveRef.current = null\n rejectRef.current = null\n }\n cleanupCancelHandler()\n }\n\n input.addEventListener('cancel', handleCancel)\n activeCancelHandlerRef.current = {input, handler: handleCancel}\n\n if (isAndroid()) {\n // Android requires explicit camera permission\n requestPermission({permission: 'CAMERA'})\n .then(({granted}) => {\n if (granted) {\n input.click()\n } else {\n reject(new Error('Camera permission not granted'))\n resolveRef.current = null\n rejectRef.current = null\n }\n })\n .catch(() => {\n reject(new Error('Camera permission not granted'))\n resolveRef.current = null\n rejectRef.current = null\n })\n } else {\n input.click()\n }\n })\n },\n [rejectPendingPromise, cleanupCancelHandler, requestPermission]\n )\n\n useEffect(() => {\n return () => {\n rejectPendingPromise()\n cleanupCancelHandler()\n }\n }, [rejectPendingPromise, cleanupCancelHandler])\n\n const contextValue: ImagePickerContextValue = useMemo(\n () => ({\n openCamera,\n openGallery,\n }),\n [openCamera, openGallery]\n )\n\n return (\n <ImagePickerContext.Provider value={contextValue}>\n {children}\n <input\n ref={galleryInputRef}\n type=\"file\"\n accept=\"image/*\"\n onChange={handleFileChange}\n style={{display: 'none'}}\n aria-hidden=\"true\"\n />\n <input\n ref={frontCameraInputRef}\n type=\"file\"\n accept=\"image/*\"\n capture=\"user\"\n onChange={handleFileChange}\n style={{display: 'none'}}\n aria-hidden=\"true\"\n />\n <input\n ref={backCameraInputRef}\n type=\"file\"\n accept=\"image/*\"\n capture=\"environment\"\n onChange={handleFileChange}\n style={{display: 'none'}}\n aria-hidden=\"true\"\n />\n </ImagePickerContext.Provider>\n )\n}\n"],"names":["ImagePickerContext","createContext","useImagePickerContext","context","useContext","isAndroid","ImagePickerProvider","children","galleryInputRef","useRef","frontCameraInputRef","backCameraInputRef","resolveRef","rejectRef","activeCancelHandlerRef","requestPermission","useRequestPermissions","cleanupCancelHandler","useCallback","input","handler","rejectPendingPromise","handleFileChange","event","file","openGallery","resolve","reject","handleCancel","openCamera","cameraFacing","granted","useEffect","contextValue","useMemo","jsxs","jsx"],"mappings":"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"}
1
+ {"version":3,"file":"ImagePickerProvider.js","sources":["../../src/providers/ImagePickerProvider.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n useRef,\n useCallback,\n useEffect,\n useMemo,\n} from 'react'\n\nimport {useRequestPermissions} from '../hooks/util/useRequestPermissions'\n\nexport type CameraFacing = 'front' | 'back'\n\ninterface ImagePickerContextValue {\n openCamera: (cameraFacing?: CameraFacing) => Promise<File>\n openGallery: () => Promise<File>\n}\n\nconst ImagePickerContext = createContext<ImagePickerContextValue | null>(null)\n\nexport function useImagePickerContext() {\n const context = useContext(ImagePickerContext)\n if (!context) {\n throw new Error(\n 'useImagePickerContext must be used within an ImagePickerProvider'\n )\n }\n return context\n}\n\ninterface ImagePickerProviderProps {\n children: React.ReactNode\n}\n\nexport function ImagePickerProvider({children}: ImagePickerProviderProps) {\n const galleryInputRef = useRef<HTMLInputElement>(null)\n const frontCameraInputRef = useRef<HTMLInputElement>(null)\n const backCameraInputRef = useRef<HTMLInputElement>(null)\n const resolveRef = useRef<((file: File) => void) | null>(null)\n const rejectRef = useRef<((reason: Error) => void) | null>(null)\n const activeCancelHandlerRef = useRef<{\n input: HTMLInputElement\n handler: () => void\n } | null>(null)\n\n const {requestPermission} = useRequestPermissions()\n\n const cleanupCancelHandler = useCallback(() => {\n if (activeCancelHandlerRef.current) {\n const {input, handler} = activeCancelHandlerRef.current\n input.removeEventListener('cancel', handler)\n activeCancelHandlerRef.current = null\n }\n }, [])\n\n const rejectPendingPromise = useCallback(() => {\n if (rejectRef.current) {\n rejectRef.current(\n new Error('New file picker opened before previous completed')\n )\n resolveRef.current = null\n rejectRef.current = null\n }\n }, [])\n\n const handleFileChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0]\n\n if (file && resolveRef.current) {\n resolveRef.current(file)\n\n resolveRef.current = null\n rejectRef.current = null\n\n cleanupCancelHandler()\n }\n\n event.target.value = ''\n },\n [cleanupCancelHandler]\n )\n\n const openGallery = useCallback(() => {\n return new Promise<File>((resolve, reject) => {\n rejectPendingPromise()\n cleanupCancelHandler()\n\n resolveRef.current = resolve\n rejectRef.current = reject\n\n const input = galleryInputRef.current\n\n if (!input) {\n reject(new Error('Gallery input not found'))\n resolveRef.current = null\n rejectRef.current = null\n return\n }\n\n const handleCancel = () => {\n if (rejectRef.current) {\n rejectRef.current(new Error('User cancelled file selection'))\n resolveRef.current = null\n rejectRef.current = null\n }\n cleanupCancelHandler()\n }\n\n input.addEventListener('cancel', handleCancel)\n activeCancelHandlerRef.current = {input, handler: handleCancel}\n\n requestPermission({permission: 'CAMERA'})\n .then(() => {\n // This will show both Camera and Gallery\n input.click()\n })\n .catch(() => {\n // Show only Gallery\n input.click()\n })\n })\n }, [rejectPendingPromise, cleanupCancelHandler, requestPermission])\n\n const openCamera = useCallback(\n (cameraFacing: CameraFacing = 'back') => {\n return new Promise<File>((resolve, reject) => {\n rejectPendingPromise()\n cleanupCancelHandler()\n\n resolveRef.current = resolve\n rejectRef.current = reject\n\n const input =\n cameraFacing === 'front'\n ? frontCameraInputRef.current\n : backCameraInputRef.current\n\n if (!input) {\n reject(new Error('Camera input not found'))\n resolveRef.current = null\n rejectRef.current = null\n return\n }\n\n const handleCancel = () => {\n if (rejectRef.current) {\n rejectRef.current(new Error('User cancelled camera'))\n resolveRef.current = null\n rejectRef.current = null\n }\n cleanupCancelHandler()\n }\n\n input.addEventListener('cancel', handleCancel)\n activeCancelHandlerRef.current = {input, handler: handleCancel}\n\n requestPermission({permission: 'CAMERA'})\n .then(({granted}) => {\n if (granted) {\n input.click()\n } else {\n reject(new Error('Camera permission not granted'))\n resolveRef.current = null\n rejectRef.current = null\n }\n })\n .catch(() => {\n reject(new Error('Camera permission not granted'))\n resolveRef.current = null\n rejectRef.current = null\n })\n })\n },\n [rejectPendingPromise, cleanupCancelHandler, requestPermission]\n )\n\n useEffect(() => {\n return () => {\n rejectPendingPromise()\n cleanupCancelHandler()\n }\n }, [rejectPendingPromise, cleanupCancelHandler])\n\n const contextValue: ImagePickerContextValue = useMemo(\n () => ({\n openCamera,\n openGallery,\n }),\n [openCamera, openGallery]\n )\n\n return (\n <ImagePickerContext.Provider value={contextValue}>\n {children}\n <input\n ref={galleryInputRef}\n type=\"file\"\n accept=\"image/*\"\n onChange={handleFileChange}\n style={{display: 'none'}}\n aria-hidden=\"true\"\n />\n <input\n ref={frontCameraInputRef}\n type=\"file\"\n accept=\"image/*\"\n capture=\"user\"\n onChange={handleFileChange}\n style={{display: 'none'}}\n aria-hidden=\"true\"\n />\n <input\n ref={backCameraInputRef}\n type=\"file\"\n accept=\"image/*\"\n capture=\"environment\"\n onChange={handleFileChange}\n style={{display: 'none'}}\n aria-hidden=\"true\"\n />\n </ImagePickerContext.Provider>\n )\n}\n"],"names":["ImagePickerContext","createContext","useImagePickerContext","context","useContext","ImagePickerProvider","children","galleryInputRef","useRef","frontCameraInputRef","backCameraInputRef","resolveRef","rejectRef","activeCancelHandlerRef","requestPermission","useRequestPermissions","cleanupCancelHandler","useCallback","input","handler","rejectPendingPromise","handleFileChange","event","file","openGallery","resolve","reject","handleCancel","openCamera","cameraFacing","granted","useEffect","contextValue","useMemo","jsxs","jsx"],"mappings":"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"}
@@ -0,0 +1,201 @@
1
+ # Shop Minis ESLint Plugin
2
+
3
+ Custom ESLint rules for Shop Minis apps. ESLint is included with the SDK.
4
+
5
+ ## Quick Setup
6
+
7
+ Create **`eslint.config.js`** (NOT `.eslintrc.js`):
8
+
9
+ ```javascript
10
+ const shopMinisConfig = require('@shopify/shop-minis-react/eslint/config')
11
+
12
+ module.exports = [shopMinisConfig]
13
+ ```
14
+
15
+ **That's it!** TypeScript and JSX are supported out of the box.
16
+
17
+ **Important:**
18
+ - File must be named `eslint.config.js` (no dot, no "rc")
19
+ - This will lint all `.js`, `.jsx`, `.ts`, `.tsx` files in your project
20
+ - TypeScript and JSX parsing is configured automatically
21
+
22
+ ## Usage
23
+
24
+ ```bash
25
+ # Check for errors
26
+ npx eslint .
27
+
28
+ # Auto-fix (converts <img> to <Image> AND adds import!)
29
+ npx eslint . --fix
30
+ ```
31
+
32
+ ## What You Get
33
+
34
+ - ✅ No internal imports allowed
35
+ - ✅ Warnings for `<img>`, `<button>`, `<label>` tags (auto-fixes with imports)
36
+ - ✅ Manifest scope validation - ensures manifest.json has scopes for hooks you use (auto-fixes manifest)
37
+
38
+ ## Rules
39
+
40
+ ### `no-internal-imports`
41
+
42
+ Prevents importing from internal SDK directories.
43
+
44
+ ```tsx
45
+ // ❌ Error
46
+ import {something} from '@shopify/shop-minis-react/internal'
47
+
48
+ // ✅ Correct
49
+ import {Component} from '@shopify/shop-minis-react'
50
+ ```
51
+
52
+ ### `prefer-sdk-components`
53
+
54
+ Suggests using SDK components instead of native HTML elements. **Fully auto-fixable** - fixes both tags and imports!
55
+
56
+ **Before:**
57
+ ```tsx
58
+ const MyComponent = () => (
59
+ <img src="product.jpg" alt="Product" />
60
+ )
61
+ ```
62
+
63
+ **After running `npx eslint . --fix`:**
64
+ ```tsx
65
+ import {Image} from '@shopify/shop-minis-react'
66
+
67
+ const MyComponent = () => (
68
+ <Image src="product.jpg" alt="Product" />
69
+ )
70
+ ```
71
+
72
+ **Supported Components:**
73
+ - `<img>` → `<Image>`
74
+ - `<button>` → `<Button>`
75
+ - `<label>` → `<Label>`
76
+
77
+ **Auto-fix does TWO things:**
78
+ 1. ✅ Replaces native element with SDK component
79
+ 2. ✅ Adds import statement automatically (or adds to existing import)
80
+
81
+ ### `validate-manifest`
82
+
83
+ Validates `src/manifest.json` configuration for scopes and permissions. **Auto-fixable** - adds missing values to manifest!
84
+
85
+ #### Scopes
86
+
87
+ Checks that hooks have required scopes:
88
+
89
+ ```tsx
90
+ // If you use this hook:
91
+ import {useCurrentUser} from '@shopify/shop-minis-react'
92
+
93
+ // Manifest must include:
94
+ {
95
+ "scopes": ["USER_SETTINGS_READ"]
96
+ }
97
+ ```
98
+
99
+ **Scope Requirements:**
100
+ - `useCurrentUser` → `USER_SETTINGS_READ`
101
+ - `useSavedProducts` → `FAVORITES`
102
+ - `useOrders` → `ORDERS`
103
+
104
+ #### Permissions
105
+
106
+ Checks for native permission usage:
107
+
108
+ ```tsx
109
+ // If you use this hook:
110
+ import {useImagePicker} from '@shopify/shop-minis-react'
111
+
112
+ // Or browser APIs:
113
+ navigator.mediaDevices.getUserMedia({video: true})
114
+
115
+ // Manifest must include:
116
+ {
117
+ "permissions": ["CAMERA"]
118
+ }
119
+ ```
120
+
121
+ **Supported Permissions:**
122
+ - `CAMERA` - Required for `useImagePicker` hook or getUserMedia video
123
+ - `MICROPHONE` - Required for getUserMedia audio
124
+ - `MOTION` - Required for DeviceOrientation/DeviceMotion events
125
+
126
+ #### Trusted Domains
127
+
128
+ Checks that external URLs are in trusted_domains. Detects:
129
+
130
+ **Network Requests:**
131
+ - `fetch('https://api.example.com/data')`
132
+ - `new XMLHttpRequest().open('GET', 'https://...')`
133
+ - `new WebSocket('wss://api.example.com')`
134
+ - `new EventSource('https://api.example.com/events')`
135
+ - `navigator.sendBeacon('https://analytics.example.com')`
136
+ - `window.open('https://external.com')`
137
+
138
+ **Media & Resources:**
139
+ - `<img src="https://cdn.shopify.com/image.jpg" />`
140
+ - `<video src="https://videos.example.com/video.mp4" />`
141
+ - `<video poster="https://cdn.example.com/poster.jpg" />`
142
+ - `<audio src="https://audio.example.com/sound.mp3" />`
143
+ - `<source src="https://media.example.com/video.mp4" />`
144
+ - `<track src="https://cdn.example.com/captions.vtt" />`
145
+ - `<object data="https://cdn.example.com/file.pdf" />`
146
+ - `<embed src="https://cdn.example.com/file.swf" />`
147
+ - `<form action="https://api.example.com/submit" />`
148
+
149
+ **Note:** External scripts (`<script>`), stylesheets (`<link>`), and iframes are not supported and excluded from validation.
150
+
151
+ **Example manifest:**
152
+ ```json
153
+ {
154
+ "trusted_domains": [
155
+ "api.example.com",
156
+ "cdn.shopify.com",
157
+ "videos.example.com"
158
+ ]
159
+ }
160
+ ```
161
+
162
+ **Wildcard support:**
163
+ ```json
164
+ {
165
+ "trusted_domains": [
166
+ "*.shopify.com", // Matches any Shopify subdomain
167
+ "api.example.com", // Exact domain
168
+ "cdn.example.com/assets" // Specific path
169
+ ]
170
+ }
171
+ ```
172
+
173
+ **Auto-fix:**
174
+ ```bash
175
+ npx eslint . --fix
176
+ # Automatically updates src/manifest.json
177
+ ```
178
+
179
+ **Example errors:**
180
+ ```
181
+ Hook "useCurrentUser" requires scope "USER_SETTINGS_READ" in src/manifest.json.
182
+ Hook "useImagePicker" requires permission "CAMERA" in src/manifest.json.
183
+ fetch() call loads from "api.example.com" which is not in trusted_domains.
184
+ <img> src attribute loads from "cdn.shopify.com" which is not in trusted_domains.
185
+ ```
186
+
187
+ ## Extending Rules
188
+
189
+ To add more component mappings to `prefer-sdk-components`, edit `eslint/rules/prefer-sdk-components.cjs`:
190
+
191
+ ```javascript
192
+ const defaultComponents = {
193
+ img: 'Image',
194
+ button: 'Button',
195
+ label: 'Label',
196
+ input: 'Input', // Add this
197
+ a: 'TransitionLink', // Add this
198
+ }
199
+ ```
200
+
201
+ All consumers automatically get new rules - no config changes needed!
@@ -0,0 +1,32 @@
1
+ /* eslint-disable import/extensions */
2
+ /**
3
+ * ESLint config for projects using @shopify/shop-minis-react
4
+ * @fileoverview Recommended ESLint configuration for Shop Minis apps
5
+ *
6
+ * This config uses ESLint flat config format (supported in ESLint 8.57+)
7
+ */
8
+
9
+ // Import the plugin directly so consumers don't need to install it separately
10
+ const shopMinisPlugin = require('./index.cjs')
11
+
12
+ module.exports = {
13
+ files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'],
14
+ languageOptions: {
15
+ parser: require('@typescript-eslint/parser'),
16
+ ecmaVersion: 'latest',
17
+ sourceType: 'module',
18
+ parserOptions: {
19
+ ecmaFeatures: {
20
+ jsx: true,
21
+ },
22
+ },
23
+ },
24
+ plugins: {
25
+ 'shop-minis': shopMinisPlugin,
26
+ },
27
+ rules: {
28
+ 'shop-minis/no-internal-imports': 'error',
29
+ 'shop-minis/prefer-sdk-components': 'warn',
30
+ 'shop-minis/validate-manifest': 'error',
31
+ },
32
+ }
@@ -0,0 +1,17 @@
1
+ /* eslint-disable import/extensions */
2
+ /**
3
+ * ESLint plugin for @shopify/shop-minis-react
4
+ * @fileoverview Custom ESLint rules for Shop Minis React SDK
5
+ */
6
+
7
+ const noInternalImports = require('./rules/no-internal-imports.cjs')
8
+ const preferSdkComponents = require('./rules/prefer-sdk-components.cjs')
9
+ const validateManifest = require('./rules/validate-manifest.cjs')
10
+
11
+ module.exports = {
12
+ rules: {
13
+ 'no-internal-imports': noInternalImports,
14
+ 'prefer-sdk-components': preferSdkComponents,
15
+ 'validate-manifest': validateManifest,
16
+ },
17
+ }