@tiledev/sdk-shopify 0.5.0 → 0.8.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.
- package/README.md +144 -1
- package/dist/alertSettings.d.ts +39 -0
- package/dist/alertSettings.d.ts.map +1 -0
- package/dist/alertSettings.js +76 -0
- package/dist/alertSettings.js.map +1 -0
- package/dist/blogs.d.ts.map +1 -1
- package/dist/blogs.js +3 -3
- package/dist/blogs.js.map +1 -1
- package/dist/cart.d.ts.map +1 -1
- package/dist/cart.js +27 -22
- package/dist/cart.js.map +1 -1
- package/dist/client.d.ts +20 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +80 -19
- package/dist/client.js.map +1 -1
- package/dist/collections.d.ts.map +1 -1
- package/dist/collections.js +8 -5
- package/dist/collections.js.map +1 -1
- package/dist/customer.d.ts.map +1 -1
- package/dist/customer.js +3 -3
- package/dist/customer.js.map +1 -1
- package/dist/deviceStore.d.ts +9 -0
- package/dist/deviceStore.d.ts.map +1 -0
- package/dist/deviceStore.js +77 -0
- package/dist/deviceStore.js.map +1 -0
- package/dist/deviceStore.native.d.ts +14 -0
- package/dist/deviceStore.native.d.ts.map +1 -0
- package/dist/deviceStore.native.js +47 -0
- package/dist/deviceStore.native.js.map +1 -0
- package/dist/deviceStoreCore.d.ts +24 -0
- package/dist/deviceStoreCore.d.ts.map +1 -0
- package/dist/deviceStoreCore.js +40 -0
- package/dist/deviceStoreCore.js.map +1 -0
- package/dist/filters.d.ts +32 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +67 -0
- package/dist/filters.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -1
- package/dist/index.js.map +1 -1
- package/dist/productStore.d.ts +43 -0
- package/dist/productStore.d.ts.map +1 -0
- package/dist/productStore.js +309 -0
- package/dist/productStore.js.map +1 -0
- package/dist/products.d.ts.map +1 -1
- package/dist/products.js +28 -19
- package/dist/products.js.map +1 -1
- package/dist/queries.d.ts +24 -24
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +4 -3
- package/dist/queries.js.map +1 -1
- package/dist/react/ShopifyProvider.d.ts +2 -0
- package/dist/react/ShopifyProvider.d.ts.map +1 -1
- package/dist/react/ShopifyProvider.js +18 -1
- package/dist/react/ShopifyProvider.js.map +1 -1
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +11 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/useAlertSettings.d.ts +32 -0
- package/dist/react/useAlertSettings.d.ts.map +1 -0
- package/dist/react/useAlertSettings.js +35 -0
- package/dist/react/useAlertSettings.js.map +1 -0
- package/dist/react/useCollectionProducts.d.ts +12 -20
- package/dist/react/useCollectionProducts.d.ts.map +1 -1
- package/dist/react/useCollectionProducts.js +36 -137
- package/dist/react/useCollectionProducts.js.map +1 -1
- package/dist/react/useProduct.d.ts +36 -0
- package/dist/react/useProduct.d.ts.map +1 -0
- package/dist/react/useProduct.js +126 -0
- package/dist/react/useProduct.js.map +1 -0
- package/dist/react/useProductFeed.d.ts +57 -0
- package/dist/react/useProductFeed.d.ts.map +1 -0
- package/dist/react/useProductFeed.js +231 -0
- package/dist/react/useProductFeed.js.map +1 -0
- package/dist/react/useSearch.d.ts +30 -0
- package/dist/react/useSearch.d.ts.map +1 -0
- package/dist/react/useSearch.js +61 -0
- package/dist/react/useSearch.js.map +1 -0
- package/dist/requestCache.d.ts +48 -0
- package/dist/requestCache.d.ts.map +1 -0
- package/dist/requestCache.js +173 -0
- package/dist/requestCache.js.map +1 -0
- package/dist/types.d.ts +67 -20
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -1
- package/dist/types.js.map +1 -1
- package/dist/variants.js +1 -1
- package/dist/variants.js.map +1 -1
- package/dist/wishlist.d.ts.map +1 -1
- package/dist/wishlist.js +4 -1
- package/dist/wishlist.js.map +1 -1
- package/package.json +9 -3
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AlertMessages, CartPolicy } from "../types";
|
|
2
|
+
import type { ShopifyEvent } from "./ShopifyProvider";
|
|
3
|
+
export interface UseAlertSettingsOptions {
|
|
4
|
+
/** The Live Layer's `settings.alerts` subtree (`ALERT_SETTINGS_PATH`). */
|
|
5
|
+
alerts: unknown;
|
|
6
|
+
/** The Live Layer's `settings.cart.maxLineItems` (`MAX_LINE_ITEMS_SETTING_PATH`). */
|
|
7
|
+
maxLineItems?: unknown;
|
|
8
|
+
/** Show one alert, e.g. the app's toast. Not called for alerts the merchant cleared. */
|
|
9
|
+
show: (message: string, severity: ShopifyEvent["severity"]) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface AlertSettingsProps {
|
|
12
|
+
messages: AlertMessages;
|
|
13
|
+
cartPolicy: CartPolicy;
|
|
14
|
+
onEvent: (event: ShopifyEvent) => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The Settings panel's alerts and cart limit, as `ShopifyProvider` props. The app
|
|
18
|
+
* reads the two Live Layer values and passes its toast; the path table, the
|
|
19
|
+
* "cleared means silent" rule and the limit bounds stay here.
|
|
20
|
+
*
|
|
21
|
+
* ```tsx
|
|
22
|
+
* const alerts = useLL(ALERT_SETTINGS_PATH);
|
|
23
|
+
* const maxLineItems = useLL(MAX_LINE_ITEMS_SETTING_PATH);
|
|
24
|
+
* const props = useAlertSettings({ alerts, maxLineItems, show: showToast });
|
|
25
|
+
* <ShopifyProvider config={config} {...props}>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* `onEvent` keeps one identity for the provider's lifetime, so a new `show`
|
|
29
|
+
* arrow on each render rebuilds nothing.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useAlertSettings({ alerts, maxLineItems, show }: UseAlertSettingsOptions): AlertSettingsProps;
|
|
32
|
+
//# sourceMappingURL=useAlertSettings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAlertSettings.d.ts","sourceRoot":"","sources":["../../src/react/useAlertSettings.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,WAAW,uBAAuB;IACtC,0EAA0E;IAC1E,MAAM,EAAE,OAAO,CAAC;IAChB,qFAAqF;IACrF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wFAAwF;IACxF,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;CACrE;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,aAAa,CAAC;IACxB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CACxC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,uBAAuB,GAAG,kBAAkB,CAc5G"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useAlertSettings = useAlertSettings;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const alertSettings_1 = require("../alertSettings");
|
|
6
|
+
/**
|
|
7
|
+
* The Settings panel's alerts and cart limit, as `ShopifyProvider` props. The app
|
|
8
|
+
* reads the two Live Layer values and passes its toast; the path table, the
|
|
9
|
+
* "cleared means silent" rule and the limit bounds stay here.
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const alerts = useLL(ALERT_SETTINGS_PATH);
|
|
13
|
+
* const maxLineItems = useLL(MAX_LINE_ITEMS_SETTING_PATH);
|
|
14
|
+
* const props = useAlertSettings({ alerts, maxLineItems, show: showToast });
|
|
15
|
+
* <ShopifyProvider config={config} {...props}>
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* `onEvent` keeps one identity for the provider's lifetime, so a new `show`
|
|
19
|
+
* arrow on each render rebuilds nothing.
|
|
20
|
+
*/
|
|
21
|
+
function useAlertSettings({ alerts, maxLineItems, show }) {
|
|
22
|
+
const { messages, silenced } = (0, react_1.useMemo)(() => (0, alertSettings_1.readAlertSettings)(alerts), [alerts]);
|
|
23
|
+
const cartPolicy = (0, react_1.useMemo)(() => (0, alertSettings_1.readCartPolicy)(maxLineItems), [maxLineItems]);
|
|
24
|
+
const latest = (0, react_1.useRef)({ silenced, show });
|
|
25
|
+
latest.current = { silenced, show };
|
|
26
|
+
const onEvent = (0, react_1.useCallback)((event) => {
|
|
27
|
+
const { silenced: quiet, show: display } = latest.current;
|
|
28
|
+
if ((0, alertSettings_1.isAlertSilenced)(event, quiet))
|
|
29
|
+
return;
|
|
30
|
+
if (event.message)
|
|
31
|
+
display(event.message, event.severity);
|
|
32
|
+
}, []);
|
|
33
|
+
return { messages, cartPolicy, onEvent };
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=useAlertSettings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAlertSettings.js","sourceRoot":"","sources":["../../src/react/useAlertSettings.ts"],"names":[],"mappings":";;AAoCA,4CAcC;AAlDD,iCAAqD;AAErD,oDAAsF;AAmBtF;;;;;;;;;;;;;;GAcG;AACH,SAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAA2B;IACtF,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE,CAAC,IAAA,iCAAiB,EAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAClF,MAAM,UAAU,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE,CAAC,IAAA,8BAAc,EAAC,YAAY,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAE/E,MAAM,MAAM,GAAG,IAAA,cAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,MAAM,CAAC,OAAO,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAEpC,MAAM,OAAO,GAAG,IAAA,mBAAW,EAAC,CAAC,KAAmB,EAAE,EAAE;QAClD,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC;QAC1D,IAAI,IAAA,+BAAe,EAAC,KAAK,EAAE,KAAK,CAAC;YAAE,OAAO;QAC1C,IAAI,KAAK,CAAC,OAAO;YAAE,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC5D,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AAC3C,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type ProductFeedResult, type ProductFeedState } from './useProductFeed';
|
|
2
|
+
import type { ImageTransform, ProductFilter } from '../types';
|
|
2
3
|
/** How a collection is ordered. `key` is a Storefront `ProductCollectionSortKeys` value. */
|
|
3
4
|
export interface CollectionSort {
|
|
4
5
|
key: string;
|
|
@@ -10,9 +11,15 @@ export interface UseCollectionProductsOptions {
|
|
|
10
11
|
handle: string | null | undefined;
|
|
11
12
|
/** Storefront sort. Omit for the merchant's own collection order. */
|
|
12
13
|
sort?: CollectionSort;
|
|
13
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Filters the app always applies, e.g. in stock only. Shopify's own product filters: a
|
|
16
|
+
* `FilterValue.input` parsed and handed back, never hand-built. The shopper's selection
|
|
17
|
+
* (`setFilters`) is applied on top.
|
|
18
|
+
*/
|
|
14
19
|
filters?: ProductFilter[];
|
|
15
20
|
pageSize?: number;
|
|
21
|
+
/** Resize/convert the products' images on Shopify's CDN, e.g. `{ maxWidth: 330, scale: 2 }`. */
|
|
22
|
+
imageTransform?: ImageTransform;
|
|
16
23
|
/**
|
|
17
24
|
* Called with any transport error (first page or paging) so the host app can log it — the hook has
|
|
18
25
|
* no logger of its own. Optional; when omitted the error only surfaces as `error` / a stopped feed.
|
|
@@ -22,22 +29,7 @@ export interface UseCollectionProductsOptions {
|
|
|
22
29
|
handle: string;
|
|
23
30
|
}) => void;
|
|
24
31
|
}
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
title: string | null;
|
|
29
|
-
/** The facets Shopify offers for this collection under the current filters. */
|
|
30
|
-
availableFilters: Filter[];
|
|
31
|
-
/** A first page is in flight: the grid has nothing to show yet. */
|
|
32
|
-
loading: boolean;
|
|
33
|
-
/** A later page is in flight: the grid keeps what it has and shows a footer spinner. */
|
|
34
|
-
loadingMore: boolean;
|
|
35
|
-
hasMore: boolean;
|
|
36
|
-
error: boolean;
|
|
37
|
-
}
|
|
38
|
-
export interface UseCollectionProductsResult extends CollectionProductsState {
|
|
39
|
-
loadMore: () => void;
|
|
40
|
-
retry: () => void;
|
|
41
|
-
}
|
|
42
|
-
export declare function useCollectionProducts({ handle, sort, filters, pageSize, onError, }: UseCollectionProductsOptions): UseCollectionProductsResult;
|
|
32
|
+
export type CollectionProductsState = ProductFeedState;
|
|
33
|
+
export type UseCollectionProductsResult = ProductFeedResult;
|
|
34
|
+
export declare function useCollectionProducts({ handle, sort, filters, pageSize, imageTransform, onError, }: UseCollectionProductsOptions): UseCollectionProductsResult;
|
|
43
35
|
//# sourceMappingURL=useCollectionProducts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCollectionProducts.d.ts","sourceRoot":"","sources":["../../src/react/useCollectionProducts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useCollectionProducts.d.ts","sourceRoot":"","sources":["../../src/react/useCollectionProducts.ts"],"names":[],"mappings":"AASA,OAAO,EAAkB,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9D,4FAA4F;AAC5F,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,oFAAoF;AACpF,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,qEAAqE;IACrE,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gGAAgG;IAChG,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC7E;AAED,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AACvD,MAAM,MAAM,2BAA2B,GAAG,iBAAiB,CAAC;AAE5D,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,IAAI,EACJ,OAAO,EACP,QAA+B,EAC/B,cAAc,EACd,OAAO,GACR,EAAE,4BAA4B,GAAG,2BAA2B,CAgC5D"}
|
|
@@ -3,149 +3,48 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.COLLECTION_PAGE_SIZE = void 0;
|
|
4
4
|
exports.useCollectionProducts = useCollectionProducts;
|
|
5
5
|
/**
|
|
6
|
-
* useCollectionProducts — one collection's products,
|
|
6
|
+
* useCollectionProducts — one collection's products (a product listing page), cache first.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* re-sends the same failing request. Cursor paging has no such cap, never re-downloads earlier pages,
|
|
13
|
-
* and — because `loadMore` failing only stops growth — leaves the loaded products in place.
|
|
14
|
-
*
|
|
15
|
-
* Every response is checked against the request that is current (`requestId`), so a page resolving
|
|
16
|
-
* after the sort or filters changed cannot merge into the new list.
|
|
8
|
+
* The collection's last first page renders immediately (memory, else the device store, so after a
|
|
9
|
+
* cold start too), then the network answer replaces it in place. Cursor-paged, appending. The
|
|
10
|
+
* shopper's filter selection is held here as `FilterValue.input` strings. The engine, and why each
|
|
11
|
+
* part is shaped the way it is, is `useProductFeed`.
|
|
17
12
|
*/
|
|
18
|
-
const react_1 = require("react");
|
|
19
13
|
const shopify_1 = require("../shopify");
|
|
20
|
-
const
|
|
14
|
+
const useProductFeed_1 = require("./useProductFeed");
|
|
21
15
|
/** Default page size — 12 is six rows of two, a full first screenful on a phone. */
|
|
22
16
|
exports.COLLECTION_PAGE_SIZE = 12;
|
|
23
|
-
|
|
24
|
-
products: [],
|
|
25
|
-
title: null,
|
|
26
|
-
availableFilters: [],
|
|
27
|
-
loading: false,
|
|
28
|
-
loadingMore: false,
|
|
29
|
-
hasMore: false,
|
|
30
|
-
error: false,
|
|
31
|
-
};
|
|
32
|
-
function useCollectionProducts({ handle, sort, filters, pageSize = exports.COLLECTION_PAGE_SIZE, onError, }) {
|
|
33
|
-
const { ready } = (0, ShopifyProvider_1.useShopify)();
|
|
34
|
-
const [state, setState] = (0, react_1.useState)(IDLE);
|
|
35
|
-
const [attempt, setAttempt] = (0, react_1.useState)(0);
|
|
36
|
-
/**
|
|
37
|
-
* Identifies the request the state belongs to. Bumped on every fresh read; an append carries the
|
|
38
|
-
* value it started with, so a response whose counter has moved on is dropped rather than merged.
|
|
39
|
-
*/
|
|
40
|
-
const requestId = (0, react_1.useRef)(0);
|
|
41
|
-
/** The cursor for the next page, held in a ref so `loadMore` does not need a fresh callback. */
|
|
42
|
-
const cursor = (0, react_1.useRef)(null);
|
|
43
|
-
/**
|
|
44
|
-
* The filter array and sort object are rebuilt on every render of the screen that owns them, so the
|
|
45
|
-
* effect depends on their serialisation rather than their identity — otherwise every keystroke
|
|
46
|
-
* elsewhere on the screen would refetch.
|
|
47
|
-
*/
|
|
48
|
-
const filterKey = (filters === null || filters === void 0 ? void 0 : filters.length) ? JSON.stringify(filters) : '';
|
|
17
|
+
function useCollectionProducts({ handle, sort, filters, pageSize = exports.COLLECTION_PAGE_SIZE, imageTransform, onError, }) {
|
|
49
18
|
const sortKey = sort ? `${sort.key}:${sort.reverse ? 'desc' : 'asc'}` : '';
|
|
50
|
-
(0,
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
sortKey: sort === null || sort === void 0 ? void 0 : sort.key,
|
|
66
|
-
reverse: (_a = sort === null || sort === void 0 ? void 0 : sort.reverse) !== null && _a !== void 0 ? _a : false,
|
|
67
|
-
filters: (filters === null || filters === void 0 ? void 0 : filters.length) ? filters : undefined,
|
|
68
|
-
})
|
|
69
|
-
.then((page) => {
|
|
70
|
-
var _a, _b, _c;
|
|
71
|
-
if (requestId.current !== id)
|
|
72
|
-
return;
|
|
73
|
-
cursor.current = page.pageInfo.endCursor;
|
|
74
|
-
setState({
|
|
75
|
-
products: page.nodes,
|
|
76
|
-
title: (_b = (_a = page.collection) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : null,
|
|
77
|
-
availableFilters: (_c = page.filters) !== null && _c !== void 0 ? _c : [],
|
|
78
|
-
loading: false,
|
|
79
|
-
loadingMore: false,
|
|
80
|
-
hasMore: page.pageInfo.hasNextPage,
|
|
81
|
-
error: false,
|
|
82
|
-
});
|
|
83
|
-
})
|
|
84
|
-
.catch((caught) => {
|
|
85
|
-
onError === null || onError === void 0 ? void 0 : onError(caught, { at: 'useCollectionProducts', handle });
|
|
86
|
-
if (requestId.current !== id)
|
|
87
|
-
return;
|
|
88
|
-
// A first-page failure genuinely has nothing to show, so the consumer renders its error state.
|
|
89
|
-
setState({ ...IDLE, error: true });
|
|
90
|
-
});
|
|
91
|
-
// Keyed on the serialised filters and sort; both change identity every render.
|
|
92
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
93
|
-
}, [ready, handle, sortKey, filterKey, pageSize, attempt]);
|
|
94
|
-
/**
|
|
95
|
-
* Guarded on both in-flight flags as well as on `hasMore`: a list's `onEndReached` fires every
|
|
96
|
-
* time the end stays in view, so an ungated version would run through the collection a page a frame.
|
|
97
|
-
*/
|
|
98
|
-
const loadMore = (0, react_1.useCallback)(() => {
|
|
99
|
-
var _a;
|
|
100
|
-
if (state.loading || state.loadingMore || !state.hasMore || !cursor.current)
|
|
101
|
-
return;
|
|
102
|
-
const id = requestId.current;
|
|
103
|
-
const after = cursor.current;
|
|
104
|
-
// Claim the cursor synchronously. `onEndReached` fires repeatedly and the `loadingMore` guard
|
|
105
|
-
// reads a `state` snapshot that is stale until the next render, so a second call in the same frame
|
|
106
|
-
// would otherwise fetch this SAME page again and append it twice — duplicate keys. Nulling the ref
|
|
107
|
-
// now makes the re-entrant call bail on `!cursor.current`; the fetch restores it, a failure leaves
|
|
108
|
-
// it null (paging has stopped anyway).
|
|
109
|
-
cursor.current = null;
|
|
110
|
-
setState((current) => ({ ...current, loadingMore: true }));
|
|
111
|
-
shopify_1.shopify.collections
|
|
112
|
-
.products(handle, {
|
|
113
|
-
first: pageSize,
|
|
114
|
-
after,
|
|
115
|
-
sortKey: sort === null || sort === void 0 ? void 0 : sort.key,
|
|
116
|
-
reverse: (_a = sort === null || sort === void 0 ? void 0 : sort.reverse) !== null && _a !== void 0 ? _a : false,
|
|
117
|
-
filters: (filters === null || filters === void 0 ? void 0 : filters.length) ? filters : undefined,
|
|
118
|
-
})
|
|
119
|
-
.then((page) => {
|
|
120
|
-
if (requestId.current !== id)
|
|
121
|
-
return;
|
|
122
|
-
cursor.current = page.pageInfo.endCursor;
|
|
123
|
-
setState((current) => {
|
|
124
|
-
// Defensive against any overlap between pages: never append a product already held.
|
|
125
|
-
const seen = new Set(current.products.map((p) => p.id));
|
|
126
|
-
const fresh = page.nodes.filter((p) => !seen.has(p.id));
|
|
127
|
-
return {
|
|
128
|
-
...current,
|
|
129
|
-
products: fresh.length ? [...current.products, ...fresh] : current.products,
|
|
130
|
-
loadingMore: false,
|
|
131
|
-
hasMore: page.pageInfo.hasNextPage,
|
|
132
|
-
};
|
|
19
|
+
return (0, useProductFeed_1.useProductFeed)({
|
|
20
|
+
id: handle ? `collection:${handle}` : null,
|
|
21
|
+
// The transform is part of the key: a page cached at one image size is not another's.
|
|
22
|
+
paramsKey: `${sortKey}|${pageSize}|${imageTransform ? JSON.stringify(imageTransform) : ''}`,
|
|
23
|
+
fixedFilters: filters,
|
|
24
|
+
fetch: async ({ after, filters: applied, fresh }) => {
|
|
25
|
+
var _a, _b, _c, _d;
|
|
26
|
+
const page = await shopify_1.shopify.collections.products(handle, {
|
|
27
|
+
first: pageSize,
|
|
28
|
+
after,
|
|
29
|
+
sortKey: sort === null || sort === void 0 ? void 0 : sort.key,
|
|
30
|
+
reverse: (_a = sort === null || sort === void 0 ? void 0 : sort.reverse) !== null && _a !== void 0 ? _a : false,
|
|
31
|
+
filters: applied,
|
|
32
|
+
fresh,
|
|
33
|
+
imageTransform,
|
|
133
34
|
});
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
retry: () => setAttempt((n) => n + 1),
|
|
149
|
-
};
|
|
35
|
+
return {
|
|
36
|
+
nodes: page.nodes,
|
|
37
|
+
pageInfo: page.pageInfo,
|
|
38
|
+
filters: (_b = page.filters) !== null && _b !== void 0 ? _b : [],
|
|
39
|
+
title: (_d = (_c = page.collection) === null || _c === void 0 ? void 0 : _c.title) !== null && _d !== void 0 ? _d : null,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
onError: onError
|
|
43
|
+
? (error, phase) => onError(error, {
|
|
44
|
+
at: phase === 'first' ? 'useCollectionProducts' : 'useCollectionProducts.loadMore',
|
|
45
|
+
handle: handle,
|
|
46
|
+
})
|
|
47
|
+
: undefined,
|
|
48
|
+
});
|
|
150
49
|
}
|
|
151
50
|
//# sourceMappingURL=useCollectionProducts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCollectionProducts.js","sourceRoot":"","sources":["../../src/react/useCollectionProducts.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"useCollectionProducts.js","sourceRoot":"","sources":["../../src/react/useCollectionProducts.ts"],"names":[],"mappings":";;;AA4CA,sDAuCC;AAnFD;;;;;;;GAOG;AACH,wCAAqC;AACrC,qDAAiG;AASjG,oFAAoF;AACvE,QAAA,oBAAoB,GAAG,EAAE,CAAC;AAyBvC,SAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,IAAI,EACJ,OAAO,EACP,QAAQ,GAAG,4BAAoB,EAC/B,cAAc,EACd,OAAO,GACsB;IAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,OAAO,IAAA,+BAAc,EAAC;QACpB,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,cAAc,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI;QAC1C,sFAAsF;QACtF,SAAS,EAAE,GAAG,OAAO,IAAI,QAAQ,IAAI,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE;QAC3F,YAAY,EAAE,OAAO;QACrB,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;;YAClD,MAAM,IAAI,GAAG,MAAM,iBAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAgB,EAAE;gBAChE,KAAK,EAAE,QAAQ;gBACf,KAAK;gBACL,OAAO,EAAE,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,GAAG;gBAClB,OAAO,EAAE,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,OAAO,mCAAI,KAAK;gBAC/B,OAAO,EAAE,OAAO;gBAChB,KAAK;gBACL,cAAc;aACf,CAAC,CAAC;YACH,OAAO;gBACL,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,OAAO,EAAE,MAAA,IAAI,CAAC,OAAO,mCAAI,EAAE;gBAC3B,KAAK,EAAE,MAAA,MAAA,IAAI,CAAC,UAAU,0CAAE,KAAK,mCAAI,IAAI;aACtC,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,OAAO;YACd,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CACf,OAAO,CAAC,KAAK,EAAE;gBACb,EAAE,EAAE,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,gCAAgC;gBAClF,MAAM,EAAE,MAAgB;aACzB,CAAC;YACN,CAAC,CAAC,SAAS;KACd,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ProductBase } from '../productStore';
|
|
2
|
+
import type { ImageTransform, Product } from '../types';
|
|
3
|
+
export interface UseProductOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Resize/convert the full product's images on Shopify's CDN (the gallery). The first-render
|
|
6
|
+
* `preview.featuredImage` is whatever the grid that showed the product fetched, which is the image
|
|
7
|
+
* already on screen and in the image cache.
|
|
8
|
+
*/
|
|
9
|
+
imageTransform?: ImageTransform;
|
|
10
|
+
onError?: (error: unknown, context: {
|
|
11
|
+
at: string;
|
|
12
|
+
handle: string;
|
|
13
|
+
}) => void;
|
|
14
|
+
}
|
|
15
|
+
export interface UseProductState {
|
|
16
|
+
/** The full product: variants, media, description. Null until known. */
|
|
17
|
+
product: Product | null;
|
|
18
|
+
/** What can render right away: title, image and price. Set whenever anything is known. */
|
|
19
|
+
preview: ProductBase | null;
|
|
20
|
+
/** How much is known: `'full'`, `'base'` (preview only), or null (nothing yet). */
|
|
21
|
+
level: 'full' | 'base' | null;
|
|
22
|
+
/** Nothing to show yet: no cached copy, and the read is in flight. */
|
|
23
|
+
loading: boolean;
|
|
24
|
+
/** A cached copy is on screen while the fresh one is read. */
|
|
25
|
+
refreshing: boolean;
|
|
26
|
+
/** The read failed. A cached copy, if any, is still shown. */
|
|
27
|
+
error: boolean;
|
|
28
|
+
/** Shopify has no product with this handle. */
|
|
29
|
+
notFound: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface UseProductResult extends UseProductState {
|
|
32
|
+
/** Read the product from the network again (pull-to-refresh). */
|
|
33
|
+
refresh: () => void;
|
|
34
|
+
}
|
|
35
|
+
export declare function useProduct(handle: string | null | undefined, options?: UseProductOptions): UseProductResult;
|
|
36
|
+
//# sourceMappingURL=useProduct.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useProduct.d.ts","sourceRoot":"","sources":["../../src/react/useProduct.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAmC,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAIpF,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExD,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC7E;AAED,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,0FAA0F;IAC1F,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,mFAAmF;IACnF,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC9B,sEAAsE;IACtE,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,UAAU,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,KAAK,EAAE,OAAO,CAAC;IACf,+CAA+C;IAC/C,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,iEAAiE;IACjE,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AA0BD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,GAAE,iBAAsB,GAAG,gBAAgB,CAgF/G"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useProduct = useProduct;
|
|
4
|
+
/**
|
|
5
|
+
* useProduct — one product (a product page), cache first, built to open instantly.
|
|
6
|
+
*
|
|
7
|
+
* First render, synchronously from the product store (memory, else the device store: MMKV on
|
|
8
|
+
* native, read by key in microseconds):
|
|
9
|
+
* - the **full** product, if this device has read it before; or
|
|
10
|
+
* - the **base** keys (`PRODUCT_BASE_KEYS`: title, image, price), which every collection, search,
|
|
11
|
+
* recommendation and wishlist read records. A product tapped in a grid always has these.
|
|
12
|
+
*
|
|
13
|
+
* Then the full product is read in the background and replaces what is shown, with `refreshing` true
|
|
14
|
+
* meanwhile rather than a spinner. A full product read under `REVALIDATE_AFTER_MS` ago is not read
|
|
15
|
+
* again. Render `preview` straight away; render what needs variants (the picker, add to cart) once
|
|
16
|
+
* `product` is set.
|
|
17
|
+
*/
|
|
18
|
+
const react_1 = require("react");
|
|
19
|
+
const client_1 = require("../client");
|
|
20
|
+
const productStore_1 = require("../productStore");
|
|
21
|
+
const shopify_1 = require("../shopify");
|
|
22
|
+
const ShopifyProvider_1 = require("./ShopifyProvider");
|
|
23
|
+
const useProductFeed_1 = require("./useProductFeed");
|
|
24
|
+
const EMPTY = {
|
|
25
|
+
product: null,
|
|
26
|
+
preview: null,
|
|
27
|
+
level: null,
|
|
28
|
+
loading: false,
|
|
29
|
+
refreshing: false,
|
|
30
|
+
error: false,
|
|
31
|
+
notFound: false,
|
|
32
|
+
};
|
|
33
|
+
function fromCache(entry, reading) {
|
|
34
|
+
if (!entry)
|
|
35
|
+
return { ...EMPTY, loading: reading };
|
|
36
|
+
const full = entry.full;
|
|
37
|
+
return {
|
|
38
|
+
product: full,
|
|
39
|
+
preview: entry.base,
|
|
40
|
+
level: full ? 'full' : 'base',
|
|
41
|
+
loading: false,
|
|
42
|
+
refreshing: reading,
|
|
43
|
+
error: false,
|
|
44
|
+
notFound: false,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function useProduct(handle, options = {}) {
|
|
48
|
+
const { ready } = (0, ShopifyProvider_1.useShopify)();
|
|
49
|
+
const configured = ready || (0, client_1.isConfigured)();
|
|
50
|
+
const onErrorRef = (0, react_1.useRef)(options.onError);
|
|
51
|
+
onErrorRef.current = options.onError;
|
|
52
|
+
const transformKey = options.imageTransform ? JSON.stringify(options.imageTransform) : '';
|
|
53
|
+
const transformRef = (0, react_1.useRef)(options.imageTransform);
|
|
54
|
+
transformRef.current = options.imageTransform;
|
|
55
|
+
// First render: whatever the store knows, so the page paints on frame one.
|
|
56
|
+
const [state, setState] = (0, react_1.useState)(() => fromCache(configured && handle ? (0, productStore_1.peekProduct)(handle) : null, !!handle));
|
|
57
|
+
const [attempt, setAttempt] = (0, react_1.useState)(0);
|
|
58
|
+
const requestId = (0, react_1.useRef)(0);
|
|
59
|
+
const freshNext = (0, react_1.useRef)(false);
|
|
60
|
+
(0, react_1.useEffect)(() => {
|
|
61
|
+
if (!handle || !configured) {
|
|
62
|
+
requestId.current += 1;
|
|
63
|
+
setState(EMPTY);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const id = (requestId.current += 1);
|
|
67
|
+
const fresh = freshNext.current;
|
|
68
|
+
freshNext.current = false;
|
|
69
|
+
const cached = (0, productStore_1.peekProduct)(handle);
|
|
70
|
+
if ((cached === null || cached === void 0 ? void 0 : cached.full) && !fresh && Date.now() - cached.fullAt < useProductFeed_1.REVALIDATE_AFTER_MS) {
|
|
71
|
+
setState(fromCache(cached, false));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
setState((current) => {
|
|
75
|
+
if (cached)
|
|
76
|
+
return fromCache(cached, true);
|
|
77
|
+
// A refresh keeps what is on screen until the fresh product lands.
|
|
78
|
+
if (fresh && current.preview)
|
|
79
|
+
return { ...current, refreshing: true, error: false };
|
|
80
|
+
return { ...EMPTY, loading: true };
|
|
81
|
+
});
|
|
82
|
+
shopify_1.shopify.products
|
|
83
|
+
.byHandle(handle, { fresh, imageTransform: transformRef.current })
|
|
84
|
+
.then((product) => {
|
|
85
|
+
var _a, _b;
|
|
86
|
+
if (requestId.current !== id)
|
|
87
|
+
return;
|
|
88
|
+
if (!product) {
|
|
89
|
+
setState({ ...EMPTY, notFound: true });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
setState({
|
|
93
|
+
product,
|
|
94
|
+
preview: {
|
|
95
|
+
id: product.id,
|
|
96
|
+
handle: product.handle,
|
|
97
|
+
title: product.title,
|
|
98
|
+
featuredImage: (_a = product.featuredImage) !== null && _a !== void 0 ? _a : null,
|
|
99
|
+
priceRange: product.priceRange,
|
|
100
|
+
compareAtPriceRange: (_b = product.compareAtPriceRange) !== null && _b !== void 0 ? _b : null,
|
|
101
|
+
},
|
|
102
|
+
level: 'full',
|
|
103
|
+
loading: false,
|
|
104
|
+
refreshing: false,
|
|
105
|
+
error: false,
|
|
106
|
+
notFound: false,
|
|
107
|
+
});
|
|
108
|
+
})
|
|
109
|
+
.catch((caught) => {
|
|
110
|
+
var _a;
|
|
111
|
+
(_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, caught, { at: 'useProduct', handle });
|
|
112
|
+
if (requestId.current !== id)
|
|
113
|
+
return;
|
|
114
|
+
// Whatever was shown stays; the page can say the fresh read failed.
|
|
115
|
+
setState((current) => ({ ...current, loading: false, refreshing: false, error: true }));
|
|
116
|
+
});
|
|
117
|
+
// Keyed on the transform's serialisation; the object changes identity every render.
|
|
118
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
119
|
+
}, [configured, handle, attempt, transformKey]);
|
|
120
|
+
const refresh = (0, react_1.useCallback)(() => {
|
|
121
|
+
freshNext.current = true;
|
|
122
|
+
setAttempt((n) => n + 1);
|
|
123
|
+
}, []);
|
|
124
|
+
return { ...state, refresh };
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=useProduct.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useProduct.js","sourceRoot":"","sources":["../../src/react/useProduct.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Filter, PageInfo, Product, ProductFilter } from '../types';
|
|
2
|
+
/** A cached first page younger than this is shown without asking the network again. */
|
|
3
|
+
export declare const REVALIDATE_AFTER_MS: number;
|
|
4
|
+
export interface FeedPage {
|
|
5
|
+
nodes: Product[];
|
|
6
|
+
pageInfo: PageInfo;
|
|
7
|
+
filters?: Filter[];
|
|
8
|
+
title?: string | null;
|
|
9
|
+
totalCount?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface FeedSource {
|
|
12
|
+
/** Which feed this is (`collection:<handle>`, `search:<term>`); null when there is nothing to read. */
|
|
13
|
+
id: string | null;
|
|
14
|
+
/** What else shapes the first page: sort, page size. Fixed filters are passed separately. */
|
|
15
|
+
paramsKey: string;
|
|
16
|
+
/** Filters the app always applies; the shopper's selection goes on top. */
|
|
17
|
+
fixedFilters?: ProductFilter[];
|
|
18
|
+
fetch(args: {
|
|
19
|
+
after?: string;
|
|
20
|
+
filters: ProductFilter[] | undefined;
|
|
21
|
+
fresh: boolean;
|
|
22
|
+
}): Promise<FeedPage>;
|
|
23
|
+
onError?: (error: unknown, phase: 'first' | 'more') => void;
|
|
24
|
+
}
|
|
25
|
+
export interface ProductFeedState {
|
|
26
|
+
products: Product[];
|
|
27
|
+
/** Null until a first page is known, so a screen can render its own heading first. */
|
|
28
|
+
title: string | null;
|
|
29
|
+
/** The facets Shopify offers for these products under the current filters. */
|
|
30
|
+
availableFilters: Filter[];
|
|
31
|
+
/** Search only: how many products match. */
|
|
32
|
+
totalCount?: number;
|
|
33
|
+
/** Nothing to show yet: no cached page, and the first read is in flight. */
|
|
34
|
+
loading: boolean;
|
|
35
|
+
/** A later page is in flight: the grid keeps what it has and shows a footer spinner. */
|
|
36
|
+
loadingMore: boolean;
|
|
37
|
+
/** A cached page is on screen while the fresh one is read. No spinner needed. */
|
|
38
|
+
refreshing: boolean;
|
|
39
|
+
hasMore: boolean;
|
|
40
|
+
error: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface ProductFeedResult extends ProductFeedState {
|
|
43
|
+
loadMore: () => void;
|
|
44
|
+
/** Re-reads the first page from the network. The name for an error state's button. */
|
|
45
|
+
retry: () => void;
|
|
46
|
+
/** Re-reads the first page from the network, keeping what is on screen until it lands. */
|
|
47
|
+
refresh: () => void;
|
|
48
|
+
/** The shopper's filter selection, as the `input` strings of the values they picked. */
|
|
49
|
+
selectedFilters: string[];
|
|
50
|
+
/** Replace the selection. An input that isn't a Shopify filter (unparseable JSON) is dropped. */
|
|
51
|
+
setFilters: (inputs: string[]) => void;
|
|
52
|
+
clearFilters: () => void;
|
|
53
|
+
/** True while the shopper has at least one filter selected. */
|
|
54
|
+
filterActive: boolean;
|
|
55
|
+
}
|
|
56
|
+
export declare function useProductFeed(source: FeedSource): ProductFeedResult;
|
|
57
|
+
//# sourceMappingURL=useProductFeed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useProductFeed.d.ts","sourceRoot":"","sources":["../../src/react/useProductFeed.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzE,uFAAuF;AACvF,eAAO,MAAM,mBAAmB,QAAY,CAAC;AAE7C,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,uGAAuG;IACvG,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,6FAA6F;IAC7F,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,aAAa,EAAE,CAAC;IAC/B,KAAK,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,aAAa,EAAE,GAAG,SAAS,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;CAC7D;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,sFAAsF;IACtF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;IACjB,wFAAwF;IACxF,WAAW,EAAE,OAAO,CAAC;IACrB,iFAAiF;IACjF,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,sFAAsF;IACtF,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,0FAA0F;IAC1F,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,wFAAwF;IACxF,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iGAAiG;IACjG,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,+DAA+D;IAC/D,YAAY,EAAE,OAAO,CAAC;CACvB;AAwCD,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,iBAAiB,CA4KpE"}
|