@lime-bundles/widget 2.4.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -20,12 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- LimeBundleElement: () => LimeBundleElement
23
+ LimeBundleElement: () => LimeBundleElement,
24
+ trackInputMode: () => trackInputMode
24
25
  });
25
26
  module.exports = __toCommonJS(index_exports);
26
27
 
27
28
  // src/lime-bundle.ts
28
- var import_core5 = require("@lime-bundles/core");
29
+ var import_core6 = require("@lime-bundles/core");
29
30
 
30
31
  // src/renderers/fixed.ts
31
32
  var import_core4 = require("@lime-bundles/core");
@@ -211,12 +212,11 @@ function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
211
212
  }
212
213
  function buildRowState(bundle, product, productIndex) {
213
214
  const selectedVariantIds = bundle.selectedVariantIds?.[productIndex] ?? null;
214
- const available = product.variants.nodes.filter(
215
- (v) => v.availableForSale
216
- );
217
- const eligibleVariants = selectedVariantIds && selectedVariantIds.length > 0 ? available.filter((v) => selectedVariantIds.includes(v.id)) : available;
218
- const isOos = eligibleVariants.length === 0;
219
- const selected = eligibleVariants[0] ?? null;
215
+ const merchantScoped = selectedVariantIds && selectedVariantIds.length > 0 ? product.variants.nodes.filter((v) => selectedVariantIds.includes(v.id)) : product.variants.nodes;
216
+ const firstInStock = merchantScoped.find((v) => v.availableForSale) ?? null;
217
+ const eligibleVariants = merchantScoped;
218
+ const isOos = !firstInStock;
219
+ const selected = firstInStock ?? merchantScoped[0] ?? null;
220
220
  const qty = selected ? (0, import_core4.resolveBundleQty)(bundle, product.id, selected.id) : 1;
221
221
  return { product, eligibleVariants, selected, qty, isOos };
222
222
  }
@@ -365,33 +365,85 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
365
365
  };
366
366
  applyVariantToRow(state.selected);
