@shopify/shop-minis-react 0.0.0-snapshot.20251219164319 → 0.0.0-snapshot.20251222174301

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.
Files changed (47) hide show
  1. package/dist/components/navigation/minis-router.js +8 -15
  2. package/dist/components/navigation/minis-router.js.map +1 -1
  3. package/dist/hooks/intents/useBarcodeScanner.js +10 -0
  4. package/dist/hooks/intents/useBarcodeScanner.js.map +1 -0
  5. package/dist/hooks/intents/useContentShare.js +10 -0
  6. package/dist/hooks/intents/useContentShare.js.map +1 -0
  7. package/dist/hooks/intents/useImageCapture.js +10 -0
  8. package/dist/hooks/intents/useImageCapture.js.map +1 -0
  9. package/dist/hooks/intents/useLocationSelection.js +10 -0
  10. package/dist/hooks/intents/useLocationSelection.js.map +1 -0
  11. package/dist/hooks/intents/usePaymentRequest.js +10 -0
  12. package/dist/hooks/intents/usePaymentRequest.js.map +1 -0
  13. package/dist/hooks/intents/useProductSelection.js +10 -0
  14. package/dist/hooks/intents/useProductSelection.js.map +1 -0
  15. package/dist/hooks/navigation/useNavigateWithTransition.js +6 -6
  16. package/dist/hooks/navigation/useNavigateWithTransition.js.map +1 -1
  17. package/dist/index.js +131 -118
  18. package/dist/index.js.map +1 -1
  19. package/dist/internal/useHandleIntent.js +26 -0
  20. package/dist/internal/useHandleIntent.js.map +1 -0
  21. package/dist/internal/useShopIntents.js +7 -0
  22. package/dist/internal/useShopIntents.js.map +1 -0
  23. package/dist/mocks.js +10 -11
  24. package/dist/mocks.js.map +1 -1
  25. package/dist/shop-minis-platform/src/intents/shared.js +19 -0
  26. package/dist/shop-minis-platform/src/intents/shared.js.map +1 -0
  27. package/package.json +2 -2
  28. package/src/components/navigation/minis-router.tsx +1 -9
  29. package/src/hooks/index.ts +3 -1
  30. package/src/hooks/intents/index.ts +42 -0
  31. package/src/hooks/intents/useBarcodeScanner.ts +43 -0
  32. package/src/hooks/intents/useContentShare.ts +47 -0
  33. package/src/hooks/intents/useImageCapture.ts +45 -0
  34. package/src/hooks/intents/useLocationSelection.ts +45 -0
  35. package/src/hooks/intents/usePaymentRequest.ts +47 -0
  36. package/src/hooks/intents/useProductSelection.ts +45 -0
  37. package/src/hooks/navigation/useNavigateWithTransition.test.ts +3 -17
  38. package/src/hooks/navigation/useNavigateWithTransition.ts +4 -9
  39. package/src/internal/useHandleIntent.ts +101 -0
  40. package/src/internal/useShopIntents.ts +13 -0
  41. package/src/mocks.ts +0 -1
  42. package/dist/hooks/events/useOnNavigateBack.js +0 -14
  43. package/dist/hooks/events/useOnNavigateBack.js.map +0 -1
  44. package/dist/internal/navigation-manager.js +0 -28
  45. package/dist/internal/navigation-manager.js.map +0 -1
  46. package/src/hooks/events/useOnNavigateBack.ts +0 -16
  47. package/src/internal/navigation-manager.tsx +0 -41
@@ -0,0 +1,45 @@
1
+ import {
2
+ useHandleIntent,
3
+ HandleIntentOptions,
4
+ } from '../../internal/useHandleIntent'
5
+ import {useShopIntents} from '../../internal/useShopIntents'
6
+
7
+ import type {
8
+ SelectProductIntentParams,
9
+ SelectProductIntentResult,
10
+ } from '@shopify/shop-minis-platform'
11
+
12
+ /**
13
+ * Hook to request user to select products
14
+ *
15
+ * This intent shows a product selection screen where the user can browse
16
+ * and select one or more products from the Shop catalog.
17
+ *
18
+ * @example
19
+ * const selectProducts = useProductSelection({
20
+ * onCancel: () => console.log('User cancelled'),
21
+ * })
22
+ *
23
+ * const result = await selectProducts({
24
+ * multiSelect: true,
25
+ * maxSelections: 5,
26
+ * title: 'Choose your favorites'
27
+ * })
28
+ *
29
+ * if (result) {
30
+ * console.log('Selected products:', result.products)
31
+ * }
32
+ */
33
+ export const useProductSelection = (
34
+ options?: HandleIntentOptions<SelectProductIntentResult>
35
+ ) => {
36
+ const intents = useShopIntents()
37
+ return useHandleIntent(intents.selectProduct, options)
38
+ }
39
+
40
+ /**
41
+ * Type helper for the product selection function
42
+ */
43
+ export type ProductSelectionFunction = (
44
+ params: SelectProductIntentParams
45
+ ) => Promise<SelectProductIntentResult | void>
@@ -290,29 +290,15 @@ describe('useNavigateWithTransition', () => {
290
290
  })
