@lime-bundles/widget 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +672 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +654 -0
- package/dist/index.js.map +1 -0
- package/dist/lime-bundle.global.js +193 -0
- package/dist/lime-bundle.global.js.map +1 -0
- package/dist/lime-thankyou.cjs +2 -0
- package/dist/lime-thankyou.cjs.map +1 -0
- package/dist/lime-thankyou.global.js +2 -0
- package/dist/lime-thankyou.global.js.map +1 -0
- package/dist/lime-thankyou.js +2 -0
- package/dist/lime-thankyou.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lime-bundle.ts","../src/renderers/fixed.ts","../src/renderers/mix-match.ts","../src/renderers/volume.ts","../src/styles/widget-styles.ts","../src/thankyou/index.ts","../src/index.ts"],"sourcesContent":["/**\n * <lime-bundle> Web Component — renders Lime Bundles on headless storefronts.\n *\n * Usage:\n * <lime-bundle\n * shop-domain=\"my-store.myshopify.com\"\n * storefront-token=\"abc123\"\n * bundle-gid=\"gid://shopify/Metaobject/12345\"\n * ></lime-bundle>\n */\nimport {\n createStorefrontClient,\n BUNDLE_METAOBJECT_QUERY,\n parseMetaobjectBundle,\n detectCartApi,\n createAjaxCartApi,\n createStorefrontCartApi,\n observeImpression,\n reportImpression,\n reportAddToCart,\n type ParsedBundle,\n type BundleMetaobjectResponse,\n type CartLineItem,\n} from \"@lime-bundles/core\";\nimport { renderFixedBundle } from \"./renderers/fixed\";\nimport { renderMixMatchBundle } from \"./renderers/mix-match\";\nimport { renderVolumeBundle } from \"./renderers/volume\";\nimport { WIDGET_STYLES } from \"./styles/widget-styles\";\n\nexport class LimeBundleElement extends HTMLElement {\n static observedAttributes = [\n \"shop-domain\",\n \"storefront-token\",\n \"bundle-gid\",\n \"cart-id\",\n \"app-url\",\n \"analytics\",\n \"locale\",\n ];\n\n private shadow: ShadowRoot;\n private bundle: ParsedBundle | null = null;\n private abortController: AbortController | null = null;\n private impressionCleanup: (() => void) | null = null;\n\n constructor() {\n super();\n this.shadow = this.attachShadow({ mode: \"open\" });\n }\n\n connectedCallback() {\n this.render();\n this.fetchBundle();\n }\n\n disconnectedCallback() {\n this.abortController?.abort();\n this.teardownImpression();\n }\n\n /** Clean up stale bundle state and active impression observer. */\n private teardownImpression() {\n this.impressionCleanup?.();\n this.impressionCleanup = null;\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {\n // Ignore no-op writes (same value re-set) and pre-connect attribute parsing\n if (oldValue === newValue || !this.isConnected) return;\n\n if (name === \"bundle-gid\" || name === \"shop-domain\" || name === \"storefront-token\") {\n // Only refetch when all required attributes are present — prevents\n // partial-attribute races during element initialization or programmatic updates\n if (this.shopDomain && this.storefrontToken && this.bundleGid) {\n this.fetchBundle();\n }\n }\n }\n\n private get shopDomain(): string {\n return this.getAttribute(\"shop-domain\") ?? \"\";\n }\n\n private get storefrontToken(): string {\n return this.getAttribute(\"storefront-token\") ?? \"\";\n }\n\n private get bundleGid(): string {\n return this.getAttribute(\"bundle-gid\") ?? \"\";\n }\n\n private get cartId(): string | undefined {\n return this.getAttribute(\"cart-id\") ?? undefined;\n }\n\n private get appUrl(): string {\n return this.getAttribute(\"app-url\") ?? \"\";\n }\n\n private get analyticsEnabled(): boolean {\n return this.getAttribute(\"analytics\") !== \"false\";\n }\n\n private async fetchBundle() {\n if (!this.shopDomain || !this.storefrontToken || !this.bundleGid) {\n this.renderError(\"Missing required attributes: shop-domain, storefront-token, bundle-gid\");\n return;\n }\n\n // Abort any previous fetch and create a fresh controller.\n // Capture in a local const so post-await checks reference the controller\n // for THIS invocation, not a later one created by a concurrent attributeChangedCallback.\n this.abortController?.abort();\n const controller = new AbortController();\n this.abortController = controller;\n\n this.renderLoading();\n\n try {\n const client = createStorefrontClient({\n shopDomain: this.shopDomain,\n accessToken: this.storefrontToken,\n });\n\n const data = await client.query<BundleMetaobjectResponse>(\n BUNDLE_METAOBJECT_QUERY,\n { id: this.bundleGid },\n );\n\n // Check the LOCAL controller — if a newer fetch replaced this.abortController,\n // this invocation's controller will be aborted.\n if (controller.signal.aborted) return;\n\n if (!data.metaobject) {\n this.bundle = null;\n this.teardownImpression();\n this.renderError(\"Bundle not found\");\n return;\n }\n\n this.bundle = parseMetaobjectBundle(\n data.metaobject.id,\n data.metaobject.fields,\n );\n\n if (!this.bundle) {\n this.teardownImpression();\n this.renderError(\"Bundle is not active or has expired\");\n return;\n }\n\n this.renderBundle();\n this.setupImpression();\n } catch (err) {\n if (controller.signal.aborted) return;\n this.bundle = null;\n this.teardownImpression();\n this.renderError(\n err instanceof Error ? err.message : \"Failed to load bundle\",\n );\n }\n }\n\n private renderBundle() {\n if (!this.bundle) return;\n\n const container = document.createElement(\"div\");\n container.className = \"lb-bundle\";\n container.setAttribute(\"role\", \"region\");\n container.setAttribute(\"aria-label\", this.bundle.title);\n\n const addToCart = async (items: CartLineItem[]) => {\n const apiType = detectCartApi();\n const cart =\n apiType === \"ajax\"\n ? createAjaxCartApi(this.bundleGid, this.bundle!.bundleType)\n : createStorefrontCartApi(\n createStorefrontClient({\n shopDomain: this.shopDomain,\n accessToken: this.storefrontToken,\n }),\n this.bundleGid,\n this.bundle!.bundleType,\n this.cartId,\n );\n\n let result: Awaited<ReturnType<typeof cart.addLines>>;\n try {\n result = await cart.addLines(items);\n } catch (err) {\n result = {\n success: false,\n error: err instanceof Error ? err.message : \"Cart add failed\",\n };\n }\n\n if (result.success) {\n // Always dispatch the CustomEvent so consumers can react to add-to-cart\n // regardless of analytics configuration\n this.dispatchEvent(\n new CustomEvent(\"lime-bundle:add-to-cart\", {\n detail: { items, cartId: result.cartId },\n bubbles: true,\n composed: true,\n }),\n );\n\n // Analytics reporting is separate — only fires when enabled and appUrl is set\n if (this.analyticsEnabled && this.appUrl) {\n const quantity = items.reduce((sum, i) => sum + i.quantity, 0);\n const totalPrice = items.reduce((sum, item) => {\n const product = this.bundle!.products.find((p) =>\n p.variants.nodes.some((v) => v.id === item.variantId),\n );\n const variant = product?.variants.nodes.find((v) => v.id === item.variantId);\n const price = variant ? parseFloat(variant.price.amount) : 0;\n return sum + price * item.quantity;\n }, 0);\n\n reportAddToCart(\n { shopDomain: this.shopDomain, appUrl: this.appUrl },\n {\n bundleGid: this.bundleGid,\n bundleType: this.bundle!.bundleType,\n productId: this.bundle!.products[0]?.id ?? \"\",\n quantity,\n totalPrice: Math.round(totalPrice * 100) / 100,\n },\n );\n }\n }\n\n return result;\n };\n\n switch (this.bundle.bundleType) {\n case \"fixed\":\n renderFixedBundle(container, this.bundle, addToCart);\n break;\n case \"mix_match\":\n renderMixMatchBundle(container, this.bundle, addToCart);\n break;\n case \"volume\":\n renderVolumeBundle(container, this.bundle, addToCart);\n break;\n }\n\n this.shadow.innerHTML = \"\";\n const style = document.createElement(\"style\");\n style.textContent = WIDGET_STYLES;\n this.shadow.appendChild(style);\n this.shadow.appendChild(container);\n\n this.dispatchEvent(\n new CustomEvent(\"lime-bundle:loaded\", {\n detail: {\n bundleType: this.bundle.bundleType,\n title: this.bundle.title,\n },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n private setupImpression() {\n if (!this.analyticsEnabled || !this.bundle || !this.appUrl) return;\n\n this.impressionCleanup?.();\n this.impressionCleanup = observeImpression(this, () => {\n reportImpression(\n { shopDomain: this.shopDomain, appUrl: this.appUrl },\n {\n bundleGid: this.bundleGid,\n bundleType: this.bundle!.bundleType,\n },\n );\n });\n }\n\n private renderLoading() {\n this.shadow.innerHTML = `\n <style>${WIDGET_STYLES}</style>\n <div class=\"lb-bundle lb-bundle--loading\">\n <div class=\"lb-skeleton lb-skeleton--title\"></div>\n <div class=\"lb-skeleton lb-skeleton--products\"></div>\n </div>\n `;\n }\n\n private renderError(message: string) {\n this.shadow.innerHTML = \"\";\n this.dispatchEvent(\n new CustomEvent(\"lime-bundle:error\", {\n detail: { message, code: \"LOAD_ERROR\" },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n private render() {\n // Initial empty state\n this.shadow.innerHTML = `<style>${WIDGET_STYLES}</style>`;\n }\n}\n","/**\n * DOM renderer for fixed bundles.\n */\nimport { formatMoney, type ParsedBundle, type CartLineItem, type AddToCartResult } from \"@lime-bundles/core\";\n\nexport function renderFixedBundle(\n container: HTMLElement,\n bundle: ParsedBundle,\n addToCart: (items: CartLineItem[]) => Promise<AddToCartResult>,\n) {\n const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n // Title\n const title = document.createElement(\"h3\");\n title.className = \"lb-bundle__title\";\n title.textContent = bundle.title;\n title.setAttribute(\"part\", \"title\");\n container.appendChild(title);\n\n // Discount badge\n if (bundle.discountLabel) {\n const badge = document.createElement(\"span\");\n badge.className = \"lb-bundle__discount-badge\";\n badge.textContent = bundle.discountLabel;\n container.appendChild(badge);\n }\n\n // Products\n const productsDiv = document.createElement(\"div\");\n productsDiv.className = \"lb-bundle__products\";\n\n for (const product of bundle.products) {\n const productEl = document.createElement(\"div\");\n productEl.className = \"lb-bundle__product\";\n productEl.setAttribute(\"part\", \"product\");\n\n if (product.featuredImage) {\n const img = document.createElement(\"img\");\n img.src = product.featuredImage.url;\n img.alt = product.featuredImage.altText ?? product.title;\n img.className = \"lb-bundle__product-image\";\n img.loading = \"lazy\";\n productEl.appendChild(img);\n }\n\n const info = document.createElement(\"div\");\n info.className = \"lb-bundle__product-info\";\n info.innerHTML = `\n <p class=\"lb-bundle__product-title\">${escapeHtml(product.title)}</p>\n <p class=\"lb-bundle__product-price\">${escapeHtml(formatMoney(product.priceRange.minVariantPrice.amount, currency))}</p>\n `;\n productEl.appendChild(info);\n productsDiv.appendChild(productEl);\n }\n container.appendChild(productsDiv);\n\n // CTA button\n const button = document.createElement(\"button\");\n button.className = \"lb-bundle__cta\";\n button.textContent = bundle.widgetConfig.ctaText ?? \"Add Bundle to Cart\";\n button.setAttribute(\"part\", \"button\");\n\n button.addEventListener(\"click\", async () => {\n button.disabled = true;\n button.textContent = \"Adding...\";\n\n const items: CartLineItem[] = 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 { variantId: variant.id, quantity: 1 };\n });\n\n const result = await addToCart(items);\n\n button.disabled = false;\n button.textContent = bundle.widgetConfig.ctaText ?? \"Add Bundle to Cart\";\n\n if (!result.success) {\n const error = document.createElement(\"p\");\n error.className = \"lb-bundle__error\";\n error.textContent = result.error ?? \"Failed to add to cart\";\n container.appendChild(error);\n setTimeout(() => error.remove(), 5000);\n }\n });\n\n container.appendChild(button);\n}\n\nfunction escapeHtml(str: string): string {\n const div = document.createElement(\"div\");\n div.textContent = str;\n return div.innerHTML;\n}\n","/**\n * DOM renderer for mix-and-match bundles.\n */\nimport { formatMoney, validateQuantity, type ParsedBundle, type CartLineItem, type AddToCartResult } from \"@lime-bundles/core\";\n\nexport function renderMixMatchBundle(\n container: HTMLElement,\n bundle: ParsedBundle,\n addToCart: (items: CartLineItem[]) => Promise<AddToCartResult>,\n) {\n const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n const selections = new Map<string, { variantId: string; quantity: number }>();\n\n // Title\n const title = document.createElement(\"h3\");\n title.className = \"lb-bundle__title\";\n title.textContent = bundle.title;\n title.setAttribute(\"part\", \"title\");\n container.appendChild(title);\n\n // Instructions\n const instructions = document.createElement(\"p\");\n instructions.className = \"lb-bundle__instructions\";\n instructions.textContent = bundle.minQuantity && bundle.maxQuantity\n ? `Select ${bundle.minQuantity}–${bundle.maxQuantity} items`\n : bundle.minQuantity\n ? `Select at least ${bundle.minQuantity} items`\n : \"Select your items\";\n container.appendChild(instructions);\n\n // Products\n const productsDiv = document.createElement(\"div\");\n productsDiv.className = \"lb-bundle__products lb-bundle__products--selectable\";\n\n for (const product of bundle.products) {\n const variant = product.variants.nodes.find((v) => v.availableForSale) ?? product.variants.nodes[0];\n if (!variant) continue;\n\n const productEl = document.createElement(\"div\");\n productEl.className = \"lb-bundle__product lb-bundle__product--selectable\";\n\n if (product.featuredImage) {\n const img = document.createElement(\"img\");\n img.src = product.featuredImage.url;\n img.alt = product.featuredImage.altText ?? product.title;\n img.className = \"lb-bundle__product-image\";\n img.loading = \"lazy\";\n productEl.appendChild(img);\n }\n\n const info = document.createElement(\"div\");\n info.className = \"lb-bundle__product-info\";\n info.innerHTML = `\n <p class=\"lb-bundle__product-title\">${escapeHtml(product.title)}</p>\n <p class=\"lb-bundle__product-price\">${escapeHtml(formatMoney(variant.price.amount, currency))}</p>\n `;\n productEl.appendChild(info);\n\n const selectBtn = document.createElement(\"button\");\n selectBtn.className = \"lb-bundle__select-btn\";\n selectBtn.textContent = variant.availableForSale ? \"Select\" : \"Sold out\";\n selectBtn.disabled = !variant.availableForSale;\n\n selectBtn.addEventListener(\"click\", () => {\n const key = product.id;\n if (selections.has(key)) {\n selections.delete(key);\n productEl.classList.remove(\"lb-bundle__product--selected\");\n selectBtn.textContent = \"Select\";\n } else {\n selections.set(key, { variantId: variant.id, quantity: 1 });\n productEl.classList.add(\"lb-bundle__product--selected\");\n selectBtn.textContent = \"Selected\";\n }\n updateCta();\n });\n\n productEl.appendChild(selectBtn);\n productsDiv.appendChild(productEl);\n }\n container.appendChild(productsDiv);\n\n // Validation message\n const validationEl = document.createElement(\"p\");\n validationEl.className = \"lb-bundle__validation\";\n container.appendChild(validationEl);\n\n // CTA\n const button = document.createElement(\"button\");\n button.className = \"lb-bundle__cta\";\n button.setAttribute(\"part\", \"button\");\n button.disabled = true;\n container.appendChild(button);\n\n function updateCta() {\n const total = Array.from(selections.values()).reduce((s, v) => s + v.quantity, 0);\n const validation = validateQuantity(total, bundle.minQuantity, bundle.maxQuantity);\n button.disabled = !validation.valid;\n button.textContent = bundle.widgetConfig.ctaText ?? `Add ${total} Items to Cart`;\n validationEl.textContent = validation.message ?? \"\";\n }\n\n updateCta();\n\n button.addEventListener(\"click\", async () => {\n button.disabled = true;\n button.textContent = \"Adding...\";\n\n const items: CartLineItem[] = Array.from(selections.entries()).map(([, s]) => ({\n variantId: s.variantId,\n quantity: s.quantity,\n }));\n\n const result = await addToCart(items);\n updateCta();\n\n if (!result.success) {\n const error = document.createElement(\"p\");\n error.className = \"lb-bundle__error\";\n error.textContent = result.error ?? \"Failed to add to cart\";\n container.appendChild(error);\n setTimeout(() => error.remove(), 5000);\n }\n });\n}\n\nfunction escapeHtml(str: string): string {\n const div = document.createElement(\"div\");\n div.textContent = str;\n return div.innerHTML;\n}\n","/**\n * DOM renderer for volume bundles.\n */\nimport { formatMoney, calculateTierSavings, type ParsedBundle, type CartLineItem, type AddToCartResult } from \"@lime-bundles/core\";\n\nexport function renderVolumeBundle(\n container: HTMLElement,\n bundle: ParsedBundle,\n addToCart: (items: CartLineItem[]) => Promise<AddToCartResult>,\n) {\n const product = bundle.products[0];\n if (!product) return;\n\n const basePrice = parseFloat(product.priceRange.minVariantPrice.amount);\n const currency = product.priceRange.minVariantPrice.currencyCode;\n let quantity = 1;\n\n // Title\n const title = document.createElement(\"h3\");\n title.className = \"lb-bundle__title\";\n title.textContent = bundle.title;\n title.setAttribute(\"part\", \"title\");\n container.appendChild(title);\n\n // Product\n const productEl = document.createElement(\"div\");\n productEl.className = \"lb-bundle__product lb-bundle__product--volume\";\n if (product.featuredImage) {\n const img = document.createElement(\"img\");\n img.src = product.featuredImage.url;\n img.alt = product.featuredImage.altText ?? product.title;\n img.className = \"lb-bundle__product-image\";\n img.loading = \"lazy\";\n productEl.appendChild(img);\n }\n const info = document.createElement(\"div\");\n info.className = \"lb-bundle__product-info\";\n info.innerHTML = `\n <p class=\"lb-bundle__product-title\">${escapeHtml(product.title)}</p>\n <p class=\"lb-bundle__product-price\">${escapeHtml(formatMoney(basePrice, currency))} each</p>\n `;\n productEl.appendChild(info);\n container.appendChild(productEl);\n\n // Tier table\n const tiersDiv = document.createElement(\"div\");\n tiersDiv.className = \"lb-bundle__tiers\";\n tiersDiv.setAttribute(\"role\", \"table\");\n tiersDiv.setAttribute(\"aria-label\", \"Volume discounts\");\n container.appendChild(tiersDiv);\n\n // Quantity selector\n const qtyWrapper = document.createElement(\"div\");\n qtyWrapper.className = \"lb-bundle__quantity-selector\";\n const label = document.createElement(\"label\");\n label.textContent = \"Quantity\";\n qtyWrapper.appendChild(label);\n\n const qtyControl = document.createElement(\"div\");\n qtyControl.className = \"lb-bundle__quantity-control\";\n\n const minusBtn = document.createElement(\"button\");\n minusBtn.textContent = \"−\";\n minusBtn.setAttribute(\"aria-label\", \"Decrease quantity\");\n\n const qtyInput = document.createElement(\"input\");\n qtyInput.type = \"number\";\n qtyInput.min = \"1\";\n qtyInput.value = \"1\";\n qtyInput.className = \"lb-bundle__quantity-input\";\n\n const plusBtn = document.createElement(\"button\");\n plusBtn.textContent = \"+\";\n plusBtn.setAttribute(\"aria-label\", \"Increase quantity\");\n\n qtyControl.append(minusBtn, qtyInput, plusBtn);\n qtyWrapper.appendChild(qtyControl);\n container.appendChild(qtyWrapper);\n\n // CTA\n const button = document.createElement(\"button\");\n button.className = \"lb-bundle__cta\";\n button.setAttribute(\"part\", \"button\");\n container.appendChild(button);\n\n function updateTiers() {\n const savings = calculateTierSavings(bundle.volumeTiers, basePrice, quantity);\n tiersDiv.innerHTML = \"\";\n for (const ts of savings) {\n const row = document.createElement(\"div\");\n row.className = `lb-bundle__tier${ts.isActive ? \" lb-bundle__tier--active\" : \"\"}`;\n row.setAttribute(\"role\", \"row\");\n row.innerHTML = `\n <span class=\"lb-bundle__tier-quantity\" role=\"cell\">${ts.tier.minQuantity}+ items</span>\n <span class=\"lb-bundle__tier-price\" role=\"cell\">${escapeHtml(formatMoney(ts.unitPrice, currency))} each</span>\n <span class=\"lb-bundle__tier-savings\" role=\"cell\">Save ${ts.savingsPercent.toFixed(0)}%</span>\n ${ts.tier.label ? `<span class=\"lb-bundle__tier-label\" role=\"cell\">${escapeHtml(ts.tier.label)}</span>` : \"\"}\n `;\n tiersDiv.appendChild(row);\n }\n button.textContent = bundle.widgetConfig.ctaText ?? `Add ${quantity} to Cart`;\n }\n\n updateTiers();\n\n minusBtn.addEventListener(\"click\", () => {\n if (quantity > 1) { quantity--; qtyInput.value = String(quantity); updateTiers(); }\n });\n plusBtn.addEventListener(\"click\", () => {\n quantity++; qtyInput.value = String(quantity); updateTiers();\n });\n qtyInput.addEventListener(\"change\", () => {\n const val = parseInt(qtyInput.value, 10);\n if (!isNaN(val) && val > 0) { quantity = val; updateTiers(); }\n });\n\n button.addEventListener(\"click\", async () => {\n const variant = product.variants.nodes.find((v) => v.availableForSale);\n if (!variant) return;\n\n button.disabled = true;\n button.textContent = \"Adding...\";\n\n const items: CartLineItem[] = [{ variantId: variant.id, quantity }];\n const result = await addToCart(items);\n\n button.disabled = false;\n updateTiers();\n\n if (!result.success) {\n const error = document.createElement(\"p\");\n error.className = \"lb-bundle__error\";\n error.textContent = result.error ?? \"Failed to add to cart\";\n container.appendChild(error);\n setTimeout(() => error.remove(), 5000);\n }\n });\n}\n\nfunction escapeHtml(str: string): string {\n const div = document.createElement(\"div\");\n div.textContent = str;\n return div.innerHTML;\n}\n","/**\n * CSS styles inlined into Shadow DOM.\n * Uses CSS custom properties that pierce the shadow boundary for theming.\n */\nexport const WIDGET_STYLES = `\n:host {\n display: block;\n --lb-primary-color: #000;\n --lb-secondary-color: #666;\n --lb-accent-color: #2563eb;\n --lb-background: #fff;\n --lb-border-color: #e5e7eb;\n --lb-border-radius: 8px;\n --lb-font-family: inherit;\n --lb-font-size: 14px;\n --lb-spacing-sm: 8px;\n --lb-spacing-md: 16px;\n --lb-spacing-lg: 24px;\n --lb-button-bg: var(--lb-accent-color);\n --lb-button-text: #fff;\n --lb-button-radius: var(--lb-border-radius);\n --lb-savings-color: #16a34a;\n --lb-error-color: #dc2626;\n}\n\n.lb-bundle {\n font-family: var(--lb-font-family);\n font-size: var(--lb-font-size);\n color: var(--lb-primary-color);\n background: var(--lb-background);\n border: 1px solid var(--lb-border-color);\n border-radius: var(--lb-border-radius);\n padding: var(--lb-spacing-lg);\n}\n\n.lb-bundle__title { margin: 0 0 var(--lb-spacing-md); font-size: 1.25em; font-weight: 600; }\n.lb-bundle__discount-badge { display: inline-block; background: var(--lb-savings-color); color: #fff; padding: 2px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 600; margin-bottom: var(--lb-spacing-md); }\n.lb-bundle__products { display: grid; gap: var(--lb-spacing-md); margin-bottom: var(--lb-spacing-lg); }\n.lb-bundle__product { display: flex; gap: var(--lb-spacing-md); align-items: center; padding: var(--lb-spacing-sm); border: 1px solid var(--lb-border-color); border-radius: var(--lb-border-radius); }\n.lb-bundle__product--selected { border-color: var(--lb-accent-color); }\n.lb-bundle__product-image { width: 64px; height: 64px; object-fit: cover; border-radius: calc(var(--lb-border-radius) - 2px); flex-shrink: 0; }\n.lb-bundle__product-info { flex: 1; min-width: 0; }\n.lb-bundle__product-title { margin: 0; font-weight: 500; }\n.lb-bundle__product-price { margin: 4px 0 0; color: var(--lb-secondary-color); }\n.lb-bundle__instructions { color: var(--lb-secondary-color); margin: 0 0 var(--lb-spacing-md); }\n.lb-bundle__tiers { margin-bottom: var(--lb-spacing-lg); }\n.lb-bundle__tier { display: flex; align-items: center; gap: var(--lb-spacing-md); padding: var(--lb-spacing-sm) var(--lb-spacing-md); border: 1px solid var(--lb-border-color); border-radius: var(--lb-border-radius); margin-bottom: var(--lb-spacing-sm); }\n.lb-bundle__tier--active { border-color: var(--lb-savings-color); }\n.lb-bundle__tier-savings { color: var(--lb-savings-color); font-weight: 600; }\n.lb-bundle__quantity-selector { margin-bottom: var(--lb-spacing-lg); }\n.lb-bundle__quantity-selector label { display: block; margin-bottom: var(--lb-spacing-sm); font-weight: 500; }\n.lb-bundle__quantity-control { display: inline-flex; align-items: center; border: 1px solid var(--lb-border-color); border-radius: var(--lb-border-radius); }\n.lb-bundle__quantity-control button { width: 32px; height: 32px; border: none; background: transparent; cursor: pointer; font-size: 1.1em; display: flex; align-items: center; justify-content: center; }\n.lb-bundle__quantity-input { width: 40px; text-align: center; border: none; border-left: 1px solid var(--lb-border-color); border-right: 1px solid var(--lb-border-color); height: 32px; font-size: var(--lb-font-size); -moz-appearance: textfield; }\n.lb-bundle__quantity-input::-webkit-outer-spin-button, .lb-bundle__quantity-input::-webkit-inner-spin-button { -webkit-appearance: none; }\n.lb-bundle__select-btn { padding: 6px 12px; border: 1px solid var(--lb-border-color); border-radius: var(--lb-border-radius); background: transparent; cursor: pointer; }\n.lb-bundle__select-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n.lb-bundle__cta { width: 100%; padding: 12px 24px; border: none; border-radius: var(--lb-button-radius); background: var(--lb-button-bg); color: var(--lb-button-text); font-size: 1em; font-weight: 600; cursor: pointer; transition: opacity 0.15s; }\n.lb-bundle__cta:hover:not(:disabled) { opacity: 0.9; }\n.lb-bundle__cta:disabled { opacity: 0.5; cursor: not-allowed; }\n.lb-bundle__error { color: var(--lb-error-color); margin: var(--lb-spacing-sm) 0; font-size: 0.9em; }\n.lb-bundle__validation { color: var(--lb-secondary-color); margin: var(--lb-spacing-sm) 0; font-size: 0.9em; }\n\n.lb-skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: lb-shimmer 1.5s infinite; border-radius: var(--lb-border-radius); }\n.lb-skeleton--title { height: 24px; width: 60%; margin-bottom: var(--lb-spacing-md); }\n.lb-skeleton--products { height: 200px; }\n@keyframes lb-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }\n`;\n","/**\n * Thank-you page snippet — fires purchase events for headless storefronts.\n *\n * Usage:\n * <script src=\"https://unpkg.com/@lime-bundles/widget/dist/lime-thankyou.js\"></script>\n * <script>\n * LimeBundles.trackPurchase({\n * shopDomain: 'my-store.myshopify.com',\n * orderId: 'gid://shopify/Order/12345',\n * lineItems: [\n * { bundleGid: '...', bundleType: 'fixed', price: 29.99, quantity: 1 }\n * ]\n * });\n * </script>\n */\n\ninterface PurchaseLineItem {\n bundleGid: string;\n bundleType: string;\n price: number;\n quantity: number;\n}\n\ninterface TrackPurchaseInput {\n shopDomain: string;\n orderId: string;\n lineItems: PurchaseLineItem[];\n appUrl?: string;\n}\n\n// Deduplication: track order+bundle combos we've already reported\nconst reportedPurchases = new Set<string>();\n\nexport function trackPurchase(input: TrackPurchaseInput): void {\n const appUrl = input.appUrl ?? `https://${input.shopDomain}`;\n\n // Group line items by bundleGid (merge quantities and prices)\n const bundleMap = new Map<string, { bundleType: string; revenue: number; lineItemCount: number }>();\n\n for (const item of input.lineItems) {\n if (!item.bundleGid) continue;\n\n const existing = bundleMap.get(item.bundleGid);\n if (existing) {\n existing.revenue += item.price * item.quantity;\n existing.lineItemCount += 1;\n } else {\n bundleMap.set(item.bundleGid, {\n bundleType: item.bundleType,\n revenue: item.price * item.quantity,\n lineItemCount: 1,\n });\n }\n }\n\n // Fire purchase events for each unique bundle\n for (const [bundleGid, data] of bundleMap) {\n const dedupKey = `${input.orderId}:${bundleGid}`;\n if (reportedPurchases.has(dedupKey)) continue;\n reportedPurchases.add(dedupKey);\n\n const payload = {\n shopDomain: input.shopDomain,\n eventType: \"bundle_purchased\",\n bundleGid,\n bundleType: data.bundleType,\n orderId: input.orderId,\n revenue: Math.round(data.revenue * 100) / 100,\n lineItemCount: data.lineItemCount,\n occurredAt: new Date().toISOString(),\n };\n\n // Fire-and-forget with sendBeacon fallback\n const url = `${appUrl}/api/analytics`;\n const body = JSON.stringify(payload);\n\n try {\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n }).catch(() => {\n // Fallback to sendBeacon\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, body);\n }\n });\n } catch {\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, body);\n }\n }\n }\n}\n","/**\n * @lime-bundles/widget — Vanilla JS Web Component for headless storefronts.\n *\n * Usage:\n * <script src=\"https://unpkg.com/@lime-bundles/widget/dist/lime-bundle.js\"></script>\n * <lime-bundle shop-domain=\"...\" storefront-token=\"...\" bundle-gid=\"...\"></lime-bundle>\n */\nimport { LimeBundleElement } from \"./lime-bundle\";\n\n// Register custom element\nif (typeof customElements !== \"undefined\" && !customElements.get(\"lime-bundle\")) {\n customElements.define(\"lime-bundle\", LimeBundleElement);\n}\n\nexport { LimeBundleElement };\nexport { trackPurchase } from \"./thankyou/index\";\n"],"mappings":"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","names":["formatMoney","escapeHtml","formatMoney","escapeHtml"]}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";var LimeBundles=(()=>{var A=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var Z=Object.prototype.hasOwnProperty;var ee=(t,e)=>{for(var n in e)A(t,n,{get:e[n],enumerable:!0})},te=(t,e,n,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of X(e))!Z.call(t,r)&&r!==n&&A(t,r,{get:()=>e[r],enumerable:!(a=K(e,r))||a.enumerable});return t};var ne=t=>te(A({},"__esModule",{value:!0}),t);var be={};ee(be,{LimeBundleElement:()=>E,trackPurchase:()=>Y});var L=class extends Error{constructor(t){super(t.map(e=>e.message).join("; ")),this.errors=t,this.name="StorefrontApiError"}},re="2025-10";function S(t){let e=t.apiVersion??re,n=`https://${t.shopDomain}/api/${e}/graphql.json`;return{async query(a,r){let o=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","X-Shopify-Storefront-Access-Token":t.accessToken},body:JSON.stringify({query:a,variables:r})});if(!o.ok)throw new L([{message:`Storefront API error: ${o.status} ${o.statusText}`}]);let i=await o.json();if(i.errors?.length)throw new L(i.errors);return i.data}}}var D=`#graphql
|
|
2
|
+
query BundleMetaobject($id: ID!) {
|
|
3
|
+
metaobject(id: $id) {
|
|
4
|
+
id
|
|
5
|
+
type
|
|
6
|
+
fields {
|
|
7
|
+
key
|
|
8
|
+
value
|
|
9
|
+
reference {
|
|
10
|
+
... on Product {
|
|
11
|
+
id
|
|
12
|
+
title
|
|
13
|
+
handle
|
|
14
|
+
featuredImage { url altText }
|
|
15
|
+
priceRange {
|
|
16
|
+
minVariantPrice { amount currencyCode }
|
|
17
|
+
maxVariantPrice { amount currencyCode }
|
|
18
|
+
}
|
|
19
|
+
variants(first: 100) {
|
|
20
|
+
nodes {
|
|
21
|
+
id
|
|
22
|
+
title
|
|
23
|
+
availableForSale
|
|
24
|
+
price { amount currencyCode }
|
|
25
|
+
compareAtPrice { amount currencyCode }
|
|
26
|
+
selectedOptions { name value }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
references(first: 50) {
|
|
32
|
+
nodes {
|
|
33
|
+
... on Product {
|
|
34
|
+
id
|
|
35
|
+
title
|
|
36
|
+
handle
|
|
37
|
+
featuredImage { url altText }
|
|
38
|
+
priceRange {
|
|
39
|
+
minVariantPrice { amount currencyCode }
|
|
40
|
+
maxVariantPrice { amount currencyCode }
|
|
41
|
+
}
|
|
42
|
+
variants(first: 100) {
|
|
43
|
+
nodes {
|
|
44
|
+
id
|
|
45
|
+
title
|
|
46
|
+
availableForSale
|
|
47
|
+
price { amount currencyCode }
|
|
48
|
+
compareAtPrice { amount currencyCode }
|
|
49
|
+
selectedOptions { name value }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
... on Collection {
|
|
54
|
+
id
|
|
55
|
+
title
|
|
56
|
+
handle
|
|
57
|
+
products(first: 50) {
|
|
58
|
+
nodes {
|
|
59
|
+
id
|
|
60
|
+
title
|
|
61
|
+
handle
|
|
62
|
+
featuredImage { url altText }
|
|
63
|
+
priceRange {
|
|
64
|
+
minVariantPrice { amount currencyCode }
|
|
65
|
+
maxVariantPrice { amount currencyCode }
|
|
66
|
+
}
|
|
67
|
+
variants(first: 100) {
|
|
68
|
+
nodes {
|
|
69
|
+
id
|
|
70
|
+
title
|
|
71
|
+
availableForSale
|
|
72
|
+
price { amount currencyCode }
|
|
73
|
+
compareAtPrice { amount currencyCode }
|
|
74
|
+
selectedOptions { name value }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
`,ae=new Set(["fixed","mix_match","volume"]),ie=new Set(["active"]);function $(t,e){let n=new Map(e.map(u=>[u.key,u])),a=n.get("title")?.value??"Bundle",r=n.get("bundle_type")?.value,o=n.get("status")?.value;if(!r||!ae.has(r)||!o||!ie.has(o))return null;let i=n.get("starts_at")?.value??null,s=n.get("ends_at")?.value??null,l=new Date;if(i&&new Date(i)>l||s&&new Date(s)<l)return null;let d=oe(n),p=se(n),c=le(n),f=de(n);return{id:t,title:a,bundleType:r,status:o,products:d,discountConfig:p,widgetConfig:c,volumeTiers:f,minQuantity:N(n,"min_quantity"),maxQuantity:N(n,"max_quantity"),startsAt:i,endsAt:s,discountLabel:n.get("discount_label")?.value??null,abTestId:n.get("ab_test_id")?.value??null,abTestConfig:k(n,"ab_test_config")}}function oe(t){let e=[],n=t.get("products");if(n?.reference&&"variants"in n.reference&&e.push(n.reference),n?.references?.nodes)for(let r of n.references.nodes)"variants"in r?e.push(r):"products"in r&&r.products?.nodes&&e.push(...r.products.nodes);let a=t.get("collection");if(a?.references?.nodes)for(let r of a.references.nodes)"products"in r&&r.products?.nodes&&e.push(...r.products.nodes);return e}function se(t){return{discountType:t.get("discount_type")?.value??"percentage",discountValue:parseFloat(t.get("discount_value")?.value??"0"),allowStacking:t.get("allow_stacking")?.value==="true"}}function le(t){let e=k(t,"widget_config");if(e&&typeof e=="object"){let n=e;return{primaryColor:n.primaryColor??null,ctaText:n.ctaText??null,outOfStockBehavior:n.outOfStockBehavior??"hide"}}return{primaryColor:null,ctaText:null,outOfStockBehavior:"hide"}}function de(t){let e=k(t,"volume_tiers");return Array.isArray(e)?e.filter(n=>typeof n=="object"&&n!==null).map(n=>({minQuantity:Number(n.minQuantity??0),discountType:n.discountType??"percentage",discountValue:Number(n.discountValue??0),label:n.label??null})):[]}function k(t,e){let n=t.get(e)?.value;if(!n)return null;try{return JSON.parse(n)}catch{return null}}function N(t,e){let n=t.get(e)?.value;if(!n)return null;let a=parseInt(n,10);return isNaN(a)?null:a}function B(t,e,n){return[...t].sort((r,o)=>r.minQuantity-o.minQuantity).map(r=>{let o=r.discountType==="percentage"?e*(r.discountValue/100):r.discountValue,i=Math.max(0,e-o),s=e-i,l=e>0?s/e*100:0,d=n>=r.minQuantity;return{tier:r,unitPrice:i,savings:s,savingsPercent:l,isActive:d}})}function q(t,e,n){return e!==null&&t<e?{valid:!1,totalQuantity:t,message:`Select at least ${e} item${e!==1?"s":""}`}:n!==null&&t>n?{valid:!1,totalQuantity:t,message:`Select at most ${n} item${n!==1?"s":""}`}:{valid:!0,totalQuantity:t,message:null}}function M(){return typeof window<"u"&&typeof window.Shopify=="object"?"ajax":"storefront"}var P=Promise.resolve();function O(t,e){return{async addLines(n){return new Promise(a=>{P=P.catch(()=>{}).then(async()=>{try{let r=n.map(i=>({id:ce(i.variantId),quantity:i.quantity,properties:{...i.properties,_lime_bundle_gid:t,_lime_bundle_type:e}})),o=await fetch("/cart/add.js",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({items:r})});if(!o.ok){let i=await o.json().catch(()=>({}));a({success:!1,error:i?.description?.toString()??`Cart error: ${o.status}`});return}a({success:!0})}catch(r){a({success:!1,error:r instanceof Error?r.message:"Cart add failed"})}})})}}}function ce(t){let e=t.match(/\/(\d+)$/);return e?parseInt(e[1],10):0}var ue=`#graphql
|
|
86
|
+
mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
|
|
87
|
+
cartLinesAdd(cartId: $cartId, lines: $lines) {
|
|
88
|
+
cart {
|
|
89
|
+
id
|
|
90
|
+
}
|
|
91
|
+
userErrors {
|
|
92
|
+
field
|
|
93
|
+
message
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
`,pe=`#graphql
|
|
98
|
+
mutation CartCreate($input: CartInput!) {
|
|
99
|
+
cartCreate(input: $input) {
|
|
100
|
+
cart {
|
|
101
|
+
id
|
|
102
|
+
}
|
|
103
|
+
userErrors {
|
|
104
|
+
field
|
|
105
|
+
message
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
`;function j(t,e,n,a){return{async addLines(r){try{let o=r.map(i=>({merchandiseId:i.variantId,quantity:i.quantity,attributes:[{key:"_lime_bundle_gid",value:e},{key:"_lime_bundle_type",value:n},...Object.entries(i.properties??{}).map(([s,l])=>({key:s,value:l}))]}));if(a){let i=await t.query(ue,{cartId:a,lines:o});return i.cartLinesAdd.userErrors.length>0?{success:!1,error:i.cartLinesAdd.userErrors[0].message}:{success:!0,cartId:a}}else{let i=await t.query(pe,{input:{lines:o}});return i.cartCreate.userErrors.length>0?{success:!1,error:i.cartCreate.userErrors[0].message}:{success:!0,cartId:i.cartCreate.cart?.id}}}catch(o){return{success:!1,error:o instanceof Error?o.message:"Cart add failed"}}}}}async function U(t,e){await V(t,{shopDomain:t.shopDomain,eventType:"bundle_impression",bundleGid:e.bundleGid,bundleType:e.bundleType,productId:e.productId,abTestId:e.abTestId,abVariant:e.abVariant,occurredAt:new Date().toISOString()})}async function F(t,e){await V(t,{shopDomain:t.shopDomain,eventType:"bundle_add_to_cart",...e,occurredAt:new Date().toISOString()})}function R(t,e){if(typeof IntersectionObserver>"u")return e(),()=>{};let n=new IntersectionObserver(a=>{for(let r of a)r.isIntersecting&&(e(),n.unobserve(r.target))},{threshold:.5});return n.observe(t),()=>n.disconnect()}async function V(t,e){let n=`${t.appUrl}/api/analytics`,a=JSON.stringify(e);try{(await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:a})).ok||(await new Promise(o=>setTimeout(o,2e3)),await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:a}))}catch{try{typeof navigator<"u"&&navigator.sendBeacon&&navigator.sendBeacon(n,a)}catch{}}}var fe=720*60*60;function C(t,e){let n=typeof t=="string"?parseFloat(t):t;try{return new Intl.NumberFormat(void 0,{style:"currency",currency:e}).format(n)}catch{return`${e} ${n.toFixed(2)}`}}function H(t,e,n){let a=e.products[0]?.priceRange.minVariantPrice.currencyCode??"USD",r=document.createElement("h3");if(r.className="lb-bundle__title",r.textContent=e.title,r.setAttribute("part","title"),t.appendChild(r),e.discountLabel){let s=document.createElement("span");s.className="lb-bundle__discount-badge",s.textContent=e.discountLabel,t.appendChild(s)}let o=document.createElement("div");o.className="lb-bundle__products";for(let s of e.products){let l=document.createElement("div");if(l.className="lb-bundle__product",l.setAttribute("part","product"),s.featuredImage){let p=document.createElement("img");p.src=s.featuredImage.url,p.alt=s.featuredImage.altText??s.title,p.className="lb-bundle__product-image",p.loading="lazy",l.appendChild(p)}let d=document.createElement("div");d.className="lb-bundle__product-info",d.innerHTML=`
|
|
110
|
+
<p class="lb-bundle__product-title">${G(s.title)}</p>
|
|
111
|
+
<p class="lb-bundle__product-price">${G(C(s.priceRange.minVariantPrice.amount,a))}</p>
|
|
112
|
+
`,l.appendChild(d),o.appendChild(l)}t.appendChild(o);let i=document.createElement("button");i.className="lb-bundle__cta",i.textContent=e.widgetConfig.ctaText??"Add Bundle to Cart",i.setAttribute("part","button"),i.addEventListener("click",async()=>{i.disabled=!0,i.textContent="Adding...";let s=e.products.filter(d=>d.variants.nodes.some(p=>p.availableForSale)).map(d=>({variantId:d.variants.nodes.find(c=>c.availableForSale).id,quantity:1})),l=await n(s);if(i.disabled=!1,i.textContent=e.widgetConfig.ctaText??"Add Bundle to Cart",!l.success){let d=document.createElement("p");d.className="lb-bundle__error",d.textContent=l.error??"Failed to add to cart",t.appendChild(d),setTimeout(()=>d.remove(),5e3)}}),t.appendChild(i)}function G(t){let e=document.createElement("div");return e.textContent=t,e.innerHTML}function Q(t,e,n){let a=e.products[0]?.priceRange.minVariantPrice.currencyCode??"USD",r=new Map,o=document.createElement("h3");o.className="lb-bundle__title",o.textContent=e.title,o.setAttribute("part","title"),t.appendChild(o);let i=document.createElement("p");i.className="lb-bundle__instructions",i.textContent=e.minQuantity&&e.maxQuantity?`Select ${e.minQuantity}\u2013${e.maxQuantity} items`:e.minQuantity?`Select at least ${e.minQuantity} items`:"Select your items",t.appendChild(i);let s=document.createElement("div");s.className="lb-bundle__products lb-bundle__products--selectable";for(let c of e.products){let f=c.variants.nodes.find(m=>m.availableForSale)??c.variants.nodes[0];if(!f)continue;let u=document.createElement("div");if(u.className="lb-bundle__product lb-bundle__product--selectable",c.featuredImage){let m=document.createElement("img");m.src=c.featuredImage.url,m.alt=c.featuredImage.altText??c.title,m.className="lb-bundle__product-image",m.loading="lazy",u.appendChild(m)}let g=document.createElement("div");g.className="lb-bundle__product-info",g.innerHTML=`
|
|
113
|
+
<p class="lb-bundle__product-title">${z(c.title)}</p>
|
|
114
|
+
<p class="lb-bundle__product-price">${z(C(f.price.amount,a))}</p>
|
|
115
|
+
`,u.appendChild(g);let b=document.createElement("button");b.className="lb-bundle__select-btn",b.textContent=f.availableForSale?"Select":"Sold out",b.disabled=!f.availableForSale,b.addEventListener("click",()=>{let m=c.id;r.has(m)?(r.delete(m),u.classList.remove("lb-bundle__product--selected"),b.textContent="Select"):(r.set(m,{variantId:f.id,quantity:1}),u.classList.add("lb-bundle__product--selected"),b.textContent="Selected"),p()}),u.appendChild(b),s.appendChild(u)}t.appendChild(s);let l=document.createElement("p");l.className="lb-bundle__validation",t.appendChild(l);let d=document.createElement("button");d.className="lb-bundle__cta",d.setAttribute("part","button"),d.disabled=!0,t.appendChild(d);function p(){let c=Array.from(r.values()).reduce((u,g)=>u+g.quantity,0),f=q(c,e.minQuantity,e.maxQuantity);d.disabled=!f.valid,d.textContent=e.widgetConfig.ctaText??`Add ${c} Items to Cart`,l.textContent=f.message??""}p(),d.addEventListener("click",async()=>{d.disabled=!0,d.textContent="Adding...";let c=Array.from(r.entries()).map(([,u])=>({variantId:u.variantId,quantity:u.quantity})),f=await n(c);if(p(),!f.success){let u=document.createElement("p");u.className="lb-bundle__error",u.textContent=f.error??"Failed to add to cart",t.appendChild(u),setTimeout(()=>u.remove(),5e3)}})}function z(t){let e=document.createElement("div");return e.textContent=t,e.innerHTML}function J(t,e,n){let a=e.products[0];if(!a)return;let r=parseFloat(a.priceRange.minVariantPrice.amount),o=a.priceRange.minVariantPrice.currencyCode,i=1,s=document.createElement("h3");s.className="lb-bundle__title",s.textContent=e.title,s.setAttribute("part","title"),t.appendChild(s);let l=document.createElement("div");if(l.className="lb-bundle__product lb-bundle__product--volume",a.featuredImage){let h=document.createElement("img");h.src=a.featuredImage.url,h.alt=a.featuredImage.altText??a.title,h.className="lb-bundle__product-image",h.loading="lazy",l.appendChild(h)}let d=document.createElement("div");d.className="lb-bundle__product-info",d.innerHTML=`
|
|
116
|
+
<p class="lb-bundle__product-title">${I(a.title)}</p>
|
|
117
|
+
<p class="lb-bundle__product-price">${I(C(r,o))} each</p>
|
|
118
|
+
`,l.appendChild(d),t.appendChild(l);let p=document.createElement("div");p.className="lb-bundle__tiers",p.setAttribute("role","table"),p.setAttribute("aria-label","Volume discounts"),t.appendChild(p);let c=document.createElement("div");c.className="lb-bundle__quantity-selector";let f=document.createElement("label");f.textContent="Quantity",c.appendChild(f);let u=document.createElement("div");u.className="lb-bundle__quantity-control";let g=document.createElement("button");g.textContent="\u2212",g.setAttribute("aria-label","Decrease quantity");let b=document.createElement("input");b.type="number",b.min="1",b.value="1",b.className="lb-bundle__quantity-input";let m=document.createElement("button");m.textContent="+",m.setAttribute("aria-label","Increase quantity"),u.append(g,b,m),c.appendChild(u),t.appendChild(c);let v=document.createElement("button");v.className="lb-bundle__cta",v.setAttribute("part","button"),t.appendChild(v);function T(){let h=B(e.volumeTiers,r,i);p.innerHTML="";for(let y of h){let _=document.createElement("div");_.className=`lb-bundle__tier${y.isActive?" lb-bundle__tier--active":""}`,_.setAttribute("role","row"),_.innerHTML=`
|
|
119
|
+
<span class="lb-bundle__tier-quantity" role="cell">${y.tier.minQuantity}+ items</span>
|
|
120
|
+
<span class="lb-bundle__tier-price" role="cell">${I(C(y.unitPrice,o))} each</span>
|
|
121
|
+
<span class="lb-bundle__tier-savings" role="cell">Save ${y.savingsPercent.toFixed(0)}%</span>
|
|
122
|
+
${y.tier.label?`<span class="lb-bundle__tier-label" role="cell">${I(y.tier.label)}</span>`:""}
|
|
123
|
+
`,p.appendChild(_)}v.textContent=e.widgetConfig.ctaText??`Add ${i} to Cart`}T(),g.addEventListener("click",()=>{i>1&&(i--,b.value=String(i),T())}),m.addEventListener("click",()=>{i++,b.value=String(i),T()}),b.addEventListener("change",()=>{let h=parseInt(b.value,10);!isNaN(h)&&h>0&&(i=h,T())}),v.addEventListener("click",async()=>{let h=a.variants.nodes.find(x=>x.availableForSale);if(!h)return;v.disabled=!0,v.textContent="Adding...";let y=[{variantId:h.id,quantity:i}],_=await n(y);if(v.disabled=!1,T(),!_.success){let x=document.createElement("p");x.className="lb-bundle__error",x.textContent=_.error??"Failed to add to cart",t.appendChild(x),setTimeout(()=>x.remove(),5e3)}})}function I(t){let e=document.createElement("div");return e.textContent=t,e.innerHTML}var w=`
|
|
124
|
+
:host {
|
|
125
|
+
display: block;
|
|
126
|
+
--lb-primary-color: #000;
|
|
127
|
+
--lb-secondary-color: #666;
|
|
128
|
+
--lb-accent-color: #2563eb;
|
|
129
|
+
--lb-background: #fff;
|
|
130
|
+
--lb-border-color: #e5e7eb;
|
|
131
|
+
--lb-border-radius: 8px;
|
|
132
|
+
--lb-font-family: inherit;
|
|
133
|
+
--lb-font-size: 14px;
|
|
134
|
+
--lb-spacing-sm: 8px;
|
|
135
|
+
--lb-spacing-md: 16px;
|
|
136
|
+
--lb-spacing-lg: 24px;
|
|
137
|
+
--lb-button-bg: var(--lb-accent-color);
|
|
138
|
+
--lb-button-text: #fff;
|
|
139
|
+
--lb-button-radius: var(--lb-border-radius);
|
|
140
|
+
--lb-savings-color: #16a34a;
|
|
141
|
+
--lb-error-color: #dc2626;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.lb-bundle {
|
|
145
|
+
font-family: var(--lb-font-family);
|
|
146
|
+
font-size: var(--lb-font-size);
|
|
147
|
+
color: var(--lb-primary-color);
|
|
148
|
+
background: var(--lb-background);
|
|
149
|
+
border: 1px solid var(--lb-border-color);
|
|
150
|
+
border-radius: var(--lb-border-radius);
|
|
151
|
+
padding: var(--lb-spacing-lg);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.lb-bundle__title { margin: 0 0 var(--lb-spacing-md); font-size: 1.25em; font-weight: 600; }
|
|
155
|
+
.lb-bundle__discount-badge { display: inline-block; background: var(--lb-savings-color); color: #fff; padding: 2px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 600; margin-bottom: var(--lb-spacing-md); }
|
|
156
|
+
.lb-bundle__products { display: grid; gap: var(--lb-spacing-md); margin-bottom: var(--lb-spacing-lg); }
|
|
157
|
+
.lb-bundle__product { display: flex; gap: var(--lb-spacing-md); align-items: center; padding: var(--lb-spacing-sm); border: 1px solid var(--lb-border-color); border-radius: var(--lb-border-radius); }
|
|
158
|
+
.lb-bundle__product--selected { border-color: var(--lb-accent-color); }
|
|
159
|
+
.lb-bundle__product-image { width: 64px; height: 64px; object-fit: cover; border-radius: calc(var(--lb-border-radius) - 2px); flex-shrink: 0; }
|
|
160
|
+
.lb-bundle__product-info { flex: 1; min-width: 0; }
|
|
161
|
+
.lb-bundle__product-title { margin: 0; font-weight: 500; }
|
|
162
|
+
.lb-bundle__product-price { margin: 4px 0 0; color: var(--lb-secondary-color); }
|
|
163
|
+
.lb-bundle__instructions { color: var(--lb-secondary-color); margin: 0 0 var(--lb-spacing-md); }
|
|
164
|
+
.lb-bundle__tiers { margin-bottom: var(--lb-spacing-lg); }
|
|
165
|
+
.lb-bundle__tier { display: flex; align-items: center; gap: var(--lb-spacing-md); padding: var(--lb-spacing-sm) var(--lb-spacing-md); border: 1px solid var(--lb-border-color); border-radius: var(--lb-border-radius); margin-bottom: var(--lb-spacing-sm); }
|
|
166
|
+
.lb-bundle__tier--active { border-color: var(--lb-savings-color); }
|
|
167
|
+
.lb-bundle__tier-savings { color: var(--lb-savings-color); font-weight: 600; }
|
|
168
|
+
.lb-bundle__quantity-selector { margin-bottom: var(--lb-spacing-lg); }
|
|
169
|
+
.lb-bundle__quantity-selector label { display: block; margin-bottom: var(--lb-spacing-sm); font-weight: 500; }
|
|
170
|
+
.lb-bundle__quantity-control { display: inline-flex; align-items: center; border: 1px solid var(--lb-border-color); border-radius: var(--lb-border-radius); }
|
|
171
|
+
.lb-bundle__quantity-control button { width: 32px; height: 32px; border: none; background: transparent; cursor: pointer; font-size: 1.1em; display: flex; align-items: center; justify-content: center; }
|
|
172
|
+
.lb-bundle__quantity-input { width: 40px; text-align: center; border: none; border-left: 1px solid var(--lb-border-color); border-right: 1px solid var(--lb-border-color); height: 32px; font-size: var(--lb-font-size); -moz-appearance: textfield; }
|
|
173
|
+
.lb-bundle__quantity-input::-webkit-outer-spin-button, .lb-bundle__quantity-input::-webkit-inner-spin-button { -webkit-appearance: none; }
|
|
174
|
+
.lb-bundle__select-btn { padding: 6px 12px; border: 1px solid var(--lb-border-color); border-radius: var(--lb-border-radius); background: transparent; cursor: pointer; }
|
|
175
|
+
.lb-bundle__select-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
176
|
+
.lb-bundle__cta { width: 100%; padding: 12px 24px; border: none; border-radius: var(--lb-button-radius); background: var(--lb-button-bg); color: var(--lb-button-text); font-size: 1em; font-weight: 600; cursor: pointer; transition: opacity 0.15s; }
|
|
177
|
+
.lb-bundle__cta:hover:not(:disabled) { opacity: 0.9; }
|
|
178
|
+
.lb-bundle__cta:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
179
|
+
.lb-bundle__error { color: var(--lb-error-color); margin: var(--lb-spacing-sm) 0; font-size: 0.9em; }
|
|
180
|
+
.lb-bundle__validation { color: var(--lb-secondary-color); margin: var(--lb-spacing-sm) 0; font-size: 0.9em; }
|
|
181
|
+
|
|
182
|
+
.lb-skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: lb-shimmer 1.5s infinite; border-radius: var(--lb-border-radius); }
|
|
183
|
+
.lb-skeleton--title { height: 24px; width: 60%; margin-bottom: var(--lb-spacing-md); }
|
|
184
|
+
.lb-skeleton--products { height: 200px; }
|
|
185
|
+
@keyframes lb-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
|
|
186
|
+
`;var E=class extends HTMLElement{static observedAttributes=["shop-domain","storefront-token","bundle-gid","cart-id","app-url","analytics","locale"];shadow;bundle=null;abortController=null;impressionCleanup=null;constructor(){super(),this.shadow=this.attachShadow({mode:"open"})}connectedCallback(){this.render(),this.fetchBundle()}disconnectedCallback(){this.abortController?.abort(),this.teardownImpression()}teardownImpression(){this.impressionCleanup?.(),this.impressionCleanup=null}attributeChangedCallback(e,n,a){n===a||!this.isConnected||(e==="bundle-gid"||e==="shop-domain"||e==="storefront-token")&&this.shopDomain&&this.storefrontToken&&this.bundleGid&&this.fetchBundle()}get shopDomain(){return this.getAttribute("shop-domain")??""}get storefrontToken(){return this.getAttribute("storefront-token")??""}get bundleGid(){return this.getAttribute("bundle-gid")??""}get cartId(){return this.getAttribute("cart-id")??void 0}get appUrl(){return this.getAttribute("app-url")??""}get analyticsEnabled(){return this.getAttribute("analytics")!=="false"}async fetchBundle(){if(!this.shopDomain||!this.storefrontToken||!this.bundleGid){this.renderError("Missing required attributes: shop-domain, storefront-token, bundle-gid");return}this.abortController?.abort();let e=new AbortController;this.abortController=e,this.renderLoading();try{let a=await S({shopDomain:this.shopDomain,accessToken:this.storefrontToken}).query(D,{id:this.bundleGid});if(e.signal.aborted)return;if(!a.metaobject){this.bundle=null,this.teardownImpression(),this.renderError("Bundle not found");return}if(this.bundle=$(a.metaobject.id,a.metaobject.fields),!this.bundle){this.teardownImpression(),this.renderError("Bundle is not active or has expired");return}this.renderBundle(),this.setupImpression()}catch(n){if(e.signal.aborted)return;this.bundle=null,this.teardownImpression(),this.renderError(n instanceof Error?n.message:"Failed to load bundle")}}renderBundle(){if(!this.bundle)return;let e=document.createElement("div");e.className="lb-bundle",e.setAttribute("role","region"),e.setAttribute("aria-label",this.bundle.title);let n=async r=>{let i=M()==="ajax"?O(this.bundleGid,this.bundle.bundleType):j(S({shopDomain:this.shopDomain,accessToken:this.storefrontToken}),this.bundleGid,this.bundle.bundleType,this.cartId),s;try{s=await i.addLines(r)}catch(l){s={success:!1,error:l instanceof Error?l.message:"Cart add failed"}}if(s.success&&(this.dispatchEvent(new CustomEvent("lime-bundle:add-to-cart",{detail:{items:r,cartId:s.cartId},bubbles:!0,composed:!0})),this.analyticsEnabled&&this.appUrl)){let l=r.reduce((p,c)=>p+c.quantity,0),d=r.reduce((p,c)=>{let u=this.bundle.products.find(b=>b.variants.nodes.some(m=>m.id===c.variantId))?.variants.nodes.find(b=>b.id===c.variantId),g=u?parseFloat(u.price.amount):0;return p+g*c.quantity},0);F({shopDomain:this.shopDomain,appUrl:this.appUrl},{bundleGid:this.bundleGid,bundleType:this.bundle.bundleType,productId:this.bundle.products[0]?.id??"",quantity:l,totalPrice:Math.round(d*100)/100})}return s};switch(this.bundle.bundleType){case"fixed":H(e,this.bundle,n);break;case"mix_match":Q(e,this.bundle,n);break;case"volume":J(e,this.bundle,n);break}this.shadow.innerHTML="";let a=document.createElement("style");a.textContent=w,this.shadow.appendChild(a),this.shadow.appendChild(e),this.dispatchEvent(new CustomEvent("lime-bundle:loaded",{detail:{bundleType:this.bundle.bundleType,title:this.bundle.title},bubbles:!0,composed:!0}))}setupImpression(){!this.analyticsEnabled||!this.bundle||!this.appUrl||(this.impressionCleanup?.(),this.impressionCleanup=R(this,()=>{U({shopDomain:this.shopDomain,appUrl:this.appUrl},{bundleGid:this.bundleGid,bundleType:this.bundle.bundleType})}))}renderLoading(){this.shadow.innerHTML=`
|
|
187
|
+
<style>${w}</style>
|
|
188
|
+
<div class="lb-bundle lb-bundle--loading">
|
|
189
|
+
<div class="lb-skeleton lb-skeleton--title"></div>
|
|
190
|
+
<div class="lb-skeleton lb-skeleton--products"></div>
|
|
191
|
+
</div>
|
|
192
|
+
`}renderError(e){this.shadow.innerHTML="",this.dispatchEvent(new CustomEvent("lime-bundle:error",{detail:{message:e,code:"LOAD_ERROR"},bubbles:!0,composed:!0}))}render(){this.shadow.innerHTML=`<style>${w}</style>`}};var W=new Set;function Y(t){let e=t.appUrl??`https://${t.shopDomain}`,n=new Map;for(let a of t.lineItems){if(!a.bundleGid)continue;let r=n.get(a.bundleGid);r?(r.revenue+=a.price*a.quantity,r.lineItemCount+=1):n.set(a.bundleGid,{bundleType:a.bundleType,revenue:a.price*a.quantity,lineItemCount:1})}for(let[a,r]of n){let o=`${t.orderId}:${a}`;if(W.has(o))continue;W.add(o);let i={shopDomain:t.shopDomain,eventType:"bundle_purchased",bundleGid:a,bundleType:r.bundleType,orderId:t.orderId,revenue:Math.round(r.revenue*100)/100,lineItemCount:r.lineItemCount,occurredAt:new Date().toISOString()},s=`${e}/api/analytics`,l=JSON.stringify(i);try{fetch(s,{method:"POST",headers:{"Content-Type":"application/json"},body:l}).catch(()=>{typeof navigator<"u"&&navigator.sendBeacon&&navigator.sendBeacon(s,l)})}catch{typeof navigator<"u"&&navigator.sendBeacon&&navigator.sendBeacon(s,l)}}}typeof customElements<"u"&&!customElements.get("lime-bundle")&&customElements.define("lime-bundle",E);return ne(be);})();
|
|
193
|
+
//# sourceMappingURL=lime-bundle.global.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../../core/src/storefront-api/client.ts","../../core/src/storefront-api/queries.ts","../../core/src/bundle/parser.ts","../../core/src/bundle/tier-calculator.ts","../../core/src/bundle/validator.ts","../../core/src/cart/detector.ts","../../core/src/cart/ajax-cart.ts","../../core/src/cart/storefront-cart.ts","../../core/src/analytics/reporter.ts","../../core/src/ab-test/assigner.ts","../../core/src/utils/money.ts","../src/renderers/fixed.ts","../src/renderers/mix-match.ts","../src/renderers/volume.ts","../src/styles/widget-styles.ts","../src/lime-bundle.ts","../src/thankyou/index.ts"],"sourcesContent":["/**\n * @lime-bundles/widget — Vanilla JS Web Component for headless storefronts.\n *\n * Usage:\n * <script src=\"https://unpkg.com/@lime-bundles/widget/dist/lime-bundle.js\"></script>\n * <lime-bundle shop-domain=\"...\" storefront-token=\"...\" bundle-gid=\"...\"></lime-bundle>\n */\nimport { LimeBundleElement } from \"./lime-bundle\";\n\n// Register custom element\nif (typeof customElements !== \"undefined\" && !customElements.get(\"lime-bundle\")) {\n customElements.define(\"lime-bundle\", LimeBundleElement);\n}\n\nexport { LimeBundleElement };\nexport { trackPurchase } from \"./thankyou/index\";\n","/**\n * Lightweight Storefront API client.\n * No framework dependencies — just fetch.\n */\n\nexport class StorefrontApiError extends Error {\n constructor(\n public readonly errors: Array<{ message: string; locations?: unknown[] }>,\n ) {\n super(errors.map((e) => e.message).join(\"; \"));\n this.name = \"StorefrontApiError\";\n }\n}\n\nexport interface StorefrontClient {\n query<T = unknown>(\n query: string,\n variables?: Record<string, unknown>,\n ): Promise<T>;\n}\n\nexport interface StorefrontClientConfig {\n shopDomain: string;\n accessToken: string;\n apiVersion?: string;\n}\n\nconst DEFAULT_API_VERSION = \"2025-10\";\n\nexport function createStorefrontClient(\n config: StorefrontClientConfig,\n): StorefrontClient {\n const version = config.apiVersion ?? DEFAULT_API_VERSION;\n const endpoint = `https://${config.shopDomain}/api/${version}/graphql.json`;\n\n return {\n async query<T = unknown>(\n query: string,\n variables?: Record<string, unknown>,\n ): Promise<T> {\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Shopify-Storefront-Access-Token\": config.accessToken,\n },\n body: JSON.stringify({ query, variables }),\n });\n\n if (!response.ok) {\n throw new StorefrontApiError([\n { message: `Storefront API error: ${response.status} ${response.statusText}` },\n ]);\n }\n\n const json = (await response.json()) as {\n data?: T;\n errors?: Array<{ message: string }>;\n };\n\n if (json.errors?.length) {\n throw new StorefrontApiError(json.errors);\n }\n\n return json.data as T;\n },\n };\n}\n","/**\n * Storefront API GraphQL queries for bundle metaobjects and products.\n */\n\nexport const BUNDLE_METAOBJECT_QUERY = `#graphql\n query BundleMetaobject($id: ID!) {\n metaobject(id: $id) {\n id\n type\n fields {\n key\n value\n reference {\n ... on Product {\n id\n title\n handle\n featuredImage { url altText }\n priceRange {\n minVariantPrice { amount currencyCode }\n maxVariantPrice { amount currencyCode }\n }\n variants(first: 100) {\n nodes {\n id\n title\n availableForSale\n price { amount currencyCode }\n compareAtPrice { amount currencyCode }\n selectedOptions { name value }\n }\n }\n }\n }\n references(first: 50) {\n nodes {\n ... on Product {\n id\n title\n handle\n featuredImage { url altText }\n priceRange {\n minVariantPrice { amount currencyCode }\n maxVariantPrice { amount currencyCode }\n }\n variants(first: 100) {\n nodes {\n id\n title\n availableForSale\n price { amount currencyCode }\n compareAtPrice { amount currencyCode }\n selectedOptions { name value }\n }\n }\n }\n ... on Collection {\n id\n title\n handle\n products(first: 50) {\n nodes {\n id\n title\n handle\n featuredImage { url altText }\n priceRange {\n minVariantPrice { amount currencyCode }\n maxVariantPrice { amount currencyCode }\n }\n variants(first: 100) {\n nodes {\n id\n title\n availableForSale\n price { amount currencyCode }\n compareAtPrice { amount currencyCode }\n selectedOptions { name value }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n`;\n","/**\n * Parse metaobject fields into a typed ParsedBundle.\n * Maps Storefront API metaobject field keys to structured data.\n */\nimport type { MetaobjectField, Product } from \"../storefront-api/types\";\nimport type {\n ParsedBundle,\n BundleType,\n BundleStatus,\n DiscountConfig,\n WidgetConfig,\n VolumeTier,\n} from \"./types\";\n\nconst VALID_BUNDLE_TYPES = new Set([\"fixed\", \"mix_match\", \"volume\"]);\nconst ACTIVE_STATUSES = new Set([\"active\"]);\n\n/**\n * Parse a metaobject's fields array into a structured ParsedBundle.\n * Returns null if the bundle is not renderable (invalid type, inactive, expired).\n */\nexport function parseMetaobjectBundle(\n metaobjectId: string,\n fields: MetaobjectField[],\n): ParsedBundle | null {\n const fieldMap = new Map(fields.map((f) => [f.key, f]));\n\n const title = fieldMap.get(\"title\")?.value ?? \"Bundle\";\n const bundleType = fieldMap.get(\"bundle_type\")?.value as BundleType | null;\n const status = fieldMap.get(\"status\")?.value as BundleStatus | null;\n\n // Validate bundle type\n if (!bundleType || !VALID_BUNDLE_TYPES.has(bundleType)) return null;\n\n // Only render active bundles\n if (!status || !ACTIVE_STATUSES.has(status)) return null;\n\n // Check schedule\n const startsAt = fieldMap.get(\"starts_at\")?.value ?? null;\n const endsAt = fieldMap.get(\"ends_at\")?.value ?? null;\n const now = new Date();\n\n if (startsAt && new Date(startsAt) > now) return null;\n if (endsAt && new Date(endsAt) < now) return null;\n\n // Resolve products from references\n const products = resolveProducts(fieldMap);\n\n // Parse discount config\n const discountConfig = parseDiscountConfig(fieldMap);\n\n // Parse widget config\n const widgetConfig = parseWidgetConfig(fieldMap);\n\n // Parse volume tiers\n const volumeTiers = parseVolumeTiers(fieldMap);\n\n return {\n id: metaobjectId,\n title,\n bundleType,\n status,\n products,\n discountConfig,\n widgetConfig,\n volumeTiers,\n minQuantity: parseIntField(fieldMap, \"min_quantity\"),\n maxQuantity: parseIntField(fieldMap, \"max_quantity\"),\n startsAt,\n endsAt,\n discountLabel: fieldMap.get(\"discount_label\")?.value ?? null,\n abTestId: fieldMap.get(\"ab_test_id\")?.value ?? null,\n abTestConfig: parseJsonField(fieldMap, \"ab_test_config\"),\n };\n}\n\nfunction resolveProducts(fieldMap: Map<string, MetaobjectField>): Product[] {\n const products: Product[] = [];\n\n // Single product reference\n const productsField = fieldMap.get(\"products\");\n if (productsField?.reference && \"variants\" in productsField.reference) {\n products.push(productsField.reference as Product);\n }\n\n // Multiple product/collection references\n if (productsField?.references?.nodes) {\n for (const node of productsField.references.nodes) {\n if (\"variants\" in node) {\n products.push(node as Product);\n } else if (\"products\" in node && node.products?.nodes) {\n // Collection reference — flatten products\n products.push(...node.products.nodes);\n }\n }\n }\n\n // Also check collection field\n const collectionField = fieldMap.get(\"collection\");\n if (collectionField?.references?.nodes) {\n for (const node of collectionField.references.nodes) {\n if (\"products\" in node && node.products?.nodes) {\n products.push(...node.products.nodes);\n }\n }\n }\n\n return products;\n}\n\nfunction parseDiscountConfig(\n fieldMap: Map<string, MetaobjectField>,\n): DiscountConfig {\n return {\n discountType:\n (fieldMap.get(\"discount_type\")?.value as \"percentage\" | \"fixed_amount\") ??\n \"percentage\",\n discountValue: parseFloat(fieldMap.get(\"discount_value\")?.value ?? \"0\"),\n allowStacking: fieldMap.get(\"allow_stacking\")?.value === \"true\",\n };\n}\n\nfunction parseWidgetConfig(\n fieldMap: Map<string, MetaobjectField>,\n): WidgetConfig {\n const raw = parseJsonField(fieldMap, \"widget_config\");\n if (raw && typeof raw === \"object\") {\n const obj = raw as Record<string, unknown>;\n return {\n primaryColor: (obj.primaryColor as string) ?? null,\n ctaText: (obj.ctaText as string) ?? null,\n outOfStockBehavior:\n (obj.outOfStockBehavior as \"hide\" | \"disable\" | \"show\") ?? \"hide\",\n };\n }\n return { primaryColor: null, ctaText: null, outOfStockBehavior: \"hide\" };\n}\n\nfunction parseVolumeTiers(\n fieldMap: Map<string, MetaobjectField>,\n): VolumeTier[] {\n const raw = parseJsonField(fieldMap, \"volume_tiers\");\n if (!Array.isArray(raw)) return [];\n return raw\n .filter(\n (t): t is Record<string, unknown> => typeof t === \"object\" && t !== null,\n )\n .map((t) => ({\n minQuantity: Number(t.minQuantity ?? 0),\n discountType: (t.discountType as \"percentage\" | \"fixed_amount\") ?? \"percentage\",\n discountValue: Number(t.discountValue ?? 0),\n label: (t.label as string) ?? null,\n }));\n}\n\nfunction parseJsonField(\n fieldMap: Map<string, MetaobjectField>,\n key: string,\n): unknown {\n const value = fieldMap.get(key)?.value;\n if (!value) return null;\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n}\n\nfunction parseIntField(\n fieldMap: Map<string, MetaobjectField>,\n key: string,\n): number | null {\n const value = fieldMap.get(key)?.value;\n if (!value) return null;\n const num = parseInt(value, 10);\n return isNaN(num) ? null : num;\n}\n","/**\n * Volume tier savings calculation.\n */\nimport type { VolumeTier } from \"./types\";\n\nexport interface TierSavings {\n tier: VolumeTier;\n unitPrice: number;\n savings: number;\n savingsPercent: number;\n isActive: boolean;\n}\n\n/**\n * Calculate savings for each tier at a given base price and quantity.\n */\nexport function calculateTierSavings(\n tiers: VolumeTier[],\n basePrice: number,\n currentQuantity: number,\n): TierSavings[] {\n // Sort tiers by minQuantity ascending\n const sorted = [...tiers].sort((a, b) => a.minQuantity - b.minQuantity);\n\n return sorted.map((tier) => {\n const discount =\n tier.discountType === \"percentage\"\n ? basePrice * (tier.discountValue / 100)\n : tier.discountValue;\n\n const unitPrice = Math.max(0, basePrice - discount);\n const savings = basePrice - unitPrice;\n const savingsPercent = basePrice > 0 ? (savings / basePrice) * 100 : 0;\n const isActive = currentQuantity >= tier.minQuantity;\n\n return { tier, unitPrice, savings, savingsPercent, isActive };\n });\n}\n\n/**\n * Get the active tier for a given quantity.\n * Returns the highest tier where quantity >= minQuantity.\n */\nexport function getActiveTier(\n tiers: VolumeTier[],\n quantity: number,\n): VolumeTier | null {\n const sorted = [...tiers].sort((a, b) => b.minQuantity - a.minQuantity);\n return sorted.find((t) => quantity >= t.minQuantity) ?? null;\n}\n","/**\n * Quantity constraint validation for mix-and-match bundles.\n */\n\nexport interface QuantityValidation {\n valid: boolean;\n totalQuantity: number;\n message: string | null;\n}\n\nexport function validateQuantity(\n totalQuantity: number,\n minQuantity: number | null,\n maxQuantity: number | null,\n): QuantityValidation {\n if (minQuantity !== null && totalQuantity < minQuantity) {\n return {\n valid: false,\n totalQuantity,\n message: `Select at least ${minQuantity} item${minQuantity !== 1 ? \"s\" : \"\"}`,\n };\n }\n if (maxQuantity !== null && totalQuantity > maxQuantity) {\n return {\n valid: false,\n totalQuantity,\n message: `Select at most ${maxQuantity} item${maxQuantity !== 1 ? \"s\" : \"\"}`,\n };\n }\n return { valid: true, totalQuantity, message: null };\n}\n","/**\n * Cart API auto-detection.\n * Checks for window.Shopify to determine if AJAX Cart API is available.\n */\n\nexport type CartApiType = \"ajax\" | \"storefront\";\n\nexport function detectCartApi(): CartApiType {\n if (\n typeof window !== \"undefined\" &&\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n typeof (window as any).Shopify === \"object\"\n ) {\n return \"ajax\";\n }\n return \"storefront\";\n}\n","/**\n * Shopify AJAX Cart API (/cart/add.js) wrapper.\n * Used when window.Shopify is detected (Shopify-hosted storefronts).\n *\n * Sets _lime_bundle_gid and _lime_bundle_type as line item properties\n * so the thank-you page snippet can identify bundles in the completed order.\n */\nimport type { CartApi, CartLineItem, AddToCartResult } from \"./types\";\n\n// Promise chain serialization to prevent concurrent read-modify-write races\n// (per institutional learning: shopify-cart-attribute-read-modify-write-patterns.md)\nlet cartWriteChain = Promise.resolve();\n\nexport function createAjaxCartApi(\n bundleGid: string,\n bundleType: string,\n): CartApi {\n return {\n async addLines(items: CartLineItem[]): Promise<AddToCartResult> {\n return new Promise((resolve) => {\n cartWriteChain = cartWriteChain\n .catch(() => {}) // Swallow prior failure so chain continues\n .then(async () => {\n try {\n const cartItems = items.map((item) => ({\n id: extractNumericId(item.variantId),\n quantity: item.quantity,\n properties: {\n ...item.properties,\n _lime_bundle_gid: bundleGid,\n _lime_bundle_type: bundleType,\n },\n }));\n\n const response = await fetch(\"/cart/add.js\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ items: cartItems }),\n });\n\n if (!response.ok) {\n const body = await response.json().catch(() => ({}));\n resolve({\n success: false,\n error:\n (body as Record<string, unknown>)?.description?.toString() ??\n `Cart error: ${response.status}`,\n });\n return;\n }\n\n resolve({ success: true });\n } catch (err) {\n resolve({\n success: false,\n error: err instanceof Error ? err.message : \"Cart add failed\",\n });\n }\n });\n });\n },\n };\n}\n\n/**\n * Extract numeric ID from a Shopify GID.\n * \"gid://shopify/ProductVariant/12345\" → 12345\n */\nfunction extractNumericId(gid: string): number {\n const match = gid.match(/\\/(\\d+)$/);\n return match ? parseInt(match[1], 10) : 0;\n}\n","/**\n * Shopify Storefront API Cart mutations wrapper.\n * Used on headless storefronts where window.Shopify is not available.\n */\nimport type { StorefrontClient } from \"../storefront-api/client\";\nimport type { CartApi, CartLineItem, AddToCartResult } from \"./types\";\n\nconst CART_LINES_ADD_MUTATION = `#graphql\n mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {\n cartLinesAdd(cartId: $cartId, lines: $lines) {\n cart {\n id\n }\n userErrors {\n field\n message\n }\n }\n }\n`;\n\nconst CART_CREATE_MUTATION = `#graphql\n mutation CartCreate($input: CartInput!) {\n cartCreate(input: $input) {\n cart {\n id\n }\n userErrors {\n field\n message\n }\n }\n }\n`;\n\nexport function createStorefrontCartApi(\n client: StorefrontClient,\n bundleGid: string,\n bundleType: string,\n cartId?: string,\n): CartApi {\n return {\n async addLines(items: CartLineItem[]): Promise<AddToCartResult> {\n try {\n const lines = items.map((item) => ({\n merchandiseId: item.variantId,\n quantity: item.quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundleGid },\n { key: \"_lime_bundle_type\", value: bundleType },\n ...Object.entries(item.properties ?? {}).map(([key, value]) => ({\n key,\n value,\n })),\n ],\n }));\n\n if (cartId) {\n // Add to existing cart\n const data = await client.query<{\n cartLinesAdd: {\n cart: { id: string } | null;\n userErrors: Array<{ message: string }>;\n };\n }>(CART_LINES_ADD_MUTATION, { cartId, lines });\n\n if (data.cartLinesAdd.userErrors.length > 0) {\n return {\n success: false,\n error: data.cartLinesAdd.userErrors[0].message,\n };\n }\n return { success: true, cartId };\n } else {\n // Create new cart\n const data = await client.query<{\n cartCreate: {\n cart: { id: string } | null;\n userErrors: Array<{ message: string }>;\n };\n }>(CART_CREATE_MUTATION, { input: { lines } });\n\n if (data.cartCreate.userErrors.length > 0) {\n return {\n success: false,\n error: data.cartCreate.userErrors[0].message,\n };\n }\n return {\n success: true,\n cartId: data.cartCreate.cart?.id,\n };\n }\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : \"Cart add failed\",\n };\n }\n },\n };\n}\n","/**\n * Analytics event reporter for headless widgets.\n * Fires events to /api/analytics with single retry on failure.\n */\n\nexport interface AnalyticsConfig {\n shopDomain: string;\n appUrl: string; // Base URL of the Lime Bundles app\n}\n\nexport async function reportImpression(\n config: AnalyticsConfig,\n event: {\n bundleGid: string;\n bundleType: string;\n productId?: string;\n abTestId?: string;\n abVariant?: string;\n },\n): Promise<void> {\n await sendEvent(config, {\n shopDomain: config.shopDomain,\n eventType: \"bundle_impression\",\n bundleGid: event.bundleGid,\n bundleType: event.bundleType,\n productId: event.productId,\n abTestId: event.abTestId,\n abVariant: event.abVariant,\n occurredAt: new Date().toISOString(),\n });\n}\n\nexport async function reportAddToCart(\n config: AnalyticsConfig,\n event: {\n bundleGid: string;\n bundleType: string;\n productId: string;\n quantity: number;\n totalPrice: number;\n abTestId?: string;\n abVariant?: string;\n },\n): Promise<void> {\n await sendEvent(config, {\n shopDomain: config.shopDomain,\n eventType: \"bundle_add_to_cart\",\n ...event,\n occurredAt: new Date().toISOString(),\n });\n}\n\n/**\n * IntersectionObserver-based impression tracking.\n * Fires once when element is 50% visible, then unobserves.\n */\nexport function observeImpression(\n element: Element,\n callback: () => void,\n): () => void {\n if (typeof IntersectionObserver === \"undefined\") {\n // Fallback: fire immediately if IntersectionObserver not available\n callback();\n return () => {};\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n callback();\n observer.unobserve(entry.target);\n }\n }\n },\n { threshold: 0.5 },\n );\n\n observer.observe(element);\n return () => observer.disconnect();\n}\n\n// --- Internal ---\n\nasync function sendEvent(\n config: AnalyticsConfig,\n payload: Record<string, unknown>,\n): Promise<void> {\n const url = `${config.appUrl}/api/analytics`;\n const body = JSON.stringify(payload);\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n });\n\n if (!response.ok) {\n // Single retry after 2 seconds\n await new Promise((r) => setTimeout(r, 2000));\n await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n });\n }\n } catch {\n // Fire-and-forget — never block UX\n try {\n // Use sendBeacon as last resort\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, body);\n }\n } catch {\n // Silently drop\n }\n }\n}\n","/**\n * A/B test variant assignment for headless widgets.\n * Calls /api/ab-assign and manages the lb_session cookie.\n */\n\nconst SESSION_COOKIE_NAME = \"lb_session\";\nconst SESSION_COOKIE_MAX_AGE = 30 * 24 * 60 * 60; // 30 days\n\nexport interface ABTestAssignment {\n variant: \"A\" | \"B\";\n testId: string;\n}\n\n/**\n * Get or assign an A/B test variant.\n * Reads/creates the lb_session cookie and calls /api/ab-assign.\n */\nexport async function getABTestAssignment(\n appUrl: string,\n shopDomain: string,\n testId: string,\n): Promise<ABTestAssignment | null> {\n if (typeof document === \"undefined\") return null; // SSR guard\n\n const sessionId = getOrCreateSessionId();\n\n // Deterministic assignment: fnv1a(sessionId + testId) % 2\n const variant = fnv1aVariant(sessionId, testId);\n\n // Fire-and-forget assignment persistence\n try {\n fetch(`${appUrl}/api/ab-assign`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n shopDomain,\n testId,\n sessionId,\n variant,\n }),\n }).catch(() => {}); // Swallow errors — assignment is best-effort\n } catch {\n // Silently ignore\n }\n\n return { variant, testId };\n}\n\nfunction getOrCreateSessionId(): string {\n if (typeof document === \"undefined\") return generateUUID();\n\n const cookies = document.cookie.split(\";\").map((c) => c.trim());\n const existing = cookies\n .find((c) => c.startsWith(`${SESSION_COOKIE_NAME}=`))\n ?.split(\"=\")[1];\n\n if (existing) return existing;\n\n const id = generateUUID();\n document.cookie = `${SESSION_COOKIE_NAME}=${id}; path=/; max-age=${SESSION_COOKIE_MAX_AGE}; SameSite=Lax`;\n return id;\n}\n\nfunction generateUUID(): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * FNV-1a hash-based deterministic variant assignment.\n * Must match the server-side expectedVariant() in ab-test-assignment.server.ts.\n */\nfunction fnv1aVariant(sessionId: string, testId: string): \"A\" | \"B\" {\n const input = sessionId + \":\" + testId;\n let hash = 0x811c9dc5; // FNV offset basis\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193); // FNV prime\n }\n return (hash >>> 0) % 2 === 0 ? \"A\" : \"B\";\n}\n","/**\n * Price formatting utilities.\n */\n\nexport function formatMoney(amount: string | number, currencyCode: string): string {\n const num = typeof amount === \"string\" ? parseFloat(amount) : amount;\n\n try {\n return new Intl.NumberFormat(undefined, {\n style: \"currency\",\n currency: currencyCode,\n }).format(num);\n } catch {\n return `${currencyCode} ${num.toFixed(2)}`;\n }\n}\n\nexport function calculateDiscount(\n price: number,\n discountType: \"percentage\" | \"fixed_amount\",\n discountValue: number,\n): number {\n if (discountType === \"percentage\") {\n return Math.max(0, price * (1 - discountValue / 100));\n }\n return Math.max(0, price - discountValue);\n}\n","/**\n * DOM renderer for fixed bundles.\n */\nimport { formatMoney, type ParsedBundle, type CartLineItem, type AddToCartResult } from \"@lime-bundles/core\";\n\nexport function renderFixedBundle(\n container: HTMLElement,\n bundle: ParsedBundle,\n addToCart: (items: CartLineItem[]) => Promise<AddToCartResult>,\n) {\n const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n // Title\n const title = document.createElement(\"h3\");\n title.className = \"lb-bundle__title\";\n title.textContent = bundle.title;\n title.setAttribute(\"part\", \"title\");\n container.appendChild(title);\n\n // Discount badge\n if (bundle.discountLabel) {\n const badge = document.createElement(\"span\");\n badge.className = \"lb-bundle__discount-badge\";\n badge.textContent = bundle.discountLabel;\n container.appendChild(badge);\n }\n\n // Products\n const productsDiv = document.createElement(\"div\");\n productsDiv.className = \"lb-bundle__products\";\n\n for (const product of bundle.products) {\n const productEl = document.createElement(\"div\");\n productEl.className = \"lb-bundle__product\";\n productEl.setAttribute(\"part\", \"product\");\n\n if (product.featuredImage) {\n const img = document.createElement(\"img\");\n img.src = product.featuredImage.url;\n img.alt = product.featuredImage.altText ?? product.title;\n img.className = \"lb-bundle__product-image\";\n img.loading = \"lazy\";\n productEl.appendChild(img);\n }\n\n const info = document.createElement(\"div\");\n info.className = \"lb-bundle__product-info\";\n info.innerHTML = `\n <p class=\"lb-bundle__product-title\">${escapeHtml(product.title)}</p>\n <p class=\"lb-bundle__product-price\">${escapeHtml(formatMoney(product.priceRange.minVariantPrice.amount, currency))}</p>\n `;\n productEl.appendChild(info);\n productsDiv.appendChild(productEl);\n }\n container.appendChild(productsDiv);\n\n // CTA button\n const button = document.createElement(\"button\");\n button.className = \"lb-bundle__cta\";\n button.textContent = bundle.widgetConfig.ctaText ?? \"Add Bundle to Cart\";\n button.setAttribute(\"part\", \"button\");\n\n button.addEventListener(\"click\", async () => {\n button.disabled = true;\n button.textContent = \"Adding...\";\n\n const items: CartLineItem[] = 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 { variantId: variant.id, quantity: 1 };\n });\n\n const result = await addToCart(items);\n\n button.disabled = false;\n button.textContent = bundle.widgetConfig.ctaText ?? \"Add Bundle to Cart\";\n\n if (!result.success) {\n const error = document.createElement(\"p\");\n error.className = \"lb-bundle__error\";\n error.textContent = result.error ?? \"Failed to add to cart\";\n container.appendChild(error);\n setTimeout(() => error.remove(), 5000);\n }\n });\n\n container.appendChild(button);\n}\n\nfunction escapeHtml(str: string): string {\n const div = document.createElement(\"div\");\n div.textContent = str;\n return div.innerHTML;\n}\n","/**\n * DOM renderer for mix-and-match bundles.\n */\nimport { formatMoney, validateQuantity, type ParsedBundle, type CartLineItem, type AddToCartResult } from \"@lime-bundles/core\";\n\nexport function renderMixMatchBundle(\n container: HTMLElement,\n bundle: ParsedBundle,\n addToCart: (items: CartLineItem[]) => Promise<AddToCartResult>,\n) {\n const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n const selections = new Map<string, { variantId: string; quantity: number }>();\n\n // Title\n const title = document.createElement(\"h3\");\n title.className = \"lb-bundle__title\";\n title.textContent = bundle.title;\n title.setAttribute(\"part\", \"title\");\n container.appendChild(title);\n\n // Instructions\n const instructions = document.createElement(\"p\");\n instructions.className = \"lb-bundle__instructions\";\n instructions.textContent = bundle.minQuantity && bundle.maxQuantity\n ? `Select ${bundle.minQuantity}–${bundle.maxQuantity} items`\n : bundle.minQuantity\n ? `Select at least ${bundle.minQuantity} items`\n : \"Select your items\";\n container.appendChild(instructions);\n\n // Products\n const productsDiv = document.createElement(\"div\");\n productsDiv.className = \"lb-bundle__products lb-bundle__products--selectable\";\n\n for (const product of bundle.products) {\n const variant = product.variants.nodes.find((v) => v.availableForSale) ?? product.variants.nodes[0];\n if (!variant) continue;\n\n const productEl = document.createElement(\"div\");\n productEl.className = \"lb-bundle__product lb-bundle__product--selectable\";\n\n if (product.featuredImage) {\n const img = document.createElement(\"img\");\n img.src = product.featuredImage.url;\n img.alt = product.featuredImage.altText ?? product.title;\n img.className = \"lb-bundle__product-image\";\n img.loading = \"lazy\";\n productEl.appendChild(img);\n }\n\n const info = document.createElement(\"div\");\n info.className = \"lb-bundle__product-info\";\n info.innerHTML = `\n <p class=\"lb-bundle__product-title\">${escapeHtml(product.title)}</p>\n <p class=\"lb-bundle__product-price\">${escapeHtml(formatMoney(variant.price.amount, currency))}</p>\n `;\n productEl.appendChild(info);\n\n const selectBtn = document.createElement(\"button\");\n selectBtn.className = \"lb-bundle__select-btn\";\n selectBtn.textContent = variant.availableForSale ? \"Select\" : \"Sold out\";\n selectBtn.disabled = !variant.availableForSale;\n\n selectBtn.addEventListener(\"click\", () => {\n const key = product.id;\n if (selections.has(key)) {\n selections.delete(key);\n productEl.classList.remove(\"lb-bundle__product--selected\");\n selectBtn.textContent = \"Select\";\n } else {\n selections.set(key, { variantId: variant.id, quantity: 1 });\n productEl.classList.add(\"lb-bundle__product--selected\");\n selectBtn.textContent = \"Selected\";\n }\n updateCta();\n });\n\n productEl.appendChild(selectBtn);\n productsDiv.appendChild(productEl);\n }\n container.appendChild(productsDiv);\n\n // Validation message\n const validationEl = document.createElement(\"p\");\n validationEl.className = \"lb-bundle__validation\";\n container.appendChild(validationEl);\n\n // CTA\n const button = document.createElement(\"button\");\n button.className = \"lb-bundle__cta\";\n button.setAttribute(\"part\", \"button\");\n button.disabled = true;\n container.appendChild(button);\n\n function updateCta() {\n const total = Array.from(selections.values()).reduce((s, v) => s + v.quantity, 0);\n const validation = validateQuantity(total, bundle.minQuantity, bundle.maxQuantity);\n button.disabled = !validation.valid;\n button.textContent = bundle.widgetConfig.ctaText ?? `Add ${total} Items to Cart`;\n validationEl.textContent = validation.message ?? \"\";\n }\n\n updateCta();\n\n button.addEventListener(\"click\", async () => {\n button.disabled = true;\n button.textContent = \"Adding...\";\n\n const items: CartLineItem[] = Array.from(selections.entries()).map(([, s]) => ({\n variantId: s.variantId,\n quantity: s.quantity,\n }));\n\n const result = await addToCart(items);\n updateCta();\n\n if (!result.success) {\n const error = document.createElement(\"p\");\n error.className = \"lb-bundle__error\";\n error.textContent = result.error ?? \"Failed to add to cart\";\n container.appendChild(error);\n setTimeout(() => error.remove(), 5000);\n }\n });\n}\n\nfunction escapeHtml(str: string): string {\n const div = document.createElement(\"div\");\n div.textContent = str;\n return div.innerHTML;\n}\n","/**\n * DOM renderer for volume bundles.\n */\nimport { formatMoney, calculateTierSavings, type ParsedBundle, type CartLineItem, type AddToCartResult } from \"@lime-bundles/core\";\n\nexport function renderVolumeBundle(\n container: HTMLElement,\n bundle: ParsedBundle,\n addToCart: (items: CartLineItem[]) => Promise<AddToCartResult>,\n) {\n const product = bundle.products[0];\n if (!product) return;\n\n const basePrice = parseFloat(product.priceRange.minVariantPrice.amount);\n const currency = product.priceRange.minVariantPrice.currencyCode;\n let quantity = 1;\n\n // Title\n const title = document.createElement(\"h3\");\n title.className = \"lb-bundle__title\";\n title.textContent = bundle.title;\n title.setAttribute(\"part\", \"title\");\n container.appendChild(title);\n\n // Product\n const productEl = document.createElement(\"div\");\n productEl.className = \"lb-bundle__product lb-bundle__product--volume\";\n if (product.featuredImage) {\n const img = document.createElement(\"img\");\n img.src = product.featuredImage.url;\n img.alt = product.featuredImage.altText ?? product.title;\n img.className = \"lb-bundle__product-image\";\n img.loading = \"lazy\";\n productEl.appendChild(img);\n }\n const info = document.createElement(\"div\");\n info.className = \"lb-bundle__product-info\";\n info.innerHTML = `\n <p class=\"lb-bundle__product-title\">${escapeHtml(product.title)}</p>\n <p class=\"lb-bundle__product-price\">${escapeHtml(formatMoney(basePrice, currency))} each</p>\n `;\n productEl.appendChild(info);\n container.appendChild(productEl);\n\n // Tier table\n const tiersDiv = document.createElement(\"div\");\n tiersDiv.className = \"lb-bundle__tiers\";\n tiersDiv.setAttribute(\"role\", \"table\");\n tiersDiv.setAttribute(\"aria-label\", \"Volume discounts\");\n container.appendChild(tiersDiv);\n\n // Quantity selector\n const qtyWrapper = document.createElement(\"div\");\n qtyWrapper.className = \"lb-bundle__quantity-selector\";\n const label = document.createElement(\"label\");\n label.textContent = \"Quantity\";\n qtyWrapper.appendChild(label);\n\n const qtyControl = document.createElement(\"div\");\n qtyControl.className = \"lb-bundle__quantity-control\";\n\n const minusBtn = document.createElement(\"button\");\n minusBtn.textContent = \"−\";\n minusBtn.setAttribute(\"aria-label\", \"Decrease quantity\");\n\n const qtyInput = document.createElement(\"input\");\n qtyInput.type = \"number\";\n qtyInput.min = \"1\";\n qtyInput.value = \"1\";\n qtyInput.className = \"lb-bundle__quantity-input\";\n\n const plusBtn = document.createElement(\"button\");\n plusBtn.textContent = \"+\";\n plusBtn.setAttribute(\"aria-label\", \"Increase quantity\");\n\n qtyControl.append(minusBtn, qtyInput, plusBtn);\n qtyWrapper.appendChild(qtyControl);\n container.appendChild(qtyWrapper);\n\n // CTA\n const button = document.createElement(\"button\");\n button.className = \"lb-bundle__cta\";\n button.setAttribute(\"part\", \"button\");\n container.appendChild(button);\n\n function updateTiers() {\n const savings = calculateTierSavings(bundle.volumeTiers, basePrice, quantity);\n tiersDiv.innerHTML = \"\";\n for (const ts of savings) {\n const row = document.createElement(\"div\");\n row.className = `lb-bundle__tier${ts.isActive ? \" lb-bundle__tier--active\" : \"\"}`;\n row.setAttribute(\"role\", \"row\");\n row.innerHTML = `\n <span class=\"lb-bundle__tier-quantity\" role=\"cell\">${ts.tier.minQuantity}+ items</span>\n <span class=\"lb-bundle__tier-price\" role=\"cell\">${escapeHtml(formatMoney(ts.unitPrice, currency))} each</span>\n <span class=\"lb-bundle__tier-savings\" role=\"cell\">Save ${ts.savingsPercent.toFixed(0)}%</span>\n ${ts.tier.label ? `<span class=\"lb-bundle__tier-label\" role=\"cell\">${escapeHtml(ts.tier.label)}</span>` : \"\"}\n `;\n tiersDiv.appendChild(row);\n }\n button.textContent = bundle.widgetConfig.ctaText ?? `Add ${quantity} to Cart`;\n }\n\n updateTiers();\n\n minusBtn.addEventListener(\"click\", () => {\n if (quantity > 1) { quantity--; qtyInput.value = String(quantity); updateTiers(); }\n });\n plusBtn.addEventListener(\"click\", () => {\n quantity++; qtyInput.value = String(quantity); updateTiers();\n });\n qtyInput.addEventListener(\"change\", () => {\n const val = parseInt(qtyInput.value, 10);\n if (!isNaN(val) && val > 0) { quantity = val; updateTiers(); }\n });\n\n button.addEventListener(\"click\", async () => {\n const variant = product.variants.nodes.find((v) => v.availableForSale);\n if (!variant) return;\n\n button.disabled = true;\n button.textContent = \"Adding...\";\n\n const items: CartLineItem[] = [{ variantId: variant.id, quantity }];\n const result = await addToCart(items);\n\n button.disabled = false;\n updateTiers();\n\n if (!result.success) {\n const error = document.createElement(\"p\");\n error.className = \"lb-bundle__error\";\n error.textContent = result.error ?? \"Failed to add to cart\";\n container.appendChild(error);\n setTimeout(() => error.remove(), 5000);\n }\n });\n}\n\nfunction escapeHtml(str: string): string {\n const div = document.createElement(\"div\");\n div.textContent = str;\n return div.innerHTML;\n}\n","/**\n * CSS styles inlined into Shadow DOM.\n * Uses CSS custom properties that pierce the shadow boundary for theming.\n */\nexport const WIDGET_STYLES = `\n:host {\n display: block;\n --lb-primary-color: #000;\n --lb-secondary-color: #666;\n --lb-accent-color: #2563eb;\n --lb-background: #fff;\n --lb-border-color: #e5e7eb;\n --lb-border-radius: 8px;\n --lb-font-family: inherit;\n --lb-font-size: 14px;\n --lb-spacing-sm: 8px;\n --lb-spacing-md: 16px;\n --lb-spacing-lg: 24px;\n --lb-button-bg: var(--lb-accent-color);\n --lb-button-text: #fff;\n --lb-button-radius: var(--lb-border-radius);\n --lb-savings-color: #16a34a;\n --lb-error-color: #dc2626;\n}\n\n.lb-bundle {\n font-family: var(--lb-font-family);\n font-size: var(--lb-font-size);\n color: var(--lb-primary-color);\n background: var(--lb-background);\n border: 1px solid var(--lb-border-color);\n border-radius: var(--lb-border-radius);\n padding: var(--lb-spacing-lg);\n}\n\n.lb-bundle__title { margin: 0 0 var(--lb-spacing-md); font-size: 1.25em; font-weight: 600; }\n.lb-bundle__discount-badge { display: inline-block; background: var(--lb-savings-color); color: #fff; padding: 2px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 600; margin-bottom: var(--lb-spacing-md); }\n.lb-bundle__products { display: grid; gap: var(--lb-spacing-md); margin-bottom: var(--lb-spacing-lg); }\n.lb-bundle__product { display: flex; gap: var(--lb-spacing-md); align-items: center; padding: var(--lb-spacing-sm); border: 1px solid var(--lb-border-color); border-radius: var(--lb-border-radius); }\n.lb-bundle__product--selected { border-color: var(--lb-accent-color); }\n.lb-bundle__product-image { width: 64px; height: 64px; object-fit: cover; border-radius: calc(var(--lb-border-radius) - 2px); flex-shrink: 0; }\n.lb-bundle__product-info { flex: 1; min-width: 0; }\n.lb-bundle__product-title { margin: 0; font-weight: 500; }\n.lb-bundle__product-price { margin: 4px 0 0; color: var(--lb-secondary-color); }\n.lb-bundle__instructions { color: var(--lb-secondary-color); margin: 0 0 var(--lb-spacing-md); }\n.lb-bundle__tiers { margin-bottom: var(--lb-spacing-lg); }\n.lb-bundle__tier { display: flex; align-items: center; gap: var(--lb-spacing-md); padding: var(--lb-spacing-sm) var(--lb-spacing-md); border: 1px solid var(--lb-border-color); border-radius: var(--lb-border-radius); margin-bottom: var(--lb-spacing-sm); }\n.lb-bundle__tier--active { border-color: var(--lb-savings-color); }\n.lb-bundle__tier-savings { color: var(--lb-savings-color); font-weight: 600; }\n.lb-bundle__quantity-selector { margin-bottom: var(--lb-spacing-lg); }\n.lb-bundle__quantity-selector label { display: block; margin-bottom: var(--lb-spacing-sm); font-weight: 500; }\n.lb-bundle__quantity-control { display: inline-flex; align-items: center; border: 1px solid var(--lb-border-color); border-radius: var(--lb-border-radius); }\n.lb-bundle__quantity-control button { width: 32px; height: 32px; border: none; background: transparent; cursor: pointer; font-size: 1.1em; display: flex; align-items: center; justify-content: center; }\n.lb-bundle__quantity-input { width: 40px; text-align: center; border: none; border-left: 1px solid var(--lb-border-color); border-right: 1px solid var(--lb-border-color); height: 32px; font-size: var(--lb-font-size); -moz-appearance: textfield; }\n.lb-bundle__quantity-input::-webkit-outer-spin-button, .lb-bundle__quantity-input::-webkit-inner-spin-button { -webkit-appearance: none; }\n.lb-bundle__select-btn { padding: 6px 12px; border: 1px solid var(--lb-border-color); border-radius: var(--lb-border-radius); background: transparent; cursor: pointer; }\n.lb-bundle__select-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n.lb-bundle__cta { width: 100%; padding: 12px 24px; border: none; border-radius: var(--lb-button-radius); background: var(--lb-button-bg); color: var(--lb-button-text); font-size: 1em; font-weight: 600; cursor: pointer; transition: opacity 0.15s; }\n.lb-bundle__cta:hover:not(:disabled) { opacity: 0.9; }\n.lb-bundle__cta:disabled { opacity: 0.5; cursor: not-allowed; }\n.lb-bundle__error { color: var(--lb-error-color); margin: var(--lb-spacing-sm) 0; font-size: 0.9em; }\n.lb-bundle__validation { color: var(--lb-secondary-color); margin: var(--lb-spacing-sm) 0; font-size: 0.9em; }\n\n.lb-skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: lb-shimmer 1.5s infinite; border-radius: var(--lb-border-radius); }\n.lb-skeleton--title { height: 24px; width: 60%; margin-bottom: var(--lb-spacing-md); }\n.lb-skeleton--products { height: 200px; }\n@keyframes lb-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }\n`;\n","/**\n * <lime-bundle> Web Component — renders Lime Bundles on headless storefronts.\n *\n * Usage:\n * <lime-bundle\n * shop-domain=\"my-store.myshopify.com\"\n * storefront-token=\"abc123\"\n * bundle-gid=\"gid://shopify/Metaobject/12345\"\n * ></lime-bundle>\n */\nimport {\n createStorefrontClient,\n BUNDLE_METAOBJECT_QUERY,\n parseMetaobjectBundle,\n detectCartApi,\n createAjaxCartApi,\n createStorefrontCartApi,\n observeImpression,\n reportImpression,\n reportAddToCart,\n type ParsedBundle,\n type BundleMetaobjectResponse,\n type CartLineItem,\n} from \"@lime-bundles/core\";\nimport { renderFixedBundle } from \"./renderers/fixed\";\nimport { renderMixMatchBundle } from \"./renderers/mix-match\";\nimport { renderVolumeBundle } from \"./renderers/volume\";\nimport { WIDGET_STYLES } from \"./styles/widget-styles\";\n\nexport class LimeBundleElement extends HTMLElement {\n static observedAttributes = [\n \"shop-domain\",\n \"storefront-token\",\n \"bundle-gid\",\n \"cart-id\",\n \"app-url\",\n \"analytics\",\n \"locale\",\n ];\n\n private shadow: ShadowRoot;\n private bundle: ParsedBundle | null = null;\n private abortController: AbortController | null = null;\n private impressionCleanup: (() => void) | null = null;\n\n constructor() {\n super();\n this.shadow = this.attachShadow({ mode: \"open\" });\n }\n\n connectedCallback() {\n this.render();\n this.fetchBundle();\n }\n\n disconnectedCallback() {\n this.abortController?.abort();\n this.teardownImpression();\n }\n\n /** Clean up stale bundle state and active impression observer. */\n private teardownImpression() {\n this.impressionCleanup?.();\n this.impressionCleanup = null;\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {\n // Ignore no-op writes (same value re-set) and pre-connect attribute parsing\n if (oldValue === newValue || !this.isConnected) return;\n\n if (name === \"bundle-gid\" || name === \"shop-domain\" || name === \"storefront-token\") {\n // Only refetch when all required attributes are present — prevents\n // partial-attribute races during element initialization or programmatic updates\n if (this.shopDomain && this.storefrontToken && this.bundleGid) {\n this.fetchBundle();\n }\n }\n }\n\n private get shopDomain(): string {\n return this.getAttribute(\"shop-domain\") ?? \"\";\n }\n\n private get storefrontToken(): string {\n return this.getAttribute(\"storefront-token\") ?? \"\";\n }\n\n private get bundleGid(): string {\n return this.getAttribute(\"bundle-gid\") ?? \"\";\n }\n\n private get cartId(): string | undefined {\n return this.getAttribute(\"cart-id\") ?? undefined;\n }\n\n private get appUrl(): string {\n return this.getAttribute(\"app-url\") ?? \"\";\n }\n\n private get analyticsEnabled(): boolean {\n return this.getAttribute(\"analytics\") !== \"false\";\n }\n\n private async fetchBundle() {\n if (!this.shopDomain || !this.storefrontToken || !this.bundleGid) {\n this.renderError(\"Missing required attributes: shop-domain, storefront-token, bundle-gid\");\n return;\n }\n\n // Abort any previous fetch and create a fresh controller.\n // Capture in a local const so post-await checks reference the controller\n // for THIS invocation, not a later one created by a concurrent attributeChangedCallback.\n this.abortController?.abort();\n const controller = new AbortController();\n this.abortController = controller;\n\n this.renderLoading();\n\n try {\n const client = createStorefrontClient({\n shopDomain: this.shopDomain,\n accessToken: this.storefrontToken,\n });\n\n const data = await client.query<BundleMetaobjectResponse>(\n BUNDLE_METAOBJECT_QUERY,\n { id: this.bundleGid },\n );\n\n // Check the LOCAL controller — if a newer fetch replaced this.abortController,\n // this invocation's controller will be aborted.\n if (controller.signal.aborted) return;\n\n if (!data.metaobject) {\n this.bundle = null;\n this.teardownImpression();\n this.renderError(\"Bundle not found\");\n return;\n }\n\n this.bundle = parseMetaobjectBundle(\n data.metaobject.id,\n data.metaobject.fields,\n );\n\n if (!this.bundle) {\n this.teardownImpression();\n this.renderError(\"Bundle is not active or has expired\");\n return;\n }\n\n this.renderBundle();\n this.setupImpression();\n } catch (err) {\n if (controller.signal.aborted) return;\n this.bundle = null;\n this.teardownImpression();\n this.renderError(\n err instanceof Error ? err.message : \"Failed to load bundle\",\n );\n }\n }\n\n private renderBundle() {\n if (!this.bundle) return;\n\n const container = document.createElement(\"div\");\n container.className = \"lb-bundle\";\n container.setAttribute(\"role\", \"region\");\n container.setAttribute(\"aria-label\", this.bundle.title);\n\n const addToCart = async (items: CartLineItem[]) => {\n const apiType = detectCartApi();\n const cart =\n apiType === \"ajax\"\n ? createAjaxCartApi(this.bundleGid, this.bundle!.bundleType)\n : createStorefrontCartApi(\n createStorefrontClient({\n shopDomain: this.shopDomain,\n accessToken: this.storefrontToken,\n }),\n this.bundleGid,\n this.bundle!.bundleType,\n this.cartId,\n );\n\n let result: Awaited<ReturnType<typeof cart.addLines>>;\n try {\n result = await cart.addLines(items);\n } catch (err) {\n result = {\n success: false,\n error: err instanceof Error ? err.message : \"Cart add failed\",\n };\n }\n\n if (result.success) {\n // Always dispatch the CustomEvent so consumers can react to add-to-cart\n // regardless of analytics configuration\n this.dispatchEvent(\n new CustomEvent(\"lime-bundle:add-to-cart\", {\n detail: { items, cartId: result.cartId },\n bubbles: true,\n composed: true,\n }),\n );\n\n // Analytics reporting is separate — only fires when enabled and appUrl is set\n if (this.analyticsEnabled && this.appUrl) {\n const quantity = items.reduce((sum, i) => sum + i.quantity, 0);\n const totalPrice = items.reduce((sum, item) => {\n const product = this.bundle!.products.find((p) =>\n p.variants.nodes.some((v) => v.id === item.variantId),\n );\n const variant = product?.variants.nodes.find((v) => v.id === item.variantId);\n const price = variant ? parseFloat(variant.price.amount) : 0;\n return sum + price * item.quantity;\n }, 0);\n\n reportAddToCart(\n { shopDomain: this.shopDomain, appUrl: this.appUrl },\n {\n bundleGid: this.bundleGid,\n bundleType: this.bundle!.bundleType,\n productId: this.bundle!.products[0]?.id ?? \"\",\n quantity,\n totalPrice: Math.round(totalPrice * 100) / 100,\n },\n );\n }\n }\n\n return result;\n };\n\n switch (this.bundle.bundleType) {\n case \"fixed\":\n renderFixedBundle(container, this.bundle, addToCart);\n break;\n case \"mix_match\":\n renderMixMatchBundle(container, this.bundle, addToCart);\n break;\n case \"volume\":\n renderVolumeBundle(container, this.bundle, addToCart);\n break;\n }\n\n this.shadow.innerHTML = \"\";\n const style = document.createElement(\"style\");\n style.textContent = WIDGET_STYLES;\n this.shadow.appendChild(style);\n this.shadow.appendChild(container);\n\n this.dispatchEvent(\n new CustomEvent(\"lime-bundle:loaded\", {\n detail: {\n bundleType: this.bundle.bundleType,\n title: this.bundle.title,\n },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n private setupImpression() {\n if (!this.analyticsEnabled || !this.bundle || !this.appUrl) return;\n\n this.impressionCleanup?.();\n this.impressionCleanup = observeImpression(this, () => {\n reportImpression(\n { shopDomain: this.shopDomain, appUrl: this.appUrl },\n {\n bundleGid: this.bundleGid,\n bundleType: this.bundle!.bundleType,\n },\n );\n });\n }\n\n private renderLoading() {\n this.shadow.innerHTML = `\n <style>${WIDGET_STYLES}</style>\n <div class=\"lb-bundle lb-bundle--loading\">\n <div class=\"lb-skeleton lb-skeleton--title\"></div>\n <div class=\"lb-skeleton lb-skeleton--products\"></div>\n </div>\n `;\n }\n\n private renderError(message: string) {\n this.shadow.innerHTML = \"\";\n this.dispatchEvent(\n new CustomEvent(\"lime-bundle:error\", {\n detail: { message, code: \"LOAD_ERROR\" },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n private render() {\n // Initial empty state\n this.shadow.innerHTML = `<style>${WIDGET_STYLES}</style>`;\n }\n}\n","/**\n * Thank-you page snippet — fires purchase events for headless storefronts.\n *\n * Usage:\n * <script src=\"https://unpkg.com/@lime-bundles/widget/dist/lime-thankyou.js\"></script>\n * <script>\n * LimeBundles.trackPurchase({\n * shopDomain: 'my-store.myshopify.com',\n * orderId: 'gid://shopify/Order/12345',\n * lineItems: [\n * { bundleGid: '...', bundleType: 'fixed', price: 29.99, quantity: 1 }\n * ]\n * });\n * </script>\n */\n\ninterface PurchaseLineItem {\n bundleGid: string;\n bundleType: string;\n price: number;\n quantity: number;\n}\n\ninterface TrackPurchaseInput {\n shopDomain: string;\n orderId: string;\n lineItems: PurchaseLineItem[];\n appUrl?: string;\n}\n\n// Deduplication: track order+bundle combos we've already reported\nconst reportedPurchases = new Set<string>();\n\nexport function trackPurchase(input: TrackPurchaseInput): void {\n const appUrl = input.appUrl ?? `https://${input.shopDomain}`;\n\n // Group line items by bundleGid (merge quantities and prices)\n const bundleMap = new Map<string, { bundleType: string; revenue: number; lineItemCount: number }>();\n\n for (const item of input.lineItems) {\n if (!item.bundleGid) continue;\n\n const existing = bundleMap.get(item.bundleGid);\n if (existing) {\n existing.revenue += item.price * item.quantity;\n existing.lineItemCount += 1;\n } else {\n bundleMap.set(item.bundleGid, {\n bundleType: item.bundleType,\n revenue: item.price * item.quantity,\n lineItemCount: 1,\n });\n }\n }\n\n // Fire purchase events for each unique bundle\n for (const [bundleGid, data] of bundleMap) {\n const dedupKey = `${input.orderId}:${bundleGid}`;\n if (reportedPurchases.has(dedupKey)) continue;\n reportedPurchases.add(dedupKey);\n\n const payload = {\n shopDomain: input.shopDomain,\n eventType: \"bundle_purchased\",\n bundleGid,\n bundleType: data.bundleType,\n orderId: input.orderId,\n revenue: Math.round(data.revenue * 100) / 100,\n lineItemCount: data.lineItemCount,\n occurredAt: new Date().toISOString(),\n };\n\n // Fire-and-forget with sendBeacon fallback\n const url = `${appUrl}/api/analytics`;\n const body = JSON.stringify(payload);\n\n try {\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n }).catch(() => {\n // Fallback to sendBeacon\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, body);\n }\n });\n } catch {\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, body);\n }\n }\n }\n}\n"],"mappings":"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","names":["src_exports","__export","LimeBundleElement","trackPurchase","StorefrontApiError","errors","DEFAULT_API_VERSION","createStorefrontClient","config","version","endpoint","query","variables","response","json","BUNDLE_METAOBJECT_QUERY","VALID_BUNDLE_TYPES","ACTIVE_STATUSES","parseMetaobjectBundle","metaobjectId","fields","fieldMap","f","title","bundleType","status","startsAt","endsAt","now","products","resolveProducts","discountConfig","parseDiscountConfig","widgetConfig","parseWidgetConfig","volumeTiers","parseVolumeTiers","parseIntField","parseJsonField","productsField","node","collectionField","raw","obj","t","key","value","num","calculateTierSavings","tiers","basePrice","currentQuantity","a","b","tier","discount","unitPrice","savings","savingsPercent","isActive","validateQuantity","totalQuantity","minQuantity","maxQuantity","detectCartApi","cartWriteChain","createAjaxCartApi","bundleGid","bundleType","items","resolve","cartItems","item","extractNumericId","response","body","err","gid","match","CART_LINES_ADD_MUTATION","CART_CREATE_MUTATION","createStorefrontCartApi","client","cartId","lines","key","value","data","reportImpression","config","event","sendEvent","reportAddToCart","observeImpression","element","callback","observer","entries","entry","payload","url","r","SESSION_COOKIE_MAX_AGE","formatMoney","amount","currencyCode","num","renderFixedBundle","container","bundle","addToCart","currency","title","badge","productsDiv","product","productEl","img","info","escapeHtml","formatMoney","button","items","p","v","result","error","str","div","renderMixMatchBundle","container","bundle","addToCart","currency","selections","title","instructions","productsDiv","product","variant","v","productEl","img","info","escapeHtml","formatMoney","selectBtn","key","updateCta","validationEl","button","total","s","validation","validateQuantity","items","result","error","str","div","renderVolumeBundle","container","bundle","addToCart","product","basePrice","currency","quantity","title","productEl","img","info","escapeHtml","formatMoney","tiersDiv","qtyWrapper","label","qtyControl","minusBtn","qtyInput","plusBtn","button","updateTiers","savings","calculateTierSavings","ts","row","val","variant","v","items","result","error","str","div","WIDGET_STYLES","LimeBundleElement","name","oldValue","newValue","controller","data","createStorefrontClient","BUNDLE_METAOBJECT_QUERY","parseMetaobjectBundle","err","container","addToCart","items","cart","detectCartApi","createAjaxCartApi","createStorefrontCartApi","result","quantity","sum","i","totalPrice","item","variant","p","v","price","reportAddToCart","renderFixedBundle","renderMixMatchBundle","renderVolumeBundle","style","WIDGET_STYLES","observeImpression","reportImpression","message","reportedPurchases","trackPurchase","input","appUrl","bundleMap","item","existing","bundleGid","data","dedupKey","payload","url","body","LimeBundleElement"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var d=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var m=(e,r)=>{for(var o in r)d(e,o,{get:r[o],enumerable:!0})},y=(e,r,o,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let t of l(r))!h.call(e,t)&&t!==o&&d(e,t,{get:()=>r[t],enumerable:!(n=p(r,t))||n.enumerable});return e};var b=e=>y(d({},"__esModule",{value:!0}),e);var g={};m(g,{trackPurchase:()=>f});module.exports=b(g);var u=new Set;function f(e){let r=e.appUrl??`https://${e.shopDomain}`,o=new Map;for(let n of e.lineItems){if(!n.bundleGid)continue;let t=o.get(n.bundleGid);t?(t.revenue+=n.price*n.quantity,t.lineItemCount+=1):o.set(n.bundleGid,{bundleType:n.bundleType,revenue:n.price*n.quantity,lineItemCount:1})}for(let[n,t]of o){let s=`${e.orderId}:${n}`;if(u.has(s))continue;u.add(s);let c={shopDomain:e.shopDomain,eventType:"bundle_purchased",bundleGid:n,bundleType:t.bundleType,orderId:e.orderId,revenue:Math.round(t.revenue*100)/100,lineItemCount:t.lineItemCount,occurredAt:new Date().toISOString()},a=`${r}/api/analytics`,i=JSON.stringify(c);try{fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:i}).catch(()=>{typeof navigator<"u"&&navigator.sendBeacon&&navigator.sendBeacon(a,i)})}catch{typeof navigator<"u"&&navigator.sendBeacon&&navigator.sendBeacon(a,i)}}}0&&(module.exports={trackPurchase});
|
|
2
|
+
//# sourceMappingURL=lime-thankyou.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/thankyou/index.ts"],"sourcesContent":["/**\n * Thank-you page snippet — fires purchase events for headless storefronts.\n *\n * Usage:\n * <script src=\"https://unpkg.com/@lime-bundles/widget/dist/lime-thankyou.js\"></script>\n * <script>\n * LimeBundles.trackPurchase({\n * shopDomain: 'my-store.myshopify.com',\n * orderId: 'gid://shopify/Order/12345',\n * lineItems: [\n * { bundleGid: '...', bundleType: 'fixed', price: 29.99, quantity: 1 }\n * ]\n * });\n * </script>\n */\n\ninterface PurchaseLineItem {\n bundleGid: string;\n bundleType: string;\n price: number;\n quantity: number;\n}\n\ninterface TrackPurchaseInput {\n shopDomain: string;\n orderId: string;\n lineItems: PurchaseLineItem[];\n appUrl?: string;\n}\n\n// Deduplication: track order+bundle combos we've already reported\nconst reportedPurchases = new Set<string>();\n\nexport function trackPurchase(input: TrackPurchaseInput): void {\n const appUrl = input.appUrl ?? `https://${input.shopDomain}`;\n\n // Group line items by bundleGid (merge quantities and prices)\n const bundleMap = new Map<string, { bundleType: string; revenue: number; lineItemCount: number }>();\n\n for (const item of input.lineItems) {\n if (!item.bundleGid) continue;\n\n const existing = bundleMap.get(item.bundleGid);\n if (existing) {\n existing.revenue += item.price * item.quantity;\n existing.lineItemCount += 1;\n } else {\n bundleMap.set(item.bundleGid, {\n bundleType: item.bundleType,\n revenue: item.price * item.quantity,\n lineItemCount: 1,\n });\n }\n }\n\n // Fire purchase events for each unique bundle\n for (const [bundleGid, data] of bundleMap) {\n const dedupKey = `${input.orderId}:${bundleGid}`;\n if (reportedPurchases.has(dedupKey)) continue;\n reportedPurchases.add(dedupKey);\n\n const payload = {\n shopDomain: input.shopDomain,\n eventType: \"bundle_purchased\",\n bundleGid,\n bundleType: data.bundleType,\n orderId: input.orderId,\n revenue: Math.round(data.revenue * 100) / 100,\n lineItemCount: data.lineItemCount,\n occurredAt: new Date().toISOString(),\n };\n\n // Fire-and-forget with sendBeacon fallback\n const url = `${appUrl}/api/analytics`;\n const body = JSON.stringify(payload);\n\n try {\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n }).catch(() => {\n // Fallback to sendBeacon\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, body);\n }\n });\n } catch {\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, body);\n }\n }\n }\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IAAA,eAAAC,EAAAH,GA+BA,IAAMI,EAAoB,IAAI,IAEvB,SAASF,EAAcG,EAAiC,CAC7D,IAAMC,EAASD,EAAM,QAAU,WAAWA,EAAM,UAAU,GAGpDE,EAAY,IAAI,IAEtB,QAAWC,KAAQH,EAAM,UAAW,CAClC,GAAI,CAACG,EAAK,UAAW,SAErB,IAAMC,EAAWF,EAAU,IAAIC,EAAK,SAAS,EACzCC,GACFA,EAAS,SAAWD,EAAK,MAAQA,EAAK,SACtCC,EAAS,eAAiB,GAE1BF,EAAU,IAAIC,EAAK,UAAW,CAC5B,WAAYA,EAAK,WACjB,QAASA,EAAK,MAAQA,EAAK,SAC3B,cAAe,CACjB,CAAC,CAEL,CAGA,OAAW,CAACE,EAAWC,CAAI,IAAKJ,EAAW,CACzC,IAAMK,EAAW,GAAGP,EAAM,OAAO,IAAIK,CAAS,GAC9C,GAAIN,EAAkB,IAAIQ,CAAQ,EAAG,SACrCR,EAAkB,IAAIQ,CAAQ,EAE9B,IAAMC,EAAU,CACd,WAAYR,EAAM,WAClB,UAAW,mBACX,UAAAK,EACA,WAAYC,EAAK,WACjB,QAASN,EAAM,QACf,QAAS,KAAK,MAAMM,EAAK,QAAU,GAAG,EAAI,IAC1C,cAAeA,EAAK,cACpB,WAAY,IAAI,KAAK,EAAE,YAAY,CACrC,EAGMG,EAAM,GAAGR,CAAM,iBACfS,EAAO,KAAK,UAAUF,CAAO,EAEnC,GAAI,CACF,MAAMC,EAAK,CACT,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAAC,CACF,CAAC,EAAE,MAAM,IAAM,CAET,OAAO,UAAc,KAAe,UAAU,YAChD,UAAU,WAAWD,EAAKC,CAAI,CAElC,CAAC,CACH,MAAQ,CACF,OAAO,UAAc,KAAe,UAAU,YAChD,UAAU,WAAWD,EAAKC,CAAI,CAElC,CACF,CACF","names":["thankyou_exports","__export","trackPurchase","__toCommonJS","reportedPurchases","input","appUrl","bundleMap","item","existing","bundleGid","data","dedupKey","payload","url","body"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var LimeBundles=(()=>{var d=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var m=(e,r)=>{for(var o in r)d(e,o,{get:r[o],enumerable:!0})},y=(e,r,o,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let t of l(r))!h.call(e,t)&&t!==o&&d(e,t,{get:()=>r[t],enumerable:!(n=p(r,t))||n.enumerable});return e};var b=e=>y(d({},"__esModule",{value:!0}),e);var g={};m(g,{trackPurchase:()=>f});var u=new Set;function f(e){let r=e.appUrl??`https://${e.shopDomain}`,o=new Map;for(let n of e.lineItems){if(!n.bundleGid)continue;let t=o.get(n.bundleGid);t?(t.revenue+=n.price*n.quantity,t.lineItemCount+=1):o.set(n.bundleGid,{bundleType:n.bundleType,revenue:n.price*n.quantity,lineItemCount:1})}for(let[n,t]of o){let s=`${e.orderId}:${n}`;if(u.has(s))continue;u.add(s);let c={shopDomain:e.shopDomain,eventType:"bundle_purchased",bundleGid:n,bundleType:t.bundleType,orderId:e.orderId,revenue:Math.round(t.revenue*100)/100,lineItemCount:t.lineItemCount,occurredAt:new Date().toISOString()},a=`${r}/api/analytics`,i=JSON.stringify(c);try{fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:i}).catch(()=>{typeof navigator<"u"&&navigator.sendBeacon&&navigator.sendBeacon(a,i)})}catch{typeof navigator<"u"&&navigator.sendBeacon&&navigator.sendBeacon(a,i)}}}return b(g);})();
|
|
2
|
+
//# sourceMappingURL=lime-thankyou.global.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/thankyou/index.ts"],"sourcesContent":["/**\n * Thank-you page snippet — fires purchase events for headless storefronts.\n *\n * Usage:\n * <script src=\"https://unpkg.com/@lime-bundles/widget/dist/lime-thankyou.js\"></script>\n * <script>\n * LimeBundles.trackPurchase({\n * shopDomain: 'my-store.myshopify.com',\n * orderId: 'gid://shopify/Order/12345',\n * lineItems: [\n * { bundleGid: '...', bundleType: 'fixed', price: 29.99, quantity: 1 }\n * ]\n * });\n * </script>\n */\n\ninterface PurchaseLineItem {\n bundleGid: string;\n bundleType: string;\n price: number;\n quantity: number;\n}\n\ninterface TrackPurchaseInput {\n shopDomain: string;\n orderId: string;\n lineItems: PurchaseLineItem[];\n appUrl?: string;\n}\n\n// Deduplication: track order+bundle combos we've already reported\nconst reportedPurchases = new Set<string>();\n\nexport function trackPurchase(input: TrackPurchaseInput): void {\n const appUrl = input.appUrl ?? `https://${input.shopDomain}`;\n\n // Group line items by bundleGid (merge quantities and prices)\n const bundleMap = new Map<string, { bundleType: string; revenue: number; lineItemCount: number }>();\n\n for (const item of input.lineItems) {\n if (!item.bundleGid) continue;\n\n const existing = bundleMap.get(item.bundleGid);\n if (existing) {\n existing.revenue += item.price * item.quantity;\n existing.lineItemCount += 1;\n } else {\n bundleMap.set(item.bundleGid, {\n bundleType: item.bundleType,\n revenue: item.price * item.quantity,\n lineItemCount: 1,\n });\n }\n }\n\n // Fire purchase events for each unique bundle\n for (const [bundleGid, data] of bundleMap) {\n const dedupKey = `${input.orderId}:${bundleGid}`;\n if (reportedPurchases.has(dedupKey)) continue;\n reportedPurchases.add(dedupKey);\n\n const payload = {\n shopDomain: input.shopDomain,\n eventType: \"bundle_purchased\",\n bundleGid,\n bundleType: data.bundleType,\n orderId: input.orderId,\n revenue: Math.round(data.revenue * 100) / 100,\n lineItemCount: data.lineItemCount,\n occurredAt: new Date().toISOString(),\n };\n\n // Fire-and-forget with sendBeacon fallback\n const url = `${appUrl}/api/analytics`;\n const body = JSON.stringify(payload);\n\n try {\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n }).catch(() => {\n // Fallback to sendBeacon\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, body);\n }\n });\n } catch {\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, body);\n }\n }\n }\n}\n"],"mappings":"+bAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IA+BA,IAAMC,EAAoB,IAAI,IAEvB,SAASD,EAAcE,EAAiC,CAC7D,IAAMC,EAASD,EAAM,QAAU,WAAWA,EAAM,UAAU,GAGpDE,EAAY,IAAI,IAEtB,QAAWC,KAAQH,EAAM,UAAW,CAClC,GAAI,CAACG,EAAK,UAAW,SAErB,IAAMC,EAAWF,EAAU,IAAIC,EAAK,SAAS,EACzCC,GACFA,EAAS,SAAWD,EAAK,MAAQA,EAAK,SACtCC,EAAS,eAAiB,GAE1BF,EAAU,IAAIC,EAAK,UAAW,CAC5B,WAAYA,EAAK,WACjB,QAASA,EAAK,MAAQA,EAAK,SAC3B,cAAe,CACjB,CAAC,CAEL,CAGA,OAAW,CAACE,EAAWC,CAAI,IAAKJ,EAAW,CACzC,IAAMK,EAAW,GAAGP,EAAM,OAAO,IAAIK,CAAS,GAC9C,GAAIN,EAAkB,IAAIQ,CAAQ,EAAG,SACrCR,EAAkB,IAAIQ,CAAQ,EAE9B,IAAMC,EAAU,CACd,WAAYR,EAAM,WAClB,UAAW,mBACX,UAAAK,EACA,WAAYC,EAAK,WACjB,QAASN,EAAM,QACf,QAAS,KAAK,MAAMM,EAAK,QAAU,GAAG,EAAI,IAC1C,cAAeA,EAAK,cACpB,WAAY,IAAI,KAAK,EAAE,YAAY,CACrC,EAGMG,EAAM,GAAGR,CAAM,iBACfS,EAAO,KAAK,UAAUF,CAAO,EAEnC,GAAI,CACF,MAAMC,EAAK,CACT,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAAC,CACF,CAAC,EAAE,MAAM,IAAM,CAET,OAAO,UAAc,KAAe,UAAU,YAChD,UAAU,WAAWD,EAAKC,CAAI,CAElC,CAAC,CACH,MAAQ,CACF,OAAO,UAAc,KAAe,UAAU,YAChD,UAAU,WAAWD,EAAKC,CAAI,CAElC,CACF,CACF","names":["thankyou_exports","__export","trackPurchase","reportedPurchases","input","appUrl","bundleMap","item","existing","bundleGid","data","dedupKey","payload","url","body"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var d=new Set;function c(t){let s=t.appUrl??`https://${t.shopDomain}`,r=new Map;for(let e of t.lineItems){if(!e.bundleGid)continue;let n=r.get(e.bundleGid);n?(n.revenue+=e.price*e.quantity,n.lineItemCount+=1):r.set(e.bundleGid,{bundleType:e.bundleType,revenue:e.price*e.quantity,lineItemCount:1})}for(let[e,n]of r){let i=`${t.orderId}:${e}`;if(d.has(i))continue;d.add(i);let u={shopDomain:t.shopDomain,eventType:"bundle_purchased",bundleGid:e,bundleType:n.bundleType,orderId:t.orderId,revenue:Math.round(n.revenue*100)/100,lineItemCount:n.lineItemCount,occurredAt:new Date().toISOString()},o=`${s}/api/analytics`,a=JSON.stringify(u);try{fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:a}).catch(()=>{typeof navigator<"u"&&navigator.sendBeacon&&navigator.sendBeacon(o,a)})}catch{typeof navigator<"u"&&navigator.sendBeacon&&navigator.sendBeacon(o,a)}}}export{c as trackPurchase};
|
|
2
|
+
//# sourceMappingURL=lime-thankyou.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/thankyou/index.ts"],"sourcesContent":["/**\n * Thank-you page snippet — fires purchase events for headless storefronts.\n *\n * Usage:\n * <script src=\"https://unpkg.com/@lime-bundles/widget/dist/lime-thankyou.js\"></script>\n * <script>\n * LimeBundles.trackPurchase({\n * shopDomain: 'my-store.myshopify.com',\n * orderId: 'gid://shopify/Order/12345',\n * lineItems: [\n * { bundleGid: '...', bundleType: 'fixed', price: 29.99, quantity: 1 }\n * ]\n * });\n * </script>\n */\n\ninterface PurchaseLineItem {\n bundleGid: string;\n bundleType: string;\n price: number;\n quantity: number;\n}\n\ninterface TrackPurchaseInput {\n shopDomain: string;\n orderId: string;\n lineItems: PurchaseLineItem[];\n appUrl?: string;\n}\n\n// Deduplication: track order+bundle combos we've already reported\nconst reportedPurchases = new Set<string>();\n\nexport function trackPurchase(input: TrackPurchaseInput): void {\n const appUrl = input.appUrl ?? `https://${input.shopDomain}`;\n\n // Group line items by bundleGid (merge quantities and prices)\n const bundleMap = new Map<string, { bundleType: string; revenue: number; lineItemCount: number }>();\n\n for (const item of input.lineItems) {\n if (!item.bundleGid) continue;\n\n const existing = bundleMap.get(item.bundleGid);\n if (existing) {\n existing.revenue += item.price * item.quantity;\n existing.lineItemCount += 1;\n } else {\n bundleMap.set(item.bundleGid, {\n bundleType: item.bundleType,\n revenue: item.price * item.quantity,\n lineItemCount: 1,\n });\n }\n }\n\n // Fire purchase events for each unique bundle\n for (const [bundleGid, data] of bundleMap) {\n const dedupKey = `${input.orderId}:${bundleGid}`;\n if (reportedPurchases.has(dedupKey)) continue;\n reportedPurchases.add(dedupKey);\n\n const payload = {\n shopDomain: input.shopDomain,\n eventType: \"bundle_purchased\",\n bundleGid,\n bundleType: data.bundleType,\n orderId: input.orderId,\n revenue: Math.round(data.revenue * 100) / 100,\n lineItemCount: data.lineItemCount,\n occurredAt: new Date().toISOString(),\n };\n\n // Fire-and-forget with sendBeacon fallback\n const url = `${appUrl}/api/analytics`;\n const body = JSON.stringify(payload);\n\n try {\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n }).catch(() => {\n // Fallback to sendBeacon\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, body);\n }\n });\n } catch {\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, body);\n }\n }\n }\n}\n"],"mappings":"AA+BA,IAAMA,EAAoB,IAAI,IAEvB,SAASC,EAAcC,EAAiC,CAC7D,IAAMC,EAASD,EAAM,QAAU,WAAWA,EAAM,UAAU,GAGpDE,EAAY,IAAI,IAEtB,QAAWC,KAAQH,EAAM,UAAW,CAClC,GAAI,CAACG,EAAK,UAAW,SAErB,IAAMC,EAAWF,EAAU,IAAIC,EAAK,SAAS,EACzCC,GACFA,EAAS,SAAWD,EAAK,MAAQA,EAAK,SACtCC,EAAS,eAAiB,GAE1BF,EAAU,IAAIC,EAAK,UAAW,CAC5B,WAAYA,EAAK,WACjB,QAASA,EAAK,MAAQA,EAAK,SAC3B,cAAe,CACjB,CAAC,CAEL,CAGA,OAAW,CAACE,EAAWC,CAAI,IAAKJ,EAAW,CACzC,IAAMK,EAAW,GAAGP,EAAM,OAAO,IAAIK,CAAS,GAC9C,GAAIP,EAAkB,IAAIS,CAAQ,EAAG,SACrCT,EAAkB,IAAIS,CAAQ,EAE9B,IAAMC,EAAU,CACd,WAAYR,EAAM,WAClB,UAAW,mBACX,UAAAK,EACA,WAAYC,EAAK,WACjB,QAASN,EAAM,QACf,QAAS,KAAK,MAAMM,EAAK,QAAU,GAAG,EAAI,IAC1C,cAAeA,EAAK,cACpB,WAAY,IAAI,KAAK,EAAE,YAAY,CACrC,EAGMG,EAAM,GAAGR,CAAM,iBACfS,EAAO,KAAK,UAAUF,CAAO,EAEnC,GAAI,CACF,MAAMC,EAAK,CACT,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAAC,CACF,CAAC,EAAE,MAAM,IAAM,CAET,OAAO,UAAc,KAAe,UAAU,YAChD,UAAU,WAAWD,EAAKC,CAAI,CAElC,CAAC,CACH,MAAQ,CACF,OAAO,UAAc,KAAe,UAAU,YAChD,UAAU,WAAWD,EAAKC,CAAI,CAElC,CACF,CACF","names":["reportedPurchases","trackPurchase","input","appUrl","bundleMap","item","existing","bundleGid","data","dedupKey","payload","url","body"]}
|