367
367
  if (state.eligibleVariants.length > 1) {
368
- const select = document.createElement("select");
369
- select.className = "lb-bundle-variant-select";
370
- select.setAttribute("data-variant-select", "");
371
- select.name = `lb-variant-${state.product.id.replace(/^.*\//, "")}`;
372
- select.setAttribute(
373
- "aria-label",
374
- `Select variant for ${state.product.title}`
368
+ const optionNames = state.eligibleVariants[0].selectedOptions.map(
369
+ (o) => o.name
375
370
  );
376
- state.eligibleVariants.forEach((variant) => {
377
- const opt = document.createElement("option");
378
- opt.value = variant.id;
379
- opt.textContent = variant.title;
380
- if (variant.id === state.selected?.id) opt.selected = true;
381
- select.appendChild(opt);
382
- });
383
- select.addEventListener("change", () => {
384
- const variant = state.eligibleVariants.find(
385
- (v) => v.id === select.value
371
+ const productIdTail = state.product.id.replace(/^.*\//, "");
372
+ const optionSelects = [];
373
+ const resolveVariant = (values) => state.eligibleVariants.find(
374
+ (v) => v.selectedOptions.every((o, i) => o.value === values[i]) && v.selectedOptions.length === values.length
375
+ ) ?? null;
376
+ const syncSelectsToVariant = (variant) => {
377
+ variant.selectedOptions.forEach((o, i) => {
378
+ const sel = optionSelects[i];
379
+ if (sel && sel.value !== o.value) sel.value = o.value;
380
+ });
381
+ };
382
+ const isValueAvailable = (optionIndex, value, selected) => state.eligibleVariants.some((v) => {
383
+ if (!v.availableForSale) return false;
384
+ if (v.selectedOptions[optionIndex]?.value !== value) return false;
385
+ return v.selectedOptions.every(
386
+ (o, i) => i === optionIndex || o.value === selected[i]
386
387
  );
387
- if (!variant) return;
388
+ });
389
+ const recomputeDisabled = (selected) => {
390
+ optionSelects.forEach((sel, i) => {
391
+ Array.from(sel.options).forEach((opt) => {
392
+ opt.disabled = !isValueAvailable(i, opt.value, selected);
393
+ });
394
+ });
395
+ };
396
+ const handleChange = () => {
397
+ const values = optionSelects.map((s) => s.value);
398
+ const variant = resolveVariant(values);
399
+ if (!variant) {
400
+ if (state.selected) {
401
+ syncSelectsToVariant(state.selected);
402
+ recomputeDisabled(state.selected.selectedOptions.map((o) => o.value));
403
+ }
404
+ return;
405
+ }
388
406
  state.selected = variant;
389
407
  state.qty = qtyFor(state.product.id, variant.id);
390
408
  if (qtyBadgeRef) qtyBadgeRef.textContent = String(state.qty);
391
409
  applyVariantToRow(variant);
410
+ recomputeDisabled(variant.selectedOptions.map((o) => o.value));
392
411
  onVariantChange();
412
+ };
413
+ const groupsContainer = el("div", "lb-bundle-variant-option-groups");
414
+ optionNames.forEach((name2, position) => {
415
+ const group = el("div", "lb-bundle-variant-option-group");
416
+ const label = el("span", "lb-bundle-variant-option-label");
417
+ label.textContent = name2;
418
+ group.appendChild(label);
419
+ const select = document.createElement("select");
420
+ select.className = "lb-bundle-variant-select";
421
+ select.setAttribute("data-variant-option", "");
422
+ select.setAttribute("data-option-position", String(position + 1));
423
+ select.name = `lb-variant-${productIdTail}-${position + 1}`;
424
+ select.setAttribute("aria-label", name2);
425
+ const seen = /* @__PURE__ */ new Set();
426
+ state.eligibleVariants.forEach((v) => {
427
+ const value = v.selectedOptions[position]?.value;
428
+ if (!value || seen.has(value)) return;
429
+ seen.add(value);
430
+ const opt = document.createElement("option");
431
+ opt.value = value;
432
+ opt.textContent = value;
433
+ if (state.selected?.selectedOptions[position]?.value === value) {
434
+ opt.selected = true;
435
+ }
436
+ select.appendChild(opt);
437
+ });
438
+ select.addEventListener("change", handleChange);
439
+ optionSelects.push(select);
440
+ group.appendChild(select);
441
+ groupsContainer.appendChild(group);
393
442
  });
394
- info.appendChild(select);
443
+ info.appendChild(groupsContainer);
444
+ if (state.selected) {
445
+ recomputeDisabled(state.selected.selectedOptions.map((o) => o.value));
446
+ }
395
447
  } else if (state.eligibleVariants.length === 1 && state.product.variants.nodes.length > 1) {
396
448
  const badge = el("span", "lb-bundle-variant-badge");
397
449
  badge.textContent = state.eligibleVariants[0].title;
@@ -480,6 +532,7 @@ function computeSale(totalCents, discount, rows) {
480
532
  }
481
533
 
482
534
  // src/renderers/mix-match.ts
535
+ var import_core5 = require("@lime-bundles/core");
483
536
  var PLACEHOLDER_THUMB_SVG2 = `
484
537
  <svg class="lb-bundle-placeholder-icon" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
485
538
  <rect x="4" y="4" width="20" height="20" rx="3"></rect>
@@ -547,9 +600,6 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
547
600
  showSearch: wc.showSearch,
548
601
  onAdd: (product, variant) => addSelection(product, variant),
549
602
  onRemove: (productId, variantId) => removeSelection(productId, variantId),
550
- countFor: (productId, variantId) => selections.filter(
551
- (s) => s.productId === productId && s.variantId === variantId
552
- ).length,
553
603
  isOverMax: () => selections.length >= maxQty
554
604
  });
555
605
  root.appendChild(modal.el);
@@ -559,7 +609,7 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
559
609
  if (cta.disabled) return;
560
610
  const lines = selections.map((s) => ({
561
611
  merchandiseId: s.variantId,
562
- quantity: 1,
612
+ quantity: s.quantity,
563
613
  attributes: [
564
614
  { key: "_lime_bundle_gid", value: bundle.id },
565
615
  { key: "_lime_bundle_type", value: bundle.bundleType }
@@ -593,7 +643,8 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
593
643
  firstVariant.unitPrice,
594
644
  firstVariant.unitPriceMeasurement,
595
645
  currency
596
- )
646
+ ),
647
+ quantity: (0, import_core5.resolveBundleQty)(bundle, firstEligible.product.id, firstVariant.id)
597
648
  });
598
649
  }
599
650
  afterMutation();
@@ -611,7 +662,8 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
611
662
  variant.unitPrice,
612
663
  variant.unitPriceMeasurement,
613
664
  currency
614
- )
665
+ ),
666
+ quantity: (0, import_core5.resolveBundleQty)(bundle, product.id, variant.id)
615
667
  });
616
668
  afterMutation();
617
669
  }
@@ -778,6 +830,9 @@ function renderFilledSlot(selection, index, currency, onRemove) {
778
830
  } else {
779
831
  thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
780
832
  }
833
+ const qtyBadge = el("span", "lb-bundle-qty-badge");
834
+ qtyBadge.textContent = String(selection.quantity);
835
+ thumb.appendChild(qtyBadge);
781
836
  slot.appendChild(thumb);
782
837
  const info = el("div", "lb-mix-match__filled-info");
783
838
  const title = el("span", "lb-mix-match__filled-title");
@@ -788,14 +843,16 @@ function renderFilledSlot(selection, index, currency, onRemove) {
788
843
  variant.textContent = selection.variantTitle;
789
844
  info.appendChild(variant);
790
845
  }
846
+ const linePrice = selection.priceCents * selection.quantity;
847
+ const lineCompare = selection.compareCents !== null ? selection.compareCents * selection.quantity : null;
791
848
  const priceWrap = el("span", "lb-mix-match__filled-price");
792
- if (selection.compareCents && selection.compareCents > selection.priceCents) {
849
+ if (lineCompare !== null && lineCompare > linePrice) {
793
850
  const compare = el("span", "lb-mix-match__filled-compare");
794
- compare.textContent = (0, import_core.formatCents)(selection.compareCents, currency);
851
+ compare.textContent = (0, import_core.formatCents)(lineCompare, currency);
795
852
  priceWrap.appendChild(compare);
796
853
  }
797
854
  const priceEl = document.createElement("span");
798
- priceEl.textContent = (0, import_core.formatCents)(selection.priceCents, currency);
855
+ priceEl.textContent = (0, import_core.formatCents)(linePrice, currency);
799
856
  priceWrap.appendChild(priceEl);
800
857
  info.appendChild(priceWrap);
801
858
  if (selection.unitPriceLabel) {
@@ -836,7 +893,10 @@ function renderPricingSection(showCompareAtPrice) {
836
893
  return;
837
894
  }
838
895
  wrap.style.display = "";
839
- const totalCents = selections.reduce((s, sel) => s + sel.priceCents, 0);
896
+ const totalCents = selections.reduce(
897
+ (s, sel) => s + sel.priceCents * sel.quantity,
898
+ 0
899
+ );
840
900
  const saleCents = (0, import_core.computeBundleSaleCents)(totalCents, bundle.discountConfig);
841
901
  if (showCompareAtPrice && totalCents > saleCents) {
842
902
  compare.textContent = (0, import_core.formatCents)(totalCents, currency);
@@ -863,7 +923,10 @@ function renderSavingsBar2() {
863
923
  wrap.style.display = "none";
864
924
  return;
865
925
  }
866
- const totalCents = selections.reduce((s, sel) => s + sel.priceCents, 0);
926
+ const totalCents = selections.reduce(
927
+ (s, sel) => s + sel.priceCents * sel.quantity,
928
+ 0
929
+ );
867
930
  const saleCents = (0, import_core.computeBundleSaleCents)(totalCents, bundle.discountConfig);
868
931
  const savings = Math.max(0, totalCents - saleCents);
869
932
  if (savings <= 0) {
@@ -957,8 +1020,8 @@ function renderModal(bundle, eligible, currency, handlers) {
957
1020
  rowsBuilt = true;
958
1021
  list.innerHTML = "";
959
1022
  eligible.forEach((ep) => {
960
- const availableVariants = ep.variants.filter((v) => v.availableForSale);
961
- const firstAvailVariant = availableVariants[0] ?? ep.firstAvailableVariant ?? ep.variants[0];
1023
+ const availableVariants = ep.variants;
1024
+ const firstAvailVariant = ep.variants.find((v) => v.availableForSale) ?? ep.firstAvailableVariant ?? ep.variants[0];
962
1025
  if (!firstAvailVariant) return;
963
1026
  let currentVariant = firstAvailVariant;
964
1027
  const productEl = el(
@@ -983,7 +1046,9 @@ function renderModal(bundle, eligible, currency, handlers) {
983
1046
  thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
984
1047
  }
985
1048
  const countBadge = el("span", "lb-bundle-qty-badge");
986
- countBadge.style.display = "none";
1049
+ countBadge.textContent = String(
1050
+ (0, import_core5.resolveBundleQty)(bundle, ep.product.id, currentVariant.id)
1051
+ );
987
1052
  thumb.appendChild(countBadge);
988
1053
  productEl.appendChild(thumb);
989
1054
  const info = el("div", "lb-mix-match__modal-product-info");
@@ -992,7 +1057,7 @@ function renderModal(bundle, eligible, currency, handlers) {
992
1057
  info.appendChild(title);
993
1058
  const price = el("p", "lb-mix-match__modal-product-price");
994
1059
  price.textContent = (0, import_core.formatCents)(
995
- (0, import_core.parseCents)(currentVariant.price.amount),
1060
+ (0, import_core.parseCents)(currentVariant.price.amount) * (0, import_core5.resolveBundleQty)(bundle, ep.product.id, currentVariant.id),
996
1061
  currency
997
1062
  );
998
1063
  info.appendChild(price);
@@ -1019,28 +1084,53 @@ function renderModal(bundle, eligible, currency, handlers) {
1019
1084
  }
1020
1085
  };
1021
1086
  if (availableVariants.length > 1) {
1022
- const select = document.createElement("select");
1023
- select.className = "lb-mix-match__variant-select";
1024
- select.setAttribute("data-variant-select", "");
1025
- select.name = `lb-variant-${ep.product.id.replace(/^.*\//, "")}`;
1026
- select.setAttribute(
1027
- "aria-label",
1028
- `Select variant for ${ep.product.title}`
1087
+ const optionNames = availableVariants[0].selectedOptions.map(
1088
+ (o) => o.name
1029
1089
  );
1030
- availableVariants.forEach((v) => {
1031
- const opt = document.createElement("option");
1032
- opt.value = v.id;
1033
- opt.textContent = v.title;
1034
- if (v.id === firstAvailVariant.id) opt.selected = true;
1035
- select.appendChild(opt);
1090
+ const productIdTail = ep.product.id.replace(/^.*\//, "");
1091
+ const optionSelects = [];
1092
+ const resolveVariant = (values) => availableVariants.find(
1093
+ (v) => v.selectedOptions.length === values.length && v.selectedOptions.every((o, i) => o.value === values[i])
1094
+ ) ?? null;
1095
+ const syncSelectsToVariant = (v) => {
1096
+ v.selectedOptions.forEach((o, i) => {
1097
+ const sel = optionSelects[i];
1098
+ if (sel && sel.value !== o.value) sel.value = o.value;
1099
+ });
1100
+ };
1101
+ const isValueAvailable = (optionIndex, value, selected) => availableVariants.some((v) => {
1102
+ if (!v.availableForSale) return false;
1103
+ if (v.selectedOptions[optionIndex]?.value !== value) return false;
1104
+ return v.selectedOptions.every(
1105
+ (o, i) => i === optionIndex || o.value === selected[i]
1106
+ );
1036
1107
  });
1037
- select.addEventListener("change", () => {
1038
- const next = availableVariants.find((v) => v.id === select.value);
1039
- if (!next) return;
1108
+ const recomputeDisabled = (selected) => {
1109
+ optionSelects.forEach((sel, i) => {
1110
+ Array.from(sel.options).forEach((opt) => {
1111
+ opt.disabled = !isValueAvailable(i, opt.value, selected);
1112
+ });
1113
+ });
1114
+ };
1115
+ const handleChange = () => {
1116
+ const values = optionSelects.map((s) => s.value);
1117
+ const next = resolveVariant(values);
1118
+ if (!next) {
1119
+ syncSelectsToVariant(currentVariant);
1120
+ recomputeDisabled(
1121
+ currentVariant.selectedOptions.map((o) => o.value)
1122
+ );
1123
+ return;
1124
+ }
1040
1125
  currentVariant = next;
1041
1126
  row.variant = next;
1127
+ const nextQty = (0, import_core5.resolveBundleQty)(
1128
+ bundle,
1129
+ ep.product.id,
1130
+ currentVariant.id
1131
+ );
1042
1132
  price.textContent = (0, import_core.formatCents)(
1043
- (0, import_core.parseCents)(currentVariant.price.amount),
1133
+ (0, import_core.parseCents)(currentVariant.price.amount) * nextQty,
1044
1134
  currency
1045
1135
  );
1046
1136
  const nextUnitText = (0, import_core.formatUnitPrice)(
@@ -1055,9 +1145,43 @@ function renderModal(bundle, eligible, currency, handlers) {
1055
1145
  unitPrice.textContent = "";
1056
1146
  unitPrice.hidden = true;
1057
1147
  }
1148
+ recomputeDisabled(next.selectedOptions.map((o) => o.value));
1058
1149
  rowUpdateCount();
1150
+ };
1151
+ const groupsContainer = el("div", "lb-bundle-variant-option-groups");
1152
+ optionNames.forEach((name, position) => {
1153
+ const group = el("div", "lb-bundle-variant-option-group");
1154
+ const label = el("span", "lb-bundle-variant-option-label");
1155
+ label.textContent = name;
1156
+ group.appendChild(label);
1157
+ const select = document.createElement("select");
1158
+ select.className = "lb-mix-match__variant-select";
1159
+ select.setAttribute("data-variant-option", "");
1160
+ select.setAttribute("data-option-position", String(position + 1));
1161
+ select.name = `lb-variant-${productIdTail}-${position + 1}`;
1162
+ select.setAttribute("aria-label", name);
1163
+ const seen = /* @__PURE__ */ new Set();
1164
+ availableVariants.forEach((v) => {
1165
+ const value = v.selectedOptions[position]?.value;
1166
+ if (!value || seen.has(value)) return;
1167
+ seen.add(value);
1168
+ const opt = document.createElement("option");
1169
+ opt.value = value;
1170
+ opt.textContent = value;
1171
+ if (firstAvailVariant.selectedOptions[position]?.value === value) {
1172
+ opt.selected = true;
1173
+ }
1174
+ select.appendChild(opt);
1175
+ });
1176
+ select.addEventListener("change", handleChange);
1177
+ optionSelects.push(select);
1178
+ group.appendChild(select);
1179
+ groupsContainer.appendChild(group);
1059
1180
  });
1060
- info.appendChild(select);
1181
+ info.appendChild(groupsContainer);
1182
+ recomputeDisabled(
1183
+ firstAvailVariant.selectedOptions.map((o) => o.value)
1184
+ );
1061
1185
  } else if (availableVariants.length === 1 && firstAvailVariant.title !== "Default Title") {
1062
1186
  const variantLabel = el("span", "lb-mix-match__filled-variant");
1063
1187
  variantLabel.textContent = firstAvailVariant.title;
@@ -1070,13 +1194,9 @@ function renderModal(bundle, eligible, currency, handlers) {
1070
1194
  }
1071
1195
  productEl.appendChild(info);
1072
1196
  const rowUpdateCount = () => {
1073
- const count = handlers.countFor(ep.product.id, currentVariant.id);
1074
- if (count > 0) {
1075
- countBadge.textContent = String(count);
1076
- countBadge.style.display = "";
1077
- } else {
1078
- countBadge.style.display = "none";
1079
- }
1197
+ countBadge.textContent = String(
1198
+ (0, import_core5.resolveBundleQty)(bundle, ep.product.id, currentVariant.id)
1199
+ );
1080
1200
  };
1081
1201
  if (!ep.isOos) {
1082
1202
  const addBtn = document.createElement("button");
@@ -1086,6 +1206,7 @@ function renderModal(bundle, eligible, currency, handlers) {
1086
1206
  addBtn.addEventListener("click", () => {
1087
1207
  if (handlers.isOverMax()) return;
1088
1208
  handlers.onAdd(ep.product, currentVariant);
1209
+ close();
1089
1210
  });
1090
1211
  productEl.appendChild(addBtn);
1091
1212
  }
@@ -1489,7 +1610,62 @@ function clamp(n, min, max) {
1489
1610
  }
1490
1611
 
1491
1612
  // src/lime-bundle.ts
1492
- var import_core6 = require("@lime-bundles/core");
1613
+ var import_core7 = require("@lime-bundles/core");
1614
+
1615
+ // src/utils/input-mode.ts
1616
+ var NAV_KEYS = /* @__PURE__ */ new Set([
1617
+ "Tab",
1618
+ "ArrowUp",
1619
+ "ArrowDown",
1620
+ "ArrowLeft",
1621
+ "ArrowRight",
1622
+ "Home",
1623
+ "End",
1624
+ "PageUp",
1625
+ "PageDown",
1626
+ "Enter",
1627
+ " ",
1628
+ "Escape"
1629
+ ]);
1630
+ var targets = /* @__PURE__ */ new Set();
1631
+ var listenersAttached = false;
1632
+ function setAll(on) {
1633
+ const off = on === "using-mouse" ? "using-keyboard" : "using-mouse";
1634
+ for (const el2 of targets) {
1635
+ el2.classList.add(on);
1636
+ el2.classList.remove(off);
1637
+ }
1638
+ }
1639
+ function onKeyDown(e) {
1640
+ if (NAV_KEYS.has(e.key)) setAll("using-keyboard");
1641
+ }
1642
+ function onPointerDown() {
1643
+ setAll("using-mouse");
1644
+ }
1645
+ function attachListeners() {
1646
+ if (listenersAttached) return;
1647
+ listenersAttached = true;
1648
+ document.addEventListener("keydown", onKeyDown, true);
1649
+ document.addEventListener("pointerdown", onPointerDown, true);
1650
+ }
1651
+ function detachListeners() {
1652
+ if (!listenersAttached) return;
1653
+ listenersAttached = false;
1654
+ document.removeEventListener("keydown", onKeyDown, true);
1655
+ document.removeEventListener("pointerdown", onPointerDown, true);
1656
+ }
1657
+ function trackInputMode(target) {
1658
+ target.classList.add("using-mouse");
1659
+ target.classList.remove("using-keyboard");
1660
+ targets.add(target);
1661
+ attachListeners();
1662
+ return () => {
1663
+ targets.delete(target);
1664
+ target.classList.remove("using-mouse");
1665
+ target.classList.remove("using-keyboard");
1666
+ if (targets.size === 0) detachListeners();
1667
+ };
1668
+ }
1493
1669
 
1494
1670
  // src/styles/bundle-css.ts
1495
1671
  var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle widget types */
@@ -1775,6 +1951,56 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1775
1951
  margin-top: 8px;
1776
1952
  }
1777
1953
 
1954
+ /* Per-option variant pickers. Each option's label + select sit inside a
1955
+ .lb-bundle-variant-option-group (flex column, 2px gap between label and
1956
+ select); the groups stack inside a .lb-bundle-variant-option-groups
1957
+ parent (flex column, 12px gap between groups). The parent owns the top
1958
+ offset from the preceding unit-price line, so individual labels and
1959
+ selects don't carry their own vertical margins. */
1960
+ .lb-bundle-variant-option-groups {
1961
+ display: flex;
1962
+ flex-direction: column;
1963
+ gap: 12px;
1964
+ margin-top: 8px;
1965
+ }
1966
+
1967
+ .lb-bundle-variant-option-group {
1968
+ display: flex;
1969
+ flex-direction: column;
1970
+ gap: 2px;
1971
+ }
1972
+
1973
+ .lb-bundle-variant-option-label {
1974
+ display: block;
1975
+ margin: 0;
1976
+ font-size: 12px;
1977
+ line-height: 16px;
1978
+ font-weight: 600;
1979
+ letter-spacing: 0.05em;
1980
+ text-transform: uppercase;
1981
+ color: color-mix(in srgb, var(--lb-text) 60%, transparent);
1982
+ }
1983
+
1984
+ .lb-mix-match__modal-product-info .lb-bundle-variant-option-groups {
1985
+ margin-top: 4px;
1986
+ }
1987
+
1988
+ /* Focus-ring suppression for mouse users. A small JS helper \u2014 see
1989
+ packages/widget/src/utils/input-mode.ts and bundle-widget.js \u2014 toggles
1990
+ .using-mouse / .using-keyboard on the widget root (or html in the Liquid
1991
+ path) based on the customer's current input device. Default is mouse, so
1992
+ click-to-focus doesn't leave a keyboard-style ring. The modal overlay
1993
+ gets its own selector because the Liquid path reparents it to body,
1994
+ outside the widget root. */
1995
+ .using-mouse .lb-bundle-widget :focus,
1996
+ .using-mouse .lb-bundle-widget :focus-visible,
1997
+ .using-mouse .lb-mix-match__modal-overlay :focus,
1998
+ .using-mouse .lb-mix-match__modal-overlay :focus-visible {
1999
+ outline: none;
2000
+ outline-offset: 0;
2001
+ box-shadow: none;
2002
+ }
2003
+
1778
2004
  .lb-bundle-quantity {
1779
2005
  font-size: 12px;
1780
2006
  line-height: 16px;
@@ -2032,9 +2258,10 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
2032
2258
  border: none;
2033
2259
  }
2034
2260
 
2035
- /* Variant picker select \u2014 styled to match the variant badge aesthetic */
2261
+ /* Variant picker select \u2014 styled to match the variant badge aesthetic.
2262
+ Sits inside .lb-bundle-variant-option-group so vertical spacing is owned
2263
+ by the group/groups flex gap, not the select itself. */
2036
2264
  .lb-bundle-variant-select {
2037
- margin-top: 8px;
2038
2265
  display: inline-block;
2039
2266
  border: var(--lb-variant-border-width) solid var(--lb-variant-border-color);
2040
2267
  border-radius: var(--lb-variant-radius);
@@ -2051,7 +2278,8 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
2051
2278
  background-repeat: no-repeat;
2052
2279
  background-position: right 8px center;
2053
2280
  background-size: 12px;
2054
- max-width: 100%;
2281
+ width: 50%;
2282
+ max-width: 50%;
2055
2283
  }
2056
2284
 
2057
2285
  .lb-bundle-variant-select:focus-visible {
@@ -2458,7 +2686,6 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
2458
2686
 
2459
2687
  .lb-mix-match__variant-select {
2460
2688
  font-size: 12px;
2461
- margin: 4px 0 0;
2462
2689
  padding: 4px 24px 4px 8px;
2463
2690
  border: var(--lb-picker-variant-border-width) solid var(--lb-picker-variant-border-color);
2464
2691
  border-radius: var(--lb-picker-variant-radius);
@@ -2471,7 +2698,8 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
2471
2698
  font-family: inherit;
2472
2699
  min-height: 32px;
2473
2700
  cursor: pointer;
2474
- max-width: 120px;
2701
+ width: 50%;
2702
+ max-width: 50%;
2475
2703
  appearance: none;
2476
2704
  -webkit-appearance: none;
2477
2705
  }
@@ -2555,6 +2783,11 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
2555
2783
  .lb-mix-match__modal-overlay--open .lb-mix-match__modal {
2556
2784
  transform: translateY(0);
2557
2785
  }
2786
+
2787
+ .lb-mix-match__variant-select {
2788
+ width: 80%;
2789
+ max-width: 80%;
2790
+ }
2558
2791
  }
2559
2792
 
2560
2793
  /* === Reduced Motion === */
@@ -2874,7 +3107,7 @@ var LimeBundleElement = class extends HTMLElement {
2874
3107
  const controller = new AbortController();
2875
3108
  this.abortController = controller;
2876
3109
  this.renderLoading();
2877
- const client = (0, import_core5.createStorefrontClient)({
3110
+ const client = (0, import_core6.createStorefrontClient)({
2878
3111
  shopDomain: this.shopDomain,
2879
3112
  accessToken: this.storefrontToken
2880
3113
  });
@@ -2899,7 +3132,7 @@ var LimeBundleElement = class extends HTMLElement {
2899
3132
  handle
2900
3133
  );
2901
3134
  }
2902
- const cssPromise = client.query(import_core5.SHOP_CUSTOM_CSS_QUERY, void 0, {
3135
+ const cssPromise = client.query(import_core6.SHOP_CUSTOM_CSS_QUERY, void 0, {
2903
3136
  signal: controller.signal
2904
3137
  }).catch(() => null);
2905
3138
  await bundlePromise;
@@ -2911,8 +3144,8 @@ var LimeBundleElement = class extends HTMLElement {
2911
3144
  }
2912
3145
  const css = await cssPromise;
2913
3146
  if (css?.shop?.metafield?.value) {
2914
- (0, import_core5.injectCustomCss)(this.shopDomain, css.shop.metafield.value);
2915
- const sanitized = (0, import_core5.sanitizeCustomCss)(css.shop.metafield.value);
3147
+ (0, import_core6.injectCustomCss)(this.shopDomain, css.shop.metafield.value);
3148
+ const sanitized = (0, import_core6.sanitizeCustomCss)(css.shop.metafield.value);
2916
3149
  if (sanitized.ok) this.shopCustomCss = sanitized.css;
2917
3150
  }
2918
3151
  await this.applyABVariants();
@@ -2940,14 +3173,14 @@ var LimeBundleElement = class extends HTMLElement {
2940
3173
  this.bundles.map(async (bundle) => {
2941
3174
  if (!bundle.abTestId || !bundle.abVariantB) return bundle;
2942
3175
  try {
2943
- const assignment = await (0, import_core5.getABTestAssignment)(
3176
+ const assignment = await (0, import_core6.getABTestAssignment)(
2944
3177
  this.appUrl,
2945
3178
  this.shopDomain,
2946
3179
  bundle.abTestId,
2947
3180
  bundle.id
2948
3181
  );
2949
3182
  if (assignment?.variant === "B") {
2950
- return (0, import_core5.applyABVariantB)(bundle);
3183
+ return (0, import_core6.applyABVariantB)(bundle);
2951
3184
  }
2952
3185
  } catch (err) {
2953
3186
  console.warn(
@@ -2962,7 +3195,7 @@ var LimeBundleElement = class extends HTMLElement {
2962
3195
  }
2963
3196
  async fetchSingleBundle(client, signal) {
2964
3197
  const data = await client.query(
2965
- import_core5.BUNDLE_METAOBJECT_QUERY,
3198
+ import_core6.BUNDLE_METAOBJECT_QUERY,
2966
3199
  { id: this.bundleGid },
2967
3200
  { signal }
2968
3201
  );
@@ -2970,7 +3203,7 @@ var LimeBundleElement = class extends HTMLElement {
2970
3203
  this.bundles = [];
2971
3204
  return;
2972
3205
  }
2973
- const parsed = (0, import_core5.parseMetaobjectBundle)(
3206
+ const parsed = (0, import_core6.parseMetaobjectBundle)(
2974
3207
  data.metaobject.id,
2975
3208
  data.metaobject.fields
2976
3209
  );
@@ -2978,7 +3211,7 @@ var LimeBundleElement = class extends HTMLElement {
2978
3211
  }
2979
3212
  async fetchProductBundles(client, signal, productHandle) {
2980
3213
  const data = await client.query(
2981
- import_core5.BUNDLES_FOR_PRODUCT_QUERY,
3214
+ import_core6.BUNDLES_FOR_PRODUCT_QUERY,
2982
3215
  { handle: productHandle },
2983
3216
  { signal }
2984
3217
  );
@@ -2989,7 +3222,7 @@ var LimeBundleElement = class extends HTMLElement {
2989
3222
  const refs = data.product.metafield?.references?.nodes ?? [];
2990
3223
  const bundles = [];
2991
3224
  for (const ref of refs) {
2992
- const parsed = (0, import_core5.parseMetaobjectBundle)(ref.id, ref.fields);
3225
+ const parsed = (0, import_core6.parseMetaobjectBundle)(ref.id, ref.fields);
2993
3226
  if (parsed) bundles.push(parsed);
2994
3227
  }
2995
3228
  this.bundles = bundles;
@@ -3022,7 +3255,7 @@ var LimeBundleElement = class extends HTMLElement {
3022
3255
  */
3023
3256
  async defaultAddToCart(lines) {
3024
3257
  if (typeof window === "undefined") return;
3025
- const client = (0, import_core5.createStorefrontClient)({
3258
+ const client = (0, import_core6.createStorefrontClient)({
3026
3259
  shopDomain: this.shopDomain,
3027
3260
  accessToken: this.storefrontToken
3028
3261
  });
@@ -3033,7 +3266,7 @@ var LimeBundleElement = class extends HTMLElement {
3033
3266
  let checkoutUrl = null;
3034
3267
  if (existingCartId) {
3035
3268
  const res = await client.query(
3036
- import_core5.CART_LINES_ADD_MUTATION,
3269
+ import_core6.CART_LINES_ADD_MUTATION,
3037
3270
  { cartId: existingCartId, lines }
3038
3271
  );
3039
3272
  const payload = res.cartLinesAdd;
@@ -3045,7 +3278,7 @@ var LimeBundleElement = class extends HTMLElement {
3045
3278
  }
3046
3279
  if (!checkoutUrl) {
3047
3280
  const res = await client.query(
3048
- import_core5.CART_CREATE_MUTATION,
3281
+ import_core6.CART_CREATE_MUTATION,
3049
3282
  { input: { lines } }
3050
3283
  );
3051
3284
  const payload = res.cartCreate;
@@ -3091,7 +3324,7 @@ var LimeBundleElement = class extends HTMLElement {
3091
3324
  const price = variant ? parseFloat(variant.price.amount) : 0;
3092
3325
  return sum + price * line.quantity;
3093
3326
  }, 0);
3094
- (0, import_core5.reportAddToCart)(
3327
+ (0, import_core6.reportAddToCart)(
3095
3328
  { shopDomain: this.shopDomain, appUrl: this.appUrl },
3096
3329
  {
3097
3330
  bundleGid: bundle.id,
@@ -3127,7 +3360,8 @@ var LimeBundleElement = class extends HTMLElement {
3127
3360
  container.setAttribute("aria-label", bundle.title);
3128
3361
  container.setAttribute("data-bundle-type", bundle.bundleType);
3129
3362
  container.setAttribute("data-bundle-gid", bundle.id);
3130
- (0, import_core6.applyWidgetConfigVars)(container, bundle.widgetConfig);
3363
+ (0, import_core7.applyWidgetConfigVars)(container, bundle.widgetConfig);
3364
+ this.renderCleanups.push(trackInputMode(container));
3131
3365
  const dispatch = (lines) => this.handleAddToCart(bundle, lines);
3132
3366
  const registerCleanup = (fn) => this.renderCleanups.push(fn);
3133
3367
  switch (bundle.bundleType) {
@@ -3162,8 +3396,8 @@ var LimeBundleElement = class extends HTMLElement {
3162
3396
  }
3163
3397
  setupImpressionFor(bundle, element) {
3164
3398
  if (!this.analyticsEnabled || !this.appUrl) return;
3165
- const cleanup = (0, import_core5.observeImpression)(element, () => {
3166
- (0, import_core5.reportImpression)(
3399
+ const cleanup = (0, import_core6.observeImpression)(element, () => {
3400
+ (0, import_core6.reportImpression)(
3167
3401
  { shopDomain: this.shopDomain, appUrl: this.appUrl },
3168
3402
  {
3169
3403
  bundleGid: bundle.id,
@@ -3230,6 +3464,7 @@ if (typeof customElements !== "undefined" && !customElements.get("lime-bundle"))
3230
3464
  }
3231
3465
  // Annotate the CommonJS export names for ESM import in node:
3232
3466
  0 && (module.exports = {
3233
- LimeBundleElement
3467
+ LimeBundleElement,
3468
+ trackInputMode
3234
3469
  });
3235
3470
  //# sourceMappingURL=index.cjs.map