@shopify/shop-minis-react 0.13.2 → 0.15.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.
Files changed (70) hide show
  1. package/dist/_virtual/index2.js +4 -4
  2. package/dist/_virtual/index3.js +2 -5
  3. package/dist/_virtual/index3.js.map +1 -1
  4. package/dist/_virtual/index4.js +2 -2
  5. package/dist/_virtual/index5.js +3 -2
  6. package/dist/_virtual/index5.js.map +1 -1
  7. package/dist/_virtual/index6.js +2 -2
  8. package/dist/_virtual/index7.js +2 -3
  9. package/dist/_virtual/index7.js.map +1 -1
  10. package/dist/_virtual/index8.js +2 -2
  11. package/dist/components/atoms/list.js.map +1 -1
  12. package/dist/components/commerce/product-link.js.map +1 -1
  13. package/dist/components/commerce/search.js.map +1 -1
  14. package/dist/components/navigation/transition-link.js.map +1 -1
  15. package/dist/hooks/intents/useIntent.js +43 -0
  16. package/dist/hooks/intents/useIntent.js.map +1 -0
  17. package/dist/index.js +45 -43
  18. package/dist/index.js.map +1 -1
  19. package/dist/internal/components/product-review-stars.js.map +1 -1
  20. package/dist/shop-minis-react/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.1.6_react@19.1.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.js +1 -1
  21. package/dist/shop-minis-react/node_modules/.pnpm/@videojs_xhr@2.7.0/node_modules/@videojs/xhr/lib/index.js +1 -1
  22. package/dist/shop-minis-react/node_modules/.pnpm/@xmldom_xmldom@0.8.10/node_modules/@xmldom/xmldom/lib/index.js +1 -1
  23. package/dist/shop-minis-react/node_modules/.pnpm/color-convert@3.1.3/node_modules/color-convert/conversions.js +422 -0
  24. package/dist/shop-minis-react/node_modules/.pnpm/color-convert@3.1.3/node_modules/color-convert/conversions.js.map +1 -0
  25. package/dist/shop-minis-react/node_modules/.pnpm/color-convert@3.1.3/node_modules/color-convert/index.js +36 -0
  26. package/dist/shop-minis-react/node_modules/.pnpm/color-convert@3.1.3/node_modules/color-convert/index.js.map +1 -0
  27. package/dist/shop-minis-react/node_modules/.pnpm/color-convert@3.1.3/node_modules/color-convert/route.js +47 -0
  28. package/dist/shop-minis-react/node_modules/.pnpm/color-convert@3.1.3/node_modules/color-convert/route.js.map +1 -0
  29. package/dist/shop-minis-react/node_modules/.pnpm/color-name@2.1.0/node_modules/color-name/index.js +156 -0
  30. package/dist/shop-minis-react/node_modules/.pnpm/color-name@2.1.0/node_modules/color-name/index.js.map +1 -0
  31. package/dist/shop-minis-react/node_modules/.pnpm/color-string@2.1.4/node_modules/color-string/index.js +106 -0
  32. package/dist/shop-minis-react/node_modules/.pnpm/color-string@2.1.4/node_modules/color-string/index.js.map +1 -0
  33. package/dist/shop-minis-react/node_modules/.pnpm/color@5.0.3/node_modules/color/index.js +263 -0
  34. package/dist/shop-minis-react/node_modules/.pnpm/color@5.0.3/node_modules/color/index.js.map +1 -0
  35. package/dist/shop-minis-react/node_modules/.pnpm/mpd-parser@1.3.1/node_modules/mpd-parser/dist/mpd-parser.es.js +1 -1
  36. package/dist/shop-minis-react/node_modules/.pnpm/querystringify@2.2.0/node_modules/querystringify/index.js +1 -1
  37. package/dist/shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/shim/index.js +1 -1
  38. package/dist/shop-minis-react/node_modules/.pnpm/video.js@8.23.3/node_modules/video.js/dist/video.es.js +1 -1
  39. package/dist/utils/colors.js +1 -1
  40. package/package.json +3 -3
  41. package/src/components/atoms/list.tsx +4 -5
  42. package/src/components/commerce/product-link.tsx +2 -1
  43. package/src/components/commerce/search.tsx +6 -3
  44. package/src/components/navigation/transition-link.tsx +1 -2
  45. package/src/hooks/index.ts +3 -0
  46. package/src/hooks/intents/useIntent.test.ts +316 -0
  47. package/src/hooks/intents/useIntent.ts +111 -0
  48. package/src/internal/components/product-review-stars.tsx +1 -2
  49. package/dist/_virtual/index10.js +0 -5
  50. package/dist/_virtual/index10.js.map +0 -1
  51. package/dist/_virtual/index11.js +0 -5
  52. package/dist/_virtual/index11.js.map +0 -1
  53. package/dist/_virtual/index9.js +0 -5
  54. package/dist/_virtual/index9.js.map +0 -1
  55. package/dist/shop-minis-react/node_modules/.pnpm/color-convert@2.0.1/node_modules/color-convert/conversions.js +0 -308
  56. package/dist/shop-minis-react/node_modules/.pnpm/color-convert@2.0.1/node_modules/color-convert/conversions.js.map +0 -1
  57. package/dist/shop-minis-react/node_modules/.pnpm/color-convert@2.0.1/node_modules/color-convert/index.js +0 -41
  58. package/dist/shop-minis-react/node_modules/.pnpm/color-convert@2.0.1/node_modules/color-convert/index.js.map +0 -1
  59. package/dist/shop-minis-react/node_modules/.pnpm/color-convert@2.0.1/node_modules/color-convert/route.js +0 -53
  60. package/dist/shop-minis-react/node_modules/.pnpm/color-convert@2.0.1/node_modules/color-convert/route.js.map +0 -1
  61. package/dist/shop-minis-react/node_modules/.pnpm/color-name@1.1.4/node_modules/color-name/index.js +0 -157
  62. package/dist/shop-minis-react/node_modules/.pnpm/color-name@1.1.4/node_modules/color-name/index.js.map +0 -1
  63. package/dist/shop-minis-react/node_modules/.pnpm/color-string@1.9.1/node_modules/color-string/index.js +0 -103
  64. package/dist/shop-minis-react/node_modules/.pnpm/color-string@1.9.1/node_modules/color-string/index.js.map +0 -1
  65. package/dist/shop-minis-react/node_modules/.pnpm/color@4.2.3/node_modules/color/index.js +0 -269
  66. package/dist/shop-minis-react/node_modules/.pnpm/color@4.2.3/node_modules/color/index.js.map +0 -1
  67. package/dist/shop-minis-react/node_modules/.pnpm/is-arrayish@0.3.2/node_modules/is-arrayish/index.js +0 -10
  68. package/dist/shop-minis-react/node_modules/.pnpm/is-arrayish@0.3.2/node_modules/is-arrayish/index.js.map +0 -1
  69. package/dist/shop-minis-react/node_modules/.pnpm/simple-swizzle@0.2.2/node_modules/simple-swizzle/index.js +0 -23
  70. package/dist/shop-minis-react/node_modules/.pnpm/simple-swizzle@0.2.2/node_modules/simple-swizzle/index.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import S from "../../../../../../../_virtual/window.js";
