@shopify/hydrogen-react 2024.10.1 → 2025.1.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/customer-account.schema.json +1 -1
- package/dist/browser-dev/CartLineProvider.mjs.map +1 -1
- package/dist/browser-dev/CartProvider.mjs.map +1 -1
- package/dist/browser-dev/Image.mjs.map +1 -1
- package/dist/browser-dev/ModelViewer.mjs.map +1 -1
- package/dist/browser-dev/Money.mjs.map +1 -1
- package/dist/browser-dev/Video.mjs.map +1 -1
- package/dist/browser-dev/codegen.helpers.mjs.map +1 -1
- package/dist/browser-dev/getProductOptions.mjs +11 -9
- package/dist/browser-dev/getProductOptions.mjs.map +1 -1
- package/dist/browser-dev/optionValueDecoder.mjs +10 -8
- package/dist/browser-dev/optionValueDecoder.mjs.map +1 -1
- package/dist/browser-dev/packages/hydrogen-react/package.json.mjs +1 -1
- package/dist/browser-dev/storefront-api-constants.mjs +1 -1
- package/dist/browser-dev/storefront-api-constants.mjs.map +1 -1
- package/dist/browser-dev/useCartAPIStateMachine.mjs.map +1 -1
- package/dist/browser-dev/useCartActions.mjs.map +1 -1
- package/dist/browser-prod/CartLineProvider.mjs.map +1 -1
- package/dist/browser-prod/CartProvider.mjs.map +1 -1
- package/dist/browser-prod/Image.mjs.map +1 -1
- package/dist/browser-prod/ModelViewer.mjs.map +1 -1
- package/dist/browser-prod/Money.mjs.map +1 -1
- package/dist/browser-prod/Video.mjs.map +1 -1
- package/dist/browser-prod/codegen.helpers.mjs.map +1 -1
- package/dist/browser-prod/getProductOptions.mjs +11 -9
- package/dist/browser-prod/getProductOptions.mjs.map +1 -1
- package/dist/browser-prod/optionValueDecoder.mjs +10 -8
- package/dist/browser-prod/optionValueDecoder.mjs.map +1 -1
- package/dist/browser-prod/packages/hydrogen-react/package.json.mjs +1 -1
- package/dist/browser-prod/storefront-api-constants.mjs +1 -1
- package/dist/browser-prod/storefront-api-constants.mjs.map +1 -1
- package/dist/browser-prod/useCartAPIStateMachine.mjs.map +1 -1
- package/dist/browser-prod/useCartActions.mjs.map +1 -1
- package/dist/node-dev/CartLineProvider.js.map +1 -1
- package/dist/node-dev/CartLineProvider.mjs.map +1 -1
- package/dist/node-dev/CartProvider.js.map +1 -1
- package/dist/node-dev/CartProvider.mjs.map +1 -1
- package/dist/node-dev/Image.js.map +1 -1
- package/dist/node-dev/Image.mjs.map +1 -1
- package/dist/node-dev/ModelViewer.js.map +1 -1
- package/dist/node-dev/ModelViewer.mjs.map +1 -1
- package/dist/node-dev/Money.js.map +1 -1
- package/dist/node-dev/Money.mjs.map +1 -1
- package/dist/node-dev/Video.js.map +1 -1
- package/dist/node-dev/Video.mjs.map +1 -1
- package/dist/node-dev/codegen.helpers.js.map +1 -1
- package/dist/node-dev/codegen.helpers.mjs.map +1 -1
- package/dist/node-dev/getProductOptions.js +11 -9
- package/dist/node-dev/getProductOptions.js.map +1 -1
- package/dist/node-dev/getProductOptions.mjs +11 -9
- package/dist/node-dev/getProductOptions.mjs.map +1 -1
- package/dist/node-dev/optionValueDecoder.js +10 -8
- package/dist/node-dev/optionValueDecoder.js.map +1 -1
- package/dist/node-dev/optionValueDecoder.mjs +10 -8
- package/dist/node-dev/optionValueDecoder.mjs.map +1 -1
- package/dist/node-dev/packages/hydrogen-react/package.json.js +1 -1
- package/dist/node-dev/packages/hydrogen-react/package.json.mjs +1 -1
- package/dist/node-dev/storefront-api-constants.js +1 -1
- package/dist/node-dev/storefront-api-constants.js.map +1 -1
- package/dist/node-dev/storefront-api-constants.mjs +1 -1
- package/dist/node-dev/storefront-api-constants.mjs.map +1 -1
- package/dist/node-dev/useCartAPIStateMachine.js.map +1 -1
- package/dist/node-dev/useCartAPIStateMachine.mjs.map +1 -1
- package/dist/node-dev/useCartActions.js.map +1 -1
- package/dist/node-dev/useCartActions.mjs.map +1 -1
- package/dist/node-prod/CartLineProvider.js.map +1 -1
- package/dist/node-prod/CartLineProvider.mjs.map +1 -1
- package/dist/node-prod/CartProvider.js.map +1 -1
- package/dist/node-prod/CartProvider.mjs.map +1 -1
- package/dist/node-prod/Image.js.map +1 -1
- package/dist/node-prod/Image.mjs.map +1 -1
- package/dist/node-prod/ModelViewer.js.map +1 -1
- package/dist/node-prod/ModelViewer.mjs.map +1 -1
- package/dist/node-prod/Money.js.map +1 -1
- package/dist/node-prod/Money.mjs.map +1 -1
- package/dist/node-prod/Video.js.map +1 -1
- package/dist/node-prod/Video.mjs.map +1 -1
- package/dist/node-prod/codegen.helpers.js.map +1 -1
- package/dist/node-prod/codegen.helpers.mjs.map +1 -1
- package/dist/node-prod/getProductOptions.js +11 -9
- package/dist/node-prod/getProductOptions.js.map +1 -1
- package/dist/node-prod/getProductOptions.mjs +11 -9
- package/dist/node-prod/getProductOptions.mjs.map +1 -1
- package/dist/node-prod/optionValueDecoder.js +10 -8
- package/dist/node-prod/optionValueDecoder.js.map +1 -1
- package/dist/node-prod/optionValueDecoder.mjs +10 -8
- package/dist/node-prod/optionValueDecoder.mjs.map +1 -1
- package/dist/node-prod/packages/hydrogen-react/package.json.js +1 -1
- package/dist/node-prod/packages/hydrogen-react/package.json.mjs +1 -1
- package/dist/node-prod/storefront-api-constants.js +1 -1
- package/dist/node-prod/storefront-api-constants.js.map +1 -1
- package/dist/node-prod/storefront-api-constants.mjs +1 -1
- package/dist/node-prod/storefront-api-constants.mjs.map +1 -1
- package/dist/node-prod/useCartAPIStateMachine.js.map +1 -1
- package/dist/node-prod/useCartAPIStateMachine.mjs.map +1 -1
- package/dist/node-prod/useCartActions.js.map +1 -1
- package/dist/node-prod/useCartActions.mjs.map +1 -1
- package/dist/types/CartLineProvider.d.ts +1 -1
- package/dist/types/CartProvider.d.ts +2 -2
- package/dist/types/Image.d.ts +1 -1
- package/dist/types/ModelViewer.d.ts +1 -1
- package/dist/types/Money.d.ts +1 -1
- package/dist/types/Video.d.ts +1 -1
- package/dist/types/codegen.helpers.d.ts +2 -2
- package/dist/types/optionValueDecoder.d.ts +2 -2
- package/dist/types/storefront-api-constants.d.ts +1 -1
- package/dist/types/storefront-api-types.d.ts +641 -127
- package/dist/types/useCartAPIStateMachine.d.ts +2 -2
- package/dist/types/useCartActions.d.ts +2 -2
- package/dist/umd/hydrogen-react.dev.js +23 -19
- package/dist/umd/hydrogen-react.dev.js.map +1 -1
- package/dist/umd/hydrogen-react.prod.js +7 -7
- package/dist/umd/hydrogen-react.prod.js.map +1 -1
- package/package.json +3 -3
- package/storefront.schema.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CartLineProvider.mjs","sources":["../../src/CartLineProvider.tsx"],"sourcesContent":["import {useContext, createContext, type ReactNode} from 'react';\nimport {\n ComponentizableCartLine,\n type CartLine,\n} from './storefront-api-types.js';\nimport type {PartialDeep} from 'type-fest';\n\ntype CartLinePartialDeep = PartialDeep<\n CartLine | ComponentizableCartLine,\n {recurseIntoArrays: true}\n>;\n\nexport const CartLineContext = createContext<CartLinePartialDeep | null>(null);\n\n/**\n * The `useCartLine` hook provides access to the [CartLine object](https://shopify.dev/api/storefront/
|
|
1
|
+
{"version":3,"file":"CartLineProvider.mjs","sources":["../../src/CartLineProvider.tsx"],"sourcesContent":["import {useContext, createContext, type ReactNode} from 'react';\nimport {\n ComponentizableCartLine,\n type CartLine,\n} from './storefront-api-types.js';\nimport type {PartialDeep} from 'type-fest';\n\ntype CartLinePartialDeep = PartialDeep<\n CartLine | ComponentizableCartLine,\n {recurseIntoArrays: true}\n>;\n\nexport const CartLineContext = createContext<CartLinePartialDeep | null>(null);\n\n/**\n * The `useCartLine` hook provides access to the [CartLine object](https://shopify.dev/api/storefront/2025-01/objects/cartline) from the Storefront API. It must be a descendent of a `CartProvider` component.\n */\nexport function useCartLine(): CartLinePartialDeep {\n const context = useContext(CartLineContext);\n\n if (context == null) {\n throw new Error('Expected a cart line context but none was found');\n }\n\n return context;\n}\n\ntype CartLineProviderProps = {\n /** Any `ReactNode` elements. */\n children: ReactNode;\n /** A cart line object. */\n line: CartLinePartialDeep;\n};\n\n/**\n * The `CartLineProvider` component creates a context for using a cart line.\n */\nexport function CartLineProvider({\n children,\n line,\n}: CartLineProviderProps): JSX.Element {\n return (\n <CartLineContext.Provider value={line}>{children}</CartLineContext.Provider>\n );\n}\n"],"names":[],"mappings":";;AAYa,MAAA,kBAAkB,cAA0C,IAAI;AAKtE,SAAS,cAAmC;AAC3C,QAAA,UAAU,WAAW,eAAe;AAE1C,MAAI,WAAW,MAAM;AACb,UAAA,IAAI,MAAM,iDAAiD;AAAA,EACnE;AAEO,SAAA;AACT;AAYO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AACF,GAAuC;AACrC,6BACG,gBAAgB,UAAhB,EAAyB,OAAO,MAAO,SAAS,CAAA;AAErD;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CartProvider.mjs","sources":["../../src/CartProvider.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useTransition,\n createContext,\n useContext,\n} from 'react';\nimport {\n AttributeInput,\n CartBuyerIdentityInput,\n CartInput,\n CartLineInput,\n CartLineUpdateInput,\n CountryCode,\n LanguageCode,\n Cart as CartType,\n MutationCartNoteUpdateArgs,\n} from './storefront-api-types.js';\nimport {\n BuyerIdentityUpdateEvent,\n CartMachineContext,\n CartMachineEvent,\n CartMachineTypeState,\n CartWithActions,\n CartWithActionsDocs,\n} from './cart-types.js';\nimport {useCartAPIStateMachine} from './useCartAPIStateMachine.js';\nimport {CART_ID_STORAGE_KEY} from './cart-constants.js';\nimport {PartialDeep} from 'type-fest';\nimport {defaultCartFragment} from './cart-queries.js';\nimport {useShop} from './ShopifyProvider.js';\n\nexport const CartContext = createContext<CartWithActions | null>(null);\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\ntype UseCartDocs = () => CartWithActionsDocs;\n\n/**\n * The `useCart` hook provides access to the cart object. It must be a descendent of a `CartProvider` component.\n */\nexport function useCart(): CartWithActions {\n const context = useContext(CartContext);\n\n if (!context) {\n throw new Error('Expected a Cart Context, but no Cart Context was found');\n }\n\n return context;\n}\n\ntype CartProviderProps = {\n /** Any `ReactNode` elements. */\n children: React.ReactNode;\n /** Maximum number of cart lines to fetch. Defaults to 250 cart lines. */\n numCartLines?: number;\n /** A callback that is invoked when the process to create a cart begins, but before the cart is created in the Storefront API. */\n onCreate?: () => void;\n /** A callback that is invoked when the process to add a line item to the cart begins, but before the line item is added to the Storefront API. */\n onLineAdd?: () => void;\n /** A callback that is invoked when the process to remove a line item to the cart begins, but before the line item is removed from the Storefront API. */\n onLineRemove?: () => void;\n /** A callback that is invoked when the process to update a line item in the cart begins, but before the line item is updated in the Storefront API. */\n onLineUpdate?: () => void;\n /** A callback that is invoked when the process to add or update a note in the cart begins, but before the note is added or updated in the Storefront API. */\n onNoteUpdate?: () => void;\n /** A callback that is invoked when the process to update the buyer identity begins, but before the buyer identity is updated in the Storefront API. */\n onBuyerIdentityUpdate?: () => void;\n /** A callback that is invoked when the process to update the cart attributes begins, but before the attributes are updated in the Storefront API. */\n onAttributesUpdate?: () => void;\n /** A callback that is invoked when the process to update the cart discount codes begins, but before the discount codes are updated in the Storefront API. */\n onDiscountCodesUpdate?: () => void;\n /** A callback that is invoked when the process to create a cart completes */\n onCreateComplete?: () => void;\n /** A callback that is invoked when the process to add a line item to the cart completes */\n onLineAddComplete?: () => void;\n /** A callback that is invoked when the process to remove a line item to the cart completes */\n onLineRemoveComplete?: () => void;\n /** A callback that is invoked when the process to update a line item in the cart completes */\n onLineUpdateComplete?: () => void;\n /** A callback that is invoked when the process to add or update a note in the cart completes */\n onNoteUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the buyer identity completes */\n onBuyerIdentityUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the cart attributes completes */\n onAttributesUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the cart discount codes completes */\n onDiscountCodesUpdateComplete?: () => void;\n /** An object with fields that correspond to the Storefront API's [Cart object](https://shopify.dev/api/storefront/2024-10/objects/cart). */\n data?: PartialDeep<CartType, {recurseIntoArrays: true}>;\n /** A fragment used to query the Storefront API's [Cart object](https://shopify.dev/api/storefront/2024-10/objects/cart) for all queries and mutations. A default value is used if no argument is provided. */\n cartFragment?: string;\n /** A customer access token that's accessible on the server if there's a customer login. */\n customerAccessToken?: CartBuyerIdentityInput['customerAccessToken'];\n /** The ISO country code for i18n. */\n countryCode?: CountryCode;\n /** The ISO language code for i18n. */\n languageCode?: LanguageCode;\n};\n\n/**\n * The `CartProvider` component synchronizes the state of the Storefront API Cart and a customer's cart,\n * and allows you to more easily manipulate the cart by adding, removing, and updating it.\n * It could be placed at the root of your app so that your whole app is able to use the `useCart()` hook anywhere.\n *\n * There are props that trigger when a call to the Storefront API is made, such as `onLineAdd={}` when a line is added to the cart.\n * There are also props that trigger when a call to the Storefront API is completed, such as `onLineAddComplete={}` when the fetch request for adding a line to the cart completes.\n *\n * The `CartProvider` component must be a descendant of the `ShopifyProvider` component.\n */\nexport function CartProvider({\n children,\n numCartLines,\n onCreate,\n onLineAdd,\n onLineRemove,\n onLineUpdate,\n onNoteUpdate,\n onBuyerIdentityUpdate,\n onAttributesUpdate,\n onDiscountCodesUpdate,\n onCreateComplete,\n onLineAddComplete,\n onLineRemoveComplete,\n onLineUpdateComplete,\n onNoteUpdateComplete,\n onBuyerIdentityUpdateComplete,\n onAttributesUpdateComplete,\n onDiscountCodesUpdateComplete,\n data: cart,\n cartFragment = defaultCartFragment,\n customerAccessToken,\n countryCode,\n languageCode,\n}: CartProviderProps): JSX.Element {\n const shop = useShop();\n\n if (!shop)\n throw new Error(\n '<CartProvider> needs to be a descendant of <ShopifyProvider>',\n );\n\n countryCode = (\n (countryCode as string) ??\n shop.countryIsoCode ??\n 'US'\n ).toUpperCase() as CountryCode;\n\n languageCode = (\n (languageCode as string) ??\n shop.languageIsoCode ??\n 'EN'\n ).toUpperCase() as LanguageCode;\n\n if (countryCode) countryCode = countryCode.toUpperCase() as CountryCode;\n\n const [prevCountryCode, setPrevCountryCode] = useState(countryCode);\n const [prevCustomerAccessToken, setPrevCustomerAccessToken] =\n useState(customerAccessToken);\n const customerOverridesCountryCode = useRef(false);\n\n if (\n prevCountryCode !== countryCode ||\n prevCustomerAccessToken !== customerAccessToken\n ) {\n setPrevCountryCode(countryCode);\n setPrevCustomerAccessToken(customerAccessToken);\n customerOverridesCountryCode.current = false;\n }\n\n const [cartState, cartSend] = useCartAPIStateMachine({\n numCartLines,\n data: cart,\n cartFragment,\n countryCode,\n languageCode,\n onCartActionEntry(_, event) {\n try {\n switch (event.type) {\n case 'CART_CREATE':\n return onCreate?.();\n case 'CARTLINE_ADD':\n return onLineAdd?.();\n case 'CARTLINE_REMOVE':\n return onLineRemove?.();\n case 'CARTLINE_UPDATE':\n return onLineUpdate?.();\n case 'NOTE_UPDATE':\n return onNoteUpdate?.();\n case 'BUYER_IDENTITY_UPDATE':\n return onBuyerIdentityUpdate?.();\n case 'CART_ATTRIBUTES_UPDATE':\n return onAttributesUpdate?.();\n case 'DISCOUNT_CODES_UPDATE':\n return onDiscountCodesUpdate?.();\n }\n } catch (error) {\n console.error('Cart entry action failed', error);\n }\n },\n onCartActionOptimisticUI(context, event) {\n if (!context.cart) return {...context};\n switch (event.type) {\n case 'CARTLINE_REMOVE':\n return {\n ...context,\n cart: {\n ...context.cart,\n lines: context?.cart?.lines?.filter(\n (line) => line?.id && !event.payload.lines.includes(line?.id),\n ),\n },\n };\n case 'CARTLINE_UPDATE':\n return {\n ...context,\n cart: {\n ...context.cart,\n lines: context?.cart?.lines?.map((line) => {\n const updatedLine = event.payload.lines.find(\n ({id}) => id === line?.id,\n );\n\n if (updatedLine && updatedLine.quantity) {\n return {\n ...line,\n quantity: updatedLine.quantity,\n };\n }\n\n return line;\n }),\n },\n };\n }\n return {...context};\n },\n onCartActionComplete(context, event) {\n const cartActionEvent = event.payload.cartActionEvent;\n try {\n switch (event.type) {\n case 'RESOLVE':\n switch (cartActionEvent.type) {\n case 'CART_CREATE':\n return onCreateComplete?.();\n case 'CARTLINE_ADD':\n return onLineAddComplete?.();\n case 'CARTLINE_REMOVE':\n return onLineRemoveComplete?.();\n case 'CARTLINE_UPDATE':\n return onLineUpdateComplete?.();\n case 'NOTE_UPDATE':\n return onNoteUpdateComplete?.();\n case 'BUYER_IDENTITY_UPDATE':\n if (countryCodeNotUpdated(context, cartActionEvent)) {\n customerOverridesCountryCode.current = true;\n }\n return onBuyerIdentityUpdateComplete?.();\n case 'CART_ATTRIBUTES_UPDATE':\n return onAttributesUpdateComplete?.();\n case 'DISCOUNT_CODES_UPDATE':\n return onDiscountCodesUpdateComplete?.();\n }\n }\n } catch (error) {\n console.error('onCartActionComplete failed', error);\n }\n },\n });\n\n const cartReady = useRef(false);\n const [isCartReady, setIsCartReady] = useState(false);\n const cartCompleted = cartState.matches('cartCompleted');\n\n const countryChanged =\n (cartState.value === 'idle' ||\n cartState.value === 'error' ||\n cartState.value === 'cartCompleted') &&\n countryCode !== cartState?.context?.cart?.buyerIdentity?.countryCode &&\n !cartState.context.errors;\n\n const fetchingFromStorage = useRef(false);\n\n /**\n * Initializes cart with priority in this order:\n * 1. cart props\n * 2. localStorage cartId\n */\n useEffect(() => {\n if (!cartReady.current && !fetchingFromStorage.current) {\n if (!cart && storageAvailable('localStorage')) {\n fetchingFromStorage.current = true;\n try {\n const cartId = window.localStorage.getItem(CART_ID_STORAGE_KEY);\n if (cartId) {\n cartSend({type: 'CART_FETCH', payload: {cartId}});\n }\n } catch (error) {\n console.warn('error fetching cartId');\n console.warn(error);\n }\n }\n cartReady.current = true;\n // Providing a separate cart ready state variable to avoid re-renders in this logic while still being able to pass the reactive status through context.\n setIsCartReady(true);\n }\n }, [cart, cartReady, cartSend]);\n\n // Update cart country code if cart and props countryCode's as different\n useEffect(() => {\n if (!countryChanged || customerOverridesCountryCode.current) return;\n cartSend({\n type: 'BUYER_IDENTITY_UPDATE',\n payload: {buyerIdentity: {countryCode, customerAccessToken}},\n });\n }, [\n countryCode,\n customerAccessToken,\n countryChanged,\n customerOverridesCountryCode,\n cartSend,\n ]);\n\n // send cart events when ready\n const onCartReadySend = useCallback(\n (cartEvent: CartMachineEvent) => {\n if (!cartReady.current) {\n return console.warn(\"Cart isn't ready yet\");\n }\n cartSend(cartEvent);\n },\n [cartSend],\n );\n\n // save cart id to local storage\n useEffect(() => {\n if (cartState?.context?.cart?.id && storageAvailable('localStorage')) {\n try {\n window.localStorage.setItem(\n CART_ID_STORAGE_KEY,\n cartState.context.cart?.id,\n );\n } catch (error) {\n console.warn('Failed to save cartId to localStorage', error);\n }\n }\n }, [cartState?.context?.cart?.id]);\n\n // delete cart from local storage if cart fetched has been completed\n useEffect(() => {\n if (cartCompleted && storageAvailable('localStorage')) {\n try {\n window.localStorage.removeItem(CART_ID_STORAGE_KEY);\n } catch (error) {\n console.warn('Failed to delete cartId from localStorage', error);\n }\n }\n }, [cartCompleted]);\n\n const cartCreate = useCallback(\n (cartInput: CartInput) => {\n if (countryCode && !cartInput.buyerIdentity?.countryCode) {\n if (cartInput.buyerIdentity == null) {\n cartInput.buyerIdentity = {};\n }\n cartInput.buyerIdentity.countryCode = countryCode;\n }\n\n if (\n customerAccessToken &&\n !cartInput.buyerIdentity?.customerAccessToken\n ) {\n if (cartInput.buyerIdentity == null) {\n cartInput.buyerIdentity = {};\n }\n cartInput.buyerIdentity.customerAccessToken = customerAccessToken;\n }\n onCartReadySend({\n type: 'CART_CREATE',\n payload: cartInput,\n });\n },\n [countryCode, customerAccessToken, onCartReadySend],\n );\n\n // Delays the cart state in the context if the page is hydrating\n // preventing suspense boundary errors.\n const cartDisplayState = useDelayedStateUntilHydration(cartState);\n\n const cartContextValue = useMemo<CartWithActions>(() => {\n return {\n ...(cartDisplayState?.context?.cart ?? {lines: [], attributes: []}),\n status: transposeStatus(cartDisplayState.value),\n error: cartDisplayState?.context?.errors,\n totalQuantity: cartDisplayState?.context?.cart?.totalQuantity ?? 0,\n cartCreate,\n cartReady: isCartReady,\n linesAdd(lines: CartLineInput[]): void {\n if (cartDisplayState?.context?.cart?.id) {\n onCartReadySend({\n type: 'CARTLINE_ADD',\n payload: {lines},\n });\n } else {\n cartCreate({lines});\n }\n },\n linesRemove(lines: string[]): void {\n onCartReadySend({\n type: 'CARTLINE_REMOVE',\n payload: {\n lines,\n },\n });\n },\n linesUpdate(lines: CartLineUpdateInput[]): void {\n onCartReadySend({\n type: 'CARTLINE_UPDATE',\n payload: {\n lines,\n },\n });\n },\n noteUpdate(note: MutationCartNoteUpdateArgs['note']): void {\n onCartReadySend({\n type: 'NOTE_UPDATE',\n payload: {\n note,\n },\n });\n },\n buyerIdentityUpdate(buyerIdentity: CartBuyerIdentityInput): void {\n onCartReadySend({\n type: 'BUYER_IDENTITY_UPDATE',\n payload: {\n buyerIdentity,\n },\n });\n },\n cartAttributesUpdate(attributes: AttributeInput[]): void {\n onCartReadySend({\n type: 'CART_ATTRIBUTES_UPDATE',\n payload: {\n attributes,\n },\n });\n },\n discountCodesUpdate(discountCodes: string[]): void {\n onCartReadySend({\n type: 'DISCOUNT_CODES_UPDATE',\n payload: {\n discountCodes,\n },\n });\n },\n cartFragment,\n };\n }, [\n cartCreate,\n isCartReady,\n cartDisplayState?.context?.cart,\n cartDisplayState?.context?.errors,\n cartDisplayState.value,\n cartFragment,\n onCartReadySend,\n ]);\n\n return (\n <CartContext.Provider value={cartContextValue}>\n {children}\n </CartContext.Provider>\n );\n}\n\nfunction transposeStatus(\n status: CartMachineTypeState['value'],\n): CartWithActions['status'] {\n switch (status) {\n case 'uninitialized':\n case 'initializationError':\n return 'uninitialized';\n case 'idle':\n case 'cartCompleted':\n case 'error':\n return 'idle';\n case 'cartFetching':\n return 'fetching';\n case 'cartCreating':\n return 'creating';\n case 'cartLineAdding':\n case 'cartLineRemoving':\n case 'cartLineUpdating':\n case 'noteUpdating':\n case 'buyerIdentityUpdating':\n case 'cartAttributesUpdating':\n case 'discountCodesUpdating':\n return 'updating';\n }\n}\n\n/**\n * Delays a state update until hydration finishes. Useful for preventing suspense boundaries errors when updating a context\n * @remarks this uses startTransition and waits for it to finish.\n */\nfunction useDelayedStateUntilHydration<T>(state: T): T {\n const [isPending, startTransition] = useTransition();\n const [delayedState, setDelayedState] = useState(state);\n\n const firstTimePending = useRef(false);\n if (isPending) {\n firstTimePending.current = true;\n }\n\n const firstTimePendingFinished = useRef(false);\n if (!isPending && firstTimePending.current) {\n firstTimePendingFinished.current = true;\n }\n\n useEffect(() => {\n startTransition(() => {\n if (!firstTimePendingFinished.current) {\n setDelayedState(state);\n }\n });\n }, [state]);\n\n const displayState = firstTimePendingFinished.current ? state : delayedState;\n\n return displayState;\n}\n\n/**\n * Check for storage availability function obtained from\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API\n */\nexport function storageAvailable(\n type: 'localStorage' | 'sessionStorage',\n): boolean {\n let storage;\n try {\n storage = window[type];\n const x = '__storage_test__';\n storage.setItem(x, x);\n storage.removeItem(x);\n return true;\n } catch (e) {\n return !!(\n e instanceof DOMException &&\n // everything except Firefox\n (e.code === 22 ||\n // Firefox\n e.code === 1014 ||\n // test name field too, because code might not be present\n // everything except Firefox\n e.name === 'QuotaExceededError' ||\n // Firefox\n e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&\n // acknowledge QuotaExceededError only if there's something already stored\n storage &&\n storage.length !== 0\n );\n }\n}\n\nfunction countryCodeNotUpdated(\n context: CartMachineContext,\n event: BuyerIdentityUpdateEvent,\n): boolean {\n return !!(\n event.payload.buyerIdentity.countryCode &&\n context.cart?.buyerIdentity?.countryCode !==\n event.payload.buyerIdentity.countryCode\n );\n}\n"],"names":["_b","_a","_d","_c"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CartProvider.mjs","sources":["../../src/CartProvider.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useTransition,\n createContext,\n useContext,\n} from 'react';\nimport {\n AttributeInput,\n CartBuyerIdentityInput,\n CartInput,\n CartLineInput,\n CartLineUpdateInput,\n CountryCode,\n LanguageCode,\n Cart as CartType,\n MutationCartNoteUpdateArgs,\n} from './storefront-api-types.js';\nimport {\n BuyerIdentityUpdateEvent,\n CartMachineContext,\n CartMachineEvent,\n CartMachineTypeState,\n CartWithActions,\n CartWithActionsDocs,\n} from './cart-types.js';\nimport {useCartAPIStateMachine} from './useCartAPIStateMachine.js';\nimport {CART_ID_STORAGE_KEY} from './cart-constants.js';\nimport {PartialDeep} from 'type-fest';\nimport {defaultCartFragment} from './cart-queries.js';\nimport {useShop} from './ShopifyProvider.js';\n\nexport const CartContext = createContext<CartWithActions | null>(null);\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\ntype UseCartDocs = () => CartWithActionsDocs;\n\n/**\n * The `useCart` hook provides access to the cart object. It must be a descendent of a `CartProvider` component.\n */\nexport function useCart(): CartWithActions {\n const context = useContext(CartContext);\n\n if (!context) {\n throw new Error('Expected a Cart Context, but no Cart Context was found');\n }\n\n return context;\n}\n\ntype CartProviderProps = {\n /** Any `ReactNode` elements. */\n children: React.ReactNode;\n /** Maximum number of cart lines to fetch. Defaults to 250 cart lines. */\n numCartLines?: number;\n /** A callback that is invoked when the process to create a cart begins, but before the cart is created in the Storefront API. */\n onCreate?: () => void;\n /** A callback that is invoked when the process to add a line item to the cart begins, but before the line item is added to the Storefront API. */\n onLineAdd?: () => void;\n /** A callback that is invoked when the process to remove a line item to the cart begins, but before the line item is removed from the Storefront API. */\n onLineRemove?: () => void;\n /** A callback that is invoked when the process to update a line item in the cart begins, but before the line item is updated in the Storefront API. */\n onLineUpdate?: () => void;\n /** A callback that is invoked when the process to add or update a note in the cart begins, but before the note is added or updated in the Storefront API. */\n onNoteUpdate?: () => void;\n /** A callback that is invoked when the process to update the buyer identity begins, but before the buyer identity is updated in the Storefront API. */\n onBuyerIdentityUpdate?: () => void;\n /** A callback that is invoked when the process to update the cart attributes begins, but before the attributes are updated in the Storefront API. */\n onAttributesUpdate?: () => void;\n /** A callback that is invoked when the process to update the cart discount codes begins, but before the discount codes are updated in the Storefront API. */\n onDiscountCodesUpdate?: () => void;\n /** A callback that is invoked when the process to create a cart completes */\n onCreateComplete?: () => void;\n /** A callback that is invoked when the process to add a line item to the cart completes */\n onLineAddComplete?: () => void;\n /** A callback that is invoked when the process to remove a line item to the cart completes */\n onLineRemoveComplete?: () => void;\n /** A callback that is invoked when the process to update a line item in the cart completes */\n onLineUpdateComplete?: () => void;\n /** A callback that is invoked when the process to add or update a note in the cart completes */\n onNoteUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the buyer identity completes */\n onBuyerIdentityUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the cart attributes completes */\n onAttributesUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the cart discount codes completes */\n onDiscountCodesUpdateComplete?: () => void;\n /** An object with fields that correspond to the Storefront API's [Cart object](https://shopify.dev/api/storefront/2025-01/objects/cart). */\n data?: PartialDeep<CartType, {recurseIntoArrays: true}>;\n /** A fragment used to query the Storefront API's [Cart object](https://shopify.dev/api/storefront/2025-01/objects/cart) for all queries and mutations. A default value is used if no argument is provided. */\n cartFragment?: string;\n /** A customer access token that's accessible on the server if there's a customer login. */\n customerAccessToken?: CartBuyerIdentityInput['customerAccessToken'];\n /** The ISO country code for i18n. */\n countryCode?: CountryCode;\n /** The ISO language code for i18n. */\n languageCode?: LanguageCode;\n};\n\n/**\n * The `CartProvider` component synchronizes the state of the Storefront API Cart and a customer's cart,\n * and allows you to more easily manipulate the cart by adding, removing, and updating it.\n * It could be placed at the root of your app so that your whole app is able to use the `useCart()` hook anywhere.\n *\n * There are props that trigger when a call to the Storefront API is made, such as `onLineAdd={}` when a line is added to the cart.\n * There are also props that trigger when a call to the Storefront API is completed, such as `onLineAddComplete={}` when the fetch request for adding a line to the cart completes.\n *\n * The `CartProvider` component must be a descendant of the `ShopifyProvider` component.\n */\nexport function CartProvider({\n children,\n numCartLines,\n onCreate,\n onLineAdd,\n onLineRemove,\n onLineUpdate,\n onNoteUpdate,\n onBuyerIdentityUpdate,\n onAttributesUpdate,\n onDiscountCodesUpdate,\n onCreateComplete,\n onLineAddComplete,\n onLineRemoveComplete,\n onLineUpdateComplete,\n onNoteUpdateComplete,\n onBuyerIdentityUpdateComplete,\n onAttributesUpdateComplete,\n onDiscountCodesUpdateComplete,\n data: cart,\n cartFragment = defaultCartFragment,\n customerAccessToken,\n countryCode,\n languageCode,\n}: CartProviderProps): JSX.Element {\n const shop = useShop();\n\n if (!shop)\n throw new Error(\n '<CartProvider> needs to be a descendant of <ShopifyProvider>',\n );\n\n countryCode = (\n (countryCode as string) ??\n shop.countryIsoCode ??\n 'US'\n ).toUpperCase() as CountryCode;\n\n languageCode = (\n (languageCode as string) ??\n shop.languageIsoCode ??\n 'EN'\n ).toUpperCase() as LanguageCode;\n\n if (countryCode) countryCode = countryCode.toUpperCase() as CountryCode;\n\n const [prevCountryCode, setPrevCountryCode] = useState(countryCode);\n const [prevCustomerAccessToken, setPrevCustomerAccessToken] =\n useState(customerAccessToken);\n const customerOverridesCountryCode = useRef(false);\n\n if (\n prevCountryCode !== countryCode ||\n prevCustomerAccessToken !== customerAccessToken\n ) {\n setPrevCountryCode(countryCode);\n setPrevCustomerAccessToken(customerAccessToken);\n customerOverridesCountryCode.current = false;\n }\n\n const [cartState, cartSend] = useCartAPIStateMachine({\n numCartLines,\n data: cart,\n cartFragment,\n countryCode,\n languageCode,\n onCartActionEntry(_, event) {\n try {\n switch (event.type) {\n case 'CART_CREATE':\n return onCreate?.();\n case 'CARTLINE_ADD':\n return onLineAdd?.();\n case 'CARTLINE_REMOVE':\n return onLineRemove?.();\n case 'CARTLINE_UPDATE':\n return onLineUpdate?.();\n case 'NOTE_UPDATE':\n return onNoteUpdate?.();\n case 'BUYER_IDENTITY_UPDATE':\n return onBuyerIdentityUpdate?.();\n case 'CART_ATTRIBUTES_UPDATE':\n return onAttributesUpdate?.();\n case 'DISCOUNT_CODES_UPDATE':\n return onDiscountCodesUpdate?.();\n }\n } catch (error) {\n console.error('Cart entry action failed', error);\n }\n },\n onCartActionOptimisticUI(context, event) {\n if (!context.cart) return {...context};\n switch (event.type) {\n case 'CARTLINE_REMOVE':\n return {\n ...context,\n cart: {\n ...context.cart,\n lines: context?.cart?.lines?.filter(\n (line) => line?.id && !event.payload.lines.includes(line?.id),\n ),\n },\n };\n case 'CARTLINE_UPDATE':\n return {\n ...context,\n cart: {\n ...context.cart,\n lines: context?.cart?.lines?.map((line) => {\n const updatedLine = event.payload.lines.find(\n ({id}) => id === line?.id,\n );\n\n if (updatedLine && updatedLine.quantity) {\n return {\n ...line,\n quantity: updatedLine.quantity,\n };\n }\n\n return line;\n }),\n },\n };\n }\n return {...context};\n },\n onCartActionComplete(context, event) {\n const cartActionEvent = event.payload.cartActionEvent;\n try {\n switch (event.type) {\n case 'RESOLVE':\n switch (cartActionEvent.type) {\n case 'CART_CREATE':\n return onCreateComplete?.();\n case 'CARTLINE_ADD':\n return onLineAddComplete?.();\n case 'CARTLINE_REMOVE':\n return onLineRemoveComplete?.();\n case 'CARTLINE_UPDATE':\n return onLineUpdateComplete?.();\n case 'NOTE_UPDATE':\n return onNoteUpdateComplete?.();\n case 'BUYER_IDENTITY_UPDATE':\n if (countryCodeNotUpdated(context, cartActionEvent)) {\n customerOverridesCountryCode.current = true;\n }\n return onBuyerIdentityUpdateComplete?.();\n case 'CART_ATTRIBUTES_UPDATE':\n return onAttributesUpdateComplete?.();\n case 'DISCOUNT_CODES_UPDATE':\n return onDiscountCodesUpdateComplete?.();\n }\n }\n } catch (error) {\n console.error('onCartActionComplete failed', error);\n }\n },\n });\n\n const cartReady = useRef(false);\n const [isCartReady, setIsCartReady] = useState(false);\n const cartCompleted = cartState.matches('cartCompleted');\n\n const countryChanged =\n (cartState.value === 'idle' ||\n cartState.value === 'error' ||\n cartState.value === 'cartCompleted') &&\n countryCode !== cartState?.context?.cart?.buyerIdentity?.countryCode &&\n !cartState.context.errors;\n\n const fetchingFromStorage = useRef(false);\n\n /**\n * Initializes cart with priority in this order:\n * 1. cart props\n * 2. localStorage cartId\n */\n useEffect(() => {\n if (!cartReady.current && !fetchingFromStorage.current) {\n if (!cart && storageAvailable('localStorage')) {\n fetchingFromStorage.current = true;\n try {\n const cartId = window.localStorage.getItem(CART_ID_STORAGE_KEY);\n if (cartId) {\n cartSend({type: 'CART_FETCH', payload: {cartId}});\n }\n } catch (error) {\n console.warn('error fetching cartId');\n console.warn(error);\n }\n }\n cartReady.current = true;\n // Providing a separate cart ready state variable to avoid re-renders in this logic while still being able to pass the reactive status through context.\n setIsCartReady(true);\n }\n }, [cart, cartReady, cartSend]);\n\n // Update cart country code if cart and props countryCode's as different\n useEffect(() => {\n if (!countryChanged || customerOverridesCountryCode.current) return;\n cartSend({\n type: 'BUYER_IDENTITY_UPDATE',\n payload: {buyerIdentity: {countryCode, customerAccessToken}},\n });\n }, [\n countryCode,\n customerAccessToken,\n countryChanged,\n customerOverridesCountryCode,\n cartSend,\n ]);\n\n // send cart events when ready\n const onCartReadySend = useCallback(\n (cartEvent: CartMachineEvent) => {\n if (!cartReady.current) {\n return console.warn(\"Cart isn't ready yet\");\n }\n cartSend(cartEvent);\n },\n [cartSend],\n );\n\n // save cart id to local storage\n useEffect(() => {\n if (cartState?.context?.cart?.id && storageAvailable('localStorage')) {\n try {\n window.localStorage.setItem(\n CART_ID_STORAGE_KEY,\n cartState.context.cart?.id,\n );\n } catch (error) {\n console.warn('Failed to save cartId to localStorage', error);\n }\n }\n }, [cartState?.context?.cart?.id]);\n\n // delete cart from local storage if cart fetched has been completed\n useEffect(() => {\n if (cartCompleted && storageAvailable('localStorage')) {\n try {\n window.localStorage.removeItem(CART_ID_STORAGE_KEY);\n } catch (error) {\n console.warn('Failed to delete cartId from localStorage', error);\n }\n }\n }, [cartCompleted]);\n\n const cartCreate = useCallback(\n (cartInput: CartInput) => {\n if (countryCode && !cartInput.buyerIdentity?.countryCode) {\n if (cartInput.buyerIdentity == null) {\n cartInput.buyerIdentity = {};\n }\n cartInput.buyerIdentity.countryCode = countryCode;\n }\n\n if (\n customerAccessToken &&\n !cartInput.buyerIdentity?.customerAccessToken\n ) {\n if (cartInput.buyerIdentity == null) {\n cartInput.buyerIdentity = {};\n }\n cartInput.buyerIdentity.customerAccessToken = customerAccessToken;\n }\n onCartReadySend({\n type: 'CART_CREATE',\n payload: cartInput,\n });\n },\n [countryCode, customerAccessToken, onCartReadySend],\n );\n\n // Delays the cart state in the context if the page is hydrating\n // preventing suspense boundary errors.\n const cartDisplayState = useDelayedStateUntilHydration(cartState);\n\n const cartContextValue = useMemo<CartWithActions>(() => {\n return {\n ...(cartDisplayState?.context?.cart ?? {lines: [], attributes: []}),\n status: transposeStatus(cartDisplayState.value),\n error: cartDisplayState?.context?.errors,\n totalQuantity: cartDisplayState?.context?.cart?.totalQuantity ?? 0,\n cartCreate,\n cartReady: isCartReady,\n linesAdd(lines: CartLineInput[]): void {\n if (cartDisplayState?.context?.cart?.id) {\n onCartReadySend({\n type: 'CARTLINE_ADD',\n payload: {lines},\n });\n } else {\n cartCreate({lines});\n }\n },\n linesRemove(lines: string[]): void {\n onCartReadySend({\n type: 'CARTLINE_REMOVE',\n payload: {\n lines,\n },\n });\n },\n linesUpdate(lines: CartLineUpdateInput[]): void {\n onCartReadySend({\n type: 'CARTLINE_UPDATE',\n payload: {\n lines,\n },\n });\n },\n noteUpdate(note: MutationCartNoteUpdateArgs['note']): void {\n onCartReadySend({\n type: 'NOTE_UPDATE',\n payload: {\n note,\n },\n });\n },\n buyerIdentityUpdate(buyerIdentity: CartBuyerIdentityInput): void {\n onCartReadySend({\n type: 'BUYER_IDENTITY_UPDATE',\n payload: {\n buyerIdentity,\n },\n });\n },\n cartAttributesUpdate(attributes: AttributeInput[]): void {\n onCartReadySend({\n type: 'CART_ATTRIBUTES_UPDATE',\n payload: {\n attributes,\n },\n });\n },\n discountCodesUpdate(discountCodes: string[]): void {\n onCartReadySend({\n type: 'DISCOUNT_CODES_UPDATE',\n payload: {\n discountCodes,\n },\n });\n },\n cartFragment,\n };\n }, [\n cartCreate,\n isCartReady,\n cartDisplayState?.context?.cart,\n cartDisplayState?.context?.errors,\n cartDisplayState.value,\n cartFragment,\n onCartReadySend,\n ]);\n\n return (\n <CartContext.Provider value={cartContextValue}>\n {children}\n </CartContext.Provider>\n );\n}\n\nfunction transposeStatus(\n status: CartMachineTypeState['value'],\n): CartWithActions['status'] {\n switch (status) {\n case 'uninitialized':\n case 'initializationError':\n return 'uninitialized';\n case 'idle':\n case 'cartCompleted':\n case 'error':\n return 'idle';\n case 'cartFetching':\n return 'fetching';\n case 'cartCreating':\n return 'creating';\n case 'cartLineAdding':\n case 'cartLineRemoving':\n case 'cartLineUpdating':\n case 'noteUpdating':\n case 'buyerIdentityUpdating':\n case 'cartAttributesUpdating':\n case 'discountCodesUpdating':\n return 'updating';\n }\n}\n\n/**\n * Delays a state update until hydration finishes. Useful for preventing suspense boundaries errors when updating a context\n * @remarks this uses startTransition and waits for it to finish.\n */\nfunction useDelayedStateUntilHydration<T>(state: T): T {\n const [isPending, startTransition] = useTransition();\n const [delayedState, setDelayedState] = useState(state);\n\n const firstTimePending = useRef(false);\n if (isPending) {\n firstTimePending.current = true;\n }\n\n const firstTimePendingFinished = useRef(false);\n if (!isPending && firstTimePending.current) {\n firstTimePendingFinished.current = true;\n }\n\n useEffect(() => {\n startTransition(() => {\n if (!firstTimePendingFinished.current) {\n setDelayedState(state);\n }\n });\n }, [state]);\n\n const displayState = firstTimePendingFinished.current ? state : delayedState;\n\n return displayState;\n}\n\n/**\n * Check for storage availability function obtained from\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API\n */\nexport function storageAvailable(\n type: 'localStorage' | 'sessionStorage',\n): boolean {\n let storage;\n try {\n storage = window[type];\n const x = '__storage_test__';\n storage.setItem(x, x);\n storage.removeItem(x);\n return true;\n } catch (e) {\n return !!(\n e instanceof DOMException &&\n // everything except Firefox\n (e.code === 22 ||\n // Firefox\n e.code === 1014 ||\n // test name field too, because code might not be present\n // everything except Firefox\n e.name === 'QuotaExceededError' ||\n // Firefox\n e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&\n // acknowledge QuotaExceededError only if there's something already stored\n storage &&\n storage.length !== 0\n );\n }\n}\n\nfunction countryCodeNotUpdated(\n context: CartMachineContext,\n event: BuyerIdentityUpdateEvent,\n): boolean {\n return !!(\n event.payload.buyerIdentity.countryCode &&\n context.cart?.buyerIdentity?.countryCode !==\n event.payload.buyerIdentity.countryCode\n );\n}\n"],"names":["_b","_a","_d","_c"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Image.mjs","sources":["../../src/Image.tsx"],"sourcesContent":["/* eslint-disable eslint-comments/disable-enable-pair */\n/* eslint-disable @typescript-eslint/explicit-function-return-type */\n/* eslint-disable hydrogen/prefer-image-component */\nimport * as React from 'react';\nimport type {PartialDeep} from 'type-fest';\nimport type {Image as ImageType} from './storefront-api-types.js';\n\n/*\n * An optional prop you can use to change the\n * default srcSet generation behaviour\n */\ntype SrcSetOptions = {\n /** The number of sizes to generate */\n intervals: number;\n /** The smallest image size */\n startingWidth: number;\n /** The increment by which to increase for each size, in pixels */\n incrementSize: number;\n /** The size used for placeholder fallback images */\n placeholderWidth: number;\n};\n\ntype NormalizedProps = {\n alt: string;\n aspectRatio: string | undefined;\n height: string;\n src: string | undefined;\n width: string;\n};\n\nexport type LoaderParams = {\n /** The base URL of the image */\n src?: ImageType['url'];\n /** The URL param that controls width */\n width?: number;\n /** The URL param that controls height */\n height?: number;\n /** The URL param that controls the cropping region */\n crop?: Crop;\n};\n\nexport type Loader = (params: LoaderParams) => string;\n\n/*\n * @TODO: Expand to include focal point support; and/or switch this to be an SF API type\n */\ntype Crop = 'center' | 'top' | 'bottom' | 'left' | 'right';\n\nexport type HydrogenImageProps = React.ComponentPropsWithRef<'img'> &\n HydrogenImageBaseProps;\n\ntype HydrogenImageBaseProps = {\n /** The aspect ratio of the image, in the format of `width/height`.\n *\n * @example\n * ```\n * <Image data={productImage} aspectRatio=\"4/5\" />\n * ```\n */\n aspectRatio?: string;\n /** The crop position of the image.\n *\n * @remarks\n * In the event that AspectRatio is set, without specifying a crop,\n * the Shopify CDN won't return the expected image.\n *\n * @defaultValue `center`\n */\n crop?: Crop;\n /** Data mapping to the [Storefront API `Image`](https://shopify.dev/docs/api/storefront/2024-10/objects/Image) object. Must be an Image object.\n *\n * @example\n * ```\n * import {IMAGE_FRAGMENT, Image} from '@shopify/hydrogen';\n *\n * export const IMAGE_QUERY = `#graphql\n * ${IMAGE_FRAGMENT}\n * query {\n * product {\n * featuredImage {\n * ...Image\n * }\n * }\n * }`\n *\n * <Image\n * data={productImage}\n * sizes=\"(min-width: 45em) 50vw, 100vw\"\n * aspectRatio=\"4/5\"\n * />\n * ```\n *\n * Image: {@link https://shopify.dev/api/storefront/reference/common-objects/image}\n */\n data?: PartialDeep<ImageType, {recurseIntoArrays: true}>;\n /** A function that returns a URL string for an image.\n *\n * @remarks\n * By default, this uses Shopify’s CDN {@link https://cdn.shopify.com/} but you can provide\n * your own function to use a another provider, as long as they support URL based image transformations.\n */\n loader?: Loader;\n /** An optional prop you can use to change the default srcSet generation behaviour */\n srcSetOptions?: SrcSetOptions;\n};\n\n/**\n * A Storefront API GraphQL fragment that can be used to query for an image.\n */\nexport const IMAGE_FRAGMENT = `#graphql\n fragment Image on Image {\n altText\n url\n width\n height\n }\n`;\n\n/**\n * Hydrogen’s Image component is a wrapper around the HTML image element.\n * It supports the same props as the HTML `img` element, but automatically\n * generates the srcSet and sizes attributes for you. For most use cases,\n * you’ll want to set the `aspectRatio` prop to ensure the image is sized\n * correctly.\n *\n * @remarks\n * - `decoding` is set to `async` by default.\n * - `loading` is set to `lazy` by default.\n * - `alt` will automatically be set to the `altText` from the Storefront API if passed in the `data` prop\n * - `src` will automatically be set to the `url` from the Storefront API if passed in the `data` prop\n *\n * @example\n * A responsive image with a 4:5 aspect ratio:\n * ```\n * <Image\n * data={product.featuredImage}\n * aspectRatio=\"4/5\"\n * sizes=\"(min-width: 45em) 40vw, 100vw\"\n * />\n * ```\n * @example\n * A fixed size image:\n * ```\n * <Image\n * data={product.featuredImage}\n * width={100}\n * height={100}\n * />\n * ```\n *\n * {@link https://shopify.dev/docs/api/hydrogen-react/components/image}\n */\nexport const Image = React.forwardRef<HTMLImageElement, HydrogenImageProps>(\n (\n {\n alt,\n aspectRatio,\n crop = 'center',\n data,\n decoding = 'async',\n height = 'auto',\n loader = shopifyLoader,\n loading = 'lazy',\n sizes,\n src,\n srcSetOptions = {\n intervals: 15,\n startingWidth: 200,\n incrementSize: 200,\n placeholderWidth: 100,\n },\n width = '100%',\n ...passthroughProps\n },\n ref,\n ) => {\n /*\n * Gets normalized values for width, height from data prop\n */\n const normalizedData = React.useMemo(() => {\n /* Only use data width if height is also set */\n const dataWidth: number | undefined =\n data?.width && data?.height ? data?.width : undefined;\n\n const dataHeight: number | undefined =\n data?.width && data?.height ? data?.height : undefined;\n\n return {\n width: dataWidth,\n height: dataHeight,\n unitsMatch: Boolean(unitsMatch(dataWidth, dataHeight)),\n };\n }, [data]);\n\n /*\n * Gets normalized values for width, height, src, alt, and aspectRatio props\n * supporting the presence of `data` in addition to flat props.\n */\n const normalizedProps = React.useMemo(() => {\n const nWidthProp: string | number = width || '100%';\n const widthParts = getUnitValueParts(nWidthProp.toString());\n const nWidth = `${widthParts.number}${widthParts.unit}`;\n\n const autoHeight = height === undefined || height === null;\n const heightParts = autoHeight\n ? null\n : getUnitValueParts(height.toString());\n\n const fixedHeight = heightParts\n ? `${heightParts.number}${heightParts.unit}`\n : '';\n\n const nHeight = autoHeight ? 'auto' : fixedHeight;\n\n const nSrc: string | undefined = src || data?.url;\n\n if (__HYDROGEN_DEV__ && !nSrc) {\n console.warn(\n `No src or data.url provided to Image component.`,\n passthroughProps?.key || '',\n );\n }\n\n const nAlt: string = data?.altText && !alt ? data?.altText : alt || '';\n\n const nAspectRatio: string | undefined = aspectRatio\n ? aspectRatio\n : normalizedData.unitsMatch\n ? [\n getNormalizedFixedUnit(normalizedData.width),\n getNormalizedFixedUnit(normalizedData.height),\n ].join('/')\n : undefined;\n\n return {\n width: nWidth,\n height: nHeight,\n src: nSrc,\n alt: nAlt,\n aspectRatio: nAspectRatio,\n };\n }, [\n width,\n height,\n src,\n data,\n alt,\n aspectRatio,\n normalizedData,\n passthroughProps?.key,\n ]);\n\n const {intervals, startingWidth, incrementSize, placeholderWidth} =\n srcSetOptions;\n\n /*\n * This function creates an array of widths to be used in srcSet\n */\n const imageWidths = React.useMemo(() => {\n return generateImageWidths(\n width,\n intervals,\n startingWidth,\n incrementSize,\n );\n }, [width, intervals, startingWidth, incrementSize]);\n\n const fixedWidth = isFixedWidth(normalizedProps.width);\n\n if (__HYDROGEN_DEV__ && !sizes && !fixedWidth) {\n console.warn(\n [\n 'No sizes prop provided to Image component,',\n 'you may be loading unnecessarily large images.',\n `Image used is ${\n src || data?.url || passthroughProps?.key || 'unknown'\n }`,\n ].join(' '),\n );\n }\n\n /*\n * We check to see whether the image is fixed width or not,\n * if fixed, we still provide a srcSet, but only to account for\n * different pixel densities.\n */\n if (fixedWidth) {\n return (\n <FixedWidthImage\n aspectRatio={aspectRatio}\n crop={crop}\n decoding={decoding}\n height={height}\n imageWidths={imageWidths}\n loader={loader}\n loading={loading}\n normalizedProps={normalizedProps}\n passthroughProps={passthroughProps}\n ref={ref}\n width={width}\n data={data}\n />\n );\n } else {\n return (\n <FluidImage\n aspectRatio={aspectRatio}\n crop={crop}\n decoding={decoding}\n imageWidths={imageWidths}\n loader={loader}\n loading={loading}\n normalizedProps={normalizedProps}\n passthroughProps={passthroughProps}\n placeholderWidth={placeholderWidth}\n ref={ref}\n sizes={sizes}\n data={data}\n />\n );\n }\n },\n);\n\ntype FixedImageExludedProps =\n | 'data'\n | 'loader'\n | 'loaderOptions'\n | 'sizes'\n | 'srcSetOptions'\n | 'widths';\n\ntype FixedWidthImageProps = Omit<HydrogenImageProps, FixedImageExludedProps> &\n Pick<HydrogenImageBaseProps, 'data'> & {\n loader: Loader;\n passthroughProps: React.ImgHTMLAttributes<HTMLImageElement>;\n normalizedProps: NormalizedProps;\n imageWidths: number[];\n ref: React.Ref<HTMLImageElement>;\n };\n\nconst FixedWidthImage = React.forwardRef<\n HTMLImageElement,\n FixedWidthImageProps\n>(\n (\n {\n aspectRatio,\n crop,\n decoding,\n height,\n imageWidths,\n loader = shopifyLoader,\n loading,\n normalizedProps,\n passthroughProps,\n width,\n data,\n },\n ref,\n ) => {\n const fixed = React.useMemo(() => {\n const intWidth: number | undefined = getNormalizedFixedUnit(width);\n const intHeight: number | undefined = getNormalizedFixedUnit(height);\n\n /*\n * The aspect ratio for fixed width images is taken from the explicitly\n * set prop, but if that's not present, and both width and height are\n * set, we calculate the aspect ratio from the width and height—as\n * long as they share the same unit type (e.g. both are 'px').\n */\n const fixedAspectRatio = aspectRatio\n ? aspectRatio\n : unitsMatch(normalizedProps.width, normalizedProps.height)\n ? [intWidth, intHeight].join('/')\n : normalizedProps.aspectRatio\n ? normalizedProps.aspectRatio\n : undefined;\n\n /*\n * The Sizes Array generates an array of all the parts\n * that make up the srcSet, including the width, height, and crop\n */\n const sizesArray =\n imageWidths === undefined\n ? undefined\n : generateSizes(imageWidths, fixedAspectRatio, crop, {\n width: data?.width ?? undefined,\n height: data?.height ?? undefined,\n });\n\n const fixedHeight = intHeight\n ? intHeight\n : fixedAspectRatio && intWidth\n ? intWidth * (parseAspectRatio(fixedAspectRatio) ?? 1)\n : undefined;\n\n const srcSet = generateSrcSet(normalizedProps.src, sizesArray, loader);\n const src = loader({\n src: normalizedProps.src,\n width: intWidth,\n height: fixedHeight,\n crop: normalizedProps.height === 'auto' ? undefined : crop,\n });\n\n return {\n width: intWidth,\n aspectRatio: fixedAspectRatio,\n height: fixedHeight,\n srcSet,\n src,\n };\n }, [\n aspectRatio,\n crop,\n data,\n height,\n imageWidths,\n loader,\n normalizedProps,\n width,\n ]);\n\n return (\n <img\n ref={ref}\n alt={normalizedProps.alt}\n decoding={decoding}\n height={fixed.height}\n loading={loading}\n src={fixed.src}\n srcSet={fixed.srcSet}\n width={fixed.width}\n style={{\n aspectRatio: fixed.aspectRatio,\n ...passthroughProps.style,\n }}\n {...passthroughProps}\n />\n );\n },\n);\n\ntype FluidImageExcludedProps =\n | 'data'\n | 'width'\n | 'height'\n | 'loader'\n | 'loaderOptions'\n | 'srcSetOptions';\n\ntype FluidImageProps = Omit<HydrogenImageProps, FluidImageExcludedProps> &\n Pick<HydrogenImageBaseProps, 'data'> & {\n imageWidths: number[];\n loader: Loader;\n normalizedProps: NormalizedProps;\n passthroughProps: React.ImgHTMLAttributes<HTMLImageElement>;\n placeholderWidth: number;\n ref: React.Ref<HTMLImageElement>;\n };\n\nconst FluidImage = React.forwardRef<HTMLImageElement, FluidImageProps>(\n (\n {\n crop,\n decoding,\n imageWidths,\n loader = shopifyLoader,\n loading,\n normalizedProps,\n passthroughProps,\n placeholderWidth,\n sizes,\n data,\n },\n ref,\n ) => {\n const fluid = React.useMemo(() => {\n const sizesArray =\n imageWidths === undefined\n ? undefined\n : generateSizes(imageWidths, normalizedProps.aspectRatio, crop, {\n width: data?.width ?? undefined,\n height: data?.height ?? undefined,\n });\n\n const placeholderHeight =\n normalizedProps.aspectRatio && placeholderWidth\n ? placeholderWidth *\n (parseAspectRatio(normalizedProps.aspectRatio) ?? 1)\n : undefined;\n\n const srcSet = generateSrcSet(normalizedProps.src, sizesArray, loader);\n\n const src = loader({\n src: normalizedProps.src,\n width: placeholderWidth,\n height: placeholderHeight,\n crop,\n });\n\n return {\n placeholderHeight,\n srcSet,\n src,\n };\n }, [crop, data, imageWidths, loader, normalizedProps, placeholderWidth]);\n\n return (\n <img\n ref={ref}\n alt={normalizedProps.alt}\n decoding={decoding}\n height={fluid.placeholderHeight}\n loading={loading}\n sizes={sizes}\n src={fluid.src}\n srcSet={fluid.srcSet}\n width={placeholderWidth}\n {...passthroughProps}\n style={{\n width: normalizedProps.width,\n aspectRatio: normalizedProps.aspectRatio,\n ...passthroughProps.style,\n }}\n />\n );\n },\n);\n\n/**\n * The shopifyLoader function is a simple utility function that takes a src, width,\n * height, and crop and returns a string that can be used as the src for an image.\n * It can be used with the Hydrogen Image component or with the next/image component.\n * (or any others that accept equivalent configuration)\n * @param src - The source URL of the image, e.g. `https://cdn.shopify.com/static/sample-images/garnished.jpeg`\n * @param width - The width of the image, e.g. `100`\n * @param height - The height of the image, e.g. `100`\n * @param crop - The crop of the image, e.g. `center`\n * @returns A Shopify image URL with the correct query parameters, e.g. `https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=100&height=100&crop=center`\n *\n * @example\n * ```\n * shopifyLoader({\n * src: 'https://cdn.shopify.com/static/sample-images/garnished.jpeg',\n * width: 100,\n * height: 100,\n * crop: 'center',\n * })\n * ```\n */\nconst PLACEHOLDER_DOMAIN = 'https://placeholder.shopify.com';\nexport function shopifyLoader({src, width, height, crop}: LoaderParams) {\n if (!src) {\n return '';\n }\n\n const url = new URL(src, PLACEHOLDER_DOMAIN);\n\n if (width) {\n url.searchParams.append('width', Math.round(width).toString());\n }\n\n if (height) {\n url.searchParams.append('height', Math.round(height).toString());\n }\n\n if (crop) {\n url.searchParams.append('crop', crop);\n }\n return url.href.replace(PLACEHOLDER_DOMAIN, '');\n}\n\n/**\n * Checks whether the width and height share the same unit type\n * @param width - The width of the image, e.g. 100% | 10px\n * @param height - The height of the image, e.g. auto | 100px\n * @returns Whether the width and height share the same unit type (boolean)\n */\nfunction unitsMatch(\n width: string | number = '100%',\n height: string | number = 'auto',\n): boolean {\n return (\n getUnitValueParts(width.toString()).unit ===\n getUnitValueParts(height.toString()).unit\n );\n}\n\n/**\n * Given a CSS size, returns the unit and number parts of the value\n * @param value - The CSS size, e.g. 100px\n * @returns The unit and number parts of the value, e.g. \\{unit: 'px', number: 100\\}\n */\nfunction getUnitValueParts(value: string): {unit: string; number: number} {\n const unit = value.replace(/[0-9.]/g, '');\n const number = parseFloat(value.replace(unit, ''));\n\n return {\n unit: unit === '' ? (number === undefined ? 'auto' : 'px') : unit,\n number,\n };\n}\n\n/**\n * Given a value, returns the width of the image as an integer in pixels\n * @param value - The width of the image, e.g. 16px | 1rem | 1em | 16\n * @returns The width of the image in pixels, e.g. 16, or undefined if the value is not a fixed unit\n */\nfunction getNormalizedFixedUnit(value?: string | number): number | undefined {\n if (value === undefined) {\n return;\n }\n\n const {unit, number} = getUnitValueParts(value.toString());\n\n switch (unit) {\n case 'em':\n return number * 16;\n case 'rem':\n return number * 16;\n case 'px':\n return number;\n case '':\n return number;\n default:\n return;\n }\n}\n\n/**\n * This function checks whether a width is fixed or not.\n * @param width - The width of the image, e.g. 100 | '100px' | '100em' | '100rem'\n * @returns Whether the width is fixed or not\n */\nfunction isFixedWidth(width: string | number): boolean {\n const fixedEndings = /\\d(px|em|rem)$/;\n return typeof width === 'number' || fixedEndings.test(width);\n}\n\n/**\n * This function generates a srcSet for Shopify images.\n * @param src - The source URL of the image, e.g. https://cdn.shopify.com/static/sample-images/garnished.jpeg\n * @param sizesArray - An array of objects containing the `width`, `height`, and `crop` of the image, e.g. [\\{width: 200, height: 200, crop: 'center'\\}, \\{width: 400, height: 400, crop: 'center'\\}]\n * @param loader - A function that takes a Shopify image URL and returns a Shopify image URL with the correct query parameters\n * @returns A srcSet for Shopify images, e.g. 'https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=200&height=200&crop=center 200w, https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=400&height=400&crop=center 400w'\n */\nexport function generateSrcSet(\n src?: string,\n sizesArray?: Array<{width?: number; height?: number; crop?: Crop}>,\n loader: Loader = shopifyLoader,\n): string {\n if (!src) {\n return '';\n }\n\n if (sizesArray?.length === 0 || !sizesArray) {\n return src;\n }\n\n return sizesArray\n .map(\n (size, i) =>\n `${loader({\n src,\n width: size.width,\n height: size.height,\n crop: size.crop,\n })} ${sizesArray.length === 3 ? `${i + 1}x` : `${size.width ?? 0}w`}`,\n )\n .join(`, `);\n}\n\n/**\n * This function generates an array of sizes for Shopify images, for both fixed and responsive images.\n * @param width - The CSS width of the image\n * @param intervals - The number of intervals to generate\n * @param startingWidth - The starting width of the image\n * @param incrementSize - The size of each interval\n * @returns An array of widths\n */\nexport function generateImageWidths(\n width: string | number = '100%',\n intervals: number,\n startingWidth: number,\n incrementSize: number,\n): number[] {\n const responsive = Array.from(\n {length: intervals},\n (_, i) => i * incrementSize + startingWidth,\n );\n\n const fixed = Array.from(\n {length: 3},\n (_, i) => (i + 1) * (getNormalizedFixedUnit(width) ?? 0),\n );\n\n return isFixedWidth(width) ? fixed : responsive;\n}\n\n/**\n * Simple utility function to convert an aspect ratio CSS string to a decimal, currently only supports values like `1/1`, not `0.5`, or `auto`\n * @param aspectRatio - The aspect ratio of the image, e.g. `1/1`\n * @returns The aspect ratio as a number, e.g. `0.5`\n *\n * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio}\n */\nexport function parseAspectRatio(aspectRatio?: string): number | undefined {\n if (!aspectRatio) return;\n const [width, height] = aspectRatio.split('/');\n return 1 / (Number(width) / Number(height));\n}\n\n// Generate data needed for Imagery loader\nexport function generateSizes(\n imageWidths?: number[],\n aspectRatio?: string,\n crop: Crop = 'center',\n sourceDimensions?: {width?: number; height?: number},\n):\n | {\n width: number;\n height: number | undefined;\n crop: Crop;\n }[]\n | undefined {\n if (!imageWidths) return;\n return imageWidths\n .map((width: number) => {\n return {\n width,\n height: aspectRatio\n ? width * (parseAspectRatio(aspectRatio) ?? 1)\n : undefined,\n crop,\n };\n })\n .filter(({width, height}) => {\n if (sourceDimensions?.width && width > sourceDimensions.width) {\n return false;\n }\n\n if (\n sourceDimensions?.height &&\n height &&\n height > sourceDimensions.height\n ) {\n return false;\n }\n\n return true;\n });\n /*\n Given:\n ([100, 200], 1/1, 'center')\n Returns:\n [{width: 100, height: 100, crop: 'center'},\n {width: 200, height: 200, crop: 'center'}]\n */\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Image.mjs","sources":["../../src/Image.tsx"],"sourcesContent":["/* eslint-disable eslint-comments/disable-enable-pair */\n/* eslint-disable @typescript-eslint/explicit-function-return-type */\n/* eslint-disable hydrogen/prefer-image-component */\nimport * as React from 'react';\nimport type {PartialDeep} from 'type-fest';\nimport type {Image as ImageType} from './storefront-api-types.js';\n\n/*\n * An optional prop you can use to change the\n * default srcSet generation behaviour\n */\ntype SrcSetOptions = {\n /** The number of sizes to generate */\n intervals: number;\n /** The smallest image size */\n startingWidth: number;\n /** The increment by which to increase for each size, in pixels */\n incrementSize: number;\n /** The size used for placeholder fallback images */\n placeholderWidth: number;\n};\n\ntype NormalizedProps = {\n alt: string;\n aspectRatio: string | undefined;\n height: string;\n src: string | undefined;\n width: string;\n};\n\nexport type LoaderParams = {\n /** The base URL of the image */\n src?: ImageType['url'];\n /** The URL param that controls width */\n width?: number;\n /** The URL param that controls height */\n height?: number;\n /** The URL param that controls the cropping region */\n crop?: Crop;\n};\n\nexport type Loader = (params: LoaderParams) => string;\n\n/*\n * @TODO: Expand to include focal point support; and/or switch this to be an SF API type\n */\ntype Crop = 'center' | 'top' | 'bottom' | 'left' | 'right';\n\nexport type HydrogenImageProps = React.ComponentPropsWithRef<'img'> &\n HydrogenImageBaseProps;\n\ntype HydrogenImageBaseProps = {\n /** The aspect ratio of the image, in the format of `width/height`.\n *\n * @example\n * ```\n * <Image data={productImage} aspectRatio=\"4/5\" />\n * ```\n */\n aspectRatio?: string;\n /** The crop position of the image.\n *\n * @remarks\n * In the event that AspectRatio is set, without specifying a crop,\n * the Shopify CDN won't return the expected image.\n *\n * @defaultValue `center`\n */\n crop?: Crop;\n /** Data mapping to the [Storefront API `Image`](https://shopify.dev/docs/api/storefront/2025-01/objects/Image) object. Must be an Image object.\n *\n * @example\n * ```\n * import {IMAGE_FRAGMENT, Image} from '@shopify/hydrogen';\n *\n * export const IMAGE_QUERY = `#graphql\n * ${IMAGE_FRAGMENT}\n * query {\n * product {\n * featuredImage {\n * ...Image\n * }\n * }\n * }`\n *\n * <Image\n * data={productImage}\n * sizes=\"(min-width: 45em) 50vw, 100vw\"\n * aspectRatio=\"4/5\"\n * />\n * ```\n *\n * Image: {@link https://shopify.dev/api/storefront/reference/common-objects/image}\n */\n data?: PartialDeep<ImageType, {recurseIntoArrays: true}>;\n /** A function that returns a URL string for an image.\n *\n * @remarks\n * By default, this uses Shopify’s CDN {@link https://cdn.shopify.com/} but you can provide\n * your own function to use a another provider, as long as they support URL based image transformations.\n */\n loader?: Loader;\n /** An optional prop you can use to change the default srcSet generation behaviour */\n srcSetOptions?: SrcSetOptions;\n};\n\n/**\n * A Storefront API GraphQL fragment that can be used to query for an image.\n */\nexport const IMAGE_FRAGMENT = `#graphql\n fragment Image on Image {\n altText\n url\n width\n height\n }\n`;\n\n/**\n * Hydrogen’s Image component is a wrapper around the HTML image element.\n * It supports the same props as the HTML `img` element, but automatically\n * generates the srcSet and sizes attributes for you. For most use cases,\n * you’ll want to set the `aspectRatio` prop to ensure the image is sized\n * correctly.\n *\n * @remarks\n * - `decoding` is set to `async` by default.\n * - `loading` is set to `lazy` by default.\n * - `alt` will automatically be set to the `altText` from the Storefront API if passed in the `data` prop\n * - `src` will automatically be set to the `url` from the Storefront API if passed in the `data` prop\n *\n * @example\n * A responsive image with a 4:5 aspect ratio:\n * ```\n * <Image\n * data={product.featuredImage}\n * aspectRatio=\"4/5\"\n * sizes=\"(min-width: 45em) 40vw, 100vw\"\n * />\n * ```\n * @example\n * A fixed size image:\n * ```\n * <Image\n * data={product.featuredImage}\n * width={100}\n * height={100}\n * />\n * ```\n *\n * {@link https://shopify.dev/docs/api/hydrogen-react/components/image}\n */\nexport const Image = React.forwardRef<HTMLImageElement, HydrogenImageProps>(\n (\n {\n alt,\n aspectRatio,\n crop = 'center',\n data,\n decoding = 'async',\n height = 'auto',\n loader = shopifyLoader,\n loading = 'lazy',\n sizes,\n src,\n srcSetOptions = {\n intervals: 15,\n startingWidth: 200,\n incrementSize: 200,\n placeholderWidth: 100,\n },\n width = '100%',\n ...passthroughProps\n },\n ref,\n ) => {\n /*\n * Gets normalized values for width, height from data prop\n */\n const normalizedData = React.useMemo(() => {\n /* Only use data width if height is also set */\n const dataWidth: number | undefined =\n data?.width && data?.height ? data?.width : undefined;\n\n const dataHeight: number | undefined =\n data?.width && data?.height ? data?.height : undefined;\n\n return {\n width: dataWidth,\n height: dataHeight,\n unitsMatch: Boolean(unitsMatch(dataWidth, dataHeight)),\n };\n }, [data]);\n\n /*\n * Gets normalized values for width, height, src, alt, and aspectRatio props\n * supporting the presence of `data` in addition to flat props.\n */\n const normalizedProps = React.useMemo(() => {\n const nWidthProp: string | number = width || '100%';\n const widthParts = getUnitValueParts(nWidthProp.toString());\n const nWidth = `${widthParts.number}${widthParts.unit}`;\n\n const autoHeight = height === undefined || height === null;\n const heightParts = autoHeight\n ? null\n : getUnitValueParts(height.toString());\n\n const fixedHeight = heightParts\n ? `${heightParts.number}${heightParts.unit}`\n : '';\n\n const nHeight = autoHeight ? 'auto' : fixedHeight;\n\n const nSrc: string | undefined = src || data?.url;\n\n if (__HYDROGEN_DEV__ && !nSrc) {\n console.warn(\n `No src or data.url provided to Image component.`,\n passthroughProps?.key || '',\n );\n }\n\n const nAlt: string = data?.altText && !alt ? data?.altText : alt || '';\n\n const nAspectRatio: string | undefined = aspectRatio\n ? aspectRatio\n : normalizedData.unitsMatch\n ? [\n getNormalizedFixedUnit(normalizedData.width),\n getNormalizedFixedUnit(normalizedData.height),\n ].join('/')\n : undefined;\n\n return {\n width: nWidth,\n height: nHeight,\n src: nSrc,\n alt: nAlt,\n aspectRatio: nAspectRatio,\n };\n }, [\n width,\n height,\n src,\n data,\n alt,\n aspectRatio,\n normalizedData,\n passthroughProps?.key,\n ]);\n\n const {intervals, startingWidth, incrementSize, placeholderWidth} =\n srcSetOptions;\n\n /*\n * This function creates an array of widths to be used in srcSet\n */\n const imageWidths = React.useMemo(() => {\n return generateImageWidths(\n width,\n intervals,\n startingWidth,\n incrementSize,\n );\n }, [width, intervals, startingWidth, incrementSize]);\n\n const fixedWidth = isFixedWidth(normalizedProps.width);\n\n if (__HYDROGEN_DEV__ && !sizes && !fixedWidth) {\n console.warn(\n [\n 'No sizes prop provided to Image component,',\n 'you may be loading unnecessarily large images.',\n `Image used is ${\n src || data?.url || passthroughProps?.key || 'unknown'\n }`,\n ].join(' '),\n );\n }\n\n /*\n * We check to see whether the image is fixed width or not,\n * if fixed, we still provide a srcSet, but only to account for\n * different pixel densities.\n */\n if (fixedWidth) {\n return (\n <FixedWidthImage\n aspectRatio={aspectRatio}\n crop={crop}\n decoding={decoding}\n height={height}\n imageWidths={imageWidths}\n loader={loader}\n loading={loading}\n normalizedProps={normalizedProps}\n passthroughProps={passthroughProps}\n ref={ref}\n width={width}\n data={data}\n />\n );\n } else {\n return (\n <FluidImage\n aspectRatio={aspectRatio}\n crop={crop}\n decoding={decoding}\n imageWidths={imageWidths}\n loader={loader}\n loading={loading}\n normalizedProps={normalizedProps}\n passthroughProps={passthroughProps}\n placeholderWidth={placeholderWidth}\n ref={ref}\n sizes={sizes}\n data={data}\n />\n );\n }\n },\n);\n\ntype FixedImageExludedProps =\n | 'data'\n | 'loader'\n | 'loaderOptions'\n | 'sizes'\n | 'srcSetOptions'\n | 'widths';\n\ntype FixedWidthImageProps = Omit<HydrogenImageProps, FixedImageExludedProps> &\n Pick<HydrogenImageBaseProps, 'data'> & {\n loader: Loader;\n passthroughProps: React.ImgHTMLAttributes<HTMLImageElement>;\n normalizedProps: NormalizedProps;\n imageWidths: number[];\n ref: React.Ref<HTMLImageElement>;\n };\n\nconst FixedWidthImage = React.forwardRef<\n HTMLImageElement,\n FixedWidthImageProps\n>(\n (\n {\n aspectRatio,\n crop,\n decoding,\n height,\n imageWidths,\n loader = shopifyLoader,\n loading,\n normalizedProps,\n passthroughProps,\n width,\n data,\n },\n ref,\n ) => {\n const fixed = React.useMemo(() => {\n const intWidth: number | undefined = getNormalizedFixedUnit(width);\n const intHeight: number | undefined = getNormalizedFixedUnit(height);\n\n /*\n * The aspect ratio for fixed width images is taken from the explicitly\n * set prop, but if that's not present, and both width and height are\n * set, we calculate the aspect ratio from the width and height—as\n * long as they share the same unit type (e.g. both are 'px').\n */\n const fixedAspectRatio = aspectRatio\n ? aspectRatio\n : unitsMatch(normalizedProps.width, normalizedProps.height)\n ? [intWidth, intHeight].join('/')\n : normalizedProps.aspectRatio\n ? normalizedProps.aspectRatio\n : undefined;\n\n /*\n * The Sizes Array generates an array of all the parts\n * that make up the srcSet, including the width, height, and crop\n */\n const sizesArray =\n imageWidths === undefined\n ? undefined\n : generateSizes(imageWidths, fixedAspectRatio, crop, {\n width: data?.width ?? undefined,\n height: data?.height ?? undefined,\n });\n\n const fixedHeight = intHeight\n ? intHeight\n : fixedAspectRatio && intWidth\n ? intWidth * (parseAspectRatio(fixedAspectRatio) ?? 1)\n : undefined;\n\n const srcSet = generateSrcSet(normalizedProps.src, sizesArray, loader);\n const src = loader({\n src: normalizedProps.src,\n width: intWidth,\n height: fixedHeight,\n crop: normalizedProps.height === 'auto' ? undefined : crop,\n });\n\n return {\n width: intWidth,\n aspectRatio: fixedAspectRatio,\n height: fixedHeight,\n srcSet,\n src,\n };\n }, [\n aspectRatio,\n crop,\n data,\n height,\n imageWidths,\n loader,\n normalizedProps,\n width,\n ]);\n\n return (\n <img\n ref={ref}\n alt={normalizedProps.alt}\n decoding={decoding}\n height={fixed.height}\n loading={loading}\n src={fixed.src}\n srcSet={fixed.srcSet}\n width={fixed.width}\n style={{\n aspectRatio: fixed.aspectRatio,\n ...passthroughProps.style,\n }}\n {...passthroughProps}\n />\n );\n },\n);\n\ntype FluidImageExcludedProps =\n | 'data'\n | 'width'\n | 'height'\n | 'loader'\n | 'loaderOptions'\n | 'srcSetOptions';\n\ntype FluidImageProps = Omit<HydrogenImageProps, FluidImageExcludedProps> &\n Pick<HydrogenImageBaseProps, 'data'> & {\n imageWidths: number[];\n loader: Loader;\n normalizedProps: NormalizedProps;\n passthroughProps: React.ImgHTMLAttributes<HTMLImageElement>;\n placeholderWidth: number;\n ref: React.Ref<HTMLImageElement>;\n };\n\nconst FluidImage = React.forwardRef<HTMLImageElement, FluidImageProps>(\n (\n {\n crop,\n decoding,\n imageWidths,\n loader = shopifyLoader,\n loading,\n normalizedProps,\n passthroughProps,\n placeholderWidth,\n sizes,\n data,\n },\n ref,\n ) => {\n const fluid = React.useMemo(() => {\n const sizesArray =\n imageWidths === undefined\n ? undefined\n : generateSizes(imageWidths, normalizedProps.aspectRatio, crop, {\n width: data?.width ?? undefined,\n height: data?.height ?? undefined,\n });\n\n const placeholderHeight =\n normalizedProps.aspectRatio && placeholderWidth\n ? placeholderWidth *\n (parseAspectRatio(normalizedProps.aspectRatio) ?? 1)\n : undefined;\n\n const srcSet = generateSrcSet(normalizedProps.src, sizesArray, loader);\n\n const src = loader({\n src: normalizedProps.src,\n width: placeholderWidth,\n height: placeholderHeight,\n crop,\n });\n\n return {\n placeholderHeight,\n srcSet,\n src,\n };\n }, [crop, data, imageWidths, loader, normalizedProps, placeholderWidth]);\n\n return (\n <img\n ref={ref}\n alt={normalizedProps.alt}\n decoding={decoding}\n height={fluid.placeholderHeight}\n loading={loading}\n sizes={sizes}\n src={fluid.src}\n srcSet={fluid.srcSet}\n width={placeholderWidth}\n {...passthroughProps}\n style={{\n width: normalizedProps.width,\n aspectRatio: normalizedProps.aspectRatio,\n ...passthroughProps.style,\n }}\n />\n );\n },\n);\n\n/**\n * The shopifyLoader function is a simple utility function that takes a src, width,\n * height, and crop and returns a string that can be used as the src for an image.\n * It can be used with the Hydrogen Image component or with the next/image component.\n * (or any others that accept equivalent configuration)\n * @param src - The source URL of the image, e.g. `https://cdn.shopify.com/static/sample-images/garnished.jpeg`\n * @param width - The width of the image, e.g. `100`\n * @param height - The height of the image, e.g. `100`\n * @param crop - The crop of the image, e.g. `center`\n * @returns A Shopify image URL with the correct query parameters, e.g. `https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=100&height=100&crop=center`\n *\n * @example\n * ```\n * shopifyLoader({\n * src: 'https://cdn.shopify.com/static/sample-images/garnished.jpeg',\n * width: 100,\n * height: 100,\n * crop: 'center',\n * })\n * ```\n */\nconst PLACEHOLDER_DOMAIN = 'https://placeholder.shopify.com';\nexport function shopifyLoader({src, width, height, crop}: LoaderParams) {\n if (!src) {\n return '';\n }\n\n const url = new URL(src, PLACEHOLDER_DOMAIN);\n\n if (width) {\n url.searchParams.append('width', Math.round(width).toString());\n }\n\n if (height) {\n url.searchParams.append('height', Math.round(height).toString());\n }\n\n if (crop) {\n url.searchParams.append('crop', crop);\n }\n return url.href.replace(PLACEHOLDER_DOMAIN, '');\n}\n\n/**\n * Checks whether the width and height share the same unit type\n * @param width - The width of the image, e.g. 100% | 10px\n * @param height - The height of the image, e.g. auto | 100px\n * @returns Whether the width and height share the same unit type (boolean)\n */\nfunction unitsMatch(\n width: string | number = '100%',\n height: string | number = 'auto',\n): boolean {\n return (\n getUnitValueParts(width.toString()).unit ===\n getUnitValueParts(height.toString()).unit\n );\n}\n\n/**\n * Given a CSS size, returns the unit and number parts of the value\n * @param value - The CSS size, e.g. 100px\n * @returns The unit and number parts of the value, e.g. \\{unit: 'px', number: 100\\}\n */\nfunction getUnitValueParts(value: string): {unit: string; number: number} {\n const unit = value.replace(/[0-9.]/g, '');\n const number = parseFloat(value.replace(unit, ''));\n\n return {\n unit: unit === '' ? (number === undefined ? 'auto' : 'px') : unit,\n number,\n };\n}\n\n/**\n * Given a value, returns the width of the image as an integer in pixels\n * @param value - The width of the image, e.g. 16px | 1rem | 1em | 16\n * @returns The width of the image in pixels, e.g. 16, or undefined if the value is not a fixed unit\n */\nfunction getNormalizedFixedUnit(value?: string | number): number | undefined {\n if (value === undefined) {\n return;\n }\n\n const {unit, number} = getUnitValueParts(value.toString());\n\n switch (unit) {\n case 'em':\n return number * 16;\n case 'rem':\n return number * 16;\n case 'px':\n return number;\n case '':\n return number;\n default:\n return;\n }\n}\n\n/**\n * This function checks whether a width is fixed or not.\n * @param width - The width of the image, e.g. 100 | '100px' | '100em' | '100rem'\n * @returns Whether the width is fixed or not\n */\nfunction isFixedWidth(width: string | number): boolean {\n const fixedEndings = /\\d(px|em|rem)$/;\n return typeof width === 'number' || fixedEndings.test(width);\n}\n\n/**\n * This function generates a srcSet for Shopify images.\n * @param src - The source URL of the image, e.g. https://cdn.shopify.com/static/sample-images/garnished.jpeg\n * @param sizesArray - An array of objects containing the `width`, `height`, and `crop` of the image, e.g. [\\{width: 200, height: 200, crop: 'center'\\}, \\{width: 400, height: 400, crop: 'center'\\}]\n * @param loader - A function that takes a Shopify image URL and returns a Shopify image URL with the correct query parameters\n * @returns A srcSet for Shopify images, e.g. 'https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=200&height=200&crop=center 200w, https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=400&height=400&crop=center 400w'\n */\nexport function generateSrcSet(\n src?: string,\n sizesArray?: Array<{width?: number; height?: number; crop?: Crop}>,\n loader: Loader = shopifyLoader,\n): string {\n if (!src) {\n return '';\n }\n\n if (sizesArray?.length === 0 || !sizesArray) {\n return src;\n }\n\n return sizesArray\n .map(\n (size, i) =>\n `${loader({\n src,\n width: size.width,\n height: size.height,\n crop: size.crop,\n })} ${sizesArray.length === 3 ? `${i + 1}x` : `${size.width ?? 0}w`}`,\n )\n .join(`, `);\n}\n\n/**\n * This function generates an array of sizes for Shopify images, for both fixed and responsive images.\n * @param width - The CSS width of the image\n * @param intervals - The number of intervals to generate\n * @param startingWidth - The starting width of the image\n * @param incrementSize - The size of each interval\n * @returns An array of widths\n */\nexport function generateImageWidths(\n width: string | number = '100%',\n intervals: number,\n startingWidth: number,\n incrementSize: number,\n): number[] {\n const responsive = Array.from(\n {length: intervals},\n (_, i) => i * incrementSize + startingWidth,\n );\n\n const fixed = Array.from(\n {length: 3},\n (_, i) => (i + 1) * (getNormalizedFixedUnit(width) ?? 0),\n );\n\n return isFixedWidth(width) ? fixed : responsive;\n}\n\n/**\n * Simple utility function to convert an aspect ratio CSS string to a decimal, currently only supports values like `1/1`, not `0.5`, or `auto`\n * @param aspectRatio - The aspect ratio of the image, e.g. `1/1`\n * @returns The aspect ratio as a number, e.g. `0.5`\n *\n * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio}\n */\nexport function parseAspectRatio(aspectRatio?: string): number | undefined {\n if (!aspectRatio) return;\n const [width, height] = aspectRatio.split('/');\n return 1 / (Number(width) / Number(height));\n}\n\n// Generate data needed for Imagery loader\nexport function generateSizes(\n imageWidths?: number[],\n aspectRatio?: string,\n crop: Crop = 'center',\n sourceDimensions?: {width?: number; height?: number},\n):\n | {\n width: number;\n height: number | undefined;\n crop: Crop;\n }[]\n | undefined {\n if (!imageWidths) return;\n return imageWidths\n .map((width: number) => {\n return {\n width,\n height: aspectRatio\n ? width * (parseAspectRatio(aspectRatio) ?? 1)\n : undefined,\n crop,\n };\n })\n .filter(({width, height}) => {\n if (sourceDimensions?.width && width > sourceDimensions.width) {\n return false;\n }\n\n if (\n sourceDimensions?.height &&\n height &&\n height > sourceDimensions.height\n ) {\n return false;\n }\n\n return true;\n });\n /*\n Given:\n ([100, 200], 1/1, 'center')\n Returns:\n [{width: 100, height: 100, crop: 'center'},\n {width: 200, height: 200, crop: 'center'}]\n */\n}\n"],"names":[],"mappings":";;AA6GO,MAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2CvB,MAAM,QAAQ,MAAM;AAAA,EACzB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA,WAAW;AAAA,IACX,SAAS;AAAA,IACT,SAAS;AAAA,IACT,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,MACd,WAAW;AAAA,MACX,eAAe;AAAA,MACf,eAAe;AAAA,MACf,kBAAkB;AAAA,IACpB;AAAA,IACA,QAAQ;AAAA,IACR,GAAG;AAAA,KAEL,QACG;AAIG,UAAA,iBAAiB,MAAM,QAAQ,MAAM;AAEzC,YAAM,aACJ,6BAAM,WAAS,6BAAM,UAAS,6BAAM,QAAQ;AAE9C,YAAM,cACJ,6BAAM,WAAS,6BAAM,UAAS,6BAAM,SAAS;AAExC,aAAA;AAAA,QACL,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,YAAY,QAAQ,WAAW,WAAW,UAAU,CAAC;AAAA,MAAA;AAAA,IACvD,GACC,CAAC,IAAI,CAAC;AAMH,UAAA,kBAAkB,MAAM,QAAQ,MAAM;AAC1C,YAAM,aAA8B,SAAS;AAC7C,YAAM,aAAa,kBAAkB,WAAW,SAAU,CAAA;AAC1D,YAAM,SAAS,GAAG,WAAW,MAAM,GAAG,WAAW,IAAI;AAE/C,YAAA,aAAa,WAAW,UAAa,WAAW;AACtD,YAAM,cAAc,aAChB,OACA,kBAAkB,OAAO,UAAU;AAEjC,YAAA,cAAc,cAChB,GAAG,YAAY,MAAM,GAAG,YAAY,IAAI,KACxC;AAEE,YAAA,UAAU,aAAa,SAAS;AAEhC,YAAA,OAA2B,QAAO,6BAAM;AAE1C,UAAoB,CAAC,MAAM;AACrB,gBAAA;AAAA,UACN;AAAA,WACA,qDAAkB,QAAO;AAAA,QAAA;AAAA,MAE7B;AAEA,YAAM,QAAe,6BAAM,YAAW,CAAC,MAAM,6BAAM,UAAU,OAAO;AAEpE,YAAM,eAAmC,cACrC,cACA,eAAe,aACf;AAAA,QACE,uBAAuB,eAAe,KAAK;AAAA,QAC3C,uBAAuB,eAAe,MAAM;AAAA,MAC9C,EAAE,KAAK,GAAG,IACV;AAEG,aAAA;AAAA,QACL,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,KAAK;AAAA,QACL,KAAK;AAAA,QACL,aAAa;AAAA,MAAA;AAAA,IACf,GACC;AAAA,MACD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,qDAAkB;AAAA,IAAA,CACnB;AAED,UAAM,EAAC,WAAW,eAAe,eAAe,qBAC9C;AAKI,UAAA,cAAc,MAAM,QAAQ,MAAM;AAC/B,aAAA;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,OAED,CAAC,OAAO,WAAW,eAAe,aAAa,CAAC;AAE7C,UAAA,aAAa,aAAa,gBAAgB,KAAK;AAE7B,QAAA,CAAC,SAAS,CAAC,YAAY;AACrC,cAAA;AAAA,QACN;AAAA,UACE;AAAA,UACA;AAAA,UACA,iBACE,QAAO,6BAAM,SAAO,qDAAkB,QAAO,SAC/C;AAAA,QAAA,EACA,KAAK,GAAG;AAAA,MAAA;AAAA,IAEd;AAOA,QAAI,YAAY;AAEZ,aAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,MAAA;AAAA,IACF,OAEG;AAEH,aAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,MAAA;AAAA,IAGN;AAAA,EACF;AACF;AAmBA,MAAM,kBAAkB,MAAM;AAAA,EAI5B,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,KAEF,QACG;AACG,UAAA,QAAQ,MAAM,QAAQ,MAAM;AAC1B,YAAA,WAA+B,uBAAuB,KAAK;AAC3D,YAAA,YAAgC,uBAAuB,MAAM;AAQnE,YAAM,mBAAmB,cACrB,cACA,WAAW,gBAAgB,OAAO,gBAAgB,MAAM,IACxD,CAAC,UAAU,SAAS,EAAE,KAAK,GAAG,IAC9B,gBAAgB,cAChB,gBAAgB,cAChB;AAMJ,YAAM,aACJ,gBAAgB,SACZ,SACA,cAAc,aAAa,kBAAkB,MAAM;AAAA,QACjD,QAAO,6BAAM,UAAS;AAAA,QACtB,SAAQ,6BAAM,WAAU;AAAA,MAAA,CACzB;AAED,YAAA,cAAc,YAChB,YACA,oBAAoB,WACpB,YAAY,iBAAiB,gBAAgB,KAAK,KAClD;AAEJ,YAAM,SAAS,eAAe,gBAAgB,KAAK,YAAY,MAAM;AACrE,YAAM,MAAM,OAAO;AAAA,QACjB,KAAK,gBAAgB;AAAA,QACrB,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,MAAM,gBAAgB,WAAW,SAAS,SAAY;AAAA,MAAA,CACvD;AAEM,aAAA;AAAA,QACL,OAAO;AAAA,QACP,aAAa;AAAA,QACb,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,MAAA;AAAA,IACF,GACC;AAAA,MACD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAGC,WAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,KAAK,gBAAgB;AAAA,QACrB;AAAA,QACA,QAAQ,MAAM;AAAA,QACd;AAAA,QACA,KAAK,MAAM;AAAA,QACX,QAAQ,MAAM;AAAA,QACd,OAAO,MAAM;AAAA,QACb,OAAO;AAAA,UACL,aAAa,MAAM;AAAA,UACnB,GAAG,iBAAiB;AAAA,QACtB;AAAA,QACC,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AACF;AAoBA,MAAM,aAAa,MAAM;AAAA,EACvB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,KAEF,QACG;AACG,UAAA,QAAQ,MAAM,QAAQ,MAAM;AAC1B,YAAA,aACJ,gBAAgB,SACZ,SACA,cAAc,aAAa,gBAAgB,aAAa,MAAM;AAAA,QAC5D,QAAO,6BAAM,UAAS;AAAA,QACtB,SAAQ,6BAAM,WAAU;AAAA,MAAA,CACzB;AAED,YAAA,oBACJ,gBAAgB,eAAe,mBAC3B,oBACC,iBAAiB,gBAAgB,WAAW,KAAK,KAClD;AAEN,YAAM,SAAS,eAAe,gBAAgB,KAAK,YAAY,MAAM;AAErE,YAAM,MAAM,OAAO;AAAA,QACjB,KAAK,gBAAgB;AAAA,QACrB,OAAO;AAAA,QACP,QAAQ;AAAA,QACR;AAAA,MAAA,CACD;AAEM,aAAA;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IACF,GACC,CAAC,MAAM,MAAM,aAAa,QAAQ,iBAAiB,gBAAgB,CAAC;AAGrE,WAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,KAAK,gBAAgB;AAAA,QACrB;AAAA,QACA,QAAQ,MAAM;AAAA,QACd;AAAA,QACA;AAAA,QACA,KAAK,MAAM;AAAA,QACX,QAAQ,MAAM;AAAA,QACd,OAAO;AAAA,QACN,GAAG;AAAA,QACJ,OAAO;AAAA,UACL,OAAO,gBAAgB;AAAA,UACvB,aAAa,gBAAgB;AAAA,UAC7B,GAAG,iBAAiB;AAAA,QACtB;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AACF;AAuBA,MAAM,qBAAqB;AACpB,SAAS,cAAc,EAAC,KAAK,OAAO,QAAQ,QAAqB;AACtE,MAAI,CAAC,KAAK;AACD,WAAA;AAAA,EACT;AAEA,QAAM,MAAM,IAAI,IAAI,KAAK,kBAAkB;AAE3C,MAAI,OAAO;AACL,QAAA,aAAa,OAAO,SAAS,KAAK,MAAM,KAAK,EAAE,UAAU;AAAA,EAC/D;AAEA,MAAI,QAAQ;AACN,QAAA,aAAa,OAAO,UAAU,KAAK,MAAM,MAAM,EAAE,UAAU;AAAA,EACjE;AAEA,MAAI,MAAM;AACJ,QAAA,aAAa,OAAO,QAAQ,IAAI;AAAA,EACtC;AACA,SAAO,IAAI,KAAK,QAAQ,oBAAoB,EAAE;AAChD;AAQA,SAAS,WACP,QAAyB,QACzB,SAA0B,QACjB;AAEP,SAAA,kBAAkB,MAAM,SAAA,CAAU,EAAE,SACpC,kBAAkB,OAAO,UAAU,EAAE;AAEzC;AAOA,SAAS,kBAAkB,OAA+C;AACxE,QAAM,OAAO,MAAM,QAAQ,WAAW,EAAE;AACxC,QAAM,SAAS,WAAW,MAAM,QAAQ,MAAM,EAAE,CAAC;AAE1C,SAAA;AAAA,IACL,MAAM,SAAS,KAAM,WAAW,SAAY,SAAS,OAAQ;AAAA,IAC7D;AAAA,EAAA;AAEJ;AAOA,SAAS,uBAAuB,OAA6C;AAC3E,MAAI,UAAU,QAAW;AACvB;AAAA,EACF;AAEA,QAAM,EAAC,MAAM,WAAU,kBAAkB,MAAM,UAAU;AAEzD,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,SAAS;AAAA,IAClB,KAAK;AACH,aAAO,SAAS;AAAA,IAClB,KAAK;AACI,aAAA;AAAA,IACT,KAAK;AACI,aAAA;AAAA,IACT;AACE;AAAA,EACJ;AACF;AAOA,SAAS,aAAa,OAAiC;AACrD,QAAM,eAAe;AACrB,SAAO,OAAO,UAAU,YAAY,aAAa,KAAK,KAAK;AAC7D;AASO,SAAS,eACd,KACA,YACA,SAAiB,eACT;AACR,MAAI,CAAC,KAAK;AACD,WAAA;AAAA,EACT;AAEA,OAAI,yCAAY,YAAW,KAAK,CAAC,YAAY;AACpC,WAAA;AAAA,EACT;AAEA,SAAO,WACJ;AAAA,IACC,CAAC,MAAM,MACL,GAAG,OAAO;AAAA,MACR;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,MAAM,KAAK;AAAA,IAAA,CACZ,CAAC,IAAI,WAAW,WAAW,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,SAAS,CAAC,GAAG;AAAA,EAAA,EAEtE,KAAK,IAAI;AACd;AAUO,SAAS,oBACd,QAAyB,QACzB,WACA,eACA,eACU;AACV,QAAM,aAAa,MAAM;AAAA,IACvB,EAAC,QAAQ,UAAS;AAAA,IAClB,CAAC,GAAG,MAAM,IAAI,gBAAgB;AAAA,EAAA;AAGhC,QAAM,QAAQ,MAAM;AAAA,IAClB,EAAC,QAAQ,EAAC;AAAA,IACV,CAAC,GAAG,OAAO,IAAI,MAAM,uBAAuB,KAAK,KAAK;AAAA,EAAA;AAGjD,SAAA,aAAa,KAAK,IAAI,QAAQ;AACvC;AASO,SAAS,iBAAiB,aAA0C;AACzE,MAAI,CAAC;AAAa;AAClB,QAAM,CAAC,OAAO,MAAM,IAAI,YAAY,MAAM,GAAG;AAC7C,SAAO,KAAK,OAAO,KAAK,IAAI,OAAO,MAAM;AAC3C;AAGO,SAAS,cACd,aACA,aACA,OAAa,UACb,kBAOY;AACZ,MAAI,CAAC;AAAa;AACX,SAAA,YACJ,IAAI,CAAC,UAAkB;AACf,WAAA;AAAA,MACL;AAAA,MACA,QAAQ,cACJ,SAAS,iBAAiB,WAAW,KAAK,KAC1C;AAAA,MACJ;AAAA,IAAA;AAAA,EAEH,CAAA,EACA,OAAO,CAAC,EAAC,OAAO,aAAY;AAC3B,SAAI,qDAAkB,UAAS,QAAQ,iBAAiB,OAAO;AACtD,aAAA;AAAA,IACT;AAEA,SACE,qDAAkB,WAClB,UACA,SAAS,iBAAiB,QAC1B;AACO,aAAA;AAAA,IACT;AAEO,WAAA;AAAA,EAAA,CACR;AAQL;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModelViewer.mjs","sources":["../../src/ModelViewer.tsx"],"sourcesContent":["import {useState, useEffect, useCallback} from 'react';\nimport {useLoadScript} from './load-script.js';\nimport type {Model3d} from './storefront-api-types.js';\nimport type {PartialDeep} from 'type-fest';\nimport type {ModelViewerElement} from '@google/model-viewer/lib/model-viewer.js';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace JSX {\n interface IntrinsicElements {\n 'model-viewer': PartialDeep<\n ModelViewerElement,\n {recurseIntoArrays: true}\n >;\n }\n }\n}\n\ntype ModelViewerProps = Omit<\n PartialDeep<JSX.IntrinsicElements['model-viewer'], {recurseIntoArrays: true}>,\n 'src'\n> &\n ModelViewerBaseProps;\n\ntype ModelViewerBaseProps = {\n /** An object with fields that correspond to the Storefront API's [Model3D object](https://shopify.dev/api/storefront/2024-10/objects/model3d). */\n data: PartialDeep<Model3d, {recurseIntoArrays: true}>;\n /** The callback to invoke when the 'error' event is triggered. Refer to [error in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-loading-events-error). */\n onError?: (event: Event) => void;\n /** The callback to invoke when the `load` event is triggered. Refer to [load in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-loading-events-load). */\n onLoad?: (event: Event) => void;\n /** The callback to invoke when the 'preload' event is triggered. Refer to [preload in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-loading-events-preload). */\n onPreload?: (event: Event) => void;\n /** The callback to invoke when the 'model-visibility' event is triggered. Refer to [model-visibility in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-loading-events-modelVisibility). */\n onModelVisibility?: (event: Event) => void;\n /** The callback to invoke when the 'progress' event is triggered. Refer to [progress in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-loading-events-progress). */\n onProgress?: (event: Event) => void;\n /** The callback to invoke when the 'ar-status' event is triggered. Refer to [ar-status in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-augmentedreality-events-arStatus). */\n onArStatus?: (event: Event) => void;\n /** The callback to invoke when the 'ar-tracking' event is triggered. Refer to [ar-tracking in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-augmentedreality-events-arTracking). */\n onArTracking?: (event: Event) => void;\n /** The callback to invoke when the 'quick-look-button-tapped' event is triggered. Refer to [quick-look-button-tapped in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-augmentedreality-events-quickLookButtonTapped). */\n onQuickLookButtonTapped?: (event: Event) => void;\n /** The callback to invoke when the 'camera-change' event is triggered. Refer to [camera-change in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-stagingandcameras-events-cameraChange). */\n onCameraChange?: (event: Event) => void;\n /** The callback to invoke when the 'environment-change' event is triggered. Refer to [environment-change in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-lightingandenv-events-environmentChange). */\n onEnvironmentChange?: (event: Event) => void;\n /** The callback to invoke when the 'play' event is triggered. Refer to [play in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-animation-events-play). */\n onPlay?: (event: Event) => void;\n /** The callback to invoke when the 'pause' event is triggered. Refer to [pause in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-animation-events-pause). */\n onPause?: (event: Event) => void;\n /** The callback to invoke when the 'scene-graph-ready' event is triggered. Refer to [scene-graph-ready in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-scenegraph-events-sceneGraphReady). */\n onSceneGraphReady?: (event: Event) => void;\n};\n\n/**\n * The `ModelViewer` component renders a 3D model (with the `model-viewer` custom element) for\n * the Storefront API's [Model3d object](https://shopify.dev/api/storefront/reference/products/model3d).\n *\n * The `model-viewer` custom element is lazily downloaded through a dynamically-injected `<script type=\"module\">` tag when the `<ModelViewer />` component is rendered\n *\n * ModelViewer is using version `1.21.1` of the `@google/model-viewer` library.\n */\nexport function ModelViewer(props: ModelViewerProps): JSX.Element | null {\n const [modelViewer, setModelViewer] = useState<undefined | HTMLElement>(\n undefined,\n );\n const callbackRef = useCallback((node: HTMLElement) => {\n setModelViewer(node);\n }, []);\n const {data, children, className, ...passthroughProps} = props;\n\n const modelViewerLoadedStatus = useLoadScript(\n 'https://unpkg.com/@google/model-viewer@v1.12.1/dist/model-viewer.min.js',\n {\n module: true,\n },\n );\n\n useEffect(() => {\n const hydrogenEventListener = {\n error: passthroughProps.onError,\n load: passthroughProps.onLoad,\n preload: passthroughProps.onPreload,\n 'model-visibility': passthroughProps.onModelVisibility,\n progress: passthroughProps.onProgress,\n 'ar-status': passthroughProps.onArStatus,\n 'ar-tracking': passthroughProps.onArTracking,\n 'quick-look-button-tapped': passthroughProps.onQuickLookButtonTapped,\n 'camera-change': passthroughProps.onCameraChange,\n 'environment-change': passthroughProps.onEnvironmentChange,\n play: passthroughProps.onPlay,\n pause: passthroughProps.onPause,\n 'scene-graph-ready': passthroughProps.onSceneGraphReady,\n };\n\n if (!modelViewer) {\n return;\n }\n Object.entries(hydrogenEventListener).forEach(\n ([eventName, callbackFunc]) => {\n if (callbackFunc) {\n modelViewer.addEventListener(eventName, callbackFunc);\n }\n },\n );\n\n return () => {\n if (modelViewer == null) {\n return;\n }\n Object.entries(hydrogenEventListener).forEach(\n ([eventName, callbackFunc]) => {\n if (callbackFunc) {\n modelViewer.removeEventListener(eventName, callbackFunc);\n }\n },\n );\n };\n }, [\n modelViewer,\n passthroughProps.onArStatus,\n passthroughProps.onArTracking,\n passthroughProps.onCameraChange,\n passthroughProps.onEnvironmentChange,\n passthroughProps.onError,\n passthroughProps.onLoad,\n passthroughProps.onModelVisibility,\n passthroughProps.onPause,\n passthroughProps.onPlay,\n passthroughProps.onPreload,\n passthroughProps.onProgress,\n passthroughProps.onQuickLookButtonTapped,\n passthroughProps.onSceneGraphReady,\n ]);\n\n if (modelViewerLoadedStatus !== 'done') {\n // TODO: What do we want to display while the model-viewer library loads?\n return null;\n }\n\n if (!data.sources?.[0]?.url) {\n const sourcesUrlError = `<ModelViewer/> requires 'data.sources' prop to be an array, with an object that has a property 'url' on it. Rendering 'null'`;\n if (__HYDROGEN_DEV__) {\n throw new Error(sourcesUrlError);\n } else {\n console.error(sourcesUrlError);\n return null;\n }\n }\n\n if (__HYDROGEN_DEV__ && !data.alt) {\n console.warn(\n `<ModelViewer/> requires the 'data.alt' prop for accessibility`,\n );\n }\n\n return (\n <model-viewer\n ref={callbackRef}\n {...passthroughProps}\n // @ts-expect-error src should exist\n // @eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n class={className}\n id={passthroughProps.id ?? data.id}\n src={data.sources[0].url}\n alt={data.alt ?? null}\n camera-controls={passthroughProps.cameraControls ?? true}\n poster={(passthroughProps.poster || data.previewImage?.url) ?? null}\n autoplay={passthroughProps.autoplay ?? true}\n loading={passthroughProps.loading}\n reveal={passthroughProps.reveal}\n ar={passthroughProps.ar}\n ar-modes={passthroughProps.arModes}\n ar-scale={passthroughProps.arScale}\n // @ts-expect-error arPlacement should exist as a type, not sure why it doesn't. https://modelviewer.dev/docs/index.html#entrydocs-augmentedreality-attributes-arPlacement\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n ar-placement={passthroughProps.arPlacement}\n ios-src={passthroughProps.iosSrc}\n touch-action={passthroughProps.touchAction}\n disable-zoom={passthroughProps.disableZoom}\n orbit-sensitivity={passthroughProps.orbitSensitivity}\n auto-rotate={passthroughProps.autoRotate}\n auto-rotate-delay={passthroughProps.autoRotateDelay}\n // @ts-expect-error rotationPerSecond should exist as a type, not sure why it doesn't. https://modelviewer.dev/docs/index.html#entrydocs-stagingandcameras-attributes-rotationPerSecond\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n rotation-per-second={passthroughProps.rotationPerSecond}\n interaction-policy={passthroughProps.interactionPolicy}\n interaction-prompt={passthroughProps.interactionPrompt}\n interaction-prompt-style={passthroughProps.interactionPromptStyle}\n interaction-prompt-threshold={passthroughProps.interactionPromptThreshold}\n camera-orbit={passthroughProps.cameraOrbit}\n camera-target={passthroughProps.cameraTarget}\n field-of-view={passthroughProps.fieldOfView}\n max-camera-orbit={passthroughProps.maxCameraOrbit}\n min-camera-orbit={passthroughProps.minCameraOrbit}\n max-field-of-view={passthroughProps.maxFieldOfView}\n min-field-of-view={passthroughProps.minFieldOfView}\n bounds={passthroughProps.bounds}\n interpolation-decay={passthroughProps.interpolationDecay ?? 100}\n skybox-image={passthroughProps.skyboxImage}\n environment-image={passthroughProps.environmentImage}\n exposure={passthroughProps.exposure}\n shadow-intensity={passthroughProps.shadowIntensity ?? 0}\n shadow-softness={passthroughProps.shadowSoftness ?? 0}\n animation-name={passthroughProps.animationName}\n animation-crossfade-duration={passthroughProps.animationCrossfadeDuration}\n variant-name={passthroughProps.variantName}\n orientation={passthroughProps.orientation}\n scale={passthroughProps.scale}\n >\n {children}\n </model-viewer>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ModelViewer.mjs","sources":["../../src/ModelViewer.tsx"],"sourcesContent":["import {useState, useEffect, useCallback} from 'react';\nimport {useLoadScript} from './load-script.js';\nimport type {Model3d} from './storefront-api-types.js';\nimport type {PartialDeep} from 'type-fest';\nimport type {ModelViewerElement} from '@google/model-viewer/lib/model-viewer.js';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace JSX {\n interface IntrinsicElements {\n 'model-viewer': PartialDeep<\n ModelViewerElement,\n {recurseIntoArrays: true}\n >;\n }\n }\n}\n\ntype ModelViewerProps = Omit<\n PartialDeep<JSX.IntrinsicElements['model-viewer'], {recurseIntoArrays: true}>,\n 'src'\n> &\n ModelViewerBaseProps;\n\ntype ModelViewerBaseProps = {\n /** An object with fields that correspond to the Storefront API's [Model3D object](https://shopify.dev/api/storefront/2025-01/objects/model3d). */\n data: PartialDeep<Model3d, {recurseIntoArrays: true}>;\n /** The callback to invoke when the 'error' event is triggered. Refer to [error in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-loading-events-error). */\n onError?: (event: Event) => void;\n /** The callback to invoke when the `load` event is triggered. Refer to [load in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-loading-events-load). */\n onLoad?: (event: Event) => void;\n /** The callback to invoke when the 'preload' event is triggered. Refer to [preload in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-loading-events-preload). */\n onPreload?: (event: Event) => void;\n /** The callback to invoke when the 'model-visibility' event is triggered. Refer to [model-visibility in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-loading-events-modelVisibility). */\n onModelVisibility?: (event: Event) => void;\n /** The callback to invoke when the 'progress' event is triggered. Refer to [progress in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-loading-events-progress). */\n onProgress?: (event: Event) => void;\n /** The callback to invoke when the 'ar-status' event is triggered. Refer to [ar-status in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-augmentedreality-events-arStatus). */\n onArStatus?: (event: Event) => void;\n /** The callback to invoke when the 'ar-tracking' event is triggered. Refer to [ar-tracking in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-augmentedreality-events-arTracking). */\n onArTracking?: (event: Event) => void;\n /** The callback to invoke when the 'quick-look-button-tapped' event is triggered. Refer to [quick-look-button-tapped in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-augmentedreality-events-quickLookButtonTapped). */\n onQuickLookButtonTapped?: (event: Event) => void;\n /** The callback to invoke when the 'camera-change' event is triggered. Refer to [camera-change in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-stagingandcameras-events-cameraChange). */\n onCameraChange?: (event: Event) => void;\n /** The callback to invoke when the 'environment-change' event is triggered. Refer to [environment-change in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-lightingandenv-events-environmentChange). */\n onEnvironmentChange?: (event: Event) => void;\n /** The callback to invoke when the 'play' event is triggered. Refer to [play in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-animation-events-play). */\n onPlay?: (event: Event) => void;\n /** The callback to invoke when the 'pause' event is triggered. Refer to [pause in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-animation-events-pause). */\n onPause?: (event: Event) => void;\n /** The callback to invoke when the 'scene-graph-ready' event is triggered. Refer to [scene-graph-ready in the <model-viewer> documentation](https://modelviewer.dev/docs/index.html#entrydocs-scenegraph-events-sceneGraphReady). */\n onSceneGraphReady?: (event: Event) => void;\n};\n\n/**\n * The `ModelViewer` component renders a 3D model (with the `model-viewer` custom element) for\n * the Storefront API's [Model3d object](https://shopify.dev/api/storefront/reference/products/model3d).\n *\n * The `model-viewer` custom element is lazily downloaded through a dynamically-injected `<script type=\"module\">` tag when the `<ModelViewer />` component is rendered\n *\n * ModelViewer is using version `1.21.1` of the `@google/model-viewer` library.\n */\nexport function ModelViewer(props: ModelViewerProps): JSX.Element | null {\n const [modelViewer, setModelViewer] = useState<undefined | HTMLElement>(\n undefined,\n );\n const callbackRef = useCallback((node: HTMLElement) => {\n setModelViewer(node);\n }, []);\n const {data, children, className, ...passthroughProps} = props;\n\n const modelViewerLoadedStatus = useLoadScript(\n 'https://unpkg.com/@google/model-viewer@v1.12.1/dist/model-viewer.min.js',\n {\n module: true,\n },\n );\n\n useEffect(() => {\n const hydrogenEventListener = {\n error: passthroughProps.onError,\n load: passthroughProps.onLoad,\n preload: passthroughProps.onPreload,\n 'model-visibility': passthroughProps.onModelVisibility,\n progress: passthroughProps.onProgress,\n 'ar-status': passthroughProps.onArStatus,\n 'ar-tracking': passthroughProps.onArTracking,\n 'quick-look-button-tapped': passthroughProps.onQuickLookButtonTapped,\n 'camera-change': passthroughProps.onCameraChange,\n 'environment-change': passthroughProps.onEnvironmentChange,\n play: passthroughProps.onPlay,\n pause: passthroughProps.onPause,\n 'scene-graph-ready': passthroughProps.onSceneGraphReady,\n };\n\n if (!modelViewer) {\n return;\n }\n Object.entries(hydrogenEventListener).forEach(\n ([eventName, callbackFunc]) => {\n if (callbackFunc) {\n modelViewer.addEventListener(eventName, callbackFunc);\n }\n },\n );\n\n return () => {\n if (modelViewer == null) {\n return;\n }\n Object.entries(hydrogenEventListener).forEach(\n ([eventName, callbackFunc]) => {\n if (callbackFunc) {\n modelViewer.removeEventListener(eventName, callbackFunc);\n }\n },\n );\n };\n }, [\n modelViewer,\n passthroughProps.onArStatus,\n passthroughProps.onArTracking,\n passthroughProps.onCameraChange,\n passthroughProps.onEnvironmentChange,\n passthroughProps.onError,\n passthroughProps.onLoad,\n passthroughProps.onModelVisibility,\n passthroughProps.onPause,\n passthroughProps.onPlay,\n passthroughProps.onPreload,\n passthroughProps.onProgress,\n passthroughProps.onQuickLookButtonTapped,\n passthroughProps.onSceneGraphReady,\n ]);\n\n if (modelViewerLoadedStatus !== 'done') {\n // TODO: What do we want to display while the model-viewer library loads?\n return null;\n }\n\n if (!data.sources?.[0]?.url) {\n const sourcesUrlError = `<ModelViewer/> requires 'data.sources' prop to be an array, with an object that has a property 'url' on it. Rendering 'null'`;\n if (__HYDROGEN_DEV__) {\n throw new Error(sourcesUrlError);\n } else {\n console.error(sourcesUrlError);\n return null;\n }\n }\n\n if (__HYDROGEN_DEV__ && !data.alt) {\n console.warn(\n `<ModelViewer/> requires the 'data.alt' prop for accessibility`,\n );\n }\n\n return (\n <model-viewer\n ref={callbackRef}\n {...passthroughProps}\n // @ts-expect-error src should exist\n // @eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n class={className}\n id={passthroughProps.id ?? data.id}\n src={data.sources[0].url}\n alt={data.alt ?? null}\n camera-controls={passthroughProps.cameraControls ?? true}\n poster={(passthroughProps.poster || data.previewImage?.url) ?? null}\n autoplay={passthroughProps.autoplay ?? true}\n loading={passthroughProps.loading}\n reveal={passthroughProps.reveal}\n ar={passthroughProps.ar}\n ar-modes={passthroughProps.arModes}\n ar-scale={passthroughProps.arScale}\n // @ts-expect-error arPlacement should exist as a type, not sure why it doesn't. https://modelviewer.dev/docs/index.html#entrydocs-augmentedreality-attributes-arPlacement\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n ar-placement={passthroughProps.arPlacement}\n ios-src={passthroughProps.iosSrc}\n touch-action={passthroughProps.touchAction}\n disable-zoom={passthroughProps.disableZoom}\n orbit-sensitivity={passthroughProps.orbitSensitivity}\n auto-rotate={passthroughProps.autoRotate}\n auto-rotate-delay={passthroughProps.autoRotateDelay}\n // @ts-expect-error rotationPerSecond should exist as a type, not sure why it doesn't. https://modelviewer.dev/docs/index.html#entrydocs-stagingandcameras-attributes-rotationPerSecond\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n rotation-per-second={passthroughProps.rotationPerSecond}\n interaction-policy={passthroughProps.interactionPolicy}\n interaction-prompt={passthroughProps.interactionPrompt}\n interaction-prompt-style={passthroughProps.interactionPromptStyle}\n interaction-prompt-threshold={passthroughProps.interactionPromptThreshold}\n camera-orbit={passthroughProps.cameraOrbit}\n camera-target={passthroughProps.cameraTarget}\n field-of-view={passthroughProps.fieldOfView}\n max-camera-orbit={passthroughProps.maxCameraOrbit}\n min-camera-orbit={passthroughProps.minCameraOrbit}\n max-field-of-view={passthroughProps.maxFieldOfView}\n min-field-of-view={passthroughProps.minFieldOfView}\n bounds={passthroughProps.bounds}\n interpolation-decay={passthroughProps.interpolationDecay ?? 100}\n skybox-image={passthroughProps.skyboxImage}\n environment-image={passthroughProps.environmentImage}\n exposure={passthroughProps.exposure}\n shadow-intensity={passthroughProps.shadowIntensity ?? 0}\n shadow-softness={passthroughProps.shadowSoftness ?? 0}\n animation-name={passthroughProps.animationName}\n animation-crossfade-duration={passthroughProps.animationCrossfadeDuration}\n variant-name={passthroughProps.variantName}\n orientation={passthroughProps.orientation}\n scale={passthroughProps.scale}\n >\n {children}\n </model-viewer>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Money.mjs","sources":["../../src/Money.tsx"],"sourcesContent":["import {type ReactNode} from 'react';\nimport {useMoney} from './useMoney.js';\nimport type {MoneyV2, UnitPriceMeasurement} from './storefront-api-types.js';\nimport type {PartialDeep} from 'type-fest';\n\nexport interface MoneyPropsBase<ComponentGeneric extends React.ElementType> {\n /** An HTML tag or React Component to be rendered as the base element wrapper. The default is `div`. */\n as?: ComponentGeneric;\n /** An object with fields that correspond to the Storefront API's [MoneyV2 object](https://shopify.dev/api/storefront/reference/common-objects/moneyv2). */\n data: PartialDeep<MoneyV2, {recurseIntoArrays: true}>;\n /** Whether to remove the currency symbol from the output. */\n withoutCurrency?: boolean;\n /** Whether to remove trailing zeros (fractional money) from the output. */\n withoutTrailingZeros?: boolean;\n /** A [UnitPriceMeasurement object](https://shopify.dev/api/storefront/
|
|
1
|
+
{"version":3,"file":"Money.mjs","sources":["../../src/Money.tsx"],"sourcesContent":["import {type ReactNode} from 'react';\nimport {useMoney} from './useMoney.js';\nimport type {MoneyV2, UnitPriceMeasurement} from './storefront-api-types.js';\nimport type {PartialDeep} from 'type-fest';\n\nexport interface MoneyPropsBase<ComponentGeneric extends React.ElementType> {\n /** An HTML tag or React Component to be rendered as the base element wrapper. The default is `div`. */\n as?: ComponentGeneric;\n /** An object with fields that correspond to the Storefront API's [MoneyV2 object](https://shopify.dev/api/storefront/reference/common-objects/moneyv2). */\n data: PartialDeep<MoneyV2, {recurseIntoArrays: true}>;\n /** Whether to remove the currency symbol from the output. */\n withoutCurrency?: boolean;\n /** Whether to remove trailing zeros (fractional money) from the output. */\n withoutTrailingZeros?: boolean;\n /** A [UnitPriceMeasurement object](https://shopify.dev/api/storefront/2025-01/objects/unitpricemeasurement). */\n measurement?: PartialDeep<UnitPriceMeasurement, {recurseIntoArrays: true}>;\n /** Customizes the separator between the money output and the measurement output. Used with the `measurement` prop. Defaults to `'/'`. */\n measurementSeparator?: ReactNode;\n}\n\n// This article helps understand the typing here https://www.benmvp.com/blog/polymorphic-react-components-typescript/ Ben is the best :)\nexport type MoneyProps<ComponentGeneric extends React.ElementType> =\n MoneyPropsBase<ComponentGeneric> &\n (ComponentGeneric extends keyof React.JSX.IntrinsicElements\n ? Omit<\n React.ComponentPropsWithoutRef<ComponentGeneric>,\n keyof MoneyPropsBase<ComponentGeneric>\n >\n : React.ComponentPropsWithoutRef<ComponentGeneric>);\n\n/**\n * The `Money` component renders a string of the Storefront API's\n * [MoneyV2 object](https://shopify.dev/api/storefront/reference/common-objects/moneyv2)\n * according to the `locale` in the `ShopifyProvider` component.\n * \n * @see {@link https://shopify.dev/api/hydrogen/components/money}\n * @example basic usage, outputs: $100.00\n * ```ts\n * <Money data={{amount: '100.00', currencyCode: 'USD'}} />\n * ```\n * \n *\n * @example without currency, outputs: 100.00\n * ```ts\n * <Money data={{amount: '100.00', currencyCode: 'USD'}} withoutCurrency />\n * ```\n * \n *\n * @example without trailing zeros, outputs: $100\n * ```ts\n * <Money data={{amount: '100.00', currencyCode: 'USD'}} withoutTrailingZeros />\n * ```\n * \n *\n * @example with per-unit measurement, outputs: $100.00 per G\n * ```ts\n * <Money\n * data={{amount: '100.00', currencyCode: 'USD'}}\n * measurement={{referenceUnit: 'G'}}\n * measurementSeparator=\" per \"\n * />\n * ```\n */\nexport function Money<ComponentGeneric extends React.ElementType = 'div'>({\n data,\n as,\n withoutCurrency,\n withoutTrailingZeros,\n measurement,\n measurementSeparator = '/',\n ...passthroughProps\n}: MoneyProps<ComponentGeneric>): JSX.Element {\n if (!isMoney(data)) {\n throw new Error(\n `<Money/> needs a valid 'data' prop that has 'amount' and 'currencyCode'`,\n );\n }\n const moneyObject = useMoney(data);\n const Wrapper = as ?? 'div';\n\n let output = moneyObject.localizedString;\n\n if (withoutCurrency || withoutTrailingZeros) {\n if (withoutCurrency && !withoutTrailingZeros) {\n output = moneyObject.amount;\n } else if (!withoutCurrency && withoutTrailingZeros) {\n output = moneyObject.withoutTrailingZeros;\n } else {\n // both\n output = moneyObject.withoutTrailingZerosAndCurrency;\n }\n }\n\n return (\n <Wrapper {...passthroughProps}>\n {output}\n {measurement && measurement.referenceUnit && (\n <>\n {measurementSeparator}\n {measurement.referenceUnit}\n </>\n )}\n </Wrapper>\n );\n}\n\n// required in order to narrow the money object down and make TS happy\nfunction isMoney(\n maybeMoney: PartialDeep<MoneyV2, {recurseIntoArrays: true}>,\n): maybeMoney is MoneyV2 {\n return (\n typeof maybeMoney.amount === 'string' &&\n !!maybeMoney.amount &&\n typeof maybeMoney.currencyCode === 'string' &&\n !!maybeMoney.currencyCode\n );\n}\n"],"names":[],"mappings":";;AA+DO,SAAS,MAA0D;AAAA,EACxE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,uBAAuB;AAAA,EACvB,GAAG;AACL,GAA8C;AACxC,MAAA,CAAC,QAAQ,IAAI,GAAG;AAClB,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AACM,QAAA,cAAc,SAAS,IAAI;AACjC,QAAM,UAAU,MAAM;AAEtB,MAAI,SAAS,YAAY;AAEzB,MAAI,mBAAmB,sBAAsB;AACvC,QAAA,mBAAmB,CAAC,sBAAsB;AAC5C,eAAS,YAAY;AAAA,IAAA,WACZ,CAAC,mBAAmB,sBAAsB;AACnD,eAAS,YAAY;AAAA,IAAA,OAChB;AAEL,eAAS,YAAY;AAAA,IACvB;AAAA,EACF;AAGE,SAAA,qBAAC,SAAS,EAAA,GAAG,kBACV,UAAA;AAAA,IAAA;AAAA,IACA,eAAe,YAAY,iBAEvB,qBAAA,UAAA,EAAA,UAAA;AAAA,MAAA;AAAA,MACA,YAAY;AAAA,IAAA,GACf;AAAA,EAEJ,EAAA,CAAA;AAEJ;AAGA,SAAS,QACP,YACuB;AACvB,SACE,OAAO,WAAW,WAAW,YAC7B,CAAC,CAAC,WAAW,UACb,OAAO,WAAW,iBAAiB,YACnC,CAAC,CAAC,WAAW;AAEjB;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Video.mjs","sources":["../../src/Video.tsx"],"sourcesContent":["import {forwardRef, type HTMLAttributes} from 'react';\nimport {shopifyLoader} from './Image.js';\nimport type {Video as VideoType} from './storefront-api-types.js';\nimport type {PartialDeep} from 'type-fest';\n\nexport interface VideoProps {\n /** An object with fields that correspond to the Storefront API's [Video object](https://shopify.dev/api/storefront/
|
|
1
|
+
{"version":3,"file":"Video.mjs","sources":["../../src/Video.tsx"],"sourcesContent":["import {forwardRef, type HTMLAttributes} from 'react';\nimport {shopifyLoader} from './Image.js';\nimport type {Video as VideoType} from './storefront-api-types.js';\nimport type {PartialDeep} from 'type-fest';\n\nexport interface VideoProps {\n /** An object with fields that correspond to the Storefront API's [Video object](https://shopify.dev/api/storefront/2025-01/objects/video). */\n data: PartialDeep<VideoType, {recurseIntoArrays: true}>;\n /** An object of image size options for the video's `previewImage`. Uses `shopifyImageLoader` to generate the `poster` URL. */\n previewImageOptions?: Parameters<typeof shopifyLoader>[0];\n /** Props that will be passed to the `video` element's `source` children elements. */\n sourceProps?: HTMLAttributes<HTMLSourceElement> & {\n 'data-testid'?: string;\n };\n}\n\n/**\n * The `Video` component renders a `video` for the Storefront API's [Video object](https://shopify.dev/api/storefront/reference/products/video).\n */\nexport const Video = forwardRef<\n HTMLVideoElement,\n JSX.IntrinsicElements['video'] & VideoProps\n>((props, ref): JSX.Element => {\n const {\n data,\n previewImageOptions,\n id = data.id,\n playsInline = true,\n controls = true,\n sourceProps = {},\n ...passthroughProps\n } = props;\n\n const posterUrl = shopifyLoader({\n src: data.previewImage?.url ?? '',\n ...previewImageOptions,\n });\n\n if (!data.sources) {\n throw new Error(`<Video/> requires a 'data.sources' array`);\n }\n\n return (\n // eslint-disable-next-line jsx-a11y/media-has-caption\n <video\n {...passthroughProps}\n id={id}\n playsInline={playsInline}\n controls={controls}\n poster={posterUrl}\n ref={ref}\n >\n {data.sources.map((source) => {\n if (!(source?.url && source?.mimeType)) {\n throw new Error(`<Video/> needs 'source.url' and 'source.mimeType'`);\n }\n return (\n <source\n {...sourceProps}\n key={source.url}\n src={source.url}\n type={source.mimeType}\n />\n );\n })}\n </video>\n );\n});\n"],"names":[],"mappings":";;;AAmBO,MAAM,QAAQ,WAGnB,CAAC,OAAO,QAAqB;;AACvB,QAAA;AAAA,IACJ;AAAA,IACA;AAAA,IACA,KAAK,KAAK;AAAA,IACV,cAAc;AAAA,IACd,WAAW;AAAA,IACX,cAAc,CAAC;AAAA,IACf,GAAG;AAAA,EACD,IAAA;AAEJ,QAAM,YAAY,cAAc;AAAA,IAC9B,OAAK,UAAK,iBAAL,mBAAmB,QAAO;AAAA,IAC/B,GAAG;AAAA,EAAA,CACJ;AAEG,MAAA,CAAC,KAAK,SAAS;AACX,UAAA,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AAEA;AAAA;AAAA,IAEE;AAAA,MAAC;AAAA,MAAA;AAAA,QACE,GAAG;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,QAEC,UAAK,KAAA,QAAQ,IAAI,CAAC,WAAW;AAC5B,cAAI,GAAE,iCAAQ,SAAO,iCAAQ,YAAW;AAChC,kBAAA,IAAI,MAAM,mDAAmD;AAAA,UACrE;AAEE,iBAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACE,GAAG;AAAA,cACJ,KAAK,OAAO;AAAA,cACZ,KAAK,OAAO;AAAA,cACZ,MAAM,OAAO;AAAA,YAAA;AAAA,UAAA;AAAA,QACf,CAEH;AAAA,MAAA;AAAA,IACH;AAAA;AAEJ,CAAC;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen.helpers.mjs","sources":["../../src/codegen.helpers.ts"],"sourcesContent":["/**\n * Meant to be used with GraphQL CodeGen to type the Storefront API's custom scalars correctly.\n * Reference for the GraphQL types: https://shopify.dev/docs/api/storefront/
|
|
1
|
+
{"version":3,"file":"codegen.helpers.mjs","sources":["../../src/codegen.helpers.ts"],"sourcesContent":["/**\n * Meant to be used with GraphQL CodeGen to type the Storefront API's custom scalars correctly.\n * Reference for the GraphQL types: https://shopify.dev/docs/api/storefront/2025-01/scalars/HTML\n * Note: JSON is generated as 'unknown' by default.\n */\nexport const storefrontApiCustomScalars = {\n // Keep in sync with the definitions in the app/nextjs/codegen.ts!\n DateTime: 'string',\n Decimal: 'string',\n HTML: 'string',\n URL: 'string',\n Color: 'string',\n UnsignedInt64: 'string',\n};\n\n/**\n * Meant to be used with GraphQL CodeGen to type the Storefront API's custom scalars correctly.\n * Reference for the GraphQL types: https://shopify.dev/docs/api/customer/2025-01/scalars/HTML\n * Note: JSON is generated as 'unknown' by default.\n */\nexport const customerAccountApiCustomScalars = {\n DateTime: 'string',\n Decimal: 'string',\n HTML: 'string',\n ISO8601DateTime: 'string',\n URL: 'string',\n UnsignedInt64: 'string',\n};\n"],"names":[],"mappings":"AAKO,MAAM,6BAA6B;AAAA;AAAA,EAExC,UAAU;AAAA,EACV,SAAS;AAAA,EACT,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,eAAe;AACjB;AAOO,MAAM,kCAAkC;AAAA,EAC7C,UAAU;AAAA,EACV,SAAS;AAAA,EACT,MAAM;AAAA,EACN,iBAAiB;AAAA,EACjB,KAAK;AAAA,EACL,eAAe;AACjB;"}
|
|
@@ -78,10 +78,13 @@ function checkProductParam(product, checkAll = false) {
|
|
|
78
78
|
validParam = logErrorAndReturnFalse("options.name");
|
|
79
79
|
}
|
|
80
80
|
if ((_a = product == null ? void 0 : product.options[0]) == null ? void 0 : _a.optionValues) {
|
|
81
|
-
|
|
81
|
+
let firstOptionValues = product.options[0].optionValues[0];
|
|
82
82
|
if (checkAll && !(firstOptionValues == null ? void 0 : firstOptionValues.name)) {
|
|
83
83
|
validParam = logErrorAndReturnFalse("options.optionValues.name");
|
|
84
84
|
}
|
|
85
|
+
firstOptionValues = product.options[0].optionValues.filter(
|
|
86
|
+
(value) => !!(value == null ? void 0 : value.firstSelectableVariant)
|
|
87
|
+
)[0];
|
|
85
88
|
if (firstOptionValues == null ? void 0 : firstOptionValues.firstSelectableVariant) {
|
|
86
89
|
validParam = checkProductVariantParam(
|
|
87
90
|
firstOptionValues.firstSelectableVariant,
|
|
@@ -89,10 +92,6 @@ function checkProductParam(product, checkAll = false) {
|
|
|
89
92
|
validParam,
|
|
90
93
|
checkAll
|
|
91
94
|
);
|
|
92
|
-
} else {
|
|
93
|
-
validParam = logErrorAndReturnFalse(
|
|
94
|
-
"options.optionValues.firstSelectableVariant"
|
|
95
|
-
);
|
|
96
95
|
}
|
|
97
96
|
} else {
|
|
98
97
|
validParam = logErrorAndReturnFalse("options.optionValues");
|
|
@@ -210,11 +209,14 @@ function getProductOptions(product) {
|
|
|
210
209
|
encodedVariantAvailability || ""
|
|
211
210
|
);
|
|
212
211
|
const variant = variants[targetKey] || value.firstSelectableVariant;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
let variantOptionParam = {};
|
|
213
|
+
if (variant) {
|
|
214
|
+
variantOptionParam = mapSelectedProductOptionToObject(
|
|
215
|
+
variant.selectedOptions || []
|
|
216
|
+
);
|
|
217
|
+
}
|
|
216
218
|
const searchParams = new URLSearchParams(variantOptionParam);
|
|
217
|
-
const handle = (_a = variant == null ? void 0 : variant.product) == null ? void 0 : _a.handle;
|
|
219
|
+
const handle = ((_a = variant == null ? void 0 : variant.product) == null ? void 0 : _a.handle) || productHandle;
|
|
218
220
|
return {
|
|
219
221
|
...value,
|
|
220
222
|
variant,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getProductOptions.mjs","sources":["../../src/getProductOptions.ts"],"sourcesContent":["import {isOptionValueCombinationInEncodedVariant} from './optionValueDecoder.js';\nimport type {\n Product,\n ProductOption,\n ProductOptionValue,\n ProductVariant,\n SelectedOption,\n} from './storefront-api-types';\n\nexport type RecursivePartial<T> = {\n [P in keyof T]?: RecursivePartial<T[P]>;\n};\ntype ProductOptionsMapping = Record<string, number>;\ntype ProductOptionValueState = {\n variant: ProductVariant;\n handle: string;\n variantUriQuery: string;\n selected: boolean;\n exists: boolean;\n available: boolean;\n isDifferentProduct: boolean;\n};\ntype MappedProductOptionValue = ProductOptionValue & ProductOptionValueState;\n\n/**\n * Creates a mapping of product options to their index for matching encoded values\n * For example, a product option of\n * [\n * \\{\n * name: 'Color',\n * optionValues: [\\{name: 'Red'\\}, \\{name: 'Blue'\\}]\n * \\},\n * \\{\n * name: 'Size',\n * optionValues: [\\{name: 'Small'\\}, \\{name: 'Medium'\\}, \\{name: 'Large'\\}]\n * \\}\n * ]\n * Would return\n * [\n * \\{Red: 0, Blue: 1\\},\n * \\{Small: 0, Medium: 1, Large: 2\\}\n * ]\n */\nfunction mapProductOptions(options: ProductOption[]): ProductOptionsMapping[] {\n return options.map((option: ProductOption) => {\n return Object.assign(\n {},\n ...(option?.optionValues\n ? option.optionValues.map((value, index) => {\n return {[value.name]: index};\n })\n : []),\n ) as ProductOptionsMapping;\n });\n}\n\n/**\n * Converts the product option into an Object\\<key, value\\> for building query params\n * For example, a selected product option of\n * [\n * \\{\n * name: 'Color',\n * value: 'Red',\n * \\},\n * \\{\n * name: 'Size',\n * value: 'Medium',\n * \\}\n * ]\n * Would return\n * \\{\n * Color: 'Red',\n * Size: 'Medium',\n * \\}\n */\nexport function mapSelectedProductOptionToObject(\n options: Pick<SelectedOption, 'name' | 'value'>[],\n): Record<string, string> {\n return Object.assign(\n {},\n ...options.map((key) => {\n return {[key.name]: key.value};\n }),\n ) as Record<string, string>;\n}\n\n/**\n * Returns the JSON stringify result of mapSelectedProductOptionToObject\n */\nfunction mapSelectedProductOptionToObjectAsString(\n options: Pick<SelectedOption, 'name' | 'value'>[],\n): string {\n return JSON.stringify(mapSelectedProductOptionToObject(options));\n}\n\n/**\n * Encode the selected product option as a key for mapping to the encoded variants\n * For example, a selected product option of\n * [\n * \\{\n * name: 'Color',\n * value: 'Red',\n * \\},\n * \\{\n * name: 'Size',\n * value: 'Medium',\n * \\}\n * ]\n * Would return\n * [0,1]\n *\n * Also works with the result of mapSelectedProductOption. For example:\n * \\{\n * Color: 'Red',\n * Size: 'Medium',\n * \\}\n * Would return\n * [0,1]\n *\n * @param selectedOption - The selected product option\n * @param productOptionMappings - The result of product option mapping from mapProductOptions\n * @returns\n */\nfunction encodeSelectedProductOptionAsKey(\n selectedOption:\n | Pick<SelectedOption, 'name' | 'value'>[]\n | Record<string, string>,\n productOptionMappings: ProductOptionsMapping[],\n): string {\n if (Array.isArray(selectedOption)) {\n return JSON.stringify(\n selectedOption.map((key, index) => {\n return productOptionMappings[index][key.value];\n }),\n );\n } else {\n return JSON.stringify(\n Object.keys(selectedOption).map((key, index) => {\n return productOptionMappings[index][selectedOption[key]];\n }),\n );\n }\n}\n\n/**\n * Takes an array of product variants and maps them to an object with the encoded selected option values as the key.\n * For example, a product variant of\n * [\n * \\{\n * id: 1,\n * selectedOptions: [\n * \\{name: 'Color', value: 'Red'\\},\n * \\{name: 'Size', value: 'Small'\\},\n * ],\n * \\},\n * \\{\n * id: 2,\n * selectedOptions: [\n * \\{name: 'Color', value: 'Red'\\},\n * \\{name: 'Size', value: 'Medium'\\},\n * ],\n * \\}\n * ]\n * Would return\n * \\{\n * '[0,0]': \\{id: 1, selectedOptions: [\\{name: 'Color', value: 'Red'\\}, \\{name: 'Size', value: 'Small'\\}]\\},\n * '[0,1]': \\{id: 2, selectedOptions: [\\{name: 'Color', value: 'Red'\\}, \\{name: 'Size', value: 'Medium'\\}]\\},\n * \\}\n */\nfunction mapVariants(\n variants: ProductVariant[],\n productOptionMappings: ProductOptionsMapping[],\n): Record<string, ProductVariant> {\n return Object.assign(\n {},\n ...variants.map((variant) => {\n const variantKey = encodeSelectedProductOptionAsKey(\n variant.selectedOptions || [],\n productOptionMappings,\n );\n return {[variantKey]: variant};\n }),\n ) as Record<string, ProductVariant>;\n}\n\nexport type MappedProductOptions = Omit<ProductOption, 'optionValues'> & {\n optionValues: MappedProductOptionValue[];\n};\n\nconst PRODUCT_INPUTS = [\n 'options',\n 'selectedOrFirstAvailableVariant',\n 'adjacentVariants',\n];\n\nconst PRODUCT_INPUTS_EXTRA = [\n 'handle',\n 'encodedVariantExistence',\n 'encodedVariantAvailability',\n];\n\nfunction logErrorAndReturnFalse(key: string): boolean {\n console.error(\n `[h2:error:getProductOptions] product.${key} is missing. Make sure you query for this field from the Storefront API.`,\n );\n return false;\n}\n\nexport function checkProductParam(\n product: RecursivePartial<Product>,\n checkAll = false,\n): Product {\n let validParam = true;\n const productKeys = Object.keys(product);\n\n // Check product input\n (checkAll\n ? [...PRODUCT_INPUTS, ...PRODUCT_INPUTS_EXTRA]\n : PRODUCT_INPUTS\n ).forEach((key) => {\n if (!productKeys.includes(key)) {\n validParam = logErrorAndReturnFalse(key);\n }\n });\n\n // Check for nested options requirements\n if (product.options) {\n const firstOption = product?.options[0];\n\n if (checkAll && !firstOption?.name) {\n validParam = logErrorAndReturnFalse('options.name');\n }\n\n // Check for options.optionValues\n if (product?.options[0]?.optionValues) {\n const firstOptionValues = product.options[0].optionValues[0];\n\n // Check for options.optionValues.name\n if (checkAll && !firstOptionValues?.name) {\n validParam = logErrorAndReturnFalse('options.optionValues.name');\n }\n\n // Check for options.optionValues.firstSelectableVariant\n if (firstOptionValues?.firstSelectableVariant) {\n // check product variant\n validParam = checkProductVariantParam(\n firstOptionValues.firstSelectableVariant,\n 'options.optionValues.firstSelectableVariant',\n validParam,\n checkAll,\n );\n } else {\n validParam = logErrorAndReturnFalse(\n 'options.optionValues.firstSelectableVariant',\n );\n }\n } else {\n validParam = logErrorAndReturnFalse('options.optionValues');\n }\n }\n\n // Check for nested selectedOrFirstAvailableVariant requirements\n if (product.selectedOrFirstAvailableVariant) {\n validParam = checkProductVariantParam(\n product.selectedOrFirstAvailableVariant,\n 'selectedOrFirstAvailableVariant',\n validParam,\n checkAll,\n );\n }\n\n // Check for nested adjacentVariants requirements\n if (!!product.adjacentVariants && product.adjacentVariants[0]) {\n validParam = checkProductVariantParam(\n product.adjacentVariants[0],\n 'adjacentVariants',\n validParam,\n checkAll,\n );\n }\n\n return (validParam ? product : {}) as Product;\n}\n\nfunction checkProductVariantParam(\n variant: RecursivePartial<ProductVariant>,\n key: string,\n currentValidParamState: boolean,\n checkAll: boolean,\n): boolean {\n let validParam = currentValidParamState;\n\n if (checkAll && !variant.product?.handle) {\n validParam = logErrorAndReturnFalse(`${key}.product.handle`);\n }\n if (variant.selectedOptions) {\n const firstSelectedOption = variant.selectedOptions[0];\n if (!firstSelectedOption?.name) {\n validParam = logErrorAndReturnFalse(`${key}.selectedOptions.name`);\n }\n if (!firstSelectedOption?.value) {\n validParam = logErrorAndReturnFalse(`${key}.selectedOptions.value`);\n }\n } else {\n validParam = logErrorAndReturnFalse(`${key}.selectedOptions`);\n }\n\n return validParam;\n}\n\n/**\n * Finds all the variants provided by adjacentVariants, options.optionValues.firstAvailableVariant,\n * and selectedOrFirstAvailableVariant and return them in a single array\n */\nexport function getAdjacentAndFirstAvailableVariants(\n product: RecursivePartial<Product>,\n): ProductVariant[] {\n // Checks for valid product input\n const checkedProduct = checkProductParam(product);\n\n if (!checkedProduct.options) return [];\n\n const availableVariants: Record<string, ProductVariant> = {};\n checkedProduct.options.map((option) => {\n option.optionValues?.map((value) => {\n if (value.firstSelectableVariant) {\n const variantKey = mapSelectedProductOptionToObjectAsString(\n value.firstSelectableVariant.selectedOptions,\n );\n availableVariants[variantKey] = value.firstSelectableVariant;\n }\n });\n });\n\n checkedProduct.adjacentVariants.map((variant) => {\n const variantKey = mapSelectedProductOptionToObjectAsString(\n variant.selectedOptions,\n );\n availableVariants[variantKey] = variant;\n });\n\n const selectedVariant = checkedProduct.selectedOrFirstAvailableVariant;\n if (selectedVariant) {\n const variantKey = mapSelectedProductOptionToObjectAsString(\n selectedVariant.selectedOptions,\n );\n availableVariants[variantKey] = selectedVariant;\n }\n\n return Object.values(availableVariants);\n}\n\n/**\n * Returns a product options array with its relevant information\n * about the variant\n */\nexport function getProductOptions(\n product: RecursivePartial<Product>,\n): MappedProductOptions[] {\n // Checks for valid product input\n const checkedProduct = checkProductParam(product, true);\n\n if (!checkedProduct.options) return [];\n\n const {\n options,\n selectedOrFirstAvailableVariant: selectedVariant,\n adjacentVariants,\n encodedVariantExistence,\n encodedVariantAvailability,\n handle: productHandle,\n } = checkedProduct;\n // Get a mapping of product option names to their index for matching encoded values\n const productOptionMappings = mapProductOptions(options);\n\n // Get the adjacent variants mapped to the encoded selected option values\n const variants = mapVariants(\n selectedVariant ? [selectedVariant, ...adjacentVariants] : adjacentVariants,\n productOptionMappings,\n );\n\n // Get the key:value version of selected options for building url query params\n const selectedOptions = mapSelectedProductOptionToObject(\n selectedVariant ? selectedVariant.selectedOptions : [],\n );\n\n const productOptions = options.map((option, optionIndex) => {\n return {\n ...option,\n optionValues: option.optionValues.map((value) => {\n const targetOptionParams = {...selectedOptions}; // Clones the selected options\n\n // Modify the selected option value to the current option value\n targetOptionParams[option.name] = value.name;\n\n // Encode the new selected option values as a key for mapping to the product variants\n const targetKey = encodeSelectedProductOptionAsKey(\n targetOptionParams || [],\n productOptionMappings,\n );\n\n // Top-down option check for existence and availability\n const topDownKey = (JSON.parse(targetKey) as number[]).slice(\n 0,\n optionIndex + 1,\n );\n const exists = isOptionValueCombinationInEncodedVariant(\n topDownKey,\n encodedVariantExistence || '',\n );\n const available = isOptionValueCombinationInEncodedVariant(\n topDownKey,\n encodedVariantAvailability || '',\n );\n\n // Get the variant for the current option value if exists, else use the first selectable variant\n const variant: ProductVariant =\n variants[targetKey] || value.firstSelectableVariant;\n\n // Build the query params for this option value\n const variantOptionParam = mapSelectedProductOptionToObject(\n variant.selectedOptions || [],\n );\n const searchParams = new URLSearchParams(variantOptionParam);\n const handle = variant?.product?.handle;\n\n return {\n ...value,\n variant,\n handle,\n variantUriQuery: searchParams.toString(),\n selected: selectedOptions[option.name] === value.name,\n exists,\n available,\n isDifferentProduct: handle !== productHandle,\n };\n }),\n };\n });\n\n return productOptions;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"getProductOptions.mjs","sources":["../../src/getProductOptions.ts"],"sourcesContent":["import {isOptionValueCombinationInEncodedVariant} from './optionValueDecoder.js';\nimport type {\n Product,\n ProductOption,\n ProductOptionValue,\n ProductVariant,\n SelectedOption,\n} from './storefront-api-types';\n\nexport type RecursivePartial<T> = {\n [P in keyof T]?: RecursivePartial<T[P]>;\n};\ntype ProductOptionsMapping = Record<string, number>;\ntype ProductOptionValueState = {\n variant: ProductVariant;\n handle: string;\n variantUriQuery: string;\n selected: boolean;\n exists: boolean;\n available: boolean;\n isDifferentProduct: boolean;\n};\ntype MappedProductOptionValue = ProductOptionValue & ProductOptionValueState;\n\n/**\n * Creates a mapping of product options to their index for matching encoded values\n * For example, a product option of\n * [\n * \\{\n * name: 'Color',\n * optionValues: [\\{name: 'Red'\\}, \\{name: 'Blue'\\}]\n * \\},\n * \\{\n * name: 'Size',\n * optionValues: [\\{name: 'Small'\\}, \\{name: 'Medium'\\}, \\{name: 'Large'\\}]\n * \\}\n * ]\n * Would return\n * [\n * \\{Red: 0, Blue: 1\\},\n * \\{Small: 0, Medium: 1, Large: 2\\}\n * ]\n */\nfunction mapProductOptions(options: ProductOption[]): ProductOptionsMapping[] {\n return options.map((option: ProductOption) => {\n return Object.assign(\n {},\n ...(option?.optionValues\n ? option.optionValues.map((value, index) => {\n return {[value.name]: index};\n })\n : []),\n ) as ProductOptionsMapping;\n });\n}\n\n/**\n * Converts the product option into an Object\\<key, value\\> for building query params\n * For example, a selected product option of\n * [\n * \\{\n * name: 'Color',\n * value: 'Red',\n * \\},\n * \\{\n * name: 'Size',\n * value: 'Medium',\n * \\}\n * ]\n * Would return\n * \\{\n * Color: 'Red',\n * Size: 'Medium',\n * \\}\n */\nexport function mapSelectedProductOptionToObject(\n options: Pick<SelectedOption, 'name' | 'value'>[],\n): Record<string, string> {\n return Object.assign(\n {},\n ...options.map((key) => {\n return {[key.name]: key.value};\n }),\n ) as Record<string, string>;\n}\n\n/**\n * Returns the JSON stringify result of mapSelectedProductOptionToObject\n */\nfunction mapSelectedProductOptionToObjectAsString(\n options: Pick<SelectedOption, 'name' | 'value'>[],\n): string {\n return JSON.stringify(mapSelectedProductOptionToObject(options));\n}\n\n/**\n * Encode the selected product option as a key for mapping to the encoded variants\n * For example, a selected product option of\n * [\n * \\{\n * name: 'Color',\n * value: 'Red',\n * \\},\n * \\{\n * name: 'Size',\n * value: 'Medium',\n * \\}\n * ]\n * Would return\n * [0,1]\n *\n * Also works with the result of mapSelectedProductOption. For example:\n * \\{\n * Color: 'Red',\n * Size: 'Medium',\n * \\}\n * Would return\n * [0,1]\n *\n * @param selectedOption - The selected product option\n * @param productOptionMappings - The result of product option mapping from mapProductOptions\n * @returns\n */\nfunction encodeSelectedProductOptionAsKey(\n selectedOption:\n | Pick<SelectedOption, 'name' | 'value'>[]\n | Record<string, string>,\n productOptionMappings: ProductOptionsMapping[],\n): string {\n if (Array.isArray(selectedOption)) {\n return JSON.stringify(\n selectedOption.map((key, index) => {\n return productOptionMappings[index][key.value];\n }),\n );\n } else {\n return JSON.stringify(\n Object.keys(selectedOption).map((key, index) => {\n return productOptionMappings[index][selectedOption[key]];\n }),\n );\n }\n}\n\n/**\n * Takes an array of product variants and maps them to an object with the encoded selected option values as the key.\n * For example, a product variant of\n * [\n * \\{\n * id: 1,\n * selectedOptions: [\n * \\{name: 'Color', value: 'Red'\\},\n * \\{name: 'Size', value: 'Small'\\},\n * ],\n * \\},\n * \\{\n * id: 2,\n * selectedOptions: [\n * \\{name: 'Color', value: 'Red'\\},\n * \\{name: 'Size', value: 'Medium'\\},\n * ],\n * \\}\n * ]\n * Would return\n * \\{\n * '[0,0]': \\{id: 1, selectedOptions: [\\{name: 'Color', value: 'Red'\\}, \\{name: 'Size', value: 'Small'\\}]\\},\n * '[0,1]': \\{id: 2, selectedOptions: [\\{name: 'Color', value: 'Red'\\}, \\{name: 'Size', value: 'Medium'\\}]\\},\n * \\}\n */\nfunction mapVariants(\n variants: ProductVariant[],\n productOptionMappings: ProductOptionsMapping[],\n): Record<string, ProductVariant> {\n return Object.assign(\n {},\n ...variants.map((variant) => {\n const variantKey = encodeSelectedProductOptionAsKey(\n variant.selectedOptions || [],\n productOptionMappings,\n );\n return {[variantKey]: variant};\n }),\n ) as Record<string, ProductVariant>;\n}\n\nexport type MappedProductOptions = Omit<ProductOption, 'optionValues'> & {\n optionValues: MappedProductOptionValue[];\n};\n\nconst PRODUCT_INPUTS = [\n 'options',\n 'selectedOrFirstAvailableVariant',\n 'adjacentVariants',\n];\n\nconst PRODUCT_INPUTS_EXTRA = [\n 'handle',\n 'encodedVariantExistence',\n 'encodedVariantAvailability',\n];\n\nfunction logErrorAndReturnFalse(key: string): boolean {\n console.error(\n `[h2:error:getProductOptions] product.${key} is missing. Make sure you query for this field from the Storefront API.`,\n );\n return false;\n}\n\nexport function checkProductParam(\n product: RecursivePartial<Product>,\n checkAll = false,\n): Product {\n let validParam = true;\n const productKeys = Object.keys(product);\n\n // Check product input\n (checkAll\n ? [...PRODUCT_INPUTS, ...PRODUCT_INPUTS_EXTRA]\n : PRODUCT_INPUTS\n ).forEach((key) => {\n if (!productKeys.includes(key)) {\n validParam = logErrorAndReturnFalse(key);\n }\n });\n\n // Check for nested options requirements\n if (product.options) {\n const firstOption = product?.options[0];\n\n if (checkAll && !firstOption?.name) {\n validParam = logErrorAndReturnFalse('options.name');\n }\n\n // Check for options.optionValues\n if (product?.options[0]?.optionValues) {\n let firstOptionValues = product.options[0].optionValues[0];\n\n // Check for options.optionValues.name\n if (checkAll && !firstOptionValues?.name) {\n validParam = logErrorAndReturnFalse('options.optionValues.name');\n }\n\n // It is possible for firstSelectableVariant to be null\n firstOptionValues = product.options[0].optionValues.filter(\n (value) => !!value?.firstSelectableVariant,\n )[0];\n\n // Check for options.optionValues.firstSelectableVariant\n if (firstOptionValues?.firstSelectableVariant) {\n // check product variant\n validParam = checkProductVariantParam(\n firstOptionValues.firstSelectableVariant,\n 'options.optionValues.firstSelectableVariant',\n validParam,\n checkAll,\n );\n }\n } else {\n validParam = logErrorAndReturnFalse('options.optionValues');\n }\n }\n\n // Check for nested selectedOrFirstAvailableVariant requirements\n if (product.selectedOrFirstAvailableVariant) {\n validParam = checkProductVariantParam(\n product.selectedOrFirstAvailableVariant,\n 'selectedOrFirstAvailableVariant',\n validParam,\n checkAll,\n );\n }\n\n // Check for nested adjacentVariants requirements\n if (!!product.adjacentVariants && product.adjacentVariants[0]) {\n validParam = checkProductVariantParam(\n product.adjacentVariants[0],\n 'adjacentVariants',\n validParam,\n checkAll,\n );\n }\n\n return (validParam ? product : {}) as Product;\n}\n\nfunction checkProductVariantParam(\n variant: RecursivePartial<ProductVariant>,\n key: string,\n currentValidParamState: boolean,\n checkAll: boolean,\n): boolean {\n let validParam = currentValidParamState;\n\n if (checkAll && !variant.product?.handle) {\n validParam = logErrorAndReturnFalse(`${key}.product.handle`);\n }\n if (variant.selectedOptions) {\n const firstSelectedOption = variant.selectedOptions[0];\n if (!firstSelectedOption?.name) {\n validParam = logErrorAndReturnFalse(`${key}.selectedOptions.name`);\n }\n if (!firstSelectedOption?.value) {\n validParam = logErrorAndReturnFalse(`${key}.selectedOptions.value`);\n }\n } else {\n validParam = logErrorAndReturnFalse(`${key}.selectedOptions`);\n }\n\n return validParam;\n}\n\n/**\n * Finds all the variants provided by adjacentVariants, options.optionValues.firstAvailableVariant,\n * and selectedOrFirstAvailableVariant and return them in a single array\n */\nexport function getAdjacentAndFirstAvailableVariants(\n product: RecursivePartial<Product>,\n): ProductVariant[] {\n // Checks for valid product input\n const checkedProduct = checkProductParam(product);\n\n if (!checkedProduct.options) return [];\n\n const availableVariants: Record<string, ProductVariant> = {};\n checkedProduct.options.map((option) => {\n option.optionValues?.map((value) => {\n if (value.firstSelectableVariant) {\n const variantKey = mapSelectedProductOptionToObjectAsString(\n value.firstSelectableVariant.selectedOptions,\n );\n availableVariants[variantKey] = value.firstSelectableVariant;\n }\n });\n });\n\n checkedProduct.adjacentVariants.map((variant) => {\n const variantKey = mapSelectedProductOptionToObjectAsString(\n variant.selectedOptions,\n );\n availableVariants[variantKey] = variant;\n });\n\n const selectedVariant = checkedProduct.selectedOrFirstAvailableVariant;\n if (selectedVariant) {\n const variantKey = mapSelectedProductOptionToObjectAsString(\n selectedVariant.selectedOptions,\n );\n availableVariants[variantKey] = selectedVariant;\n }\n\n return Object.values(availableVariants);\n}\n\n/**\n * Returns a product options array with its relevant information\n * about the variant\n */\nexport function getProductOptions(\n product: RecursivePartial<Product>,\n): MappedProductOptions[] {\n // Checks for valid product input\n const checkedProduct = checkProductParam(product, true);\n\n if (!checkedProduct.options) return [];\n\n const {\n options,\n selectedOrFirstAvailableVariant: selectedVariant,\n adjacentVariants,\n encodedVariantExistence,\n encodedVariantAvailability,\n handle: productHandle,\n } = checkedProduct;\n // Get a mapping of product option names to their index for matching encoded values\n const productOptionMappings = mapProductOptions(options);\n\n // Get the adjacent variants mapped to the encoded selected option values\n const variants = mapVariants(\n selectedVariant ? [selectedVariant, ...adjacentVariants] : adjacentVariants,\n productOptionMappings,\n );\n\n // Get the key:value version of selected options for building url query params\n const selectedOptions = mapSelectedProductOptionToObject(\n selectedVariant ? selectedVariant.selectedOptions : [],\n );\n\n const productOptions = options.map((option, optionIndex) => {\n return {\n ...option,\n optionValues: option.optionValues.map((value) => {\n const targetOptionParams = {...selectedOptions}; // Clones the selected options\n\n // Modify the selected option value to the current option value\n targetOptionParams[option.name] = value.name;\n\n // Encode the new selected option values as a key for mapping to the product variants\n const targetKey = encodeSelectedProductOptionAsKey(\n targetOptionParams || [],\n productOptionMappings,\n );\n\n // Top-down option check for existence and availability\n const topDownKey = (JSON.parse(targetKey) as number[]).slice(\n 0,\n optionIndex + 1,\n );\n const exists = isOptionValueCombinationInEncodedVariant(\n topDownKey,\n encodedVariantExistence || '',\n );\n const available = isOptionValueCombinationInEncodedVariant(\n topDownKey,\n encodedVariantAvailability || '',\n );\n\n // Get the variant for the current option value if exists, else use the first selectable variant\n const variant: ProductVariant =\n variants[targetKey] || value.firstSelectableVariant;\n\n // Build the query params for this option value\n let variantOptionParam = {};\n if (variant) {\n variantOptionParam = mapSelectedProductOptionToObject(\n variant.selectedOptions || [],\n );\n }\n const searchParams = new URLSearchParams(variantOptionParam);\n const handle = variant?.product?.handle || productHandle;\n\n return {\n ...value,\n variant,\n handle,\n variantUriQuery: searchParams.toString(),\n selected: selectedOptions[option.name] === value.name,\n exists,\n available,\n isDifferentProduct: handle !== productHandle,\n };\n }),\n };\n });\n\n return productOptions;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -72,14 +72,16 @@ function v1Decoder(encodedVariantField) {
|
|
|
72
72
|
}
|
|
73
73
|
index = tokenizer.lastIndex;
|
|
74
74
|
}
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
77
|
-
const finalValueIndex = parseInt(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
const encodingEndsWithIndex = encodedVariantField.match(/\d+$/g);
|
|
76
|
+
if (encodingEndsWithIndex) {
|
|
77
|
+
const finalValueIndex = parseInt(encodingEndsWithIndex[0]);
|
|
78
|
+
if (rangeStart != null) {
|
|
79
|
+
for (; rangeStart <= finalValueIndex; rangeStart++) {
|
|
80
|
+
currentOptionValue[depth] = rangeStart;
|
|
81
|
+
options.push([...currentOptionValue]);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
options.push([finalValueIndex]);
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
return options;
|