diy-template-components 5.12.0 → 5.13.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/build/index.es.js CHANGED
@@ -609,7 +609,11 @@ const Button = /*#__PURE__*/forwardRef(function Button({
609
609
  rel: data?.isExternal ? 'nofollow noopener' : null,
610
610
  className: (active ? classes.active : '') + ' ' + classes[type] + ' ' + classes[size] + ' ' + classes.anchorClass,
611
611
  style: styling
612
- }, elementProps), data?.value) : /*#__PURE__*/React.createElement("button", _extends({
612
+ }, elementProps), /*#__PURE__*/React.createElement("span", {
613
+ dangerouslySetInnerHTML: {
614
+ __html: data?.value || ''
615
+ }
616
+ })) : /*#__PURE__*/React.createElement("button", _extends({
613
617
  ref: ref,
614
618
  className: (active ? classes.active : '') + ' ' + classes[type] + ' ' + classes[size],
615
619
  onClick: isEdit ? null : onClick,
@@ -12609,29 +12613,125 @@ const useCounterSectionStyles = createUseStyles(theme => {
12609
12613
  const DURATION_MS = 2000;
12610
12614
  const EASING = t => 1 - Math.pow(1 - t, 3); // easeOutCubic
12611
12615
 
12612
- function parseCounterValue(value) {
12616
+ function getPlainTextFromHtml(html) {
12617
+ if (typeof document === 'undefined') {
12618
+ return String(html).replace(/<[^>]*>/g, '');
12619
+ }
12620
+ const div = document.createElement('div');
12621
+ div.innerHTML = html;
12622
+ return div.textContent || div.innerText || '';
12623
+ }
12624
+ function parseStyleString(styleStr) {
12625
+ if (!styleStr || typeof styleStr !== 'string') return {};
12626
+ const obj = {};
12627
+ styleStr.split(';').forEach(part => {
12628
+ const colonIdx = part.indexOf(':');
12629
+ if (colonIdx > 0) {
12630
+ const key = part.slice(0, colonIdx).trim();
12631
+ const val = part.slice(colonIdx + 1).trim();
12632
+ if (key && val) obj[key] = val;
12633
+ }
12634
+ });
12635
+ return obj;
12636
+ }
12637
+ const SEMANTIC_TAG_STYLES = {
12638
+ strong: {
12639
+ fontWeight: 'bold'
12640
+ },
12641
+ b: {
12642
+ fontWeight: 'bold'
12643
+ },
12644
+ em: {
12645
+ fontStyle: 'italic'
12646
+ },
12647
+ i: {
12648
+ fontStyle: 'italic'
12649
+ },
12650
+ u: {
12651
+ textDecoration: 'underline'
12652
+ }
12653
+ };
12654
+
12655
+ /**
12656
+ * Parses value (plain text or HTML) to extract number for animation and wrapper attributes.
12657
+ * Returns { end, suffix, wrapperProps } or null if not parseable.
12658
+ * wrapperProps: { className, style } for the rich text wrapper, or null if plain text.
12659
+ */
12660
+ function parseCounterValueFromHtml(value) {
12613
12661
  if (value == null || value === '') return null;
12614
- const match = String(value).trim().match(/^([\d.]+)(.*)$/);
12662
+ const str = String(value).trim();
12663
+ const plainText = /<[^>]+>/.test(str) ? getPlainTextFromHtml(str) : str;
12664
+ const match = plainText.match(/^([\d.]+)(.*)$/);
12615
12665
  if (!match) return null;
12616
12666
  const num = parseFloat(match[1]);
12617
12667
  if (Number.isNaN(num)) return null;
12618
- return {
12619
- end: num,
12620
- suffix: match[2] || ''
12621
- };
12668
+ const suffix = match[2] || '';
12669
+ if (!/<[^>]+>/.test(str)) {
12670
+ return {
12671
+ end: num,
12672
+ suffix,
12673
+ wrapperProps: null
12674
+ };
12675
+ }
12676
+ if (typeof document === 'undefined') {
12677
+ return {
12678
+ end: num,
12679
+ suffix,
12680
+ wrapperProps: null
12681
+ };
12682
+ }
12683
+ try {
12684
+ const parser = new DOMParser();
12685
+ const doc = parser.parseFromString(str, 'text/html');
12686
+ const root = doc.body?.firstElementChild;
12687
+ if (!root) return {
12688
+ end: num,
12689
+ suffix,
12690
+ wrapperProps: null
12691
+ };
12692
+ const classes = [];
12693
+ const styleObj = {};
12694
+ const collectFromElement = el => {
12695
+ const c = el.getAttribute('class');
12696
+ if (c) classes.push(...c.split(/\s+/).filter(Boolean));
12697
+ const s = el.getAttribute('style');
12698
+ if (s) Object.assign(styleObj, parseStyleString(s));
12699
+ const tagStyles = SEMANTIC_TAG_STYLES[el.tagName?.toLowerCase()];
12700
+ if (tagStyles) Object.assign(styleObj, tagStyles);
12701
+ for (const child of el.children) {
12702
+ collectFromElement(child);
12703
+ }
12704
+ };
12705
+ collectFromElement(root);
12706
+ const className = [...new Set(classes)].join(' ');
12707
+ return {
12708
+ end: num,
12709
+ suffix,
12710
+ wrapperProps: className || Object.keys(styleObj).length ? {
12711
+ className,
12712
+ style: styleObj
12713
+ } : null
12714
+ };
12715
+ } catch {
12716
+ return {
12717
+ end: num,
12718
+ suffix,
12719
+ wrapperProps: null
12720
+ };
12721
+ }
12622
12722
  }
12623
12723
  function AnimatedCounter({
12624
12724
  value,
12625
12725
  className,
12626
12726
  refSetter
12627
12727
  }) {
12628
- const elRef = useRef(null);
12728
+ const animSpanRef = useRef(null);
12629
12729
  const rafIdRef = useRef(null);
12630
12730
  const hasAnimatedRef = useRef(false);
12631
12731
  useLayoutEffect(() => {
12632
12732
  hasAnimatedRef.current = false; // Reset so we can re-animate when value changes (e.g. live edit)
12633
- const parsed = parseCounterValue(value);
12634
- if (!parsed || !elRef.current) return;
12733
+ const parsed = parseCounterValueFromHtml(value);
12734
+ if (!parsed || !animSpanRef.current) return;
12635
12735
  const observer = new IntersectionObserver(entries => {
12636
12736
  const [entry] = entries;
12637
12737
  if (!entry.isIntersecting || hasAnimatedRef.current) return;
@@ -12646,9 +12746,9 @@ function AnimatedCounter({
12646
12746
  const progress = Math.min(elapsed / DURATION_MS, 1);
12647
12747
  const eased = EASING(progress);
12648
12748
  const current = eased * end;
12649
- if (elRef.current) {
12749
+ if (animSpanRef.current) {
12650
12750
  const displayValue = Number.isInteger(end) ? Math.round(current) : parseFloat(current.toFixed(2));
12651
- elRef.current.textContent = displayValue + suffix;
12751
+ animSpanRef.current.textContent = displayValue + suffix;
12652
12752
  }
12653
12753
  if (progress < 1) {
12654
12754
  rafIdRef.current = requestAnimationFrame(tick);
@@ -12658,22 +12758,34 @@ function AnimatedCounter({
12658
12758
  }, {
12659
12759
  threshold: 0.2
12660
12760
  });
12661
- observer.observe(elRef.current);
12761
+ observer.observe(animSpanRef.current);
12662
12762
  return () => {
12663
12763
  observer.disconnect();
12664
12764
  if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current);
12665
12765
  };
12666
12766
  }, [value]);
12667
- const parsed = parseCounterValue(value);
12767
+ const parsed = parseCounterValueFromHtml(value);
12668
12768
  const setRef = el => {
12669
- elRef.current = el;
12670
12769
  refSetter?.(el);
12671
12770
  };
12672
12771
  if (parsed) {
12772
+ const {
12773
+ end,
12774
+ suffix,
12775
+ wrapperProps
12776
+ } = parsed;
12777
+ const initialDisplay = Number.isInteger(end) ? '0' : '0.00';
12778
+ const animatedContent = /*#__PURE__*/React.createElement("span", {
12779
+ ref: animSpanRef
12780
+ }, initialDisplay, suffix);
12781
+ const content = wrapperProps ? /*#__PURE__*/React.createElement("span", {
12782
+ className: wrapperProps.className || undefined,
12783
+ style: Object.keys(wrapperProps.style || {}).length ? wrapperProps.style : undefined
12784
+ }, animatedContent) : animatedContent;
12673
12785
  return /*#__PURE__*/React.createElement("h2", {
12674
12786
  ref: setRef,
12675
12787
  className: className
12676
- }, "0", parsed.suffix);
12788
+ }, content);
12677
12789
  }
12678
12790
  return /*#__PURE__*/React.createElement("h2", {
12679
12791
  ref: refSetter,
@@ -12725,6 +12837,7 @@ function CounterSection({
12725
12837
  key: item._id || index,
12726
12838
  className: classes.counterItem
12727
12839
  }, value != null && value !== '' && /*#__PURE__*/React.createElement(AnimatedCounter, {
12840
+ key: `${item._id || index}-${/<[^>]+>/.test(value || '') ? 'html' : 'plain'}`,
12728
12841
  value: value,
12729
12842
  className: classes.value,
12730
12843
  refSetter: getValueRefSetter(item)
@@ -12852,8 +12965,8 @@ const getFloatingButtonBase = theme => ({
12852
12965
  zIndex: 9999,
12853
12966
  display: 'inline-flex',
12854
12967
  alignItems: 'center',
12855
- gap: '10px',
12856
- padding: '12px 20px',
12968
+ gap: '8px',
12969
+ padding: '14px',
12857
12970
  backgroundColor: theme?.colors?.ctaColor || '#FFFFFF',
12858
12971
  border: `1px solid ${theme?.palette?.border?.secondary ?? '#E5E7EB'}`,
12859
12972
  borderRadius: '9999px',
@@ -12863,7 +12976,6 @@ const getFloatingButtonBase = theme => ({
12863
12976
  fontFamily: theme?.typography?.fontFamily ?? 'inherit',
12864
12977
  fontWeight: theme?.typography?.fontWeight?.bold ?? 700,
12865
12978
  fontSize: '14px',
12866
- textTransform: 'uppercase',
12867
12979
  letterSpacing: '0.02em',
12868
12980
  color: theme?.colors?.CtaTextColor || 'white',
12869
12981
  transition: 'box-shadow 0.2s ease, transform 0.2s ease'
@@ -12875,7 +12987,7 @@ const getFloatingButtonResponsive = theme => {
12875
12987
  ...base,
12876
12988
  bottom: 142,
12877
12989
  left: 16,
12878
- padding: '10px 16px',
12990
+ padding: '10px',
12879
12991
  gap: '8px',
12880
12992
  fontSize: '12px',
12881
12993
  background: theme?.colors?.ctaColor || '#FFFFFF'
@@ -12999,6 +13111,7 @@ function StickyCta({
12999
13111
  const contentList = node?.contentList?.components || [];
13000
13112
  const showEditHint = sectionData?.isDefaultEditor ?? false;
13001
13113
  sectionData?.bgSection?.components?.[0]?.sectionBgData?.metadata?.value;
13114
+ const redirectUrl = node?.subheading?.metadata?.value || '';
13002
13115
  const propsFromSection = node ? {
13003
13116
  type: node?.title?.metadata?.value ?? type,
13004
13117
  stickyMessage: contentList[1]?.contentPara?.metadata?.value ?? stickyMessage,
@@ -13021,7 +13134,7 @@ function StickyCta({
13021
13134
  whatsAppButtonBackgroundColor,
13022
13135
  iconImageUrl
13023
13136
  };
13024
- const whatsAppUrl = buildWhatsAppUrl(p?.whatsAppPhoneNumber) ?? p?.whatsAppHref ?? '#';
13137
+ buildWhatsAppUrl(p?.whatsAppPhoneNumber) ?? p?.whatsAppHref ?? '#';
13025
13138
  const theme = useTheme() || {};
13026
13139
  const [breakpoint, setBreakpoint] = useState(() => typeof window !== 'undefined' ? getBreakpoint(window.innerWidth, isMobile) : 'mobile');
13027
13140
  useEffect(() => {
@@ -13060,7 +13173,7 @@ function StickyCta({
13060
13173
  const iconSize = getWhatsAppIconSize(breakpoint);
13061
13174
  const iconColor = p.whatsAppIconColor ?? DEFAULT_WHATSAPP_ICON_COLOR;
13062
13175
  return /*#__PURE__*/React.createElement(React.Fragment, null, isMobile ? null : editHintBlock, /*#__PURE__*/React.createElement("a", {
13063
- href: whatsAppUrl,
13176
+ href: redirectUrl,
13064
13177
  target: "_blank",
13065
13178
  rel: "noopener noreferrer",
13066
13179
  "access-cta": "DIY",
@@ -13078,7 +13191,11 @@ function StickyCta({
13078
13191
  size: iconSize,
13079
13192
  color: iconColor,
13080
13193
  iconImageUrl: p.iconImageUrl
13081
- }), /*#__PURE__*/React.createElement("span", null, p.whatsAppLabel)));
13194
+ }), p.whatsAppLabel ? /*#__PURE__*/React.createElement("span", {
13195
+ dangerouslySetInnerHTML: {
13196
+ __html: p.whatsAppLabel
13197
+ }
13198
+ }) : null));
13082
13199
  }
13083
13200
 
13084
13201
  // type === 'sticky' – use primary Button (same as other sections) linking to WhatsApp
@@ -13089,12 +13206,15 @@ function StickyCta({
13089
13206
  role: "banner",
13090
13207
  "access-cta-sticky": "DIY"
13091
13208
  }, /*#__PURE__*/React.createElement("p", {
13092
- style: textStyle
13093
- }, p.stickyMessage), /*#__PURE__*/React.createElement(Button, {
13209
+ style: textStyle,
13210
+ dangerouslySetInnerHTML: {
13211
+ __html: p.stickyMessage || ''
13212
+ }
13213
+ }), /*#__PURE__*/React.createElement(Button, {
13094
13214
  data: {
13095
13215
  isLink: 1,
13096
13216
  isExternal: 1,
13097
- link: whatsAppUrl,
13217
+ link: redirectUrl,
13098
13218
  value: p.stickyButtonText
13099
13219
  },
13100
13220
  type: "primary",