@lime-bundles/react 6.2.0 → 7.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.js CHANGED
@@ -81,13 +81,10 @@ async function fetchBundleDataWithWarnings(options) {
81
81
  // src/fetchShopSettings.ts
82
82
  import {
83
83
  createStorefrontClient as createStorefrontClient2,
84
- SHOP_SETTINGS_QUERY,
85
- DEFAULT_OUT_OF_STOCK_BEHAVIOR,
86
- parseOutOfStockBehavior
84
+ SHOP_SETTINGS_QUERY
87
85
  } from "@lime-bundles/core";
88
86
  var FALLBACK = {
89
- customCss: null,
90
- outOfStockBehavior: DEFAULT_OUT_OF_STOCK_BEHAVIOR
87
+ customCss: null
91
88
  };
92
89
  var cache = /* @__PURE__ */ new Map();
93
90
  async function fetchShopSettings(options) {
@@ -105,10 +102,7 @@ async function fetchShopSettings(options) {
105
102
  { signal: options.signal }
106
103
  );
107
104
  return {
108
- customCss: data.shop?.customCss?.value ?? null,
109
- outOfStockBehavior: parseOutOfStockBehavior(
110
- data.shop?.outOfStockBehavior?.value
111
- )
105
+ customCss: data.shop?.customCss?.value ?? null
112
106
  };
113
107
  })();
114
108
  cache.set(options.shopDomain, promise);
