@lime-bundles/react 2.0.0 → 2.1.1
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/dist/index.cjs +38 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +42 -23
- package/dist/index.js.map +1 -1
- package/docs/web-component.md +19 -0
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -281,33 +281,42 @@ function FixedBundle(props) {
|
|
|
281
281
|
}, [result, onError]);
|
|
282
282
|
const handleAddToCart = (0, import_react3.useCallback)(async () => {
|
|
283
283
|
if (!bundle) return;
|
|
284
|
-
const
|
|
284
|
+
const bundleLines = bundle.products.filter((p) => p.variants.nodes.some((v) => v.availableForSale)).map((p) => {
|
|
285
285
|
const variant = p.variants.nodes.find((v) => v.availableForSale);
|
|
286
286
|
return {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
{ key: "_lime_bundle_gid", value: bundle.id },
|
|
291
|
-
{ key: "_lime_bundle_type", value: bundle.bundleType }
|
|
292
|
-
]
|
|
287
|
+
productId: p.id,
|
|
288
|
+
variant,
|
|
289
|
+
quantity: (0, import_core5.resolveBundleQty)(bundle, p.id, variant.id)
|
|
293
290
|
};
|
|
294
|
-
});
|
|
295
|
-
if (
|
|
291
|
+
}).filter((line) => line.quantity > 0);
|
|
292
|
+
if (bundleLines.length === 0) return;
|
|
293
|
+
const lines = bundleLines.map((line) => ({
|
|
294
|
+
merchandiseId: line.variant.id,
|
|
295
|
+
quantity: line.quantity,
|
|
296
|
+
attributes: [
|
|
297
|
+
{ key: "_lime_bundle_gid", value: bundle.id },
|
|
298
|
+
{ key: "_lime_bundle_type", value: bundle.bundleType }
|
|
299
|
+
]
|
|
300
|
+
}));
|
|
296
301
|
setAddingToCart(true);
|
|
297
302
|
setCartError(null);
|
|
298
303
|
try {
|
|
299
304
|
await onAddToCart(lines);
|
|
300
|
-
const totalPrice =
|
|
301
|
-
const price = parseFloat(
|
|
302
|
-
|
|
305
|
+
const totalPrice = bundleLines.reduce((sum, line) => {
|
|
306
|
+
const price = parseFloat(line.variant.price.amount);
|
|
307
|
+
const discounted = (0, import_core5.calculateDiscount)(
|
|
303
308
|
price,
|
|
304
309
|
bundle.discountConfig.discountType,
|
|
305
310
|
bundle.discountConfig.discountValue
|
|
306
311
|
);
|
|
312
|
+
return sum + discounted * line.quantity;
|
|
307
313
|
}, 0);
|
|
314
|
+
const totalQuantity = bundleLines.reduce(
|
|
315
|
+
(sum, line) => sum + line.quantity,
|
|
316
|
+
0
|
|
317
|
+
);
|
|
308
318
|
trackAddToCart({
|
|
309
|
-
|
|
310
|
-
quantity: 1,
|
|
319
|
+
quantity: totalQuantity,
|
|
311
320
|
totalPrice: Math.round(totalPrice * 100) / 100
|
|
312
321
|
});
|
|
313
322
|
} catch (err) {
|
|
@@ -427,12 +436,17 @@ function MixMatchBundle(props) {
|
|
|
427
436
|
if (next.has(key)) {
|
|
428
437
|
next.delete(key);
|
|
429
438
|
} else {
|
|
430
|
-
|
|
439
|
+
const configuredQty = bundle ? (0, import_core6.resolveBundleQty)(bundle, productId, variant.id) : 1;
|
|
440
|
+
next.set(key, {
|
|
441
|
+
productId,
|
|
442
|
+
variantId: variant.id,
|
|
443
|
+
quantity: configuredQty
|
|
444
|
+
});
|
|
431
445
|
}
|
|
432
446
|
return next;
|
|
433
447
|
});
|
|
434
448
|
},
|
|
435
|
-
[]
|
|
449
|
+
[bundle]
|
|
436
450
|
);
|
|
437
451
|
const updateQuantity = (0, import_react4.useCallback)(
|
|
438
452
|
(productId, variantId, quantity) => {
|
|
@@ -473,7 +487,6 @@ function MixMatchBundle(props) {
|
|
|
473
487
|
return sum + parseFloat(variant?.price.amount ?? "0") * line.quantity;
|
|
474
488
|
}, 0);
|
|
475
489
|
trackAddToCart({
|
|
476
|
-
productId: bundle.products[0]?.id ?? "",
|
|
477
490
|
quantity: totalQuantity,
|
|
478
491
|
totalPrice: Math.round(totalPrice * 100) / 100
|
|
479
492
|
});
|
|
@@ -641,7 +654,12 @@ function VolumeBundle(props) {
|
|
|
641
654
|
const product = bundle?.products[0];
|
|
642
655
|
const basePrice = product ? parseFloat(product.priceRange.minVariantPrice.amount) : 0;
|
|
643
656
|
const currency = product?.priceRange.minVariantPrice.currencyCode ?? "USD";
|
|
644
|
-
const tierSavings = bundle ? (0, import_core7.calculateTierSavings)(
|
|
657
|
+
const tierSavings = bundle ? (0, import_core7.calculateTierSavings)(
|
|
658
|
+
bundle.volumeTiers,
|
|
659
|
+
basePrice,
|
|
660
|
+
quantity,
|
|
661
|
+
bundle.discountConfig.discountType
|
|
662
|
+
) : [];
|
|
645
663
|
const activeTier = bundle ? (0, import_core7.getActiveTier)(bundle.volumeTiers, quantity) : null;
|
|
646
664
|
const handleAddToCart = (0, import_react5.useCallback)(async () => {
|
|
647
665
|
if (!bundle || !product) return;
|
|
@@ -661,7 +679,7 @@ function VolumeBundle(props) {
|
|
|
661
679
|
setCartError(null);
|
|
662
680
|
try {
|
|
663
681
|
await onAddToCart(lines);
|
|
664
|
-
const unitPrice =
|
|
682
|
+
const unitPrice = tierSavings.find((ts) => ts.tier === activeTier)?.unitPrice ?? basePrice;
|
|
665
683
|
trackAddToCart({
|
|
666
684
|
productId: product.id,
|
|
667
685
|
quantity,
|
|
@@ -744,8 +762,7 @@ function VolumeBundle(props) {
|
|
|
744
762
|
"Save ",
|
|
745
763
|
ts.savingsPercent.toFixed(0),
|
|
746
764
|
"%"
|
|
747
|
-
] })
|
|
748
|
-
ts.tier.label && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "lb-bundle__tier-label", role: "cell", children: ts.tier.label })
|
|
765
|
+
] })
|
|
749
766
|
]
|
|
750
767
|
},
|
|
751
768
|
ts.tier.minQuantity
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/FixedBundle.tsx","../src/hooks/useBundleData.ts","../src/fetchBundleData.ts","../src/fetchShopCustomCss.ts","../src/hooks/useAnalytics.ts","../src/components/MixMatchBundle.tsx","../src/components/VolumeBundle.tsx","../src/hooks/useBundlesForProduct.ts"],"sourcesContent":["// Bundle components — BYO-cart via `onAddToCart`.\nexport { FixedBundle } from \"./components/FixedBundle\";\nexport { MixMatchBundle } from \"./components/MixMatchBundle\";\nexport { VolumeBundle } from \"./components/VolumeBundle\";\nexport type { BundleComponentProps } from \"./components/types\";\n\n// Data fetching\nexport {\n fetchBundleData,\n type FetchBundleDataOptions,\n} from \"./fetchBundleData\";\nexport {\n fetchShopCustomCss,\n type FetchShopCustomCssOptions,\n} from \"./fetchShopCustomCss\";\nexport {\n useBundleData,\n type UseBundleDataOptions,\n type UseBundleDataResult,\n} from \"./hooks/useBundleData\";\nexport {\n useBundlesForProduct,\n type UseBundlesForProductOptions,\n type UseBundlesForProductResult,\n} from \"./hooks/useBundlesForProduct\";\nexport { useAnalytics } from \"./hooks/useAnalytics\";\n\n// Re-export core types consumers commonly need.\nexport type {\n ParsedBundle,\n FixedBundleData,\n VolumeBundleData,\n MixMatchBundleData,\n BundleType,\n Product,\n ProductVariant,\n CartLineInput,\n WidgetConfig,\n HeaderConfig,\n LayoutConfig,\n ProductListConfig,\n PricingConfig,\n SavingsBarConfig,\n CtaConfig,\n CountdownConfig,\n PopularBadgeConfig,\n VolumeTier,\n DiscountConfig,\n ABVariantOverrides,\n PricingRow,\n FixedBundlePricing,\n ImageTransform,\n} from \"@lime-bundles/core\";\nexport { BundleParseError, StorefrontApiError } from \"@lime-bundles/core\";\n\n// DIY widget primitives — every helper a developer needs to build their\n// own bundle UI with the same data, math, and theming the first-party\n// widget uses. All live in `@lime-bundles/core`; importing from the\n// React package is shorthand for consumers who already pulled `react`.\nexport {\n // Bundle config + variant merging\n WIDGET_CONFIG_DEFAULTS,\n mergeWidgetConfig,\n applyWidgetConfigVars,\n applyABVariantB,\n // Pricing (integer cents, matches Shopify Discount Function rounding)\n parseCents,\n formatCents,\n percentageDiscountUnit,\n computeFixedPricing,\n computeBundleSaleCents,\n // Volume-tier calculation\n calculateTierSavings,\n getActiveTier,\n // Mix-match quantity validation\n validateQuantity,\n // Image / countdown / money helpers\n transformImageUrl,\n formatCountdown,\n formatMoney,\n calculateDiscount,\n // Analytics (optional — FixedBundle / VolumeBundle / MixMatchBundle\n // auto-fire these unless analyticsEnabled is set to false)\n reportImpression,\n reportAddToCart,\n observeImpression,\n // A/B testing\n getABTestAssignment,\n setConsent,\n hasConsent,\n // CSS injection + sanitizer\n injectCustomCss,\n sanitizeCustomCss,\n // Custom-storefront data fetching\n createStorefrontClient,\n fetchBundlesForProduct,\n parseMetaobjectBundle,\n parseMetaobjectBundleStrict,\n BUNDLE_METAOBJECT_QUERY,\n BUNDLES_FOR_PRODUCT_QUERY,\n SHOP_CUSTOM_CSS_QUERY,\n} from \"@lime-bundles/core\";\n","/**\n * Fixed bundle component — pre-selected products with a shared discount.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n calculateDiscount,\n type CartLineInput,\n type FixedBundleData,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\nexport function FixedBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"fixed\",\n enabled: analyticsEnabled !== false,\n });\n\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"fixed\"\n ? (result.bundle as FixedBundleData)\n : null;\n\n // Surface load/narrowing errors via onError — in a useEffect so we don't\n // fire side effects during render.\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"fixed\"\n ) {\n onError?.(\n new Error(\n `FixedBundle: expected bundleType=\"fixed\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle) return;\n\n const lines: CartLineInput[] = bundle.products\n .filter((p) => p.variants.nodes.some((v) => v.availableForSale))\n .map((p) => {\n const variant = p.variants.nodes.find((v) => v.availableForSale)!;\n return {\n merchandiseId: variant.id,\n quantity: 1,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n };\n });\n\n if (lines.length === 0) return;\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n const totalPrice = bundle.products.reduce((sum, p) => {\n const price = parseFloat(p.priceRange.minVariantPrice.amount);\n return (\n sum +\n calculateDiscount(\n price,\n bundle.discountConfig.discountType,\n bundle.discountConfig.discountValue,\n )\n );\n }, 0);\n trackAddToCart({\n productId: bundle.products[0]?.id ?? \"\",\n quantity: 1,\n totalPrice: Math.round(totalPrice * 100) / 100,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [bundle, onAddToCart, onError, trackAddToCart]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--products\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n // Bundle loaded but wasn't a \"fixed\" variant — render nothing; the\n // useEffect above has already invoked onError with a descriptive message.\n if (!bundle) return null;\n\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--fixed ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n {bundle.discountLabel && (\n <span className=\"lb-bundle__discount-badge\">{bundle.discountLabel}</span>\n )}\n\n <div className=\"lb-bundle__products\">\n {bundle.products.map((product) => (\n <div key={product.id} className=\"lb-bundle__product\" part=\"product\">\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(product.priceRange.minVariantPrice.amount, currency)}\n </p>\n </div>\n </div>\n ))}\n </div>\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? \"Add Bundle to Cart\")}\n </button>\n </div>\n );\n}\n","/**\n * Client-side React hook wrapping `fetchBundleData` with loading / error\n * state + abort-on-unmount.\n *\n * For SSR / RSC contexts (Hydrogen loaders, Next.js server components),\n * prefer `fetchBundleData` directly and pass the result to a client\n * component as a prop — the hook's only value-add over the async\n * function is React state management.\n *\n * `UseBundleDataResult` is a discriminated union locked at v1.0.0:\n * narrowing via `status` is safe. Widening or adding states is a\n * breaking change.\n */\nimport { useState, useEffect } from \"react\";\nimport { injectCustomCss, type ParsedBundle } from \"@lime-bundles/core\";\nimport { fetchBundleData } from \"../fetchBundleData\";\nimport { fetchShopCustomCss } from \"../fetchShopCustomCss\";\n\nexport interface UseBundleDataOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n bundleGid: string;\n}\n\nexport type UseBundleDataResult =\n | { status: \"loading\"; bundle: null; error: null }\n | { status: \"success\"; bundle: ParsedBundle; error: null }\n | { status: \"error\"; bundle: null; error: Error };\n\nconst INITIAL_STATE: UseBundleDataResult = {\n status: \"loading\",\n bundle: null,\n error: null,\n};\n\nexport function useBundleData(\n options: UseBundleDataOptions,\n): UseBundleDataResult {\n const [state, setState] = useState<UseBundleDataResult>(INITIAL_STATE);\n\n useEffect(() => {\n const controller = new AbortController();\n setState(INITIAL_STATE);\n\n fetchBundleData({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n bundleGid: options.bundleGid,\n signal: controller.signal,\n })\n .then((bundle) => {\n if (controller.signal.aborted) return;\n setState({ status: \"success\", bundle, error: null });\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return;\n setState({\n status: \"error\",\n bundle: null,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n });\n\n // Fire-and-forget custom CSS fetch — best-effort, failures never block\n // bundle rendering.\n fetchShopCustomCss({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n signal: controller.signal,\n })\n .then((css) => {\n if (controller.signal.aborted) return;\n injectCustomCss(options.shopDomain, css);\n })\n .catch(() => {\n // Silent — no merchant CSS is fine.\n });\n\n return () => {\n controller.abort();\n };\n }, [\n options.shopDomain,\n options.storefrontAccessToken,\n options.bundleGid,\n ]);\n\n return state;\n}\n","/**\n * Framework-agnostic async fetcher for bundle data.\n *\n * Primary path for Next.js App Router / RSC and Hydrogen loaders — call\n * this in a server-side context and pass the result as a prop to a\n * `\"use client\"` component. `useBundleData` is NOT needed in that\n * component because the data is already in hand.\n *\n * Always forward the end-buyer's IP via `buyerIp` when calling from SSR:\n * Shopify may return 430 Security Rejection for server-originated traffic\n * without it.\n *\n * Throws:\n * - `StorefrontApiError` on network / HTTP / GraphQL errors\n * - `BundleParseError` when the metaobject is missing, invalid type,\n * inactive, not yet started, or expired\n */\nimport {\n createStorefrontClient,\n BUNDLE_METAOBJECT_QUERY,\n parseMetaobjectBundleStrict,\n BundleParseError,\n type ParsedBundle,\n type BundleMetaobjectResponse,\n} from \"@lime-bundles/core\";\n\nexport interface FetchBundleDataOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n bundleGid: string;\n /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR. */\n buyerIp?: string;\n /** Abort the in-flight request (used by `useBundleData` to cancel on unmount). */\n signal?: AbortSignal;\n /** Override the Storefront API version pinned in `createStorefrontClient`. */\n apiVersion?: string;\n}\n\nexport async function fetchBundleData(\n options: FetchBundleDataOptions,\n): Promise<ParsedBundle> {\n const client = createStorefrontClient({\n shopDomain: options.shopDomain,\n accessToken: options.storefrontAccessToken,\n buyerIp: options.buyerIp,\n apiVersion: options.apiVersion,\n });\n\n const data = await client.query<BundleMetaobjectResponse>(\n BUNDLE_METAOBJECT_QUERY,\n { id: options.bundleGid },\n { signal: options.signal },\n );\n\n if (!data.metaobject) {\n throw new BundleParseError(\n `Bundle not found: ${options.bundleGid}`,\n \"not_found\",\n );\n }\n\n return parseMetaobjectBundleStrict(\n data.metaobject.id,\n data.metaobject.fields,\n );\n}\n","/**\n * Best-effort fetch for the shop's `$app:custom_css` metafield.\n *\n * Returns the raw CSS string (still needs sanitization before injection —\n * see `injectCustomCss` from core) or null if the metafield is unset or\n * the request fails.\n *\n * **Per-shop in-memory cache.** Multiple `<FixedBundle>` / `<VolumeBundle>`\n * / `<MixMatchBundle>` components on the same page share one network\n * request — the first caller fetches, concurrent callers await the\n * same promise, and resolved values are cached so subsequent mounts\n * return instantly. Failures are NOT cached so the next caller retries.\n *\n * The cache lives for the life of the JS module (typically one page\n * load). Merchants updating their CSS see changes on the next page\n * load. The cache is exposed as `__clearShopCssCache` for test use only.\n */\nimport {\n createStorefrontClient,\n SHOP_CUSTOM_CSS_QUERY,\n type ShopCustomCssResponse,\n} from \"@lime-bundles/core\";\n\nexport interface FetchShopCustomCssOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n buyerIp?: string;\n signal?: AbortSignal;\n}\n\n// Cache map: key = shopDomain, value = pending or resolved promise.\n// Failures delete their own entry so retries get a fresh fetch.\nconst cache = new Map<string, Promise<string | null>>();\n\nexport async function fetchShopCustomCss(\n options: FetchShopCustomCssOptions,\n): Promise<string | null> {\n const cached = cache.get(options.shopDomain);\n if (cached) return cached;\n\n const promise = (async () => {\n const client = createStorefrontClient({\n shopDomain: options.shopDomain,\n accessToken: options.storefrontAccessToken,\n buyerIp: options.buyerIp,\n });\n\n const data = await client.query<ShopCustomCssResponse>(\n SHOP_CUSTOM_CSS_QUERY,\n undefined,\n { signal: options.signal },\n );\n\n return data.shop?.metafield?.value ?? null;\n })();\n\n // Cache the promise synchronously so concurrent callers dedupe.\n cache.set(options.shopDomain, promise);\n\n // Evict on failure so the next caller can retry.\n promise.catch(() => {\n if (cache.get(options.shopDomain) === promise) {\n cache.delete(options.shopDomain);\n }\n });\n\n return promise;\n}\n\n/**\n * Test-only helper. Clears the per-shop CSS cache so each test starts\n * from a known state. Do not call from application code.\n */\nexport function __clearShopCssCache(): void {\n cache.clear();\n}\n","/**\n * Hook for analytics event reporting (impressions + add-to-cart).\n * Impressions fire once per mount using IntersectionObserver.\n */\nimport { useEffect, useMemo, useRef, useCallback, useState } from \"react\";\nimport {\n reportImpression,\n reportAddToCart,\n observeImpression,\n type AnalyticsConfig,\n} from \"@lime-bundles/core\";\n\nexport interface UseAnalyticsOptions {\n shopDomain: string;\n appUrl: string;\n bundleGid: string;\n bundleType: string;\n enabled?: boolean;\n abTestId?: string;\n abVariant?: string;\n}\n\nexport function useAnalytics(options: UseAnalyticsOptions) {\n const impressionFiredRef = useRef(false);\n // Callback ref + state so the observer effect re-runs when the host element\n // is attached (e.g. after a `loading` branch swaps in the real DOM node).\n const [element, setElement] = useState<HTMLElement | null>(null);\n const elementRef = useCallback((el: HTMLElement | null) => {\n setElement(el);\n }, []);\n\n const config: AnalyticsConfig = useMemo(\n () => ({ shopDomain: options.shopDomain, appUrl: options.appUrl }),\n [options.shopDomain, options.appUrl],\n );\n\n // Reset the fired flag when bundle identity changes so a reused component\n // (e.g. via navigation) can report a fresh impression.\n useEffect(() => {\n impressionFiredRef.current = false;\n }, [options.bundleGid, options.abVariant]);\n\n // Set up impression observer once the element is attached\n useEffect(() => {\n if (options.enabled === false) return;\n if (impressionFiredRef.current) return;\n if (!element) return;\n\n const cleanup = observeImpression(element, () => {\n if (impressionFiredRef.current) return;\n impressionFiredRef.current = true;\n\n reportImpression(config, {\n bundleGid: options.bundleGid,\n bundleType: options.bundleType,\n abTestId: options.abTestId,\n abVariant: options.abVariant,\n });\n });\n\n return cleanup;\n }, [\n element,\n config,\n options.enabled,\n options.bundleGid,\n options.bundleType,\n options.abTestId,\n options.abVariant,\n ]);\n\n const trackAddToCart = useCallback(\n (event: {\n productId: string;\n quantity: number;\n totalPrice: number;\n }) => {\n if (options.enabled === false) return;\n\n reportAddToCart(config, {\n bundleGid: options.bundleGid,\n bundleType: options.bundleType,\n productId: event.productId,\n quantity: event.quantity,\n totalPrice: event.totalPrice,\n abTestId: options.abTestId,\n abVariant: options.abVariant,\n });\n },\n [\n config,\n options.enabled,\n options.bundleGid,\n options.bundleType,\n options.abTestId,\n options.abVariant,\n ],\n );\n\n return { elementRef, trackAddToCart };\n}\n","/**\n * Mix-and-match bundle component — customer picks products from a pool.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n validateQuantity,\n type CartLineInput,\n type MixMatchBundleData,\n type ProductVariant,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\ninterface SelectedItem {\n productId: string;\n variantId: string;\n quantity: number;\n}\n\nexport function MixMatchBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"mix_match\",\n enabled: analyticsEnabled !== false,\n });\n\n const [selections, setSelections] = useState<Map<string, SelectedItem>>(\n new Map(),\n );\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"mix_match\"\n ? (result.bundle as MixMatchBundleData)\n : null;\n\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"mix_match\"\n ) {\n onError?.(\n new Error(\n `MixMatchBundle: expected bundleType=\"mix_match\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const totalQuantity = Array.from(selections.values()).reduce(\n (sum, s) => sum + s.quantity,\n 0,\n );\n\n const validation = bundle\n ? validateQuantity(totalQuantity, bundle.minQuantity, bundle.maxQuantity)\n : { valid: false, totalQuantity: 0, message: null };\n\n const toggleProduct = useCallback(\n (productId: string, variant: ProductVariant) => {\n setSelections((prev) => {\n const next = new Map(prev);\n const key = `${productId}:${variant.id}`;\n if (next.has(key)) {\n next.delete(key);\n } else {\n next.set(key, { productId, variantId: variant.id, quantity: 1 });\n }\n return next;\n });\n },\n [],\n );\n\n const updateQuantity = useCallback(\n (productId: string, variantId: string, quantity: number) => {\n setSelections((prev) => {\n const next = new Map(prev);\n const key = `${productId}:${variantId}`;\n if (quantity <= 0) {\n next.delete(key);\n } else {\n next.set(key, { productId, variantId, quantity });\n }\n return next;\n });\n },\n [],\n );\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle || !validation.valid) return;\n\n const lines: CartLineInput[] = Array.from(selections.values()).map((s) => ({\n merchandiseId: s.variantId,\n quantity: s.quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n }));\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n\n const totalPrice = lines.reduce((sum, line) => {\n const product = bundle.products.find((p) =>\n p.variants.nodes.some((v) => v.id === line.merchandiseId),\n );\n const variant = product?.variants.nodes.find(\n (v) => v.id === line.merchandiseId,\n );\n return (\n sum + parseFloat(variant?.price.amount ?? \"0\") * line.quantity\n );\n }, 0);\n\n trackAddToCart({\n productId: bundle.products[0]?.id ?? \"\",\n quantity: totalQuantity,\n totalPrice: Math.round(totalPrice * 100) / 100,\n });\n\n setSelections(new Map());\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [\n bundle,\n selections,\n validation.valid,\n onAddToCart,\n onError,\n trackAddToCart,\n totalQuantity,\n ]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--products\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n if (!bundle) return null;\n\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--mix-match ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n {bundle.discountLabel && (\n <span className=\"lb-bundle__discount-badge\">{bundle.discountLabel}</span>\n )}\n\n <p className=\"lb-bundle__instructions\">\n {bundle.minQuantity && bundle.maxQuantity\n ? `Select ${bundle.minQuantity}–${bundle.maxQuantity} items`\n : bundle.minQuantity\n ? `Select at least ${bundle.minQuantity} items`\n : bundle.maxQuantity\n ? `Select up to ${bundle.maxQuantity} items`\n : \"Select your items\"}\n </p>\n\n <div className=\"lb-bundle__products lb-bundle__products--selectable\">\n {bundle.products.map((product) => {\n const defaultVariant =\n product.variants.nodes.find((v) => v.availableForSale) ??\n product.variants.nodes[0];\n if (!defaultVariant) return null;\n\n const key = `${product.id}:${defaultVariant.id}`;\n const selected = selections.get(key);\n\n return (\n <div\n key={product.id}\n className={`lb-bundle__product lb-bundle__product--selectable ${selected ? \"lb-bundle__product--selected\" : \"\"}`}\n >\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(defaultVariant.price.amount, currency)}\n </p>\n </div>\n <div className=\"lb-bundle__product-actions\">\n {selected ? (\n <div className=\"lb-bundle__quantity-control\">\n <button\n aria-label={`Decrease ${product.title}`}\n onClick={() =>\n updateQuantity(\n product.id,\n defaultVariant.id,\n selected.quantity - 1,\n )\n }\n >\n −\n </button>\n <span>{selected.quantity}</span>\n <button\n aria-label={`Increase ${product.title}`}\n onClick={() =>\n updateQuantity(\n product.id,\n defaultVariant.id,\n selected.quantity + 1,\n )\n }\n >\n +\n </button>\n </div>\n ) : (\n <button\n className=\"lb-bundle__select-btn\"\n onClick={() => toggleProduct(product.id, defaultVariant)}\n disabled={!defaultVariant.availableForSale}\n >\n {defaultVariant.availableForSale ? \"Select\" : \"Sold out\"}\n </button>\n )}\n </div>\n </div>\n );\n })}\n </div>\n\n {validation.message && (\n <p className=\"lb-bundle__validation\" role=\"status\">\n {validation.message}\n </p>\n )}\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart || !validation.valid}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? `Add ${totalQuantity} Items to Cart`)}\n </button>\n </div>\n );\n}\n","/**\n * Volume bundle component — quantity selector with tier calculator.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n calculateTierSavings,\n getActiveTier,\n type CartLineInput,\n type VolumeBundleData,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\nexport function VolumeBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"volume\",\n enabled: analyticsEnabled !== false,\n });\n\n const [quantity, setQuantity] = useState(1);\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"volume\"\n ? (result.bundle as VolumeBundleData)\n : null;\n\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"volume\"\n ) {\n onError?.(\n new Error(\n `VolumeBundle: expected bundleType=\"volume\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const product = bundle?.products[0];\n const basePrice = product\n ? parseFloat(product.priceRange.minVariantPrice.amount)\n : 0;\n const currency = product?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n const tierSavings = bundle\n ? calculateTierSavings(bundle.volumeTiers, basePrice, quantity)\n : [];\n const activeTier = bundle\n ? getActiveTier(bundle.volumeTiers, quantity)\n : null;\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle || !product) return;\n\n const variant = product.variants.nodes.find((v) => v.availableForSale);\n if (!variant) return;\n\n const lines: CartLineInput[] = [\n {\n merchandiseId: variant.id,\n quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n },\n ];\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n const unitPrice = activeTier\n ? basePrice * (1 - activeTier.discountValue / 100)\n : basePrice;\n trackAddToCart({\n productId: product.id,\n quantity,\n totalPrice: Math.round(unitPrice * quantity * 100) / 100,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [\n bundle,\n product,\n quantity,\n activeTier,\n basePrice,\n onAddToCart,\n onError,\n trackAddToCart,\n ]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--tiers\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n if (!bundle) return null;\n if (!product) return null;\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--volume ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n <div className=\"lb-bundle__product lb-bundle__product--volume\">\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(basePrice, currency)} each\n </p>\n </div>\n </div>\n\n <div\n className=\"lb-bundle__tiers\"\n role=\"table\"\n aria-label=\"Volume discounts\"\n >\n {tierSavings.map((ts) => (\n <div\n key={ts.tier.minQuantity}\n className={`lb-bundle__tier ${ts.isActive ? \"lb-bundle__tier--active\" : \"\"}`}\n role=\"row\"\n >\n <span className=\"lb-bundle__tier-quantity\" role=\"cell\">\n {ts.tier.minQuantity}+ items\n </span>\n <span className=\"lb-bundle__tier-price\" role=\"cell\">\n {formatMoney(ts.unitPrice, currency)} each\n </span>\n <span className=\"lb-bundle__tier-savings\" role=\"cell\">\n Save {ts.savingsPercent.toFixed(0)}%\n </span>\n {ts.tier.label && (\n <span className=\"lb-bundle__tier-label\" role=\"cell\">\n {ts.tier.label}\n </span>\n )}\n </div>\n ))}\n </div>\n\n <div className=\"lb-bundle__quantity-selector\">\n <label htmlFor={`lb-qty-${bundle.id}`}>Quantity</label>\n <div className=\"lb-bundle__quantity-control\">\n <button\n aria-label=\"Decrease quantity\"\n onClick={() => setQuantity((q) => Math.max(1, q - 1))}\n >\n −\n </button>\n <input\n id={`lb-qty-${bundle.id}`}\n type=\"number\"\n min={1}\n value={quantity}\n onChange={(e) => {\n const val = parseInt(e.target.value, 10);\n if (!isNaN(val) && val > 0) setQuantity(val);\n }}\n className=\"lb-bundle__quantity-input\"\n />\n <button\n aria-label=\"Increase quantity\"\n onClick={() => setQuantity((q) => q + 1)}\n >\n +\n </button>\n </div>\n </div>\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? `Add ${quantity} to Cart`)}\n </button>\n </div>\n );\n}\n","/**\n * Client-side React hook wrapping `fetchBundlesForProduct` — the\n * React-SDK analogue of the web component's auto-detect-by-current-product\n * mode. The web component reads the handle from `location.pathname`; a\n * React app already has it on the router, so the hook takes it as an\n * explicit `productHandle` option.\n *\n * Returns every active bundle configured against the product. Inactive,\n * draft, scheduled-for-later, and expired bundles are filtered out by\n * the core helper — consumers don't have to guard.\n *\n * For SSR / RSC contexts (Hydrogen loaders, Next.js server components),\n * prefer `fetchBundlesForProduct` directly and pass the list to a client\n * component as a prop. This hook's only value-add over the async\n * function is React state management + abort-on-unmount.\n *\n * `UseBundlesForProductResult` is a discriminated union locked at v2.0.0:\n * narrowing via `status` is safe. Widening or adding states is a\n * breaking change.\n */\nimport { useState, useEffect } from \"react\";\nimport {\n fetchBundlesForProduct,\n injectCustomCss,\n type ParsedBundle,\n} from \"@lime-bundles/core\";\nimport { fetchShopCustomCss } from \"../fetchShopCustomCss\";\n\nexport interface UseBundlesForProductOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n productHandle: string;\n /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR paths. */\n buyerIp?: string;\n}\n\nexport type UseBundlesForProductResult =\n | { status: \"loading\"; bundles: null; error: null }\n | { status: \"success\"; bundles: ParsedBundle[]; error: null }\n | { status: \"error\"; bundles: null; error: Error };\n\nconst INITIAL_STATE: UseBundlesForProductResult = {\n status: \"loading\",\n bundles: null,\n error: null,\n};\n\nexport function useBundlesForProduct(\n options: UseBundlesForProductOptions,\n): UseBundlesForProductResult {\n const [state, setState] = useState<UseBundlesForProductResult>(\n INITIAL_STATE,\n );\n\n useEffect(() => {\n const controller = new AbortController();\n setState(INITIAL_STATE);\n\n fetchBundlesForProduct({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n productHandle: options.productHandle,\n buyerIp: options.buyerIp,\n signal: controller.signal,\n })\n .then((bundles) => {\n if (controller.signal.aborted) return;\n setState({ status: \"success\", bundles, error: null });\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return;\n setState({\n status: \"error\",\n bundles: null,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n });\n\n // Fire-and-forget custom CSS fetch so storefronts using this hook get\n // merchant styling automatically — same behaviour as `useBundleData`.\n fetchShopCustomCss({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n signal: controller.signal,\n })\n .then((css) => {\n if (controller.signal.aborted) return;\n injectCustomCss(options.shopDomain, css);\n })\n .catch(() => {\n // Silent — no merchant CSS is fine.\n });\n\n return () => {\n controller.abort();\n };\n }, [\n options.shopDomain,\n options.storefrontAccessToken,\n options.productHandle,\n options.buyerIp,\n ]);\n\n return state;\n}\n"],"mappings":"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","names":["import_react","import_core","import_core","import_core","import_react","import_core","import_react","import_core","import_jsx_runtime","import_react","import_core","import_jsx_runtime","import_react","import_core","INITIAL_STATE","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/FixedBundle.tsx","../src/hooks/useBundleData.ts","../src/fetchBundleData.ts","../src/fetchShopCustomCss.ts","../src/hooks/useAnalytics.ts","../src/components/MixMatchBundle.tsx","../src/components/VolumeBundle.tsx","../src/hooks/useBundlesForProduct.ts"],"sourcesContent":["// Bundle components — BYO-cart via `onAddToCart`.\nexport { FixedBundle } from \"./components/FixedBundle\";\nexport { MixMatchBundle } from \"./components/MixMatchBundle\";\nexport { VolumeBundle } from \"./components/VolumeBundle\";\nexport type { BundleComponentProps } from \"./components/types\";\n\n// Data fetching\nexport {\n fetchBundleData,\n type FetchBundleDataOptions,\n} from \"./fetchBundleData\";\nexport {\n fetchShopCustomCss,\n type FetchShopCustomCssOptions,\n} from \"./fetchShopCustomCss\";\nexport {\n useBundleData,\n type UseBundleDataOptions,\n type UseBundleDataResult,\n} from \"./hooks/useBundleData\";\nexport {\n useBundlesForProduct,\n type UseBundlesForProductOptions,\n type UseBundlesForProductResult,\n} from \"./hooks/useBundlesForProduct\";\nexport { useAnalytics } from \"./hooks/useAnalytics\";\n\n// Re-export core types consumers commonly need.\nexport type {\n ParsedBundle,\n FixedBundleData,\n VolumeBundleData,\n MixMatchBundleData,\n BundleType,\n Product,\n ProductVariant,\n CartLineInput,\n WidgetConfig,\n HeaderConfig,\n LayoutConfig,\n ProductListConfig,\n PricingConfig,\n SavingsBarConfig,\n CtaConfig,\n CountdownConfig,\n PopularBadgeConfig,\n VolumeTier,\n DiscountConfig,\n ABVariantOverrides,\n PricingRow,\n FixedBundlePricing,\n ImageTransform,\n} from \"@lime-bundles/core\";\nexport { BundleParseError, StorefrontApiError } from \"@lime-bundles/core\";\n\n// DIY widget primitives — every helper a developer needs to build their\n// own bundle UI with the same data, math, and theming the first-party\n// widget uses. All live in `@lime-bundles/core`; importing from the\n// React package is shorthand for consumers who already pulled `react`.\nexport {\n // Bundle config + variant merging\n WIDGET_CONFIG_DEFAULTS,\n mergeWidgetConfig,\n applyWidgetConfigVars,\n applyABVariantB,\n // Pricing (integer cents, matches Shopify Discount Function rounding)\n parseCents,\n formatCents,\n percentageDiscountUnit,\n computeFixedPricing,\n computeBundleSaleCents,\n // Volume-tier calculation\n calculateTierSavings,\n getActiveTier,\n // Mix-match quantity validation\n validateQuantity,\n // Image / countdown / money helpers\n transformImageUrl,\n formatCountdown,\n formatMoney,\n calculateDiscount,\n // Analytics (optional — FixedBundle / VolumeBundle / MixMatchBundle\n // auto-fire these unless analyticsEnabled is set to false)\n reportImpression,\n reportAddToCart,\n observeImpression,\n // A/B testing\n getABTestAssignment,\n setConsent,\n hasConsent,\n // CSS injection + sanitizer\n injectCustomCss,\n sanitizeCustomCss,\n // Custom-storefront data fetching\n createStorefrontClient,\n fetchBundlesForProduct,\n parseMetaobjectBundle,\n parseMetaobjectBundleStrict,\n BUNDLE_METAOBJECT_QUERY,\n BUNDLES_FOR_PRODUCT_QUERY,\n SHOP_CUSTOM_CSS_QUERY,\n} from \"@lime-bundles/core\";\n","/**\n * Fixed bundle component — pre-selected products with a shared discount.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n calculateDiscount,\n resolveBundleQty,\n type CartLineInput,\n type FixedBundleData,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\nexport function FixedBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"fixed\",\n enabled: analyticsEnabled !== false,\n });\n\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"fixed\"\n ? (result.bundle as FixedBundleData)\n : null;\n\n // Surface load/narrowing errors via onError — in a useEffect so we don't\n // fire side effects during render.\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"fixed\"\n ) {\n onError?.(\n new Error(\n `FixedBundle: expected bundleType=\"fixed\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle) return;\n\n // Qty resolution (variantQuantities → productQuantities → 1) lives in\n // `@lime-bundles/core` so the five-runtime fallback chain has a single\n // source of truth. See resolveBundleQty + PR #147 history.\n const bundleLines = bundle.products\n .filter((p) => p.variants.nodes.some((v) => v.availableForSale))\n .map((p) => {\n const variant = p.variants.nodes.find((v) => v.availableForSale)!;\n return {\n productId: p.id,\n variant,\n quantity: resolveBundleQty(bundle, p.id, variant.id),\n };\n })\n .filter((line) => line.quantity > 0);\n\n if (bundleLines.length === 0) return;\n\n const lines: CartLineInput[] = bundleLines.map((line) => ({\n merchandiseId: line.variant.id,\n quantity: line.quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n }));\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n const totalPrice = bundleLines.reduce((sum, line) => {\n const price = parseFloat(line.variant.price.amount);\n const discounted = calculateDiscount(\n price,\n bundle.discountConfig.discountType,\n bundle.discountConfig.discountValue,\n );\n return sum + discounted * line.quantity;\n }, 0);\n const totalQuantity = bundleLines.reduce(\n (sum, line) => sum + line.quantity,\n 0,\n );\n // productId is intentionally omitted — a fixed bundle spans multiple\n // products with no single \"page product\"; bundleGid on the event\n // already attributes to the bundle.\n trackAddToCart({\n quantity: totalQuantity,\n totalPrice: Math.round(totalPrice * 100) / 100,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [bundle, onAddToCart, onError, trackAddToCart]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--products\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n // Bundle loaded but wasn't a \"fixed\" variant — render nothing; the\n // useEffect above has already invoked onError with a descriptive message.\n if (!bundle) return null;\n\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--fixed ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n {bundle.discountLabel && (\n <span className=\"lb-bundle__discount-badge\">{bundle.discountLabel}</span>\n )}\n\n <div className=\"lb-bundle__products\">\n {bundle.products.map((product) => (\n <div key={product.id} className=\"lb-bundle__product\" part=\"product\">\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(product.priceRange.minVariantPrice.amount, currency)}\n </p>\n </div>\n </div>\n ))}\n </div>\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? \"Add Bundle to Cart\")}\n </button>\n </div>\n );\n}\n","/**\n * Client-side React hook wrapping `fetchBundleData` with loading / error\n * state + abort-on-unmount.\n *\n * For SSR / RSC contexts (Hydrogen loaders, Next.js server components),\n * prefer `fetchBundleData` directly and pass the result to a client\n * component as a prop — the hook's only value-add over the async\n * function is React state management.\n *\n * `UseBundleDataResult` is a discriminated union locked at v1.0.0:\n * narrowing via `status` is safe. Widening or adding states is a\n * breaking change.\n */\nimport { useState, useEffect } from \"react\";\nimport { injectCustomCss, type ParsedBundle } from \"@lime-bundles/core\";\nimport { fetchBundleData } from \"../fetchBundleData\";\nimport { fetchShopCustomCss } from \"../fetchShopCustomCss\";\n\nexport interface UseBundleDataOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n bundleGid: string;\n}\n\nexport type UseBundleDataResult =\n | { status: \"loading\"; bundle: null; error: null }\n | { status: \"success\"; bundle: ParsedBundle; error: null }\n | { status: \"error\"; bundle: null; error: Error };\n\nconst INITIAL_STATE: UseBundleDataResult = {\n status: \"loading\",\n bundle: null,\n error: null,\n};\n\nexport function useBundleData(\n options: UseBundleDataOptions,\n): UseBundleDataResult {\n const [state, setState] = useState<UseBundleDataResult>(INITIAL_STATE);\n\n useEffect(() => {\n const controller = new AbortController();\n setState(INITIAL_STATE);\n\n fetchBundleData({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n bundleGid: options.bundleGid,\n signal: controller.signal,\n })\n .then((bundle) => {\n if (controller.signal.aborted) return;\n setState({ status: \"success\", bundle, error: null });\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return;\n setState({\n status: \"error\",\n bundle: null,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n });\n\n // Fire-and-forget custom CSS fetch — best-effort, failures never block\n // bundle rendering.\n fetchShopCustomCss({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n signal: controller.signal,\n })\n .then((css) => {\n if (controller.signal.aborted) return;\n injectCustomCss(options.shopDomain, css);\n })\n .catch(() => {\n // Silent — no merchant CSS is fine.\n });\n\n return () => {\n controller.abort();\n };\n }, [\n options.shopDomain,\n options.storefrontAccessToken,\n options.bundleGid,\n ]);\n\n return state;\n}\n","/**\n * Framework-agnostic async fetcher for bundle data.\n *\n * Primary path for Next.js App Router / RSC and Hydrogen loaders — call\n * this in a server-side context and pass the result as a prop to a\n * `\"use client\"` component. `useBundleData` is NOT needed in that\n * component because the data is already in hand.\n *\n * Always forward the end-buyer's IP via `buyerIp` when calling from SSR:\n * Shopify may return 430 Security Rejection for server-originated traffic\n * without it.\n *\n * Throws:\n * - `StorefrontApiError` on network / HTTP / GraphQL errors\n * - `BundleParseError` when the metaobject is missing, invalid type,\n * inactive, not yet started, or expired\n */\nimport {\n createStorefrontClient,\n BUNDLE_METAOBJECT_QUERY,\n parseMetaobjectBundleStrict,\n BundleParseError,\n type ParsedBundle,\n type BundleMetaobjectResponse,\n} from \"@lime-bundles/core\";\n\nexport interface FetchBundleDataOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n bundleGid: string;\n /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR. */\n buyerIp?: string;\n /** Abort the in-flight request (used by `useBundleData` to cancel on unmount). */\n signal?: AbortSignal;\n /** Override the Storefront API version pinned in `createStorefrontClient`. */\n apiVersion?: string;\n}\n\nexport async function fetchBundleData(\n options: FetchBundleDataOptions,\n): Promise<ParsedBundle> {\n const client = createStorefrontClient({\n shopDomain: options.shopDomain,\n accessToken: options.storefrontAccessToken,\n buyerIp: options.buyerIp,\n apiVersion: options.apiVersion,\n });\n\n const data = await client.query<BundleMetaobjectResponse>(\n BUNDLE_METAOBJECT_QUERY,\n { id: options.bundleGid },\n { signal: options.signal },\n );\n\n if (!data.metaobject) {\n throw new BundleParseError(\n `Bundle not found: ${options.bundleGid}`,\n \"not_found\",\n );\n }\n\n return parseMetaobjectBundleStrict(\n data.metaobject.id,\n data.metaobject.fields,\n );\n}\n","/**\n * Best-effort fetch for the shop's `$app:custom_css` metafield.\n *\n * Returns the raw CSS string (still needs sanitization before injection —\n * see `injectCustomCss` from core) or null if the metafield is unset or\n * the request fails.\n *\n * **Per-shop in-memory cache.** Multiple `<FixedBundle>` / `<VolumeBundle>`\n * / `<MixMatchBundle>` components on the same page share one network\n * request — the first caller fetches, concurrent callers await the\n * same promise, and resolved values are cached so subsequent mounts\n * return instantly. Failures are NOT cached so the next caller retries.\n *\n * The cache lives for the life of the JS module (typically one page\n * load). Merchants updating their CSS see changes on the next page\n * load. The cache is exposed as `__clearShopCssCache` for test use only.\n */\nimport {\n createStorefrontClient,\n SHOP_CUSTOM_CSS_QUERY,\n type ShopCustomCssResponse,\n} from \"@lime-bundles/core\";\n\nexport interface FetchShopCustomCssOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n buyerIp?: string;\n signal?: AbortSignal;\n}\n\n// Cache map: key = shopDomain, value = pending or resolved promise.\n// Failures delete their own entry so retries get a fresh fetch.\nconst cache = new Map<string, Promise<string | null>>();\n\nexport async function fetchShopCustomCss(\n options: FetchShopCustomCssOptions,\n): Promise<string | null> {\n const cached = cache.get(options.shopDomain);\n if (cached) return cached;\n\n const promise = (async () => {\n const client = createStorefrontClient({\n shopDomain: options.shopDomain,\n accessToken: options.storefrontAccessToken,\n buyerIp: options.buyerIp,\n });\n\n const data = await client.query<ShopCustomCssResponse>(\n SHOP_CUSTOM_CSS_QUERY,\n undefined,\n { signal: options.signal },\n );\n\n return data.shop?.metafield?.value ?? null;\n })();\n\n // Cache the promise synchronously so concurrent callers dedupe.\n cache.set(options.shopDomain, promise);\n\n // Evict on failure so the next caller can retry.\n promise.catch(() => {\n if (cache.get(options.shopDomain) === promise) {\n cache.delete(options.shopDomain);\n }\n });\n\n return promise;\n}\n\n/**\n * Test-only helper. Clears the per-shop CSS cache so each test starts\n * from a known state. Do not call from application code.\n */\nexport function __clearShopCssCache(): void {\n cache.clear();\n}\n","/**\n * Hook for analytics event reporting (impressions + add-to-cart).\n * Impressions fire once per mount using IntersectionObserver.\n */\nimport { useEffect, useMemo, useRef, useCallback, useState } from \"react\";\nimport {\n reportImpression,\n reportAddToCart,\n observeImpression,\n type AnalyticsConfig,\n} from \"@lime-bundles/core\";\n\nexport interface UseAnalyticsOptions {\n shopDomain: string;\n appUrl: string;\n bundleGid: string;\n bundleType: string;\n enabled?: boolean;\n abTestId?: string;\n abVariant?: string;\n}\n\nexport function useAnalytics(options: UseAnalyticsOptions) {\n const impressionFiredRef = useRef(false);\n // Callback ref + state so the observer effect re-runs when the host element\n // is attached (e.g. after a `loading` branch swaps in the real DOM node).\n const [element, setElement] = useState<HTMLElement | null>(null);\n const elementRef = useCallback((el: HTMLElement | null) => {\n setElement(el);\n }, []);\n\n const config: AnalyticsConfig = useMemo(\n () => ({ shopDomain: options.shopDomain, appUrl: options.appUrl }),\n [options.shopDomain, options.appUrl],\n );\n\n // Reset the fired flag when bundle identity changes so a reused component\n // (e.g. via navigation) can report a fresh impression.\n useEffect(() => {\n impressionFiredRef.current = false;\n }, [options.bundleGid, options.abVariant]);\n\n // Set up impression observer once the element is attached\n useEffect(() => {\n if (options.enabled === false) return;\n if (impressionFiredRef.current) return;\n if (!element) return;\n\n const cleanup = observeImpression(element, () => {\n if (impressionFiredRef.current) return;\n impressionFiredRef.current = true;\n\n reportImpression(config, {\n bundleGid: options.bundleGid,\n bundleType: options.bundleType,\n abTestId: options.abTestId,\n abVariant: options.abVariant,\n });\n });\n\n return cleanup;\n }, [\n element,\n config,\n options.enabled,\n options.bundleGid,\n options.bundleType,\n options.abTestId,\n options.abVariant,\n ]);\n\n const trackAddToCart = useCallback(\n (event: {\n /** Optional — omit for multi-product bundles (fixed, mix-match). */\n productId?: string;\n quantity: number;\n totalPrice: number;\n }) => {\n if (options.enabled === false) return;\n\n reportAddToCart(config, {\n bundleGid: options.bundleGid,\n bundleType: options.bundleType,\n productId: event.productId,\n quantity: event.quantity,\n totalPrice: event.totalPrice,\n abTestId: options.abTestId,\n abVariant: options.abVariant,\n });\n },\n [\n config,\n options.enabled,\n options.bundleGid,\n options.bundleType,\n options.abTestId,\n options.abVariant,\n ],\n );\n\n return { elementRef, trackAddToCart };\n}\n","/**\n * Mix-and-match bundle component — customer picks products from a pool.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n validateQuantity,\n resolveBundleQty,\n type CartLineInput,\n type MixMatchBundleData,\n type ProductVariant,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\ninterface SelectedItem {\n productId: string;\n variantId: string;\n quantity: number;\n}\n\nexport function MixMatchBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"mix_match\",\n enabled: analyticsEnabled !== false,\n });\n\n const [selections, setSelections] = useState<Map<string, SelectedItem>>(\n new Map(),\n );\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"mix_match\"\n ? (result.bundle as MixMatchBundleData)\n : null;\n\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"mix_match\"\n ) {\n onError?.(\n new Error(\n `MixMatchBundle: expected bundleType=\"mix_match\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const totalQuantity = Array.from(selections.values()).reduce(\n (sum, s) => sum + s.quantity,\n 0,\n );\n\n const validation = bundle\n ? validateQuantity(totalQuantity, bundle.minQuantity, bundle.maxQuantity)\n : { valid: false, totalQuantity: 0, message: null };\n\n const toggleProduct = useCallback(\n (productId: string, variant: ProductVariant) => {\n setSelections((prev) => {\n const next = new Map(prev);\n const key = `${productId}:${variant.id}`;\n if (next.has(key)) {\n next.delete(key);\n } else {\n // Pre-fill slot qty from the canonical resolver — variant-level\n // wins, product-level fallback, then 1. Customer can still adjust\n // via the stepper afterwards.\n const configuredQty = bundle\n ? resolveBundleQty(bundle, productId, variant.id)\n : 1;\n next.set(key, {\n productId,\n variantId: variant.id,\n quantity: configuredQty,\n });\n }\n return next;\n });\n },\n [bundle],\n );\n\n const updateQuantity = useCallback(\n (productId: string, variantId: string, quantity: number) => {\n setSelections((prev) => {\n const next = new Map(prev);\n const key = `${productId}:${variantId}`;\n if (quantity <= 0) {\n next.delete(key);\n } else {\n next.set(key, { productId, variantId, quantity });\n }\n return next;\n });\n },\n [],\n );\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle || !validation.valid) return;\n\n const lines: CartLineInput[] = Array.from(selections.values()).map((s) => ({\n merchandiseId: s.variantId,\n quantity: s.quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n }));\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n\n const totalPrice = lines.reduce((sum, line) => {\n const product = bundle.products.find((p) =>\n p.variants.nodes.some((v) => v.id === line.merchandiseId),\n );\n const variant = product?.variants.nodes.find(\n (v) => v.id === line.merchandiseId,\n );\n return (\n sum + parseFloat(variant?.price.amount ?? \"0\") * line.quantity\n );\n }, 0);\n\n // productId is intentionally omitted — mix-match bundles span\n // multiple products; bundleGid attributes the event to the bundle.\n trackAddToCart({\n quantity: totalQuantity,\n totalPrice: Math.round(totalPrice * 100) / 100,\n });\n\n setSelections(new Map());\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [\n bundle,\n selections,\n validation.valid,\n onAddToCart,\n onError,\n trackAddToCart,\n totalQuantity,\n ]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--products\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n if (!bundle) return null;\n\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--mix-match ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n {bundle.discountLabel && (\n <span className=\"lb-bundle__discount-badge\">{bundle.discountLabel}</span>\n )}\n\n <p className=\"lb-bundle__instructions\">\n {bundle.minQuantity && bundle.maxQuantity\n ? `Select ${bundle.minQuantity}–${bundle.maxQuantity} items`\n : bundle.minQuantity\n ? `Select at least ${bundle.minQuantity} items`\n : bundle.maxQuantity\n ? `Select up to ${bundle.maxQuantity} items`\n : \"Select your items\"}\n </p>\n\n <div className=\"lb-bundle__products lb-bundle__products--selectable\">\n {bundle.products.map((product) => {\n const defaultVariant =\n product.variants.nodes.find((v) => v.availableForSale) ??\n product.variants.nodes[0];\n if (!defaultVariant) return null;\n\n const key = `${product.id}:${defaultVariant.id}`;\n const selected = selections.get(key);\n\n return (\n <div\n key={product.id}\n className={`lb-bundle__product lb-bundle__product--selectable ${selected ? \"lb-bundle__product--selected\" : \"\"}`}\n >\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(defaultVariant.price.amount, currency)}\n </p>\n </div>\n <div className=\"lb-bundle__product-actions\">\n {selected ? (\n <div className=\"lb-bundle__quantity-control\">\n <button\n aria-label={`Decrease ${product.title}`}\n onClick={() =>\n updateQuantity(\n product.id,\n defaultVariant.id,\n selected.quantity - 1,\n )\n }\n >\n −\n </button>\n <span>{selected.quantity}</span>\n <button\n aria-label={`Increase ${product.title}`}\n onClick={() =>\n updateQuantity(\n product.id,\n defaultVariant.id,\n selected.quantity + 1,\n )\n }\n >\n +\n </button>\n </div>\n ) : (\n <button\n className=\"lb-bundle__select-btn\"\n onClick={() => toggleProduct(product.id, defaultVariant)}\n disabled={!defaultVariant.availableForSale}\n >\n {defaultVariant.availableForSale ? \"Select\" : \"Sold out\"}\n </button>\n )}\n </div>\n </div>\n );\n })}\n </div>\n\n {validation.message && (\n <p className=\"lb-bundle__validation\" role=\"status\">\n {validation.message}\n </p>\n )}\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart || !validation.valid}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? `Add ${totalQuantity} Items to Cart`)}\n </button>\n </div>\n );\n}\n","/**\n * Volume bundle component — quantity selector with tier calculator.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n calculateTierSavings,\n getActiveTier,\n type CartLineInput,\n type VolumeBundleData,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\nexport function VolumeBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"volume\",\n enabled: analyticsEnabled !== false,\n });\n\n const [quantity, setQuantity] = useState(1);\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"volume\"\n ? (result.bundle as VolumeBundleData)\n : null;\n\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"volume\"\n ) {\n onError?.(\n new Error(\n `VolumeBundle: expected bundleType=\"volume\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const product = bundle?.products[0];\n const basePrice = product\n ? parseFloat(product.priceRange.minVariantPrice.amount)\n : 0;\n const currency = product?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n const tierSavings = bundle\n ? calculateTierSavings(\n bundle.volumeTiers,\n basePrice,\n quantity,\n bundle.discountConfig.discountType,\n )\n : [];\n const activeTier = bundle\n ? getActiveTier(bundle.volumeTiers, quantity)\n : null;\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle || !product) return;\n\n const variant = product.variants.nodes.find((v) => v.availableForSale);\n if (!variant) return;\n\n const lines: CartLineInput[] = [\n {\n merchandiseId: variant.id,\n quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n },\n ];\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n // Reuse the canonical computation from calculateTierSavings so the\n // analytics unit price matches what the component rendered.\n const unitPrice =\n tierSavings.find((ts) => ts.tier === activeTier)?.unitPrice ?? basePrice;\n trackAddToCart({\n productId: product.id,\n quantity,\n totalPrice: Math.round(unitPrice * quantity * 100) / 100,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [\n bundle,\n product,\n quantity,\n activeTier,\n basePrice,\n onAddToCart,\n onError,\n trackAddToCart,\n ]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--tiers\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n if (!bundle) return null;\n if (!product) return null;\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--volume ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n <div className=\"lb-bundle__product lb-bundle__product--volume\">\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(basePrice, currency)} each\n </p>\n </div>\n </div>\n\n <div\n className=\"lb-bundle__tiers\"\n role=\"table\"\n aria-label=\"Volume discounts\"\n >\n {tierSavings.map((ts) => (\n <div\n key={ts.tier.minQuantity}\n className={`lb-bundle__tier ${ts.isActive ? \"lb-bundle__tier--active\" : \"\"}`}\n role=\"row\"\n >\n <span className=\"lb-bundle__tier-quantity\" role=\"cell\">\n {ts.tier.minQuantity}+ items\n </span>\n <span className=\"lb-bundle__tier-price\" role=\"cell\">\n {formatMoney(ts.unitPrice, currency)} each\n </span>\n <span className=\"lb-bundle__tier-savings\" role=\"cell\">\n Save {ts.savingsPercent.toFixed(0)}%\n </span>\n </div>\n ))}\n </div>\n\n <div className=\"lb-bundle__quantity-selector\">\n <label htmlFor={`lb-qty-${bundle.id}`}>Quantity</label>\n <div className=\"lb-bundle__quantity-control\">\n <button\n aria-label=\"Decrease quantity\"\n onClick={() => setQuantity((q) => Math.max(1, q - 1))}\n >\n −\n </button>\n <input\n id={`lb-qty-${bundle.id}`}\n type=\"number\"\n min={1}\n value={quantity}\n onChange={(e) => {\n const val = parseInt(e.target.value, 10);\n if (!isNaN(val) && val > 0) setQuantity(val);\n }}\n className=\"lb-bundle__quantity-input\"\n />\n <button\n aria-label=\"Increase quantity\"\n onClick={() => setQuantity((q) => q + 1)}\n >\n +\n </button>\n </div>\n </div>\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? `Add ${quantity} to Cart`)}\n </button>\n </div>\n );\n}\n","/**\n * Client-side React hook wrapping `fetchBundlesForProduct` — the\n * React-SDK analogue of the web component's auto-detect-by-current-product\n * mode. The web component reads the handle from `location.pathname`; a\n * React app already has it on the router, so the hook takes it as an\n * explicit `productHandle` option.\n *\n * Returns every active bundle configured against the product. Inactive,\n * draft, scheduled-for-later, and expired bundles are filtered out by\n * the core helper — consumers don't have to guard.\n *\n * For SSR / RSC contexts (Hydrogen loaders, Next.js server components),\n * prefer `fetchBundlesForProduct` directly and pass the list to a client\n * component as a prop. This hook's only value-add over the async\n * function is React state management + abort-on-unmount.\n *\n * `UseBundlesForProductResult` is a discriminated union locked at v2.0.0:\n * narrowing via `status` is safe. Widening or adding states is a\n * breaking change.\n */\nimport { useState, useEffect } from \"react\";\nimport {\n fetchBundlesForProduct,\n injectCustomCss,\n type ParsedBundle,\n} from \"@lime-bundles/core\";\nimport { fetchShopCustomCss } from \"../fetchShopCustomCss\";\n\nexport interface UseBundlesForProductOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n productHandle: string;\n /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR paths. */\n buyerIp?: string;\n}\n\nexport type UseBundlesForProductResult =\n | { status: \"loading\"; bundles: null; error: null }\n | { status: \"success\"; bundles: ParsedBundle[]; error: null }\n | { status: \"error\"; bundles: null; error: Error };\n\nconst INITIAL_STATE: UseBundlesForProductResult = {\n status: \"loading\",\n bundles: null,\n error: null,\n};\n\nexport function useBundlesForProduct(\n options: UseBundlesForProductOptions,\n): UseBundlesForProductResult {\n const [state, setState] = useState<UseBundlesForProductResult>(\n INITIAL_STATE,\n );\n\n useEffect(() => {\n const controller = new AbortController();\n setState(INITIAL_STATE);\n\n fetchBundlesForProduct({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n productHandle: options.productHandle,\n buyerIp: options.buyerIp,\n signal: controller.signal,\n })\n .then((bundles) => {\n if (controller.signal.aborted) return;\n setState({ status: \"success\", bundles, error: null });\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return;\n setState({\n status: \"error\",\n bundles: null,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n });\n\n // Fire-and-forget custom CSS fetch so storefronts using this hook get\n // merchant styling automatically — same behaviour as `useBundleData`.\n fetchShopCustomCss({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n signal: controller.signal,\n })\n .then((css) => {\n if (controller.signal.aborted) return;\n injectCustomCss(options.shopDomain, css);\n })\n .catch(() => {\n // Silent — no merchant CSS is fine.\n });\n\n return () => {\n controller.abort();\n };\n }, [\n options.shopDomain,\n options.storefrontAccessToken,\n options.productHandle,\n options.buyerIp,\n ]);\n\n return state;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGA,IAAAA,gBAAiD;AACjD,IAAAC,eAMO;;;ACGP,mBAAoC;AACpC,IAAAC,eAAmD;;;ACGnD,kBAOO;AAcP,eAAsB,gBACpB,SACuB;AACvB,QAAM,aAAS,oCAAuB;AAAA,IACpC,YAAY,QAAQ;AAAA,IACpB,aAAa,QAAQ;AAAA,IACrB,SAAS,QAAQ;AAAA,IACjB,YAAY,QAAQ;AAAA,EACtB,CAAC;AAED,QAAM,OAAO,MAAM,OAAO;AAAA,IACxB;AAAA,IACA,EAAE,IAAI,QAAQ,UAAU;AAAA,IACxB,EAAE,QAAQ,QAAQ,OAAO;AAAA,EAC3B;AAEA,MAAI,CAAC,KAAK,YAAY;AACpB,UAAM,IAAI;AAAA,MACR,qBAAqB,QAAQ,SAAS;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AAEA,aAAO;AAAA,IACL,KAAK,WAAW;AAAA,IAChB,KAAK,WAAW;AAAA,EAClB;AACF;;;AChDA,IAAAC,eAIO;AAWP,IAAM,QAAQ,oBAAI,IAAoC;AAEtD,eAAsB,mBACpB,SACwB;AACxB,QAAM,SAAS,MAAM,IAAI,QAAQ,UAAU;AAC3C,MAAI,OAAQ,QAAO;AAEnB,QAAM,WAAW,YAAY;AAC3B,UAAM,aAAS,qCAAuB;AAAA,MACpC,YAAY,QAAQ;AAAA,MACpB,aAAa,QAAQ;AAAA,MACrB,SAAS,QAAQ;AAAA,IACnB,CAAC;AAED,UAAM,OAAO,MAAM,OAAO;AAAA,MACxB;AAAA,MACA;AAAA,MACA,EAAE,QAAQ,QAAQ,OAAO;AAAA,IAC3B;AAEA,WAAO,KAAK,MAAM,WAAW,SAAS;AAAA,EACxC,GAAG;AAGH,QAAM,IAAI,QAAQ,YAAY,OAAO;AAGrC,UAAQ,MAAM,MAAM;AAClB,QAAI,MAAM,IAAI,QAAQ,UAAU,MAAM,SAAS;AAC7C,YAAM,OAAO,QAAQ,UAAU;AAAA,IACjC;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;AFtCA,IAAM,gBAAqC;AAAA,EACzC,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AACT;AAEO,SAAS,cACd,SACqB;AACrB,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAA8B,aAAa;AAErE,8BAAU,MAAM;AACd,UAAM,aAAa,IAAI,gBAAgB;AACvC,aAAS,aAAa;AAEtB,oBAAgB;AAAA,MACd,YAAY,QAAQ;AAAA,MACpB,uBAAuB,QAAQ;AAAA,MAC/B,WAAW,QAAQ;AAAA,MACnB,QAAQ,WAAW;AAAA,IACrB,CAAC,EACE,KAAK,CAAC,WAAW;AAChB,UAAI,WAAW,OAAO,QAAS;AAC/B,eAAS,EAAE,QAAQ,WAAW,QAAQ,OAAO,KAAK,CAAC;AAAA,IACrD,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,UAAI,WAAW,OAAO,QAAS;AAC/B,eAAS;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAAA,MAC3D,CAAC;AAAA,IACH,CAAC;AAIH,uBAAmB;AAAA,MACjB,YAAY,QAAQ;AAAA,MACpB,uBAAuB,QAAQ;AAAA,MAC/B,QAAQ,WAAW;AAAA,IACrB,CAAC,EACE,KAAK,CAAC,QAAQ;AACb,UAAI,WAAW,OAAO,QAAS;AAC/B,wCAAgB,QAAQ,YAAY,GAAG;AAAA,IACzC,CAAC,EACA,MAAM,MAAM;AAAA,IAEb,CAAC;AAEH,WAAO,MAAM;AACX,iBAAW,MAAM;AAAA,IACnB;AAAA,EACF,GAAG;AAAA,IACD,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV,CAAC;AAED,SAAO;AACT;;;AGpFA,IAAAC,gBAAkE;AAClE,IAAAC,eAKO;AAYA,SAAS,aAAa,SAA8B;AACzD,QAAM,yBAAqB,sBAAO,KAAK;AAGvC,QAAM,CAAC,SAAS,UAAU,QAAI,wBAA6B,IAAI;AAC/D,QAAM,iBAAa,2BAAY,CAAC,OAA2B;AACzD,eAAW,EAAE;AAAA,EACf,GAAG,CAAC,CAAC;AAEL,QAAM,aAA0B;AAAA,IAC9B,OAAO,EAAE,YAAY,QAAQ,YAAY,QAAQ,QAAQ,OAAO;AAAA,IAChE,CAAC,QAAQ,YAAY,QAAQ,MAAM;AAAA,EACrC;AAIA,+BAAU,MAAM;AACd,uBAAmB,UAAU;AAAA,EAC/B,GAAG,CAAC,QAAQ,WAAW,QAAQ,SAAS,CAAC;AAGzC,+BAAU,MAAM;AACd,QAAI,QAAQ,YAAY,MAAO;AAC/B,QAAI,mBAAmB,QAAS;AAChC,QAAI,CAAC,QAAS;AAEd,UAAM,cAAU,gCAAkB,SAAS,MAAM;AAC/C,UAAI,mBAAmB,QAAS;AAChC,yBAAmB,UAAU;AAE7B,yCAAiB,QAAQ;AAAA,QACvB,WAAW,QAAQ;AAAA,QACnB,YAAY,QAAQ;AAAA,QACpB,UAAU,QAAQ;AAAA,QAClB,WAAW,QAAQ;AAAA,MACrB,CAAC;AAAA,IACH,CAAC;AAED,WAAO;AAAA,EACT,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV,CAAC;AAED,QAAM,qBAAiB;AAAA,IACrB,CAAC,UAKK;AACJ,UAAI,QAAQ,YAAY,MAAO;AAE/B,wCAAgB,QAAQ;AAAA,QACtB,WAAW,QAAQ;AAAA,QACnB,YAAY,QAAQ;AAAA,QACpB,WAAW,MAAM;AAAA,QACjB,UAAU,MAAM;AAAA,QAChB,YAAY,MAAM;AAAA,QAClB,UAAU,QAAQ;AAAA,QAClB,WAAW,QAAQ;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,IACA;AAAA,MACE;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,SAAO,EAAE,YAAY,eAAe;AACtC;;;AJ+BM;AArHC,SAAS,YAAY,OAA6B;AACvD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,SAAS,cAAc;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,EAAE,YAAY,eAAe,IAAI,aAAa;AAAA,IAClD;AAAA,IACA,QAAQ,UAAU,WAAW,UAAU;AAAA,IACvC;AAAA,IACA,YAAY;AAAA,IACZ,SAAS,qBAAqB;AAAA,EAChC,CAAC;AAED,QAAM,CAAC,cAAc,eAAe,QAAI,wBAAS,KAAK;AACtD,QAAM,CAAC,WAAW,YAAY,QAAI,wBAAwB,IAAI;AAE9D,QAAM,SACJ,OAAO,WAAW,aAAa,OAAO,OAAO,eAAe,UACvD,OAAO,SACR;AAIN,+BAAU,MAAM;AACd,QAAI,OAAO,WAAW,SAAS;AAC7B,gBAAU,OAAO,KAAK;AACtB;AAAA,IACF;AACA,QACE,OAAO,WAAW,aAClB,OAAO,OAAO,eAAe,SAC7B;AACA;AAAA,QACE,IAAI;AAAA,UACF,kDAAkD,OAAO,OAAO,UAAU;AAAA,QAC5E;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,QAAQ,OAAO,CAAC;AAEpB,QAAM,sBAAkB,2BAAY,YAAY;AAC9C,QAAI,CAAC,OAAQ;AAKb,UAAM,cAAc,OAAO,SACxB,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC9D,IAAI,CAAC,MAAM;AACV,YAAM,UAAU,EAAE,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,gBAAgB;AAC/D,aAAO;AAAA,QACL,WAAW,EAAE;AAAA,QACb;AAAA,QACA,cAAU,+BAAiB,QAAQ,EAAE,IAAI,QAAQ,EAAE;AAAA,MACrD;AAAA,IACF,CAAC,EACA,OAAO,CAAC,SAAS,KAAK,WAAW,CAAC;AAErC,QAAI,YAAY,WAAW,EAAG;AAE9B,UAAM,QAAyB,YAAY,IAAI,CAAC,UAAU;AAAA,MACxD,eAAe,KAAK,QAAQ;AAAA,MAC5B,UAAU,KAAK;AAAA,MACf,YAAY;AAAA,QACV,EAAE,KAAK,oBAAoB,OAAO,OAAO,GAAG;AAAA,QAC5C,EAAE,KAAK,qBAAqB,OAAO,OAAO,WAAW;AAAA,MACvD;AAAA,IACF,EAAE;AAEF,oBAAgB,IAAI;AACpB,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,YAAY,KAAK;AACvB,YAAM,aAAa,YAAY,OAAO,CAAC,KAAK,SAAS;AACnD,cAAM,QAAQ,WAAW,KAAK,QAAQ,MAAM,MAAM;AAClD,cAAM,iBAAa;AAAA,UACjB;AAAA,UACA,OAAO,eAAe;AAAA,UACtB,OAAO,eAAe;AAAA,QACxB;AACA,eAAO,MAAM,aAAa,KAAK;AAAA,MACjC,GAAG,CAAC;AACJ,YAAM,gBAAgB,YAAY;AAAA,QAChC,CAAC,KAAK,SAAS,MAAM,KAAK;AAAA,QAC1B;AAAA,MACF;AAIA,qBAAe;AAAA,QACb,UAAU;AAAA,QACV,YAAY,KAAK,MAAM,aAAa,GAAG,IAAI;AAAA,MAC7C,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,mBAAa,MAAM,OAAO;AAC1B,gBAAU,KAAK;AAAA,IACjB,UAAE;AACA,sBAAgB,KAAK;AAAA,IACvB;AAAA,EACF,GAAG,CAAC,QAAQ,aAAa,SAAS,cAAc,CAAC;AAEjD,MAAI,OAAO,WAAW,WAAW;AAC/B,WACE,6CAAC,SAAI,WAAW,gCAAgC,aAAa,EAAE,IAC7D;AAAA,kDAAC,SAAI,WAAU,kCAAiC;AAAA,MAChD,4CAAC,SAAI,WAAU,qCAAoC;AAAA,OACrD;AAAA,EAEJ;AAEA,MAAI,OAAO,WAAW,QAAS,QAAO;AAGtC,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,WACJ,OAAO,SAAS,CAAC,GAAG,WAAW,gBAAgB,gBAAgB;AAEjE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,8BAA8B,aAAa,EAAE;AAAA,MACxD,MAAK;AAAA,MACL,cAAY,OAAO;AAAA,MAEnB;AAAA,oDAAC,QAAG,WAAU,oBAAoB,iBAAO,OAAM;AAAA,QAE9C,OAAO,iBACN,4CAAC,UAAK,WAAU,6BAA6B,iBAAO,eAAc;AAAA,QAGpE,4CAAC,SAAI,WAAU,uBACZ,iBAAO,SAAS,IAAI,CAAC,YACpB,6CAAC,SAAqB,WAAU,sBAAqB,MAAK,WACvD;AAAA,kBAAQ,iBACP;AAAA,YAAC;AAAA;AAAA,cACC,KAAK,QAAQ,cAAc;AAAA,cAC3B,KAAK,QAAQ,cAAc,WAAW,QAAQ;AAAA,cAC9C,WAAU;AAAA,cACV,SAAQ;AAAA;AAAA,UACV;AAAA,UAEF,6CAAC,SAAI,WAAU,2BACb;AAAA,wDAAC,OAAE,WAAU,4BAA4B,kBAAQ,OAAM;AAAA,YACvD,4CAAC,OAAE,WAAU,4BACV,wCAAY,QAAQ,WAAW,gBAAgB,QAAQ,QAAQ,GAClE;AAAA,aACF;AAAA,aAdQ,QAAQ,EAelB,CACD,GACH;AAAA,QAEC,aACC,4CAAC,OAAE,WAAU,oBAAmB,MAAK,SAClC,qBACH;AAAA,QAGF;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,SAAS;AAAA,YACT,UAAU;AAAA,YACV,aAAW;AAAA,YAEV,yBACG,cACC,OAAO,aAAa,IAAI,WAAW;AAAA;AAAA,QAC1C;AAAA;AAAA;AAAA,EACF;AAEJ;;;AKpMA,IAAAC,gBAAiD;AACjD,IAAAC,eAOO;AA4KD,IAAAC,sBAAA;AAjKC,SAAS,eAAe,OAA6B;AAC1D,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,SAAS,cAAc;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,EAAE,YAAY,eAAe,IAAI,aAAa;AAAA,IAClD;AAAA,IACA,QAAQ,UAAU,WAAW,UAAU;AAAA,IACvC;AAAA,IACA,YAAY;AAAA,IACZ,SAAS,qBAAqB;AAAA,EAChC,CAAC;AAED,QAAM,CAAC,YAAY,aAAa,QAAI;AAAA,IAClC,oBAAI,IAAI;AAAA,EACV;AACA,QAAM,CAAC,cAAc,eAAe,QAAI,wBAAS,KAAK;AACtD,QAAM,CAAC,WAAW,YAAY,QAAI,wBAAwB,IAAI;AAE9D,QAAM,SACJ,OAAO,WAAW,aAAa,OAAO,OAAO,eAAe,cACvD,OAAO,SACR;AAEN,+BAAU,MAAM;AACd,QAAI,OAAO,WAAW,SAAS;AAC7B,gBAAU,OAAO,KAAK;AACtB;AAAA,IACF;AACA,QACE,OAAO,WAAW,aAClB,OAAO,OAAO,eAAe,aAC7B;AACA;AAAA,QACE,IAAI;AAAA,UACF,yDAAyD,OAAO,OAAO,UAAU;AAAA,QACnF;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,QAAQ,OAAO,CAAC;AAEpB,QAAM,gBAAgB,MAAM,KAAK,WAAW,OAAO,CAAC,EAAE;AAAA,IACpD,CAAC,KAAK,MAAM,MAAM,EAAE;AAAA,IACpB;AAAA,EACF;AAEA,QAAM,aAAa,aACf,+BAAiB,eAAe,OAAO,aAAa,OAAO,WAAW,IACtE,EAAE,OAAO,OAAO,eAAe,GAAG,SAAS,KAAK;AAEpD,QAAM,oBAAgB;AAAA,IACpB,CAAC,WAAmB,YAA4B;AAC9C,oBAAc,CAAC,SAAS;AACtB,cAAM,OAAO,IAAI,IAAI,IAAI;AACzB,cAAM,MAAM,GAAG,SAAS,IAAI,QAAQ,EAAE;AACtC,YAAI,KAAK,IAAI,GAAG,GAAG;AACjB,eAAK,OAAO,GAAG;AAAA,QACjB,OAAO;AAIL,gBAAM,gBAAgB,aAClB,+BAAiB,QAAQ,WAAW,QAAQ,EAAE,IAC9C;AACJ,eAAK,IAAI,KAAK;AAAA,YACZ;AAAA,YACA,WAAW,QAAQ;AAAA,YACnB,UAAU;AAAA,UACZ,CAAC;AAAA,QACH;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,qBAAiB;AAAA,IACrB,CAAC,WAAmB,WAAmB,aAAqB;AAC1D,oBAAc,CAAC,SAAS;AACtB,cAAM,OAAO,IAAI,IAAI,IAAI;AACzB,cAAM,MAAM,GAAG,SAAS,IAAI,SAAS;AACrC,YAAI,YAAY,GAAG;AACjB,eAAK,OAAO,GAAG;AAAA,QACjB,OAAO;AACL,eAAK,IAAI,KAAK,EAAE,WAAW,WAAW,SAAS,CAAC;AAAA,QAClD;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,sBAAkB,2BAAY,YAAY;AAC9C,QAAI,CAAC,UAAU,CAAC,WAAW,MAAO;AAElC,UAAM,QAAyB,MAAM,KAAK,WAAW,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,MACzE,eAAe,EAAE;AAAA,MACjB,UAAU,EAAE;AAAA,MACZ,YAAY;AAAA,QACV,EAAE,KAAK,oBAAoB,OAAO,OAAO,GAAG;AAAA,QAC5C,EAAE,KAAK,qBAAqB,OAAO,OAAO,WAAW;AAAA,MACvD;AAAA,IACF,EAAE;AAEF,oBAAgB,IAAI;AACpB,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,YAAY,KAAK;AAEvB,YAAM,aAAa,MAAM,OAAO,CAAC,KAAK,SAAS;AAC7C,cAAM,UAAU,OAAO,SAAS;AAAA,UAAK,CAAC,MACpC,EAAE,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,aAAa;AAAA,QAC1D;AACA,cAAM,UAAU,SAAS,SAAS,MAAM;AAAA,UACtC,CAAC,MAAM,EAAE,OAAO,KAAK;AAAA,QACvB;AACA,eACE,MAAM,WAAW,SAAS,MAAM,UAAU,GAAG,IAAI,KAAK;AAAA,MAE1D,GAAG,CAAC;AAIJ,qBAAe;AAAA,QACb,UAAU;AAAA,QACV,YAAY,KAAK,MAAM,aAAa,GAAG,IAAI;AAAA,MAC7C,CAAC;AAED,oBAAc,oBAAI,IAAI,CAAC;AAAA,IACzB,SAAS,KAAK;AACZ,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,mBAAa,MAAM,OAAO;AAC1B,gBAAU,KAAK;AAAA,IACjB,UAAE;AACA,sBAAgB,KAAK;AAAA,IACvB;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,MAAI,OAAO,WAAW,WAAW;AAC/B,WACE,8CAAC,SAAI,WAAW,gCAAgC,aAAa,EAAE,IAC7D;AAAA,mDAAC,SAAI,WAAU,kCAAiC;AAAA,MAChD,6CAAC,SAAI,WAAU,qCAAoC;AAAA,OACrD;AAAA,EAEJ;AAEA,MAAI,OAAO,WAAW,QAAS,QAAO;AACtC,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,WACJ,OAAO,SAAS,CAAC,GAAG,WAAW,gBAAgB,gBAAgB;AAEjE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,kCAAkC,aAAa,EAAE;AAAA,MAC5D,MAAK;AAAA,MACL,cAAY,OAAO;AAAA,MAEnB;AAAA,qDAAC,QAAG,WAAU,oBAAoB,iBAAO,OAAM;AAAA,QAE9C,OAAO,iBACN,6CAAC,UAAK,WAAU,6BAA6B,iBAAO,eAAc;AAAA,QAGpE,6CAAC,OAAE,WAAU,2BACV,iBAAO,eAAe,OAAO,cAC1B,UAAU,OAAO,WAAW,SAAI,OAAO,WAAW,WAClD,OAAO,cACL,mBAAmB,OAAO,WAAW,WACrC,OAAO,cACL,gBAAgB,OAAO,WAAW,WAClC,qBACV;AAAA,QAEA,6CAAC,SAAI,WAAU,uDACZ,iBAAO,SAAS,IAAI,CAAC,YAAY;AAChC,gBAAM,iBACJ,QAAQ,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,gBAAgB,KACrD,QAAQ,SAAS,MAAM,CAAC;AAC1B,cAAI,CAAC,eAAgB,QAAO;AAE5B,gBAAM,MAAM,GAAG,QAAQ,EAAE,IAAI,eAAe,EAAE;AAC9C,gBAAM,WAAW,WAAW,IAAI,GAAG;AAEnC,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,WAAW,qDAAqD,WAAW,iCAAiC,EAAE;AAAA,cAE7G;AAAA,wBAAQ,iBACP;AAAA,kBAAC;AAAA;AAAA,oBACC,KAAK,QAAQ,cAAc;AAAA,oBAC3B,KAAK,QAAQ,cAAc,WAAW,QAAQ;AAAA,oBAC9C,WAAU;AAAA,oBACV,SAAQ;AAAA;AAAA,gBACV;AAAA,gBAEF,8CAAC,SAAI,WAAU,2BACb;AAAA,+DAAC,OAAE,WAAU,4BAA4B,kBAAQ,OAAM;AAAA,kBACvD,6CAAC,OAAE,WAAU,4BACV,wCAAY,eAAe,MAAM,QAAQ,QAAQ,GACpD;AAAA,mBACF;AAAA,gBACA,6CAAC,SAAI,WAAU,8BACZ,qBACC,8CAAC,SAAI,WAAU,+BACb;AAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,cAAY,YAAY,QAAQ,KAAK;AAAA,sBACrC,SAAS,MACP;AAAA,wBACE,QAAQ;AAAA,wBACR,eAAe;AAAA,wBACf,SAAS,WAAW;AAAA,sBACtB;AAAA,sBAEH;AAAA;AAAA,kBAED;AAAA,kBACA,6CAAC,UAAM,mBAAS,UAAS;AAAA,kBACzB;AAAA,oBAAC;AAAA;AAAA,sBACC,cAAY,YAAY,QAAQ,KAAK;AAAA,sBACrC,SAAS,MACP;AAAA,wBACE,QAAQ;AAAA,wBACR,eAAe;AAAA,wBACf,SAAS,WAAW;AAAA,sBACtB;AAAA,sBAEH;AAAA;AAAA,kBAED;AAAA,mBACF,IAEA;AAAA,kBAAC;AAAA;AAAA,oBACC,WAAU;AAAA,oBACV,SAAS,MAAM,cAAc,QAAQ,IAAI,cAAc;AAAA,oBACvD,UAAU,CAAC,eAAe;AAAA,oBAEzB,yBAAe,mBAAmB,WAAW;AAAA;AAAA,gBAChD,GAEJ;AAAA;AAAA;AAAA,YAvDK,QAAQ;AAAA,UAwDf;AAAA,QAEJ,CAAC,GACH;AAAA,QAEC,WAAW,WACV,6CAAC,OAAE,WAAU,yBAAwB,MAAK,UACvC,qBAAW,SACd;AAAA,QAGD,aACC,6CAAC,OAAE,WAAU,oBAAmB,MAAK,SAClC,qBACH;AAAA,QAGF;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,SAAS;AAAA,YACT,UAAU,gBAAgB,CAAC,WAAW;AAAA,YACtC,aAAW;AAAA,YAEV,yBACG,cACC,OAAO,aAAa,IAAI,WAAW,OAAO,aAAa;AAAA;AAAA,QAC9D;AAAA;AAAA;AAAA,EACF;AAEJ;;;ACzTA,IAAAC,gBAAiD;AACjD,IAAAC,eAMO;AA6HD,IAAAC,sBAAA;AAxHC,SAAS,aAAa,OAA6B;AACxD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,SAAS,cAAc;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,EAAE,YAAY,eAAe,IAAI,aAAa;AAAA,IAClD;AAAA,IACA,QAAQ,UAAU,WAAW,UAAU;AAAA,IACvC;AAAA,IACA,YAAY;AAAA,IACZ,SAAS,qBAAqB;AAAA,EAChC,CAAC;AAED,QAAM,CAAC,UAAU,WAAW,QAAI,wBAAS,CAAC;AAC1C,QAAM,CAAC,cAAc,eAAe,QAAI,wBAAS,KAAK;AACtD,QAAM,CAAC,WAAW,YAAY,QAAI,wBAAwB,IAAI;AAE9D,QAAM,SACJ,OAAO,WAAW,aAAa,OAAO,OAAO,eAAe,WACvD,OAAO,SACR;AAEN,+BAAU,MAAM;AACd,QAAI,OAAO,WAAW,SAAS;AAC7B,gBAAU,OAAO,KAAK;AACtB;AAAA,IACF;AACA,QACE,OAAO,WAAW,aAClB,OAAO,OAAO,eAAe,UAC7B;AACA;AAAA,QACE,IAAI;AAAA,UACF,oDAAoD,OAAO,OAAO,UAAU;AAAA,QAC9E;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,QAAQ,OAAO,CAAC;AAEpB,QAAM,UAAU,QAAQ,SAAS,CAAC;AAClC,QAAM,YAAY,UACd,WAAW,QAAQ,WAAW,gBAAgB,MAAM,IACpD;AACJ,QAAM,WAAW,SAAS,WAAW,gBAAgB,gBAAgB;AAErE,QAAM,cAAc,aAChB;AAAA,IACE,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,OAAO,eAAe;AAAA,EACxB,IACA,CAAC;AACL,QAAM,aAAa,aACf,4BAAc,OAAO,aAAa,QAAQ,IAC1C;AAEJ,QAAM,sBAAkB,2BAAY,YAAY;AAC9C,QAAI,CAAC,UAAU,CAAC,QAAS;AAEzB,UAAM,UAAU,QAAQ,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,gBAAgB;AACrE,QAAI,CAAC,QAAS;AAEd,UAAM,QAAyB;AAAA,MAC7B;AAAA,QACE,eAAe,QAAQ;AAAA,QACvB;AAAA,QACA,YAAY;AAAA,UACV,EAAE,KAAK,oBAAoB,OAAO,OAAO,GAAG;AAAA,UAC5C,EAAE,KAAK,qBAAqB,OAAO,OAAO,WAAW;AAAA,QACvD;AAAA,MACF;AAAA,IACF;AAEA,oBAAgB,IAAI;AACpB,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,YAAY,KAAK;AAGvB,YAAM,YACJ,YAAY,KAAK,CAAC,OAAO,GAAG,SAAS,UAAU,GAAG,aAAa;AACjE,qBAAe;AAAA,QACb,WAAW,QAAQ;AAAA,QACnB;AAAA,QACA,YAAY,KAAK,MAAM,YAAY,WAAW,GAAG,IAAI;AAAA,MACvD,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,mBAAa,MAAM,OAAO;AAC1B,gBAAU,KAAK;AAAA,IACjB,UAAE;AACA,sBAAgB,KAAK;AAAA,IACvB;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,MAAI,OAAO,WAAW,WAAW;AAC/B,WACE,8CAAC,SAAI,WAAW,gCAAgC,aAAa,EAAE,IAC7D;AAAA,mDAAC,SAAI,WAAU,kCAAiC;AAAA,MAChD,6CAAC,SAAI,WAAU,kCAAiC;AAAA,OAClD;AAAA,EAEJ;AAEA,MAAI,OAAO,WAAW,QAAS,QAAO;AACtC,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,CAAC,QAAS,QAAO;AAErB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,+BAA+B,aAAa,EAAE;AAAA,MACzD,MAAK;AAAA,MACL,cAAY,OAAO;AAAA,MAEnB;AAAA,qDAAC,QAAG,WAAU,oBAAoB,iBAAO,OAAM;AAAA,QAE/C,8CAAC,SAAI,WAAU,iDACZ;AAAA,kBAAQ,iBACP;AAAA,YAAC;AAAA;AAAA,cACC,KAAK,QAAQ,cAAc;AAAA,cAC3B,KAAK,QAAQ,cAAc,WAAW,QAAQ;AAAA,cAC9C,WAAU;AAAA,cACV,SAAQ;AAAA;AAAA,UACV;AAAA,UAEF,8CAAC,SAAI,WAAU,2BACb;AAAA,yDAAC,OAAE,WAAU,4BAA4B,kBAAQ,OAAM;AAAA,YACvD,8CAAC,OAAE,WAAU,4BACV;AAAA,4CAAY,WAAW,QAAQ;AAAA,cAAE;AAAA,eACpC;AAAA,aACF;AAAA,WACF;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,MAAK;AAAA,YACL,cAAW;AAAA,YAEV,sBAAY,IAAI,CAAC,OAChB;AAAA,cAAC;AAAA;AAAA,gBAEC,WAAW,mBAAmB,GAAG,WAAW,4BAA4B,EAAE;AAAA,gBAC1E,MAAK;AAAA,gBAEL;AAAA,gEAAC,UAAK,WAAU,4BAA2B,MAAK,QAC7C;AAAA,uBAAG,KAAK;AAAA,oBAAY;AAAA,qBACvB;AAAA,kBACA,8CAAC,UAAK,WAAU,yBAAwB,MAAK,QAC1C;AAAA,kDAAY,GAAG,WAAW,QAAQ;AAAA,oBAAE;AAAA,qBACvC;AAAA,kBACA,8CAAC,UAAK,WAAU,2BAA0B,MAAK,QAAO;AAAA;AAAA,oBAC9C,GAAG,eAAe,QAAQ,CAAC;AAAA,oBAAE;AAAA,qBACrC;AAAA;AAAA;AAAA,cAZK,GAAG,KAAK;AAAA,YAaf,CACD;AAAA;AAAA,QACH;AAAA,QAEA,8CAAC,SAAI,WAAU,gCACb;AAAA,uDAAC,WAAM,SAAS,UAAU,OAAO,EAAE,IAAI,sBAAQ;AAAA,UAC/C,8CAAC,SAAI,WAAU,+BACb;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,cAAW;AAAA,gBACX,SAAS,MAAM,YAAY,CAAC,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;AAAA,gBACrD;AAAA;AAAA,YAED;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,IAAI,UAAU,OAAO,EAAE;AAAA,gBACvB,MAAK;AAAA,gBACL,KAAK;AAAA,gBACL,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM;AACf,wBAAM,MAAM,SAAS,EAAE,OAAO,OAAO,EAAE;AACvC,sBAAI,CAAC,MAAM,GAAG,KAAK,MAAM,EAAG,aAAY,GAAG;AAAA,gBAC7C;AAAA,gBACA,WAAU;AAAA;AAAA,YACZ;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,cAAW;AAAA,gBACX,SAAS,MAAM,YAAY,CAAC,MAAM,IAAI,CAAC;AAAA,gBACxC;AAAA;AAAA,YAED;AAAA,aACF;AAAA,WACF;AAAA,QAEC,aACC,6CAAC,OAAE,WAAU,oBAAmB,MAAK,SAClC,qBACH;AAAA,QAGF;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,SAAS;AAAA,YACT,UAAU;AAAA,YACV,aAAW;AAAA,YAEV,yBACG,cACC,OAAO,aAAa,IAAI,WAAW,OAAO,QAAQ;AAAA;AAAA,QACzD;AAAA;AAAA;AAAA,EACF;AAEJ;;;AC/NA,IAAAC,gBAAoC;AACpC,IAAAC,eAIO;AAgBP,IAAMC,iBAA4C;AAAA,EAChD,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AACT;AAEO,SAAS,qBACd,SAC4B;AAC5B,QAAM,CAAC,OAAO,QAAQ,QAAI;AAAA,IACxBA;AAAA,EACF;AAEA,+BAAU,MAAM;AACd,UAAM,aAAa,IAAI,gBAAgB;AACvC,aAASA,cAAa;AAEtB,6CAAuB;AAAA,MACrB,YAAY,QAAQ;AAAA,MACpB,uBAAuB,QAAQ;AAAA,MAC/B,eAAe,QAAQ;AAAA,MACvB,SAAS,QAAQ;AAAA,MACjB,QAAQ,WAAW;AAAA,IACrB,CAAC,EACE,KAAK,CAAC,YAAY;AACjB,UAAI,WAAW,OAAO,QAAS;AAC/B,eAAS,EAAE,QAAQ,WAAW,SAAS,OAAO,KAAK,CAAC;AAAA,IACtD,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,UAAI,WAAW,OAAO,QAAS;AAC/B,eAAS;AAAA,QACP,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAAA,MAC3D,CAAC;AAAA,IACH,CAAC;AAIH,uBAAmB;AAAA,MACjB,YAAY,QAAQ;AAAA,MACpB,uBAAuB,QAAQ;AAAA,MAC/B,QAAQ,WAAW;AAAA,IACrB,CAAC,EACE,KAAK,CAAC,QAAQ;AACb,UAAI,WAAW,OAAO,QAAS;AAC/B,wCAAgB,QAAQ,YAAY,GAAG;AAAA,IACzC,CAAC,EACA,MAAM,MAAM;AAAA,IAEb,CAAC;AAEH,WAAO,MAAM;AACX,iBAAW,MAAM;AAAA,IACnB;AAAA,EACF,GAAG;AAAA,IACD,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV,CAAC;AAED,SAAO;AACT;;;ARnDA,IAAAC,eAAqD;AAMrD,IAAAA,gBA0CO;","names":["import_react","import_core","import_core","import_core","import_react","import_core","import_react","import_core","import_jsx_runtime","import_react","import_core","import_jsx_runtime","import_react","import_core","INITIAL_STATE","import_core"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -133,7 +133,8 @@ interface UseAnalyticsOptions {
|
|
|
133
133
|
declare function useAnalytics(options: UseAnalyticsOptions): {
|
|
134
134
|
elementRef: (el: HTMLElement | null) => void;
|
|
135
135
|
trackAddToCart: (event: {
|
|
136
|
-
|
|
136
|
+
/** Optional — omit for multi-product bundles (fixed, mix-match). */
|
|
137
|
+
productId?: string;
|
|
137
138
|
quantity: number;
|
|
138
139
|
totalPrice: number;
|
|
139
140
|
}) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -133,7 +133,8 @@ interface UseAnalyticsOptions {
|
|
|
133
133
|
declare function useAnalytics(options: UseAnalyticsOptions): {
|
|
134
134
|
elementRef: (el: HTMLElement | null) => void;
|
|
135
135
|
trackAddToCart: (event: {
|
|
136
|
-
|
|
136
|
+
/** Optional — omit for multi-product bundles (fixed, mix-match). */
|
|
137
|
+
productId?: string;
|
|
137
138
|
quantity: number;
|
|
138
139
|
totalPrice: number;
|
|
139
140
|
}) => void;
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { useCallback as useCallback2, useEffect as useEffect3, useState as useState3 } from "react";
|
|
3
3
|
import {
|
|
4
4
|
formatMoney,
|
|
5
|
-
calculateDiscount
|
|
5
|
+
calculateDiscount,
|
|
6
|
+
resolveBundleQty
|
|
6
7
|
} from "@lime-bundles/core";
|
|
7
8
|
|
|
8
9
|
// src/hooks/useBundleData.ts
|
|
@@ -230,33 +231,42 @@ function FixedBundle(props) {
|
|
|
230
231
|
}, [result, onError]);
|
|
231
232
|
const handleAddToCart = useCallback2(async () => {
|
|
232
233
|
if (!bundle) return;
|
|
233
|
-
const
|
|
234
|
+
const bundleLines = bundle.products.filter((p) => p.variants.nodes.some((v) => v.availableForSale)).map((p) => {
|
|
234
235
|
const variant = p.variants.nodes.find((v) => v.availableForSale);
|
|
235
236
|
return {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
{ key: "_lime_bundle_gid", value: bundle.id },
|
|
240
|
-
{ key: "_lime_bundle_type", value: bundle.bundleType }
|
|
241
|
-
]
|
|
237
|
+
productId: p.id,
|
|
238
|
+
variant,
|
|
239
|
+
quantity: resolveBundleQty(bundle, p.id, variant.id)
|
|
242
240
|
};
|
|
243
|
-
});
|
|
244
|
-
if (
|
|
241
|
+
}).filter((line) => line.quantity > 0);
|
|
242
|
+
if (bundleLines.length === 0) return;
|
|
243
|
+
const lines = bundleLines.map((line) => ({
|
|
244
|
+
merchandiseId: line.variant.id,
|
|
245
|
+
quantity: line.quantity,
|
|
246
|
+
attributes: [
|
|
247
|
+
{ key: "_lime_bundle_gid", value: bundle.id },
|
|
248
|
+
{ key: "_lime_bundle_type", value: bundle.bundleType }
|
|
249
|
+
]
|
|
250
|
+
}));
|
|
245
251
|
setAddingToCart(true);
|
|
246
252
|
setCartError(null);
|
|
247
253
|
try {
|
|
248
254
|
await onAddToCart(lines);
|
|
249
|
-
const totalPrice =
|
|
250
|
-
const price = parseFloat(
|
|
251
|
-
|
|
255
|
+
const totalPrice = bundleLines.reduce((sum, line) => {
|
|
256
|
+
const price = parseFloat(line.variant.price.amount);
|
|
257
|
+
const discounted = calculateDiscount(
|
|
252
258
|
price,
|
|
253
259
|
bundle.discountConfig.discountType,
|
|
254
260
|
bundle.discountConfig.discountValue
|
|
255
261
|
);
|
|
262
|
+
return sum + discounted * line.quantity;
|
|
256
263
|
}, 0);
|
|
264
|
+
const totalQuantity = bundleLines.reduce(
|
|
265
|
+
(sum, line) => sum + line.quantity,
|
|
266
|
+
0
|
|
267
|
+
);
|
|
257
268
|
trackAddToCart({
|
|
258
|
-
|
|
259
|
-
quantity: 1,
|
|
269
|
+
quantity: totalQuantity,
|
|
260
270
|
totalPrice: Math.round(totalPrice * 100) / 100
|
|
261
271
|
});
|
|
262
272
|
} catch (err) {
|
|
@@ -321,7 +331,8 @@ function FixedBundle(props) {
|
|
|
321
331
|
import { useCallback as useCallback3, useEffect as useEffect4, useState as useState4 } from "react";
|
|
322
332
|
import {
|
|
323
333
|
formatMoney as formatMoney2,
|
|
324
|
-
validateQuantity
|
|
334
|
+
validateQuantity,
|
|
335
|
+
resolveBundleQty as resolveBundleQty2
|
|
325
336
|
} from "@lime-bundles/core";
|
|
326
337
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
327
338
|
function MixMatchBundle(props) {
|
|
@@ -379,12 +390,17 @@ function MixMatchBundle(props) {
|
|
|
379
390
|
if (next.has(key)) {
|
|
380
391
|
next.delete(key);
|
|
381
392
|
} else {
|
|
382
|
-
|
|
393
|
+
const configuredQty = bundle ? resolveBundleQty2(bundle, productId, variant.id) : 1;
|
|
394
|
+
next.set(key, {
|
|
395
|
+
productId,
|
|
396
|
+
variantId: variant.id,
|
|
397
|
+
quantity: configuredQty
|
|
398
|
+
});
|
|
383
399
|
}
|
|
384
400
|
return next;
|
|
385
401
|
});
|
|
386
402
|
},
|
|
387
|
-
[]
|
|
403
|
+
[bundle]
|
|
388
404
|
);
|
|
389
405
|
const updateQuantity = useCallback3(
|
|
390
406
|
(productId, variantId, quantity) => {
|
|
@@ -425,7 +441,6 @@ function MixMatchBundle(props) {
|
|
|
425
441
|
return sum + parseFloat(variant?.price.amount ?? "0") * line.quantity;
|
|
426
442
|
}, 0);
|
|
427
443
|
trackAddToCart({
|
|
428
|
-
productId: bundle.products[0]?.id ?? "",
|
|
429
444
|
quantity: totalQuantity,
|
|
430
445
|
totalPrice: Math.round(totalPrice * 100) / 100
|
|
431
446
|
});
|
|
@@ -597,7 +612,12 @@ function VolumeBundle(props) {
|
|
|
597
612
|
const product = bundle?.products[0];
|
|
598
613
|
const basePrice = product ? parseFloat(product.priceRange.minVariantPrice.amount) : 0;
|
|
599
614
|
const currency = product?.priceRange.minVariantPrice.currencyCode ?? "USD";
|
|
600
|
-
const tierSavings = bundle ? calculateTierSavings(
|
|
615
|
+
const tierSavings = bundle ? calculateTierSavings(
|
|
616
|
+
bundle.volumeTiers,
|
|
617
|
+
basePrice,
|
|
618
|
+
quantity,
|
|
619
|
+
bundle.discountConfig.discountType
|
|
620
|
+
) : [];
|
|
601
621
|
const activeTier = bundle ? getActiveTier(bundle.volumeTiers, quantity) : null;
|
|
602
622
|
const handleAddToCart = useCallback4(async () => {
|
|
603
623
|
if (!bundle || !product) return;
|
|
@@ -617,7 +637,7 @@ function VolumeBundle(props) {
|
|
|
617
637
|
setCartError(null);
|
|
618
638
|
try {
|
|
619
639
|
await onAddToCart(lines);
|
|
620
|
-
const unitPrice =
|
|
640
|
+
const unitPrice = tierSavings.find((ts) => ts.tier === activeTier)?.unitPrice ?? basePrice;
|
|
621
641
|
trackAddToCart({
|
|
622
642
|
productId: product.id,
|
|
623
643
|
quantity,
|
|
@@ -700,8 +720,7 @@ function VolumeBundle(props) {
|
|
|
700
720
|
"Save ",
|
|
701
721
|
ts.savingsPercent.toFixed(0),
|
|
702
722
|
"%"
|
|
703
|
-
] })
|
|
704
|
-
ts.tier.label && /* @__PURE__ */ jsx3("span", { className: "lb-bundle__tier-label", role: "cell", children: ts.tier.label })
|
|
723
|
+
] })
|
|
705
724
|
]
|
|
706
725
|
},
|
|
707
726
|
ts.tier.minQuantity
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/FixedBundle.tsx","../src/hooks/useBundleData.ts","../src/fetchBundleData.ts","../src/fetchShopCustomCss.ts","../src/hooks/useAnalytics.ts","../src/components/MixMatchBundle.tsx","../src/components/VolumeBundle.tsx","../src/hooks/useBundlesForProduct.ts","../src/index.ts"],"sourcesContent":["/**\n * Fixed bundle component — pre-selected products with a shared discount.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n calculateDiscount,\n type CartLineInput,\n type FixedBundleData,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\nexport function FixedBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"fixed\",\n enabled: analyticsEnabled !== false,\n });\n\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"fixed\"\n ? (result.bundle as FixedBundleData)\n : null;\n\n // Surface load/narrowing errors via onError — in a useEffect so we don't\n // fire side effects during render.\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"fixed\"\n ) {\n onError?.(\n new Error(\n `FixedBundle: expected bundleType=\"fixed\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle) return;\n\n const lines: CartLineInput[] = bundle.products\n .filter((p) => p.variants.nodes.some((v) => v.availableForSale))\n .map((p) => {\n const variant = p.variants.nodes.find((v) => v.availableForSale)!;\n return {\n merchandiseId: variant.id,\n quantity: 1,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n };\n });\n\n if (lines.length === 0) return;\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n const totalPrice = bundle.products.reduce((sum, p) => {\n const price = parseFloat(p.priceRange.minVariantPrice.amount);\n return (\n sum +\n calculateDiscount(\n price,\n bundle.discountConfig.discountType,\n bundle.discountConfig.discountValue,\n )\n );\n }, 0);\n trackAddToCart({\n productId: bundle.products[0]?.id ?? \"\",\n quantity: 1,\n totalPrice: Math.round(totalPrice * 100) / 100,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [bundle, onAddToCart, onError, trackAddToCart]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--products\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n // Bundle loaded but wasn't a \"fixed\" variant — render nothing; the\n // useEffect above has already invoked onError with a descriptive message.\n if (!bundle) return null;\n\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--fixed ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n {bundle.discountLabel && (\n <span className=\"lb-bundle__discount-badge\">{bundle.discountLabel}</span>\n )}\n\n <div className=\"lb-bundle__products\">\n {bundle.products.map((product) => (\n <div key={product.id} className=\"lb-bundle__product\" part=\"product\">\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(product.priceRange.minVariantPrice.amount, currency)}\n </p>\n </div>\n </div>\n ))}\n </div>\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? \"Add Bundle to Cart\")}\n </button>\n </div>\n );\n}\n","/**\n * Client-side React hook wrapping `fetchBundleData` with loading / error\n * state + abort-on-unmount.\n *\n * For SSR / RSC contexts (Hydrogen loaders, Next.js server components),\n * prefer `fetchBundleData` directly and pass the result to a client\n * component as a prop — the hook's only value-add over the async\n * function is React state management.\n *\n * `UseBundleDataResult` is a discriminated union locked at v1.0.0:\n * narrowing via `status` is safe. Widening or adding states is a\n * breaking change.\n */\nimport { useState, useEffect } from \"react\";\nimport { injectCustomCss, type ParsedBundle } from \"@lime-bundles/core\";\nimport { fetchBundleData } from \"../fetchBundleData\";\nimport { fetchShopCustomCss } from \"../fetchShopCustomCss\";\n\nexport interface UseBundleDataOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n bundleGid: string;\n}\n\nexport type UseBundleDataResult =\n | { status: \"loading\"; bundle: null; error: null }\n | { status: \"success\"; bundle: ParsedBundle; error: null }\n | { status: \"error\"; bundle: null; error: Error };\n\nconst INITIAL_STATE: UseBundleDataResult = {\n status: \"loading\",\n bundle: null,\n error: null,\n};\n\nexport function useBundleData(\n options: UseBundleDataOptions,\n): UseBundleDataResult {\n const [state, setState] = useState<UseBundleDataResult>(INITIAL_STATE);\n\n useEffect(() => {\n const controller = new AbortController();\n setState(INITIAL_STATE);\n\n fetchBundleData({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n bundleGid: options.bundleGid,\n signal: controller.signal,\n })\n .then((bundle) => {\n if (controller.signal.aborted) return;\n setState({ status: \"success\", bundle, error: null });\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return;\n setState({\n status: \"error\",\n bundle: null,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n });\n\n // Fire-and-forget custom CSS fetch — best-effort, failures never block\n // bundle rendering.\n fetchShopCustomCss({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n signal: controller.signal,\n })\n .then((css) => {\n if (controller.signal.aborted) return;\n injectCustomCss(options.shopDomain, css);\n })\n .catch(() => {\n // Silent — no merchant CSS is fine.\n });\n\n return () => {\n controller.abort();\n };\n }, [\n options.shopDomain,\n options.storefrontAccessToken,\n options.bundleGid,\n ]);\n\n return state;\n}\n","/**\n * Framework-agnostic async fetcher for bundle data.\n *\n * Primary path for Next.js App Router / RSC and Hydrogen loaders — call\n * this in a server-side context and pass the result as a prop to a\n * `\"use client\"` component. `useBundleData` is NOT needed in that\n * component because the data is already in hand.\n *\n * Always forward the end-buyer's IP via `buyerIp` when calling from SSR:\n * Shopify may return 430 Security Rejection for server-originated traffic\n * without it.\n *\n * Throws:\n * - `StorefrontApiError` on network / HTTP / GraphQL errors\n * - `BundleParseError` when the metaobject is missing, invalid type,\n * inactive, not yet started, or expired\n */\nimport {\n createStorefrontClient,\n BUNDLE_METAOBJECT_QUERY,\n parseMetaobjectBundleStrict,\n BundleParseError,\n type ParsedBundle,\n type BundleMetaobjectResponse,\n} from \"@lime-bundles/core\";\n\nexport interface FetchBundleDataOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n bundleGid: string;\n /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR. */\n buyerIp?: string;\n /** Abort the in-flight request (used by `useBundleData` to cancel on unmount). */\n signal?: AbortSignal;\n /** Override the Storefront API version pinned in `createStorefrontClient`. */\n apiVersion?: string;\n}\n\nexport async function fetchBundleData(\n options: FetchBundleDataOptions,\n): Promise<ParsedBundle> {\n const client = createStorefrontClient({\n shopDomain: options.shopDomain,\n accessToken: options.storefrontAccessToken,\n buyerIp: options.buyerIp,\n apiVersion: options.apiVersion,\n });\n\n const data = await client.query<BundleMetaobjectResponse>(\n BUNDLE_METAOBJECT_QUERY,\n { id: options.bundleGid },\n { signal: options.signal },\n );\n\n if (!data.metaobject) {\n throw new BundleParseError(\n `Bundle not found: ${options.bundleGid}`,\n \"not_found\",\n );\n }\n\n return parseMetaobjectBundleStrict(\n data.metaobject.id,\n data.metaobject.fields,\n );\n}\n","/**\n * Best-effort fetch for the shop's `$app:custom_css` metafield.\n *\n * Returns the raw CSS string (still needs sanitization before injection —\n * see `injectCustomCss` from core) or null if the metafield is unset or\n * the request fails.\n *\n * **Per-shop in-memory cache.** Multiple `<FixedBundle>` / `<VolumeBundle>`\n * / `<MixMatchBundle>` components on the same page share one network\n * request — the first caller fetches, concurrent callers await the\n * same promise, and resolved values are cached so subsequent mounts\n * return instantly. Failures are NOT cached so the next caller retries.\n *\n * The cache lives for the life of the JS module (typically one page\n * load). Merchants updating their CSS see changes on the next page\n * load. The cache is exposed as `__clearShopCssCache` for test use only.\n */\nimport {\n createStorefrontClient,\n SHOP_CUSTOM_CSS_QUERY,\n type ShopCustomCssResponse,\n} from \"@lime-bundles/core\";\n\nexport interface FetchShopCustomCssOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n buyerIp?: string;\n signal?: AbortSignal;\n}\n\n// Cache map: key = shopDomain, value = pending or resolved promise.\n// Failures delete their own entry so retries get a fresh fetch.\nconst cache = new Map<string, Promise<string | null>>();\n\nexport async function fetchShopCustomCss(\n options: FetchShopCustomCssOptions,\n): Promise<string | null> {\n const cached = cache.get(options.shopDomain);\n if (cached) return cached;\n\n const promise = (async () => {\n const client = createStorefrontClient({\n shopDomain: options.shopDomain,\n accessToken: options.storefrontAccessToken,\n buyerIp: options.buyerIp,\n });\n\n const data = await client.query<ShopCustomCssResponse>(\n SHOP_CUSTOM_CSS_QUERY,\n undefined,\n { signal: options.signal },\n );\n\n return data.shop?.metafield?.value ?? null;\n })();\n\n // Cache the promise synchronously so concurrent callers dedupe.\n cache.set(options.shopDomain, promise);\n\n // Evict on failure so the next caller can retry.\n promise.catch(() => {\n if (cache.get(options.shopDomain) === promise) {\n cache.delete(options.shopDomain);\n }\n });\n\n return promise;\n}\n\n/**\n * Test-only helper. Clears the per-shop CSS cache so each test starts\n * from a known state. Do not call from application code.\n */\nexport function __clearShopCssCache(): void {\n cache.clear();\n}\n","/**\n * Hook for analytics event reporting (impressions + add-to-cart).\n * Impressions fire once per mount using IntersectionObserver.\n */\nimport { useEffect, useMemo, useRef, useCallback, useState } from \"react\";\nimport {\n reportImpression,\n reportAddToCart,\n observeImpression,\n type AnalyticsConfig,\n} from \"@lime-bundles/core\";\n\nexport interface UseAnalyticsOptions {\n shopDomain: string;\n appUrl: string;\n bundleGid: string;\n bundleType: string;\n enabled?: boolean;\n abTestId?: string;\n abVariant?: string;\n}\n\nexport function useAnalytics(options: UseAnalyticsOptions) {\n const impressionFiredRef = useRef(false);\n // Callback ref + state so the observer effect re-runs when the host element\n // is attached (e.g. after a `loading` branch swaps in the real DOM node).\n const [element, setElement] = useState<HTMLElement | null>(null);\n const elementRef = useCallback((el: HTMLElement | null) => {\n setElement(el);\n }, []);\n\n const config: AnalyticsConfig = useMemo(\n () => ({ shopDomain: options.shopDomain, appUrl: options.appUrl }),\n [options.shopDomain, options.appUrl],\n );\n\n // Reset the fired flag when bundle identity changes so a reused component\n // (e.g. via navigation) can report a fresh impression.\n useEffect(() => {\n impressionFiredRef.current = false;\n }, [options.bundleGid, options.abVariant]);\n\n // Set up impression observer once the element is attached\n useEffect(() => {\n if (options.enabled === false) return;\n if (impressionFiredRef.current) return;\n if (!element) return;\n\n const cleanup = observeImpression(element, () => {\n if (impressionFiredRef.current) return;\n impressionFiredRef.current = true;\n\n reportImpression(config, {\n bundleGid: options.bundleGid,\n bundleType: options.bundleType,\n abTestId: options.abTestId,\n abVariant: options.abVariant,\n });\n });\n\n return cleanup;\n }, [\n element,\n config,\n options.enabled,\n options.bundleGid,\n options.bundleType,\n options.abTestId,\n options.abVariant,\n ]);\n\n const trackAddToCart = useCallback(\n (event: {\n productId: string;\n quantity: number;\n totalPrice: number;\n }) => {\n if (options.enabled === false) return;\n\n reportAddToCart(config, {\n bundleGid: options.bundleGid,\n bundleType: options.bundleType,\n productId: event.productId,\n quantity: event.quantity,\n totalPrice: event.totalPrice,\n abTestId: options.abTestId,\n abVariant: options.abVariant,\n });\n },\n [\n config,\n options.enabled,\n options.bundleGid,\n options.bundleType,\n options.abTestId,\n options.abVariant,\n ],\n );\n\n return { elementRef, trackAddToCart };\n}\n","/**\n * Mix-and-match bundle component — customer picks products from a pool.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n validateQuantity,\n type CartLineInput,\n type MixMatchBundleData,\n type ProductVariant,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\ninterface SelectedItem {\n productId: string;\n variantId: string;\n quantity: number;\n}\n\nexport function MixMatchBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"mix_match\",\n enabled: analyticsEnabled !== false,\n });\n\n const [selections, setSelections] = useState<Map<string, SelectedItem>>(\n new Map(),\n );\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"mix_match\"\n ? (result.bundle as MixMatchBundleData)\n : null;\n\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"mix_match\"\n ) {\n onError?.(\n new Error(\n `MixMatchBundle: expected bundleType=\"mix_match\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const totalQuantity = Array.from(selections.values()).reduce(\n (sum, s) => sum + s.quantity,\n 0,\n );\n\n const validation = bundle\n ? validateQuantity(totalQuantity, bundle.minQuantity, bundle.maxQuantity)\n : { valid: false, totalQuantity: 0, message: null };\n\n const toggleProduct = useCallback(\n (productId: string, variant: ProductVariant) => {\n setSelections((prev) => {\n const next = new Map(prev);\n const key = `${productId}:${variant.id}`;\n if (next.has(key)) {\n next.delete(key);\n } else {\n next.set(key, { productId, variantId: variant.id, quantity: 1 });\n }\n return next;\n });\n },\n [],\n );\n\n const updateQuantity = useCallback(\n (productId: string, variantId: string, quantity: number) => {\n setSelections((prev) => {\n const next = new Map(prev);\n const key = `${productId}:${variantId}`;\n if (quantity <= 0) {\n next.delete(key);\n } else {\n next.set(key, { productId, variantId, quantity });\n }\n return next;\n });\n },\n [],\n );\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle || !validation.valid) return;\n\n const lines: CartLineInput[] = Array.from(selections.values()).map((s) => ({\n merchandiseId: s.variantId,\n quantity: s.quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n }));\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n\n const totalPrice = lines.reduce((sum, line) => {\n const product = bundle.products.find((p) =>\n p.variants.nodes.some((v) => v.id === line.merchandiseId),\n );\n const variant = product?.variants.nodes.find(\n (v) => v.id === line.merchandiseId,\n );\n return (\n sum + parseFloat(variant?.price.amount ?? \"0\") * line.quantity\n );\n }, 0);\n\n trackAddToCart({\n productId: bundle.products[0]?.id ?? \"\",\n quantity: totalQuantity,\n totalPrice: Math.round(totalPrice * 100) / 100,\n });\n\n setSelections(new Map());\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [\n bundle,\n selections,\n validation.valid,\n onAddToCart,\n onError,\n trackAddToCart,\n totalQuantity,\n ]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--products\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n if (!bundle) return null;\n\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--mix-match ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n {bundle.discountLabel && (\n <span className=\"lb-bundle__discount-badge\">{bundle.discountLabel}</span>\n )}\n\n <p className=\"lb-bundle__instructions\">\n {bundle.minQuantity && bundle.maxQuantity\n ? `Select ${bundle.minQuantity}–${bundle.maxQuantity} items`\n : bundle.minQuantity\n ? `Select at least ${bundle.minQuantity} items`\n : bundle.maxQuantity\n ? `Select up to ${bundle.maxQuantity} items`\n : \"Select your items\"}\n </p>\n\n <div className=\"lb-bundle__products lb-bundle__products--selectable\">\n {bundle.products.map((product) => {\n const defaultVariant =\n product.variants.nodes.find((v) => v.availableForSale) ??\n product.variants.nodes[0];\n if (!defaultVariant) return null;\n\n const key = `${product.id}:${defaultVariant.id}`;\n const selected = selections.get(key);\n\n return (\n <div\n key={product.id}\n className={`lb-bundle__product lb-bundle__product--selectable ${selected ? \"lb-bundle__product--selected\" : \"\"}`}\n >\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(defaultVariant.price.amount, currency)}\n </p>\n </div>\n <div className=\"lb-bundle__product-actions\">\n {selected ? (\n <div className=\"lb-bundle__quantity-control\">\n <button\n aria-label={`Decrease ${product.title}`}\n onClick={() =>\n updateQuantity(\n product.id,\n defaultVariant.id,\n selected.quantity - 1,\n )\n }\n >\n −\n </button>\n <span>{selected.quantity}</span>\n <button\n aria-label={`Increase ${product.title}`}\n onClick={() =>\n updateQuantity(\n product.id,\n defaultVariant.id,\n selected.quantity + 1,\n )\n }\n >\n +\n </button>\n </div>\n ) : (\n <button\n className=\"lb-bundle__select-btn\"\n onClick={() => toggleProduct(product.id, defaultVariant)}\n disabled={!defaultVariant.availableForSale}\n >\n {defaultVariant.availableForSale ? \"Select\" : \"Sold out\"}\n </button>\n )}\n </div>\n </div>\n );\n })}\n </div>\n\n {validation.message && (\n <p className=\"lb-bundle__validation\" role=\"status\">\n {validation.message}\n </p>\n )}\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart || !validation.valid}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? `Add ${totalQuantity} Items to Cart`)}\n </button>\n </div>\n );\n}\n","/**\n * Volume bundle component — quantity selector with tier calculator.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n calculateTierSavings,\n getActiveTier,\n type CartLineInput,\n type VolumeBundleData,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\nexport function VolumeBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"volume\",\n enabled: analyticsEnabled !== false,\n });\n\n const [quantity, setQuantity] = useState(1);\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"volume\"\n ? (result.bundle as VolumeBundleData)\n : null;\n\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"volume\"\n ) {\n onError?.(\n new Error(\n `VolumeBundle: expected bundleType=\"volume\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const product = bundle?.products[0];\n const basePrice = product\n ? parseFloat(product.priceRange.minVariantPrice.amount)\n : 0;\n const currency = product?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n const tierSavings = bundle\n ? calculateTierSavings(bundle.volumeTiers, basePrice, quantity)\n : [];\n const activeTier = bundle\n ? getActiveTier(bundle.volumeTiers, quantity)\n : null;\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle || !product) return;\n\n const variant = product.variants.nodes.find((v) => v.availableForSale);\n if (!variant) return;\n\n const lines: CartLineInput[] = [\n {\n merchandiseId: variant.id,\n quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n },\n ];\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n const unitPrice = activeTier\n ? basePrice * (1 - activeTier.discountValue / 100)\n : basePrice;\n trackAddToCart({\n productId: product.id,\n quantity,\n totalPrice: Math.round(unitPrice * quantity * 100) / 100,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [\n bundle,\n product,\n quantity,\n activeTier,\n basePrice,\n onAddToCart,\n onError,\n trackAddToCart,\n ]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--tiers\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n if (!bundle) return null;\n if (!product) return null;\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--volume ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n <div className=\"lb-bundle__product lb-bundle__product--volume\">\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(basePrice, currency)} each\n </p>\n </div>\n </div>\n\n <div\n className=\"lb-bundle__tiers\"\n role=\"table\"\n aria-label=\"Volume discounts\"\n >\n {tierSavings.map((ts) => (\n <div\n key={ts.tier.minQuantity}\n className={`lb-bundle__tier ${ts.isActive ? \"lb-bundle__tier--active\" : \"\"}`}\n role=\"row\"\n >\n <span className=\"lb-bundle__tier-quantity\" role=\"cell\">\n {ts.tier.minQuantity}+ items\n </span>\n <span className=\"lb-bundle__tier-price\" role=\"cell\">\n {formatMoney(ts.unitPrice, currency)} each\n </span>\n <span className=\"lb-bundle__tier-savings\" role=\"cell\">\n Save {ts.savingsPercent.toFixed(0)}%\n </span>\n {ts.tier.label && (\n <span className=\"lb-bundle__tier-label\" role=\"cell\">\n {ts.tier.label}\n </span>\n )}\n </div>\n ))}\n </div>\n\n <div className=\"lb-bundle__quantity-selector\">\n <label htmlFor={`lb-qty-${bundle.id}`}>Quantity</label>\n <div className=\"lb-bundle__quantity-control\">\n <button\n aria-label=\"Decrease quantity\"\n onClick={() => setQuantity((q) => Math.max(1, q - 1))}\n >\n −\n </button>\n <input\n id={`lb-qty-${bundle.id}`}\n type=\"number\"\n min={1}\n value={quantity}\n onChange={(e) => {\n const val = parseInt(e.target.value, 10);\n if (!isNaN(val) && val > 0) setQuantity(val);\n }}\n className=\"lb-bundle__quantity-input\"\n />\n <button\n aria-label=\"Increase quantity\"\n onClick={() => setQuantity((q) => q + 1)}\n >\n +\n </button>\n </div>\n </div>\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? `Add ${quantity} to Cart`)}\n </button>\n </div>\n );\n}\n","/**\n * Client-side React hook wrapping `fetchBundlesForProduct` — the\n * React-SDK analogue of the web component's auto-detect-by-current-product\n * mode. The web component reads the handle from `location.pathname`; a\n * React app already has it on the router, so the hook takes it as an\n * explicit `productHandle` option.\n *\n * Returns every active bundle configured against the product. Inactive,\n * draft, scheduled-for-later, and expired bundles are filtered out by\n * the core helper — consumers don't have to guard.\n *\n * For SSR / RSC contexts (Hydrogen loaders, Next.js server components),\n * prefer `fetchBundlesForProduct` directly and pass the list to a client\n * component as a prop. This hook's only value-add over the async\n * function is React state management + abort-on-unmount.\n *\n * `UseBundlesForProductResult` is a discriminated union locked at v2.0.0:\n * narrowing via `status` is safe. Widening or adding states is a\n * breaking change.\n */\nimport { useState, useEffect } from \"react\";\nimport {\n fetchBundlesForProduct,\n injectCustomCss,\n type ParsedBundle,\n} from \"@lime-bundles/core\";\nimport { fetchShopCustomCss } from \"../fetchShopCustomCss\";\n\nexport interface UseBundlesForProductOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n productHandle: string;\n /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR paths. */\n buyerIp?: string;\n}\n\nexport type UseBundlesForProductResult =\n | { status: \"loading\"; bundles: null; error: null }\n | { status: \"success\"; bundles: ParsedBundle[]; error: null }\n | { status: \"error\"; bundles: null; error: Error };\n\nconst INITIAL_STATE: UseBundlesForProductResult = {\n status: \"loading\",\n bundles: null,\n error: null,\n};\n\nexport function useBundlesForProduct(\n options: UseBundlesForProductOptions,\n): UseBundlesForProductResult {\n const [state, setState] = useState<UseBundlesForProductResult>(\n INITIAL_STATE,\n );\n\n useEffect(() => {\n const controller = new AbortController();\n setState(INITIAL_STATE);\n\n fetchBundlesForProduct({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n productHandle: options.productHandle,\n buyerIp: options.buyerIp,\n signal: controller.signal,\n })\n .then((bundles) => {\n if (controller.signal.aborted) return;\n setState({ status: \"success\", bundles, error: null });\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return;\n setState({\n status: \"error\",\n bundles: null,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n });\n\n // Fire-and-forget custom CSS fetch so storefronts using this hook get\n // merchant styling automatically — same behaviour as `useBundleData`.\n fetchShopCustomCss({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n signal: controller.signal,\n })\n .then((css) => {\n if (controller.signal.aborted) return;\n injectCustomCss(options.shopDomain, css);\n })\n .catch(() => {\n // Silent — no merchant CSS is fine.\n });\n\n return () => {\n controller.abort();\n };\n }, [\n options.shopDomain,\n options.storefrontAccessToken,\n options.productHandle,\n options.buyerIp,\n ]);\n\n return state;\n}\n","// Bundle components — BYO-cart via `onAddToCart`.\nexport { FixedBundle } from \"./components/FixedBundle\";\nexport { MixMatchBundle } from \"./components/MixMatchBundle\";\nexport { VolumeBundle } from \"./components/VolumeBundle\";\nexport type { BundleComponentProps } from \"./components/types\";\n\n// Data fetching\nexport {\n fetchBundleData,\n type FetchBundleDataOptions,\n} from \"./fetchBundleData\";\nexport {\n fetchShopCustomCss,\n type FetchShopCustomCssOptions,\n} from \"./fetchShopCustomCss\";\nexport {\n useBundleData,\n type UseBundleDataOptions,\n type UseBundleDataResult,\n} from \"./hooks/useBundleData\";\nexport {\n useBundlesForProduct,\n type UseBundlesForProductOptions,\n type UseBundlesForProductResult,\n} from \"./hooks/useBundlesForProduct\";\nexport { useAnalytics } from \"./hooks/useAnalytics\";\n\n// Re-export core types consumers commonly need.\nexport type {\n ParsedBundle,\n FixedBundleData,\n VolumeBundleData,\n MixMatchBundleData,\n BundleType,\n Product,\n ProductVariant,\n CartLineInput,\n WidgetConfig,\n HeaderConfig,\n LayoutConfig,\n ProductListConfig,\n PricingConfig,\n SavingsBarConfig,\n CtaConfig,\n CountdownConfig,\n PopularBadgeConfig,\n VolumeTier,\n DiscountConfig,\n ABVariantOverrides,\n PricingRow,\n FixedBundlePricing,\n ImageTransform,\n} from \"@lime-bundles/core\";\nexport { BundleParseError, StorefrontApiError } from \"@lime-bundles/core\";\n\n// DIY widget primitives — every helper a developer needs to build their\n// own bundle UI with the same data, math, and theming the first-party\n// widget uses. All live in `@lime-bundles/core`; importing from the\n// React package is shorthand for consumers who already pulled `react`.\nexport {\n // Bundle config + variant merging\n WIDGET_CONFIG_DEFAULTS,\n mergeWidgetConfig,\n applyWidgetConfigVars,\n applyABVariantB,\n // Pricing (integer cents, matches Shopify Discount Function rounding)\n parseCents,\n formatCents,\n percentageDiscountUnit,\n computeFixedPricing,\n computeBundleSaleCents,\n // Volume-tier calculation\n calculateTierSavings,\n getActiveTier,\n // Mix-match quantity validation\n validateQuantity,\n // Image / countdown / money helpers\n transformImageUrl,\n formatCountdown,\n formatMoney,\n calculateDiscount,\n // Analytics (optional — FixedBundle / VolumeBundle / MixMatchBundle\n // auto-fire these unless analyticsEnabled is set to false)\n reportImpression,\n reportAddToCart,\n observeImpression,\n // A/B testing\n getABTestAssignment,\n setConsent,\n hasConsent,\n // CSS injection + sanitizer\n injectCustomCss,\n sanitizeCustomCss,\n // Custom-storefront data fetching\n createStorefrontClient,\n fetchBundlesForProduct,\n parseMetaobjectBundle,\n parseMetaobjectBundleStrict,\n BUNDLE_METAOBJECT_QUERY,\n BUNDLES_FOR_PRODUCT_QUERY,\n SHOP_CUSTOM_CSS_QUERY,\n} from \"@lime-bundles/core\";\n"],"mappings":"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","names":["useCallback","useEffect","useState","createStorefrontClient","useEffect","useState","useState","useEffect","useCallback","useCallback","useEffect","useState","formatMoney","jsx","jsxs","useState","useEffect","useCallback","formatMoney","useCallback","useEffect","useState","formatMoney","jsx","jsxs","useState","useEffect","useCallback","formatMoney","useState","useEffect","injectCustomCss","INITIAL_STATE","useState","useEffect","injectCustomCss","BundleParseError","calculateTierSavings","getActiveTier","validateQuantity","formatMoney","calculateDiscount","reportImpression","reportAddToCart","observeImpression","injectCustomCss","createStorefrontClient","fetchBundlesForProduct","parseMetaobjectBundleStrict","BUNDLE_METAOBJECT_QUERY","SHOP_CUSTOM_CSS_QUERY"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/FixedBundle.tsx","../src/hooks/useBundleData.ts","../src/fetchBundleData.ts","../src/fetchShopCustomCss.ts","../src/hooks/useAnalytics.ts","../src/components/MixMatchBundle.tsx","../src/components/VolumeBundle.tsx","../src/hooks/useBundlesForProduct.ts","../src/index.ts"],"sourcesContent":["/**\n * Fixed bundle component — pre-selected products with a shared discount.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n calculateDiscount,\n resolveBundleQty,\n type CartLineInput,\n type FixedBundleData,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\nexport function FixedBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"fixed\",\n enabled: analyticsEnabled !== false,\n });\n\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"fixed\"\n ? (result.bundle as FixedBundleData)\n : null;\n\n // Surface load/narrowing errors via onError — in a useEffect so we don't\n // fire side effects during render.\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"fixed\"\n ) {\n onError?.(\n new Error(\n `FixedBundle: expected bundleType=\"fixed\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle) return;\n\n // Qty resolution (variantQuantities → productQuantities → 1) lives in\n // `@lime-bundles/core` so the five-runtime fallback chain has a single\n // source of truth. See resolveBundleQty + PR #147 history.\n const bundleLines = bundle.products\n .filter((p) => p.variants.nodes.some((v) => v.availableForSale))\n .map((p) => {\n const variant = p.variants.nodes.find((v) => v.availableForSale)!;\n return {\n productId: p.id,\n variant,\n quantity: resolveBundleQty(bundle, p.id, variant.id),\n };\n })\n .filter((line) => line.quantity > 0);\n\n if (bundleLines.length === 0) return;\n\n const lines: CartLineInput[] = bundleLines.map((line) => ({\n merchandiseId: line.variant.id,\n quantity: line.quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n }));\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n const totalPrice = bundleLines.reduce((sum, line) => {\n const price = parseFloat(line.variant.price.amount);\n const discounted = calculateDiscount(\n price,\n bundle.discountConfig.discountType,\n bundle.discountConfig.discountValue,\n );\n return sum + discounted * line.quantity;\n }, 0);\n const totalQuantity = bundleLines.reduce(\n (sum, line) => sum + line.quantity,\n 0,\n );\n // productId is intentionally omitted — a fixed bundle spans multiple\n // products with no single \"page product\"; bundleGid on the event\n // already attributes to the bundle.\n trackAddToCart({\n quantity: totalQuantity,\n totalPrice: Math.round(totalPrice * 100) / 100,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [bundle, onAddToCart, onError, trackAddToCart]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--products\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n // Bundle loaded but wasn't a \"fixed\" variant — render nothing; the\n // useEffect above has already invoked onError with a descriptive message.\n if (!bundle) return null;\n\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--fixed ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n {bundle.discountLabel && (\n <span className=\"lb-bundle__discount-badge\">{bundle.discountLabel}</span>\n )}\n\n <div className=\"lb-bundle__products\">\n {bundle.products.map((product) => (\n <div key={product.id} className=\"lb-bundle__product\" part=\"product\">\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(product.priceRange.minVariantPrice.amount, currency)}\n </p>\n </div>\n </div>\n ))}\n </div>\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? \"Add Bundle to Cart\")}\n </button>\n </div>\n );\n}\n","/**\n * Client-side React hook wrapping `fetchBundleData` with loading / error\n * state + abort-on-unmount.\n *\n * For SSR / RSC contexts (Hydrogen loaders, Next.js server components),\n * prefer `fetchBundleData` directly and pass the result to a client\n * component as a prop — the hook's only value-add over the async\n * function is React state management.\n *\n * `UseBundleDataResult` is a discriminated union locked at v1.0.0:\n * narrowing via `status` is safe. Widening or adding states is a\n * breaking change.\n */\nimport { useState, useEffect } from \"react\";\nimport { injectCustomCss, type ParsedBundle } from \"@lime-bundles/core\";\nimport { fetchBundleData } from \"../fetchBundleData\";\nimport { fetchShopCustomCss } from \"../fetchShopCustomCss\";\n\nexport interface UseBundleDataOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n bundleGid: string;\n}\n\nexport type UseBundleDataResult =\n | { status: \"loading\"; bundle: null; error: null }\n | { status: \"success\"; bundle: ParsedBundle; error: null }\n | { status: \"error\"; bundle: null; error: Error };\n\nconst INITIAL_STATE: UseBundleDataResult = {\n status: \"loading\",\n bundle: null,\n error: null,\n};\n\nexport function useBundleData(\n options: UseBundleDataOptions,\n): UseBundleDataResult {\n const [state, setState] = useState<UseBundleDataResult>(INITIAL_STATE);\n\n useEffect(() => {\n const controller = new AbortController();\n setState(INITIAL_STATE);\n\n fetchBundleData({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n bundleGid: options.bundleGid,\n signal: controller.signal,\n })\n .then((bundle) => {\n if (controller.signal.aborted) return;\n setState({ status: \"success\", bundle, error: null });\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return;\n setState({\n status: \"error\",\n bundle: null,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n });\n\n // Fire-and-forget custom CSS fetch — best-effort, failures never block\n // bundle rendering.\n fetchShopCustomCss({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n signal: controller.signal,\n })\n .then((css) => {\n if (controller.signal.aborted) return;\n injectCustomCss(options.shopDomain, css);\n })\n .catch(() => {\n // Silent — no merchant CSS is fine.\n });\n\n return () => {\n controller.abort();\n };\n }, [\n options.shopDomain,\n options.storefrontAccessToken,\n options.bundleGid,\n ]);\n\n return state;\n}\n","/**\n * Framework-agnostic async fetcher for bundle data.\n *\n * Primary path for Next.js App Router / RSC and Hydrogen loaders — call\n * this in a server-side context and pass the result as a prop to a\n * `\"use client\"` component. `useBundleData` is NOT needed in that\n * component because the data is already in hand.\n *\n * Always forward the end-buyer's IP via `buyerIp` when calling from SSR:\n * Shopify may return 430 Security Rejection for server-originated traffic\n * without it.\n *\n * Throws:\n * - `StorefrontApiError` on network / HTTP / GraphQL errors\n * - `BundleParseError` when the metaobject is missing, invalid type,\n * inactive, not yet started, or expired\n */\nimport {\n createStorefrontClient,\n BUNDLE_METAOBJECT_QUERY,\n parseMetaobjectBundleStrict,\n BundleParseError,\n type ParsedBundle,\n type BundleMetaobjectResponse,\n} from \"@lime-bundles/core\";\n\nexport interface FetchBundleDataOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n bundleGid: string;\n /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR. */\n buyerIp?: string;\n /** Abort the in-flight request (used by `useBundleData` to cancel on unmount). */\n signal?: AbortSignal;\n /** Override the Storefront API version pinned in `createStorefrontClient`. */\n apiVersion?: string;\n}\n\nexport async function fetchBundleData(\n options: FetchBundleDataOptions,\n): Promise<ParsedBundle> {\n const client = createStorefrontClient({\n shopDomain: options.shopDomain,\n accessToken: options.storefrontAccessToken,\n buyerIp: options.buyerIp,\n apiVersion: options.apiVersion,\n });\n\n const data = await client.query<BundleMetaobjectResponse>(\n BUNDLE_METAOBJECT_QUERY,\n { id: options.bundleGid },\n { signal: options.signal },\n );\n\n if (!data.metaobject) {\n throw new BundleParseError(\n `Bundle not found: ${options.bundleGid}`,\n \"not_found\",\n );\n }\n\n return parseMetaobjectBundleStrict(\n data.metaobject.id,\n data.metaobject.fields,\n );\n}\n","/**\n * Best-effort fetch for the shop's `$app:custom_css` metafield.\n *\n * Returns the raw CSS string (still needs sanitization before injection —\n * see `injectCustomCss` from core) or null if the metafield is unset or\n * the request fails.\n *\n * **Per-shop in-memory cache.** Multiple `<FixedBundle>` / `<VolumeBundle>`\n * / `<MixMatchBundle>` components on the same page share one network\n * request — the first caller fetches, concurrent callers await the\n * same promise, and resolved values are cached so subsequent mounts\n * return instantly. Failures are NOT cached so the next caller retries.\n *\n * The cache lives for the life of the JS module (typically one page\n * load). Merchants updating their CSS see changes on the next page\n * load. The cache is exposed as `__clearShopCssCache` for test use only.\n */\nimport {\n createStorefrontClient,\n SHOP_CUSTOM_CSS_QUERY,\n type ShopCustomCssResponse,\n} from \"@lime-bundles/core\";\n\nexport interface FetchShopCustomCssOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n buyerIp?: string;\n signal?: AbortSignal;\n}\n\n// Cache map: key = shopDomain, value = pending or resolved promise.\n// Failures delete their own entry so retries get a fresh fetch.\nconst cache = new Map<string, Promise<string | null>>();\n\nexport async function fetchShopCustomCss(\n options: FetchShopCustomCssOptions,\n): Promise<string | null> {\n const cached = cache.get(options.shopDomain);\n if (cached) return cached;\n\n const promise = (async () => {\n const client = createStorefrontClient({\n shopDomain: options.shopDomain,\n accessToken: options.storefrontAccessToken,\n buyerIp: options.buyerIp,\n });\n\n const data = await client.query<ShopCustomCssResponse>(\n SHOP_CUSTOM_CSS_QUERY,\n undefined,\n { signal: options.signal },\n );\n\n return data.shop?.metafield?.value ?? null;\n })();\n\n // Cache the promise synchronously so concurrent callers dedupe.\n cache.set(options.shopDomain, promise);\n\n // Evict on failure so the next caller can retry.\n promise.catch(() => {\n if (cache.get(options.shopDomain) === promise) {\n cache.delete(options.shopDomain);\n }\n });\n\n return promise;\n}\n\n/**\n * Test-only helper. Clears the per-shop CSS cache so each test starts\n * from a known state. Do not call from application code.\n */\nexport function __clearShopCssCache(): void {\n cache.clear();\n}\n","/**\n * Hook for analytics event reporting (impressions + add-to-cart).\n * Impressions fire once per mount using IntersectionObserver.\n */\nimport { useEffect, useMemo, useRef, useCallback, useState } from \"react\";\nimport {\n reportImpression,\n reportAddToCart,\n observeImpression,\n type AnalyticsConfig,\n} from \"@lime-bundles/core\";\n\nexport interface UseAnalyticsOptions {\n shopDomain: string;\n appUrl: string;\n bundleGid: string;\n bundleType: string;\n enabled?: boolean;\n abTestId?: string;\n abVariant?: string;\n}\n\nexport function useAnalytics(options: UseAnalyticsOptions) {\n const impressionFiredRef = useRef(false);\n // Callback ref + state so the observer effect re-runs when the host element\n // is attached (e.g. after a `loading` branch swaps in the real DOM node).\n const [element, setElement] = useState<HTMLElement | null>(null);\n const elementRef = useCallback((el: HTMLElement | null) => {\n setElement(el);\n }, []);\n\n const config: AnalyticsConfig = useMemo(\n () => ({ shopDomain: options.shopDomain, appUrl: options.appUrl }),\n [options.shopDomain, options.appUrl],\n );\n\n // Reset the fired flag when bundle identity changes so a reused component\n // (e.g. via navigation) can report a fresh impression.\n useEffect(() => {\n impressionFiredRef.current = false;\n }, [options.bundleGid, options.abVariant]);\n\n // Set up impression observer once the element is attached\n useEffect(() => {\n if (options.enabled === false) return;\n if (impressionFiredRef.current) return;\n if (!element) return;\n\n const cleanup = observeImpression(element, () => {\n if (impressionFiredRef.current) return;\n impressionFiredRef.current = true;\n\n reportImpression(config, {\n bundleGid: options.bundleGid,\n bundleType: options.bundleType,\n abTestId: options.abTestId,\n abVariant: options.abVariant,\n });\n });\n\n return cleanup;\n }, [\n element,\n config,\n options.enabled,\n options.bundleGid,\n options.bundleType,\n options.abTestId,\n options.abVariant,\n ]);\n\n const trackAddToCart = useCallback(\n (event: {\n /** Optional — omit for multi-product bundles (fixed, mix-match). */\n productId?: string;\n quantity: number;\n totalPrice: number;\n }) => {\n if (options.enabled === false) return;\n\n reportAddToCart(config, {\n bundleGid: options.bundleGid,\n bundleType: options.bundleType,\n productId: event.productId,\n quantity: event.quantity,\n totalPrice: event.totalPrice,\n abTestId: options.abTestId,\n abVariant: options.abVariant,\n });\n },\n [\n config,\n options.enabled,\n options.bundleGid,\n options.bundleType,\n options.abTestId,\n options.abVariant,\n ],\n );\n\n return { elementRef, trackAddToCart };\n}\n","/**\n * Mix-and-match bundle component — customer picks products from a pool.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n validateQuantity,\n resolveBundleQty,\n type CartLineInput,\n type MixMatchBundleData,\n type ProductVariant,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\ninterface SelectedItem {\n productId: string;\n variantId: string;\n quantity: number;\n}\n\nexport function MixMatchBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"mix_match\",\n enabled: analyticsEnabled !== false,\n });\n\n const [selections, setSelections] = useState<Map<string, SelectedItem>>(\n new Map(),\n );\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"mix_match\"\n ? (result.bundle as MixMatchBundleData)\n : null;\n\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"mix_match\"\n ) {\n onError?.(\n new Error(\n `MixMatchBundle: expected bundleType=\"mix_match\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const totalQuantity = Array.from(selections.values()).reduce(\n (sum, s) => sum + s.quantity,\n 0,\n );\n\n const validation = bundle\n ? validateQuantity(totalQuantity, bundle.minQuantity, bundle.maxQuantity)\n : { valid: false, totalQuantity: 0, message: null };\n\n const toggleProduct = useCallback(\n (productId: string, variant: ProductVariant) => {\n setSelections((prev) => {\n const next = new Map(prev);\n const key = `${productId}:${variant.id}`;\n if (next.has(key)) {\n next.delete(key);\n } else {\n // Pre-fill slot qty from the canonical resolver — variant-level\n // wins, product-level fallback, then 1. Customer can still adjust\n // via the stepper afterwards.\n const configuredQty = bundle\n ? resolveBundleQty(bundle, productId, variant.id)\n : 1;\n next.set(key, {\n productId,\n variantId: variant.id,\n quantity: configuredQty,\n });\n }\n return next;\n });\n },\n [bundle],\n );\n\n const updateQuantity = useCallback(\n (productId: string, variantId: string, quantity: number) => {\n setSelections((prev) => {\n const next = new Map(prev);\n const key = `${productId}:${variantId}`;\n if (quantity <= 0) {\n next.delete(key);\n } else {\n next.set(key, { productId, variantId, quantity });\n }\n return next;\n });\n },\n [],\n );\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle || !validation.valid) return;\n\n const lines: CartLineInput[] = Array.from(selections.values()).map((s) => ({\n merchandiseId: s.variantId,\n quantity: s.quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n }));\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n\n const totalPrice = lines.reduce((sum, line) => {\n const product = bundle.products.find((p) =>\n p.variants.nodes.some((v) => v.id === line.merchandiseId),\n );\n const variant = product?.variants.nodes.find(\n (v) => v.id === line.merchandiseId,\n );\n return (\n sum + parseFloat(variant?.price.amount ?? \"0\") * line.quantity\n );\n }, 0);\n\n // productId is intentionally omitted — mix-match bundles span\n // multiple products; bundleGid attributes the event to the bundle.\n trackAddToCart({\n quantity: totalQuantity,\n totalPrice: Math.round(totalPrice * 100) / 100,\n });\n\n setSelections(new Map());\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [\n bundle,\n selections,\n validation.valid,\n onAddToCart,\n onError,\n trackAddToCart,\n totalQuantity,\n ]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--products\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n if (!bundle) return null;\n\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--mix-match ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n {bundle.discountLabel && (\n <span className=\"lb-bundle__discount-badge\">{bundle.discountLabel}</span>\n )}\n\n <p className=\"lb-bundle__instructions\">\n {bundle.minQuantity && bundle.maxQuantity\n ? `Select ${bundle.minQuantity}–${bundle.maxQuantity} items`\n : bundle.minQuantity\n ? `Select at least ${bundle.minQuantity} items`\n : bundle.maxQuantity\n ? `Select up to ${bundle.maxQuantity} items`\n : \"Select your items\"}\n </p>\n\n <div className=\"lb-bundle__products lb-bundle__products--selectable\">\n {bundle.products.map((product) => {\n const defaultVariant =\n product.variants.nodes.find((v) => v.availableForSale) ??\n product.variants.nodes[0];\n if (!defaultVariant) return null;\n\n const key = `${product.id}:${defaultVariant.id}`;\n const selected = selections.get(key);\n\n return (\n <div\n key={product.id}\n className={`lb-bundle__product lb-bundle__product--selectable ${selected ? \"lb-bundle__product--selected\" : \"\"}`}\n >\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(defaultVariant.price.amount, currency)}\n </p>\n </div>\n <div className=\"lb-bundle__product-actions\">\n {selected ? (\n <div className=\"lb-bundle__quantity-control\">\n <button\n aria-label={`Decrease ${product.title}`}\n onClick={() =>\n updateQuantity(\n product.id,\n defaultVariant.id,\n selected.quantity - 1,\n )\n }\n >\n −\n </button>\n <span>{selected.quantity}</span>\n <button\n aria-label={`Increase ${product.title}`}\n onClick={() =>\n updateQuantity(\n product.id,\n defaultVariant.id,\n selected.quantity + 1,\n )\n }\n >\n +\n </button>\n </div>\n ) : (\n <button\n className=\"lb-bundle__select-btn\"\n onClick={() => toggleProduct(product.id, defaultVariant)}\n disabled={!defaultVariant.availableForSale}\n >\n {defaultVariant.availableForSale ? \"Select\" : \"Sold out\"}\n </button>\n )}\n </div>\n </div>\n );\n })}\n </div>\n\n {validation.message && (\n <p className=\"lb-bundle__validation\" role=\"status\">\n {validation.message}\n </p>\n )}\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart || !validation.valid}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? `Add ${totalQuantity} Items to Cart`)}\n </button>\n </div>\n );\n}\n","/**\n * Volume bundle component — quantity selector with tier calculator.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n calculateTierSavings,\n getActiveTier,\n type CartLineInput,\n type VolumeBundleData,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\nexport function VolumeBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"volume\",\n enabled: analyticsEnabled !== false,\n });\n\n const [quantity, setQuantity] = useState(1);\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"volume\"\n ? (result.bundle as VolumeBundleData)\n : null;\n\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"volume\"\n ) {\n onError?.(\n new Error(\n `VolumeBundle: expected bundleType=\"volume\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const product = bundle?.products[0];\n const basePrice = product\n ? parseFloat(product.priceRange.minVariantPrice.amount)\n : 0;\n const currency = product?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n const tierSavings = bundle\n ? calculateTierSavings(\n bundle.volumeTiers,\n basePrice,\n quantity,\n bundle.discountConfig.discountType,\n )\n : [];\n const activeTier = bundle\n ? getActiveTier(bundle.volumeTiers, quantity)\n : null;\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle || !product) return;\n\n const variant = product.variants.nodes.find((v) => v.availableForSale);\n if (!variant) return;\n\n const lines: CartLineInput[] = [\n {\n merchandiseId: variant.id,\n quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n },\n ];\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n // Reuse the canonical computation from calculateTierSavings so the\n // analytics unit price matches what the component rendered.\n const unitPrice =\n tierSavings.find((ts) => ts.tier === activeTier)?.unitPrice ?? basePrice;\n trackAddToCart({\n productId: product.id,\n quantity,\n totalPrice: Math.round(unitPrice * quantity * 100) / 100,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [\n bundle,\n product,\n quantity,\n activeTier,\n basePrice,\n onAddToCart,\n onError,\n trackAddToCart,\n ]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--tiers\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n if (!bundle) return null;\n if (!product) return null;\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--volume ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n <div className=\"lb-bundle__product lb-bundle__product--volume\">\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(basePrice, currency)} each\n </p>\n </div>\n </div>\n\n <div\n className=\"lb-bundle__tiers\"\n role=\"table\"\n aria-label=\"Volume discounts\"\n >\n {tierSavings.map((ts) => (\n <div\n key={ts.tier.minQuantity}\n className={`lb-bundle__tier ${ts.isActive ? \"lb-bundle__tier--active\" : \"\"}`}\n role=\"row\"\n >\n <span className=\"lb-bundle__tier-quantity\" role=\"cell\">\n {ts.tier.minQuantity}+ items\n </span>\n <span className=\"lb-bundle__tier-price\" role=\"cell\">\n {formatMoney(ts.unitPrice, currency)} each\n </span>\n <span className=\"lb-bundle__tier-savings\" role=\"cell\">\n Save {ts.savingsPercent.toFixed(0)}%\n </span>\n </div>\n ))}\n </div>\n\n <div className=\"lb-bundle__quantity-selector\">\n <label htmlFor={`lb-qty-${bundle.id}`}>Quantity</label>\n <div className=\"lb-bundle__quantity-control\">\n <button\n aria-label=\"Decrease quantity\"\n onClick={() => setQuantity((q) => Math.max(1, q - 1))}\n >\n −\n </button>\n <input\n id={`lb-qty-${bundle.id}`}\n type=\"number\"\n min={1}\n value={quantity}\n onChange={(e) => {\n const val = parseInt(e.target.value, 10);\n if (!isNaN(val) && val > 0) setQuantity(val);\n }}\n className=\"lb-bundle__quantity-input\"\n />\n <button\n aria-label=\"Increase quantity\"\n onClick={() => setQuantity((q) => q + 1)}\n >\n +\n </button>\n </div>\n </div>\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? `Add ${quantity} to Cart`)}\n </button>\n </div>\n );\n}\n","/**\n * Client-side React hook wrapping `fetchBundlesForProduct` — the\n * React-SDK analogue of the web component's auto-detect-by-current-product\n * mode. The web component reads the handle from `location.pathname`; a\n * React app already has it on the router, so the hook takes it as an\n * explicit `productHandle` option.\n *\n * Returns every active bundle configured against the product. Inactive,\n * draft, scheduled-for-later, and expired bundles are filtered out by\n * the core helper — consumers don't have to guard.\n *\n * For SSR / RSC contexts (Hydrogen loaders, Next.js server components),\n * prefer `fetchBundlesForProduct` directly and pass the list to a client\n * component as a prop. This hook's only value-add over the async\n * function is React state management + abort-on-unmount.\n *\n * `UseBundlesForProductResult` is a discriminated union locked at v2.0.0:\n * narrowing via `status` is safe. Widening or adding states is a\n * breaking change.\n */\nimport { useState, useEffect } from \"react\";\nimport {\n fetchBundlesForProduct,\n injectCustomCss,\n type ParsedBundle,\n} from \"@lime-bundles/core\";\nimport { fetchShopCustomCss } from \"../fetchShopCustomCss\";\n\nexport interface UseBundlesForProductOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n productHandle: string;\n /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR paths. */\n buyerIp?: string;\n}\n\nexport type UseBundlesForProductResult =\n | { status: \"loading\"; bundles: null; error: null }\n | { status: \"success\"; bundles: ParsedBundle[]; error: null }\n | { status: \"error\"; bundles: null; error: Error };\n\nconst INITIAL_STATE: UseBundlesForProductResult = {\n status: \"loading\",\n bundles: null,\n error: null,\n};\n\nexport function useBundlesForProduct(\n options: UseBundlesForProductOptions,\n): UseBundlesForProductResult {\n const [state, setState] = useState<UseBundlesForProductResult>(\n INITIAL_STATE,\n );\n\n useEffect(() => {\n const controller = new AbortController();\n setState(INITIAL_STATE);\n\n fetchBundlesForProduct({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n productHandle: options.productHandle,\n buyerIp: options.buyerIp,\n signal: controller.signal,\n })\n .then((bundles) => {\n if (controller.signal.aborted) return;\n setState({ status: \"success\", bundles, error: null });\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return;\n setState({\n status: \"error\",\n bundles: null,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n });\n\n // Fire-and-forget custom CSS fetch so storefronts using this hook get\n // merchant styling automatically — same behaviour as `useBundleData`.\n fetchShopCustomCss({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n signal: controller.signal,\n })\n .then((css) => {\n if (controller.signal.aborted) return;\n injectCustomCss(options.shopDomain, css);\n })\n .catch(() => {\n // Silent — no merchant CSS is fine.\n });\n\n return () => {\n controller.abort();\n };\n }, [\n options.shopDomain,\n options.storefrontAccessToken,\n options.productHandle,\n options.buyerIp,\n ]);\n\n return state;\n}\n","// Bundle components — BYO-cart via `onAddToCart`.\nexport { FixedBundle } from \"./components/FixedBundle\";\nexport { MixMatchBundle } from \"./components/MixMatchBundle\";\nexport { VolumeBundle } from \"./components/VolumeBundle\";\nexport type { BundleComponentProps } from \"./components/types\";\n\n// Data fetching\nexport {\n fetchBundleData,\n type FetchBundleDataOptions,\n} from \"./fetchBundleData\";\nexport {\n fetchShopCustomCss,\n type FetchShopCustomCssOptions,\n} from \"./fetchShopCustomCss\";\nexport {\n useBundleData,\n type UseBundleDataOptions,\n type UseBundleDataResult,\n} from \"./hooks/useBundleData\";\nexport {\n useBundlesForProduct,\n type UseBundlesForProductOptions,\n type UseBundlesForProductResult,\n} from \"./hooks/useBundlesForProduct\";\nexport { useAnalytics } from \"./hooks/useAnalytics\";\n\n// Re-export core types consumers commonly need.\nexport type {\n ParsedBundle,\n FixedBundleData,\n VolumeBundleData,\n MixMatchBundleData,\n BundleType,\n Product,\n ProductVariant,\n CartLineInput,\n WidgetConfig,\n HeaderConfig,\n LayoutConfig,\n ProductListConfig,\n PricingConfig,\n SavingsBarConfig,\n CtaConfig,\n CountdownConfig,\n PopularBadgeConfig,\n VolumeTier,\n DiscountConfig,\n ABVariantOverrides,\n PricingRow,\n FixedBundlePricing,\n ImageTransform,\n} from \"@lime-bundles/core\";\nexport { BundleParseError, StorefrontApiError } from \"@lime-bundles/core\";\n\n// DIY widget primitives — every helper a developer needs to build their\n// own bundle UI with the same data, math, and theming the first-party\n// widget uses. All live in `@lime-bundles/core`; importing from the\n// React package is shorthand for consumers who already pulled `react`.\nexport {\n // Bundle config + variant merging\n WIDGET_CONFIG_DEFAULTS,\n mergeWidgetConfig,\n applyWidgetConfigVars,\n applyABVariantB,\n // Pricing (integer cents, matches Shopify Discount Function rounding)\n parseCents,\n formatCents,\n percentageDiscountUnit,\n computeFixedPricing,\n computeBundleSaleCents,\n // Volume-tier calculation\n calculateTierSavings,\n getActiveTier,\n // Mix-match quantity validation\n validateQuantity,\n // Image / countdown / money helpers\n transformImageUrl,\n formatCountdown,\n formatMoney,\n calculateDiscount,\n // Analytics (optional — FixedBundle / VolumeBundle / MixMatchBundle\n // auto-fire these unless analyticsEnabled is set to false)\n reportImpression,\n reportAddToCart,\n observeImpression,\n // A/B testing\n getABTestAssignment,\n setConsent,\n hasConsent,\n // CSS injection + sanitizer\n injectCustomCss,\n sanitizeCustomCss,\n // Custom-storefront data fetching\n createStorefrontClient,\n fetchBundlesForProduct,\n parseMetaobjectBundle,\n parseMetaobjectBundleStrict,\n BUNDLE_METAOBJECT_QUERY,\n BUNDLES_FOR_PRODUCT_QUERY,\n SHOP_CUSTOM_CSS_QUERY,\n} from \"@lime-bundles/core\";\n"],"mappings":"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","names":["useCallback","useEffect","useState","createStorefrontClient","useEffect","useState","useState","useEffect","useCallback","useCallback","useEffect","useState","formatMoney","resolveBundleQty","jsx","jsxs","useState","useEffect","useCallback","resolveBundleQty","formatMoney","useCallback","useEffect","useState","formatMoney","jsx","jsxs","useState","useEffect","useCallback","formatMoney","useState","useEffect","injectCustomCss","INITIAL_STATE","useState","useEffect","injectCustomCss","BundleParseError","calculateTierSavings","getActiveTier","validateQuantity","formatMoney","calculateDiscount","reportImpression","reportAddToCart","observeImpression","injectCustomCss","createStorefrontClient","fetchBundlesForProduct","parseMetaobjectBundleStrict","BUNDLE_METAOBJECT_QUERY","SHOP_CUSTOM_CSS_QUERY"]}
|
package/docs/web-component.md
CHANGED
|
@@ -155,6 +155,25 @@ type CartLineInput = {
|
|
|
155
155
|
|
|
156
156
|
Every `CartLineInput.attributes` array includes `{ key: "_lime_bundle_gid", value: <bundle GID> }`. Preserve it on the way to Shopify cart mutation or purchase attribution breaks.
|
|
157
157
|
|
|
158
|
+
### Loading state (optional)
|
|
159
|
+
|
|
160
|
+
The widget's CTA button is structured to support a spinner overlay that matches the theme-extension look. The SDK itself stays neutral on timing — you toggle the state during your async cart mutation:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
el.addEventListener("lime-bundle:add-to-cart", async (e: CustomEvent) => {
|
|
164
|
+
const btn = (e.target as Element).shadowRoot
|
|
165
|
+
?.querySelector<HTMLButtonElement>("[data-add-bundle]");
|
|
166
|
+
btn?.setAttribute("data-loading", "true");
|
|
167
|
+
try {
|
|
168
|
+
await cart.linesAdd(e.detail.lines);
|
|
169
|
+
} finally {
|
|
170
|
+
btn?.removeAttribute("data-loading");
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Under the hood the button is a 1×1 CSS grid with two overlaid children (`.lb-cta-label` and `.lb-cta-spinner`). Setting `data-loading="true"` hides the label and reveals the spinner without resizing the button. Omit this pattern entirely and the button simply stays static during the cart call — no visual regression.
|
|
176
|
+
|
|
158
177
|
### `lime-bundle:loaded`
|
|
159
178
|
|
|
160
179
|
Fired once bundle data has been fetched and parsed successfully. Useful for hiding a placeholder or triggering analytics in non-SDK systems. The event fires even when zero bundles apply to the current product (product has no bundles attached); check `bundleCount` to tell the difference.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lime-bundles/react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "React components and hooks for the Lime Bundles Shopify app. Use on Hydrogen, Next.js, Vite, or any React-based headless storefront.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"react-dom": ">=18.0.0"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@lime-bundles/core": "^2.
|
|
55
|
+
"@lime-bundles/core": "^2.2.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@shopify/hydrogen-react": "^2026.4.1",
|