@lime-bundles/widget 4.1.1 → 4.2.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.js CHANGED
@@ -15,8 +15,6 @@ import {
15
15
  hasInContext,
16
16
  withInContext,
17
17
  isVisibleInMarket,
18
- parseOutOfStockBehavior,
19
- DEFAULT_OUT_OF_STOCK_BEHAVIOR,
20
18
  getVisitorId,
21
19
  resolveAbTests
22
20
  } from "@lime-bundles/core";
@@ -25,7 +23,8 @@ import {
25
23
  import {
26
24
  formatUnitPrice,
27
25
  isVariantFulfillable,
28
- resolveBundleQty
26
+ resolveBundleQty,
27
+ stockCapForVariant
29
28
  } from "@lime-bundles/core";
30
29
  function numericId(gid) {
31
30
  const tail = gid.slice(gid.lastIndexOf("/") + 1);
@@ -49,7 +48,10 @@ function adaptVariant(v, requiredQty, fallbackCurrency) {
49
48
  price: toCents(v.price?.amount),
50
49
  compareAtPrice: toCents(v.compareAtPrice?.amount),
51
50
  unitPrice: formatUnitPrice(v.unitPrice, v.unitPriceMeasurement, fallbackCurrency),
52
- image: v.image?.url ?? null
51
+ image: v.image?.url ?? null,
52
+ // Carried so the picker's stepper cap and cross-slot stock guard can
53
+ // bind on the headless surface; dropping it here left them capless.
54
+ inventoryQuantity: stockCapForVariant(v)
53
55
  };
54
56
  }
55
57
  function adaptProduct(product, bundleId, opts) {
@@ -104,6 +106,14 @@ function findVariant(product, variantId) {
104
106
  }
105
107
  return product.variants[0];
106
108
  }
109
+ function resolveSellableVariant(product) {
110
+ const seeded = findVariant(product, product.selectedVariantId);
111
+ if (seeded && seeded.available !== false) return seeded;
112
+ for (let i = 0; i < product.variants.length; i++) {
113
+ if (product.variants[i].available !== false) return product.variants[i];
114
+ }
115
+ return null;
116
+ }
107
117
  function findVariantByOptions(product, optionValues) {
108
118
  if (!product.variants || !optionValues) return null;
109
119
  for (let i = 0; i < product.variants.length; i++) {
@@ -1174,12 +1184,11 @@ function bindDropdowns(root) {
1174
1184
  }
1175
1185
 
1176
1186
  // src/renderers/fixed.ts
1177
- function renderFixedBundle(container, bundle, outOfStockBehavior, onAddToCart, onCleanup) {
1187
+ function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
1178
1188
  const wc = bundle.widgetConfig;
1179
1189
  const products = adaptProducts(bundle);
1180
1190
  if (!products.length) return;
1181
1191
  const oosProducts = products.filter((p) => !p.variants.some((v) => v.available));
1182
- if (outOfStockBehavior === "hide" && oosProducts.length > 0) return;
1183
1192
  const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
1184
1193
  const formatMoney = intlFormatMoney(currency);
1185
1194
  const t = DEFAULT_STRINGS;
@@ -1204,7 +1213,9 @@ function renderFixedBundle(container, bundle, outOfStockBehavior, onAddToCart, o
1204
1213
  });
1205
1214
  shell.products.appendChild(row);
1206
1215
  if (isOos) continue;
1207
- const selected = findVariant(product, product.selectedVariantId);
1216
+ const sellable = resolveSellableVariant(product);
1217
+ if (sellable) product.selectedVariantId = sellable.id;
1218
+ const selected = sellable ?? findVariant(product, product.selectedVariantId);
1208
1219
  hydrateRowControls(row, product, selected);
1209
1220
  applyVariant(row, selected, product, formatMoney);
1210
1221
  bindVariantSelects(
@@ -1799,6 +1810,17 @@ function unlock() {
1799
1810
  window.scrollTo(0, savedY);
1800
1811
  }
1801
1812
 
1813
+ // ../render/src/picker/sort.ts
1814
+ function sinkAddedRows(list, rowEls, isAdded) {
1815
+ const front = [];
1816
+ const back = [];
1817
+ for (let i = 0; i < rowEls.length; i++) {
1818
+ (isAdded(i) ? back : front).push(rowEls[i]);
1819
+ }
1820
+ for (const el2 of front) list.appendChild(el2);
1821
+ for (const el2 of back) list.appendChild(el2);
1822
+ }
1823
+
1802
1824
  // ../render/src/mix-match/modal.ts
1803
1825
  var ALL_TYPES = "__all__";
1804
1826
  var CLOSE_TRANSITION_MS = 300;
@@ -2023,12 +2045,22 @@ function createPickerModal(deps) {
2023
2045
  syncActiveFilterPill();
2024
2046
  if (searchInput) searchInput.value = "";
2025
2047
  filterProducts(searchInput ? searchInput.value : "");
2048
+ if (modalList) {
2049
+ const selectedProductIds = /* @__PURE__ */ Object.create(null);
2050
+ for (const it of items) selectedProductIds[String(it.productId)] = true;
2051
+ sinkAddedRows(
2052
+ modalList,
2053
+ rows.map((r) => r.el),
2054
+ (i) => selectedProductIds[String(eligibleProducts[i].id)] === true
2055
+ );
2056
+ modalList.scrollTop = 0;
2057
+ }
2026
2058
  overlay.style.display = "";
2027
2059
  void overlay.offsetHeight;
2028
2060
  overlay.classList.add("lb-mix-match__modal-overlay--open");
2029
2061
  lock();
2030
2062
  setTimeout(() => {
2031
- if (searchInput) searchInput.focus();
2063
+ if (closeBtn) closeBtn.focus();
2032
2064
  else modalDialog?.focus();
2033
2065
  }, 50);
2034
2066
  }
@@ -2585,7 +2617,7 @@ function buildPickerModal(opts) {
2585
2617
  }
2586
2618
 
2587
2619
  // src/renderers/mix-match.ts
2588
- function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart, onCleanup, currentProductHandle) {
2620
+ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup, currentProductHandle) {
2589
2621
  const wc = bundle.widgetConfig;
2590
2622
  const t = DEFAULT_STRINGS;
2591
2623
  const requiredQty = bundle.minQuantity || 1;
@@ -2608,12 +2640,12 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
2608
2640
  compareAtPrice: v.compareAtPrice,
2609
2641
  unitPrice: v.unitPrice,
2610
2642
  image: v.image,
2611
- inventoryQuantity: null
2643
+ inventoryQuantity: v.inventoryQuantity ?? null
2612
2644
  }))
2613
2645
  }));