@@ -266,19 +260,25 @@ function useWidgetConfigVars(config) {
266
260
  import { useEffect as useEffect4, useMemo as useMemo2, useState as useState3, useCallback as useCallback3 } from "react";
267
261
  import { variants as variantsNs } from "@lime-bundles/core";
268
262
  var { findVariantByOptions, isOptionValueAvailable, toPickerVariant } = variantsNs;
269
- function defaultInitial(variants) {
270
- return variants.find((v) => v.availableForSale) ?? variants[0] ?? null;
271
- }
272
263
  function useVariantSelection({
273
264
  variants,
274
265
  optionNames,
275
266
  initialVariant,
276
- optionValuesByPosition
267
+ optionValuesByPosition,
268
+ requiredQtyFor
277
269
  }) {
278
- const seed = useMemo2(
279
- () => initialVariant ?? defaultInitial(variants),
280
- [initialVariant, variants]
270
+ const pickerVariants = useMemo2(
271
+ () => variants.map((v) => toPickerVariant(v, requiredQtyFor?.(v.id))),
272
+ [variants, requiredQtyFor]
281
273
  );
274
+ const seed = useMemo2(() => {
275
+ if (initialVariant) return initialVariant;
276
+ const covered = pickerVariants.find((p) => p.available);
277
+ if (covered) {
278
+ return variants.find((v) => v.id === covered.id) ?? null;
279
+ }
280
+ return variants.find((v) => v.availableForSale) ?? variants[0] ?? null;
281
+ }, [initialVariant, variants, pickerVariants]);
282
282
  const [selectedValues, setSelectedValues] = useState3(
283
283
  () => seed ? seed.selectedOptions.map((o) => o.value) : optionNames.map(() => "")
284
284
  );
@@ -286,10 +286,6 @@ function useVariantSelection({
286
286
  if (!seed) return;
287
287
  setSelectedValues(seed.selectedOptions.map((o) => o.value));
288
288
  }, [seed?.id]);
289
- const pickerVariants = useMemo2(
290
- () => variants.map((v) => toPickerVariant(v)),
291
- [variants]
292
- );
293
289
  const selectedVariant = useMemo2(() => {
294
290
  if (selectedValues.length !== optionNames.length) return null;
295
291
  const match = findVariantByOptions(pickerVariants, selectedValues);
@@ -924,7 +920,11 @@ function FixedProductRow({ bundle, product, currency, isOos, onVariantChange })
924
920
  const showPicker = variants.length > 1 && optionNames.length > 0;
925
921
  const { selectedValues, selectedVariant, setOptionValue, optionsFor } = useVariantSelection({
926
922
  variants,
927
- optionNames
923
+ optionNames,
924
+ // Quantity-aware dropdown: a variant that can't cover this row's
925
+ // bundle quantity greys out instead of walking the shopper into a
926
+ // cart Shopify caps at add time.
927
+ requiredQtyFor: (variantId) => resolveBundleQty(bundle, product.id, variantId)
928
928
  });
929
929
  useEffect7(() => {
930
930
  onVariantChange(product.id, selectedVariant ?? null);
@@ -1019,7 +1019,7 @@ function FixedProductRow({ bundle, product, currency, isOos, onVariantChange })
1019
1019
  }
1020
1020
 
1021
1021
  // src/components/MixMatchBundle.tsx
1022
- import { useCallback as useCallback6, useEffect as useEffect12, useMemo as useMemo6, useState as useState9 } from "react";
1022
+ import { useCallback as useCallback6, useEffect as useEffect11, useMemo as useMemo6, useState as useState8 } from "react";
1023
1023
  import {
1024
1024
  formatMoney as formatMoney3,
1025
1025
  formatUnitPrice as formatUnitPrice3,
@@ -1028,39 +1028,12 @@ import {
1028
1028
  DEFAULT_PRODUCT_RULE as DEFAULT_PRODUCT_RULE2
1029
1029
  } from "@lime-bundles/core";
1030
1030
 
1031
- // src/hooks/useShopSettings.ts
1032
- import { useEffect as useEffect8, useState as useState7 } from "react";
1033
- import {
1034
- DEFAULT_OUT_OF_STOCK_BEHAVIOR as DEFAULT_OUT_OF_STOCK_BEHAVIOR2
1035
- } from "@lime-bundles/core";
1036
- function useShopSettings(options) {
1037
- const [outOfStockBehavior, setOutOfStockBehavior] = useState7(DEFAULT_OUT_OF_STOCK_BEHAVIOR2);
1038
- const { shopDomain, storefrontAccessToken } = options;
1039
- useEffect8(() => {
1040
- const controller = new AbortController();
1041
- let active = true;
1042
- fetchShopSettingsOrDefault({
1043
- shopDomain,
1044
- storefrontAccessToken,
1045
- signal: controller.signal
1046
- }).then((settings) => {
1047
- if (!active || controller.signal.aborted) return;
1048
- setOutOfStockBehavior(settings.outOfStockBehavior);
1049
- });
1050
- return () => {
1051
- active = false;
1052
- controller.abort();
1053
- };
1054
- }, [shopDomain, storefrontAccessToken]);
1055
- return { outOfStockBehavior };
1056
- }
1057
-
1058
1031
  // src/components/MixMatchPicker.tsx
1059
1032
  import {
1060
- useEffect as useEffect11,
1033
+ useEffect as useEffect10,
1061
1034
  useMemo as useMemo5,
1062
1035
  useRef as useRef6,
1063
- useState as useState8
1036
+ useState as useState7
1064
1037
  } from "react";
1065
1038
  import { createPortal } from "react-dom";
1066
1039
  import {
@@ -1072,7 +1045,7 @@ import {
1072
1045
  } from "@lime-bundles/core";
1073
1046
 
1074
1047
  // src/hooks/useScrollLock.ts
1075
- import { useEffect as useEffect9 } from "react";
1048
+ import { useEffect as useEffect8 } from "react";
1076
1049
  var lockCount = 0;
1077
1050
  var savedY = 0;
1078
1051
  function lock() {
@@ -1101,7 +1074,7 @@ function unlock() {
1101
1074
  window.scrollTo(0, savedY);
1102
1075
  }
1103
1076
  function useScrollLock(active) {
1104
- useEffect9(() => {
1077
+ useEffect8(() => {
1105
1078
  if (!active) return;
1106
1079
  lock();
1107
1080
  return unlock;
@@ -1109,7 +1082,7 @@ function useScrollLock(active) {
1109
1082
  }
1110
1083
 
1111
1084
  // src/hooks/useFocusTrap.ts
1112
- import { useEffect as useEffect10, useRef as useRef5 } from "react";
1085
+ import { useEffect as useEffect9, useRef as useRef5 } from "react";
1113
1086
  var FOCUSABLE_SELECTOR = [
1114
1087
  "a[href]",
1115
1088
  "button:not([disabled])",
@@ -1132,14 +1105,16 @@ function useFocusTrap({
1132
1105
  active,
1133
1106
  containerRef,
1134
1107
  onEscape,
1108
+ initialFocusRef,
1135
1109
  returnFocusRef
1136
1110
  }) {
1137
1111
  const previouslyFocused = useRef5(null);
1138
- useEffect10(() => {
1112
+ useEffect9(() => {
1139
1113
  if (!active) return;
1140
1114
  const container = containerRef.current;
1141
1115
  previouslyFocused.current = document.activeElement ?? null;
1142
- container?.focus();
1116
+ const initial = initialFocusRef?.current ?? container;
1117
+ initial?.focus();
1143
1118
  function onKeyDown(e) {
1144
1119
  if (e.key === "Escape") {
1145
1120
  e.preventDefault();
@@ -1191,7 +1166,7 @@ function variantRuleFor(bundle, variantId) {
1191
1166
  function sanitizeId(gid) {
1192
1167
  return gid.replace(/[^a-zA-Z0-9_-]/g, "-");
1193
1168
  }
1194
- function buildEligibleProducts(bundle, oosBehavior) {
1169
+ function buildEligibleProducts(bundle) {
1195
1170
  const result = [];
1196
1171
  const seen = /* @__PURE__ */ new Set();
1197
1172
  for (const product of bundle.products) {
@@ -1202,7 +1177,6 @@ function buildEligibleProducts(bundle, oosBehavior) {
1202
1177
  (v) => isVariantFulfillable2(v, rule.min)
1203
1178
  );
1204
1179
  const isOos = available.length === 0;
1205
- if (isOos && oosBehavior === "hide") continue;
1206
1180
  result.push({
1207
1181
  product,
1208
1182
  variants: product.variants.nodes,
@@ -1217,7 +1191,6 @@ function normalizeText(str) {
1217
1191
  }
1218
1192
  var FILTERS_ALL = "__all__";
1219
1193
  function MixMatchPicker({
1220
- outOfStockBehavior,
1221
1194
  bundle,
1222
1195
  currency,
1223
1196
  requiredQty,
@@ -1235,13 +1208,14 @@ function MixMatchPicker({
1235
1208
  const wc = bundle.widgetConfig;
1236
1209
  const overlayRef = useRef6(null);
1237
1210
  const dialogRef = useRef6(null);
1211
+ const closeBtnRef = useRef6(null);
1238
1212
  const mouseDownTarget = useRef6(null);
1239
- const [open, setOpen] = useState8(false);
1240
- const [query, setQuery] = useState8("");
1241
- const [activeType, setActiveType] = useState8(FILTERS_ALL);
1213
+ const [open, setOpen] = useState7(false);
1214
+ const [query, setQuery] = useState7("");
1215
+ const [activeType, setActiveType] = useState7(FILTERS_ALL);
1242
1216
  const eligible = useMemo5(
1243
- () => buildEligibleProducts(bundle, outOfStockBehavior),
1244
- [bundle, outOfStockBehavior]
1217
+ () => buildEligibleProducts(bundle),
1218
+ [bundle]
1245
1219
  );
1246
1220
  const productTypes = useMemo5(() => {
1247
1221
  const types = [];
@@ -1259,7 +1233,7 @@ function MixMatchPicker({
1259
1233
  const totalUnits = selections.reduce((sum, s) => sum + s.quantity, 0);
1260
1234
  const atCapacity = totalUnits >= requiredQty;
1261
1235
  const shownUnits = Math.min(totalUnits, requiredQty);
1262
- useEffect11(() => {
1236
+ useEffect10(() => {
1263
1237
  const id = requestAnimationFrame(() => setOpen(true));
1264
1238
  return () => cancelAnimationFrame(id);
1265
1239
  }, []);
@@ -1267,6 +1241,7 @@ function MixMatchPicker({
1267
1241
  useFocusTrap({
1268
1242
  active: true,
1269
1243
  containerRef: dialogRef,
1244
+ initialFocusRef: closeBtnRef,
1270
1245
  onEscape: onClose
1271
1246
  });
1272
1247
  const remaining = Math.max(0, requiredQty - shownUnits);
@@ -1275,14 +1250,25 @@ function MixMatchPicker({
1275
1250
  const pct = bundle.discountConfig.discountType === "percentage" && bundle.discountConfig.discountValue ? Math.round(bundle.discountConfig.discountValue) : 0;
1276
1251
  return pct > 0 ? `Choose ${remaining} more to unlock ${pct}% off` : `Choose ${remaining} more to complete your bundle`;
1277
1252
  })();
1253
+ const [selectedAtOpen] = useState7(
1254
+ () => new Set(selections.map((s) => s.productId))
1255
+ );
1256
+ const ordered = useMemo5(() => {
1257
+ const front = [];
1258
+ const back = [];
1259
+ for (const ep of eligible) {
1260
+ (selectedAtOpen.has(ep.product.id) ? back : front).push(ep);
1261
+ }
1262
+ return front.concat(back);
1263
+ }, [eligible, selectedAtOpen]);
1278
1264
  const filtered = useMemo5(() => {
1279
1265
  const nq = normalizeText(query.trim());
1280
- return eligible.filter((ep) => {
1266
+ return ordered.filter((ep) => {
1281
1267
  const matchesQuery = !nq || normalizeText(ep.product.title).includes(nq);
1282
1268
  const matchesType = activeType === FILTERS_ALL || (ep.product.productType ?? "") === activeType;
1283
1269
  return matchesQuery && matchesType;
1284
1270
  });
1285
- }, [eligible, query, activeType]);
1271
+ }, [ordered, query, activeType]);
1286
1272
  const showEmpty = filtered.length === 0 && query.trim().length > 0;
1287
1273
  function onOverlayMouseDown(e) {
1288
1274
  mouseDownTarget.current = e.target;
@@ -1331,6 +1317,7 @@ function MixMatchPicker({
1331
1317
  /* @__PURE__ */ jsx4(
1332
1318
  "button",
1333
1319
  {
1320
+ ref: closeBtnRef,
1334
1321
  type: "button",
1335
1322
  className: "lb-mix-match__modal-close",
1336
1323
  "data-modal-close": true,
@@ -1533,12 +1520,12 @@ function MixMatchPickerRow({
1533
1520
  Math.max(0, alreadyInBundle - committedQty)
1534
1521
  ) : 0;
1535
1522
  const stepperMax = Math.max(vRule.min, cap);
1536
- const [qty, setQty] = useState8(vRule.min);
1537
- useEffect11(() => {
1523
+ const [qty, setQty] = useState7(vRule.min);
1524
+ useEffect10(() => {
1538
1525
  setQty((prev) => Math.max(vRule.min, Math.min(prev, stepperMax)));
1539
1526
  }, [stepperMax, vRule.min]);
1540
1527
  const currentVariantId = currentVariant?.id ?? null;
1541
- useEffect11(() => {
1528
+ useEffect10(() => {
1542
1529
  if (!variantInBundle) setQty(vRule.min);
1543
1530
  }, [variantInBundle, currentVariantId, vRule.min]);
1544
1531
  if (!currentVariant) return null;
@@ -1800,10 +1787,6 @@ function MixMatchBundle(props) {
1800
1787
  storefrontAccessToken,
1801
1788
  bundleGid
1802
1789
  });
1803
- const { outOfStockBehavior } = useShopSettings({
1804
- shopDomain,
1805
- storefrontAccessToken
1806
- });
1807
1790
  const { elementRef, trackAddToCart } = useAnalytics({
1808
1791
  shopDomain,
1809
1792
  appUrl: appUrl ?? `https://${shopDomain}`,
@@ -1811,16 +1794,16 @@ function MixMatchBundle(props) {
1811
1794
  bundleType: "mix_match",
1812
1795
  enabled: analyticsEnabled !== false
1813
1796
  });
1814
- const [selections, setSelections] = useState9([]);
1815
- const [seededFor, setSeededFor] = useState9(null);
1816
- const [pickerOpen, setPickerOpen] = useState9(false);
1817
- const [addingToCart, setAddingToCart] = useState9(false);
1818
- const [cartError, setCartError] = useState9(null);
1797
+ const [selections, setSelections] = useState8([]);
1798
+ const [seededFor, setSeededFor] = useState8(null);
1799
+ const [pickerOpen, setPickerOpen] = useState8(false);
1800
+ const [addingToCart, setAddingToCart] = useState8(false);
1801
+ const [cartError, setCartError] = useState8(null);
1819
1802
  const bundle = result.status === "success" && result.bundle.bundleType === "mix_match" ? result.bundle : null;
1820
1803
  const setConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
1821
1804
  const setModalConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
1822
1805
  const slotsEdgeFadeRef = useEdgeFade();
1823
- useEffect12(() => {
1806
+ useEffect11(() => {
1824
1807
  if (result.status === "error") {
1825
1808
  onError?.(result.error);
1826
1809
  return;
@@ -1833,7 +1816,7 @@ function MixMatchBundle(props) {
1833
1816
  );
1834
1817
  }
1835
1818
  }, [result, onError]);
1836
- useEffect12(() => {
1819
+ useEffect11(() => {
1837
1820
  if (!bundle || seededFor === bundle.id) return;
1838
1821
  setSeededFor(bundle.id);
1839
1822
  const seedCurrency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
@@ -2243,7 +2226,6 @@ function MixMatchBundle(props) {
2243
2226
  pickerOpen && /* @__PURE__ */ jsx5(
2244
2227
  MixMatchPicker,
2245
2228
  {
2246
- outOfStockBehavior,
2247
2229
  bundle,
2248
2230
  currency,
2249
2231
  requiredQty: requiredUnits,
@@ -2316,7 +2298,7 @@ function CloseIcon2() {
2316
2298
  }
2317
2299
 
2318
2300
  // src/components/VolumeBundle.tsx
2319
- import { useCallback as useCallback7, useEffect as useEffect13, useMemo as useMemo7, useState as useState10 } from "react";
2301
+ import { useCallback as useCallback7, useEffect as useEffect12, useMemo as useMemo7, useState as useState9 } from "react";
2320
2302
  import {
2321
2303
  formatMoney as formatMoney4,
2322
2304
  formatUnitPrice as formatUnitPrice4,
@@ -2353,12 +2335,12 @@ function VolumeBundle(props) {
2353
2335
  bundleType: "volume",
2354
2336
  enabled: analyticsEnabled !== false
2355
2337
  });
2356
- const [quantity, setQuantity] = useState10(1);
2357
- const [addingToCart, setAddingToCart] = useState10(false);
2358
- const [cartError, setCartError] = useState10(null);
2359
- const [seededFor, setSeededFor] = useState10(null);
2338
+ const [quantity, setQuantity] = useState9(1);
2339
+ const [addingToCart, setAddingToCart] = useState9(false);
2340
+ const [cartError, setCartError] = useState9(null);
2341
+ const [seededFor, setSeededFor] = useState9(null);
2360
2342
  const bundle = result.status === "success" && result.bundle.bundleType === "volume" ? result.bundle : null;
2361
- useEffect13(() => {
2343
+ useEffect12(() => {
2362
2344
  if (!bundle || seededFor === bundle.id) return;
2363
2345
  setSeededFor(bundle.id);
2364
2346
  const tiers = bundle.volumeTiers;
@@ -2376,7 +2358,7 @@ function VolumeBundle(props) {
2376
2358
  }, [bundle, seededFor]);
2377
2359
  const setConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
2378
2360
  const tiersEdgeFadeRef = useEdgeFade();
2379
- useEffect13(() => {
2361
+ useEffect12(() => {
2380
2362
  if (result.status === "error") {
2381
2363
  onError?.(result.error);
2382
2364
  return;
@@ -2400,6 +2382,11 @@ function VolumeBundle(props) {
2400
2382
  const minTierQty = bundle ? getMinTierQuantity(bundle.volumeTiers) : 1;
2401
2383
  const isSoldOut = variants.length > 0 && !variants.some((v) => isVariantFulfillable4(v, minTierQty));
2402
2384
  const displayVariant = selectedVariant ?? variants.find((v) => isVariantFulfillable4(v, minTierQty)) ?? variants[0];
2385
+ const tierAvailable = useCallback7(
2386
+ (tierQty) => Boolean(displayVariant && isVariantFulfillable4(displayVariant, tierQty)),
2387
+ [displayVariant]
2388
+ );
2389
+ const selectedTierUnavailable = !isSoldOut && !tierAvailable(quantity);
2403
2390
  const basePrice = displayVariant ? parseFloat(displayVariant.price.amount) : product ? parseFloat(product.priceRange.minVariantPrice.amount) : 0;
2404
2391
  const currency = product?.priceRange.minVariantPrice.currencyCode ?? "USD";
2405
2392
  const thumbImage = displayVariant?.image ?? product?.featuredImage ?? null;
@@ -2557,16 +2544,20 @@ function VolumeBundle(props) {
2557
2544
  const savingsPercent = Math.round(ts.savingsPercent);
2558
2545
  const showCompare = bundle.widgetConfig.pricing.showComparePrice && ts.savings > 0;
2559
2546
  const isChecked = activeTier === ts.tier;
2547
+ const available = tierAvailable(ts.tier.minQuantity);
2560
2548
  return /* @__PURE__ */ jsxs6(
2561
2549
  "div",
2562
2550
  {
2563
- className: `lb-bundle__tier ${ts.isActive ? "lb-bundle__tier--active" : ""}`,
2551
+ className: `lb-bundle__tier ${ts.isActive ? "lb-bundle__tier--active" : ""} ${available ? "" : "lb-bundle__tier--oos"}`,
2564
2552
  role: "radio",
2565
2553
  "aria-checked": isChecked,
2554
+ "aria-disabled": available ? void 0 : true,
2566
2555
  tabIndex: isChecked ? 0 : -1,
2567
- onClick: () => setQuantity(ts.tier.minQuantity),
2556
+ onClick: () => {
2557
+ if (available) setQuantity(ts.tier.minQuantity);
2558
+ },
2568
2559
  onKeyDown: (e) => {
2569
- if (e.key === "Enter" || e.key === " ") {
2560
+ if ((e.key === "Enter" || e.key === " ") && available) {
2570
2561
  e.preventDefault();
2571
2562
  setQuantity(ts.tier.minQuantity);
2572
2563
  }
@@ -2681,9 +2672,9 @@ function VolumeBundle(props) {
2681
2672
  {
2682
2673
  className: "lb-bundle__cta",
2683
2674
  onClick: handleAddToCart,
2684
- disabled: addingToCart || isSoldOut,
2675
+ disabled: addingToCart || isSoldOut || selectedTierUnavailable,
2685
2676
  "aria-busy": addingToCart,
2686
- children: addingToCart ? "Adding..." : isSoldOut ? "Sold out" : bundle.widgetConfig.cta.ctaText ?? `Add ${quantity} to Cart`
2677
+ children: addingToCart ? "Adding..." : isSoldOut || selectedTierUnavailable ? "Sold out" : bundle.widgetConfig.cta.ctaText ?? `Add ${quantity} to Cart`
2687
2678
  }
2688
2679
  )
2689
2680
  ]
@@ -2692,7 +2683,7 @@ function VolumeBundle(props) {
2692
2683
  }
2693
2684
 
2694
2685
  // src/components/BogoBundle.tsx
2695
- import { useCallback as useCallback8, useEffect as useEffect14, useMemo as useMemo8, useState as useState11 } from "react";
2686
+ import { useCallback as useCallback8, useEffect as useEffect13, useMemo as useMemo8, useState as useState10 } from "react";
2696
2687
  import {
2697
2688
  formatMoney as formatMoney5,
2698
2689
  formatUnitPrice as formatUnitPrice5,
@@ -2727,9 +2718,9 @@ function BogoBundle(props) {
2727
2718
  bundleType: "bogo",
2728
2719
  enabled: analyticsEnabled !== false
2729
2720
  });
2730
- const [addingToCart, setAddingToCart] = useState11(false);
2731
- const [cartError, setCartError] = useState11(null);
2732
- const [selectedVariants, setSelectedVariants] = useState11({ buy: null, get: null });
2721
+ const [addingToCart, setAddingToCart] = useState10(false);
2722
+ const [cartError, setCartError] = useState10(null);
2723
+ const [selectedVariants, setSelectedVariants] = useState10({ buy: null, get: null });
2733
2724
  const handleVariantChange = useCallback8(
2734
2725
  (role, variant) => {
2735
2726
  setSelectedVariants((prev) => {
@@ -2741,7 +2732,7 @@ function BogoBundle(props) {
2741
2732
  );
2742
2733
  const bundle = result.status === "success" && result.bundle.bundleType === "bogo" ? result.bundle : null;
2743
2734
  const setConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
2744
- useEffect14(() => {
2735
+ useEffect13(() => {
2745
2736
  if (result.status === "error") {
2746
2737
  onError?.(result.error);
2747
2738
  return;
@@ -2940,7 +2931,7 @@ function BogoProductRow({ side, product, quantity, percent, badge, currency, onV
2940
2931
  variants,
2941
2932
  optionNames
2942
2933
  });
2943
- useEffect14(() => {
2934
+ useEffect13(() => {
2944
2935
  onVariantChange(side, selectedVariant ?? null);
2945
2936
  }, [selectedVariant, side, onVariantChange]);
2946
2937
  const displayVariant = selectedVariant ?? variants.find((v) => isVariantFulfillable5(v, quantity)) ?? variants[0];
@@ -3013,7 +3004,7 @@ function BogoProductRow({ side, product, quantity, percent, badge, currency, onV
3013
3004
  }
3014
3005
 
3015
3006
  // src/components/MultiStepBundle.tsx
3016
- import { useCallback as useCallback9, useEffect as useEffect16, useMemo as useMemo10, useState as useState13 } from "react";
3007
+ import { useCallback as useCallback9, useEffect as useEffect15, useMemo as useMemo10, useState as useState12 } from "react";
3017
3008
  import {
3018
3009
  formatMoney as formatMoney7,
3019
3010
  formatUnitPrice as formatUnitPrice7,
@@ -3026,10 +3017,10 @@ import {
3026
3017
 
3027
3018
  // src/components/MultiStepPicker.tsx
3028
3019
  import {
3029
- useEffect as useEffect15,
3020
+ useEffect as useEffect14,
3030
3021
  useMemo as useMemo9,
3031
3022
  useRef as useRef7,
3032
- useState as useState12
3023
+ useState as useState11
3033
3024
  } from "react";
3034
3025
  import { createPortal as createPortal2 } from "react-dom";
3035
3026
  import {
@@ -3055,7 +3046,7 @@ function variantRuleFor3(bundle, variantId) {
3055
3046
  function sanitizeId2(gid) {
3056
3047
  return gid.replace(/[^a-zA-Z0-9_-]/g, "-");
3057
3048
  }
3058
- function buildEligibleProducts2(bundle, stepIndex, oosBehavior) {
3049
+ function buildEligibleProducts2(bundle, stepIndex) {
3059
3050
  const result = [];
3060
3051
  for (const product of productsForStep(bundle, stepIndex)) {
3061
3052
  const rule = ruleFor3(bundle, product.id);
@@ -3063,7 +3054,6 @@ function buildEligibleProducts2(bundle, stepIndex, oosBehavior) {
3063
3054
  (v) => isVariantFulfillable6(v, rule.min)
3064
3055
  );
3065
3056
  const isOos = available.length === 0;
3066
- if (isOos && oosBehavior === "hide") continue;
3067
3057
  result.push({
3068
3058
  product,
3069
3059
  variants: product.variants.nodes,
@@ -3078,7 +3068,6 @@ function normalizeText2(str) {
3078
3068
  }
3079
3069
  var FILTERS_ALL2 = "__all__";
3080
3070
  function MultiStepPicker({
3081
- outOfStockBehavior,
3082
3071
  bundle,
3083
3072
  currency,
3084
3073
  initialStep,
@@ -3098,14 +3087,16 @@ function MultiStepPicker({
3098
3087
  const overlayRef = useRef7(null);
3099
3088
  const dialogRef = useRef7(null);
3100
3089
  const titleRef = useRef7(null);
3090
+ const closeBtnRef = useRef7(null);
3091
+ const listRef = useRef7(null);
3101
3092
  const mouseDownTarget = useRef7(null);
3102
- const [open, setOpen] = useState12(false);
3103
- const [stepIndex, setStepIndex] = useState12(
3093
+ const [open, setOpen] = useState11(false);
3094
+ const [stepIndex, setStepIndex] = useState11(
3104
3095
  () => Math.max(0, Math.min(steps.length - 1, initialStep))
3105
3096
  );
3106
- const [query, setQuery] = useState12("");
3107
- const [activeType, setActiveType] = useState12(FILTERS_ALL2);
3108
- const [announcement, setAnnouncement] = useState12("");
3097
+ const [query, setQuery] = useState11("");
3098
+ const [activeType, setActiveType] = useState11(FILTERS_ALL2);
3099
+ const [announcement, setAnnouncement] = useState11("");
3109
3100
  const step = steps[stepIndex];
3110
3101
  const stepMin = step?.minQuantity ?? 1;
3111
3102
  const stepPicks = selections[stepIndex] ?? [];
@@ -3114,8 +3105,8 @@ function MultiStepPicker({
3114
3105
  const isLastStep = stepIndex === steps.length - 1;
3115
3106
  const shownUnits = Math.min(unitsInStep, stepMin);
3116
3107
  const eligible = useMemo9(
3117
- () => buildEligibleProducts2(bundle, stepIndex, outOfStockBehavior),
3118
- [bundle, stepIndex, outOfStockBehavior]
3108
+ () => buildEligibleProducts2(bundle, stepIndex),
3109
+ [bundle, stepIndex]
3119
3110
  );
3120
3111
  const productTypes = useMemo9(() => {
3121
3112
  const types = [];
@@ -3130,7 +3121,7 @@ function MultiStepPicker({
3130
3121
  return types;
3131
3122
  }, [eligible]);
3132
3123
  const showFilters = wc.mixMatchShowTypeFilters && productTypes.length >= 2;
3133
- useEffect15(() => {
3124
+ useEffect14(() => {
3134
3125
  const id = requestAnimationFrame(() => setOpen(true));
3135
3126
  return () => cancelAnimationFrame(id);
3136
3127
  }, []);
@@ -3138,8 +3129,23 @@ function MultiStepPicker({
3138
3129
  useFocusTrap({
3139
3130
  active: true,
3140
3131
  containerRef: dialogRef,
3132
+ initialFocusRef: closeBtnRef,
3141
3133
  onEscape: onClose
3142
3134
  });
3135
+ useEffect14(() => {
3136
+ if (listRef.current) listRef.current.scrollTop = 0;
3137
+ }, [stepIndex]);
3138
+ const [selectedAtLoad, setSelectedAtLoad] = useState11(
3139
+ () => new Set((selections[stepIndex] ?? []).map((s) => s.productId))
3140
+ );
3141
+ const ordered = useMemo9(() => {
3142
+ const front = [];
3143
+ const back = [];
3144
+ for (const ep of eligible) {
3145
+ (selectedAtLoad.has(ep.product.id) ? back : front).push(ep);
3146
+ }
3147
+ return front.concat(back);
3148
+ }, [eligible, selectedAtLoad]);
3143
3149
  function goToStep(i) {
3144
3150
  const clamped = Math.max(0, Math.min(steps.length - 1, i));
3145
3151
  setStepIndex(clamped);
@@ -3147,6 +3153,7 @@ function MultiStepPicker({
3147
3153
  setActiveType(FILTERS_ALL2);
3148
3154
  const target = steps[clamped];
3149
3155
  const targetPicks = selections[clamped] ?? [];
3156
+ setSelectedAtLoad(new Set(targetPicks.map((s) => s.productId)));
3150
3157
  const targetUnits = targetPicks.reduce((sum, s) => sum + s.quantity, 0);
3151
3158
  setAnnouncement(
3152
3159
  `Step ${clamped + 1} of ${steps.length}. ${target.name}. ${Math.min(targetUnits, target.minQuantity)} of ${target.minQuantity} added`
@@ -3156,12 +3163,12 @@ function MultiStepPicker({
3156
3163
  const subtitle = `Step ${stepIndex + 1} of ${steps.length} \xB7 ${step?.name ?? ""}`;
3157
3164
  const filtered = useMemo9(() => {
3158
3165
  const nq = normalizeText2(query.trim());
3159
- return eligible.filter((ep) => {
3166
+ return ordered.filter((ep) => {
3160
3167
  const matchesQuery = !nq || normalizeText2(ep.product.title).includes(nq);
3161
3168
  const matchesType = activeType === FILTERS_ALL2 || (ep.product.productType ?? "") === activeType;
3162
3169
  return matchesQuery && matchesType;
3163
3170
  });
3164
- }, [eligible, query, activeType]);
3171
+ }, [ordered, query, activeType]);
3165
3172
  const showEmpty = filtered.length === 0 && query.trim().length > 0;
3166
3173
  function onOverlayMouseDown(e) {
3167
3174
  mouseDownTarget.current = e.target;
@@ -3219,6 +3226,7 @@ function MultiStepPicker({
3219
3226
  /* @__PURE__ */ jsx8(
3220
3227
  "button",
3221
3228
  {
3229
+ ref: closeBtnRef,
3222
3230
  type: "button",
3223
3231
  className: "lb-mix-match__modal-close",
3224
3232
  "data-modal-close": true,
@@ -3306,7 +3314,7 @@ function MultiStepPicker({
3306
3314
  })
3307
3315
  }
3308
3316
  ),
3309
- /* @__PURE__ */ jsx8("div", { className: "lb-mix-match__modal-list", "data-modal-list": true, children: filtered.map((ep) => /* @__PURE__ */ jsx8(
3317
+ /* @__PURE__ */ jsx8("div", { className: "lb-mix-match__modal-list", "data-modal-list": true, ref: listRef, children: filtered.map((ep) => /* @__PURE__ */ jsx8(
3310
3318
  MultiStepPickerRow,
3311
3319
  {
3312
3320
  bundle,
@@ -3453,12 +3461,12 @@ function MultiStepPickerRow({
3453
3461
  const stockRemaining = currentVariant ? maxAddableQuantity2(currentVariant, Number.MAX_SAFE_INTEGER, variantOtherUnits) : 0;
3454
3462
  const cap = Math.max(0, Math.min(spotCeiling, stockRemaining));
3455
3463
  const stepperMax = Math.max(vRule.min, cap);
3456
- const [qty, setQty] = useState12(vRule.min);
3457
- useEffect15(() => {
3464
+ const [qty, setQty] = useState11(vRule.min);
3465
+ useEffect14(() => {
3458
3466
  setQty((prev) => Math.max(vRule.min, Math.min(prev, stepperMax)));
3459
3467
  }, [stepperMax, vRule.min]);
3460
3468
  const currentVariantId = currentVariant?.id ?? null;
3461
- useEffect15(() => {
3469
+ useEffect14(() => {
3462
3470
  if (!variantInStep) setQty(vRule.min);
3463
3471
  }, [variantInStep, currentVariantId, vRule.min]);
3464
3472
  if (!currentVariant || !step) return null;
@@ -3729,10 +3737,6 @@ function MultiStepBundle(props) {
3729
3737
  storefrontAccessToken,
3730
3738
  bundleGid
3731
3739
  });
3732
- const { outOfStockBehavior } = useShopSettings({
3733
- shopDomain,
3734
- storefrontAccessToken
3735
- });
3736
3740
  const { elementRef, trackAddToCart } = useAnalytics({
3737
3741
  shopDomain,
3738
3742
  appUrl: appUrl ?? `https://${shopDomain}`,
@@ -3740,14 +3744,14 @@ function MultiStepBundle(props) {
3740
3744
  bundleType: "multi_step",
3741
3745
  enabled: analyticsEnabled !== false
3742
3746
  });
3743
- const [selections, setSelections] = useState13([]);
3744
- const [pickerOpenAt, setPickerOpenAt] = useState13(null);
3745
- const [addingToCart, setAddingToCart] = useState13(false);
3746
- const [cartError, setCartError] = useState13(null);
3747
+ const [selections, setSelections] = useState12([]);
3748
+ const [pickerOpenAt, setPickerOpenAt] = useState12(null);
3749
+ const [addingToCart, setAddingToCart] = useState12(false);
3750
+ const [cartError, setCartError] = useState12(null);
3747
3751
  const bundle = result.status === "success" && result.bundle.bundleType === "multi_step" ? result.bundle : null;
3748
3752
  const setConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
3749
3753
  const setModalConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
3750
- useEffect16(() => {
3754
+ useEffect15(() => {
3751
3755
  if (result.status === "error") {
3752
3756
  onError?.(result.error);
3753
3757
  return;
@@ -3761,7 +3765,7 @@ function MultiStepBundle(props) {
3761
3765
  }
3762
3766
  }, [result, onError]);
3763
3767
  const steps = useMemo10(() => bundle?.steps ?? [], [bundle]);
3764
- useEffect16(() => {
3768
+ useEffect15(() => {
3765
3769
  const seeded = steps.map(() => []);
3766
3770
  if (bundle) {
3767
3771
  const seedCurrency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
@@ -4201,7 +4205,6 @@ function MultiStepBundle(props) {
4201
4205
  pickerOpenAt !== null && /* @__PURE__ */ jsx9(
4202
4206
  MultiStepPicker,
4203
4207
  {
4204
- outOfStockBehavior,
4205
4208
  bundle,
4206
4209
  currency,
4207
4210
  initialStep: pickerOpenAt,
@@ -4275,7 +4278,7 @@ async function fetchShopCustomCss(options) {
4275
4278
  }
4276
4279
 
4277
4280
  // src/hooks/useBundlesForProduct.ts
4278
- import { useState as useState14, useEffect as useEffect17 } from "react";
4281
+ import { useState as useState13, useEffect as useEffect16 } from "react";
4279
4282
  import {
4280
4283
  fetchBundlesForProduct,
4281
4284
  injectCustomCss as injectCustomCss2
@@ -4286,10 +4289,10 @@ var INITIAL_STATE2 = {
4286
4289
  error: null
4287
4290
  };
4288
4291
  function useBundlesForProduct(options) {
4289
- const [state, setState] = useState14(
4292
+ const [state, setState] = useState13(
4290
4293
  INITIAL_STATE2
4291
4294
  );
4292
- useEffect17(() => {
4295
+ useEffect16(() => {
4293
4296
  const controller = new AbortController();
4294
4297
  setState(INITIAL_STATE2);
4295
4298
  fetchBundlesForProduct({
@@ -4363,9 +4366,7 @@ import {
4363
4366
  parseMetaobjectBundleStrict as parseMetaobjectBundleStrict2,
4364
4367
  BUNDLE_METAOBJECT_QUERY as BUNDLE_METAOBJECT_QUERY2,
4365
4368
  BUNDLES_FOR_PRODUCT_QUERY,
4366
- SHOP_SETTINGS_QUERY as SHOP_SETTINGS_QUERY2,
4367
- DEFAULT_OUT_OF_STOCK_BEHAVIOR as DEFAULT_OUT_OF_STOCK_BEHAVIOR3,
4368
- parseOutOfStockBehavior as parseOutOfStockBehavior2
4369
+ SHOP_SETTINGS_QUERY as SHOP_SETTINGS_QUERY2
4369
4370
  } from "@lime-bundles/core";
4370
4371
  export {
4371
4372
  BUNDLES_FOR_PRODUCT_QUERY,
@@ -4373,7 +4374,6 @@ export {
4373
4374
  BogoBundle,
4374
4375
  BundleCountdown,
4375
4376
  BundleParseError2 as BundleParseError,
4376
- DEFAULT_OUT_OF_STOCK_BEHAVIOR3 as DEFAULT_OUT_OF_STOCK_BEHAVIOR,
4377
4377
  FixedBundle,
4378
4378
  MixMatchBundle,
4379
4379
  MultiStepBundle,
@@ -4404,7 +4404,6 @@ export {
4404
4404
  parseCents,
4405
4405
  parseMetaobjectBundle,
4406
4406
  parseMetaobjectBundleStrict2 as parseMetaobjectBundleStrict,
4407
- parseOutOfStockBehavior2 as parseOutOfStockBehavior,
4408
4407
  percentageDiscountUnit,
4409
4408
  productsForStep3 as productsForStep,
4410
4409
  reportAddToCart2 as reportAddToCart,
@@ -4416,7 +4415,6 @@ export {
4416
4415
  useAnalytics,
4417
4416
  useBundleData,
4418
4417
  useBundlesForProduct,
4419
- useShopSettings,
4420
4418
  useVariantSelection,
4421
4419
  useWidgetConfigVars,
4422
4420
  validateQuantity