@lime-bundles/widget 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -0
- package/dist/index.cjs +238 -111
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -9
- package/dist/index.d.ts +71 -9
- package/dist/index.js +243 -112
- package/dist/index.js.map +1 -1
- package/dist/lime-bundle.global.js +126 -17
- package/dist/lime-bundle.global.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +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/index.ts"],"sourcesContent":["/**\n * <lime-bundle> Web Component — renders Lime Bundles on any storefront.\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 * app-url=\"https://bundles.example.com\"\n * ></lime-bundle>\n *\n * document.querySelector(\"lime-bundle\").addEventListener(\n * \"lime-bundle:add-to-cart\",\n * (ev) => { cart.linesAdd(ev.detail.lines); }\n * );\n *\n * BYO-cart model: the widget fires `lime-bundle:add-to-cart` with a\n * `CartLineInput[]` payload in the `detail.lines` field. Merchants wire\n * this to their cart system (Hydrogen's useCart, Storefront Cart API,\n * ajax cart — whatever). The widget does not perform the cart add\n * itself; it optimistically reports success to the UI after dispatch.\n *\n * Analytics: the widget calls `reportImpression` / `reportAddToCart`\n * against the app URL if the `analytics` attribute is not \"false\" and\n * `app-url` is set. These fire-and-forget.\n */\nimport {\n createStorefrontClient,\n BUNDLE_METAOBJECT_QUERY,\n SHOP_CUSTOM_CSS_QUERY,\n parseMetaobjectBundle,\n observeImpression,\n reportImpression,\n reportAddToCart,\n injectCustomCss,\n type ParsedBundle,\n type FixedBundleData,\n type VolumeBundleData,\n type MixMatchBundleData,\n type BundleMetaobjectResponse,\n type ShopCustomCssResponse,\n type CartLineInput,\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 \"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 private teardownImpression() {\n this.impressionCleanup?.();\n this.impressionCleanup = null;\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null,\n ) {\n if (oldValue === newValue || !this.isConnected) return;\n\n if (\n name === \"bundle-gid\" ||\n name === \"shop-domain\" ||\n name === \"storefront-token\"\n ) {\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 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(\n \"Missing required attributes: shop-domain, storefront-token, bundle-gid\",\n );\n return;\n }\n\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 // Fetch bundle data and custom CSS in parallel. Custom CSS is best-effort;\n // if it fails we still render the bundle without merchant styling.\n const [bundleData, cssData] = await Promise.all([\n client.query<BundleMetaobjectResponse>(\n BUNDLE_METAOBJECT_QUERY,\n { id: this.bundleGid },\n { signal: controller.signal },\n ),\n client\n .query<ShopCustomCssResponse>(\n SHOP_CUSTOM_CSS_QUERY,\n undefined,\n { signal: controller.signal },\n )\n .catch(() => null),\n ]);\n\n if (controller.signal.aborted) return;\n\n if (!bundleData.metaobject) {\n this.bundle = null;\n this.teardownImpression();\n this.renderError(\"Bundle not found\");\n return;\n }\n\n this.bundle = parseMetaobjectBundle(\n bundleData.metaobject.id,\n bundleData.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 // Best-effort custom CSS injection. No-op in jsdom/SSR contexts.\n if (cssData?.shop?.metafield?.value) {\n injectCustomCss(this.shopDomain, cssData.shop.metafield.value);\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 /**\n * Dispatch add-to-cart for merchant handling. Returns true — the widget\n * reports success optimistically. If the merchant's cart mutation fails,\n * they're responsible for surfacing that error in their own UI.\n */\n private dispatchAddToCart = (lines: CartLineInput[]): void => {\n this.dispatchEvent(\n new CustomEvent(\"lime-bundle:add-to-cart\", {\n detail: { lines },\n bubbles: true,\n composed: true,\n }),\n );\n\n if (this.analyticsEnabled && this.appUrl && this.bundle) {\n const quantity = lines.reduce((sum, l) => sum + l.quantity, 0);\n const totalPrice = lines.reduce((sum, line) => {\n const product = this.bundle!.products.find((p) =>\n p.variants.nodes.some((v) => v.id === line.merchandiseId),\n );\n const variant = product?.variants.nodes.find(\n (v) => v.id === line.merchandiseId,\n );\n const price = variant ? parseFloat(variant.price.amount) : 0;\n return sum + price * line.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 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 switch (this.bundle.bundleType) {\n case \"fixed\":\n renderFixedBundle(\n container,\n this.bundle as FixedBundleData,\n this.dispatchAddToCart,\n );\n break;\n case \"mix_match\":\n renderMixMatchBundle(\n container,\n this.bundle as MixMatchBundleData,\n this.dispatchAddToCart,\n );\n break;\n case \"volume\":\n renderVolumeBundle(\n container,\n this.bundle as VolumeBundleData,\n this.dispatchAddToCart,\n );\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 this.shadow.innerHTML = `<style>${WIDGET_STYLES}</style>`;\n }\n}\n","/**\n * DOM renderer for fixed bundles.\n *\n * Takes the narrowed `FixedBundleData` variant so we get TS errors if any\n * caller passes a non-fixed bundle.\n *\n * `onAddToCart` is a BYO-cart dispatch: the widget owner listens for\n * `lime-bundle:add-to-cart` (CustomEvent) and performs the actual cart\n * mutation. This renderer only builds the DOM and invokes the dispatch\n * — it does not know how or whether the add succeeds.\n */\nimport {\n formatMoney,\n type FixedBundleData,\n type CartLineInput,\n} from \"@lime-bundles/core\";\n\nexport function renderFixedBundle(\n container: HTMLElement,\n bundle: FixedBundleData,\n onAddToCart: (lines: CartLineInput[]) => void,\n) {\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\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 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 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 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\", () => {\n const lines: CartLineInput[] = bundle.products\n .filter((p) => p.variants.nodes.some((v) => v.availableForSale))\n .map((p) => {\n const variant = p.variants.nodes.find((v) => v.availableForSale)!;\n return {\n merchandiseId: variant.id,\n quantity: 1,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n };\n });\n\n if (lines.length === 0) return;\n onAddToCart(lines);\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. See fixed.ts for the BYO-cart\n * contract.\n */\nimport {\n formatMoney,\n validateQuantity,\n type MixMatchBundleData,\n type CartLineInput,\n} from \"@lime-bundles/core\";\n\nexport function renderMixMatchBundle(\n container: HTMLElement,\n bundle: MixMatchBundleData,\n onAddToCart: (lines: CartLineInput[]) => void,\n) {\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n const selections = new Map<string, { variantId: string; quantity: number }>();\n\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 const instructions = document.createElement(\"p\");\n instructions.className = \"lb-bundle__instructions\";\n instructions.textContent =\n 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 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 =\n product.variants.nodes.find((v) => v.availableForSale) ??\n 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 const validationEl = document.createElement(\"p\");\n validationEl.className = \"lb-bundle__validation\";\n container.appendChild(validationEl);\n\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(\n (s, v) => s + v.quantity,\n 0,\n );\n const validation = validateQuantity(\n total,\n bundle.minQuantity,\n bundle.maxQuantity,\n );\n button.disabled = !validation.valid;\n button.textContent =\n bundle.widgetConfig.ctaText ?? `Add ${total} Items to Cart`;\n validationEl.textContent = validation.message ?? \"\";\n }\n\n updateCta();\n\n button.addEventListener(\"click\", () => {\n const lines: CartLineInput[] = Array.from(selections.values()).map((s) => ({\n merchandiseId: s.variantId,\n quantity: s.quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n }));\n\n if (lines.length === 0) return;\n onAddToCart(lines);\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. See fixed.ts for the BYO-cart contract.\n */\nimport {\n formatMoney,\n calculateTierSavings,\n type VolumeBundleData,\n type CartLineInput,\n} from \"@lime-bundles/core\";\n\nexport function renderVolumeBundle(\n container: HTMLElement,\n bundle: VolumeBundleData,\n onAddToCart: (lines: CartLineInput[]) => void,\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 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 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 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 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 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(\n bundle.volumeTiers,\n basePrice,\n quantity,\n );\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) {\n quantity--;\n qtyInput.value = String(quantity);\n updateTiers();\n }\n });\n plusBtn.addEventListener(\"click\", () => {\n quantity++;\n qtyInput.value = String(quantity);\n updateTiers();\n });\n qtyInput.addEventListener(\"change\", () => {\n const val = parseInt(qtyInput.value, 10);\n if (!isNaN(val) && val > 0) {\n quantity = val;\n updateTiers();\n }\n });\n\n button.addEventListener(\"click\", () => {\n const variant = product.variants.nodes.find((v) => v.availableForSale);\n if (!variant) return;\n\n onAddToCart([\n {\n merchandiseId: variant.id,\n quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n },\n ]);\n });\n}\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-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 (\n typeof customElements !== \"undefined\" &&\n !customElements.get(\"lime-bundle\")\n) {\n customElements.define(\"lime-bundle\", LimeBundleElement);\n}\n\nexport { LimeBundleElement };\n"],"mappings":"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","names":["formatMoney","escapeHtml","formatMoney","escapeHtml"]}
|
|
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/index.ts"],"sourcesContent":["/**\n * <lime-bundle> Web Component — renders Lime Bundles on any storefront.\n *\n * ## The two modes\n *\n * Single-bundle (pinned):\n * <lime-bundle\n * shop-domain=\"my-shop.myshopify.com\"\n * storefront-token=\"shpat_...\"\n * bundle-gid=\"gid://shopify/Metaobject/42\"\n * ></lime-bundle>\n *\n * Product-aware (matches classic Liquid theme block behaviour — one snippet\n * on the product page renders every active bundle configured against that\n * product):\n * <lime-bundle\n * shop-domain=\"my-shop.myshopify.com\"\n * storefront-token=\"shpat_...\"\n * ></lime-bundle>\n *\n * Product resolution cascade (when no `bundle-gid` is set):\n * 1. explicit `product-handle` attribute\n * 2. <meta name=\"shopify:product-handle\" content=\"...\"> on the page\n * 3. /products/<handle> segment of window.location.pathname\n * 4. fallthrough: renders nothing, fires `lime-bundle:error`\n *\n * ## Add-to-cart behaviour\n *\n * Merchants who do nothing get a default: the widget calls Shopify's\n * Storefront Cart API (tokenless — no extra scopes required) and redirects\n * the browser to the returned checkoutUrl. One-click-to-checkout is the\n * right UX for most merchants pasting the widget into Webflow / Wix /\n * Squarespace / static HTML.\n *\n * Merchants with their own cart state (Hydrogen's useCart, a custom cart\n * drawer, etc.) opt out by attaching a listener that calls\n * `event.preventDefault()`:\n *\n * document.querySelector(\"lime-bundle\").addEventListener(\n * \"lime-bundle:add-to-cart\",\n * (ev) => {\n * ev.preventDefault(); // suppress the default redirect\n * myCart.linesAdd(ev.detail.lines);\n * },\n * );\n *\n * The event is always dispatched; only the default action is conditional.\n */\nimport {\n createStorefrontClient,\n BUNDLE_METAOBJECT_QUERY,\n BUNDLES_FOR_PRODUCT_QUERY,\n CART_CREATE_MUTATION,\n CART_LINES_ADD_MUTATION,\n SHOP_CUSTOM_CSS_QUERY,\n parseMetaobjectBundle,\n observeImpression,\n reportImpression,\n reportAddToCart,\n injectCustomCss,\n fetchBundlesForProduct,\n type ParsedBundle,\n type FixedBundleData,\n type VolumeBundleData,\n type MixMatchBundleData,\n type BundleMetaobjectResponse,\n type BundlesForProductResponse,\n type CartCreateResponse,\n type CartLinesAddResponse,\n type ShopCustomCssResponse,\n type CartLineInput,\n type StorefrontClient,\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\n/**\n * Per-shop localStorage key for the active cart id. Scoping by shop domain\n * keeps the cart isolated when a single browser visits multiple Lime-\n * Bundles-powered storefronts (rare, but correct).\n */\nconst cartStorageKey = (shopDomain: string) => `lb_cart_id:${shopDomain}`;\n\n/**\n * Resolve the current product handle from the page. Runs the cascade\n * documented on LimeBundleElement and returns null if no source matches.\n */\nfunction resolveProductHandle(explicit: string | null): string | null {\n if (explicit) return explicit.trim() || null;\n\n if (typeof document !== \"undefined\") {\n const meta = document.querySelector<HTMLMetaElement>(\n 'meta[name=\"shopify:product-handle\"]',\n );\n if (meta?.content) return meta.content.trim() || null;\n }\n\n if (typeof window !== \"undefined\") {\n const match = window.location.pathname.match(/\\/products\\/([^/?#]+)/);\n if (match?.[1]) return decodeURIComponent(match[1]);\n }\n\n return null;\n}\n\nexport class LimeBundleElement extends HTMLElement {\n static observedAttributes = [\n \"shop-domain\",\n \"storefront-token\",\n \"bundle-gid\",\n \"product-handle\",\n \"app-url\",\n \"analytics\",\n \"locale\",\n ];\n\n private shadow: ShadowRoot;\n private bundles: ParsedBundle[] = [];\n private abortController: AbortController | null = null;\n private impressionCleanups: Array<() => void> = [];\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.teardownImpressions();\n }\n\n private teardownImpressions() {\n for (const cleanup of this.impressionCleanups) cleanup();\n this.impressionCleanups = [];\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null,\n ) {\n if (oldValue === newValue || !this.isConnected) return;\n if (\n name === \"bundle-gid\" ||\n name === \"product-handle\" ||\n name === \"shop-domain\" ||\n name === \"storefront-token\"\n ) {\n if (this.shopDomain && this.storefrontToken) {\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 productHandleAttr(): string {\n return this.getAttribute(\"product-handle\") ?? \"\";\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) {\n this.renderError(\n \"Missing required attributes: shop-domain, storefront-token\",\n );\n return;\n }\n\n this.abortController?.abort();\n const controller = new AbortController();\n this.abortController = controller;\n\n this.renderLoading();\n\n const client = createStorefrontClient({\n shopDomain: this.shopDomain,\n accessToken: this.storefrontToken,\n });\n\n try {\n // Fire bundle query BEFORE CSS so order-sensitive call logs (and any\n // test harness that mocks fetch with sequential mockResolvedValueOnce)\n // see the bundle as call #1, CSS as call #2. Parallelism preserved\n // via Promise.all at the await site below.\n let bundlePromise: Promise<void>;\n let singleBundleMode = false;\n if (this.bundleGid) {\n singleBundleMode = true;\n bundlePromise = this.fetchSingleBundle(client, controller.signal);\n } else {\n const handle = resolveProductHandle(this.productHandleAttr);\n if (!handle) {\n this.teardownImpressions();\n this.renderError(\n \"No bundle-gid or product-handle provided, and the current URL doesn't match /products/<handle>.\",\n );\n return;\n }\n bundlePromise = this.fetchProductBundles(\n client,\n controller.signal,\n handle,\n );\n }\n\n // CSS fetch kicks off AFTER bundle fetch for deterministic call order.\n // Best-effort — widget renders without merchant styling if it fails.\n const cssPromise = client\n .query<ShopCustomCssResponse>(SHOP_CUSTOM_CSS_QUERY, undefined, {\n signal: controller.signal,\n })\n .catch(() => null);\n\n await bundlePromise;\n if (controller.signal.aborted) return;\n\n // Single-bundle mode with an explicit GID that didn't resolve is a\n // genuine error — the merchant pinned a specific bundle and it's\n // missing. Product-handle mode with zero bundles is NOT an error:\n // the product legitimately has no bundles configured; widget stays\n // invisible (mirrors classic-theme Liquid block behaviour).\n if (singleBundleMode && this.bundles.length === 0) {\n this.teardownImpressions();\n this.renderError(\"Bundle not found\");\n return;\n }\n\n const css = await cssPromise;\n if (css?.shop?.metafield?.value) {\n injectCustomCss(this.shopDomain, css.shop.metafield.value);\n }\n\n this.renderBundles();\n } catch (err) {\n if (controller.signal.aborted) return;\n this.bundles = [];\n this.teardownImpressions();\n this.renderError(\n err instanceof Error ? err.message : \"Failed to load bundle\",\n );\n }\n }\n\n private async fetchSingleBundle(\n client: StorefrontClient,\n signal: AbortSignal,\n ): Promise<void> {\n const data = await client.query<BundleMetaobjectResponse>(\n BUNDLE_METAOBJECT_QUERY,\n { id: this.bundleGid },\n { signal },\n );\n if (!data.metaobject) {\n this.bundles = [];\n return;\n }\n const parsed = parseMetaobjectBundle(\n data.metaobject.id,\n data.metaobject.fields,\n );\n this.bundles = parsed ? [parsed] : [];\n }\n\n private async fetchProductBundles(\n client: StorefrontClient,\n signal: AbortSignal,\n productHandle: string,\n ): Promise<void> {\n // fetchBundlesForProduct re-creates its own client; skip that indirection\n // and reuse the one we already built so the request shares the same\n // AbortSignal and header config.\n const data = await client.query<BundlesForProductResponse>(\n BUNDLES_FOR_PRODUCT_QUERY,\n { handle: productHandle },\n { signal },\n );\n if (!data.product) {\n this.bundles = [];\n return;\n }\n const refs = data.product.metafield?.references?.nodes ?? [];\n const bundles: ParsedBundle[] = [];\n for (const ref of refs) {\n const parsed = parseMetaobjectBundle(ref.id, ref.fields);\n if (parsed) bundles.push(parsed);\n }\n this.bundles = bundles;\n }\n\n /**\n * Dispatch add-to-cart with a cancelable event, then — unless a listener\n * called preventDefault — execute the default cart-and-checkout flow.\n *\n * `fire-and-forget` against `reportAddToCart` runs regardless so merchants\n * with BYO cart still get analytics.\n */\n private handleAddToCart = async (\n bundle: ParsedBundle,\n lines: CartLineInput[],\n ): Promise<void> => {\n const ev = new CustomEvent(\"lime-bundle:add-to-cart\", {\n detail: { lines },\n bubbles: true,\n composed: true,\n cancelable: true,\n });\n // dispatchEvent returns false if preventDefault() was called on a\n // cancelable event. That's how merchants opt out of the default flow.\n const allowDefault = this.dispatchEvent(ev);\n\n // Analytics fire regardless of which cart path runs.\n this.reportAddToCartEvent(bundle, lines);\n\n if (allowDefault) {\n await this.defaultAddToCart(lines);\n }\n };\n\n /**\n * Default cart flow: Shopify's Storefront Cart API is tokenless, so we\n * don't need any additional scopes. Persist the cart ID in localStorage\n * so subsequent adds on the same browser session join the existing cart\n * instead of creating a new one every click.\n */\n private async defaultAddToCart(lines: CartLineInput[]): Promise<void> {\n if (typeof window === \"undefined\") return;\n\n const client = createStorefrontClient({\n shopDomain: this.shopDomain,\n accessToken: this.storefrontToken,\n });\n const storage = window.localStorage;\n const key = cartStorageKey(this.shopDomain);\n const existingCartId = storage?.getItem(key) ?? null;\n\n try {\n let checkoutUrl: string | null = null;\n\n if (existingCartId) {\n const res = await client.query<CartLinesAddResponse>(\n CART_LINES_ADD_MUTATION,\n { cartId: existingCartId, lines },\n );\n const payload = res.cartLinesAdd;\n if (payload?.userErrors?.length) {\n // Cart GID expired or was merged on Shopify's side — fall back to\n // cartCreate below. This happens after ~10 days of inactivity.\n storage?.removeItem(key);\n } else if (payload?.cart) {\n checkoutUrl = payload.cart.checkoutUrl;\n }\n }\n\n if (!checkoutUrl) {\n const res = await client.query<CartCreateResponse>(\n CART_CREATE_MUTATION,\n { input: { lines } },\n );\n const payload = res.cartCreate;\n if (payload?.cart) {\n storage?.setItem(key, payload.cart.id);\n checkoutUrl = payload.cart.checkoutUrl;\n }\n }\n\n if (checkoutUrl) {\n window.location.assign(checkoutUrl);\n } else {\n this.dispatchEvent(\n new CustomEvent(\"lime-bundle:error\", {\n detail: { message: \"Cart creation failed\", code: \"CART_ERROR\" },\n bubbles: true,\n composed: true,\n }),\n );\n }\n } catch (err) {\n this.dispatchEvent(\n new CustomEvent(\"lime-bundle:error\", {\n detail: {\n message:\n err instanceof Error ? err.message : \"Cart mutation failed\",\n code: \"CART_ERROR\",\n },\n bubbles: true,\n composed: true,\n }),\n );\n }\n }\n\n private reportAddToCartEvent(\n bundle: ParsedBundle,\n lines: CartLineInput[],\n ): void {\n if (!this.analyticsEnabled || !this.appUrl) return;\n\n const quantity = lines.reduce((sum, l) => sum + l.quantity, 0);\n const totalPrice = lines.reduce((sum, line) => {\n const product = bundle.products.find((p) =>\n p.variants.nodes.some((v) => v.id === line.merchandiseId),\n );\n const variant = product?.variants.nodes.find(\n (v) => v.id === line.merchandiseId,\n );\n const price = variant ? parseFloat(variant.price.amount) : 0;\n return sum + price * line.quantity;\n }, 0);\n\n reportAddToCart(\n { shopDomain: this.shopDomain, appUrl: this.appUrl },\n {\n bundleGid: bundle.id,\n bundleType: bundle.bundleType,\n productId: bundle.products[0]?.id ?? \"\",\n quantity,\n totalPrice: Math.round(totalPrice * 100) / 100,\n },\n );\n }\n\n private renderBundles() {\n this.teardownImpressions();\n this.shadow.innerHTML = \"\";\n\n const style = document.createElement(\"style\");\n style.textContent = WIDGET_STYLES;\n this.shadow.appendChild(style);\n\n // Empty state: the shadow root holds only the <style> tag — nothing\n // visible. Matches Liquid theme UX where a product with no bundles\n // simply shows no block. We still fire `lime-bundle:loaded` below so\n // consumers know the async work completed (important for test\n // synchronisation and for merchants who want to hide a parent\n // placeholder once the widget has decided whether to render).\n\n for (const bundle of this.bundles) {\n const container = document.createElement(\"div\");\n container.className = \"lb-bundle\";\n container.setAttribute(\"role\", \"region\");\n container.setAttribute(\"aria-label\", bundle.title);\n\n const dispatch = (lines: CartLineInput[]) =>\n this.handleAddToCart(bundle, lines);\n\n switch (bundle.bundleType) {\n case \"fixed\":\n renderFixedBundle(container, bundle as FixedBundleData, dispatch);\n break;\n case \"mix_match\":\n renderMixMatchBundle(\n container,\n bundle as MixMatchBundleData,\n dispatch,\n );\n break;\n case \"volume\":\n renderVolumeBundle(container, bundle as VolumeBundleData, dispatch);\n break;\n }\n\n this.shadow.appendChild(container);\n this.setupImpressionFor(bundle, container);\n }\n\n const first = this.bundles[0];\n this.dispatchEvent(\n new CustomEvent(\"lime-bundle:loaded\", {\n detail: {\n bundleCount: this.bundles.length,\n bundleTypes: this.bundles.map((b) => b.bundleType),\n // Legacy fields — meaningful only in single-bundle mode. Preserved\n // for merchants who attached listeners against the pre-1.0 shape.\n bundleType: first?.bundleType,\n title: first?.title,\n },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n private setupImpressionFor(bundle: ParsedBundle, element: Element) {\n if (!this.analyticsEnabled || !this.appUrl) return;\n const cleanup = observeImpression(element, () => {\n reportImpression(\n { shopDomain: this.shopDomain, appUrl: this.appUrl },\n {\n bundleGid: bundle.id,\n bundleType: bundle.bundleType,\n },\n );\n });\n this.impressionCleanups.push(cleanup);\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 this.shadow.innerHTML = `<style>${WIDGET_STYLES}</style>`;\n }\n}\n\n// Re-export the helper so consumers of the widget package can import it\n// when they want to share the URL-detection logic with their own code.\nexport { resolveProductHandle, fetchBundlesForProduct };\n","/**\n * DOM renderer for fixed bundles.\n *\n * Takes the narrowed `FixedBundleData` variant so we get TS errors if any\n * caller passes a non-fixed bundle.\n *\n * `onAddToCart` is a BYO-cart dispatch: the widget owner listens for\n * `lime-bundle:add-to-cart` (CustomEvent) and performs the actual cart\n * mutation. This renderer only builds the DOM and invokes the dispatch\n * — it does not know how or whether the add succeeds.\n */\nimport {\n formatMoney,\n type FixedBundleData,\n type CartLineInput,\n} from \"@lime-bundles/core\";\n\nexport function renderFixedBundle(\n container: HTMLElement,\n bundle: FixedBundleData,\n onAddToCart: (lines: CartLineInput[]) => void,\n) {\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\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 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 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 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\", () => {\n const lines: CartLineInput[] = bundle.products\n .filter((p) => p.variants.nodes.some((v) => v.availableForSale))\n .map((p) => {\n const variant = p.variants.nodes.find((v) => v.availableForSale)!;\n return {\n merchandiseId: variant.id,\n quantity: 1,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n };\n });\n\n if (lines.length === 0) return;\n onAddToCart(lines);\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. See fixed.ts for the BYO-cart\n * contract.\n */\nimport {\n formatMoney,\n validateQuantity,\n type MixMatchBundleData,\n type CartLineInput,\n} from \"@lime-bundles/core\";\n\nexport function renderMixMatchBundle(\n container: HTMLElement,\n bundle: MixMatchBundleData,\n onAddToCart: (lines: CartLineInput[]) => void,\n) {\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n const selections = new Map<string, { variantId: string; quantity: number }>();\n\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 const instructions = document.createElement(\"p\");\n instructions.className = \"lb-bundle__instructions\";\n instructions.textContent =\n 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 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 =\n product.variants.nodes.find((v) => v.availableForSale) ??\n 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 const validationEl = document.createElement(\"p\");\n validationEl.className = \"lb-bundle__validation\";\n container.appendChild(validationEl);\n\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(\n (s, v) => s + v.quantity,\n 0,\n );\n const validation = validateQuantity(\n total,\n bundle.minQuantity,\n bundle.maxQuantity,\n );\n button.disabled = !validation.valid;\n button.textContent =\n bundle.widgetConfig.ctaText ?? `Add ${total} Items to Cart`;\n validationEl.textContent = validation.message ?? \"\";\n }\n\n updateCta();\n\n button.addEventListener(\"click\", () => {\n const lines: CartLineInput[] = Array.from(selections.values()).map((s) => ({\n merchandiseId: s.variantId,\n quantity: s.quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n }));\n\n if (lines.length === 0) return;\n onAddToCart(lines);\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. See fixed.ts for the BYO-cart contract.\n */\nimport {\n formatMoney,\n calculateTierSavings,\n type VolumeBundleData,\n type CartLineInput,\n} from \"@lime-bundles/core\";\n\nexport function renderVolumeBundle(\n container: HTMLElement,\n bundle: VolumeBundleData,\n onAddToCart: (lines: CartLineInput[]) => void,\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 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 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 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 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 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(\n bundle.volumeTiers,\n basePrice,\n quantity,\n );\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) {\n quantity--;\n qtyInput.value = String(quantity);\n updateTiers();\n }\n });\n plusBtn.addEventListener(\"click\", () => {\n quantity++;\n qtyInput.value = String(quantity);\n updateTiers();\n });\n qtyInput.addEventListener(\"change\", () => {\n const val = parseInt(qtyInput.value, 10);\n if (!isNaN(val) && val > 0) {\n quantity = val;\n updateTiers();\n }\n });\n\n button.addEventListener(\"click\", () => {\n const variant = product.variants.nodes.find((v) => v.availableForSale);\n if (!variant) return;\n\n onAddToCart([\n {\n merchandiseId: variant.id,\n quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n },\n ]);\n });\n}\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-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 (\n typeof customElements !== \"undefined\" &&\n !customElements.get(\"lime-bundle\")\n) {\n customElements.define(\"lime-bundle\", LimeBundleElement);\n}\n\nexport { LimeBundleElement };\n"],"mappings":"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","names":["formatMoney","escapeHtml","formatMoney","escapeHtml"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var LimeBundles=(()=>{var
|
|
1
|
+
"use strict";var LimeBundles=(()=>{var A=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var Z=Object.prototype.hasOwnProperty;var ee=(r,e)=>{for(var t in e)A(r,t,{get:e[t],enumerable:!0})},te=(r,e,t,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of X(e))!Z.call(r,n)&&n!==t&&A(r,n,{get:()=>e[n],enumerable:!(a=K(e,n))||a.enumerable});return r};var ne=r=>te(A({},"__esModule",{value:!0}),r);var ve={};ee(ve,{LimeBundleElement:()=>x});var P=class extends Error{constructor(r){super(r.map(e=>e.message).join("; ")),this.errors=r,this.name="StorefrontApiError"}},re="2025-10";function I(r){let e=r.apiVersion??re,t=`https://${r.shopDomain}/api/${e}/graphql.json`;return{async query(a,n,s){let i={"Content-Type":"application/json","X-Shopify-Storefront-Access-Token":r.accessToken};r.buyerIp&&(i["Shopify-Storefront-Buyer-IP"]=r.buyerIp);let l=await fetch(t,{method:"POST",headers:i,body:JSON.stringify({query:a,variables:n}),signal:s?.signal});if(!l.ok)throw new P([{message:`Storefront API error: ${l.status} ${l.statusText}`}]);let o=await l.json();if(o.errors?.length)throw new P(o.errors);return o.data}}}var $=`#graphql
|
|
2
2
|
query BundleMetaobject($id: ID!) {
|
|
3
3
|
metaobject(id: $id) {
|
|
4
4
|
id
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
`,
|
|
85
|
+
`,M=`#graphql
|
|
86
86
|
query ShopCustomCss {
|
|
87
87
|
shop {
|
|
88
88
|
metafield(namespace: "$app", key: "custom_css") {
|
|
@@ -90,21 +90,130 @@
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
-
`,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
`,N=`#graphql
|
|
94
|
+
query BundlesForProduct($handle: String!) {
|
|
95
|
+
product(handle: $handle) {
|
|
96
|
+
id
|
|
97
|
+
handle
|
|
98
|
+
title
|
|
99
|
+
metafield(namespace: "$app", key: "active_bundle_ids") {
|
|
100
|
+
references(first: 10) {
|
|
101
|
+
nodes {
|
|
102
|
+
... on Metaobject {
|
|
103
|
+
id
|
|
104
|
+
type
|
|
105
|
+
fields {
|
|
106
|
+
key
|
|
107
|
+
value
|
|
108
|
+
reference {
|
|
109
|
+
... on Product {
|
|
110
|
+
id
|
|
111
|
+
title
|
|
112
|
+
handle
|
|
113
|
+
featuredImage { url altText }
|
|
114
|
+
priceRange {
|
|
115
|
+
minVariantPrice { amount currencyCode }
|
|
116
|
+
maxVariantPrice { amount currencyCode }
|
|
117
|
+
}
|
|
118
|
+
variants(first: 100) {
|
|
119
|
+
nodes {
|
|
120
|
+
id
|
|
121
|
+
title
|
|
122
|
+
availableForSale
|
|
123
|
+
price { amount currencyCode }
|
|
124
|
+
compareAtPrice { amount currencyCode }
|
|
125
|
+
selectedOptions { name value }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
references(first: 50) {
|
|
131
|
+
nodes {
|
|
132
|
+
... on Product {
|
|
133
|
+
id
|
|
134
|
+
title
|
|
135
|
+
handle
|
|
136
|
+
featuredImage { url altText }
|
|
137
|
+
priceRange {
|
|
138
|
+
minVariantPrice { amount currencyCode }
|
|
139
|
+
maxVariantPrice { amount currencyCode }
|
|
140
|
+
}
|
|
141
|
+
variants(first: 100) {
|
|
142
|
+
nodes {
|
|
143
|
+
id
|
|
144
|
+
title
|
|
145
|
+
availableForSale
|
|
146
|
+
price { amount currencyCode }
|
|
147
|
+
compareAtPrice { amount currencyCode }
|
|
148
|
+
selectedOptions { name value }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
... on Collection {
|
|
153
|
+
id
|
|
154
|
+
title
|
|
155
|
+
handle
|
|
156
|
+
products(first: 50) {
|
|
157
|
+
nodes {
|
|
158
|
+
id
|
|
159
|
+
title
|
|
160
|
+
handle
|
|
161
|
+
featuredImage { url altText }
|
|
162
|
+
priceRange {
|
|
163
|
+
minVariantPrice { amount currencyCode }
|
|
164
|
+
maxVariantPrice { amount currencyCode }
|
|
165
|
+
}
|
|
166
|
+
variants(first: 100) {
|
|
167
|
+
nodes {
|
|
168
|
+
id
|
|
169
|
+
title
|
|
170
|
+
availableForSale
|
|
171
|
+
price { amount currencyCode }
|
|
172
|
+
compareAtPrice { amount currencyCode }
|
|
173
|
+
selectedOptions { name value }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
`,R=`#graphql
|
|
189
|
+
mutation CartCreate($input: CartInput!) {
|
|
190
|
+
cartCreate(input: $input) {
|
|
191
|
+
cart { id checkoutUrl }
|
|
192
|
+
userErrors { field message }
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
`,O=`#graphql
|
|
196
|
+
mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
|
|
197
|
+
cartLinesAdd(cartId: $cartId, lines: $lines) {
|
|
198
|
+
cart { id checkoutUrl }
|
|
199
|
+
userErrors { field message }
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
`,y=class extends Error{constructor(r,e){super(r),this.reason=e,this.name="BundleParseError"}},ae=new Set(["fixed","mix_match","volume"]),ie=new Set(["active"]);function k(r,e){try{return oe(r,e)}catch(t){if(t instanceof y)return null;throw t}}function oe(r,e){let t=new Map(e.map(d=>[d.key,d])),a=t.get("title")?.value??"Bundle",n=t.get("bundle_type")?.value,s=t.get("status")?.value;if(!n||!ae.has(n))throw new y(`Invalid or missing bundle_type: ${n??"null"}`,"invalid_type");let i=n;if(!s||!ie.has(s))throw new y(`Bundle is not active: status=${s??"null"}`,"inactive");let l=s,o=t.get("starts_at")?.value??null,c=t.get("ends_at")?.value??null,u=new Date;if(o){let d=new Date(o);if(Number.isNaN(d.getTime()))throw new y(`Invalid starts_at: ${o}`,"invalid_type");if(d>u)throw new y(`Bundle not yet started: starts_at=${o}`,"not_started")}if(c){let d=new Date(c);if(Number.isNaN(d.getTime()))throw new y(`Invalid ends_at: ${c}`,"invalid_type");if(d<u)throw new y(`Bundle has expired: ends_at=${c}`,"expired")}let p=se(t),m=le(t),f=de(t),h={id:r,title:a,status:l,products:p,discountConfig:m,widgetConfig:f,startsAt:o,endsAt:c,discountLabel:t.get("discount_label")?.value??null,abTestId:t.get("ab_test_id")?.value??null,abTestConfig:B(t,"ab_test_config")};switch(i){case"fixed":return{...h,bundleType:"fixed"};case"volume":return{...h,bundleType:"volume",volumeTiers:ce(t)};case"mix_match":return{...h,bundleType:"mix_match",minQuantity:L(t,"min_quantity"),maxQuantity:L(t,"max_quantity")}}}function se(r){let e=[],t=r.get("products");if(t?.reference&&"variants"in t.reference&&e.push(t.reference),t?.references?.nodes)for(let n of t.references.nodes)"variants"in n?e.push(n):"products"in n&&n.products?.nodes&&e.push(...n.products.nodes);let a=r.get("collection");if(a?.references?.nodes)for(let n of a.references.nodes)"products"in n&&n.products?.nodes&&e.push(...n.products.nodes);return e}function le(r){return{discountType:r.get("discount_type")?.value??"percentage",discountValue:parseFloat(r.get("discount_value")?.value??"0"),allowStacking:r.get("allow_stacking")?.value==="true"}}function de(r){let e=B(r,"widget_config");if(e&&typeof e=="object"){let t=e;return{primaryColor:t.primaryColor??null,ctaText:t.ctaText??null,outOfStockBehavior:t.outOfStockBehavior??"hide"}}return{primaryColor:null,ctaText:null,outOfStockBehavior:"hide"}}function ce(r){let e=B(r,"volume_tiers");return Array.isArray(e)?e.filter(t=>typeof t=="object"&&t!==null).map(t=>({minQuantity:Number(t.minQuantity??0),discountType:t.discountType??"percentage",discountValue:Number(t.discountValue??0),label:t.label??null})):[]}function B(r,e){let t=r.get(e)?.value;if(!t)return null;try{return JSON.parse(t)}catch{return null}}function L(r,e){let t=r.get(e)?.value;if(!t)return null;let a=parseInt(t,10);return isNaN(a)?null:a}function q(r,e,t){return[...r].sort((n,s)=>n.minQuantity-s.minQuantity).map(n=>{let s=n.discountType==="percentage"?e*(n.discountValue/100):n.discountValue,i=Math.max(0,e-s),l=e-i,o=e>0?l/e*100:0,c=t>=n.minQuantity;return{tier:n,unitPrice:i,savings:l,savingsPercent:o,isActive:c}})}function F(r,e,t){return e!==null&&r<e?{valid:!1,totalQuantity:r,message:`Select at least ${e} item${e!==1?"s":""}`}:t!==null&&r>t?{valid:!1,totalQuantity:r,message:`Select at most ${t} item${t!==1?"s":""}`}:{valid:!0,totalQuantity:r,message:null}}async function U(r,e){await j(r,{shopDomain:r.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 V(r,e){await j(r,{shopDomain:r.shopDomain,eventType:"bundle_add_to_cart",...e,occurredAt:new Date().toISOString()})}function H(r,e){if(typeof IntersectionObserver>"u")return e(),()=>{};let t=new IntersectionObserver(a=>{for(let n of a)n.isIntersecting&&(e(),t.unobserve(n.target))},{threshold:.5});return t.observe(r),()=>t.disconnect()}async function j(r,e){let t=`${r.appUrl}/api/analytics`,a=JSON.stringify(e);try{(await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:a})).ok||(await new Promise(s=>setTimeout(s,2e3)),await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:a}))}catch{try{typeof navigator<"u"&&navigator.sendBeacon&&navigator.sendBeacon(t,a)}catch{}}}var _e=720*60*60,Ce=720*60*60;var D=1e4,ue=[[/@import/i,"@import rules"],[/@charset/i,"@charset declarations"],[/expression\s*\(/i,"CSS expressions"],[/-moz-binding/i,"-moz-binding"],[/-webkit-binding/i,"-webkit-binding"],[/behavior\s*:/i,"behavior property"]],pe=/^(https:|\/[^/]|\.\/|\.\.\/|#)/;function me(r){if(r.length>D)return{ok:!1,error:`CSS exceeds ${D.toLocaleString("en-US")} character limit`};let e=r.replace(/\/\*[\s\S]*?\*\//g,"");try{e=e.replace(/\\([0-9a-fA-F]{1,6})[ \t\n\f]?/g,(n,s)=>{let i=parseInt(s,16);return i<0||i>1114111?"\uFFFD":String.fromCodePoint(i)}),e=e.replace(/\\\r?\n/g,""),e=e.replace(/\\([^\n\r\f0-9a-fA-F])/g,"$1")}catch{return{ok:!1,error:"CSS contains invalid unicode escape sequences"}}e=e.replace(/</g,"").replace(/>/g,"");for(let[n,s]of ue)if(n.test(e))return{ok:!1,error:`CSS contains blocked pattern: ${s}`};let t=/url\s*\(\s*([\s\S]*?)\s*\)/gi,a;for(;(a=t.exec(e))!==null;){let n=a[1].trim();if((n.startsWith("'")||n.startsWith('"'))&&(n=n.slice(1)),(n.endsWith("'")||n.endsWith('"'))&&(n=n.slice(0,-1)),n=n.trim(),n&&!pe.test(n))return{ok:!1,error:"CSS url() values must use https:// or relative paths"}}return{ok:!0,css:e}}var be="lb-custom-css-";function z(r,e){if(typeof document>"u"||!e)return!1;let t=me(e);if(!t.ok||!t.css.trim())return!1;let a=be+fe(r),n=document.getElementById(a);return n||(n=document.createElement("style"),n.id=a,n.setAttribute("data-lime-bundles","custom-css"),document.head.appendChild(n)),n.textContent!==t.css&&(n.textContent=t.css),!0}function fe(r){let e=2166136261;for(let t=0;t<r.length;t++)e^=r.charCodeAt(t),e=Math.imul(e,16777619);return(e>>>0).toString(16)}function _(r,e){let t=typeof r=="string"?parseFloat(r):r;try{return new Intl.NumberFormat(void 0,{style:"currency",currency:e}).format(t)}catch{return`${e} ${t.toFixed(2)}`}}function G(r,e,t){let a=e.products[0]?.priceRange.minVariantPrice.currencyCode??"USD",n=document.createElement("h3");if(n.className="lb-bundle__title",n.textContent=e.title,n.setAttribute("part","title"),r.appendChild(n),e.discountLabel){let l=document.createElement("span");l.className="lb-bundle__discount-badge",l.textContent=e.discountLabel,r.appendChild(l)}let s=document.createElement("div");s.className="lb-bundle__products";for(let l of e.products){let o=document.createElement("div");if(o.className="lb-bundle__product",o.setAttribute("part","product"),l.featuredImage){let u=document.createElement("img");u.src=l.featuredImage.url,u.alt=l.featuredImage.altText??l.title,u.className="lb-bundle__product-image",u.loading="lazy",o.appendChild(u)}let c=document.createElement("div");c.className="lb-bundle__product-info",c.innerHTML=`
|
|
203
|
+
<p class="lb-bundle__product-title">${Q(l.title)}</p>
|
|
204
|
+
<p class="lb-bundle__product-price">${Q(_(l.priceRange.minVariantPrice.amount,a))}</p>
|
|
205
|
+
`,o.appendChild(c),s.appendChild(o)}r.appendChild(s);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",()=>{let l=e.products.filter(o=>o.variants.nodes.some(c=>c.availableForSale)).map(o=>({merchandiseId:o.variants.nodes.find(u=>u.availableForSale).id,quantity:1,attributes:[{key:"_lime_bundle_gid",value:e.id},{key:"_lime_bundle_type",value:e.bundleType}]}));l.length!==0&&t(l)}),r.appendChild(i)}function Q(r){let e=document.createElement("div");return e.textContent=r,e.innerHTML}function Y(r,e,t){let a=e.products[0]?.priceRange.minVariantPrice.currencyCode??"USD",n=new Map,s=document.createElement("h3");s.className="lb-bundle__title",s.textContent=e.title,s.setAttribute("part","title"),r.appendChild(s);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",r.appendChild(i);let l=document.createElement("div");l.className="lb-bundle__products lb-bundle__products--selectable";for(let p of e.products){let m=p.variants.nodes.find(b=>b.availableForSale)??p.variants.nodes[0];if(!m)continue;let f=document.createElement("div");if(f.className="lb-bundle__product lb-bundle__product--selectable",p.featuredImage){let b=document.createElement("img");b.src=p.featuredImage.url,b.alt=p.featuredImage.altText??p.title,b.className="lb-bundle__product-image",b.loading="lazy",f.appendChild(b)}let h=document.createElement("div");h.className="lb-bundle__product-info",h.innerHTML=`
|
|
206
|
+
<p class="lb-bundle__product-title">${W(p.title)}</p>
|
|
207
|
+
<p class="lb-bundle__product-price">${W(_(m.price.amount,a))}</p>
|
|
208
|
+
`,f.appendChild(h);let d=document.createElement("button");d.className="lb-bundle__select-btn",d.textContent=m.availableForSale?"Select":"Sold out",d.disabled=!m.availableForSale,d.addEventListener("click",()=>{let b=p.id;n.has(b)?(n.delete(b),f.classList.remove("lb-bundle__product--selected"),d.textContent="Select"):(n.set(b,{variantId:m.id,quantity:1}),f.classList.add("lb-bundle__product--selected"),d.textContent="Selected"),u()}),f.appendChild(d),l.appendChild(f)}r.appendChild(l);let o=document.createElement("p");o.className="lb-bundle__validation",r.appendChild(o);let c=document.createElement("button");c.className="lb-bundle__cta",c.setAttribute("part","button"),c.disabled=!0,r.appendChild(c);function u(){let p=Array.from(n.values()).reduce((f,h)=>f+h.quantity,0),m=F(p,e.minQuantity,e.maxQuantity);c.disabled=!m.valid,c.textContent=e.widgetConfig.ctaText??`Add ${p} Items to Cart`,o.textContent=m.message??""}u(),c.addEventListener("click",()=>{let p=Array.from(n.values()).map(m=>({merchandiseId:m.variantId,quantity:m.quantity,attributes:[{key:"_lime_bundle_gid",value:e.id},{key:"_lime_bundle_type",value:e.bundleType}]}));p.length!==0&&t(p)})}function W(r){let e=document.createElement("div");return e.textContent=r,e.innerHTML}function J(r,e,t){let a=e.products[0];if(!a)return;let n=parseFloat(a.priceRange.minVariantPrice.amount),s=a.priceRange.minVariantPrice.currencyCode,i=1,l=document.createElement("h3");l.className="lb-bundle__title",l.textContent=e.title,l.setAttribute("part","title"),r.appendChild(l);let o=document.createElement("div");if(o.className="lb-bundle__product lb-bundle__product--volume",a.featuredImage){let g=document.createElement("img");g.src=a.featuredImage.url,g.alt=a.featuredImage.altText??a.title,g.className="lb-bundle__product-image",g.loading="lazy",o.appendChild(g)}let c=document.createElement("div");c.className="lb-bundle__product-info",c.innerHTML=`
|
|
209
|
+
<p class="lb-bundle__product-title">${S(a.title)}</p>
|
|
210
|
+
<p class="lb-bundle__product-price">${S(_(n,s))} each</p>
|
|
211
|
+
`,o.appendChild(c),r.appendChild(o);let u=document.createElement("div");u.className="lb-bundle__tiers",u.setAttribute("role","table"),u.setAttribute("aria-label","Volume discounts"),r.appendChild(u);let p=document.createElement("div");p.className="lb-bundle__quantity-selector";let m=document.createElement("label");m.textContent="Quantity",p.appendChild(m);let f=document.createElement("div");f.className="lb-bundle__quantity-control";let h=document.createElement("button");h.textContent="\u2212",h.setAttribute("aria-label","Decrease quantity");let d=document.createElement("input");d.type="number",d.min="1",d.value="1",d.className="lb-bundle__quantity-input";let b=document.createElement("button");b.textContent="+",b.setAttribute("aria-label","Increase quantity"),f.append(h,d,b),p.appendChild(f),r.appendChild(p);let C=document.createElement("button");C.className="lb-bundle__cta",C.setAttribute("part","button"),r.appendChild(C);function E(){let g=q(e.volumeTiers,n,i);u.innerHTML="";for(let v of g){let T=document.createElement("div");T.className=`lb-bundle__tier${v.isActive?" lb-bundle__tier--active":""}`,T.setAttribute("role","row"),T.innerHTML=`
|
|
103
212
|
<span class="lb-bundle__tier-quantity" role="cell">${v.tier.minQuantity}+ items</span>
|
|
104
|
-
<span class="lb-bundle__tier-price" role="cell">${
|
|
213
|
+
<span class="lb-bundle__tier-price" role="cell">${S(_(v.unitPrice,s))} each</span>
|
|
105
214
|
<span class="lb-bundle__tier-savings" role="cell">Save ${v.savingsPercent.toFixed(0)}%</span>
|
|
106
|
-
${v.tier.label?`<span class="lb-bundle__tier-label" role="cell">${
|
|
107
|
-
`,u.appendChild(
|
|
215
|
+
${v.tier.label?`<span class="lb-bundle__tier-label" role="cell">${S(v.tier.label)}</span>`:""}
|
|
216
|
+
`,u.appendChild(T)}C.textContent=e.widgetConfig.ctaText??`Add ${i} to Cart`}E(),h.addEventListener("click",()=>{i>1&&(i--,d.value=String(i),E())}),b.addEventListener("click",()=>{i++,d.value=String(i),E()}),d.addEventListener("change",()=>{let g=parseInt(d.value,10);!isNaN(g)&&g>0&&(i=g,E())}),C.addEventListener("click",()=>{let g=a.variants.nodes.find(v=>v.availableForSale);g&&t([{merchandiseId:g.id,quantity:i,attributes:[{key:"_lime_bundle_gid",value:e.id},{key:"_lime_bundle_type",value:e.bundleType}]}])})}function S(r){let e=document.createElement("div");return e.textContent=r,e.innerHTML}var w=`
|
|
108
217
|
:host {
|
|
109
218
|
display: block;
|
|
110
219
|
--lb-primary-color: #000;
|
|
@@ -167,11 +276,11 @@
|
|
|
167
276
|
.lb-skeleton--title { height: 24px; width: 60%; margin-bottom: var(--lb-spacing-md); }
|
|
168
277
|
.lb-skeleton--products { height: 200px; }
|
|
169
278
|
@keyframes lb-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
|
|
170
|
-
`;var
|
|
279
|
+
`;var he=r=>`lb_cart_id:${r}`;function ge(r){if(r)return r.trim()||null;if(typeof document<"u"){let e=document.querySelector('meta[name="shopify:product-handle"]');if(e?.content)return e.content.trim()||null}if(typeof window<"u"){let e=window.location.pathname.match(/\/products\/([^/?#]+)/);if(e?.[1])return decodeURIComponent(e[1])}return null}var x=class extends HTMLElement{static observedAttributes=["shop-domain","storefront-token","bundle-gid","product-handle","app-url","analytics","locale"];shadow;bundles=[];abortController=null;impressionCleanups=[];constructor(){super(),this.shadow=this.attachShadow({mode:"open"})}connectedCallback(){this.render(),this.fetchBundle()}disconnectedCallback(){this.abortController?.abort(),this.teardownImpressions()}teardownImpressions(){for(let e of this.impressionCleanups)e();this.impressionCleanups=[]}attributeChangedCallback(e,t,a){t===a||!this.isConnected||(e==="bundle-gid"||e==="product-handle"||e==="shop-domain"||e==="storefront-token")&&this.shopDomain&&this.storefrontToken&&this.fetchBundle()}get shopDomain(){return this.getAttribute("shop-domain")??""}get storefrontToken(){return this.getAttribute("storefront-token")??""}get bundleGid(){return this.getAttribute("bundle-gid")??""}get productHandleAttr(){return this.getAttribute("product-handle")??""}get appUrl(){return this.getAttribute("app-url")??""}get analyticsEnabled(){return this.getAttribute("analytics")!=="false"}async fetchBundle(){if(!this.shopDomain||!this.storefrontToken){this.renderError("Missing required attributes: shop-domain, storefront-token");return}this.abortController?.abort();let e=new AbortController;this.abortController=e,this.renderLoading();let t=I({shopDomain:this.shopDomain,accessToken:this.storefrontToken});try{let a,n=!1;if(this.bundleGid)n=!0,a=this.fetchSingleBundle(t,e.signal);else{let l=ge(this.productHandleAttr);if(!l){this.teardownImpressions(),this.renderError("No bundle-gid or product-handle provided, and the current URL doesn't match /products/<handle>.");return}a=this.fetchProductBundles(t,e.signal,l)}let s=t.query(M,void 0,{signal:e.signal}).catch(()=>null);if(await a,e.signal.aborted)return;if(n&&this.bundles.length===0){this.teardownImpressions(),this.renderError("Bundle not found");return}let i=await s;i?.shop?.metafield?.value&&z(this.shopDomain,i.shop.metafield.value),this.renderBundles()}catch(a){if(e.signal.aborted)return;this.bundles=[],this.teardownImpressions(),this.renderError(a instanceof Error?a.message:"Failed to load bundle")}}async fetchSingleBundle(e,t){let a=await e.query($,{id:this.bundleGid},{signal:t});if(!a.metaobject){this.bundles=[];return}let n=k(a.metaobject.id,a.metaobject.fields);this.bundles=n?[n]:[]}async fetchProductBundles(e,t,a){let n=await e.query(N,{handle:a},{signal:t});if(!n.product){this.bundles=[];return}let s=n.product.metafield?.references?.nodes??[],i=[];for(let l of s){let o=k(l.id,l.fields);o&&i.push(o)}this.bundles=i}handleAddToCart=async(e,t)=>{let a=new CustomEvent("lime-bundle:add-to-cart",{detail:{lines:t},bubbles:!0,composed:!0,cancelable:!0}),n=this.dispatchEvent(a);this.reportAddToCartEvent(e,t),n&&await this.defaultAddToCart(t)};async defaultAddToCart(e){if(typeof window>"u")return;let t=I({shopDomain:this.shopDomain,accessToken:this.storefrontToken}),a=window.localStorage,n=he(this.shopDomain),s=a?.getItem(n)??null;try{let i=null;if(s){let o=(await t.query(O,{cartId:s,lines:e})).cartLinesAdd;o?.userErrors?.length?a?.removeItem(n):o?.cart&&(i=o.cart.checkoutUrl)}if(!i){let o=(await t.query(R,{input:{lines:e}})).cartCreate;o?.cart&&(a?.setItem(n,o.cart.id),i=o.cart.checkoutUrl)}i?window.location.assign(i):this.dispatchEvent(new CustomEvent("lime-bundle:error",{detail:{message:"Cart creation failed",code:"CART_ERROR"},bubbles:!0,composed:!0}))}catch(i){this.dispatchEvent(new CustomEvent("lime-bundle:error",{detail:{message:i instanceof Error?i.message:"Cart mutation failed",code:"CART_ERROR"},bubbles:!0,composed:!0}))}}reportAddToCartEvent(e,t){if(!this.analyticsEnabled||!this.appUrl)return;let a=t.reduce((s,i)=>s+i.quantity,0),n=t.reduce((s,i)=>{let o=e.products.find(u=>u.variants.nodes.some(p=>p.id===i.merchandiseId))?.variants.nodes.find(u=>u.id===i.merchandiseId),c=o?parseFloat(o.price.amount):0;return s+c*i.quantity},0);V({shopDomain:this.shopDomain,appUrl:this.appUrl},{bundleGid:e.id,bundleType:e.bundleType,productId:e.products[0]?.id??"",quantity:a,totalPrice:Math.round(n*100)/100})}renderBundles(){this.teardownImpressions(),this.shadow.innerHTML="";let e=document.createElement("style");e.textContent=w,this.shadow.appendChild(e);for(let a of this.bundles){let n=document.createElement("div");n.className="lb-bundle",n.setAttribute("role","region"),n.setAttribute("aria-label",a.title);let s=i=>this.handleAddToCart(a,i);switch(a.bundleType){case"fixed":G(n,a,s);break;case"mix_match":Y(n,a,s);break;case"volume":J(n,a,s);break}this.shadow.appendChild(n),this.setupImpressionFor(a,n)}let t=this.bundles[0];this.dispatchEvent(new CustomEvent("lime-bundle:loaded",{detail:{bundleCount:this.bundles.length,bundleTypes:this.bundles.map(a=>a.bundleType),bundleType:t?.bundleType,title:t?.title},bubbles:!0,composed:!0}))}setupImpressionFor(e,t){if(!this.analyticsEnabled||!this.appUrl)return;let a=H(t,()=>{U({shopDomain:this.shopDomain,appUrl:this.appUrl},{bundleGid:e.id,bundleType:e.bundleType})});this.impressionCleanups.push(a)}renderLoading(){this.shadow.innerHTML=`
|
|
171
280
|
<style>${w}</style>
|
|
172
281
|
<div class="lb-bundle lb-bundle--loading">
|
|
173
282
|
<div class="lb-skeleton lb-skeleton--title"></div>
|
|
174
283
|
<div class="lb-skeleton lb-skeleton--products"></div>
|
|
175
284
|
</div>
|
|
176
|
-
`}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>`}};typeof customElements<"u"&&!customElements.get("lime-bundle")&&customElements.define("lime-bundle",
|
|
285
|
+
`}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>`}};typeof customElements<"u"&&!customElements.get("lime-bundle")&&customElements.define("lime-bundle",x);return ne(ve);})();
|
|
177
286
|
//# sourceMappingURL=lime-bundle.global.js.map
|