@lime-bundles/widget 2.1.0 → 2.2.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
@@ -70,6 +70,32 @@ function parseIso(iso) {
70
70
  return Number.isFinite(t) ? t : null;
71
71
  }
72
72
 
73
+ // src/renderers/cta-button.ts
74
+ function buildCtaButton(label) {
75
+ const button = document.createElement("button");
76
+ button.type = "button";
77
+ button.className = "lb-bundle-cta";
78
+ button.setAttribute("data-add-bundle", "");
79
+ const labelSpan = document.createElement("span");
80
+ labelSpan.className = "lb-cta-label";
81
+ labelSpan.setAttribute("data-cta-label", "");
82
+ labelSpan.textContent = label;
83
+ button.appendChild(labelSpan);
84
+ const spinnerSpan = document.createElement("span");
85
+ spinnerSpan.className = "lb-cta-spinner";
86
+ spinnerSpan.setAttribute("data-cta-spinner", "");
87
+ spinnerSpan.setAttribute("aria-hidden", "true");
88
+ spinnerSpan.innerHTML = '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M12 2a10 10 0 0 1 10 10" /></svg>';
89
+ button.appendChild(spinnerSpan);
90
+ return button;
91
+ }
92
+ function setCtaLabel(button, text) {
93
+ const label = button.querySelector("[data-cta-label]");
94
+ if (label) {
95
+ label.textContent = text;
96
+ }
97
+ }
98
+
73
99
  // src/renderers/dom.ts
74
100
  function el(tag, className, attrs = {}) {
75
101
  const node = document.createElement(tag);
@@ -410,15 +436,11 @@ function renderSavingsBar() {
410
436
  };
411
437
  }
412
438
  function renderCta(bundle, oosCount, onClick) {
413
- const button = document.createElement("button");
414
- button.type = "button";
415
- button.className = "lb-bundle-cta";
416
- button.setAttribute("data-add-bundle", "");
439
+ const label = oosCount > 0 ? `${oosCount} item${oosCount === 1 ? "" : "s"} out of stock` : bundle.widgetConfig.cta.ctaText || "Add to cart";
440
+ const button = buildCtaButton(label);
417
441
  if (oosCount > 0) {
418
442
  button.disabled = true;
419
- button.textContent = `${oosCount} item${oosCount === 1 ? "" : "s"} out of stock`;
420
443
  } else {
421
- button.textContent = bundle.widgetConfig.cta.ctaText || "Add to cart";
422
444
  button.addEventListener("click", () => {
423
445
  if (button.disabled) return;
424
446
  onClick();
@@ -515,12 +537,8 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
515
537
  isOverMax: () => selections.length >= maxQty
516
538
  });
517
539
  root.appendChild(modal.el);
518
- const cta = document.createElement("button");
519
- cta.type = "button";
520
- cta.className = "lb-bundle-cta";
521
- cta.setAttribute("data-add-bundle", "");
540
+ const cta = buildCtaButton(`Select ${requiredQty} items to unlock`);
522
541
  cta.disabled = true;
523
- cta.textContent = `Select ${requiredQty} items to unlock`;
524
542
  cta.addEventListener("click", () => {
525
543
  if (cta.disabled) return;
526
544
  const lines = selections.map((s) => ({
@@ -613,10 +631,10 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
613
631
  const count = selections.length;
614
632
  if (count < requiredQty) {
615
633
  cta.disabled = true;
616
- cta.textContent = `Select ${requiredQty - count} more to unlock`;
634
+ setCtaLabel(cta, `Select ${requiredQty - count} more to unlock`);
617
635
  } else {
618
636
  cta.disabled = false;
619
- cta.textContent = wc.cta.ctaText || "Add to cart";
637
+ setCtaLabel(cta, wc.cta.ctaText || "Add to cart");
620
638
  }
621
639
  }
622
640
  }
@@ -1351,12 +1369,9 @@ function renderSavingsBar3(resolved, selectedIndex, currency) {
1351
1369
  function renderCta2(bundle, onClick) {
1352
1370
  const product = bundle.products[0];
1353
1371
  const isAvailable = product?.variants.nodes.some((v) => v.availableForSale);
1354
- const button = document.createElement("button");
1355
- button.type = "button";
1356
- button.className = "lb-bundle-cta";
1357
- button.setAttribute("data-add-bundle", "");
1372
+ const label = isAvailable ? bundle.widgetConfig.cta.ctaText || "Add to cart" : "Sold out";
1373
+ const button = buildCtaButton(label);
1358
1374
  if (!isAvailable) button.disabled = true;
1359
- button.textContent = isAvailable ? bundle.widgetConfig.cta.ctaText || "Add to cart" : "Sold out";
1360
1375
  button.addEventListener("click", () => {
1361
1376
  if (button.disabled) return;
1362
1377
  onClick();
@@ -1743,9 +1758,16 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1743
1758
  display: none;
1744
1759
  }
1745
1760
 
1746
- /* CTA button */
1761
+ /* CTA button.
1762
+ * Label and spinner share a single 1\xD71 grid cell so the button's intrinsic
1763
+ * width/height stays fixed when swapping between them \u2014 no layout shift when
1764
+ * entering the loading state. Visibility (not display) is used so the hidden
1765
+ * child still contributes to the cell's min-content sizing. See the
1766
+ * [data-loading="true"] rules below. */
1747
1767
  .lb-bundle-cta {
1748
- display: block;
1768
+ display: grid;
1769
+ grid-template-rows: 1fr;
1770
+ grid-template-columns: 1fr;
1749
1771
  width: 100%;
1750
1772
  padding: 16px;
1751
1773
  border: var(--lb-cta-border-width) solid var(--lb-cta-border-color);
@@ -1774,12 +1796,51 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
1774
1796
  cursor: not-allowed;
1775
1797
  }
1776
1798
 
1799
+ /* Both the label and the spinner occupy grid cell (1, 1). Only one is
1800
+ * visible at a time; the other keeps its box for sizing but is invisible. */
1801
+ .lb-cta-label,
1802
+ .lb-cta-spinner {
1803
+ grid-row: 1;
1804
+ grid-column: 1;
1805
+ display: flex;
1806
+ align-items: center;
1807
+ justify-content: center;
1808
+ min-width: 0;
1809
+ }
1810
+
1811
+ .lb-cta-spinner {
1812
+ visibility: hidden;
1813
+ }
1814
+
1815
+ .lb-cta-spinner svg {
1816
+ width: 20px;
1817
+ height: 20px;
1818
+ animation: lb-cta-spin 0.8s linear infinite;
1819
+ }
1820
+
1821
+ @keyframes lb-cta-spin {
1822
+ to { transform: rotate(360deg); }
1823
+ }
1824
+
1777
1825
  .lb-bundle-cta[data-loading="true"] {
1778
- opacity: 0.7;
1779
1826
  cursor: wait;
1780
1827
  pointer-events: none;
1781
1828
  }
1782
1829
 
1830
+ .lb-bundle-cta[data-loading="true"] .lb-cta-label {
1831
+ visibility: hidden;
1832
+ }
1833
+
1834
+ .lb-bundle-cta[data-loading="true"] .lb-cta-spinner {
1835
+ visibility: visible;
1836
+ }
1837
+
1838
+ @media (prefers-reduced-motion: reduce) {
1839
+ .lb-cta-spinner svg {
1840
+ animation-duration: 2.5s;
1841
+ }
1842
+ }
1843
+
1783
1844
  /* Error message */
1784
1845
  .lb-bundle-error {
1785
1846
  font-size: 16px;