@lime-bundles/widget 4.6.4 → 4.6.6

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
@@ -256,6 +256,7 @@ function recalcPricing(container, products, formatMoney) {
256
256
  savingsPercentEl.textContent = "(" + pct + "%)";
257
257
  }
258
258
  savingsBar.style.display = "";
259
+ savingsBar.classList.remove("lb-savings-pending");
259
260
  } else {
260
261
  savingsBar.style.display = "none";
261
262
  }
@@ -379,6 +380,55 @@ function buildOptionGroups(container, product, selected) {
379
380
  }
380
381
  }
381
382
 
383
+ // ../render/src/product/placeholder.ts
384
+ var SVG_NS = "http://www.w3.org/2000/svg";
385
+ var PLACEHOLDER_ATTR = "data-lb-placeholder";
386
+ var PLACEHOLDER_CLASS = "lb-thumb--placeholder";
387
+ function builtInMark() {
388
+ const svg = document.createElementNS(SVG_NS, "svg");
389
+ svg.setAttribute("class", "lb-bundle-placeholder-icon");
390
+ svg.setAttribute("viewBox", "0 0 48 48");
391
+ const rect = document.createElementNS(SVG_NS, "rect");
392
+ rect.setAttribute("x", "15");
393
+ rect.setAttribute("y", "15");
394
+ rect.setAttribute("width", "18");
395
+ rect.setAttribute("height", "18");
396
+ rect.setAttribute("rx", "2");
397
+ svg.appendChild(rect);
398
+ const dot = document.createElementNS(SVG_NS, "circle");
399
+ dot.setAttribute("cx", "20.5");
400
+ dot.setAttribute("cy", "20.5");
401
+ dot.setAttribute("r", "1.5");
402
+ svg.appendChild(dot);
403
+ const ridge = document.createElementNS(SVG_NS, "path");
404
+ ridge.setAttribute("d", "M33 27l-5-5L17 33");
405
+ svg.appendChild(ridge);
406
+ return svg;
407
+ }
408
+ var make = builtInMark;
409
+ function mark() {
410
+ const node = make();
411
+ const el2 = node.nodeType === 1 ? node : null;
412
+ if (el2) {
413
+ el2.setAttribute(PLACEHOLDER_ATTR, "");
414
+ el2.setAttribute("aria-hidden", "true");
415
+ el2.setAttribute("focusable", "false");
416
+ }
417
+ return node;
418
+ }
419
+ function showThumbnailPlaceholder(box2) {
420
+ const img = box2.querySelector("img");
421
+ if (img) img.remove();
422
+ box2.classList.add(PLACEHOLDER_CLASS);
423
+ if (box2.querySelector("[" + PLACEHOLDER_ATTR + "]")) return;
424
+ box2.appendChild(mark());
425
+ }
426
+ function hideThumbnailPlaceholder(box2) {
427
+ const existing = box2.querySelector("[" + PLACEHOLDER_ATTR + "]");
428
+ if (existing) existing.remove();
429
+ box2.classList.remove(PLACEHOLDER_CLASS);
430
+ }
431
+
382
432
  // ../render/src/product/row.ts
383
433
  var productImgSrc = /* @__PURE__ */ new WeakMap();