2
2
  import B from "../../../../../../../_virtual/document.js";
3
- import il from "../../../../../../../_virtual/index3.js";
3
+ import il from "../../../../../../../_virtual/index2.js";
4
4
  import ao from "../../../../../../../_virtual/browser-index.js";
5
5
  import xe from "../../../../@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/extends.js";
6
6
  import Kc from "../../../../@videojs_vhs-utils@4.1.1/node_modules/@videojs/vhs-utils/es/resolve-url.js";
@@ -1,4 +1,4 @@
1
- import o from "../_virtual/index2.js";
1
+ import o from "../shop-minis-react/node_modules/.pnpm/color@5.0.3/node_modules/color/index.js";
2
2
  const a = (r) => o(r).darken(0.2).isDark();
3
3
  export {
4
4
  a as isDarkColor
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.13.2",
4
+ "version": "0.15.0",
5
5
  "sideEffects": false,
6
6
  "type": "module",
7
7
  "engines": {
@@ -49,7 +49,6 @@
49
49
  "@shopify/shop-minis-platform": "0.13.0",
50
50
  "@tailwindcss/vite": "4.1.8",
51
51
  "@tanstack/react-query": "5.86.0",
52
- "@types/color": "3.0.6",
53
52
  "@types/lodash": "4.17.20",
54
53
  "@types/react-window": "1.8.8",
55
54
  "@types/url-parse": "1.4.9",
@@ -57,7 +56,7 @@
57
56
  "@vitejs/plugin-react": "4.5.1",
58
57
  "class-variance-authority": "0.7.1",
59
58
  "clsx": "2.1.1",
60
- "color": "4.2.3",
59
+ "color": "5.0.3",
61
60
  "embla-carousel-react": "8.6.0",
62
61
  "eslint": "8.57.0",
63
62
  "eslint-plugin-compat": "6.0.2",
@@ -94,6 +93,7 @@
94
93
  "@types/react-dom": "^19.1.2",
95
94
  "@vitest/coverage-v8": "^2.1.8",
96
95
  "@vitest/ui": "^2.1.8",
96
+ "eslint-formatter-codeframe": "^7.32.1",
97
97
  "jsdom": "^25.0.1",
98
98
  "react": "^19.1.0",
99
99
  "react-dom": "^19.1.0",
@@ -35,11 +35,10 @@ export interface ListDocProps<T = any> {
35
35
  enablePullToRefresh?: boolean
36
36
  }
37
37
 
38
- export interface ListProps<T = any>
39
- extends Omit<
40
- VirtuosoProps<T, unknown>,
41
- 'data' | 'itemContent' | 'endReached'
42
- > {
38
+ export interface ListProps<T = any> extends Omit<
39
+ VirtuosoProps<T, unknown>,
40
+ 'data' | 'itemContent' | 'endReached'
41
+ > {
43
42
  items: T[]
44
43
  renderItem: (item: T, index: number) => React.ReactNode
45
44
  showScrollbar?: boolean
@@ -35,7 +35,8 @@ const productLinkVariants = cva('', {
35
35
 
36
36
  // Primitive components (building blocks)
37
37
  export interface ProductLinkRootProps
38
- extends React.ComponentProps<typeof Card>,
38
+ extends
39
+ React.ComponentProps<typeof Card>,
39
40
  VariantProps<typeof productLinkVariants> {
40
41
  layout?: 'horizontal' | 'vertical'
41
42
  asChild?: boolean
@@ -207,10 +207,13 @@ function SearchResultsList({
207
207
  )
208
208
  }
209
209
 
210
- interface SearchProviderPropsWithoutChildren
211
- extends Omit<SearchProviderProps, 'children'> {}
210
+ interface SearchProviderPropsWithoutChildren extends Omit<
211
+ SearchProviderProps,
212
+ 'children'
213
+ > {}
212
214
  export interface SearchResultsProps
213
- extends SearchProviderPropsWithoutChildren,
215
+ extends
216
+ SearchProviderPropsWithoutChildren,
214
217
  SearchInputProps,
215
218
  SearchResultsListProps {
216
219
  showSearchInput?: boolean
@@ -13,8 +13,7 @@ export interface TransitionLinkDocProps {
13
13
  children?: React.ReactNode
14
14
  }
15
15
 
16
- export interface TransitionLinkProps
17
- extends AnchorHTMLAttributes<HTMLAnchorElement> {
16
+ export interface TransitionLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
18
17
  to: string
19
18
  }
20
19
 
@@ -51,6 +51,9 @@ export * from './util/useImagePicker'
51
51
  export * from './util/useKeyboardAvoidingView'
52
52
  export * from './util/useRequestPermissions'
53
53
 
54
+ // - Intent Hooks
55
+ export * from './intents/useIntent'
56
+
54
57
  // - Event Hooks
55
58
  export * from './events/useOnMiniFocus'
56
59
  export * from './events/useOnMiniBlur'
@@ -0,0 +1,316 @@
1
+ import {renderHook} from '@testing-library/react'
2
+ import {describe, expect, it, vi, beforeEach} from 'vitest'
3
+
4
+ import {useIntent} from './useIntent'
5
+
6
+ vi.mock('../navigation/useDeeplink', () => ({
7
+ useDeeplink: vi.fn(),
8
+ }))
9
+
10
+ const {useDeeplink} = await import('../navigation/useDeeplink')
11
+
12
+ describe('useIntent', () => {
13
+ beforeEach(() => {
14
+ vi.clearAllMocks()
15
+ })
16
+
17
+ describe('without intent params', () => {
18
+ it('returns nulls when no query params', () => {
19
+ vi.mocked(useDeeplink).mockReturnValue({
20
+ path: '/',
21
+ queryParams: undefined,
22
+ hash: '',
23
+ })
24
+
25
+ const {result} = renderHook(() => useIntent())
26
+
27
+ expect(result.current).toEqual({query: null, data: null})
28
+ })
29
+
30
+ it('returns nulls when intentQuery is missing', () => {
31
+ vi.mocked(useDeeplink).mockReturnValue({
32
+ path: '/',
33
+ queryParams: {other: 'param'},
34
+ hash: '',
35
+ })
36
+
37
+ const {result} = renderHook(() => useIntent())
38
+
39
+ expect(result.current).toEqual({query: null, data: null})
40
+ })
41
+ })
42
+
43
+ describe('intent query parsing', () => {
44
+ it('parses action:type,value format', () => {
45
+ vi.mocked(useDeeplink).mockReturnValue({
46
+ path: '/',
47
+ queryParams: {
48
+ intentQuery: 'try_on:shopify/Product,gid://shopify/Product/123',
49
+ },
50
+ hash: '',
51
+ })
52
+
53
+ const {result} = renderHook(() => useIntent())
54
+
55
+ expect(result.current.query).toEqual({
56
+ action: 'try_on',
57
+ type: 'shopify/Product',
58
+ value: 'gid://shopify/Product/123',
59
+ })
60
+ expect(result.current.data).toBeNull()
61
+ })
62
+
63
+ it('parses action:type without value', () => {
64
+ vi.mocked(useDeeplink).mockReturnValue({
65
+ path: '/',
66
+ queryParams: {intentQuery: 'create:shopify/Product'},
67
+ hash: '',
68
+ })
69
+
70
+ const {result} = renderHook(() => useIntent())
71
+
72
+ expect(result.current.query).toEqual({
73
+ action: 'create',
74
+ type: 'shopify/Product',
75
+ value: null,
76
+ })
77
+ })
78
+
79
+ it('handles URL-encoded intentQuery', () => {
80
+ vi.mocked(useDeeplink).mockReturnValue({
81
+ path: '/',
82
+ queryParams: {
83
+ intentQuery:
84
+ 'try_on%3Ashopify%2FProduct%2Cgid%3A%2F%2Fshopify%2FProduct%2F123',
85
+ },
86
+ hash: '',
87
+ })
88
+
89
+ const {result} = renderHook(() => useIntent())
90
+
91
+ expect(result.current.query).toEqual({
92
+ action: 'try_on',
93
+ type: 'shopify/Product',
94
+ value: 'gid://shopify/Product/123',
95
+ })
96
+ })
97
+
98
+ it('returns null query for invalid format without colon', () => {
99
+ vi.mocked(useDeeplink).mockReturnValue({
100
+ path: '/',
101
+ queryParams: {intentQuery: 'invalid-no-colon'},
102
+ hash: '',
103
+ })
104
+
105
+ const {result} = renderHook(() => useIntent())
106
+
107
+ expect(result.current).toEqual({query: null, data: null})
108
+ })
109
+
110
+ it('returns null value for trailing comma', () => {
111
+ vi.mocked(useDeeplink).mockReturnValue({
112
+ path: '/',
113
+ queryParams: {intentQuery: 'create:shopify/Product,'},
114
+ hash: '',
115
+ })
116
+
117
+ const {result} = renderHook(() => useIntent())
118
+
119
+ expect(result.current.query).toEqual({
120
+ action: 'create',
121
+ type: 'shopify/Product',
122
+ value: null,
123
+ })
124
+ })
125
+
126
+ it('returns null query when type is empty', () => {
127
+ vi.mocked(useDeeplink).mockReturnValue({
128
+ path: '/',
129
+ queryParams: {intentQuery: 'try_on:'},
130
+ hash: '',
131
+ })
132
+
133
+ const {result} = renderHook(() => useIntent())
134
+
135
+ expect(result.current).toEqual({query: null, data: null})
136
+ })
137
+ })
138
+
139
+ describe('shorthand GID syntax', () => {
140
+ it('infers type and value from full GID', () => {
141
+ vi.mocked(useDeeplink).mockReturnValue({
142
+ path: '/',
143
+ queryParams: {
144
+ intentQuery: 'edit:gid://shopify/Product/123',
145
+ },
146
+ hash: '',
147
+ })
148
+
149
+ const {result} = renderHook(() => useIntent())
150
+
151
+ expect(result.current.query).toEqual({
152
+ action: 'edit',
153
+ type: 'shopify/Product',
154
+ value: 'gid://shopify/Product/123',
155
+ })
156
+ })
157
+
158
+ it('infers type with null value from GID without ID', () => {
159
+ vi.mocked(useDeeplink).mockReturnValue({
160
+ path: '/',
161
+ queryParams: {
162
+ intentQuery: 'create:gid://shopify/Product',
163
+ },
164
+ hash: '',
165
+ })
166
+
167
+ const {result} = renderHook(() => useIntent())
168
+
169
+ expect(result.current.query).toEqual({
170
+ action: 'create',
171
+ type: 'shopify/Product',
172
+ value: null,
173
+ })
174
+ })
175
+
176
+ it('parses intentData alongside shorthand GID', () => {
177
+ vi.mocked(useDeeplink).mockReturnValue({
178
+ path: '/',
179
+ queryParams: {
180
+ intentQuery: 'edit:gid://shopify/Product/123',
181
+ intentData: '{"title":"Updated"}',
182
+ },
183
+ hash: '',
184
+ })
185
+
186
+ const {result} = renderHook(() => useIntent())
187
+
188
+ expect(result.current.query).toEqual({
189
+ action: 'edit',
190
+ type: 'shopify/Product',
191
+ value: 'gid://shopify/Product/123',
192
+ })
193
+ expect(result.current.data).toEqual({title: 'Updated'})
194
+ })
195
+ })
196
+
197
+ describe('intent data parsing', () => {
198
+ it('parses JSON intentData', () => {
199
+ vi.mocked(useDeeplink).mockReturnValue({
200
+ path: '/',
201
+ queryParams: {
202
+ intentQuery: 'try_on:shopify/Product,gid://shopify/Product/123',
203
+ intentData: '{"variantId":"gid://shopify/ProductVariant/456"}',
204
+ },
205
+ hash: '',
206
+ })
207
+
208
+ const {result} = renderHook(() => useIntent())
209
+
210
+ expect(result.current.data).toEqual({
211
+ variantId: 'gid://shopify/ProductVariant/456',
212
+ })
213
+ })
214
+
215
+ it('parses URL-encoded intentData', () => {
216
+ vi.mocked(useDeeplink).mockReturnValue({
217
+ path: '/',
218
+ queryParams: {
219
+ intentQuery: 'try_on:shopify/Product,gid://shopify/Product/123',
220
+ intentData: '%7B%22variantId%22%3A%22456%22%7D',
221
+ },
222
+ hash: '',
223
+ })
224
+
225
+ const {result} = renderHook(() => useIntent())
226
+
227
+ expect(result.current.data).toEqual({variantId: '456'})
228
+ })
229
+
230
+ it('preserves percent-encoded values inside already-decoded JSON', () => {
231
+ vi.mocked(useDeeplink).mockReturnValue({
232
+ path: '/',
233
+ queryParams: {
234
+ intentQuery: 'edit:shopify/Product,gid://shopify/Product/123',
235
+ intentData: '{"redirect":"https%3A%2F%2Fexample.com"}',
236
+ },
237
+ hash: '',
238
+ })
239
+
240
+ const {result} = renderHook(() => useIntent())
241
+
242
+ expect(result.current.data).toEqual({
243
+ redirect: 'https%3A%2F%2Fexample.com',
244
+ })
245
+ })
246
+
247
+ it('returns null data when intentData is a JSON array', () => {
248
+ vi.mocked(useDeeplink).mockReturnValue({
249
+ path: '/',
250
+ queryParams: {
251
+ intentQuery: 'try_on:shopify/Product,gid://shopify/Product/123',
252
+ intentData: '[1,2,3]',
253
+ },
254
+ hash: '',
255
+ })
256
+
257
+ const {result} = renderHook(() => useIntent())
258
+
259
+ expect(result.current.query).not.toBeNull()
260
+ expect(result.current.data).toBeNull()
261
+ })
262
+
263
+ it('returns null data when intentData is a JSON primitive', () => {
264
+ vi.mocked(useDeeplink).mockReturnValue({
265
+ path: '/',
266
+ queryParams: {
267
+ intentQuery: 'try_on:shopify/Product,gid://shopify/Product/123',
268
+ intentData: '"just a string"',
269
+ },
270
+ hash: '',
271
+ })
272
+
273
+ const {result} = renderHook(() => useIntent())
274
+
275
+ expect(result.current.query).not.toBeNull()
276
+ expect(result.current.data).toBeNull()
277
+ })
278
+
279
+ it('returns null data for malformed JSON', () => {
280
+ vi.mocked(useDeeplink).mockReturnValue({
281
+ path: '/',
282
+ queryParams: {
283
+ intentQuery: 'try_on:shopify/Product,gid://shopify/Product/123',
284
+ intentData: 'not-valid-json',
285
+ },
286
+ hash: '',
287
+ })
288
+
289
+ const {result} = renderHook(() => useIntent())
290
+
291
+ expect(result.current.query).not.toBeNull()
292
+ expect(result.current.data).toBeNull()
293
+ })
294
+ })
295
+
296
+ describe('memoization', () => {
297
+ it('returns same reference when queryParams do not change', () => {
298
+ const queryParams = {
299
+ intentQuery: 'try_on:shopify/Product,gid://shopify/Product/123',
300
+ }
301
+
302
+ vi.mocked(useDeeplink).mockReturnValue({
303
+ path: '/',
304
+ queryParams,
305
+ hash: '',
306
+ })
307
+
308
+ const {result, rerender} = renderHook(() => useIntent())
309
+ const firstResult = result.current
310
+
311
+ rerender()
312
+
313
+ expect(result.current).toBe(firstResult)
314
+ })
315
+ })
316
+ })
@@ -0,0 +1,111 @@
1
+ import {useMemo} from 'react'
2
+
3
+ import {useDeeplink} from '../navigation/useDeeplink'
4
+
5
+ /**
6
+ * Structured description of an intent, following the Shopify Intents API
7
+ * URI format: `action:type,value`
8
+ *
9
+ * @see https://shopify.dev/docs/api/admin-extensions/latest/target-apis/utility-apis/intents-api
10
+ */
11
+ export interface IntentQuery {
12
+ /** Verb describing the operation (e.g., 'try_on', 'create', 'edit') */
13
+ action: string
14
+ /** Resource type identifier (e.g., 'shopify/Product', 'shop/UserImage') */
15
+ type: string
16
+ /** Resource GID (e.g., 'gid://shopify/Product/123') if applicable */
17
+ value: string | null
18
+ }
19
+
20
+ export interface UseIntentReturn {
21
+ /** Parsed intent query, or null if no intent was passed */
22
+ query: IntentQuery | null
23
+ /** Additional JSON data passed with the intent, or null */
24
+ data: {[key: string]: unknown} | null
25
+ }
26
+
27
+ /**
28
+ * Parses an intent passed to this mini via deeplink.
29
+ *
30
+ * Follows the Shopify Intents API URI format (`action:type,value`) with
31
+ * an optional JSON data payload passed separately.
32
+ *
33
+ * @see https://shopify.dev/docs/api/admin-extensions/latest/target-apis/utility-apis/intents-api
34
+ *
35
+ * Deeplink format:
36
+ * ?intentQuery=action:type,value&intentData={"key":"value"}
37
+ *
38
+ * Examples:
39
+ * ?intentQuery=try_on:shopify/Product,gid://shopify/Product/123
40
+ * ?intentQuery=create:shopify/Product
41
+ * ?intentQuery=edit:shopify/Product,gid://shopify/Product/123&intentData={"variantId":"456"}
42
+ *
43
+ * Shorthand GID syntax (type inferred from GID):
44
+ * ?intentQuery=edit:gid://shopify/Product/123
45
+ * ?intentQuery=create:gid://shopify/Product
46
+ *
47
+ * Use this hook to receive intents from the host app (Host → Mini direction).
48
+ */
49
+ export function useIntent(): UseIntentReturn {
50
+ const {queryParams} = useDeeplink()
51
+
52
+ return useMemo(() => {
53
+ const raw = queryParams?.intentQuery
54
+
55
+ if (!raw) return {query: null, data: null}
56
+
57
+ let decoded: string
58
+ try {
59
+ decoded = decodeURIComponent(raw)
60
+ } catch {
61
+ decoded = raw
62
+ }
63
+
64
+ const colonIdx = decoded.indexOf(':')
65
+ if (colonIdx === -1) return {query: null, data: null}
66
+
67
+ const action = decoded.slice(0, colonIdx)
68
+ const rest = decoded.slice(colonIdx + 1)
69
+ const commaIdx = rest.indexOf(',')
70
+
71
+ let type = commaIdx === -1 ? rest : rest.slice(0, commaIdx)
72
+ let value = commaIdx === -1 ? null : rest.slice(commaIdx + 1) || null
73
+
74
+ // Shorthand GID syntax: edit:gid://shopify/Product/123
75
+ // Infer type from GID and use full GID as value
76
+ const gidMatch = type.match(/^gid:\/\/shopify\/(\w+)(?:\/(.+))?$/)
77
+ if (gidMatch) {
78
+ type = `shopify/${gidMatch[1]}`
79
+ value = gidMatch[2] ? `gid://shopify/${gidMatch[1]}/${gidMatch[2]}` : null
80
+ }
81
+
82
+ if (!type) return {query: null, data: null}
83
+
84
+ let data: {[key: string]: unknown} | null = null
85
+ const rawData = queryParams?.intentData
86
+ if (rawData) {
87
+ let parsed: unknown
88
+ try {
89
+ parsed = JSON.parse(rawData)
90
+ } catch {
91
+ try {
92
+ parsed = JSON.parse(decodeURIComponent(rawData))
93
+ } catch {
94
+ // malformed JSON — ignore
95
+ }
96
+ }
97
+ if (
98
+ parsed !== null &&
99
+ typeof parsed === 'object' &&
100
+ !Array.isArray(parsed)
101
+ ) {
102
+ data = parsed as {[key: string]: unknown}
103
+ }
104
+ }
105
+
106
+ return {
107
+ query: {action, type, value},
108
+ data,
109
+ }
110
+ }, [queryParams])
111
+ }
@@ -17,8 +17,7 @@ const textSizeClasses = {
17
17
  lg: 'text-base',
18
18
  } as const
19
19
 
20
- export interface ProductReviewStarsProps
21
- extends React.HTMLAttributes<HTMLDivElement> {
20
+ export interface ProductReviewStarsProps extends React.HTMLAttributes<HTMLDivElement> {
22
21
  /**
23
22
  * Average rating value (0-5)
24
23
  */
@@ -1,5 +0,0 @@
1
- var e = { exports: {} };
2
- export {
3
- e as __module
4
- };
5
- //# sourceMappingURL=index10.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index10.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1,5 +0,0 @@
1
- var e = { exports: {} };
2
- export {
3
- e as __module
4
- };
5
- //# sourceMappingURL=index11.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index11.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1,5 +0,0 @@
1
- var r = {};
2
- export {
3
- r as __exports
4
- };
5
- //# sourceMappingURL=index9.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index9.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}