@lime-bundles/react 2.3.0 → 2.4.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
@@ -1,5 +1,5 @@
1
1
  // src/components/FixedBundle.tsx
2
- import { useCallback as useCallback3, useEffect as useEffect4, useState as useState3 } from "react";
2
+ import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo4, useState as useState5 } from "react";
3
3
  import {
4
4
  formatMoney,
5
5
  formatUnitPrice,
@@ -208,8 +208,340 @@ function useWidgetConfigVars(config) {
208
208
  );
209
209
  }
210
210
 
211
- // src/components/FixedBundle.tsx
211
+ // src/hooks/useVariantSelection.ts
212
+ import { useEffect as useEffect4, useMemo as useMemo2, useState as useState3, useCallback as useCallback3 } from "react";
213
+ import { variants as variantsNs } from "@lime-bundles/core";
214
+ var { findVariantByOptions, isOptionValueAvailable, toPickerVariant } = variantsNs;
215
+ function defaultInitial(variants) {
216
+ return variants.find((v) => v.availableForSale) ?? variants[0] ?? null;
217
+ }
218
+ function useVariantSelection({
219
+ variants,
220
+ optionNames,
221
+ initialVariant,
222
+ optionValuesByPosition
223
+ }) {
224
+ const seed = useMemo2(
225
+ () => initialVariant ?? defaultInitial(variants),
226
+ [initialVariant, variants]
227
+ );
228
+ const [selectedValues, setSelectedValues] = useState3(
229
+ () => seed ? seed.selectedOptions.map((o) => o.value) : optionNames.map(() => "")
230
+ );
231
+ useEffect4(() => {
232
+ if (!seed) return;
233
+ setSelectedValues(seed.selectedOptions.map((o) => o.value));
234
+ }, [seed?.id]);
235
+ const pickerVariants = useMemo2(
236
+ () => variants.map((v) => toPickerVariant(v)),
237
+ [variants]
238
+ );
239
+ const selectedVariant = useMemo2(() => {
240
+ if (selectedValues.length !== optionNames.length) return null;
241
+ const match = findVariantByOptions(pickerVariants, selectedValues);
242
+ if (!match) return null;
243
+ return variants.find((v) => v.id === match.id) ?? null;
244
+ }, [pickerVariants, selectedValues, variants, optionNames.length]);
245
+ const setOptionValue = useCallback3(
246
+ (optionIndex, value) => {
247
+ setSelectedValues((prev) => {
248
+ const next = prev.slice();
249
+ next[optionIndex] = value;
250
+ return next;
251
+ });
252
+ },
253
+ []
254
+ );
255
+ const optionsByPosition = useMemo2(() => {
256
+ if (optionValuesByPosition) {
257
+ return optionValuesByPosition.map((values) => values.slice());
258
+ }
259
+ const out = optionNames.map(() => []);
260
+ for (const v of variants) {
261
+ v.selectedOptions.forEach((o, i) => {
262
+ if (i < out.length && !out[i].includes(o.value)) out[i].push(o.value);
263
+ });
264
+ }
265
+ return out;
266
+ }, [variants, optionNames, optionValuesByPosition]);
267
+ const optionsFor = useCallback3(
268
+ (optionIndex) => {
269
+ const values = optionsByPosition[optionIndex] ?? [];
270
+ return values.map((value) => ({
271
+ value,
272
+ disabled: !isOptionValueAvailable(
273
+ pickerVariants,
274
+ optionIndex,
275
+ value,
276
+ selectedValues
277
+ )
278
+ }));
279
+ },
280
+ [optionsByPosition, pickerVariants, selectedValues]
281
+ );
282
+ return {
283
+ selectedValues,
284
+ selectedVariant,
285
+ setOptionValue,
286
+ optionsFor
287
+ };
288
+ }
289
+
290
+ // src/components/VariantDropdown.tsx
291
+ import {
292
+ useEffect as useEffect5,
293
+ useId,
294
+ useLayoutEffect,
295
+ useMemo as useMemo3,
296
+ useRef as useRef3,
297
+ useState as useState4
298
+ } from "react";
299
+ import { dropdown } from "@lime-bundles/core";
212
300
  import { jsx, jsxs } from "react/jsx-runtime";
