@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.cjs CHANGED
@@ -196,7 +196,7 @@ function bindDropdown(select) {
196
196
  if (rect.width === 0) return false;
197
197
  const visibleCount = Math.min(optionEls.length || 1, MAX_VISIBLE_ITEMS);
198
198
  const desiredHeight = visibleCount * ITEM_HEIGHT_PX + LIST_PAD_Y;
199
- const scrollable = listbox.hasAttribute("data-lb-dropdown-portal") ? null : findScrollableAncestor(trigger);
199
+ const scrollable = findScrollableAncestor(trigger);
200
200
  const clip = scrollable ? (() => {
201
201
  const r = scrollable.getBoundingClientRect();
202
202
  return { top: r.top, bottom: r.bottom };
@@ -1044,16 +1044,6 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
1044
1044
  root.appendChild(pricingSection.el);
1045
1045
  const savingsBar = wc.savingsBar.visible ? renderSavingsBar2() : null;
1046
1046
  if (savingsBar) root.appendChild(savingsBar.el);
1047
- const placeholder = el("div", "lb-mix-match__price-placeholder", {
1048
- "data-price-placeholder": ""
1049
- });
1050
- const placeholderText = el(
1051
- "span",
1052
- "lb-mix-match__price-placeholder-text"
1053
- );
1054
- placeholderText.textContent = `Select ${requiredQty} items to see price`;
1055
- placeholder.appendChild(placeholderText);
1056
- root.appendChild(placeholder);
1057
1047
  const modal = renderModal(bundle, eligible, currency, {
1058
1048
  showSearch: wc.showSearch,
1059
1049
  showQtySelector,
@@ -1118,7 +1108,6 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
1118
1108
  progress.update(selections.length);
1119
1109
  pricingSection.update(selections, bundle, currency);
1120
1110
  if (savingsBar) savingsBar.update(selections, bundle, currency);
1121
- placeholder.style.display = selections.length === 0 ? "" : "none";
1122
1111
  modal.refreshCounts();
1123
1112
  updateCta();
1124
1113
  }
@@ -1558,14 +1547,12 @@ function renderModal(bundle, eligible, currency, handlers) {
1558
1547
  return { min: rule.min, max, cap };
1559
1548
  };
1560
1549
  let stepper = null;
1550
+ let qtyGroup = null;
1561
1551
  if (!ep.isOos && handlers.showQtySelector) {
1562
- const qtyGroup = el(
1552
+ qtyGroup = el(
1563
1553
  "div",
1564
1554
  "lb-bundle-variant-option-group lb-mix-match__qty-stepper-group"
1565
1555
  );
1566
- const qtyLabel = el("span", "lb-bundle-variant-option-label");
1567
- qtyLabel.textContent = "Quantity";
1568
- qtyGroup.appendChild(qtyLabel);
1569
1556
  stepper = renderQtyStepper({
1570
1557
  initial: rule.min,
1571
1558
  getBounds: () => {
@@ -1574,7 +1561,6 @@ function renderModal(bundle, eligible, currency, handlers) {
1574
1561
  }
1575
1562
  });
1576
1563
  qtyGroup.appendChild(stepper.el);
1577
- info.appendChild(qtyGroup);
1578
1564
  }
1579
1565
  const row = {
1580
1566
  el: productEl,
@@ -1710,10 +1696,22 @@ function renderModal(bundle, eligible, currency, handlers) {
1710
1696
  } else {
1711
1697
  countBadge.hidden = true;
1712
1698
  }
1713
- addBtn.disabled = handlers.isComplete() || cap < rule.min;
1699
+ const productInBundle = handlers.selections.some(
1700
+ (s) => s.productId === ep.product.id
1701
+ );
1702
+ if (productInBundle) {
1703
+ addBtn.disabled = true;
1704
+ addBtn.textContent = "Added";
1705
+ productEl.classList.add("lb-mix-match__modal-product--in-bundle");
1706
+ } else {
1707
+ addBtn.textContent = "Add";
1708
+ productEl.classList.remove("lb-mix-match__modal-product--in-bundle");
1709
+ addBtn.disabled = handlers.isComplete() || cap < rule.min;
1710
+ }
1714
1711
  };
1715
1712
  if (!ep.isOos) {
1716
1713
  const actions = el("div", "lb-mix-match__modal-product-actions");
1714
+ if (qtyGroup) actions.appendChild(qtyGroup);
1717
1715
  addBtn = document.createElement("button");
1718
1716
  addBtn.type = "button";
1719
1717
  addBtn.className = "lb-mix-match__modal-add";
@@ -1727,7 +1725,7 @@ function renderModal(bundle, eligible, currency, handlers) {
1727
1725
  close();
1728
1726
  });
1729
1727
  actions.appendChild(addBtn);
1730
- productEl.appendChild(actions);
1728
+ info.appendChild(actions);
1731
1729
  }
1732
1730
  row.refreshFromSelections = () => {
1733
1731
  if (stepper) stepper.refresh();
@@ -2567,7 +2565,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
2567
2565
  .lb-bundle-variant-option-groups {
2568
2566
  display: flex;
2569
2567
  flex-direction: column;
2570
- gap: 12px;
2568
+ gap: 8px;
2571
2569
  margin-top: 8px;
2572
2570
  }
2573
2571
 
@@ -2589,7 +2587,23 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
2589
2587
  }
2590
2588
 
2591
2589
  .lb-mix-match__modal-product-info .lb-bundle-variant-option-groups {
2592
- margin-top: 4px;
2590
+ margin-top: 8px;
2591
+ }
2592
+
2593
+ /* Desktop: lay variant option groups in a wrapping row inside the modal
2594
+ (Size + Color side-by-side). Mobile keeps the column stack from the
2595
+ base rule above. The 768px breakpoint mirrors bundle-mix-match.css. */
2596
+ @media (min-width: 768px) {
2597
+ .lb-mix-match__modal-product-info .lb-bundle-variant-option-groups {
2598
+ flex-direction: row;
2599
+ flex-wrap: wrap;
2600
+ }
2601
+ /* Direct children only \u2014 the qty-stepper-group is also a
2602
+ .lb-bundle-variant-option-group but lives in the actions wrapper
2603
+ and must keep its natural width. */
2604
+ .lb-mix-match__modal-product-info .lb-bundle-variant-option-groups > .lb-bundle-variant-option-group {
2605
+ flex: 1;
2606
+ }
2593
2607
  }
2594
2608
 
2595
2609
  /* Focus-ring suppression for mouse users. A small JS helper \u2014 see
@@ -3118,17 +3132,6 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3118
3132
  outline-offset: 2px;
3119
3133
  }
3120
3134
 
3121
- /* Price placeholder */
3122
- .lb-mix-match__price-placeholder {
3123
- padding: 4px 0 16px;
3124
- text-align: center;
3125
- }
3126
-
3127
- .lb-mix-match__price-placeholder-text {
3128
- font-size: 16px;
3129
- color: var(--lb-text);
3130
- }
3131
-
3132
3135
  /* === Modal Overlay === */
3133
3136
  .lb-mix-match__modal-overlay {
3134
3137
  position: fixed;
@@ -3157,7 +3160,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3157
3160
  border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
3158
3161
  box-sizing: border-box;
3159
3162
  width: 100%;
3160
- max-width: 480px;
3163
+ max-width: 520px;
3161
3164
  max-height: 70vh;
3162
3165
  display: flex;
3163
3166
  flex-direction: column;
@@ -3267,10 +3270,12 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3267
3270
  }
3268
3271
 
3269
3272
  .lb-mix-match__modal-product {
3270
- display: flex;
3271
- align-items: center;
3272
- gap: 20px;
3273
- padding: 12px 0;
3273
+ display: grid;
3274
+ grid-template-columns: auto 1fr;
3275
+ align-items: start;
3276
+ column-gap: 20px;
3277
+ padding-top: 20px;
3278
+ padding-bottom: 16px;
3274
3279
  border-bottom: 1px solid color-mix(in srgb, var(--lb-picker-text) 7%, transparent);
3275
3280
  }
3276
3281
 
@@ -3371,11 +3376,10 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3371
3376
 
3372
3377
  /* Per-pick quantity stepper inside the picker modal product row.
3373
3378
  Three flex cells (\u2212 / number / +) sharing a single outer border \u2014
3374
- the same picker-variant border vars the variant select uses, so
3375
- the two pieces visually agree even when the merchant tweaks
3376
- --lb-picker-variant-border-width / -color / -radius. The cell
3377
- dividers come from a 1px border on the centre value rather than
3378
- per-button borders, so the rounded outer corners stay clean.
3379
+ driven by its own --lb-picker-qty-stepper-* variables so merchants
3380
+ can style the stepper independently of the variant dropdown. The
3381
+ cell dividers come from a 1px border on the centre value rather
3382
+ than per-button borders, so the rounded outer corners stay clean.
3379
3383
  Mirrors extensions/bundle-theme/assets/bundle-mix-match.css so
3380
3384
  the headless web component matches the Liquid theme widget. */
3381
3385
  /* Quantity label + stepper sit together in a .lb-bundle-variant-option-group
@@ -3384,10 +3388,11 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3384
3388
  group from the unit-price / low-stock-badge above; the stepper itself
3385
3389
  has no top margin so the group can be repositioned without coupling. */
3386
3390
  .lb-mix-match__qty-stepper-group {
3387
- margin-top: 8px;
3388
- /* Group is a flex column; without an explicit width it stretches to
3389
- fill the info column so the \u2212/value/+ cells get pulled apart. Pin
3390
- to fit-content so the wrapper hugs the stepper's natural width. */
3391
+ /* Override the base .lb-bundle-variant-option-group column flex so the
3392
+ stepper child stretches on the cross-axis (vertical) \u2014 this is what
3393
+ lets the group track the Add button's height in the action row
3394
+ without pinning a fixed pixel value. */
3395
+ flex-direction: row;
3391
3396
  width: fit-content;
3392
3397
  }
3393
3398
 
@@ -3395,9 +3400,8 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3395
3400
  display: inline-flex;
3396
3401
  align-items: stretch;
3397
3402
  flex-shrink: 0;
3398
- height: 32px;
3399
- border: var(--lb-picker-variant-border-width) solid var(--lb-picker-variant-border-color);
3400
- border-radius: var(--lb-picker-variant-radius);
3403
+ border: var(--lb-picker-qty-stepper-border-width) solid var(--lb-picker-qty-stepper-border-color);
3404
+ border-radius: var(--lb-picker-qty-stepper-radius);
3401
3405
  background-color: var(--lb-picker-bg);
3402
3406
  overflow: hidden;
3403
3407
  box-sizing: border-box;
@@ -3449,8 +3453,24 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3449
3453
  box-sizing: border-box;
3450
3454
  }
3451
3455
 
3456
+ /* Action row \u2014 qty stepper (left) + Add button (right). Sits at the
3457
+ bottom of the info column with a 12px top margin so it visually
3458
+ separates from the variant pickers / price block above. When the
3459
+ stepper is hidden, Add fills the row via flex: 1 below.
3460
+ align-items: stretch so the stepper-group tracks the Add button's
3461
+ height (driven by font size + button padding) without a pinned px. */
3462
+ .lb-mix-match__modal-product-actions {
3463
+ display: flex;
3464
+ flex-wrap: wrap;
3465
+ align-items: stretch;
3466
+ row-gap: 12px;
3467
+ column-gap: 8px;
3468
+ margin-top: 12px;
3469
+ }
3470
+
3452
3471
  .lb-mix-match__modal-add {
3453
- padding: 8px 20px;
3472
+ flex: 1;
3473
+ padding: 10px 20px;
3454
3474
  background: var(--lb-picker-add-bg);
3455
3475
  color: var(--lb-picker-add-label);
3456
3476
  border: var(--lb-picker-add-border-width) solid var(--lb-picker-add-border-color);
@@ -3459,8 +3479,6 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3459
3479
  font-size: 12px;
3460
3480
  font-weight: 600;
3461
3481
  cursor: pointer;
3462
- white-space: nowrap;
3463
- flex-shrink: 0;
3464
3482
  }
3465
3483
 
3466
3484
  .lb-mix-match__modal-add:hover {
@@ -3483,6 +3501,14 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
3483
3501
  opacity: 0.5;
3484
3502
  }
3485
3503
 
3504
+ /* Already in a slot \u2014 prevents shoppers from filling multiple slots
3505
+ with the same product (which wouldn't qualify the discount: it
3506
+ counts distinct products, not slot occupancy). The thumb keeps
3507
+ its qty badge so the shopper sees what's already in the bundle. */
3508
+ .lb-mix-match__modal-product--in-bundle {
3509
+ opacity: 0.5;
3510
+ }
3511
+
3486
3512
  .lb-mix-match__modal-product--sold-out .lb-mix-match__modal-sold-out-label {
3487
3513
  font-size: 12px;
3488
3514
  color: inherit;