291
291
  })
292
292
 
293
- it('handles navigation to root path using history delta', async () => {
294
- // Mock history state with idx
295
- const originalHistoryState = window.history.state
296
- Object.defineProperty(window.history, 'state', {
297
- value: {idx: 3},
298
- writable: true,
299
- configurable: true,
300
- })
301
-
293
+ it('handles navigation to root path', async () => {
302
294
  const {result} = renderHook(() => useNavigateWithTransition())
303
295
 
304
296
  await act(async () => {
305
297
  result.current('/')
306
298
  })
307
299
 
308
- // Should navigate back using delta based on history index
309
- expect(mockNavigate).toHaveBeenCalledWith(-3)
310
-
311
- // Restore original state
312
- Object.defineProperty(window.history, 'state', {
313
- value: originalHistoryState,
314
- writable: true,
315
- configurable: true,
300
+ expect(mockNavigate).toHaveBeenCalledWith('/', {
301
+ replace: false,
316
302
  })
317
303
  })
318
304
 
@@ -38,19 +38,14 @@ export function useNavigateWithTransition(): UseNavigateWithTransitionReturns {
38
38
  }
39
39
 
40
40
  const isSameRoute = to === location.pathname
41
- const isNavigatingToHomeRoute = to === '/'
42
41
 
43
42
  // Path navigation - with options
44
43
  if (document.startViewTransition) {
45
44
  const transition = document.startViewTransition(() => {
46
- if (isNavigatingToHomeRoute) {
47
- navigate(-window.history.state.idx)
48
- } else {
49
- navigate(to, {
50
- replace: isSameRoute,
51
- ...options,
52
- })
53
- }
45
+ navigate(to, {
46
+ replace: isSameRoute,
47
+ ...options,
48
+ })
54
49
 
55
50
  if (options?.preventScrollReset !== true) {
56
51
  window.scrollTo(0, 0)
@@ -0,0 +1,101 @@
1
+ import {useCallback} from 'react'
2
+
3
+ import {IntentResultCode} from '@shopify/shop-minis-platform'
4
+
5
+ import type {ShopIntentResult} from '@shopify/shop-minis-platform'
6
+
7
+ /**
8
+ * Options for handling intent results
9
+ */
10
+ export interface HandleIntentOptions<T> {
11
+ /**
12
+ * Handler called when user cancels the intent
13
+ * If not provided, cancellation is treated as a no-op (returns undefined)
14
+ */
15
+ onCancel?: () => T | void
16
+ /**
17
+ * Handler called when intent fails
18
+ * If not provided, throws the error
19
+ */
20
+ onError?: (error: {message: string; code?: string}) => T | void
21
+ }
22
+
23
+ /**
24
+ * Wrapper to handle intent results
25
+ *
26
+ * Unlike actions which can reject, intents ALWAYS resolve with a result code.
27
+ * This hook provides a cleaner API for handling intent results:
28
+ *
29
+ * - SUCCESS: Returns the data
30
+ * - CANCELLED: Calls onCancel or returns undefined
31
+ * - FAILED: Calls onError or throws the error
32
+ *
33
+ * @example
34
+ * const selectProduct = useHandleIntent(intents.selectProduct, {
35
+ * onCancel: () => console.log('User cancelled'),
36
+ * onError: (error) => console.error('Selection failed:', error)
37
+ * })
38
+ *
39
+ * const result = await selectProduct({multiSelect: true})
40
+ * // result is the data, or undefined if cancelled
41
+ */
42
+ export const useHandleIntent = <T, Args extends unknown[]>(
43
+ intent: (...args: Args) => Promise<ShopIntentResult<T>>,
44
+ options?: HandleIntentOptions<T>
45
+ ) => {
46
+ return useCallback(
47
+ async (...args: Args): Promise<T | void> => {
48
+ const result = await intent(...args)
49
+
50
+ switch (result.resultCode) {
51
+ case IntentResultCode.SUCCESS:
52
+ return result.data!
53
+
54
+ case IntentResultCode.CANCELLED:
55
+ if (options?.onCancel) {
56
+ return options.onCancel()
57
+ }
58
+ return undefined
59
+
60
+ case IntentResultCode.FAILED:
61
+ if (options?.onError) {
62
+ return options.onError(result.error!)
63
+ }
64
+
65
+ throw new Error(result.error?.message || 'Intent failed')
66
+
67
+ default: {
68
+ // Exhaustiveness check
69
+ const _exhaustive: never = result.resultCode
70
+ throw new Error(`Unhandled result code: ${_exhaustive}`)
71
+ }
72
+ }
73
+ },
74
+ [intent, options]
75
+ )
76
+ }
77
+
78
+ /**
79
+ * Alternative: Manual result handling without throwing
80
+ *
81
+ * Use this when you want to handle all result codes explicitly
82
+ * without throwing errors.
83
+ *
84
+ * @example
85
+ * const selectProduct = useHandleIntentManual(intents.selectProduct)
86
+ *
87
+ * const result = await selectProduct({multiSelect: true})
88
+ * if (result.resultCode === 'SUCCESS') {
89
+ * console.log(result.data)
90
+ * }
91
+ */
92
+ export const useHandleIntentManual = <T, Args extends unknown[]>(
93
+ intent: (...args: Args) => Promise<ShopIntentResult<T>>
94
+ ) => {
95
+ return useCallback(
96
+ (...args: Args) => {
97
+ return intent(...args)
98
+ },
99
+ [intent]
100
+ )
101
+ }
@@ -0,0 +1,13 @@
1
+ import type {ShopIntents} from '@shopify/shop-minis-platform'
2
+
3
+ /**
4
+ * Internal hook to access Shop Intents
5
+ *
6
+ * This provides direct access to window.minisIntents
7
+ * Use specific intent hooks (like useProductSelection) instead of this hook directly
8
+ *
9
+ * @internal
10
+ */
11
+ export function useShopIntents(): ShopIntents {
12
+ return window.minisIntents
13
+ }
package/src/mocks.ts CHANGED
@@ -551,7 +551,6 @@ export function makeMockActions(): ShopActions {
551
551
  },
552
552
  reportError: undefined,
553
553
  reportFetch: undefined,
554
- reportNavigationState: undefined,
555
554
  } as const
556
555
 
557
556
  const mock: Partial<ShopActions> = {}
@@ -1,14 +0,0 @@
1
- import { useRef as i, useEffect as o } from "react";
2
- function s(n) {
3
- const e = i(n);
4
- e.current = n, o(() => {
5
- const t = window.minisEvents.on("NAVIGATE_BACK", () => {
6
- e.current();
7
- });
8
- return () => window.minisEvents.off(t);
9
- }, []);
10
- }
11
- export {
12
- s as useOnNavigateBack
13
- };
14
- //# sourceMappingURL=useOnNavigateBack.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useOnNavigateBack.js","sources":["../../../src/hooks/events/useOnNavigateBack.ts"],"sourcesContent":["import {useEffect, useRef} from 'react'\n\nexport function useOnNavigateBack(callback: () => void) {\n // Using a ref allows the callback to be updated without triggering a re-render\n // This makes the hook nicer to use because developers don't need useCallback\n const callbackRef = useRef(callback)\n callbackRef.current = callback\n\n useEffect(() => {\n const listenerId = window.minisEvents.on('NAVIGATE_BACK', () => {\n callbackRef.current()\n })\n\n return () => window.minisEvents.off(listenerId)\n }, [])\n}\n"],"names":["useOnNavigateBack","callback","callbackRef","useRef","useEffect","listenerId"],"mappings":";AAEO,SAASA,EAAkBC,GAAsB;AAGhD,QAAAC,IAAcC,EAAOF,CAAQ;AACnC,EAAAC,EAAY,UAAUD,GAEtBG,EAAU,MAAM;AACd,UAAMC,IAAa,OAAO,YAAY,GAAG,iBAAiB,MAAM;AAC9D,MAAAH,EAAY,QAAQ;AAAA,IAAA,CACrB;AAED,WAAO,MAAM,OAAO,YAAY,IAAIG,CAAU;AAAA,EAChD,GAAG,EAAE;AACP;"}
@@ -1,28 +0,0 @@
1
- import { useEffect as e } from "react";
2
- import { useLocation as a } from "../shop-minis-react/node_modules/.pnpm/react-router@7.7.0_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-router/dist/development/chunk-EF7DTUVF.js";
3
- import { useOnNavigateBack as r } from "../hooks/events/useOnNavigateBack.js";
4
- import { useNavigateWithTransition as s } from "../hooks/navigation/useNavigateWithTransition.js";
5
- import { useShopActions as h } from "./useShopActions.js";
6
- function u() {
7
- const t = a(), i = s(), { reportNavigationState: o } = h();
8
- return e(() => {
9
- const n = {
10
- location: {
11
- pathname: t.pathname,
12
- search: t.search,
13
- hash: t.hash,
14
- key: t.key,
15
- state: t.state
16
- },
17
- historyLength: typeof window > "u" ? 0 : window.history.length,
18
- historyIndex: typeof window > "u" || !window.history.state?.idx ? null : window.history.state.idx
19
- };
20
- o(n);
21
- }, [t, o]), r(() => {
22
- i(-1);
23
- }), null;
24
- }
25
- export {
26
- u as NavigationManager
27
- };
28
- //# sourceMappingURL=navigation-manager.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"navigation-manager.js","sources":["../../src/internal/navigation-manager.tsx"],"sourcesContent":["import {useEffect} from 'react'\n\nimport {useLocation} from 'react-router'\n\nimport {useOnNavigateBack} from '../hooks/events/useOnNavigateBack'\nimport {useNavigateWithTransition} from '../hooks/navigation/useNavigateWithTransition'\n\nimport {useShopActions} from './useShopActions'\n\nexport function NavigationManager() {\n const location = useLocation()\n const navigate = useNavigateWithTransition()\n const {reportNavigationState} = useShopActions()\n\n // Report navigation state on location changes\n useEffect(() => {\n const navigationState = {\n location: {\n pathname: location.pathname,\n search: location.search,\n hash: location.hash,\n key: location.key,\n state: location.state,\n },\n historyLength: typeof window === 'undefined' ? 0 : window.history.length,\n historyIndex:\n typeof window === 'undefined' || !window.history.state?.idx\n ? null\n : window.history.state.idx,\n }\n\n reportNavigationState(navigationState)\n }, [location, reportNavigationState])\n\n // Handle native back button press\n useOnNavigateBack(() => {\n navigate(-1)\n })\n\n return null\n}\n"],"names":["NavigationManager","location","useLocation","navigate","useNavigateWithTransition","reportNavigationState","useShopActions","useEffect","navigationState","useOnNavigateBack"],"mappings":";;;;;AASO,SAASA,IAAoB;AAClC,QAAMC,IAAWC,EAAY,GACvBC,IAAWC,EAA0B,GACrC,EAAC,uBAAAC,EAAqB,IAAIC,EAAe;AAG/C,SAAAC,EAAU,MAAM;AACd,UAAMC,IAAkB;AAAA,MACtB,UAAU;AAAA,QACR,UAAUP,EAAS;AAAA,QACnB,QAAQA,EAAS;AAAA,QACjB,MAAMA,EAAS;AAAA,QACf,KAAKA,EAAS;AAAA,QACd,OAAOA,EAAS;AAAA,MAClB;AAAA,MACA,eAAe,OAAO,SAAW,MAAc,IAAI,OAAO,QAAQ;AAAA,MAClE,cACE,OAAO,SAAW,OAAe,CAAC,OAAO,QAAQ,OAAO,MACpD,OACA,OAAO,QAAQ,MAAM;AAAA,IAC7B;AAEA,IAAAI,EAAsBG,CAAe;AAAA,EAAA,GACpC,CAACP,GAAUI,CAAqB,CAAC,GAGpCI,EAAkB,MAAM;AACtB,IAAAN,EAAS,EAAE;AAAA,EAAA,CACZ,GAEM;AACT;"}
@@ -1,16 +0,0 @@
1
- import {useEffect, useRef} from 'react'
2
-
3
- export function useOnNavigateBack(callback: () => void) {
4
- // Using a ref allows the callback to be updated without triggering a re-render
5
- // This makes the hook nicer to use because developers don't need useCallback
6
- const callbackRef = useRef(callback)
7
- callbackRef.current = callback
8
-
9
- useEffect(() => {
10
- const listenerId = window.minisEvents.on('NAVIGATE_BACK', () => {
11
- callbackRef.current()
12
- })
13
-
14
- return () => window.minisEvents.off(listenerId)
15
- }, [])
16
- }
@@ -1,41 +0,0 @@
1
- import {useEffect} from 'react'
2
-
3
- import {useLocation} from 'react-router'
4
-
5
- import {useOnNavigateBack} from '../hooks/events/useOnNavigateBack'
6
- import {useNavigateWithTransition} from '../hooks/navigation/useNavigateWithTransition'
7
-
8
- import {useShopActions} from './useShopActions'
9
-
10
- export function NavigationManager() {
11
- const location = useLocation()
12
- const navigate = useNavigateWithTransition()
13
- const {reportNavigationState} = useShopActions()
14
-
15
- // Report navigation state on location changes
16
- useEffect(() => {
17
- const navigationState = {
18
- location: {
19
- pathname: location.pathname,
20
- search: location.search,
21
- hash: location.hash,
22
- key: location.key,
23
- state: location.state,
24
- },
25
- historyLength: typeof window === 'undefined' ? 0 : window.history.length,
26
- historyIndex:
27
- typeof window === 'undefined' || !window.history.state?.idx
28
- ? null
29
- : window.history.state.idx,
30
- }
31
-
32
- reportNavigationState(navigationState)
33
- }, [location, reportNavigationState])
34
-
35
- // Handle native back button press
36
- useOnNavigateBack(() => {
37
- navigate(-1)
38
- })
39
-
40
- return null
41
- }