@lime-bundles/widget 2.5.1 → 3.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/dist/index.cjs CHANGED
@@ -26,7 +26,7 @@ __export(index_exports, {
26
26
  module.exports = __toCommonJS(index_exports);
27
27
 
28
28
  // src/lime-bundle.ts
29
- var import_core7 = require("@lime-bundles/core");
29
+ var import_core8 = require("@lime-bundles/core");
30
30
 
31
31
  // src/renderers/fixed.ts
32
32
  var import_core5 = require("@lime-bundles/core");
@@ -77,6 +77,20 @@ function firstEnabled(opts) {
77
77
  for (let i = 0; i < opts.length; i++) if (!opts[i].disabled) return i;
78
78
  return -1;
79
79
  }
80
+ function findScrollableAncestor(el2) {
81
+ const win = el2.ownerDocument?.defaultView;
82
+ if (!win) return null;
83
+ let cur = el2.parentElement;
84
+ while (cur && cur !== el2.ownerDocument.body) {
85
+ const style = win.getComputedStyle(cur);
86
+ const overflowY = style.overflowY;
87
+ if (overflowY === "auto" || overflowY === "scroll" || overflowY === "hidden") {
88
+ return cur;
89
+ }
90
+ cur = cur.parentElement;
91
+ }
92
+ return null;
93
+ }
80
94
  var VARIANT_SELECT_CLASSES = [
81
95
  "lb-bundle-variant-select",
82
96
  "lb-mix-match__variant-select"
@@ -182,6 +196,11 @@ function bindDropdown(select) {
182
196
  if (rect.width === 0) return false;
183
197
  const visibleCount = Math.min(optionEls.length || 1, MAX_VISIBLE_ITEMS);
184
198
  const desiredHeight = visibleCount * ITEM_HEIGHT_PX + LIST_PAD_Y;
199
+ const scrollable = listbox.hasAttribute("data-lb-dropdown-portal") ? null : findScrollableAncestor(trigger);
200
+ const clip = scrollable ? (() => {
201
+ const r = scrollable.getBoundingClientRect();
202
+ return { top: r.top, bottom: r.bottom };
203
+ })() : void 0;
185
204
  const result = computePosition({
186
205
  trigger: {
187
206
  top: rect.top,
@@ -190,7 +209,8 @@ function bindDropdown(select) {
190
209
  width: rect.width
191
210
  },
192
211
  viewportHeight: window.innerHeight,
193
- desiredHeight
212
+ desiredHeight,
213
+ clip
194
214
  });
195
215
  listbox.setAttribute("data-placement", result.placement);
196
216
  listbox.style.maxHeight = `${result.maxHeight}px`;
@@ -534,9 +554,18 @@ function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
534
554
  const list = el("div", "lb-fixed__products");
535
555
  const rowHandles = [];
536
556
  rows.forEach((rowState) => {
537
- const handle = renderProductRow(rowState, currency, qtyFor, () => {
538
- updatePricing();
539
- });
557
+ const handle = renderProductRow(
558
+ rowState,
559
+ currency,
560
+ qtyFor,
561
+ {
562
+ enabled: wc.showLowStockBadge,
563
+ threshold: wc.lowStockThreshold
564
+ },
565
+ () => {
566
+ updatePricing();
567
+ }
568
+ );
540
569
  rowHandles.push(handle);
541
570
  list.appendChild(handle.el);
542
571
  });
@@ -592,7 +621,9 @@ function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
592
621
  function buildRowState(bundle, product, productIndex) {
593
622
  const selectedVariantIds = bundle.selectedVariantIds?.[productIndex] ?? null;
594
623
  const merchantScoped = selectedVariantIds && selectedVariantIds.length > 0 ? product.variants.nodes.filter((v) => selectedVariantIds.includes(v.id)) : product.variants.nodes;
595
- const firstInStock = merchantScoped.find((v) => v.availableForSale) ?? null;
624
+ const firstInStock = merchantScoped.find(
625
+ (v) => (0, import_core5.isVariantFulfillable)(v, (0, import_core5.resolveBundleQty)(bundle, product.id, v.id))
626
+ ) ?? null;
596
627
  const eligibleVariants = merchantScoped;
597
628
  const isOos = !firstInStock;
598
629
  const selected = firstInStock ?? merchantScoped[0] ?? null;
@@ -656,7 +687,7 @@ function deriveHeaderBadge(bundle, totalCents, saleCents, currency) {
656
687
  }
657
688
  return `-${(0, import_core2.formatCents)(savings, currency)}`;
658
689
  }
659
- function renderProductRow(state, currency, qtyFor, onVariantChange) {
690
+ function renderProductRow(state, currency, qtyFor, lowStock, onVariantChange) {
660
691
  const rowEl = el(
661
692
  "div",
662
693
  state.isOos ? "lb-bundle-product-row lb-bundle-product-row--oos" : "lb-bundle-product-row",
@@ -703,6 +734,12 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
703
734
  oosLabel.textContent = "Out of stock";
704
735
  info.appendChild(oosLabel);
705
736
  } else if (state.selected) {
737
+ const isSinglePinnedVariant = state.eligibleVariants.length === 1 && state.product.variants.nodes.length > 1;
738
+ if (isSinglePinnedVariant) {
739
+ const badge = el("span", "lb-bundle-variant-badge");
740
+ badge.textContent = state.eligibleVariants[0].title;
741
+ info.appendChild(badge);
742
+ }
706
743
  const prices = el("span", "lb-bundle-product-prices");
707
744
  const compare = el("span", "lb-bundle-product-compare-price", {
708
745
  "data-product-compare-price": ""
@@ -718,6 +755,11 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
718
755
  });
719
756
  unitPriceEl.setAttribute("hidden", "");
720
757
  info.appendChild(unitPriceEl);
758
+ const lowStockEl = el("span", "lb-bundle-low-stock-badge", {
759
+ "data-low-stock-badge": ""
760
+ });
761
+ lowStockEl.setAttribute("hidden", "");
762
+ info.appendChild(lowStockEl);
721
763
  const applyVariantToRow = (variant) => {
722
764
  const unit = (0, import_core2.parseCents)(variant.price.amount);
723
765
  priceEl.textContent = (0, import_core2.formatCents)(unit, currency);
@@ -752,6 +794,18 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
752
794
  });
753
795
  thumbImg.alt = nextImage.altText ?? state.product.title;
754
796
  }
797
+ const required = qtyFor(state.product.id, variant.id);
798
+ if ((0, import_core5.shouldShowLowStockBadge)(
799
+ variant,
800
+ required,
801
+ lowStock.threshold,
802
+ lowStock.enabled
803
+ )) {
804
+ lowStockEl.textContent = `Only ${variant.quantityAvailable} left`;
805
+ lowStockEl.removeAttribute("hidden");
806
+ } else {
807
+ lowStockEl.setAttribute("hidden", "");
808
+ }
755
809
  };
756
810
  applyVariantToRow(state.selected);
757
811
  if (state.eligibleVariants.length > 1) {
@@ -770,7 +824,7 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
770
824
  });
771
825
  };
772
826
  const isValueAvailable = (optionIndex, value, selected) => state.eligibleVariants.some((v) => {
773
- if (!v.availableForSale) return false;
827
+ if (!(0, import_core5.isVariantFulfillable)(v, qtyFor(state.product.id, v.id))) return false;
774
828
  if (v.selectedOptions[optionIndex]?.value !== value) return false;
775
829
  return v.selectedOptions.every(
776
830
  (o, i) => i === optionIndex || o.value === selected[i]
@@ -834,10 +888,6 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
834
888
  if (state.selected) {
835
889
  recomputeDisabled(state.selected.selectedOptions.map((o) => o.value));
836
890
  }
837
- } else if (state.eligibleVariants.length === 1 && state.product.variants.nodes.length > 1) {
838
- const badge = el("span", "lb-bundle-variant-badge");
839
- badge.textContent = state.eligibleVariants[0].title;
840
- info.appendChild(badge);
841
891
  }
842
892
  }
843
893
  rowEl.appendChild(info);
@@ -943,18 +993,36 @@ var SEARCH_CLEAR_ICON_SVG = `
943
993
  <svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
944
994
  <path d="M14.348 5.652a.5.5 0 0 0-.707 0L10 9.293 6.36 5.652a.5.5 0 1 0-.708.707L9.293 10l-3.641 3.641a.5.5 0 0 0 .708.707L10 10.707l3.641 3.641a.5.5 0 0 0 .707-.707L10.707 10l3.641-3.641a.5.5 0 0 0 0-.707z"/>
945
995
  </svg>`;
996
+ var STEPPER_MINUS_SVG = `
997
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
998
+ <line x1="3" y1="7" x2="11" y2="7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
999
+ </svg>`;
1000
+ var STEPPER_PLUS_SVG = `
1001
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
1002
+ <line x1="7" y1="3" x2="7" y2="11" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
1003
+ <line x1="3" y1="7" x2="11" y2="7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
1004
+ </svg>`;
1005
+ function ruleFor(bundle, productId) {
1006
+ return bundle.productRules[productId] ?? import_core6.DEFAULT_PRODUCT_RULE;
1007
+ }
1008
+ function alreadyInBundleFor(selections, productId, variantId) {
1009
+ let sum = 0;
1010
+ for (const s of selections) {
1011
+ if (s.productId === productId && s.variantId === variantId) sum += s.quantity;
1012
+ }
1013
+ return sum;
1014
+ }
946
1015
  function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
947
1016
  const wc = bundle.widgetConfig;
948
1017
  const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
949
1018
  const requiredQty = bundle.minQuantity ?? 1;
950
- const maxQty = bundle.maxQuantity ?? requiredQty;
1019
+ const showQtySelector = wc.mixMatchShowQuantitySelector !== false;
951
1020
  const eligible = buildEligibleProducts(bundle, wc.outOfStockBehavior);
952
1021
  const inStockCount = eligible.filter((e) => !e.isOos).length;
953
1022
  if (inStockCount < requiredQty) return;
954
1023
  const selections = [];
955
1024
  const root = el("div", "lb-mix-match", {
956
- "data-required-quantity": String(requiredQty),
957
- "data-max-quantity": String(maxQty)
1025
+ "data-required-quantity": String(requiredQty)
958
1026
  });
959
1027
  const header = renderHeader2(bundle);
960
1028
  root.appendChild(header);
@@ -988,24 +1056,17 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
988
1056
  root.appendChild(placeholder);
989
1057
  const modal = renderModal(bundle, eligible, currency, {
990
1058
  showSearch: wc.showSearch,
991
- onAdd: (product, variant) => addSelection(product, variant),
992
- onRemove: (productId, variantId) => removeSelection(productId, variantId),
993
- isOverMax: () => selections.length >= maxQty
1059
+ showQtySelector,
1060
+ selections,
1061
+ onAdd: (product, variant, quantity) => addSelection(product, variant, quantity),
1062
+ isComplete: () => selections.length >= requiredQty
994
1063
  });
995
1064
  root.appendChild(modal.el);
996
1065
  const cta = buildCtaButton(`Select ${requiredQty} items to unlock`);
997
1066
  cta.disabled = true;
998
1067
  cta.addEventListener("click", () => {
999
1068
  if (cta.disabled) return;
1000
- const lines = selections.map((s) => ({
1001
- merchandiseId: s.variantId,
1002
- quantity: s.quantity,
1003
- attributes: [
1004
- { key: "_lime_bundle_gid", value: bundle.id },
1005
- { key: "_lime_bundle_type", value: bundle.bundleType }
1006
- ]
1007
- }));
1008
- onAddToCart(lines);
1069
+ onAddToCart(buildCartLines(selections, bundle));
1009
1070
  });
1010
1071
  root.appendChild(cta);
1011
1072
  root.appendChild(
@@ -1019,28 +1080,17 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
1019
1080
  );
1020
1081
  container.appendChild(root);
1021
1082
  onCleanup?.(() => unbindAllDropdowns(root));
1022
- const firstEligible = eligible.find((ep) => !ep.isOos);
1023
- const firstVariant = firstEligible?.firstAvailableVariant ?? firstEligible?.variants[0];
1024
- if (firstEligible && firstVariant) {
1025
- selections.push({
1026
- productId: firstEligible.product.id,
1027
- productTitle: firstEligible.product.title,
1028
- variantId: firstVariant.id,
1029
- variantTitle: firstVariant.title,
1030
- imageUrl: firstVariant.image?.url ?? firstEligible.product.featuredImage?.url ?? null,
1031
- priceCents: (0, import_core2.parseCents)(firstVariant.price.amount),
1032
- compareCents: firstVariant.compareAtPrice ? (0, import_core2.parseCents)(firstVariant.compareAtPrice.amount) : null,
1033
- unitPriceLabel: (0, import_core2.formatUnitPrice)(
1034
- firstVariant.unitPrice,
1035
- firstVariant.unitPriceMeasurement,
1036
- currency
1037
- ),
1038
- quantity: (0, import_core6.resolveBundleQty)(bundle, firstEligible.product.id, firstVariant.id)
1039
- });
1040
- }
1041
1083
  afterMutation();
1042
- function addSelection(product, variant) {
1043
- if (selections.length >= maxQty) return;
1084
+ function addSelection(product, variant, quantity) {
1085
+ if (selections.length >= requiredQty) return;
1086
+ const rule = ruleFor(bundle, product.id);
1087
+ const cap = (0, import_core6.maxAddableQuantity)(
1088
+ variant,
1089
+ rule.max,
1090
+ alreadyInBundleFor(selections, product.id, variant.id)
1091
+ );
1092
+ if (cap < rule.min) return;
1093
+ const clamped = Math.max(rule.min, Math.min(quantity, cap));
1044
1094
  selections.push({
1045
1095
  productId: product.id,
1046
1096
  productTitle: product.title,
@@ -1054,18 +1104,10 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
1054
1104
  variant.unitPriceMeasurement,
1055
1105
  currency
1056
1106
  ),
1057
- quantity: (0, import_core6.resolveBundleQty)(bundle, product.id, variant.id)
1107
+ quantity: clamped
1058
1108
  });
1059
1109
  afterMutation();
1060
1110
  }
1061
- function removeSelection(productId, variantId) {
1062
- const idx = selections.findIndex(
1063
- (s) => s.productId === productId && s.variantId === variantId
1064
- );
1065
- if (idx === -1) return;
1066
- selections.splice(idx, 1);
1067
- afterMutation();
1068
- }
1069
1111
  function removeSlotAt(index) {
1070
1112
  if (index < 0 || index >= selections.length) return;
1071
1113
  selections.splice(index, 1);
@@ -1107,6 +1149,26 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
1107
1149
  }
1108
1150
  }
1109
1151
  }
1152
+ function buildCartLines(selections, bundle) {
1153
+ const grouped = /* @__PURE__ */ new Map();
1154
+ for (const s of selections) {
1155
+ const key = `${s.productId}::${s.variantId}`;
1156
+ const existing = grouped.get(key);
1157
+ if (existing) {
1158
+ existing.quantity += s.quantity;
1159
+ } else {
1160
+ grouped.set(key, { variantId: s.variantId, quantity: s.quantity });
1161
+ }
1162
+ }
1163
+ return Array.from(grouped.values()).map((line) => ({
1164
+ merchandiseId: line.variantId,
1165
+ quantity: line.quantity,
1166
+ attributes: [
1167
+ { key: "_lime_bundle_gid", value: bundle.id },
1168
+ { key: "_lime_bundle_type", value: bundle.bundleType }
1169
+ ]
1170
+ }));
1171
+ }
1110
1172
  function renderHeader2(bundle) {
1111
1173
  const wc = bundle.widgetConfig;
1112
1174
  const header = el("div", "lb-bundle-header");
@@ -1222,7 +1284,7 @@ function renderFilledSlot(selection, index, currency, onRemove) {
1222
1284
  thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
1223
1285
  }
1224
1286
  const qtyBadge = el("span", "lb-bundle-qty-badge");
1225
- qtyBadge.textContent = String(selection.quantity);
1287
+ qtyBadge.textContent = `\xD7${selection.quantity}`;
1226
1288
  thumb.appendChild(qtyBadge);
1227
1289
  slot.appendChild(thumb);
1228
1290
  const info = el("div", "lb-mix-match__filled-info");
@@ -1411,8 +1473,9 @@ function renderModal(bundle, eligible, currency, handlers) {
1411
1473
  rowsBuilt = true;
1412
1474
  list.innerHTML = "";
1413
1475
  eligible.forEach((ep) => {
1476
+ const rule = ruleFor(bundle, ep.product.id);
1414
1477
  const availableVariants = ep.variants;
1415
- const firstAvailVariant = ep.variants.find((v) => v.availableForSale) ?? ep.firstAvailableVariant ?? ep.variants[0];
1478
+ const firstAvailVariant = ep.variants.find((v) => (0, import_core6.isVariantFulfillable)(v, rule.min)) ?? ep.firstAvailableVariant ?? ep.variants[0];
1416
1479
  if (!firstAvailVariant) return;
1417
1480
  let currentVariant = firstAvailVariant;
1418
1481
  const productEl = el(
@@ -1421,7 +1484,7 @@ function renderModal(bundle, eligible, currency, handlers) {
1421
1484
  { "data-product-id": ep.product.id.replace(/^.*\//, "") }
1422
1485
  );
1423
1486
  const thumb = el("div", "lb-mix-match__modal-product-thumb");
1424
- const initialThumbVariant = ep.variants.find((v) => v.availableForSale) ?? ep.variants[0] ?? null;
1487
+ const initialThumbVariant = ep.variants.find((v) => (0, import_core6.isVariantFulfillable)(v, rule.min)) ?? ep.variants[0] ?? null;
1425
1488
  const initialThumbImage = initialThumbVariant?.image ?? ep.product.featuredImage ?? null;
1426
1489
  let thumbImg = null;
1427
1490
  if (initialThumbImage) {
@@ -1440,9 +1503,7 @@ function renderModal(bundle, eligible, currency, handlers) {
1440
1503
  thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
1441
1504
  }
1442
1505
  const countBadge = el("span", "lb-bundle-qty-badge");
1443
- countBadge.textContent = String(
1444
- (0, import_core6.resolveBundleQty)(bundle, ep.product.id, currentVariant.id)
1445
- );
1506
+ countBadge.hidden = true;
1446
1507
  thumb.appendChild(countBadge);
1447
1508
  productEl.appendChild(thumb);
1448
1509
  const info = el("div", "lb-mix-match__modal-product-info");
@@ -1450,10 +1511,7 @@ function renderModal(bundle, eligible, currency, handlers) {
1450
1511
  title.textContent = ep.product.title;
1451
1512
  info.appendChild(title);
1452
1513
  const price = el("p", "lb-mix-match__modal-product-price");
1453
- price.textContent = (0, import_core2.formatCents)(
1454
- (0, import_core2.parseCents)(currentVariant.price.amount) * (0, import_core6.resolveBundleQty)(bundle, ep.product.id, currentVariant.id),
1455
- currency
1456
- );
1514
+ price.textContent = (0, import_core2.formatCents)((0, import_core2.parseCents)(currentVariant.price.amount), currency);
1457
1515
  info.appendChild(price);
1458
1516
  const unitPrice = el(
1459
1517
  "p",
@@ -1470,11 +1528,59 @@ function renderModal(bundle, eligible, currency, handlers) {
1470
1528
  unitPrice.hidden = true;
1471
1529
  }
1472
1530
  info.appendChild(unitPrice);
1531
+ const lowStockBadge = el("span", "lb-bundle-low-stock-badge", {
1532
+ "data-low-stock-badge": ""
1533
+ });
1534
+ lowStockBadge.hidden = true;
1535
+ info.appendChild(lowStockBadge);
1536
+ const refreshLowStockBadge = (variant) => {
1537
+ if ((0, import_core6.shouldShowLowStockBadge)(
1538
+ variant,
1539
+ rule.min,
1540
+ bundle.widgetConfig.lowStockThreshold,
1541
+ bundle.widgetConfig.showLowStockBadge
1542
+ )) {
1543
+ lowStockBadge.textContent = `Only ${variant.quantityAvailable} left`;
1544
+ lowStockBadge.hidden = false;
1545
+ } else {
1546
+ lowStockBadge.hidden = true;
1547
+ }
1548
+ };
1549
+ refreshLowStockBadge(currentVariant);
1550
+ const computeStepperBounds = () => {
1551
+ const already = alreadyInBundleFor(
1552
+ handlers.selections,
1553
+ ep.product.id,
1554
+ currentVariant.id
1555
+ );
1556
+ const cap = (0, import_core6.maxAddableQuantity)(currentVariant, rule.max, already);
1557
+ const max = Math.max(rule.min, cap);
1558
+ return { min: rule.min, max, cap };
1559
+ };
1560
+ let stepper = null;
1561
+ if (!ep.isOos && handlers.showQtySelector) {
1562
+ const qtyGroup = el(
1563
+ "div",
1564
+ "lb-bundle-variant-option-group lb-mix-match__qty-stepper-group"
1565
+ );
1566
+ const qtyLabel = el("span", "lb-bundle-variant-option-label");
1567
+ qtyLabel.textContent = "Quantity";
1568
+ qtyGroup.appendChild(qtyLabel);
1569
+ stepper = renderQtyStepper({
1570
+ initial: rule.min,
1571
+ getBounds: () => {
1572
+ const { min, max } = computeStepperBounds();
1573
+ return { min, max };
1574
+ }
1575
+ });
1576
+ qtyGroup.appendChild(stepper.el);
1577
+ info.appendChild(qtyGroup);
1578
+ }
1473
1579
  const row = {
1474
1580
  el: productEl,
1475
1581
  product: ep.product,
1476
1582
  variant: firstAvailVariant,
1477
- updateCount: () => {
1583
+ refreshFromSelections: () => {
1478
1584
  }
1479
1585
  };
1480
1586
  if (availableVariants.length > 1) {
@@ -1493,7 +1599,7 @@ function renderModal(bundle, eligible, currency, handlers) {
1493
1599
  });
1494
1600
  };
1495
1601
  const isValueAvailable = (optionIndex, value, selected) => availableVariants.some((v) => {
1496
- if (!v.availableForSale) return false;
1602
+ if (!(0, import_core6.isVariantFulfillable)(v, rule.min)) return false;
1497
1603
  if (v.selectedOptions[optionIndex]?.value !== value) return false;
1498
1604
  return v.selectedOptions.every(
1499
1605
  (o, i) => i === optionIndex || o.value === selected[i]
@@ -1518,15 +1624,7 @@ function renderModal(bundle, eligible, currency, handlers) {
1518
1624
  }
1519
1625
  currentVariant = next;
1520
1626
  row.variant = next;
1521
- const nextQty = (0, import_core6.resolveBundleQty)(
1522
- bundle,
1523
- ep.product.id,
1524
- currentVariant.id
1525
- );
1526
- price.textContent = (0, import_core2.formatCents)(
1527
- (0, import_core2.parseCents)(currentVariant.price.amount) * nextQty,
1528
- currency
1529
- );
1627
+ price.textContent = (0, import_core2.formatCents)((0, import_core2.parseCents)(currentVariant.price.amount), currency);
1530
1628
  const nextUnitText = (0, import_core2.formatUnitPrice)(
1531
1629
  currentVariant.unitPrice,
1532
1630
  currentVariant.unitPriceMeasurement,
@@ -1549,7 +1647,8 @@ function renderModal(bundle, eligible, currency, handlers) {
1549
1647
  thumbImg.alt = nextImage.altText ?? ep.product.title;
1550
1648
  }
1551
1649
  recomputeDisabled(next.selectedOptions.map((o) => o.value));
1552
- rowUpdateCount();
1650
+ refreshLowStockBadge(currentVariant);
1651
+ row.refreshFromSelections();
1553
1652
  };
1554
1653
  const groupsContainer = el("div", "lb-bundle-variant-option-groups");
1555
1654
  optionNames.forEach((name, position) => {
@@ -1596,25 +1695,45 @@ function renderModal(bundle, eligible, currency, handlers) {
1596
1695
  info.appendChild(soldOut);
1597
1696
  }
1598
1697
  productEl.appendChild(info);
1599
- const rowUpdateCount = () => {
1600
- countBadge.textContent = String(
1601
- (0, import_core6.resolveBundleQty)(bundle, ep.product.id, currentVariant.id)
1698
+ let addBtn = null;
1699
+ const refreshAddState = () => {
1700
+ if (!addBtn) return;
1701
+ const { cap } = computeStepperBounds();
1702
+ const already = alreadyInBundleFor(
1703
+ handlers.selections,
1704
+ ep.product.id,
1705
+ currentVariant.id
1602
1706
  );
1707
+ if (already > 0) {
1708
+ countBadge.textContent = String(already);
1709
+ countBadge.hidden = false;
1710
+ } else {
1711
+ countBadge.hidden = true;
1712
+ }
1713
+ addBtn.disabled = handlers.isComplete() || cap < rule.min;
1603
1714
  };
1604
1715
  if (!ep.isOos) {
1605
- const addBtn = document.createElement("button");
1716
+ const actions = el("div", "lb-mix-match__modal-product-actions");
1717
+ addBtn = document.createElement("button");
1606
1718
  addBtn.type = "button";
1607
1719
  addBtn.className = "lb-mix-match__modal-add";
1608
1720
  addBtn.textContent = "Add";
1609
1721
  addBtn.addEventListener("click", () => {
1610
- if (handlers.isOverMax()) return;
1611
- handlers.onAdd(ep.product, currentVariant);
1722
+ if (!addBtn || addBtn.disabled) return;
1723
+ if (handlers.isComplete()) return;
1724
+ const qty = stepper ? stepper.value() : rule.min;
1725
+ handlers.onAdd(ep.product, currentVariant, qty);
1726
+ if (stepper) stepper.reset(rule.min);
1612
1727
  close();
1613
1728
  });
1614
- productEl.appendChild(addBtn);
1729
+ actions.appendChild(addBtn);
1730
+ productEl.appendChild(actions);
1615
1731
  }
1616
- row.updateCount = ep.isOos ? () => {
1617
- } : rowUpdateCount;
1732
+ row.refreshFromSelections = () => {
1733
+ if (stepper) stepper.refresh();
1734
+ refreshAddState();
1735
+ };
1736
+ row.refreshFromSelections();
1618
1737
  productRows.push(row);
1619
1738
  list.appendChild(productEl);
1620
1739
  });
@@ -1649,7 +1768,7 @@ function renderModal(bundle, eligible, currency, handlers) {
1649
1768
  let isOpen = false;
1650
1769
  function open() {
1651
1770
  if (isOpen) return;
1652
- if (handlers.isOverMax()) return;
1771
+ if (handlers.isComplete()) return;
1653
1772
  isOpen = true;
1654
1773
  buildRows();
1655
1774
  lastFocused = overlay.getRootNode().activeElement;
@@ -1674,17 +1793,73 @@ function renderModal(bundle, eligible, currency, handlers) {
1674
1793
  if (e.target === overlay) close();
1675
1794
  }
1676
1795
  function refreshCounts() {
1677
- productRows.forEach((r) => r.updateCount());
1796
+ productRows.forEach((r) => r.refreshFromSelections());
1678
1797
  }
1679
1798
  return { el: overlay, open, close, refreshCounts };
1680
1799
  }
1800
+ function renderQtyStepper(opts) {
1801
+ const wrap = el("div", "lb-mix-match__qty-stepper", {
1802
+ role: "group",
1803
+ "aria-label": "Quantity"
1804
+ });
1805
+ const minus = document.createElement("button");
1806
+ minus.type = "button";
1807
+ minus.className = "lb-mix-match__qty-stepper-button lb-mix-match__qty-stepper-button--minus";
1808
+ minus.setAttribute("aria-label", "Decrease quantity");
1809
+ minus.innerHTML = STEPPER_MINUS_SVG;
1810
+ wrap.appendChild(minus);
1811
+ const valueEl = el("span", "lb-mix-match__qty-stepper-value", {
1812
+ "aria-live": "polite"
1813
+ });
1814
+ wrap.appendChild(valueEl);
1815
+ const plus = document.createElement("button");
1816
+ plus.type = "button";
1817
+ plus.className = "lb-mix-match__qty-stepper-button lb-mix-match__qty-stepper-button--plus";
1818
+ plus.setAttribute("aria-label", "Increase quantity");
1819
+ plus.innerHTML = STEPPER_PLUS_SVG;
1820
+ wrap.appendChild(plus);
1821
+ let current = clamp2(opts.initial, opts.getBounds());
1822
+ function clamp2(n, b) {
1823
+ return Math.max(b.min, Math.min(b.max, n));
1824
+ }
1825
+ function paint() {
1826
+ const b = opts.getBounds();
1827
+ current = clamp2(current, b);
1828
+ valueEl.textContent = String(current);
1829
+ minus.disabled = current <= b.min;
1830
+ plus.disabled = current >= b.max;
1831
+ }
1832
+ minus.addEventListener("click", () => {
1833
+ const b = opts.getBounds();
1834
+ current = clamp2(current - 1, b);
1835
+ paint();
1836
+ });
1837
+ plus.addEventListener("click", () => {
1838
+ const b = opts.getBounds();
1839
+ current = clamp2(current + 1, b);
1840
+ paint();
1841
+ });
1842
+ paint();
1843
+ return {
1844
+ el: wrap,
1845
+ value: () => current,
1846
+ reset(qty) {
1847
+ current = clamp2(qty, opts.getBounds());
1848
+ paint();
1849
+ },
1850
+ refresh: paint
1851
+ };
1852
+ }
1681
1853
  function buildEligibleProducts(bundle, oosBehavior) {
1682
1854
  const result = [];
1683
1855
  const seen = /* @__PURE__ */ new Set();
1684
1856
  for (const product of bundle.products) {
1685
1857
  if (seen.has(product.id)) continue;
1686
1858
  seen.add(product.id);
1687
- const available = product.variants.nodes.filter((v) => v.availableForSale);
1859
+ const rule = ruleFor(bundle, product.id);
1860
+ const available = product.variants.nodes.filter(
1861
+ (v) => (0, import_core6.isVariantFulfillable)(v, rule.min)
1862
+ );
1688
1863
  const isOos = available.length === 0;
1689
1864
  if (isOos && oosBehavior === "hide") continue;
1690
1865
  result.push({
@@ -1717,10 +1892,14 @@ function sanitizeId(gid) {
1717
1892
  }
1718
1893
 
1719
1894
  // src/renderers/volume.ts
1895
+ var import_core7 = require("@lime-bundles/core");
1720
1896
  function renderVolumeBundle(container, bundle, onAddToCart, onCleanup) {
1721
1897
  const wc = bundle.widgetConfig;
1722
1898
  const product = bundle.products[0];
1723
- const variant = product?.variants.nodes.find((v) => v.availableForSale);
1899
+ const minTierQty = bundle.volumeTiers[0]?.minQuantity ?? 1;
1900
+ const variant = product?.variants.nodes.find(
1901
+ (v) => (0, import_core7.isVariantFulfillable)(v, minTierQty)
1902
+ );
1724
1903
  if (!variant && wc.outOfStockBehavior === "hide") return;
1725
1904
  const basePriceCents = variant ? (0, import_core2.parseCents)(variant.price.amount) : 0;
1726
1905
  const currency = variant?.price.currencyCode ?? "USD";
@@ -1819,6 +1998,18 @@ function renderVolumeBundle(container, bundle, onAddToCart, onCleanup) {
1819
1998
  }
1820
1999
  ]);
1821
2000
  });
2001
+ if (variant && (0, import_core7.shouldShowLowStockBadge)(
2002
+ variant,
2003
+ minTierQty,
2004
+ wc.lowStockThreshold,
2005
+ wc.showLowStockBadge
2006
+ )) {
2007
+ const lowStock = el("span", "lb-bundle-low-stock-badge", {
2008
+ "data-low-stock-badge": ""
2009
+ });
2010
+ lowStock.textContent = `Only ${variant.quantityAvailable} left`;
2011
+ root.appendChild(lowStock);
2012
+ }
1822
2013
  root.appendChild(cta);
1823
2014
  root.appendChild(
1824
2015
  el("p", "lb-bundle-error", { "data-error": "", "aria-live": "polite" })
@@ -1972,7 +2163,10 @@ function renderSavingsBar3(resolved, selectedIndex, currency) {
1972
2163
  }
1973
2164
  function renderCta2(bundle, onClick) {
1974
2165
  const product = bundle.products[0];
1975
- const isAvailable = product?.variants.nodes.some((v) => v.availableForSale);
2166
+ const minTierQty = bundle.volumeTiers[0]?.minQuantity ?? 1;
2167
+ const isAvailable = product?.variants.nodes.some(
2168
+ (v) => (0, import_core7.isVariantFulfillable)(v, minTierQty)
2169
+ );
1976
2170
  const label = isAvailable ? bundle.widgetConfig.cta.ctaText || "Add to cart" : "Sold out";
1977
2171
  const button = buildCtaButton(label);
1978
2172
  if (!isAvailable) button.disabled = true;
@@ -2014,7 +2208,7 @@ function clamp(n, min, max) {
2014
2208
  }
2015
2209
 
2016
2210
  // src/lime-bundle.ts
2017
- var import_core8 = require("@lime-bundles/core");
2211
+ var import_core9 = require("@lime-bundles/core");
2018
2212
 
2019
2213
  // src/utils/input-mode.ts
2020
2214
  var NAV_KEYS = /* @__PURE__ */ new Set([
@@ -2349,15 +2543,19 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
2349
2543
  display: none;
2350
2544
  }
2351
2545
 
2352
- .lb-bundle-variant-badge {
2353
- display: inline-block;
2354
- border: var(--lb-variant-border-width) solid var(--lb-variant-border-color);
2355
- border-radius: var(--lb-variant-radius);
2356
- padding: 8px 12px;
2546
+ /* Read-only variant text shown below the product title when only one
2547
+ variant is in scope (single-variant product, or merchant pinned a
2548
+ single variant). Same visual treatment as the mix-match filled
2549
+ slot's variant text \u2014 see .lb-mix-match__filled-variant in
2550
+ bundle-mix-match.css. Both share this rule via the comma selector
2551
+ so the storefront UX stays consistent across bundle types. */
2552
+ .lb-bundle-variant-badge,
2553
+ .lb-mix-match__slot--filled .lb-mix-match__filled-variant {
2554
+ display: block;
2357
2555
  font-size: 12px;
2358
- line-height: 16px;
2556
+ line-height: 20px;
2557
+ margin-top: 2px;
2359
2558
  color: color-mix(in srgb, var(--lb-text) 60%, transparent);
2360
- margin-top: 8px;
2361
2559
  }
2362
2560
 
2363
2561
  /* Per-option variant pickers. Each option's label + select sit inside a
@@ -2631,6 +2829,24 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
2631
2829
  margin-left: auto;
2632
2830
  }
2633
2831
 
2832
+ /* "Only X left" low-stock badge \u2014 appears alongside product/variant
2833
+ info when ProductVariant.quantityAvailable falls at or below
2834
+ wc.lowStockThreshold. Hidden when quantityAvailable is unknown
2835
+ (Storefront token without read_product_inventory). */
2836
+ .lb-bundle-low-stock-badge {
2837
+ display: inline-flex;
2838
+ align-items: center;
2839
+ gap: 4px;
2840
+ padding: 2px 8px;
2841
+ font-size: 11px;
2842
+ font-weight: 600;
2843
+ line-height: 1.4;
2844
+ color: var(--lb-low-stock-text);
2845
+ background-color: var(--lb-low-stock-bg);
2846
+ border-radius: 4px;
2847
+ white-space: nowrap;
2848
+ }
2849
+
2634
2850
  /* A/B test: hide save badge until JS swaps the label (prevents flash of default) */
2635
2851
  .lb-ab-pending {
2636
2852
  visibility: hidden;
@@ -2854,12 +3070,9 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
2854
3070
  text-decoration: underline;
2855
3071
  }
2856
3072
 
2857
- .lb-mix-match__slot--filled .lb-mix-match__filled-variant {
2858
- font-size: 12px;
2859
- line-height: 20px;
2860
- margin-top: 2px;
2861
- color: color-mix(in srgb, var(--lb-text) 60%, transparent);
2862
- }
3073
+ /* .lb-mix-match__filled-variant \u2014 styled jointly with
3074
+ .lb-bundle-variant-badge above to keep the variant text consistent
3075
+ across mix-match and fixed bundle widgets. */
2863
3076
 
2864
3077
  .lb-mix-match__filled-price {
2865
3078
  /* --lb-product-price-display fallback is the "on" branch; Liquid sets 'none' when merchant disables. */
@@ -3067,8 +3280,8 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3067
3280
 
3068
3281
  .lb-mix-match__modal-product-thumb {
3069
3282
  position: relative;
3070
- width: 48px;
3071
- min-width: 48px;
3283
+ width: 60px;
3284
+ min-width: 60px;
3072
3285
  /* Modal picker thumbs follow the merchant's pickerThumbnailRatio \u2014
3073
3286
  independent from the main widget's thumbnailRatio so a merchant can
3074
3287
  e.g. show tall picker thumbs with square main thumbs. */
@@ -3117,7 +3330,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3117
3330
  font-size: 12px;
3118
3331
  line-height: 20px;
3119
3332
  color: inherit;
3120
- margin: 4px 0 0;
3333
+ margin: 2px 0 0;
3121
3334
  }
3122
3335
 
3123
3336
  .lb-mix-match__modal-product-unit-price {
@@ -3156,6 +3369,86 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3156
3369
  outline-offset: 2px;
3157
3370
  }
3158
3371
 
3372
+ /* Per-pick quantity stepper inside the picker modal product row.
3373
+ Three flex cells (\u2212 / number / +) sharing a single outer border \u2014
3374
+ the same picker-variant border vars the variant select uses, so
3375
+ the two pieces visually agree even when the merchant tweaks
3376
+ --lb-picker-variant-border-width / -color / -radius. The cell
3377
+ dividers come from a 1px border on the centre value rather than
3378
+ per-button borders, so the rounded outer corners stay clean.
3379
+ Mirrors extensions/bundle-theme/assets/bundle-mix-match.css so
3380
+ the headless web component matches the Liquid theme widget. */
3381
+ /* Quantity label + stepper sit together in a .lb-bundle-variant-option-group
3382
+ wrapper so the label-to-stepper gap inherits the same 2px the variant
3383
+ pickers use. The wrapper carries the margin-top that spaces the qty
3384
+ group from the unit-price / low-stock-badge above; the stepper itself
3385
+ has no top margin so the group can be repositioned without coupling. */
3386
+ .lb-mix-match__qty-stepper-group {
3387
+ margin-top: 8px;
3388
+ /* Group is a flex column; without an explicit width it stretches to
3389
+ fill the info column so the \u2212/value/+ cells get pulled apart. Pin
3390
+ to fit-content so the wrapper hugs the stepper's natural width. */
3391
+ width: fit-content;
3392
+ }
3393
+
3394
+ .lb-mix-match__qty-stepper {
3395
+ display: inline-flex;
3396
+ align-items: stretch;
3397
+ flex-shrink: 0;
3398
+ height: 32px;
3399
+ border: var(--lb-picker-variant-border-width) solid var(--lb-picker-variant-border-color);
3400
+ border-radius: var(--lb-picker-variant-radius);
3401
+ background-color: var(--lb-picker-bg);
3402
+ overflow: hidden;
3403
+ box-sizing: border-box;
3404
+ }
3405
+
3406
+ .lb-mix-match__qty-stepper-button {
3407
+ appearance: none;
3408
+ -webkit-appearance: none;
3409
+ background: transparent;
3410
+ border: none;
3411
+ margin: 0;
3412
+ padding: 0;
3413
+ width: 28px;
3414
+ font-family: inherit;
3415
+ font-size: 16px;
3416
+ font-weight: 500;
3417
+ line-height: 1;
3418
+ color: var(--lb-picker-text);
3419
+ cursor: pointer;
3420
+ display: inline-flex;
3421
+ align-items: center;
3422
+ justify-content: center;
3423
+ }
3424
+
3425
+ .lb-mix-match__qty-stepper-button:hover:not(:disabled) {
3426
+ background: color-mix(in srgb, var(--lb-picker-text) 6%, transparent);
3427
+ }
3428
+
3429
+ .lb-mix-match__qty-stepper-button:focus-visible {
3430
+ outline: 2px solid var(--lb-primary-color);
3431
+ outline-offset: -2px;
3432
+ }
3433
+
3434
+ .lb-mix-match__qty-stepper-button:disabled {
3435
+ opacity: 0.4;
3436
+ cursor: not-allowed;
3437
+ }
3438
+
3439
+ .lb-mix-match__qty-stepper-value {
3440
+ display: inline-flex;
3441
+ align-items: center;
3442
+ justify-content: center;
3443
+ min-width: 36px;
3444
+ padding: 0 8px;
3445
+ font-family: inherit;
3446
+ font-size: 13px;
3447
+ font-weight: 500;
3448
+ color: var(--lb-picker-text);
3449
+ box-sizing: border-box;
3450
+ }
3451
+
3159
3452
  .lb-mix-match__modal-add {
3160
3453
  padding: 8px 20px;
3161
3454
  background: var(--lb-picker-add-bg);
@@ -3834,7 +4127,7 @@ var LimeBundleElement = class extends HTMLElement {
3834
4127
  const controller = new AbortController();
3835
4128
  this.abortController = controller;
3836
4129
  this.renderLoading();
3837
- const client = (0, import_core7.createStorefrontClient)({
4130
+ const client = (0, import_core8.createStorefrontClient)({
3838
4131
  shopDomain: this.shopDomain,
3839
4132
  accessToken: this.storefrontToken
3840
4133
  });
@@ -3859,7 +4152,7 @@ var LimeBundleElement = class extends HTMLElement {
3859
4152
  handle
3860
4153
  );
3861
4154
  }
3862
- const cssPromise = client.query(import_core7.SHOP_CUSTOM_CSS_QUERY, void 0, {
4155
+ const cssPromise = client.query(import_core8.SHOP_CUSTOM_CSS_QUERY, void 0, {
3863
4156
  signal: controller.signal
3864
4157
  }).catch(() => null);
3865
4158
  await bundlePromise;
@@ -3871,8 +4164,8 @@ var LimeBundleElement = class extends HTMLElement {
3871
4164
  }
3872
4165
  const css = await cssPromise;
3873
4166
  if (css?.shop?.metafield?.value) {
3874
- (0, import_core7.injectCustomCss)(this.shopDomain, css.shop.metafield.value);
3875
- const sanitized = (0, import_core7.sanitizeCustomCss)(css.shop.metafield.value);
4167
+ (0, import_core8.injectCustomCss)(this.shopDomain, css.shop.metafield.value);
4168
+ const sanitized = (0, import_core8.sanitizeCustomCss)(css.shop.metafield.value);
3876
4169
  if (sanitized.ok) this.shopCustomCss = sanitized.css;
3877
4170
  }
3878
4171
  await this.applyABVariants();
@@ -3900,14 +4193,14 @@ var LimeBundleElement = class extends HTMLElement {
3900
4193
  this.bundles.map(async (bundle) => {
3901
4194
  if (!bundle.abTestId || !bundle.abVariantB) return bundle;
3902
4195
  try {
3903
- const assignment = await (0, import_core7.getABTestAssignment)(
4196
+ const assignment = await (0, import_core8.getABTestAssignment)(
3904
4197
  this.appUrl,
3905
4198
  this.shopDomain,
3906
4199
  bundle.abTestId,
3907
4200
  bundle.id
3908
4201
  );
3909
4202
  if (assignment?.variant === "B") {
3910
- return (0, import_core7.applyABVariantB)(bundle);
4203
+ return (0, import_core8.applyABVariantB)(bundle);
3911
4204
  }
3912
4205
  } catch (err) {
3913
4206
  console.warn(
@@ -3922,7 +4215,7 @@ var LimeBundleElement = class extends HTMLElement {
3922
4215
  }
3923
4216
  async fetchSingleBundle(client, signal) {
3924
4217
  const data = await client.query(
3925
- import_core7.BUNDLE_METAOBJECT_QUERY,
4218
+ import_core8.BUNDLE_METAOBJECT_QUERY,
3926
4219
  { id: this.bundleGid },
3927
4220
  { signal }
3928
4221
  );
@@ -3930,7 +4223,7 @@ var LimeBundleElement = class extends HTMLElement {
3930
4223
  this.bundles = [];
3931
4224
  return;
3932
4225
  }
3933
- const parsed = (0, import_core7.parseMetaobjectBundle)(
4226
+ const parsed = (0, import_core8.parseMetaobjectBundle)(
3934
4227
  data.metaobject.id,
3935
4228
  data.metaobject.fields
3936
4229
  );
@@ -3938,7 +4231,7 @@ var LimeBundleElement = class extends HTMLElement {
3938
4231
  }
3939
4232
  async fetchProductBundles(client, signal, productHandle) {
3940
4233
  const data = await client.query(
3941
- import_core7.BUNDLES_FOR_PRODUCT_QUERY,
4234
+ import_core8.BUNDLES_FOR_PRODUCT_QUERY,
3942
4235
  { handle: productHandle },
3943
4236
  { signal }
3944
4237
  );
@@ -3949,7 +4242,7 @@ var LimeBundleElement = class extends HTMLElement {
3949
4242
  const refs = data.product.metafield?.references?.nodes ?? [];
3950
4243
  const bundles = [];
3951
4244
  for (const ref of refs) {
3952
- const parsed = (0, import_core7.parseMetaobjectBundle)(ref.id, ref.fields);
4245
+ const parsed = (0, import_core8.parseMetaobjectBundle)(ref.id, ref.fields);
3953
4246
  if (parsed) bundles.push(parsed);
3954
4247
  }
3955
4248
  this.bundles = bundles;
@@ -3982,7 +4275,7 @@ var LimeBundleElement = class extends HTMLElement {
3982
4275
  */
3983
4276
  async defaultAddToCart(lines) {
3984
4277
  if (typeof window === "undefined") return;
3985
- const client = (0, import_core7.createStorefrontClient)({
4278
+ const client = (0, import_core8.createStorefrontClient)({
3986
4279
  shopDomain: this.shopDomain,
3987
4280
  accessToken: this.storefrontToken
3988
4281
  });
@@ -3993,7 +4286,7 @@ var LimeBundleElement = class extends HTMLElement {
3993
4286
  let checkoutUrl = null;
3994
4287
  if (existingCartId) {
3995
4288
  const res = await client.query(
3996
- import_core7.CART_LINES_ADD_MUTATION,
4289
+ import_core8.CART_LINES_ADD_MUTATION,
3997
4290
  { cartId: existingCartId, lines }
3998
4291
  );
3999
4292
  const payload = res.cartLinesAdd;
@@ -4005,7 +4298,7 @@ var LimeBundleElement = class extends HTMLElement {
4005
4298
  }
4006
4299
  if (!checkoutUrl) {
4007
4300
  const res = await client.query(
4008
- import_core7.CART_CREATE_MUTATION,
4301
+ import_core8.CART_CREATE_MUTATION,
4009
4302
  { input: { lines } }
4010
4303
  );
4011
4304
  const payload = res.cartCreate;
@@ -4051,7 +4344,7 @@ var LimeBundleElement = class extends HTMLElement {
4051
4344
  const price = variant ? parseFloat(variant.price.amount) : 0;
4052
4345
  return sum + price * line.quantity;
4053
4346
  }, 0);
4054
- (0, import_core7.reportAddToCart)(
4347
+ (0, import_core8.reportAddToCart)(
4055
4348
  { shopDomain: this.shopDomain, appUrl: this.appUrl },
4056
4349
  {
4057
4350
  bundleGid: bundle.id,
@@ -4088,7 +4381,7 @@ var LimeBundleElement = class extends HTMLElement {
4088
4381
  container.setAttribute("aria-label", bundle.title);
4089
4382
  container.setAttribute("data-bundle-type", bundle.bundleType);
4090
4383
  container.setAttribute("data-bundle-gid", bundle.id);
4091
- (0, import_core8.applyWidgetConfigVars)(container, bundle.widgetConfig);
4384
+ (0, import_core9.applyWidgetConfigVars)(container, bundle.widgetConfig);
4092
4385
  this.renderCleanups.push(trackInputMode(container));
4093
4386
  const dispatch = (lines) => this.handleAddToCart(bundle, lines);
4094
4387
  const registerCleanup = (fn) => this.renderCleanups.push(fn);
@@ -4124,8 +4417,8 @@ var LimeBundleElement = class extends HTMLElement {
4124
4417
  }
4125
4418
  setupImpressionFor(bundle, element) {
4126
4419
  if (!this.analyticsEnabled || !this.appUrl) return;
4127
- const cleanup = (0, import_core7.observeImpression)(element, () => {
4128
- (0, import_core7.reportImpression)(
4420
+ const cleanup = (0, import_core8.observeImpression)(element, () => {
4421
+ (0, import_core8.reportImpression)(
4129
4422
  { shopDomain: this.shopDomain, appUrl: this.appUrl },
4130
4423
  {
4131
4424
  bundleGid: bundle.id,