@lime-bundles/widget 3.1.0 → 3.2.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.js CHANGED
@@ -188,7 +188,7 @@ function bindDropdown(select) {
188
188
  if (rect.width === 0) return false;
189
189
  const visibleCount = Math.min(optionEls.length || 1, MAX_VISIBLE_ITEMS);
190
190
  const desiredHeight = visibleCount * ITEM_HEIGHT_PX + LIST_PAD_Y;
191
- const scrollable = listbox.hasAttribute("data-lb-dropdown-portal") ? null : findScrollableAncestor(trigger);
191
+ const scrollable = findScrollableAncestor(trigger);
192
192
  const clip = scrollable ? (() => {
193
193
  const r = scrollable.getBoundingClientRect();
194
194
  return { top: r.top, bottom: r.bottom };
@@ -1051,16 +1051,6 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
1051
1051
  root.appendChild(pricingSection.el);
1052
1052
  const savingsBar = wc.savingsBar.visible ? renderSavingsBar2() : null;
1053
1053
  if (savingsBar) root.appendChild(savingsBar.el);
1054
- const placeholder = el("div", "lb-mix-match__price-placeholder", {
1055
- "data-price-placeholder": ""
1056
- });
1057
- const placeholderText = el(
1058
- "span",
1059
- "lb-mix-match__price-placeholder-text"
1060
- );
1061
- placeholderText.textContent = `Select ${requiredQty} items to see price`;
1062
- placeholder.appendChild(placeholderText);
1063
- root.appendChild(placeholder);
1064
1054
  const modal = renderModal(bundle, eligible, currency, {
1065
1055
  showSearch: wc.showSearch,
1066
1056
  showQtySelector,
@@ -1125,7 +1115,6 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
1125
1115
  progress.update(selections.length);
1126
1116
  pricingSection.update(selections, bundle, currency);
1127
1117
  if (savingsBar) savingsBar.update(selections, bundle, currency);
1128
- placeholder.style.display = selections.length === 0 ? "" : "none";
1129
1118
  modal.refreshCounts();
1130
1119
  updateCta();
1131
1120
  }
@@ -1565,14 +1554,12 @@ function renderModal(bundle, eligible, currency, handlers) {
1565
1554
  return { min: rule.min, max, cap };
1566
1555
  };
1567
1556
  let stepper = null;
1557
+ let qtyGroup = null;
1568
1558
  if (!ep.isOos && handlers.showQtySelector) {
1569
- const qtyGroup = el(
1559
+ qtyGroup = el(
1570
1560
  "div",
1571
1561
  "lb-bundle-variant-option-group lb-mix-match__qty-stepper-group"
1572
1562
  );
1573
- const qtyLabel = el("span", "lb-bundle-variant-option-label");
1574
- qtyLabel.textContent = "Quantity";
1575
- qtyGroup.appendChild(qtyLabel);
1576
1563
  stepper = renderQtyStepper({
1577
1564
  initial: rule.min,
1578
1565
  getBounds: () => {
@@ -1581,7 +1568,6 @@ function renderModal(bundle, eligible, currency, handlers) {
1581
1568
  }
1582
1569
  });
1583
1570
  qtyGroup.appendChild(stepper.el);
1584
- info.appendChild(qtyGroup);
1585
1571
  }
1586
1572
  const row = {
1587
1573
  el: productEl,
@@ -1717,10 +1703,22 @@ function renderModal(bundle, eligible, currency, handlers) {
1717
1703
  } else {
1718
1704
  countBadge.hidden = true;
1719
1705
  }
1720
- addBtn.disabled = handlers.isComplete() || cap < rule.min;
1706
+ const productInBundle = handlers.selections.some(
1707
+ (s) => s.productId === ep.product.id
1708
+ );
1709
+ if (productInBundle) {
1710
+ addBtn.disabled = true;
1711
+ addBtn.textContent = "Added";
1712
+ productEl.classList.add("lb-mix-match__modal-product--in-bundle");
1713
+ } else {
1714
+ addBtn.textContent = "Add";
1715
+ productEl.classList.remove("lb-mix-match__modal-product--in-bundle");
1716
+ addBtn.disabled = handlers.isComplete() || cap < rule.min;
1717
+ }
1721
1718
  };
1722
1719
  if (!ep.isOos) {
1723
1720
  const actions = el("div", "lb-mix-match__modal-product-actions");
1721
+ if (qtyGroup) actions.appendChild(qtyGroup);
1724
1722
  addBtn = document.createElement("button");
1725
1723
  addBtn.type = "button";
1726
1724
  addBtn.className = "lb-mix-match__modal-add";
@@ -1734,7 +1732,7 @@ function renderModal(bundle, eligible, currency, handlers) {
1734
1732
  close();
1735
1733
  });
1736
1734
  actions.appendChild(addBtn);
1737
- productEl.appendChild(actions);
1735
+ info.appendChild(actions);
1738
1736
  }
1739
1737
  row.refreshFromSelections = () => {
1740
1738
  if (stepper) stepper.refresh();
@@ -2574,7 +2572,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
2574
2572
  .lb-bundle-variant-option-groups {
2575
2573
  display: flex;
2576
2574
  flex-direction: column;
2577
- gap: 12px;
2575
+ gap: 8px;
2578
2576
  margin-top: 8px;
2579
2577
  }
2580
2578
 
@@ -2596,7 +2594,23 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
2596
2594
  }
2597
2595
 
2598
2596
  .lb-mix-match__modal-product-info .lb-bundle-variant-option-groups {
2599
- margin-top: 4px;
2597
+ margin-top: 8px;
2598
+ }
2599
+
2600
+ /* Desktop: lay variant option groups in a wrapping row inside the modal
2601
+ (Size + Color side-by-side). Mobile keeps the column stack from the
2602
+ base rule above. The 768px breakpoint mirrors bundle-mix-match.css. */
2603
+ @media (min-width: 768px) {
2604
+ .lb-mix-match__modal-product-info .lb-bundle-variant-option-groups {
2605
+ flex-direction: row;
2606
+ flex-wrap: wrap;
2607
+ }
2608
+ /* Direct children only \u2014 the qty-stepper-group is also a
2609
+ .lb-bundle-variant-option-group but lives in the actions wrapper
2610
+ and must keep its natural width. */
2611
+ .lb-mix-match__modal-product-info .lb-bundle-variant-option-groups > .lb-bundle-variant-option-group {
2612
+ flex: 1;
2613
+ }
2600
2614
  }
2601
2615
 
2602
2616
  /* Focus-ring suppression for mouse users. A small JS helper \u2014 see
@@ -3125,17 +3139,6 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3125
3139
  outline-offset: 2px;
3126
3140
  }
3127
3141
 
3128
- /* Price placeholder */
3129
- .lb-mix-match__price-placeholder {
3130
- padding: 4px 0 16px;
3131
- text-align: center;
3132
- }
3133
-
3134
- .lb-mix-match__price-placeholder-text {
3135
- font-size: 16px;
3136
- color: var(--lb-text);
3137
- }
3138
-
3139
3142
  /* === Modal Overlay === */
3140
3143
  .lb-mix-match__modal-overlay {
3141
3144
  position: fixed;
@@ -3164,7 +3167,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3164
3167
  border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
3165
3168
  box-sizing: border-box;
3166
3169
  width: 100%;
3167
- max-width: 480px;
3170
+ max-width: 520px;
3168
3171
  max-height: 70vh;
3169
3172
  display: flex;
3170
3173
  flex-direction: column;
@@ -3274,10 +3277,12 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3274
3277
  }
3275
3278
 
3276
3279
  .lb-mix-match__modal-product {
3277
- display: flex;
3278
- align-items: center;
3279
- gap: 20px;
3280
- padding: 12px 0;
3280
+ display: grid;
3281
+ grid-template-columns: auto 1fr;
3282
+ align-items: start;
3283
+ column-gap: 20px;
3284
+ padding-top: 20px;
3285
+ padding-bottom: 16px;
3281
3286
  border-bottom: 1px solid color-mix(in srgb, var(--lb-picker-text) 7%, transparent);
3282
3287
  }
3283
3288
 
@@ -3378,11 +3383,10 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3378
3383
 
3379
3384
  /* Per-pick quantity stepper inside the picker modal product row.
3380
3385
  Three flex cells (\u2212 / number / +) sharing a single outer border \u2014
3381
- the same picker-variant border vars the variant select uses, so
3382
- the two pieces visually agree even when the merchant tweaks
3383
- --lb-picker-variant-border-width / -color / -radius. The cell
3384
- dividers come from a 1px border on the centre value rather than
3385
- per-button borders, so the rounded outer corners stay clean.
3386
+ driven by its own --lb-picker-qty-stepper-* variables so merchants
3387
+ can style the stepper independently of the variant dropdown. The
3388
+ cell dividers come from a 1px border on the centre value rather
3389
+ than per-button borders, so the rounded outer corners stay clean.
3386
3390
  Mirrors extensions/bundle-theme/assets/bundle-mix-match.css so
3387
3391
  the headless web component matches the Liquid theme widget. */
3388
3392
  /* Quantity label + stepper sit together in a .lb-bundle-variant-option-group
@@ -3391,10 +3395,11 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3391
3395
  group from the unit-price / low-stock-badge above; the stepper itself
3392
3396
  has no top margin so the group can be repositioned without coupling. */
3393
3397
  .lb-mix-match__qty-stepper-group {
3394
- margin-top: 8px;
3395
- /* Group is a flex column; without an explicit width it stretches to
3396
- fill the info column so the \u2212/value/+ cells get pulled apart. Pin
3397
- to fit-content so the wrapper hugs the stepper's natural width. */
3398
+ /* Override the base .lb-bundle-variant-option-group column flex so the
3399
+ stepper child stretches on the cross-axis (vertical) \u2014 this is what
3400
+ lets the group track the Add button's height in the action row
3401
+ without pinning a fixed pixel value. */
3402
+ flex-direction: row;
3398
3403
  width: fit-content;
3399
3404
  }
3400
3405
 
@@ -3402,9 +3407,8 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3402
3407
  display: inline-flex;
3403
3408
  align-items: stretch;
3404
3409
  flex-shrink: 0;
3405
- height: 32px;
3406
- border: var(--lb-picker-variant-border-width) solid var(--lb-picker-variant-border-color);
3407
- border-radius: var(--lb-picker-variant-radius);
3410
+ border: var(--lb-picker-qty-stepper-border-width) solid var(--lb-picker-qty-stepper-border-color);
3411
+ border-radius: var(--lb-picker-qty-stepper-radius);
3408
3412
  background-color: var(--lb-picker-bg);
3409
3413
  overflow: hidden;
3410
3414
  box-sizing: border-box;
@@ -3456,8 +3460,24 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3456
3460
  box-sizing: border-box;
3457
3461
  }
3458
3462
 
3463
+ /* Action row \u2014 qty stepper (left) + Add button (right). Sits at the
3464
+ bottom of the info column with a 12px top margin so it visually
3465
+ separates from the variant pickers / price block above. When the
3466
+ stepper is hidden, Add fills the row via flex: 1 below.
3467
+ align-items: stretch so the stepper-group tracks the Add button's
3468
+ height (driven by font size + button padding) without a pinned px. */
3469
+ .lb-mix-match__modal-product-actions {
3470
+ display: flex;
3471
+ flex-wrap: wrap;
3472
+ align-items: stretch;
3473
+ row-gap: 12px;
3474
+ column-gap: 8px;
3475
+ margin-top: 12px;
3476
+ }
3477
+
3459
3478
  .lb-mix-match__modal-add {
3460
- padding: 8px 20px;
3479
+ flex: 1;
3480
+ padding: 10px 20px;
3461
3481
  background: var(--lb-picker-add-bg);
3462
3482
  color: var(--lb-picker-add-label);
3463
3483
  border: var(--lb-picker-add-border-width) solid var(--lb-picker-add-border-color);
@@ -3466,8 +3486,6 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3466
3486
  font-size: 12px;
3467
3487
  font-weight: 600;
3468
3488
  cursor: pointer;
3469
- white-space: nowrap;
3470
- flex-shrink: 0;
3471
3489
  }
3472
3490
 
3473
3491
  .lb-mix-match__modal-add:hover {
@@ -3490,6 +3508,14 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3490
3508
  opacity: 0.5;
3491
3509
  }
3492
3510
 
3511
+ /* Already in a slot \u2014 prevents shoppers from filling multiple slots
3512
+ with the same product (which wouldn't qualify the discount: it
3513
+ counts distinct products, not slot occupancy). The thumb keeps
3514
+ its qty badge so the shopper sees what's already in the bundle. */
3515
+ .lb-mix-match__modal-product--in-bundle {
3516
+ opacity: 0.5;
3517
+ }
3518
+
3493
3519
  .lb-mix-match__modal-product--sold-out .lb-mix-match__modal-sold-out-label {
3494
3520
  font-size: 12px;
3495
3521
  color: inherit;