384
434
  function updateThumbnail(row, variant, productImage) {
@@ -389,8 +439,12 @@ function updateThumbnail(row, variant, productImage) {
389
439
  const seed = productImage || (img ? img.src : null);
390
440
  if (seed) productImgSrc.set(row, seed);
391
441
  }
392
- const nextSrc = variant.image || productImgSrc.get(row) || null;
393
- if (!nextSrc) return;
442
+ const nextSrc = variant && variant.image || productImgSrc.get(row) || null;
443
+ if (!nextSrc) {
444
+ showThumbnailPlaceholder(thumb);
445
+ return;
446
+ }
447
+ hideThumbnailPlaceholder(thumb);
394
448
  if (img) {
395
449
  img.src = nextSrc;
396
450
  img.srcset = "";
@@ -409,7 +463,10 @@ function updateInlineQuantity(row, qty) {
409
463
  el2.textContent = "\xD7" + qty;
410
464
  }
411
465
  function applyVariant(row, variant, product, formatMoney) {
412
- if (!variant) return;
466
+ if (!variant) {
467
+ updateThumbnail(row, null, product.featuredImage);
468
+ return;
469
+ }
413
470
  updateThumbnail(row, variant, product.featuredImage);
414
471
  updateInlineQuantity(row, variant.quantity || product.quantity || 1);
415
472
  const priceEl = row.querySelector("[data-product-price]");
@@ -459,8 +516,11 @@ function buildRowSkeleton(product, t, opts) {
459
516
  if (opts.bogoRole) row.setAttribute("data-bogo-role", opts.bogoRole);
460
517
  const thumb = box("div", "lb-bundle-thumbnail", { "data-thumbnail": "" });
461
518
  const ratio = product.featuredImageRatio;
462
- if (opts.thumbnailRatio === "original" && ratio != null && ratio > 0) {
463
- thumb.style.setProperty("--lb-row-thumb-ratio", String(ratio));
519
+ if (opts.thumbnailRatio === "original") {
520
+ thumb.style.setProperty(
521
+ "--lb-row-thumb-ratio",
522
+ String(ratio != null && ratio > 0 ? ratio : 1)
523
+ );
464
524
  }
465
525
  row.appendChild(thumb);
466
526
  const info = box("div", "lb-bundle-product-info");
@@ -1425,17 +1485,25 @@ function buildPickerRow(p, rowIndex, ctx) {
1425
1485
  addedBadge.textContent = t.addedItem || "Added";
1426
1486
  addedBadge.hidden = true;
1427
1487
  thumbDiv.appendChild(addedBadge);
1488
+ const paintThumb = (src) => {
1489
+ if (!src) {
1490
+ showThumbnailPlaceholder(thumbDiv);
1491
+ return;
1492
+ }
1493
+ hideThumbnailPlaceholder(thumbDiv);
1494
+ let img = thumbDiv.querySelector("img");
1495
+ if (!img) {
1496
+ img = document.createElement("img");
1497
+ img.sizes = PICKER_SIZES;
1498
+ img.alt = p.title;
1499
+ img.loading = "lazy";
1500
+ thumbDiv.appendChild(img);
1501
+ }
1502
+ img.src = imgWidth(src, PICKER_BASE_WIDTH);
1503
+ img.srcset = srcset(src, PICKER_WIDTHS);
1504
+ };
1428
1505
  const firstAvailable = p.variants.find((v) => v.available) ?? null;
1429
- const initialThumbSrc = firstAvailable && firstAvailable.image || p.featuredImage;
1430
- if (initialThumbSrc) {
1431
- const img = document.createElement("img");
1432
- img.src = imgWidth(initialThumbSrc, PICKER_BASE_WIDTH);
1433
- img.srcset = srcset(initialThumbSrc, PICKER_WIDTHS);
1434
- img.sizes = PICKER_SIZES;
1435
- img.alt = p.title;
1436
- img.loading = "lazy";
1437
- thumbDiv.appendChild(img);
1438
- }
1506
+ paintThumb(firstAvailable && firstAvailable.image || p.featuredImage);
1439
1507
  row.appendChild(thumbDiv);
1440
1508
  const infoDiv = document.createElement("div");
1441
1509
  infoDiv.className = "lb-mix-match__modal-product-info";
@@ -1646,12 +1714,7 @@ function buildPickerRow(p, rowIndex, ctx) {
1646
1714
  unitPriceEl.textContent = "";
1647
1715
  unitPriceEl.hidden = true;
1648
1716
  }
1649
- const rowImg = thumbDiv.querySelector("img");
1650
- const imgSrc = v.image || p.featuredImage;
1651
- if (rowImg && imgSrc) {
1652
- rowImg.src = imgWidth(imgSrc, PICKER_BASE_WIDTH);
1653
- rowImg.srcset = srcset(imgSrc, PICKER_WIDTHS);
1654
- }
1717
+ paintThumb(v.image || p.featuredImage);
1655
1718
  setBoundsForVariant?.(v, capacity.variantUnitsElsewhere(v.id));
1656
1719
  if (v.options) recomputeDisabled(v.options);
1657
1720
  refresh();
@@ -2229,6 +2292,8 @@ function buildSlotCard(item, trans, formatMoney, removable, onRemove, onEdit) {
2229
2292
  img.alt = item.title;
2230
2293
  img.loading = "lazy";
2231
2294
  thumb.appendChild(img);
2295
+ } else {
2296
+ showThumbnailPlaceholder(thumb);
2232
2297
  }
2233
2298
  card.appendChild(thumb);
2234
2299
  const info = document.createElement("div");
@@ -2457,7 +2522,7 @@ function seedSelection(eligibleProducts, rulesMap, requiredQty, courtesy) {
2457
2522
  required.push(pick(p, v2, Math.max(1, rule.min)));
2458
2523
  }
2459
2524
  if (required.length || !courtesy?.enabled) return required;
2460
- const qualifies = (p) => p.available && ruleFor(p.id, rulesMap).min <= requiredQty;
2525
+ const qualifies = (p) => p.available && ruleFor(p.id, rulesMap).min <= requiredQty && firstAvailable(p) !== null;
2461
2526
  const seed = (courtesy.currentProductId != null ? eligibleProducts.find(
2462
2527
  (p) => p.id === courtesy.currentProductId && qualifies(p)
2463
2528
  ) : null) ?? eligibleProducts.find(qualifies);
@@ -2495,6 +2560,7 @@ function el(tag, className, attrs = {}) {
2495
2560
  }
2496
2561
  function buildPickerModal(opts) {
2497
2562
  const titleId = "lb-modal-title-" + opts.domId;
2563
+ const s = opts.strings || {};
2498
2564
  const overlay = el("div", "lb-mix-match__modal-overlay", {
2499
2565
  "data-modal-overlay": "",
2500
2566
  "data-bundle-gid": opts.bundleGid,
@@ -2515,7 +2581,7 @@ function buildPickerModal(opts) {
2515
2581
  const heading = el("div", "lb-mix-match__modal-heading");
2516
2582
  const title = el("h4", "lb-mix-match__modal-title", { id: titleId });
2517
2583
  if (opts.wizard) title.setAttribute("tabindex", "-1");
2518
- title.textContent = "Add to your bundle";
2584
+ title.textContent = s.addToBundle || "Add to your bundle";
2519
2585
  heading.appendChild(title);
2520
2586
  heading.appendChild(
2521
2587
  el("p", "lb-mix-match__modal-subtitle", { "data-modal-subtitle": "" })
@@ -2524,7 +2590,7 @@ function buildPickerModal(opts) {
2524
2590
  const close = el("button", "lb-mix-match__modal-close", {
2525
2591
  type: "button",
2526
2592
  "data-modal-close": "",
2527
- "aria-label": "Close"
2593
+ "aria-label": s.closeModal || "Close"
2528
2594
  });
2529
2595
  close.appendChild(
2530
2596
  icon([
@@ -2565,15 +2631,15 @@ function buildPickerModal(opts) {
2565
2631
  type: "text",
2566
2632
  "data-modal-search": "",
2567
2633
  role: "searchbox",
2568
- "aria-label": "Search products",
2569
- placeholder: "Search products",
2634
+ "aria-label": s.searchProducts || "Search products",
2635
+ placeholder: s.searchProducts || "Search products",
2570
2636
  autocomplete: "off"
2571
2637
  });
2572
2638
  search.appendChild(input);
2573
2639
  const clear = el("button", "lb-mix-match__modal-search-clear", {
2574
2640
  type: "button",
2575
2641
  "data-modal-search-clear": "",
2576
- "aria-label": "Clear search",
2642
+ "aria-label": s.clearSearch || "Clear search",
2577
2643
  style: "display: none;"
2578
2644
  });
2579
2645
  clear.appendChild(
@@ -2602,7 +2668,7 @@ function buildPickerModal(opts) {
2602
2668
  style: "display: none;"
2603
2669
  });
2604
2670
  const emptyText = document.createElement("p");
2605
- emptyText.textContent = "No products match your search";
2671
+ emptyText.textContent = s.noSearchResults || "No products match your search";
2606
2672
  empty.appendChild(emptyText);
2607
2673
  dialog.appendChild(empty);
2608
2674
  dialog.appendChild(
@@ -2621,7 +2687,7 @@ function buildPickerModal(opts) {
2621
2687
  "data-modal-back": "",
2622
2688
  style: "display: none;"
2623
2689
  });
2624
- back.textContent = "Back";
2690
+ back.textContent = s.back || "Back";
2625
2691
  footer.appendChild(back);
2626
2692
  }
2627
2693
  footer.appendChild(
@@ -2636,7 +2702,7 @@ function buildPickerModal(opts) {
2636
2702
  "data-modal-next": "",
2637
2703
  style: "display: none;"
2638
2704
  });
2639
- next.textContent = "Next";
2705
+ next.textContent = s.next || "Next";
2640
2706
  footer.appendChild(next);
2641
2707
  }
2642
2708
  const done = el("button", "lb-mix-match__modal-done", {
@@ -2644,7 +2710,7 @@ function buildPickerModal(opts) {
2644
2710
  "data-modal-done": "",
2645
2711
  ...opts.wizard ? { style: "display: none;" } : {}
2646
2712
  });
2647
- done.textContent = "Done";
2713
+ done.textContent = s.done || "Done";
2648
2714
  footer.appendChild(done);
2649
2715
  dialog.appendChild(footer);
2650
2716
  overlay.appendChild(dialog);
@@ -2969,6 +3035,7 @@ function selectTier(container, allTiers, basePrice, discountType, index, transla
2969
3035
  savingsPercentEl.textContent = "(" + savingsPct + "%)";
2970
3036
  }
2971
3037
  savingsBar.style.display = "";
3038
+ savingsBar.classList.remove("lb-savings-pending");
2972
3039
  } else {
2973
3040
  savingsBar.style.display = "none";
2974
3041
  }
@@ -3152,6 +3219,7 @@ function recalcPricing2(container, sides, percent, buyQty, getQty, formatMoney)
3152
3219
  savingsPercentEl.textContent = "(" + pct + "%)";
3153
3220
  }
3154
3221
  savingsBar.style.display = "";
3222
+ savingsBar.classList.remove("lb-savings-pending");
3155
3223
  } else {
3156
3224
  savingsBar.style.display = "none";
3157
3225
  }
@@ -4542,6 +4610,15 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
4542
4610
  display: block;
4543
4611
  }
4544
4612
 
4613
+ /* Same reset, one more victim. A thumbnail is emitted empty and filled at
4614
+ hydration, so between the two it matched \`div:empty\` and vanished \u2014 taking
4615
+ the row's gap with it and starting that title 72px left of every other
4616
+ row's (issue #552). The box holds its place either way now: the product's
4617
+ image lands in it, or the placeholder mark does. */
4618
+ .lb-bundle-thumbnail:empty {
4619
+ display: flex;
4620
+ }
4621
+
4545
4622
  /* Divider */
4546
4623
  .lb-bundle-divider {
4547
4624
  height: var(--lb-border-width);
@@ -4580,11 +4657,6 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
4580
4657
  object-fit: var(--lb-thumbnail-img-fit, cover);
4581
4658
  }
4582
4659
 
4583
- .lb-bundle-thumbnail svg {
4584
- width: 28px;
4585
- height: 28px;
4586
- color: color-mix(in srgb, var(--lb-text) 35%, transparent);
4587
- }
4588
4660
 
4589
4661
  .lb-bundle-product-info {
4590
4662
  flex: 1;
@@ -4708,6 +4780,43 @@ a.lb-bundle-product-name:focus-visible {
4708
4780
  gap: 4px;
4709
4781
  }
4710
4782
 
4783
+ /* Variant picker select \u2014 styled to match the variant badge aesthetic.
4784
+ Sits inside .lb-bundle-variant-option-group so vertical spacing is owned
4785
+ by the group/groups flex gap, not the select itself.
4786
+
4787
+ Lives here, not in bundle-fixed.css, because every widget that renders an
4788
+ option group needs it and only the fixed widget loads that stylesheet: a
4789
+ page carrying just a Buy X Get Y bundle rendered raw browser selects, and
4790
+ the option-group height reservation (sized for the styled control) was
4791
+ wrong by 14px a group, which shifted the row on hydration.
4792
+ The same split applies to the headless package, which injects per type. */
4793
+ .lb-bundle-variant-select {
4794
+ display: inline-block;
4795
+ border: var(--lb-border-width) solid var(--lb-border);
4796
+ border-radius: var(--lb-radius);
4797
+ padding: 8px 32px 8px 12px;
4798
+ font-size: 12px;
4799
+ line-height: 16px;
4800
+ color: var(--lb-text);
4801
+ background: var(--lb-bg);
4802
+ font-family: inherit;
4803
+ cursor: pointer;
4804
+ appearance: none;
4805
+ -webkit-appearance: none;
4806
+ background-image: var(--lb-variant-chevron);
4807
+ background-repeat: no-repeat;
4808
+ background-position: right 8px center;
4809
+ background-size: 12px;
4810
+ width: 50%;
4811
+ max-width: 50%;
4812
+ }
4813
+
4814
+ .lb-bundle-variant-select:focus-visible {
4815
+ outline: 2px solid var(--lb-primary-color);
4816
+ outline-offset: 2px;
4817
+ box-shadow: none;
4818
+ }
4819
+
4711
4820
  .lb-bundle-variant-option-label {
4712
4821
  display: block;
4713
4822
  margin: 0;
@@ -4989,15 +5098,33 @@ a.lb-bundle-product-name:focus-visible {
4989
5098
  border: 0 !important;
4990
5099
  }
4991
5100
 
4992
- /* Placeholder SVG icon for missing images */
5101
+ /* Missing-image placeholder.
5102
+ Two marks share these rules: Shopify's own \`placeholder_svg_tag\`
5103
+ illustration, which the theme scripts clone out of the <template> in
5104
+ bundle-widget.liquid, and the line glyph the headless renderer falls back to
5105
+ when there is no Liquid to supply one. */
5106
+ .lb-thumb--placeholder > [data-lb-placeholder] {
5107
+ display: block;
5108
+ width: 100%;
5109
+ /* The mark carries the box's height, not the other way round: the
5110
+ "original" thumbnail ratio sizes a box to its image's proportions and an
5111
+ imageless row has no image, so without this the box would be 60px by
5112
+ nothing. A box that already has a ratio caps the mark instead. */
5113
+ height: auto;
5114
+ max-height: 100%;
5115
+ aspect-ratio: 1 / 1;
5116
+ }
5117
+
4993
5118
  .lb-bundle-placeholder-icon {
4994
- width: 28px;
4995
- height: 28px;
4996
5119
  stroke: color-mix(in srgb, var(--lb-text) 35%, transparent);
4997
5120
  stroke-width: 1.5;
4998
5121
  fill: none;
4999
5122
  }
5000
5123
 
5124
+ .lb-bundle-placeholder-svg {
5125
+ fill: color-mix(in srgb, var(--lb-text) 22%, transparent);
5126
+ }
5127
+
5001
5128
  /* Out-of-stock product row */
5002
5129
  .lb-bundle-product-row--oos {
5003
5130
  opacity: 0.5;
@@ -5029,6 +5156,16 @@ a.lb-bundle-product-name:focus-visible {
5029
5156
  white-space: nowrap;
5030
5157
  }
5031
5158
 
5159
+ /* The savings line is laid out from first paint and revealed on hydration,
5160
+ rather than being display:none until then.
5161
+ Its amounts are money strings the server cannot know, so filling them in
5162
+ place moves whatever shares the line; revealing an already-laid-out box
5163
+ moves nothing. The band around it is vertically centred, so reserving the
5164
+ line costs no height of its own. JS drops the class in updatePricing. */
5165
+ .lb-bundle-savings-line.lb-savings-pending {
5166
+ visibility: hidden;
5167
+ }
5168
+
5032
5169
  /* A/B test: hide save badge until JS swaps the label (prevents flash of default) */
5033
5170
  .lb-ab-pending {
5034
5171
  visibility: hidden;
@@ -5046,27 +5183,43 @@ a.lb-bundle-product-name:focus-visible {
5046
5183
  The title is the one box NOT reserved this way: text wrapping depends on the
5047
5184
  merchant's font, so it is server-rendered and its height is simply real. */
5048
5185
 
5186
+ /* A zero-width space, not a pixel height.
5187
+ These boxes are one line of text once JS fills them, and their height is
5188
+ whatever the merchant's font makes of \`line-height: normal\` \u2014 which the
5189
+ reset at the top of this file forces on every descendant, overriding the
5190
+ per-element line-heights above it. A hardcoded min-height therefore has to
5191
+ guess, and guessed 3px too tall: the row shrank on hydration in every theme.
5192
+ An empty line box measures exactly what the filled one will. */
5049
5193
  .lb-bundle-product-price:empty {
5050
5194
  display: inline-block;
5051
- min-height: 20px;
5052
5195
  }
5053
5196
 
5054
5197
  .lb-bundle-variant-badge:empty {
5055
5198
  display: block;
5056
- min-height: 20px;
5057
5199
  }
5058
5200
 
5059
5201
  .lb-bundle-product-unit-price:empty {
5060
5202
  display: block;
5061
- min-height: 16px;
5203
+ }
5204
+
5205
+ .lb-bundle-product-price:empty::before,
5206
+ .lb-bundle-variant-badge:empty::before,
5207
+ .lb-bundle-product-unit-price:empty::before,
5208
+ .lb-bundle-qty-inline:empty::before,
5209
+ .lb-bundle-sale-price:empty::before,
5210
+ .lb-bundle-compare-price:empty::before {
5211
+ content: "\\200B";
5062
5212
  }
5063
5213
 
5064
5214
  /* Label (16) + gap (4) + trigger (8 + 16 + 8 padding/line-height) per group,
5065
5215
  plus the 8px gap between groups. --lb-row-option-count is set per row by
5066
- Liquid, which knows how many options the product has. */
5216
+ Liquid, which knows how many options the product has.
5217
+
5218
+ The trigger is border-box, so its border is already inside that 52px; the
5219
+ calc used to add it again and reserved 2px per group too much. */
5067
5220
  .lb-bundle-variant-option-groups:empty {
5068
5221
  min-height: calc(
5069
- var(--lb-row-option-count, 1) * (52px + 2 * var(--lb-border-width)) +
5222
+ var(--lb-row-option-count, 1) * 52px +
5070
5223
  (var(--lb-row-option-count, 1) - 1) * 8px
5071
5224
  );
5072
5225
  }
@@ -5146,36 +5299,6 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
5146
5299
  font-variant-numeric: tabular-nums;
5147
5300
  white-space: nowrap;
5148
5301
  }
5149
-
5150
- /* Variant picker select \u2014 styled to match the variant badge aesthetic.
5151
- Sits inside .lb-bundle-variant-option-group so vertical spacing is owned
5152
- by the group/groups flex gap, not the select itself. */
5153
- .lb-bundle-variant-select {
5154
- display: inline-block;
5155
- border: var(--lb-border-width) solid var(--lb-border);
5156
- border-radius: var(--lb-radius);
5157
- padding: 8px 32px 8px 12px;
5158
- font-size: 12px;
5159
- line-height: 16px;
5160
- color: var(--lb-text);
5161
- background: var(--lb-bg);
5162
- font-family: inherit;
5163
- cursor: pointer;
5164
- appearance: none;
5165
- -webkit-appearance: none;
5166
- background-image: var(--lb-variant-chevron);
5167
- background-repeat: no-repeat;
5168
- background-position: right 8px center;
5169
- background-size: 12px;
5170
- width: 50%;
5171
- max-width: 50%;
5172
- }
5173
-
5174
- .lb-bundle-variant-select:focus-visible {
5175
- outline: 2px solid var(--lb-primary-color);
5176
- outline-offset: 2px;
5177
- box-shadow: none;
5178
- }
5179
5302
  `;
5180
5303
  var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
5181
5304
 
@@ -6498,6 +6621,15 @@ var BUNDLE_BOGO_CSS = `/* Lime Bundles \u2014 BOGO (Buy X Get Y) bundle styles *
6498
6621
  text-decoration: line-through;
6499
6622
  opacity: 0.6;
6500
6623
  }
6624
+
6625
+ /* The reward row's struck original is emitted empty and filled on hydration,
6626
+ so anything sharing its line moves when it appears. The count is the only
6627
+ such thing, and it is a count, not part of the price phrase: pinning it to
6628
+ the row's right edge makes its position independent of the price text \u2014 the
6629
+ same right-slot treatment the fixed widget gives its quantity chip. */
6630
+ .lb-bogo .lb-bundle-qty-inline {
6631
+ margin-left: auto;
6632
+ }
6501
6633
  `;
6502
6634
  var BUNDLE_MULTI_STEP_CSS = `/**
6503
6635
  * Lime Bundles \u2014 Multi-step bundle widget styles (wizard chrome ONLY).