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