@lime-bundles/widget 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
@@ -369,6 +369,7 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
369
369
  const select = document.createElement("select");
370
370
  select.className = "lb-bundle-variant-select";
371
371
  select.setAttribute("data-variant-select", "");
372
+ select.name = `lb-variant-${state.product.id.replace(/^.*\//, "")}`;
372
373
  select.setAttribute(
373
374
  "aria-label",
374
375
  `Select variant for ${state.product.title}`
@@ -588,7 +589,12 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
588
589
  variantTitle: firstVariant.title,
589
590
  imageUrl: firstEligible.product.featuredImage?.url ?? null,
590
591
  priceCents: parseCents(firstVariant.price.amount),
591
- compareCents: firstVariant.compareAtPrice ? parseCents(firstVariant.compareAtPrice.amount) : null
592
+ compareCents: firstVariant.compareAtPrice ? parseCents(firstVariant.compareAtPrice.amount) : null,
593
+ unitPriceLabel: formatUnitPrice(
594
+ firstVariant.unitPrice,
595
+ firstVariant.unitPriceMeasurement,
596
+ currency
597
+ )
592
598
  });
593
599
  }
594
600
  afterMutation();
@@ -601,7 +607,12 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
601
607
  variantTitle: variant.title,
602
608
  imageUrl: product.featuredImage?.url ?? null,
603
609
  priceCents: parseCents(variant.price.amount),
604
- compareCents: variant.compareAtPrice ? parseCents(variant.compareAtPrice.amount) : null
610
+ compareCents: variant.compareAtPrice ? parseCents(variant.compareAtPrice.amount) : null,
611
+ unitPriceLabel: formatUnitPrice(
612
+ variant.unitPrice,
613
+ variant.unitPriceMeasurement,
614
+ currency
615
+ )
605
616
  });
606
617
  afterMutation();
607
618
  }
