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