{"version":3,"file":"useMoney.mjs","names":[],"sources":["../../src/useMoney.tsx"],"sourcesContent":["import {useMemo} from 'react';\nimport {useShop} from './ShopifyProvider.js';\nimport {\n  CurrencyCode as StorefrontApiCurrencyCode,\n  MoneyV2 as StorefrontApiMoneyV2,\n} from './storefront-api-types.js';\nimport type {\n  MoneyV2 as CustomerAccountApiMoneyV2,\n  CurrencyCode as CustomerAccountApiCurrencyCode,\n} from './customer-account-api-types.js';\n\n// Support MoneyV2 from both Storefront API and Customer Account API\n// The APIs may have different CurrencyCode enums\n/**\n * Supports MoneyV2 from both Storefront API and Customer Account API.\n * The APIs may have different CurrencyCode enums (e.g., Customer Account API added USDC in 2025-10, but Storefront API doesn't support USDC in 2025-10).\n * This union type ensures useMoney works with data from either API.\n */\ntype MoneyV2 = StorefrontApiMoneyV2 | CustomerAccountApiMoneyV2;\n\n/**\n * Supports CurrencyCode from both Storefront API and Customer Account API. The APIs may have different CurrencyCode enums (e.g., Customer Account API added USDC in 2025-10, but Storefront API doesn't support USDC in 2025-10).\n * This union type ensures useMoney works with data from either API.\n */\ntype CurrencyCode = StorefrontApiCurrencyCode | CustomerAccountApiCurrencyCode;\n\nexport type UseMoneyValue = {\n  /**\n   * The currency code from the `MoneyV2` object.\n   */\n  currencyCode: CurrencyCode;\n  /**\n   * The name for the currency code, returned by `Intl.NumberFormat`.\n   */\n  currencyName?: string;\n  /**\n   * The currency symbol returned by `Intl.NumberFormat`.\n   */\n  currencySymbol?: string;\n  /**\n   * The currency narrow symbol returned by `Intl.NumberFormat`.\n   */\n  currencyNarrowSymbol?: string;\n  /**\n   * The localized amount, without any currency symbols or non-number types from the `Intl.NumberFormat.formatToParts` parts.\n   */\n  amount: string;\n  /**\n   * All parts returned by `Intl.NumberFormat.formatToParts`.\n   */\n  parts: Intl.NumberFormatPart[];\n  /**\n   * A string returned by `new Intl.NumberFormat` for the amount and currency code,\n   * using the `locale` value in the [`LocalizationProvider` component](https://shopify.dev/api/hydrogen/components/localization/localizationprovider).\n   */\n  localizedString: string;\n  /**\n   * The `MoneyV2` object provided as an argument to the hook.\n   */\n  original: MoneyV2;\n  /**\n   * A string with trailing zeros removed from the fractional part, if any exist. If there are no trailing zeros, then the fractional part remains.\n   * For example, `$640.00` turns into `$640`.\n   * `$640.42` remains `$640.42`.\n   */\n  withoutTrailingZeros: string;\n  /**\n   * A string without currency and without trailing zeros removed from the fractional part, if any exist. If there are no trailing zeros, then the fractional part remains.\n   * For example, `$640.00` turns into `640`.\n   * `$640.42` turns into `640.42`.\n   */\n  withoutTrailingZerosAndCurrency: string;\n};\n\n/**\n * The `useMoney` hook takes a [MoneyV2 object from the Storefront API](https://shopify.dev/docs/api/storefront/2026-04/objects/MoneyV2)\n * or a [MoneyV2 object from the Customer Account API](https://shopify.dev/docs/api/customer/2026-04/objects/moneyv2) and returns a\n * default-formatted string of the amount with the correct currency indicator, along with some of the parts provided by\n * [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).\n * Uses `locale` from `ShopifyProvider`\n * &nbsp;\n * @see {@link https://shopify.dev/api/hydrogen/hooks/usemoney}\n * @example initialize the money object\n * ```ts\n * const money = useMoney({\n *   amount: '100.00',\n *   currencyCode: 'USD'\n * })\n * ```\n * &nbsp;\n *\n * @example basic usage, outputs: $100.00\n * ```ts\n * money.localizedString\n * ```\n * &nbsp;\n *\n * @example without currency, outputs: 100.00\n * ```ts\n * money.amount\n * ```\n * &nbsp;\n *\n * @example without trailing zeros, outputs: $100\n * ```ts\n * money.withoutTrailingZeros\n * ```\n * &nbsp;\n *\n * @example currency name, outputs: US dollars\n * ```ts\n * money.currencyCode\n * ```\n * &nbsp;\n *\n * @example currency symbol, outputs: $\n * ```ts\n * money.currencySymbol\n * ```\n * &nbsp;\n *\n * @example without currency and without trailing zeros, outputs: 100\n * ```ts\n * money.withoutTrailingZerosAndCurrency\n * ```\n * @publicDocs\n */\nexport function useMoney(money: MoneyV2): UseMoneyValue {\n  const {countryIsoCode, languageIsoCode} = useShop();\n  const locale = languageIsoCode.includes('_')\n    ? languageIsoCode.replace('_', '-')\n    : `${languageIsoCode}-${countryIsoCode}`;\n\n  if (!locale) {\n    throw new Error(\n      `useMoney(): Unable to get 'locale' from 'useShop()', which means that 'locale' was not passed to '<ShopifyProvider/>'. 'locale' is required for 'useMoney()' to work`,\n    );\n  }\n\n  const amount = parseFloat(money.amount);\n\n  // Check if the currency code is supported by Intl.NumberFormat\n  let isCurrencySupported = true;\n  try {\n    new Intl.NumberFormat(locale, {\n      style: 'currency',\n      currency: money.currencyCode,\n    });\n  } catch (e) {\n    if (e instanceof RangeError && e.message.includes('currency')) {\n      isCurrencySupported = false;\n    }\n  }\n\n  const {\n    defaultFormatter,\n    nameFormatter,\n    narrowSymbolFormatter,\n    withoutTrailingZerosFormatter,\n    withoutCurrencyFormatter,\n    withoutTrailingZerosOrCurrencyFormatter,\n  } = useMemo(() => {\n    // For unsupported currencies (like USDC cryptocurrency), use decimal formatting with 2 decimal places\n    // We default to 2 decimal places based on research showing USDC displays like USD to reinforce its 1:1 peg\n    const options = isCurrencySupported\n      ? {\n          style: 'currency' as const,\n          currency: money.currencyCode,\n        }\n      : {\n          style: 'decimal' as const,\n          minimumFractionDigits: 2,\n          maximumFractionDigits: 2,\n        };\n\n    return {\n      defaultFormatter: getLazyFormatter(locale, options),\n      nameFormatter: getLazyFormatter(locale, {\n        ...options,\n        currencyDisplay: 'name',\n      }),\n      narrowSymbolFormatter: getLazyFormatter(locale, {\n        ...options,\n        currencyDisplay: 'narrowSymbol',\n      }),\n      withoutTrailingZerosFormatter: getLazyFormatter(locale, {\n        ...options,\n        minimumFractionDigits: 0,\n        maximumFractionDigits: 0,\n      }),\n      withoutCurrencyFormatter: getLazyFormatter(locale, {\n        minimumFractionDigits: 2,\n        maximumFractionDigits: 2,\n      }),\n      withoutTrailingZerosOrCurrencyFormatter: getLazyFormatter(locale, {\n        minimumFractionDigits: 0,\n        maximumFractionDigits: 0,\n      }),\n    };\n  }, [money.currencyCode, locale, isCurrencySupported]);\n\n  const isPartCurrency = (part: Intl.NumberFormatPart): boolean =>\n    part.type === 'currency';\n\n  // By wrapping these properties in functions, we only\n  // create formatters if they are going to be used.\n  const lazyFormatters = useMemo(\n    () => ({\n      original: (): MoneyV2 => money,\n      currencyCode: (): CurrencyCode => money.currencyCode,\n\n      localizedString: (): string => {\n        const formatted = defaultFormatter().format(amount);\n        // For unsupported currencies, append the currency code\n        return isCurrencySupported\n          ? formatted\n          : `${formatted} ${money.currencyCode}`;\n      },\n\n      parts: (): Intl.NumberFormatPart[] => {\n        const parts = defaultFormatter().formatToParts(amount);\n        // For unsupported currencies, add currency code as a currency part\n        if (!isCurrencySupported) {\n          parts.push(\n            {type: 'literal', value: ' '},\n            {type: 'currency', value: money.currencyCode},\n          );\n        }\n        return parts;\n      },\n\n      withoutTrailingZeros: (): string => {\n        const formatted =\n          amount % 1 === 0\n            ? withoutTrailingZerosFormatter().format(amount)\n            : defaultFormatter().format(amount);\n        // For unsupported currencies, append the currency code\n        return isCurrencySupported\n          ? formatted\n          : `${formatted} ${money.currencyCode}`;\n      },\n\n      withoutTrailingZerosAndCurrency: (): string =>\n        amount % 1 === 0\n          ? withoutTrailingZerosOrCurrencyFormatter().format(amount)\n          : withoutCurrencyFormatter().format(amount),\n\n      currencyName: (): string =>\n        nameFormatter().formatToParts(amount).find(isPartCurrency)?.value ??\n        money.currencyCode, // e.g. \"US dollars\"\n\n      currencySymbol: (): string =>\n        defaultFormatter().formatToParts(amount).find(isPartCurrency)?.value ??\n        money.currencyCode, // e.g. \"USD\"\n\n      currencyNarrowSymbol: (): string =>\n        narrowSymbolFormatter().formatToParts(amount).find(isPartCurrency)\n          ?.value ?? '', // e.g. \"$\"\n\n      amount: (): string =>\n        defaultFormatter()\n          .formatToParts(amount)\n          .filter((part) =>\n            ['decimal', 'fraction', 'group', 'integer', 'literal'].includes(\n              part.type,\n            ),\n          )\n          .map((part) => part.value)\n          .join(''),\n    }),\n    [\n      money,\n      amount,\n      isCurrencySupported,\n      nameFormatter,\n      defaultFormatter,\n      narrowSymbolFormatter,\n      withoutCurrencyFormatter,\n      withoutTrailingZerosFormatter,\n      withoutTrailingZerosOrCurrencyFormatter,\n    ],\n  );\n\n  // Call functions automatically when the properties are accessed\n  // to keep these functions as an implementation detail.\n  return useMemo(\n    () =>\n      new Proxy(lazyFormatters as unknown as UseMoneyValue, {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call\n        get: (target, key) => Reflect.get(target, key)?.call(null),\n      }),\n    [lazyFormatters],\n  );\n}\n\nconst formatterCache = new Map<string, Intl.NumberFormat>();\n\nfunction getLazyFormatter(\n  locale: string,\n  options?: Intl.NumberFormatOptions,\n): () => Intl.NumberFormat {\n  const key = JSON.stringify([locale, options]);\n\n  return function (): Intl.NumberFormat {\n    let formatter = formatterCache.get(key);\n    if (!formatter) {\n      try {\n        formatter = new Intl.NumberFormat(locale, options);\n      } catch (error) {\n        // Handle unsupported currency codes (e.g., USDC from Customer Account API)\n        // Fall back to formatting without currency\n        if (error instanceof RangeError && error.message.includes('currency')) {\n          const fallbackOptions = {...options};\n          delete fallbackOptions.currency;\n          delete fallbackOptions.currencyDisplay;\n          delete fallbackOptions.currencySign;\n          formatter = new Intl.NumberFormat(locale, fallbackOptions);\n        } else {\n          throw error;\n        }\n      }\n      formatterCache.set(key, formatter);\n    }\n    return formatter;\n  };\n}\n"],"mappings":"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"}