@@ -788,6 +799,11 @@ function renderFilledSlot(selection, index, currency, onRemove) {
788
799
  priceEl.textContent = formatCents(selection.priceCents, currency);
789
800
  priceWrap.appendChild(priceEl);
790
801
  info.appendChild(priceWrap);
802
+ if (selection.unitPriceLabel) {
803
+ const unitPrice = el("span", "lb-bundle-product-unit-price");
804
+ unitPrice.textContent = selection.unitPriceLabel;
805
+ info.appendChild(unitPrice);
806
+ }
791
807
  slot.appendChild(info);
792
808
  const remove = document.createElement("button");
793
809
  remove.type = "button";
@@ -942,8 +958,10 @@ function renderModal(bundle, eligible, currency, handlers) {
942
958
  rowsBuilt = true;
943
959
  list.innerHTML = "";
944
960
  eligible.forEach((ep) => {
945
- const variant = ep.firstAvailableVariant ?? ep.variants[0];
946
- if (!variant) return;
961
+ const availableVariants = ep.variants.filter((v) => v.availableForSale);
962
+ const firstAvailVariant = availableVariants[0] ?? ep.firstAvailableVariant ?? ep.variants[0];
963
+ if (!firstAvailVariant) return;
964
+ let currentVariant = firstAvailVariant;
947
965
  const productEl = el(
948
966
  "div",
949
967
  ep.isOos ? "lb-mix-match__modal-product lb-mix-match__modal-product--sold-out" : "lb-mix-match__modal-product",
@@ -965,26 +983,86 @@ function renderModal(bundle, eligible, currency, handlers) {
965
983
  } else {
966
984
  thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
967
985
  }
986
+ const countBadge = el("span", "lb-bundle-qty-badge");
987
+ countBadge.style.display = "none";
988
+ thumb.appendChild(countBadge);
968
989
  productEl.appendChild(thumb);
969
990
  const info = el("div", "lb-mix-match__modal-product-info");
970
- const title = el("span", "lb-mix-match__modal-product-title");
991
+ const title = el("p", "lb-mix-match__modal-product-title");
971
992
  title.textContent = ep.product.title;
972
993
  info.appendChild(title);
973
- const price = el("span", "lb-mix-match__modal-product-price");
974
- price.textContent = formatCents(parseCents(variant.price.amount), currency);
994
+ const price = el("p", "lb-mix-match__modal-product-price");
995
+ price.textContent = formatCents(
996
+ parseCents(currentVariant.price.amount),
997
+ currency
998
+ );
975
999
  info.appendChild(price);
976
- const unitPriceText = formatUnitPrice(
977
- variant.unitPrice,
978
- variant.unitPriceMeasurement,
1000
+ const unitPrice = el(
1001
+ "p",
1002
+ "lb-mix-match__modal-product-unit-price lb-bundle-product-unit-price"
1003
+ );
1004
+ const initialUnitText = formatUnitPrice(
1005
+ currentVariant.unitPrice,
1006
+ currentVariant.unitPriceMeasurement,
979
1007
  currency
980
1008
  );
981
- if (unitPriceText) {
982
- const unitPrice = el(
983
- "span",
984
- "lb-mix-match__modal-product-unit-price lb-bundle-product-unit-price"
1009
+ if (initialUnitText) {
1010
+ unitPrice.textContent = initialUnitText;
1011
+ } else {
1012
+ unitPrice.hidden = true;
1013
+ }
1014
+ info.appendChild(unitPrice);
1015
+ const row = {
1016
+ el: productEl,
1017
+ product: ep.product,
1018
+ variant: firstAvailVariant,
1019
+ updateCount: () => {
1020
+ }
1021
+ };
1022
+ if (availableVariants.length > 1) {
1023
+ const select = document.createElement("select");
1024
+ select.className = "lb-mix-match__variant-select";
1025
+ select.setAttribute("data-variant-select", "");
1026
+ select.name = `lb-variant-${ep.product.id.replace(/^.*\//, "")}`;
1027
+ select.setAttribute(
1028
+ "aria-label",
1029
+ `Select variant for ${ep.product.title}`
985
1030
  );
986
- unitPrice.textContent = unitPriceText;
987
- info.appendChild(unitPrice);
1031
+ availableVariants.forEach((v) => {
1032
+ const opt = document.createElement("option");
1033
+ opt.value = v.id;
1034
+ opt.textContent = v.title;
1035
+ if (v.id === firstAvailVariant.id) opt.selected = true;
1036
+ select.appendChild(opt);
1037
+ });
1038
+ select.addEventListener("change", () => {
1039
+ const next = availableVariants.find((v) => v.id === select.value);
1040
+ if (!next) return;
1041
+ currentVariant = next;
1042
+ row.variant = next;
1043
+ price.textContent = formatCents(
1044
+ parseCents(currentVariant.price.amount),
1045
+ currency
1046
+ );
1047
+ const nextUnitText = formatUnitPrice(
1048
+ currentVariant.unitPrice,
1049
+ currentVariant.unitPriceMeasurement,
1050
+ currency
1051
+ );
1052
+ if (nextUnitText) {
1053
+ unitPrice.textContent = nextUnitText;
1054
+ unitPrice.hidden = false;
1055
+ } else {
1056
+ unitPrice.textContent = "";
1057
+ unitPrice.hidden = true;
1058
+ }
1059
+ rowUpdateCount();
1060
+ });
1061
+ info.appendChild(select);
1062
+ } else if (availableVariants.length === 1 && firstAvailVariant.title !== "Default Title") {
1063
+ const variantLabel = el("span", "lb-mix-match__filled-variant");
1064
+ variantLabel.textContent = firstAvailVariant.title;
1065
+ info.appendChild(variantLabel);
988
1066
  }
989
1067
  if (ep.isOos) {
990
1068
  const soldOut = el("span", "lb-mix-match__modal-sold-out-label");
@@ -992,56 +1070,29 @@ function renderModal(bundle, eligible, currency, handlers) {
992
1070
  info.appendChild(soldOut);
993
1071
  }
994
1072
  productEl.appendChild(info);
1073
+ const rowUpdateCount = () => {
1074
+ const count = handlers.countFor(ep.product.id, currentVariant.id);
1075
+ if (count > 0) {
1076
+ countBadge.textContent = String(count);
1077
+ countBadge.style.display = "";
1078
+ } else {
1079
+ countBadge.style.display = "none";
1080
+ }
1081
+ };
995
1082
  if (!ep.isOos) {
996
1083
  const addBtn = document.createElement("button");
997
1084
  addBtn.type = "button";
998
1085
  addBtn.className = "lb-mix-match__modal-add";
999
- addBtn.setAttribute("aria-label", `Add ${ep.product.title}`);
1000
- addBtn.innerHTML = PLUS_ICON_SVG;
1001
- const countBadge = el("span", "lb-bundle-qty-badge");
1002
- countBadge.style.display = "none";
1003
- addBtn.appendChild(countBadge);
1086
+ addBtn.textContent = "Add";
1004
1087
  addBtn.addEventListener("click", () => {
1005
1088
  if (handlers.isOverMax()) return;
1006
- handlers.onAdd(ep.product, variant);
1089
+ handlers.onAdd(ep.product, currentVariant);
1007
1090
  });
1008
1091
  productEl.appendChild(addBtn);
1009
- const removeBtn = document.createElement("button");
1010
- removeBtn.type = "button";
1011
- removeBtn.className = "lb-mix-match__slot-remove";
1012
- removeBtn.setAttribute("aria-label", `Remove one ${ep.product.title}`);
1013
- removeBtn.style.display = "none";
1014
- removeBtn.innerHTML = CLOSE_ICON_SVG;
1015
- removeBtn.addEventListener("click", (e) => {
1016
- e.stopPropagation();
1017
- handlers.onRemove(ep.product.id, variant.id);
1018
- });
1019
- productEl.appendChild(removeBtn);
1020
- productRows.push({
1021
- el: productEl,
1022
- product: ep.product,
1023
- variant,
1024
- updateCount: () => {
1025
- const count = handlers.countFor(ep.product.id, variant.id);
1026
- if (count > 0) {
1027
- countBadge.textContent = String(count);
1028
- countBadge.style.display = "";
1029
- removeBtn.style.display = "";
1030
- } else {
1031
- countBadge.style.display = "none";
1032
- removeBtn.style.display = "none";
1033
- }
1034
- }
1035
- });
1036
- } else {
1037
- productRows.push({
1038
- el: productEl,
1039
- product: ep.product,
1040
- variant,
1041
- updateCount: () => {
1042
- }
1043
- });
1044
1092
  }
1093
+ row.updateCount = ep.isOos ? () => {
1094
+ } : rowUpdateCount;
1095
+ productRows.push(row);
1045
1096
  list.appendChild(productEl);
1046
1097
  });
1047
1098
  refreshCounts();
@@ -1612,15 +1663,18 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1612
1663
  /* Product rows */
1613
1664
  .lb-bundle-product-row {
1614
1665
  display: flex;
1615
- gap: 12px;
1666
+ gap: 20px;
1616
1667
  padding: 12px 0;
1617
1668
  }
1618
1669
 
1619
1670
  .lb-bundle-thumbnail {
1620
1671
  position: relative;
1621
1672
  width: 48px;
1622
- height: 48px;
1623
1673
  min-width: 48px;
1674
+ /* Aspect-ratio comes from the merchant \`thumbnailRatio\` enum via Liquid;
1675
+ "original" sets it to \`auto\` so the box sizes to the image's intrinsic
1676
+ ratio. Default keeps the historical 1:1 behaviour. */
1677
+ aspect-ratio: var(--lb-thumbnail-aspect-ratio, 1 / 1);
1624
1678
  background: var(--lb-thumbnail-bg);
1625
1679
  border-radius: 8px;
1626
1680
  overflow: hidden;
@@ -1631,8 +1685,10 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1631
1685
 
1632
1686
  .lb-bundle-thumbnail img {
1633
1687
  width: 100%;
1634
- height: 100%;
1635
- object-fit: cover;
1688
+ /* Height + fit come from the same merchant enum; "original" sets them to
1689
+ \`auto\` / \`contain\` so the image renders uncropped at intrinsic ratio. */
1690
+ height: var(--lb-thumbnail-img-height, 100%);
1691
+ object-fit: var(--lb-thumbnail-img-fit, cover);
1636
1692
  }
1637
1693
 
1638
1694
  .lb-bundle-thumbnail svg {
@@ -1663,7 +1719,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1663
1719
  .lb-bundle-product-price {
1664
1720
  /* --lb-product-price-display fallback is the "on" branch; Liquid sets 'none' when merchant disables. */
1665
1721
  display: var(--lb-product-price-display, block);
1666
- font-size: 12px;
1722
+ font-size: 14px;
1667
1723
  font-weight: 400;
1668
1724
  line-height: 20px;
1669
1725
  color: var(--lb-text);
@@ -1680,7 +1736,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1680
1736
  .lb-bundle-product-compare-price {
1681
1737
  /* --lb-product-compare-display fallback is the "on" branch; Liquid sets 'none' when merchant disables. */
1682
1738
  display: var(--lb-product-compare-display, inline);
1683
- font-size: 12px;
1739
+ font-size: 14px;
1684
1740
  font-weight: 400;
1685
1741
  line-height: 20px;
1686
1742
  color: color-mix(in srgb, var(--lb-text) 60%, transparent);
@@ -1699,7 +1755,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1699
1755
  secondary text beneath the price row so it doesn't compete visually. */
1700
1756
  .lb-bundle-product-unit-price {
1701
1757
  display: block;
1702
- font-size: 11px;
1758
+ font-size: 12px;
1703
1759
  line-height: 16px;
1704
1760
  color: color-mix(in srgb, var(--lb-text) 60%, transparent);
1705
1761
  margin-top: 2px;
@@ -1822,7 +1878,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1822
1878
  grid-template-rows: 1fr;
1823
1879
  grid-template-columns: 1fr;
1824
1880
  width: 100%;
1825
- padding: 16px;
1881
+ padding: 12px 16px;
1826
1882
  border: var(--lb-cta-border-width) solid var(--lb-cta-border-color);
1827
1883
  border-radius: var(--lb-cta-radius);
1828
1884
  font-size: 16px;
@@ -1957,14 +2013,14 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
1957
2013
 
1958
2014
  /* Fixed bundles: product rows */
1959
2015
  .lb-fixed .lb-bundle-product-row {
1960
- gap: 16px;
2016
+ gap: 20px;
1961
2017
  align-items: center;
1962
2018
  }
1963
2019
 
1964
- /* Fixed bundles: larger thumbnails */
2020
+ /* Fixed bundles: larger thumbnails. Height comes from --lb-thumbnail-aspect-ratio
2021
+ (set on .lb-bundle-widget via Liquid / applyWidgetConfigVars). */
1965
2022
  .lb-fixed .lb-bundle-thumbnail {
1966
2023
  width: 60px;
1967
- height: 60px;
1968
2024
  min-width: 60px;
1969
2025
  border-radius: var(--lb-image-border-radius);
1970
2026
  border: var(--lb-image-border-width) solid var(--lb-image-border-color);
@@ -2065,7 +2121,10 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
2065
2121
  width: 60px;
2066
2122
  height: 60px;
2067
2123
  min-width: 60px;
2068
- border: var(--lb-image-border-width) dashed color-mix(in srgb, var(--lb-text) 20%, transparent);
2124
+ /* 2px border is intentionally independent of --lb-image-border-width \u2014
2125
+ empty slots always need a visible dashed outline as an affordance,
2126
+ regardless of how the merchant has styled populated thumbnails. */
2127
+ border: 2px dashed color-mix(in srgb, var(--lb-text) 35%, transparent);
2069
2128
  border-radius: var(--lb-image-border-radius);
2070
2129
  box-sizing: border-box;
2071
2130
  display: flex;
@@ -2085,9 +2144,10 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
2085
2144
  cursor: default;
2086
2145
  }
2087
2146
 
2147
+ /* Mix-match thumbnails. Height comes from --lb-thumbnail-aspect-ratio
2148
+ (set on .lb-bundle-widget via Liquid / applyWidgetConfigVars). */
2088
2149
  .lb-mix-match .lb-bundle-thumbnail {
2089
2150
  width: 60px;
2090
- height: 60px;
2091
2151
  min-width: 60px;
2092
2152
  border-radius: var(--lb-image-border-radius);
2093
2153
  border: var(--lb-image-border-width) solid var(--lb-image-border-color);
@@ -2322,7 +2382,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
2322
2382
  .lb-mix-match__modal-product {
2323
2383
  display: flex;
2324
2384
  align-items: center;
2325
- gap: 12px;
2385
+ gap: 20px;
2326
2386
  padding: 12px 0;
2327
2387
  border-bottom: 1px solid color-mix(in srgb, var(--lb-picker-text) 7%, transparent);
2328
2388
  }
@@ -2334,8 +2394,11 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
2334
2394
  .lb-mix-match__modal-product-thumb {
2335
2395
  position: relative;
2336
2396
  width: 48px;
2337
- height: 48px;
2338
2397
  min-width: 48px;
2398
+ /* Modal picker thumbs follow the merchant's pickerThumbnailRatio \u2014
2399
+ independent from the main widget's thumbnailRatio so a merchant can
2400
+ e.g. show tall picker thumbs with square main thumbs. */
2401
+ aspect-ratio: var(--lb-picker-thumbnail-aspect-ratio, 1 / 1);
2339
2402
  border-radius: var(--lb-picker-product-radius);
2340
2403
  border: var(--lb-picker-product-border-width) solid var(--lb-picker-product-border-color);
2341
2404
  box-sizing: border-box;
@@ -2356,8 +2419,10 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
2356
2419
 
2357
2420
  .lb-mix-match__modal-product-thumb img {
2358
2421
  width: 100%;
2359
- height: 100%;
2360
- object-fit: cover;
2422
+ /* Height + fit come from pickerThumbnailRatio \u2014 "original" sets both
2423
+ to auto/contain so the image renders uncropped at intrinsic ratio. */
2424
+ height: var(--lb-picker-thumbnail-img-height, 100%);
2425
+ object-fit: var(--lb-picker-thumbnail-img-fit, cover);
2361
2426
  border-radius: max(0px, calc(var(--lb-picker-product-radius) - var(--lb-picker-product-border-width)));
2362
2427
  }
2363
2428
 
@@ -2592,7 +2657,7 @@ var BUNDLE_VOLUME_CSS = `/* Lime Bundles \u2014 Volume / Quantity Breaks styles
2592
2657
 
2593
2658
  .lb-volume__tier-price {
2594
2659
  grid-column: 1 / -1;
2595
- font-size: 12px;
2660
+ font-size: 14px;
2596
2661
  font-weight: 500;
2597
2662
  color: var(--lb-text);
2598
2663
  }
@@ -2604,7 +2669,7 @@ var BUNDLE_VOLUME_CSS = `/* Lime Bundles \u2014 Volume / Quantity Breaks styles
2604
2669
 
2605
2670
  /* Compare-at (strikethrough) price */
2606
2671
  .lb-volume__tier-compare {
2607
- font-size: 12px;
2672
+ font-size: 14px;
2608
2673
  line-height: 16px;
2609
2674
  color: color-mix(in srgb, var(--lb-text) 60%, transparent);
2610
2675
  text-decoration: line-through;