2614
2646
  if (!eligibleProducts.length) return;
2615
2647
  const poolUnits = eligibleProducts.filter((p) => p.available).length;
2616
- if (outOfStockBehavior === "hide" && poolUnits === 0) return;
2648
+ if (poolUnits === 0) return;
2617
2649
  const formatMoney = intlFormatMoney(
2618
2650
  bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD"
2619
2651
  );
@@ -2776,10 +2808,50 @@ function renderMixMatchBundle(container, bundle, outOfStockBehavior, onAddToCart
2776
2808
  // src/renderers/volume.ts
2777
2809
  import {
2778
2810
  bestValueTierIndex,
2811
+ isVariantFulfillable as isVariantFulfillable2,
2779
2812
  resolveDefaultTierIndex,
2780
- resolvePopularTierIndex
2813
+ resolvePopularTierIndex,
2814
+ stockCapForVariant as stockCapForVariant2
2781
2815
  } from "@lime-bundles/core";
2782
2816
 
2817
+ // ../render/src/volume/stock.ts
2818
+ var TIER_OOS_CLASS = "lb-volume__tier--oos";
2819
+ function tierQty(el2) {
2820
+ return parseInt(el2.getAttribute("data-tier-qty") ?? "", 10) || 0;
2821
+ }
2822
+ function tierUnavailable(el2) {
2823
+ return el2.getAttribute("aria-disabled") === "true";
2824
+ }
2825
+ function applyTierStockState(tierEls, cap) {
2826
+ for (let i = 0; i < tierEls.length; i++) {
2827
+ const el2 = tierEls[i];
2828
+ const unavailable = cap !== null && tierQty(el2) > cap;
2829
+ if (unavailable) {
2830
+ el2.classList.add(TIER_OOS_CLASS);
2831
+ el2.setAttribute("aria-disabled", "true");
2832
+ } else {
2833
+ el2.classList.remove(TIER_OOS_CLASS);
2834
+ el2.removeAttribute("aria-disabled");
2835
+ }
2836
+ }
2837
+ }
2838
+ function resolveAvailableTierIndex(tierEls, preferred) {
2839
+ const p = tierEls[preferred];
2840
+ if (p && !tierUnavailable(p)) return preferred;
2841
+ for (let i = 0; i < tierEls.length; i++) {
2842
+ if (!tierUnavailable(tierEls[i])) return i;
2843
+ }
2844
+ return -1;
2845
+ }
2846
+ function stepToAvailableTier(tierEls, from, direction) {
2847
+ let i = from + direction;
2848
+ while (i >= 0 && i < tierEls.length) {
2849
+ if (!tierUnavailable(tierEls[i])) return i;
2850
+ i += direction;
2851
+ }
2852
+ return from;
2853
+ }
2854
+
2783
2855
  // ../render/src/volume/pricing.ts
2784
2856
  function calcTierPrice(basePrice, discountType, tierEl) {
2785
2857
  if (discountType === "fixed_amount") {
@@ -2869,15 +2941,16 @@ function selectTier(container, allTiers, basePrice, discountType, index, transla
2869
2941
  }
2870
2942
 
2871
2943
  // src/renderers/volume.ts
2872
- function renderVolumeBundle(container, bundle, outOfStockBehavior, onAddToCart) {
2944
+ function renderVolumeBundle(container, bundle, onAddToCart) {
2873
2945
  const wc = bundle.widgetConfig;
2874
2946
  const product = bundle.products[0];
2875
2947
  if (!product) return;
2876
2948
  const variants = product.variants.nodes;
2877
- const pricingVariant = variants.find((v) => v.availableForSale) ?? variants[0];
2949
+ const minTierQty = Math.min(
2950
+ ...bundle.volumeTiers.map((tier) => tier.minQuantity)
2951
+ );
2952
+ const pricingVariant = variants.find((v) => isVariantFulfillable2(v, minTierQty)) ?? variants.find((v) => v.availableForSale) ?? variants[0];
2878
2953
  if (!pricingVariant) return;
2879
- const anyAvailable = variants.some((v) => v.availableForSale);
2880
- if (outOfStockBehavior === "hide" && !anyAvailable) return;
2881
2954
  const basePrice = toCents(pricingVariant.price.amount);
2882
2955
  const discountType = bundle.discountConfig.discountType;
2883
2956
  const formatMoney = intlFormatMoney(product.priceRange.minVariantPrice.currencyCode);
@@ -2928,28 +3001,31 @@ function renderVolumeBundle(container, bundle, outOfStockBehavior, onAddToCart)
2928
3001
  shell.products.replaceWith(group);
2929
3002
  const tierEls = group.querySelectorAll("[data-tier-index]");
2930
3003
  updateAllTierPrices(tierEls, basePrice, discountType, formatMoney);
2931
- let selectedIndex = defaultIdx;
3004
+ applyTierStockState(tierEls, stockCapForVariant2(pricingVariant));
3005
+ let selectedIndex = resolveAvailableTierIndex(tierEls, defaultIdx);
3006
+ const noTierAvailable = selectedIndex === -1;
3007
+ if (noTierAvailable) selectedIndex = defaultIdx;
2932
3008
  const apply = () => selectTier(shell.root, tierEls, basePrice, discountType, selectedIndex, t, formatMoney);
2933
3009
  group.addEventListener("click", (e) => {
2934
3010
  const el2 = e.target.closest("[data-tier-index]");
2935
- if (!el2) return;
3011
+ if (!el2 || tierUnavailable(el2)) return;
2936
3012
  selectedIndex = parseInt(el2.getAttribute("data-tier-index") ?? "", 10) || 0;
2937
3013
  apply();
2938
3014
  });
2939
3015
  group.addEventListener("keydown", (e) => {
2940
3016
  if (e.key === "ArrowDown" || e.key === "ArrowRight") {
2941
3017
  e.preventDefault();
2942
- selectedIndex = Math.min(selectedIndex + 1, tierEls.length - 1);
3018
+ selectedIndex = stepToAvailableTier(tierEls, selectedIndex, 1);
2943
3019
  apply();
2944
3020
  tierEls[selectedIndex].focus();
2945
3021
  } else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
2946
3022
  e.preventDefault();
2947
- selectedIndex = Math.max(selectedIndex - 1, 0);
3023
+ selectedIndex = stepToAvailableTier(tierEls, selectedIndex, -1);
2948
3024
  apply();
2949
3025
  tierEls[selectedIndex].focus();
2950
3026
  }
2951
3027
  });
2952
- if (!anyAvailable) {
3028
+ if (noTierAvailable) {
2953
3029
  shell.cta.disabled = true;
2954
3030
  const label = shell.cta.querySelector("[data-cta-label]");
2955
3031
  if (label) label.textContent = t.outOfStock || "Out of stock";
@@ -3084,7 +3160,7 @@ function buildBogoPlus() {
3084
3160
  plus.innerHTML = '<svg width="12" height="12" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><line x1="9" y1="3" x2="9" y2="15" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="3" y1="9" x2="15" y2="9" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>';
3085
3161
  return plus;
3086
3162
  }
3087
- function renderBogoBundle(container, bundle, outOfStockBehavior, onAddToCart, onCleanup) {
3163
+ function renderBogoBundle(container, bundle, onAddToCart, onCleanup) {
3088
3164
  const wc = bundle.widgetConfig;
3089
3165
  const buyProduct = bundle.products.find((p) => p.id === bundle.buyProductId);
3090
3166
  const getProduct = bundle.products.find((p) => p.id === bundle.getProductId);
@@ -3113,7 +3189,6 @@ function renderBogoBundle(container, bundle, outOfStockBehavior, onAddToCart, on
3113
3189
  }
3114
3190
  ];
3115
3191
  const oosCount = sides.filter((s) => !s.variants.some((v) => v.available)).length;
3116
- if (outOfStockBehavior === "hide" && oosCount > 0) return;
3117
3192
  const formatMoney = intlFormatMoney(
3118
3193
  buyProduct.priceRange.minVariantPrice.currencyCode
3119
3194
  );
@@ -3549,6 +3624,14 @@ function createWizard(deps) {
3549
3624
  rows.push(row);
3550
3625
  modalList.appendChild(row.el);
3551
3626
  }
3627
+ const stepPicks = selections[forStep] || [];
3628
+ const selectedProductIds = /* @__PURE__ */ Object.create(null);
3629
+ for (const it of stepPicks) selectedProductIds[String(it.productId)] = true;
3630
+ sinkAddedRows(
3631
+ modalList,
3632
+ rows.map((r) => r.el),
3633
+ (i) => selectedProductIds[String(pool[i].id)] === true
3634
+ );
3552
3635
  deps.dropdown?.bindAll(modalList);
3553
3636
  }
3554
3637
  function onListClick(e) {
@@ -3711,6 +3794,7 @@ function createWizard(deps) {
3711
3794
  syncActiveFilterPill();
3712
3795
  if (searchInput) searchInput.value = "";
3713
3796
  filterProducts("");
3797
+ if (modalList) modalList.scrollTop = 0;
3714
3798
  buildStepSegments(stepIndex);
3715
3799
  refresh();
3716
3800
  if (modalLive) {
@@ -3734,7 +3818,7 @@ function createWizard(deps) {
3734
3818
  overlay.classList.add("lb-mix-match__modal-overlay--open");
3735
3819
  lock();
3736
3820
  setTimeout(() => {
3737
- if (searchInput) searchInput.focus();
3821
+ if (closeBtn) closeBtn.focus();
3738
3822
  else modalDialog?.focus();
3739
3823
  }, 50);
3740
3824
  }
@@ -3802,7 +3886,7 @@ function createWizard(deps) {
3802
3886
  }
3803
3887
 
3804
3888
  // src/renderers/multi-step.ts
3805
- function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCart, onCleanup) {
3889
+ function renderMultiStepBundle(container, bundle, onAddToCart, onCleanup) {
3806
3890
  const wc = bundle.widgetConfig;
3807
3891
  const t = DEFAULT_STRINGS;
3808
3892
  const rulesMap = mergeRuleMaps(bundle.productRules, bundle.variantRules);
@@ -3835,13 +3919,13 @@ function renderMultiStepBundle(container, bundle, outOfStockBehavior, onAddToCar
3835
3919
  compareAtPrice: v.compareAtPrice,
3836
3920
  unitPrice: v.unitPrice,
3837
3921
  image: v.image,
3838
- inventoryQuantity: null
3922
+ inventoryQuantity: v.inventoryQuantity ?? null
3839
3923
  }))
3840
3924
  };
3841
3925
  })
3842
3926
  );
3843
- const anyPickable = pools.some((pool) => pool.some((p) => p.available));
3844
- if (outOfStockBehavior === "hide" && !anyPickable) return;
3927
+ const everyStepPickable = pools.length > 0 && pools.every((pool) => pool.some((p) => p.available));
3928
+ if (!everyStepPickable) return;
3845
3929
  const formatMoney = intlFormatMoney(
3846
3930
  bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD"
3847
3931
  );
@@ -5653,7 +5737,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
5653
5737
  transition: opacity 0.25s ease, background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
5654
5738
  }
5655
5739
 
5656
- .lb-mix-match__modal-add:hover {
5740
+ .lb-mix-match__modal-add:hover:not(:disabled) {
5657
5741
  opacity: 0.9;
5658
5742
  }
5659
5743
 
@@ -5768,8 +5852,15 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
5768
5852
  color: color-mix(in srgb, var(--lb-picker-text) 70%, transparent);
5769
5853
  }
5770
5854
 
5771
- /* "Done" reuses the picker Add-button styling so it matches the Add buttons. */
5855
+ /* "Done" reuses the picker Add-button styling so it matches the Add buttons.
5856
+ The flex centring + min-height pin the footer-button silhouette: the wizard
5857
+ Back (bundle-multi-step.css) copies these metrics so the pair stays
5858
+ equal-height whatever border widths the merchant's picker tokens set. */
5772
5859
  .lb-mix-match__modal-done {
5860
+ display: inline-flex;
5861
+ align-items: center;
5862
+ justify-content: center;
5863
+ min-height: 40px;
5773
5864
  padding: 10px 24px;
5774
5865
  transition: opacity 0.25s ease;
5775
5866
  background: var(--lb-picker-add-bg);
@@ -5783,7 +5874,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
5783
5874
  cursor: pointer;
5784
5875
  }
5785
5876
 
5786
- .lb-mix-match__modal-done:hover {
5877
+ .lb-mix-match__modal-done:hover:not(:disabled) {
5787
5878
  opacity: 0.9;
5788
5879
  }
5789
5880
 
@@ -5793,6 +5884,16 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
5793
5884
  box-shadow: none;
5794
5885
  }
5795
5886
 
5887
+ /* Disabled Next/Done (wizard step minimum not met) \u2014 same washed-out
5888
+ treatment as a disabled row Add, so an unfinished step reads at a
5889
+ glance. Hover is gated off above; the solid button returns the moment
5890
+ the requirement is met. */
5891
+ .lb-mix-match__modal-done:disabled {
5892
+ background: color-mix(in srgb, var(--lb-picker-add-bg) 35%, var(--lb-picker-bg));
5893
+ color: color-mix(in srgb, var(--lb-picker-add-label) 85%, transparent);
5894
+ cursor: not-allowed;
5895
+ }
5896
+
5796
5897
  /* Hidden utility for search filtering */
5797
5898
  .lb-hidden {
5798
5899
  display: none !important;
@@ -6049,6 +6150,13 @@ var BUNDLE_VOLUME_CSS = `/* Lime Bundles \u2014 Volume / Quantity Breaks styles
6049
6150
  white-space: nowrap;
6050
6151
  font-variant-numeric: tabular-nums;
6051
6152
  }
6153
+
6154
+ /* Tier the selected variant can't cover \u2014 same greyed treatment as an
6155
+ out-of-stock product row, and not selectable. */
6156
+ .lb-volume__tier--oos {
6157
+ opacity: 0.5;
6158
+ cursor: not-allowed;
6159
+ }
6052
6160
  `;
6053
6161
  var BUNDLE_BOGO_CSS = `/* Lime Bundles \u2014 BOGO (Buy X Get Y) bundle styles */
6054
6162
 
@@ -6217,14 +6325,23 @@ var BUNDLE_MULTI_STEP_CSS = `/**
6217
6325
  justify-self: end;
6218
6326
  }
6219
6327
 
6220
- /* Back \u2014 secondary treatment beside the primary Next/Done (which reuse
6221
- .lb-mix-match__modal-done). */
6328
+ /* Back \u2014 secondary (outlined) treatment beside the primary Next/Done
6329
+ (which reuse .lb-mix-match__modal-done). Same silhouette as Next \u2014
6330
+ metrics copied from the modal-done recipe (padding, type, radius,
6331
+ min-height) \u2014 and picker tokens only: the modal is portaled out of the
6332
+ widget, so the widget vars (--lb-text, --lb-border-*) it previously
6333
+ used don't reliably resolve here and break palette isolation. */
6222
6334
  .lb-multi-step__modal-back {
6223
- padding: 10px 16px;
6335
+ display: inline-flex;
6336
+ align-items: center;
6337
+ justify-content: center;
6338
+ min-height: 40px;
6339
+ padding: 10px 24px;
6224
6340
  background: none;
6225
- border: var(--lb-border-width) solid var(--lb-border-color);
6226
- border-radius: var(--lb-radius-sm);
6227
- color: var(--lb-text);
6341
+ border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
6342
+ border-radius: var(--lb-picker-radius-sm);
6343
+ box-sizing: border-box;
6344
+ color: var(--lb-picker-text);
6228
6345
  font-family: inherit;
6229
6346
  font-size: 14px;
6230
6347
  font-weight: 600;
@@ -6233,7 +6350,7 @@ var BUNDLE_MULTI_STEP_CSS = `/**
6233
6350
  }
6234
6351
 
6235
6352
  .lb-multi-step__modal-back:hover {
6236
- background: color-mix(in srgb, var(--lb-text) 5%, transparent);
6353
+ background: color-mix(in srgb, var(--lb-picker-text) 5%, transparent);
6237
6354
  }
6238
6355
 
6239
6356
  .lb-multi-step__modal-back:focus-visible {
@@ -6663,12 +6780,6 @@ var LimeBundleElement = class extends HTMLElement {
6663
6780
  * `.lb-bundle-widget { ... }` reach the widget's DOM.
6664
6781
  */
6665
6782
  shopCustomCss = null;
6666
- /**
6667
- * Shop-wide out-of-stock behaviour, resolved from the `$app` shop metafield
6668
- * before the first render. Defaults to `show_greyed_out` so a failed or
6669
- * missing fetch degrades to "bundles still render".
6670
- */
6671
- outOfStockBehavior = DEFAULT_OUT_OF_STOCK_BEHAVIOR;
6672
6783
  constructor() {
6673
6784
  super();
6674
6785
  this.shadow = this.attachShadow({ mode: "open" });
@@ -6804,9 +6915,6 @@ var LimeBundleElement = class extends HTMLElement {
6804
6915
  const sanitized = sanitizeCustomCss(customCss);
6805
6916
  if (sanitized.ok) this.shopCustomCss = sanitized.css;
6806
6917
  }
6807
- this.outOfStockBehavior = parseOutOfStockBehavior(
6808
- shopSettings?.shop?.outOfStockBehavior?.value
6809
- );
6810
6918
  this.renderBundles();
6811
6919
  } catch (err) {
6812
6920
  if (controller.signal.aborted) return;
@@ -6886,6 +6994,17 @@ var LimeBundleElement = class extends HTMLElement {
6886
6994
  const storage = window.localStorage;
6887
6995
  const key = cartStorageKey(this.shopDomain);
6888
6996
  const existingCartId = storage?.getItem(key) ?? null;
6997
+ const isStaleCartError = (errors) => errors.length > 0 && errors.every((e) => (e.field ?? []).includes("cartId"));
6998
+ const stockCapped = (warnings) => (warnings ?? []).some((w) => w.code === "MERCHANDISE_NOT_ENOUGH_STOCK");
6999
+ const fail = (message) => {
7000
+ this.dispatchEvent(
7001
+ new CustomEvent("lime-bundle:error", {
7002
+ detail: { message, code: "CART_ERROR" },
7003
+ bubbles: true,
7004
+ composed: true
7005
+ })
7006
+ );
7007
+ };
6889
7008
  try {
6890
7009
  let checkoutUrl = null;
6891
7010
  if (existingCartId) {
@@ -6895,7 +7014,14 @@ var LimeBundleElement = class extends HTMLElement {
6895
7014
  );
6896
7015
  const payload = res.cartLinesAdd;
6897
7016
  if (payload?.userErrors?.length) {
7017
+ if (!isStaleCartError(payload.userErrors)) {
7018
+ fail(payload.userErrors[0].message);
7019
+ return;
7020
+ }
6898
7021
  storage?.removeItem(key);
7022
+ } else if (stockCapped(payload?.warnings)) {
7023
+ fail("Some items in this bundle are out of stock.");
7024
+ return;
6899
7025
  } else if (payload?.cart) {
6900
7026
  checkoutUrl = payload.cart.checkoutUrl;
6901
7027
  }
@@ -6906,6 +7032,14 @@ var LimeBundleElement = class extends HTMLElement {
6906
7032
  { input: { lines } }
6907
7033
  );
6908
7034
  const payload = res.cartCreate;
7035
+ if (payload?.userErrors?.length) {
7036
+ fail(payload.userErrors[0].message);
7037
+ return;
7038
+ }
7039
+ if (stockCapped(payload?.warnings)) {
7040
+ fail("Some items in this bundle are out of stock.");
7041
+ return;
7042
+ }
6909
7043
  if (payload?.cart) {
6910
7044
  storage?.setItem(key, payload.cart.id);
6911
7045
  checkoutUrl = payload.cart.checkoutUrl;
@@ -6914,13 +7048,7 @@ var LimeBundleElement = class extends HTMLElement {
6914
7048
  if (checkoutUrl) {
6915
7049
  window.location.assign(checkoutUrl);
6916
7050
  } else {
6917
- this.dispatchEvent(
6918
- new CustomEvent("lime-bundle:error", {
6919
- detail: { message: "Cart creation failed", code: "CART_ERROR" },
6920
- bubbles: true,
6921
- composed: true
6922
- })
6923
- );
7051
+ fail("Cart creation failed");
6924
7052
  }
6925
7053
  } catch (err) {
6926
7054
  this.dispatchEvent(
@@ -6994,44 +7122,25 @@ var LimeBundleElement = class extends HTMLElement {
6994
7122
  const registerCleanup = (fn) => this.renderCleanups.push(fn);
6995
7123
  switch (bundle.bundleType) {
6996
7124
  case "fixed":
6997
- renderFixedBundle(
6998
- container,
6999
- bundle,
7000
- this.outOfStockBehavior,
7001
- dispatch,
7002
- registerCleanup
7003
- );
7125
+ renderFixedBundle(container, bundle, dispatch, registerCleanup);
7004
7126
  break;
7005
7127
  case "mix_match":
7006
7128
  renderMixMatchBundle(
7007
7129
  container,
7008
7130
  bundle,
7009
- this.outOfStockBehavior,
7010
7131
  dispatch,
7011
7132
  registerCleanup,
7012
7133
  this.currentProductHandle
7013
7134
  );
7014
7135
  break;
7015
7136
  case "volume":
7016
- renderVolumeBundle(container, bundle, this.outOfStockBehavior, dispatch);
7137
+ renderVolumeBundle(container, bundle, dispatch);
7017
7138
  break;
7018
7139
  case "bogo":
7019
- renderBogoBundle(
7020
- container,
7021
- bundle,
7022
- this.outOfStockBehavior,
7023
- dispatch,
7024
- registerCleanup
7025
- );
7140
+ renderBogoBundle(container, bundle, dispatch, registerCleanup);
7026
7141
  break;
7027
7142
  case "multi_step":
7028
- renderMultiStepBundle(
7029
- container,
7030
- bundle,
7031
- this.outOfStockBehavior,
7032
- dispatch,
7033
- registerCleanup
7034
- );
7143
+ renderMultiStepBundle(container, bundle, dispatch, registerCleanup);
7035
7144
  break;
7036
7145
  }
7037
7146
  this.shadow.appendChild(container);