301
+ var { computePosition, emptyTypeAheadState, handleKey, pushTypeAheadChar } = dropdown;
302
+ var ITEM_HEIGHT_PX = 32;
303
+ var LIST_PAD_Y = 8;
304
+ var MAX_VISIBLE_ITEMS = 8;
305
+ function VariantDropdown({
306
+ options,
307
+ value,
308
+ onChange,
309
+ ariaLabel,
310
+ className
311
+ }) {
312
+ const idBase = useId();
313
+ const triggerRef = useRef3(null);
314
+ const listboxRef = useRef3(null);
315
+ const typeAheadRef = useRef3(emptyTypeAheadState());
316
+ const [isOpen, setIsOpen] = useState4(false);
317
+ const [activeIndex, setActiveIndex] = useState4(-1);
318
+ const [position, setPosition] = useState4(null);
319
+ const selectedIndex = useMemo3(
320
+ () => options.findIndex((o) => o.value === value),
321
+ [options, value]
322
+ );
323
+ const optionsState = useMemo3(
324
+ () => options.map((o) => ({ disabled: !!o.disabled, label: o.label })),
325
+ [options]
326
+ );
327
+ const selectedLabel = selectedIndex >= 0 ? options[selectedIndex].label : "";
328
+ function open() {
329
+ if (isOpen) return;
330
+ setIsOpen(true);
331
+ setActiveIndex(
332
+ selectedIndex >= 0 && !optionsState[selectedIndex]?.disabled ? selectedIndex : optionsState.findIndex((o) => !o.disabled)
333
+ );
334
+ }
335
+ function close(restoreFocus) {
336
+ setIsOpen(false);
337
+ setActiveIndex(-1);
338
+ if (restoreFocus) triggerRef.current?.focus();
339
+ }
340
+ function commit(index) {
341
+ const opt = options[index];
342
+ if (!opt || opt.disabled) return;
343
+ if (opt.value !== value) onChange(opt.value);
344
+ close(true);
345
+ }
346
+ useLayoutEffect(() => {
347
+ if (!isOpen) return;
348
+ const trigger = triggerRef.current;
349
+ if (!trigger) return;
350
+ const rect = trigger.getBoundingClientRect();
351
+ if (rect.width === 0) return;
352
+ const visibleCount = Math.min(options.length || 1, MAX_VISIBLE_ITEMS);
353
+ const desiredHeight = visibleCount * ITEM_HEIGHT_PX + LIST_PAD_Y;
354
+ setPosition(
355
+ computePosition({
356
+ trigger: {
357
+ top: rect.top,
358
+ bottom: rect.bottom,
359
+ left: rect.left,
360
+ width: rect.width
361
+ },
362
+ viewportHeight: window.innerHeight,
363
+ desiredHeight
364
+ })
365
+ );
366
+ }, [isOpen, options.length]);
367
+ useEffect5(() => {
368
+ if (!isOpen) return;
369
+ const isInsideDropdown = (event) => {
370
+ const path = event.composedPath();
371
+ const trigger = triggerRef.current;
372
+ const listbox = listboxRef.current;
373
+ return trigger != null && path.includes(trigger) || listbox != null && path.includes(listbox);
374
+ };
375
+ const onPointerdown = (event) => {
376
+ if (isInsideDropdown(event)) return;
377
+ close(false);
378
+ };
379
+ const onScroll = (event) => {
380
+ if (isInsideDropdown(event)) return;
381
+ close(false);
382
+ };
383
+ const onResize = () => close(false);
384
+ document.addEventListener("pointerdown", onPointerdown, true);
385
+ window.addEventListener("scroll", onScroll, true);
386
+ window.addEventListener("resize", onResize);
387
+ return () => {
388
+ document.removeEventListener("pointerdown", onPointerdown, true);
389
+ window.removeEventListener("scroll", onScroll, true);
390
+ window.removeEventListener("resize", onResize);
391
+ };
392
+ }, [isOpen]);
393
+ useEffect5(() => {
394
+ if (!isOpen || activeIndex < 0) return;
395
+ const listbox = listboxRef.current;
396
+ if (!listbox) return;
397
+ const li = listbox.querySelector(
398
+ `#${CSS.escape(`${idBase}-opt-${activeIndex}`)}`
399
+ );
400
+ if (!li) return;
401
+ const liTop = li.offsetTop;
402
+ const liBottom = liTop + li.offsetHeight;
403
+ const visTop = listbox.scrollTop;
404
+ const visBottom = visTop + listbox.clientHeight;
405
+ if (liTop < visTop) {
406
+ listbox.scrollTop = liTop;
407
+ } else if (liBottom > visBottom) {
408
+ listbox.scrollTop = liBottom - listbox.clientHeight;
409
+ }
410
+ }, [activeIndex, isOpen, idBase]);
411
+ function onKeyDown(event) {
412
+ const action = handleKey(
413
+ {
414
+ key: event.key,
415
+ ctrlKey: event.ctrlKey,
416
+ metaKey: event.metaKey,
417
+ altKey: event.altKey,
418
+ shiftKey: event.shiftKey
419
+ },
420
+ { isOpen, activeIndex, selectedIndex, options: optionsState }
421
+ );
422
+ if (action.preventDefault) event.preventDefault();
423
+ switch (action.type) {
424
+ case "open":
425
+ open();
426
+ if (action.activeIndex >= 0) setActiveIndex(action.activeIndex);
427
+ return;
428
+ case "close":
429
+ close(action.restoreFocus);
430
+ return;
431
+ case "move-active":
432
+ setActiveIndex(action.activeIndex);
433
+ return;
434
+ case "commit":
435
+ commit(action.index);
436
+ return;
437
+ case "type-ahead": {
438
+ const result = pushTypeAheadChar(
439
+ typeAheadRef.current,
440
+ action.char,
441
+ Date.now(),
442
+ optionsState
443
+ );
444
+ typeAheadRef.current = result.newState;
445
+ if (result.matchedIndex !== null) {
446
+ if (!isOpen) open();
447
+ setActiveIndex(result.matchedIndex);
448
+ }
449
+ return;
450
+ }
451
+ case "passthrough":
452
+ return;
453
+ }
454
+ }
455
+ function onShellBlur(event) {
456
+ const next = event.relatedTarget;
457
+ if (next && event.currentTarget.contains(next)) return;
458
+ setTimeout(() => {
459
+ if (!isOpen) return;
460
+ const active = document.activeElement;
461
+ if (!triggerRef.current?.contains(active) && !listboxRef.current?.contains(active)) {
462
+ close(false);
463
+ }
464
+ }, 0);
465
+ }
466
+ const listboxId = `${idBase}-listbox`;
467
+ const activeDescendantId = activeIndex >= 0 ? `${idBase}-opt-${activeIndex}` : void 0;
468
+ const panelStyle = position ? { maxHeight: position.maxHeight } : void 0;
469
+ return /* @__PURE__ */ jsxs(
470
+ "div",
471
+ {
472
+ className: `lb-dropdown${className ? ` ${className}` : ""}`,
473
+ "data-lb-dropdown": "",
474
+ onBlur: onShellBlur,
475
+ children: [
476
+ /* @__PURE__ */ jsxs(
477
+ "button",
478
+ {
479
+ ref: triggerRef,
480
+ type: "button",
481
+ className: "lb-dropdown-trigger",
482
+ role: "combobox",
483
+ "aria-haspopup": "listbox",
484
+ "aria-expanded": isOpen,
485
+ "aria-controls": listboxId,
486
+ "aria-label": ariaLabel,
487
+ "aria-activedescendant": activeDescendantId,
488
+ onClick: (e) => {
489
+ e.preventDefault();
490
+ if (isOpen) close(false);
491
+ else open();
492
+ },
493
+ onKeyDown,
494
+ children: [
495
+ /* @__PURE__ */ jsx("span", { className: "lb-dropdown-trigger-value", children: selectedLabel }),
496
+ /* @__PURE__ */ jsx("span", { className: "lb-dropdown-chevron", "aria-hidden": "true" })
497
+ ]
498
+ }
499
+ ),
500
+ /* @__PURE__ */ jsx(
501
+ "ul",
502
+ {
503
+ ref: listboxRef,
504
+ id: listboxId,
505
+ className: "lb-dropdown-listbox",
506
+ role: "listbox",
507
+ "aria-label": ariaLabel,
508
+ hidden: !isOpen,
509
+ "data-placement": position?.placement,
510
+ style: panelStyle,
511
+ onMouseDown: (e) => e.preventDefault(),
512
+ children: options.map((opt, i) => (
513
+ // Keyboard activation lives on the trigger combobox via
514
+ // aria-activedescendant — the WAI-ARIA combobox pattern keeps
515
+ // focus on the button and tracks the active option by id, so
516
+ // the option <li> elements deliberately have no key handlers.
517
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-element-interactions
518
+ /* @__PURE__ */ jsx(
519
+ "li",
520
+ {
521
+ id: `${idBase}-opt-${i}`,
522
+ className: `lb-dropdown-option${i === activeIndex ? " is-active" : ""}`,
523
+ role: "option",
524
+ "aria-selected": i === selectedIndex,
525
+ "aria-disabled": opt.disabled || void 0,
526
+ "data-value": opt.value,
527
+ onClick: () => commit(i),
528
+ onMouseMove: () => {
529
+ if (!opt.disabled && i !== activeIndex) setActiveIndex(i);
530
+ },
531
+ children: opt.label
532
+ },
533
+ opt.value
534
+ )
535
+ ))
536
+ }
537
+ )
538
+ ]
539
+ }
540
+ );
541
+ }
542
+
543
+ // src/components/FixedBundle.tsx
544
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
213
545
  function FixedBundle(props) {
214
546
  const {
215
547
  shopDomain,
@@ -233,11 +565,21 @@ function FixedBundle(props) {
233
565
  bundleType: "fixed",
234
566
  enabled: analyticsEnabled !== false
235
567
  });
236
- const [addingToCart, setAddingToCart] = useState3(false);
237
- const [cartError, setCartError] = useState3(null);
568
+ const [addingToCart, setAddingToCart] = useState5(false);
569
+ const [cartError, setCartError] = useState5(null);
570
+ const [selectedVariants, setSelectedVariants] = useState5({});
571
+ const handleVariantChange = useCallback4(
572
+ (productId, variant) => {
573
+ setSelectedVariants((prev) => {
574
+ if (prev[productId] === variant) return prev;
575
+ return { ...prev, [productId]: variant };
576
+ });
577
+ },
578
+ []
579
+ );
238
580
  const bundle = result.status === "success" && result.bundle.bundleType === "fixed" ? result.bundle : null;
239
581
  const setConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
240
- useEffect4(() => {
582
+ useEffect6(() => {
241
583
  if (result.status === "error") {
242
584
  onError?.(result.error);
243
585
  return;
@@ -250,10 +592,10 @@ function FixedBundle(props) {
250
592
  );
251
593
  }
252
594
  }, [result, onError]);
253
- const handleAddToCart = useCallback3(async () => {
595
+ const handleAddToCart = useCallback4(async () => {
254
596
  if (!bundle) return;
255
597
  const bundleLines = bundle.products.filter((p) => p.variants.nodes.some((v) => v.availableForSale)).map((p) => {
256
- const variant = p.variants.nodes.find((v) => v.availableForSale);
598
+ const variant = selectedVariants[p.id] ?? p.variants.nodes.find((v) => v.availableForSale);
257
599
  return {
258
600
  productId: p.id,
259
601
  variant,
@@ -297,17 +639,17 @@ function FixedBundle(props) {
297
639
  } finally {
298
640
  setAddingToCart(false);
299
641
  }
300
- }, [bundle, onAddToCart, onError, trackAddToCart]);
642
+ }, [bundle, onAddToCart, onError, trackAddToCart, selectedVariants]);
301
643
  if (result.status === "loading") {
302
- return /* @__PURE__ */ jsxs("div", { className: `lb-bundle lb-bundle--loading ${className ?? ""}`, children: [
303
- /* @__PURE__ */ jsx("div", { className: "lb-skeleton lb-skeleton--title" }),
304
- /* @__PURE__ */ jsx("div", { className: "lb-skeleton lb-skeleton--products" })
644
+ return /* @__PURE__ */ jsxs2("div", { className: `lb-bundle lb-bundle--loading ${className ?? ""}`, children: [
645
+ /* @__PURE__ */ jsx2("div", { className: "lb-skeleton lb-skeleton--title" }),
646
+ /* @__PURE__ */ jsx2("div", { className: "lb-skeleton lb-skeleton--products" })
305
647
  ] });
306
648
  }
307
649
  if (result.status === "error") return null;
308
650
  if (!bundle) return null;
309
651
  const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
310
- return /* @__PURE__ */ jsxs(
652
+ return /* @__PURE__ */ jsxs2(
311
653
  "div",
312
654
  {
313
655
  ref: (el) => {
@@ -318,38 +660,19 @@ function FixedBundle(props) {
318
660
  role: "region",
319
661
  "aria-label": bundle.title,
320
662
  children: [
321
- /* @__PURE__ */ jsx("h3", { className: "lb-bundle__title", children: bundle.title }),
322
- bundle.discountLabel && /* @__PURE__ */ jsx("span", { className: "lb-bundle__discount-badge", children: bundle.discountLabel }),
323
- /* @__PURE__ */ jsx("div", { className: "lb-bundle__products", children: bundle.products.map((product) => {
324
- const variant = product.variants.nodes.find((v) => v.availableForSale) ?? product.variants.nodes[0];
325
- const priceText = variant ? formatMoney(variant.price.amount, currency) : formatMoney(
326
- product.priceRange.minVariantPrice.amount,
327
- currency
328
- );
329
- const unitPriceText = variant ? formatUnitPrice(
330
- variant.unitPrice,
331
- variant.unitPriceMeasurement,
332
- currency
333
- ) : null;
334
- return /* @__PURE__ */ jsxs("div", { className: "lb-bundle__product", part: "product", children: [
335
- product.featuredImage && /* @__PURE__ */ jsx(
336
- "img",
337
- {
338
- src: product.featuredImage.url,
339
- alt: product.featuredImage.altText ?? product.title,
340
- className: "lb-bundle__product-image",
341
- loading: "lazy"
342
- }
343
- ),
344
- /* @__PURE__ */ jsxs("div", { className: "lb-bundle__product-info", children: [
345
- /* @__PURE__ */ jsx("p", { className: "lb-bundle__product-title", children: product.title }),
346
- /* @__PURE__ */ jsx("p", { className: "lb-bundle__product-price", children: priceText }),
347
- unitPriceText && /* @__PURE__ */ jsx("p", { className: "lb-bundle__product-unit-price", children: unitPriceText })
348
- ] })
349
- ] }, product.id);
350
- }) }),
351
- cartError && /* @__PURE__ */ jsx("p", { className: "lb-bundle__error", role: "alert", children: cartError }),
352
- /* @__PURE__ */ jsx(
663
+ /* @__PURE__ */ jsx2("h3", { className: "lb-bundle__title", children: bundle.title }),
664
+ bundle.discountLabel && /* @__PURE__ */ jsx2("span", { className: "lb-bundle__discount-badge", children: bundle.discountLabel }),
665
+ /* @__PURE__ */ jsx2("div", { className: "lb-bundle__products", children: bundle.products.map((product) => /* @__PURE__ */ jsx2(
666
+ FixedProductRow,
667
+ {
668
+ product,
669
+ currency,
670
+ onVariantChange: handleVariantChange
671
+ },
672
+ product.id
673
+ )) }),
674
+ cartError && /* @__PURE__ */ jsx2("p", { className: "lb-bundle__error", role: "alert", children: cartError }),
675
+ /* @__PURE__ */ jsx2(
353
676
  "button",
354
677
  {
355
678
  className: "lb-bundle__cta",
@@ -363,16 +686,72 @@ function FixedBundle(props) {
363
686
  }
364
687
  );
365
688
  }
689
+ function FixedProductRow({ product, currency, onVariantChange }) {
690
+ const variants = product.variants.nodes;
691
+ const optionNames = useMemo4(() => {
692
+ const first = variants[0];
693
+ return first ? first.selectedOptions.map((o) => o.name) : [];
694
+ }, [variants]);
695
+ const showPicker = variants.length > 1 && optionNames.length > 0;
696
+ const { selectedValues, selectedVariant, setOptionValue, optionsFor } = useVariantSelection({
697
+ variants,
698
+ optionNames
699
+ });
700
+ useEffect6(() => {
701
+ onVariantChange(product.id, selectedVariant ?? null);
702
+ }, [selectedVariant, product.id, onVariantChange]);
703
+ const displayVariant = selectedVariant ?? variants.find((v) => v.availableForSale) ?? variants[0];
704
+ const priceText = displayVariant ? formatMoney(displayVariant.price.amount, currency) : formatMoney(product.priceRange.minVariantPrice.amount, currency);
705
+ const unitPriceText = displayVariant ? formatUnitPrice(
706
+ displayVariant.unitPrice,
707
+ displayVariant.unitPriceMeasurement,
708
+ currency
709
+ ) : null;
710
+ const thumbImage = displayVariant?.image ?? product.featuredImage;
711
+ return /* @__PURE__ */ jsxs2("div", { className: "lb-bundle__product", part: "product", children: [
712
+ thumbImage && /* @__PURE__ */ jsx2(
713
+ "img",
714
+ {
715
+ src: thumbImage.url,
716
+ alt: thumbImage.altText ?? product.title,
717
+ className: "lb-bundle__product-image",
718
+ loading: "lazy"
719
+ }
720
+ ),
721
+ /* @__PURE__ */ jsxs2("div", { className: "lb-bundle__product-info", children: [
722
+ /* @__PURE__ */ jsx2("p", { className: "lb-bundle__product-title", children: product.title }),
723
+ /* @__PURE__ */ jsx2("p", { className: "lb-bundle__product-price", children: priceText }),
724
+ unitPriceText && /* @__PURE__ */ jsx2("p", { className: "lb-bundle__product-unit-price", children: unitPriceText }),
725
+ showPicker && /* @__PURE__ */ jsx2("div", { className: "lb-bundle__product-variant-pickers", children: optionNames.map((optionName, optionIndex) => {
726
+ const dropdownOptions = optionsFor(optionIndex).map((o) => ({
727
+ value: o.value,
728
+ label: o.value,
729
+ disabled: o.disabled
730
+ }));
731
+ return /* @__PURE__ */ jsx2(
732
+ VariantDropdown,
733
+ {
734
+ options: dropdownOptions,
735
+ value: selectedValues[optionIndex] ?? null,
736
+ onChange: (v) => setOptionValue(optionIndex, v),
737
+ ariaLabel: optionName
738
+ },
739
+ optionName
740
+ );
741
+ }) })
742
+ ] })
743
+ ] });
744
+ }
366
745
 
367
746
  // src/components/MixMatchBundle.tsx
368
- import { useCallback as useCallback4, useEffect as useEffect5, useState as useState4 } from "react";
747
+ import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo5, useState as useState6 } from "react";
369
748
  import {
370
749
  formatMoney as formatMoney2,
371
750
  formatUnitPrice as formatUnitPrice2,
372
751
  validateQuantity,
373
752
  resolveBundleQty as resolveBundleQty2
374
753
  } from "@lime-bundles/core";
375
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
754
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
376
755
  function MixMatchBundle(props) {
377
756
  const {
378
757
  shopDomain,
@@ -396,14 +775,14 @@ function MixMatchBundle(props) {
396
775
  bundleType: "mix_match",
397
776
  enabled: analyticsEnabled !== false
398
777
  });
399
- const [selections, setSelections] = useState4(
778
+ const [selections, setSelections] = useState6(
400
779
  /* @__PURE__ */ new Map()
401
780
  );
402
- const [addingToCart, setAddingToCart] = useState4(false);
403
- const [cartError, setCartError] = useState4(null);
781
+ const [addingToCart, setAddingToCart] = useState6(false);
782
+ const [cartError, setCartError] = useState6(null);
404
783
  const bundle = result.status === "success" && result.bundle.bundleType === "mix_match" ? result.bundle : null;
405
784
  const setConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
406
- useEffect5(() => {
785
+ useEffect7(() => {
407
786
  if (result.status === "error") {
408
787
  onError?.(result.error);
409
788
  return;
@@ -421,7 +800,7 @@ function MixMatchBundle(props) {
421
800
  0
422
801
  );
423
802
  const validation = bundle ? validateQuantity(totalQuantity, bundle.minQuantity, bundle.maxQuantity) : { valid: false, totalQuantity: 0, message: null };
424
- const toggleProduct = useCallback4(
803
+ const toggleProduct = useCallback5(
425
804
  (productId, variant) => {
426
805
  setSelections((prev) => {
427
806
  const next = new Map(prev);
@@ -441,7 +820,7 @@ function MixMatchBundle(props) {
441
820
  },
442
821
  [bundle]
443
822
  );
444
- const updateQuantity = useCallback4(
823
+ const updateQuantity = useCallback5(
445
824
  (productId, variantId, quantity) => {
446
825
  setSelections((prev) => {
447
826
  const next = new Map(prev);
@@ -456,7 +835,7 @@ function MixMatchBundle(props) {
456
835
  },
457
836
  []
458
837
  );
459
- const handleAddToCart = useCallback4(async () => {
838
+ const handleAddToCart = useCallback5(async () => {
460
839
  if (!bundle || !validation.valid) return;
461
840
  const lines = Array.from(selections.values()).map((s) => ({
462
841
  merchandiseId: s.variantId,
@@ -501,15 +880,15 @@ function MixMatchBundle(props) {
501
880
  totalQuantity
502
881
  ]);
503
882
  if (result.status === "loading") {
504
- return /* @__PURE__ */ jsxs2("div", { className: `lb-bundle lb-bundle--loading ${className ?? ""}`, children: [
505
- /* @__PURE__ */ jsx2("div", { className: "lb-skeleton lb-skeleton--title" }),
506
- /* @__PURE__ */ jsx2("div", { className: "lb-skeleton lb-skeleton--products" })
883
+ return /* @__PURE__ */ jsxs3("div", { className: `lb-bundle lb-bundle--loading ${className ?? ""}`, children: [
884
+ /* @__PURE__ */ jsx3("div", { className: "lb-skeleton lb-skeleton--title" }),
885
+ /* @__PURE__ */ jsx3("div", { className: "lb-skeleton lb-skeleton--products" })
507
886
  ] });
508
887
  }
509
888
  if (result.status === "error") return null;
510
889
  if (!bundle) return null;
511
890
  const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
512
- return /* @__PURE__ */ jsxs2(
891
+ return /* @__PURE__ */ jsxs3(
513
892
  "div",
514
893
  {
515
894
  ref: (el) => {
@@ -520,83 +899,23 @@ function MixMatchBundle(props) {
520
899
  role: "region",
521
900
  "aria-label": bundle.title,
522
901
  children: [
523
- /* @__PURE__ */ jsx2("h3", { className: "lb-bundle__title", children: bundle.title }),
524
- bundle.discountLabel && /* @__PURE__ */ jsx2("span", { className: "lb-bundle__discount-badge", children: bundle.discountLabel }),
525
- /* @__PURE__ */ jsx2("p", { className: "lb-bundle__instructions", children: bundle.minQuantity && bundle.maxQuantity ? `Select ${bundle.minQuantity}\u2013${bundle.maxQuantity} items` : bundle.minQuantity ? `Select at least ${bundle.minQuantity} items` : bundle.maxQuantity ? `Select up to ${bundle.maxQuantity} items` : "Select your items" }),
526
- /* @__PURE__ */ jsx2("div", { className: "lb-bundle__products lb-bundle__products--selectable", children: bundle.products.map((product) => {
527
- const defaultVariant = product.variants.nodes.find((v) => v.availableForSale) ?? product.variants.nodes[0];
528
- if (!defaultVariant) return null;
529
- const key = `${product.id}:${defaultVariant.id}`;
530
- const selected = selections.get(key);
531
- return /* @__PURE__ */ jsxs2(
532
- "div",
533
- {
534
- className: `lb-bundle__product lb-bundle__product--selectable ${selected ? "lb-bundle__product--selected" : ""}`,
535
- children: [
536
- product.featuredImage && /* @__PURE__ */ jsx2(
537
- "img",
538
- {
539
- src: product.featuredImage.url,
540
- alt: product.featuredImage.altText ?? product.title,
541
- className: "lb-bundle__product-image",
542
- loading: "lazy"
543
- }
544
- ),
545
- /* @__PURE__ */ jsxs2("div", { className: "lb-bundle__product-info", children: [
546
- /* @__PURE__ */ jsx2("p", { className: "lb-bundle__product-title", children: product.title }),
547
- /* @__PURE__ */ jsx2("p", { className: "lb-bundle__product-price", children: formatMoney2(defaultVariant.price.amount, currency) }),
548
- (() => {
549
- const unitPriceText = formatUnitPrice2(
550
- defaultVariant.unitPrice,
551
- defaultVariant.unitPriceMeasurement,
552
- currency
553
- );
554
- return unitPriceText ? /* @__PURE__ */ jsx2("p", { className: "lb-bundle__product-unit-price", children: unitPriceText }) : null;
555
- })()
556
- ] }),
557
- /* @__PURE__ */ jsx2("div", { className: "lb-bundle__product-actions", children: selected ? /* @__PURE__ */ jsxs2("div", { className: "lb-bundle__quantity-control", children: [
558
- /* @__PURE__ */ jsx2(
559
- "button",
560
- {
561
- "aria-label": `Decrease ${product.title}`,
562
- onClick: () => updateQuantity(
563
- product.id,
564
- defaultVariant.id,
565
- selected.quantity - 1
566
- ),
567
- children: "\u2212"
568
- }
569
- ),
570
- /* @__PURE__ */ jsx2("span", { children: selected.quantity }),
571
- /* @__PURE__ */ jsx2(
572
- "button",
573
- {
574
- "aria-label": `Increase ${product.title}`,
575
- onClick: () => updateQuantity(
576
- product.id,
577
- defaultVariant.id,
578
- selected.quantity + 1
579
- ),
580
- children: "+"
581
- }
582
- )
583
- ] }) : /* @__PURE__ */ jsx2(
584
- "button",
585
- {
586
- className: "lb-bundle__select-btn",
587
- onClick: () => toggleProduct(product.id, defaultVariant),
588
- disabled: !defaultVariant.availableForSale,
589
- children: defaultVariant.availableForSale ? "Select" : "Sold out"
590
- }
591
- ) })
592
- ]
593
- },
594
- product.id
595
- );
596
- }) }),
597
- validation.message && /* @__PURE__ */ jsx2("p", { className: "lb-bundle__validation", role: "status", children: validation.message }),
598
- cartError && /* @__PURE__ */ jsx2("p", { className: "lb-bundle__error", role: "alert", children: cartError }),
599
- /* @__PURE__ */ jsx2(
902
+ /* @__PURE__ */ jsx3("h3", { className: "lb-bundle__title", children: bundle.title }),
903
+ bundle.discountLabel && /* @__PURE__ */ jsx3("span", { className: "lb-bundle__discount-badge", children: bundle.discountLabel }),
904
+ /* @__PURE__ */ jsx3("p", { className: "lb-bundle__instructions", children: bundle.minQuantity && bundle.maxQuantity ? `Select ${bundle.minQuantity}\u2013${bundle.maxQuantity} items` : bundle.minQuantity ? `Select at least ${bundle.minQuantity} items` : bundle.maxQuantity ? `Select up to ${bundle.maxQuantity} items` : "Select your items" }),
905
+ /* @__PURE__ */ jsx3("div", { className: "lb-bundle__products lb-bundle__products--selectable", children: bundle.products.map((product) => /* @__PURE__ */ jsx3(
906
+ MixMatchProductRow,
907
+ {
908
+ product,
909
+ currency,
910
+ selections,
911
+ onToggle: toggleProduct,
912
+ onUpdateQuantity: updateQuantity
913
+ },
914
+ product.id
915
+ )) }),
916
+ validation.message && /* @__PURE__ */ jsx3("p", { className: "lb-bundle__validation", role: "status", children: validation.message }),
917
+ cartError && /* @__PURE__ */ jsx3("p", { className: "lb-bundle__error", role: "alert", children: cartError }),
918
+ /* @__PURE__ */ jsx3(
600
919
  "button",
601
920
  {
602
921
  className: "lb-bundle__cta",
@@ -610,16 +929,115 @@ function MixMatchBundle(props) {
610
929
  }
611
930
  );
612
931
  }
932
+ function MixMatchProductRow({
933
+ product,
934
+ currency,
935
+ selections,
936
+ onToggle,
937
+ onUpdateQuantity
938
+ }) {
939
+ const variants = product.variants.nodes;
940
+ const optionNames = useMemo5(() => {
941
+ const first = variants[0];
942
+ return first ? first.selectedOptions.map((o) => o.name) : [];
943
+ }, [variants]);
944
+ const showPicker = variants.length > 1 && optionNames.length > 0;
945
+ const { selectedValues, selectedVariant, setOptionValue, optionsFor } = useVariantSelection({ variants, optionNames });
946
+ const displayVariant = selectedVariant ?? variants.find((v) => v.availableForSale) ?? variants[0] ?? null;
947
+ if (!displayVariant) return null;
948
+ const key = `${product.id}:${displayVariant.id}`;
949
+ const selected = selections.get(key);
950
+ const thumbImage = displayVariant.image ?? product.featuredImage;
951
+ const unitPriceText = formatUnitPrice2(
952
+ displayVariant.unitPrice,
953
+ displayVariant.unitPriceMeasurement,
954
+ currency
955
+ );
956
+ return /* @__PURE__ */ jsxs3(
957
+ "div",
958
+ {
959
+ className: `lb-bundle__product lb-bundle__product--selectable ${selected ? "lb-bundle__product--selected" : ""}`,
960
+ children: [
961
+ thumbImage && /* @__PURE__ */ jsx3(
962
+ "img",
963
+ {
964
+ src: thumbImage.url,
965
+ alt: thumbImage.altText ?? product.title,
966
+ className: "lb-bundle__product-image",
967
+ loading: "lazy"
968
+ }
969
+ ),
970
+ /* @__PURE__ */ jsxs3("div", { className: "lb-bundle__product-info", children: [
971
+ /* @__PURE__ */ jsx3("p", { className: "lb-bundle__product-title", children: product.title }),
972
+ /* @__PURE__ */ jsx3("p", { className: "lb-bundle__product-price", children: formatMoney2(displayVariant.price.amount, currency) }),
973
+ unitPriceText && /* @__PURE__ */ jsx3("p", { className: "lb-bundle__product-unit-price", children: unitPriceText }),
974
+ showPicker && !selected && /* @__PURE__ */ jsx3("div", { className: "lb-bundle__product-variant-pickers", children: optionNames.map((optionName, optionIndex) => {
975
+ const dropdownOptions = optionsFor(optionIndex).map((o) => ({
976
+ value: o.value,
977
+ label: o.value,
978
+ disabled: o.disabled
979
+ }));
980
+ return /* @__PURE__ */ jsx3(
981
+ VariantDropdown,
982
+ {
983
+ options: dropdownOptions,
984
+ value: selectedValues[optionIndex] ?? null,
985
+ onChange: (v) => setOptionValue(optionIndex, v),
986
+ ariaLabel: optionName
987
+ },
988
+ optionName
989
+ );
990
+ }) })
991
+ ] }),
992
+ /* @__PURE__ */ jsx3("div", { className: "lb-bundle__product-actions", children: selected ? /* @__PURE__ */ jsxs3("div", { className: "lb-bundle__quantity-control", children: [
993
+ /* @__PURE__ */ jsx3(
994
+ "button",
995
+ {
996
+ "aria-label": `Decrease ${product.title}`,
997
+ onClick: () => onUpdateQuantity(
998
+ product.id,
999
+ displayVariant.id,
1000
+ selected.quantity - 1
1001
+ ),
1002
+ children: "\u2212"
1003
+ }
1004
+ ),
1005
+ /* @__PURE__ */ jsx3("span", { children: selected.quantity }),
1006
+ /* @__PURE__ */ jsx3(
1007
+ "button",
1008
+ {
1009
+ "aria-label": `Increase ${product.title}`,
1010
+ onClick: () => onUpdateQuantity(
1011
+ product.id,
1012
+ displayVariant.id,
1013
+ selected.quantity + 1
1014
+ ),
1015
+ children: "+"
1016
+ }
1017
+ )
1018
+ ] }) : /* @__PURE__ */ jsx3(
1019
+ "button",
1020
+ {
1021
+ className: "lb-bundle__select-btn",
1022
+ onClick: () => onToggle(product.id, displayVariant),
1023
+ disabled: !displayVariant.availableForSale,
1024
+ children: displayVariant.availableForSale ? "Select" : "Sold out"
1025
+ }
1026
+ ) })
1027
+ ]
1028
+ }
1029
+ );
1030
+ }
613
1031
 
614
1032
  // src/components/VolumeBundle.tsx
615
- import { useCallback as useCallback5, useEffect as useEffect6, useState as useState5 } from "react";
1033
+ import { useCallback as useCallback6, useEffect as useEffect8, useMemo as useMemo6, useState as useState7 } from "react";
616
1034
  import {
617
1035
  formatMoney as formatMoney3,
618
1036
  formatUnitPrice as formatUnitPrice3,
619
1037
  calculateTierSavings,
620
1038
  getActiveTier
621
1039
  } from "@lime-bundles/core";
622
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1040
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
623
1041
  function VolumeBundle(props) {
624
1042
  const {
625
1043
  shopDomain,
@@ -643,12 +1061,12 @@ function VolumeBundle(props) {
643
1061
  bundleType: "volume",
644
1062
  enabled: analyticsEnabled !== false
645
1063
  });
646
- const [quantity, setQuantity] = useState5(1);
647
- const [addingToCart, setAddingToCart] = useState5(false);
648
- const [cartError, setCartError] = useState5(null);
1064
+ const [quantity, setQuantity] = useState7(1);
1065
+ const [addingToCart, setAddingToCart] = useState7(false);
1066
+ const [cartError, setCartError] = useState7(null);
649
1067
  const bundle = result.status === "success" && result.bundle.bundleType === "volume" ? result.bundle : null;
650
1068
  const setConfigVarsRef = useWidgetConfigVars(bundle?.widgetConfig);
651
- useEffect6(() => {
1069
+ useEffect8(() => {
652
1070
  if (result.status === "error") {
653
1071
  onError?.(result.error);
654
1072
  return;
@@ -662,18 +1080,35 @@ function VolumeBundle(props) {
662
1080
  }
663
1081
  }, [result, onError]);
664
1082
  const product = bundle?.products[0];
665
- const basePrice = product ? parseFloat(product.priceRange.minVariantPrice.amount) : 0;
1083
+ const variants = useMemo6(() => product?.variants.nodes ?? [], [product]);
1084
+ const optionNames = useMemo6(() => {
1085
+ const first = variants[0];
1086
+ return first ? first.selectedOptions.map((o) => o.name) : [];
1087
+ }, [variants]);
1088
+ const showPicker = variants.length > 1 && optionNames.length > 0;
1089
+ const { selectedValues, selectedVariant, setOptionValue, optionsFor } = useVariantSelection({ variants, optionNames });
1090
+ const displayVariant = selectedVariant ?? variants.find((v) => v.availableForSale) ?? variants[0];
1091
+ const basePrice = displayVariant ? parseFloat(displayVariant.price.amount) : product ? parseFloat(product.priceRange.minVariantPrice.amount) : 0;
666
1092
  const currency = product?.priceRange.minVariantPrice.currencyCode ?? "USD";
667
- const tierSavings = bundle ? calculateTierSavings(
668
- bundle.volumeTiers,
669
- basePrice,
670
- quantity,
671
- bundle.discountConfig.discountType
672
- ) : [];
1093
+ const thumbImage = displayVariant?.image ?? product?.featuredImage ?? null;
1094
+ const unitPriceText = displayVariant ? formatUnitPrice3(
1095
+ displayVariant.unitPrice,
1096
+ displayVariant.unitPriceMeasurement,
1097
+ currency
1098
+ ) : null;
1099
+ const tierSavings = useMemo6(
1100
+ () => bundle ? calculateTierSavings(
1101
+ bundle.volumeTiers,
1102
+ basePrice,
1103
+ quantity,
1104
+ bundle.discountConfig.discountType
1105
+ ) : [],
1106
+ [bundle, basePrice, quantity]
1107
+ );
673
1108
  const activeTier = bundle ? getActiveTier(bundle.volumeTiers, quantity) : null;
674
- const handleAddToCart = useCallback5(async () => {
1109
+ const handleAddToCart = useCallback6(async () => {
675
1110
  if (!bundle || !product) return;
676
- const variant = product.variants.nodes.find((v) => v.availableForSale);
1111
+ const variant = selectedVariant ?? product.variants.nodes.find((v) => v.availableForSale);
677
1112
  if (!variant) return;
678
1113
  const lines = [
679
1114
  {
@@ -710,18 +1145,20 @@ function VolumeBundle(props) {
710
1145
  basePrice,
711
1146
  onAddToCart,
712
1147
  onError,
713
- trackAddToCart
1148
+ trackAddToCart,
1149
+ selectedVariant,
1150
+ tierSavings
714
1151
  ]);
715
1152
  if (result.status === "loading") {
716
- return /* @__PURE__ */ jsxs3("div", { className: `lb-bundle lb-bundle--loading ${className ?? ""}`, children: [
717
- /* @__PURE__ */ jsx3("div", { className: "lb-skeleton lb-skeleton--title" }),
718
- /* @__PURE__ */ jsx3("div", { className: "lb-skeleton lb-skeleton--tiers" })
1153
+ return /* @__PURE__ */ jsxs4("div", { className: `lb-bundle lb-bundle--loading ${className ?? ""}`, children: [
1154
+ /* @__PURE__ */ jsx4("div", { className: "lb-skeleton lb-skeleton--title" }),
1155
+ /* @__PURE__ */ jsx4("div", { className: "lb-skeleton lb-skeleton--tiers" })
719
1156
  ] });
720
1157
  }
721
1158
  if (result.status === "error") return null;
722
1159
  if (!bundle) return null;
723
1160
  if (!product) return null;
724
- return /* @__PURE__ */ jsxs3(
1161
+ return /* @__PURE__ */ jsxs4(
725
1162
  "div",
726
1163
  {
727
1164
  ref: (el) => {
@@ -732,55 +1169,64 @@ function VolumeBundle(props) {
732
1169
  role: "region",
733
1170
  "aria-label": bundle.title,
734
1171
  children: [
735
- /* @__PURE__ */ jsx3("h3", { className: "lb-bundle__title", children: bundle.title }),
736
- /* @__PURE__ */ jsxs3("div", { className: "lb-bundle__product lb-bundle__product--volume", children: [
737
- product.featuredImage && /* @__PURE__ */ jsx3(
1172
+ /* @__PURE__ */ jsx4("h3", { className: "lb-bundle__title", children: bundle.title }),
1173
+ /* @__PURE__ */ jsxs4("div", { className: "lb-bundle__product lb-bundle__product--volume", children: [
1174
+ thumbImage && /* @__PURE__ */ jsx4(
738
1175
  "img",
739
1176
  {
740
- src: product.featuredImage.url,
741
- alt: product.featuredImage.altText ?? product.title,
1177
+ src: thumbImage.url,
1178
+ alt: thumbImage.altText ?? product.title,
742
1179
  className: "lb-bundle__product-image",
743
1180
  loading: "lazy"
744
1181
  }
745
1182
  ),
746
- /* @__PURE__ */ jsxs3("div", { className: "lb-bundle__product-info", children: [
747
- /* @__PURE__ */ jsx3("p", { className: "lb-bundle__product-title", children: product.title }),
748
- /* @__PURE__ */ jsxs3("p", { className: "lb-bundle__product-price", children: [
1183
+ /* @__PURE__ */ jsxs4("div", { className: "lb-bundle__product-info", children: [
1184
+ /* @__PURE__ */ jsx4("p", { className: "lb-bundle__product-title", children: product.title }),
1185
+ /* @__PURE__ */ jsxs4("p", { className: "lb-bundle__product-price", children: [
749
1186
  formatMoney3(basePrice, currency),
750
1187
  " each"
751
1188
  ] }),
752
- (() => {
753
- const variant = product.variants.nodes.find((v) => v.availableForSale) ?? product.variants.nodes[0];
754
- const unitPriceText = variant ? formatUnitPrice3(
755
- variant.unitPrice,
756
- variant.unitPriceMeasurement,
757
- currency
758
- ) : null;
759
- return unitPriceText ? /* @__PURE__ */ jsx3("p", { className: "lb-bundle__product-unit-price", children: unitPriceText }) : null;
760
- })()
1189
+ unitPriceText && /* @__PURE__ */ jsx4("p", { className: "lb-bundle__product-unit-price", children: unitPriceText }),
1190
+ showPicker && /* @__PURE__ */ jsx4("div", { className: "lb-bundle__product-variant-pickers", children: optionNames.map((optionName, optionIndex) => {
1191
+ const dropdownOptions = optionsFor(optionIndex).map((o) => ({
1192
+ value: o.value,
1193
+ label: o.value,
1194
+ disabled: o.disabled
1195
+ }));
1196
+ return /* @__PURE__ */ jsx4(
1197
+ VariantDropdown,
1198
+ {
1199
+ options: dropdownOptions,
1200
+ value: selectedValues[optionIndex] ?? null,
1201
+ onChange: (v) => setOptionValue(optionIndex, v),
1202
+ ariaLabel: optionName
1203
+ },
1204
+ optionName
1205
+ );
1206
+ }) })
761
1207
  ] })
762
1208
  ] }),
763
- /* @__PURE__ */ jsx3(
1209
+ /* @__PURE__ */ jsx4(
764
1210
  "div",
765
1211
  {
766
1212
  className: "lb-bundle__tiers",
767
1213
  role: "table",
768
1214
  "aria-label": "Volume discounts",
769
- children: tierSavings.map((ts) => /* @__PURE__ */ jsxs3(
1215
+ children: tierSavings.map((ts) => /* @__PURE__ */ jsxs4(
770
1216
  "div",
771
1217
  {
772
1218
  className: `lb-bundle__tier ${ts.isActive ? "lb-bundle__tier--active" : ""}`,
773
1219
  role: "row",
774
1220
  children: [
775
- /* @__PURE__ */ jsxs3("span", { className: "lb-bundle__tier-quantity", role: "cell", children: [
1221
+ /* @__PURE__ */ jsxs4("span", { className: "lb-bundle__tier-quantity", role: "cell", children: [
776
1222
  ts.tier.minQuantity,
777
1223
  "+ items"
778
1224
  ] }),
779
- /* @__PURE__ */ jsxs3("span", { className: "lb-bundle__tier-price", role: "cell", children: [
1225
+ /* @__PURE__ */ jsxs4("span", { className: "lb-bundle__tier-price", role: "cell", children: [
780
1226
  formatMoney3(ts.unitPrice, currency),
781
1227
  " each"
782
1228
  ] }),
783
- /* @__PURE__ */ jsxs3("span", { className: "lb-bundle__tier-savings", role: "cell", children: [
1229
+ /* @__PURE__ */ jsxs4("span", { className: "lb-bundle__tier-savings", role: "cell", children: [
784
1230
  "Save ",
785
1231
  ts.savingsPercent.toFixed(0),
786
1232
  "%"
@@ -791,10 +1237,10 @@ function VolumeBundle(props) {
791
1237
  ))
792
1238
  }
793
1239
  ),
794
- /* @__PURE__ */ jsxs3("div", { className: "lb-bundle__quantity-selector", children: [
795
- /* @__PURE__ */ jsx3("label", { htmlFor: `lb-qty-${bundle.id}`, children: "Quantity" }),
796
- /* @__PURE__ */ jsxs3("div", { className: "lb-bundle__quantity-control", children: [
797
- /* @__PURE__ */ jsx3(
1240
+ /* @__PURE__ */ jsxs4("div", { className: "lb-bundle__quantity-selector", children: [
1241
+ /* @__PURE__ */ jsx4("label", { htmlFor: `lb-qty-${bundle.id}`, children: "Quantity" }),
1242
+ /* @__PURE__ */ jsxs4("div", { className: "lb-bundle__quantity-control", children: [
1243
+ /* @__PURE__ */ jsx4(
798
1244
  "button",
799
1245
  {
800
1246
  "aria-label": "Decrease quantity",
@@ -802,7 +1248,7 @@ function VolumeBundle(props) {
802
1248
  children: "\u2212"
803
1249
  }
804
1250
  ),
805
- /* @__PURE__ */ jsx3(
1251
+ /* @__PURE__ */ jsx4(
806
1252
  "input",
807
1253
  {
808
1254
  id: `lb-qty-${bundle.id}`,
@@ -816,7 +1262,7 @@ function VolumeBundle(props) {
816
1262
  className: "lb-bundle__quantity-input"
817
1263
  }
818
1264
  ),
819
- /* @__PURE__ */ jsx3(
1265
+ /* @__PURE__ */ jsx4(
820
1266
  "button",
821
1267
  {
822
1268
  "aria-label": "Increase quantity",
@@ -826,8 +1272,8 @@ function VolumeBundle(props) {
826
1272
  )
827
1273
  ] })
828
1274
  ] }),
829
- cartError && /* @__PURE__ */ jsx3("p", { className: "lb-bundle__error", role: "alert", children: cartError }),
830
- /* @__PURE__ */ jsx3(
1275
+ cartError && /* @__PURE__ */ jsx4("p", { className: "lb-bundle__error", role: "alert", children: cartError }),
1276
+ /* @__PURE__ */ jsx4(
831
1277
  "button",
832
1278
  {
833
1279
  className: "lb-bundle__cta",
@@ -843,7 +1289,7 @@ function VolumeBundle(props) {
843
1289
  }
844
1290
 
845
1291
  // src/hooks/useBundlesForProduct.ts
846
- import { useState as useState6, useEffect as useEffect7 } from "react";
1292
+ import { useState as useState8, useEffect as useEffect9 } from "react";
847
1293
  import {
848
1294
  fetchBundlesForProduct,
849
1295
  injectCustomCss as injectCustomCss2
@@ -854,10 +1300,10 @@ var INITIAL_STATE2 = {
854
1300
  error: null
855
1301
  };
856
1302
  function useBundlesForProduct(options) {
857
- const [state, setState] = useState6(
1303
+ const [state, setState] = useState8(
858
1304
  INITIAL_STATE2
859
1305
  );
860
- useEffect7(() => {
1306
+ useEffect9(() => {
861
1307
  const controller = new AbortController();
862
1308
  setState(INITIAL_STATE2);
863
1309
  fetchBundlesForProduct({
@@ -943,6 +1389,7 @@ export {
943
1389
  MixMatchBundle,
944
1390
  SHOP_CUSTOM_CSS_QUERY2 as SHOP_CUSTOM_CSS_QUERY,
945
1391
  StorefrontApiError,
1392
+ VariantDropdown,
946
1393
  VolumeBundle,
947
1394
  WIDGET_CONFIG_DEFAULTS,
948
1395
  applyABVariantB,
@@ -978,6 +1425,7 @@ export {
978
1425
  useAnalytics,
979
1426
  useBundleData,
980
1427
  useBundlesForProduct,
1428
+ useVariantSelection,
981
1429
  useWidgetConfigVars,
982
1430
  validateQuantity2 as validateQuantity
983
1431
  };