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.js CHANGED
@@ -623,7 +623,11 @@ const Button = /*#__PURE__*/React.forwardRef(function Button({
623
623
  rel: data?.isExternal ? 'nofollow noopener' : null,
624
624
  className: (active ? classes.active : '') + ' ' + classes[type] + ' ' + classes[size] + ' ' + classes.anchorClass,
625
625
  style: styling
626
- }, elementProps), data?.value) : /*#__PURE__*/React__default["default"].createElement("button", _extends({
626
+ }, elementProps), /*#__PURE__*/React__default["default"].createElement("span", {
627
+ dangerouslySetInnerHTML: {
628
+ __html: data?.value || ''
629
+ }
630
+ })) : /*#__PURE__*/React__default["default"].createElement("button", _extends({
627
631
  ref: ref,
628
632
  className: (active ? classes.active : '') + ' ' + classes[type] + ' ' + classes[size],
629
633
  onClick: isEdit ? null : onClick,
@@ -12623,29 +12627,125 @@ const useCounterSectionStyles = createUseStyles(theme => {
12623
12627
  const DURATION_MS = 2000;
12624
12628
  const EASING = t => 1 - Math.pow(1 - t, 3); // easeOutCubic
12625
12629
 
12626
- function parseCounterValue(value) {
12630
+ function getPlainTextFromHtml(html) {
12631
+ if (typeof document === 'undefined') {
12632
+ return String(html).replace(/<[^>]*>/g, '');
12633
+ }
12634
+ const div = document.createElement('div');
12635
+ div.innerHTML = html;
12636
+ return div.textContent || div.innerText || '';
12637
+ }
12638
+ function parseStyleString(styleStr) {
12639
+ if (!styleStr || typeof styleStr !== 'string') return {};
12640
+ const obj = {};
12641
+ styleStr.split(';').forEach(part => {
12642
+ const colonIdx = part.indexOf(':');
12643
+ if (colonIdx > 0) {
12644
+ const key = part.slice(0, colonIdx).trim();
12645
+ const val = part.slice(colonIdx + 1).trim();
12646
+ if (key && val) obj[key] = val;
12647
+ }
12648
+ });
12649
+ return obj;
12650
+ }
12651
+ const SEMANTIC_TAG_STYLES = {
12652
+ strong: {
12653
+ fontWeight: 'bold'
12654
+ },
12655
+ b: {
12656
+ fontWeight: 'bold'
12657
+ },
12658
+ em: {
12659
+ fontStyle: 'italic'
12660
+ },
12661
+ i: {
12662
+ fontStyle: 'italic'
12663
+ },
12664
+ u: {
12665
+ textDecoration: 'underline'
12666
+ }
12667
+ };
12668
+
12669
+ /**
12670
+ * Parses value (plain text or HTML) to extract number for animation and wrapper attributes.
12671
+ * Returns { end, suffix, wrapperProps } or null if not parseable.
12672
+ * wrapperProps: { className, style } for the rich text wrapper, or null if plain text.
12673
+ */
12674
+ function parseCounterValueFromHtml(value) {
12627
12675
  if (value == null || value === '') return null;
12628
- const match = String(value).trim().match(/^([\d.]+)(.*)$/);
12676
+ const str = String(value).trim();
12677
+ const plainText = /<[^>]+>/.test(str) ? getPlainTextFromHtml(str) : str;
12678
+ const match = plainText.match(/^([\d.]+)(.*)$/);
12629
12679
  if (!match) return null;
12630
12680
  const num = parseFloat(match[1]);
12631
12681
  if (Number.isNaN(num)) return null;
12632
- return {
12633
- end: num,
12634
- suffix: match[2] || ''
12635
- };
12682
+ const suffix = match[2] || '';
12683
+ if (!/<[^>]+>/.test(str)) {
12684
+ return {
12685
+ end: num,
12686
+ suffix,
12687
+ wrapperProps: null
12688
+ };
12689
+ }
12690
+ if (typeof document === 'undefined') {
12691
+ return {
12692
+ end: num,
12693
+ suffix,
12694
+ wrapperProps: null
12695
+ };
12696
+ }
12697
+ try {
12698
+ const parser = new DOMParser();
12699
+ const doc = parser.parseFromString(str, 'text/html');
12700
+ const root = doc.body?.firstElementChild;
12701
+ if (!root) return {
12702
+ end: num,
12703
+ suffix,
12704
+ wrapperProps: null
12705
+ };
12706
+ const classes = [];
12707
+ const styleObj = {};
12708
+ const collectFromElement = el => {
12709
+ const c = el.getAttribute('class');
12710
+ if (c) classes.push(...c.split(/\s+/).filter(Boolean));
12711
+ const s = el.getAttribute('style');
12712
+ if (s) Object.assign(styleObj, parseStyleString(s));
12713
+ const tagStyles = SEMANTIC_TAG_STYLES[el.tagName?.toLowerCase()];
12714
+ if (tagStyles) Object.assign(styleObj, tagStyles);
12715
+ for (const child of el.children) {
12716
+ collectFromElement(child);
12717
+ }
12718
+ };
12719
+ collectFromElement(root);
12720
+ const className = [...new Set(classes)].join(' ');
12721
+ return {
12722
+ end: num,
12723
+ suffix,
12724
+ wrapperProps: className || Object.keys(styleObj).length ? {
12725
+ className,
12726
+ style: styleObj
12727
+ } : null
12728
+ };
12729
+ } catch {
12730
+ return {
12731
+ end: num,
12732
+ suffix,
12733
+ wrapperProps: null
12734
+ };
12735
+ }
12636
12736
  }
12637
12737
  function AnimatedCounter({
12638
12738
  value,
12639
12739
  className,
12640
12740
  refSetter
12641
12741
  }) {
12642
- const elRef = React.useRef(null);
12742
+ const animSpanRef = React.useRef(null);
12643
12743
  const rafIdRef = React.useRef(null);
12644
12744
  const hasAnimatedRef = React.useRef(false);
12645
12745
  React.useLayoutEffect(() => {
12646
12746
  hasAnimatedRef.current = false; // Reset so we can re-animate when value changes (e.g. live edit)
12647
- const parsed = parseCounterValue(value);
12648
- if (!parsed || !elRef.current) return;
12747
+ const parsed = parseCounterValueFromHtml(value);
12748
+ if (!parsed || !animSpanRef.current) return;
12649
12749
  const observer = new IntersectionObserver(entries => {
12650
12750
  const [entry] = entries;
12651
12751
  if (!entry.isIntersecting || hasAnimatedRef.current) return;
@@ -12660,9 +12760,9 @@ function AnimatedCounter({
12660
12760
  const progress = Math.min(elapsed / DURATION_MS, 1);
12661
12761
  const eased = EASING(progress);
12662
12762
  const current = eased * end;
12663
- if (elRef.current) {
12763
+ if (animSpanRef.current) {
12664
12764
  const displayValue = Number.isInteger(end) ? Math.round(current) : parseFloat(current.toFixed(2));
12665
- elRef.current.textContent = displayValue + suffix;
12765
+ animSpanRef.current.textContent = displayValue + suffix;
12666
12766
  }
12667
12767
  if (progress < 1) {
12668
12768
  rafIdRef.current = requestAnimationFrame(tick);
@@ -12672,22 +12772,34 @@ function AnimatedCounter({
12672
12772
  }, {
12673
12773
  threshold: 0.2
12674
12774
  });
12675
- observer.observe(elRef.current);
12775
+ observer.observe(animSpanRef.current);
12676
12776
  return () => {
12677
12777
  observer.disconnect();
12678
12778
  if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current);
12679
12779
  };
12680
12780
  }, [value]);
12681
- const parsed = parseCounterValue(value);
12781
+ const parsed = parseCounterValueFromHtml(value);
12682
12782
  const setRef = el => {
12683
- elRef.current = el;
12684
12783
  refSetter?.(el);
12685
12784
  };
12686
12785
  if (parsed) {
12786
+ const {
12787
+ end,
12788
+ suffix,
12789
+ wrapperProps
12790
+ } = parsed;
12791
+ const initialDisplay = Number.isInteger(end) ? '0' : '0.00';
12792
+ const animatedContent = /*#__PURE__*/React__default["default"].createElement("span", {
12793
+ ref: animSpanRef
12794
+ }, initialDisplay, suffix);
12795
+ const content = wrapperProps ? /*#__PURE__*/React__default["default"].createElement("span", {
12796
+ className: wrapperProps.className || undefined,
12797
+ style: Object.keys(wrapperProps.style || {}).length ? wrapperProps.style : undefined
12798
+ }, animatedContent) : animatedContent;
12687
12799
  return /*#__PURE__*/React__default["default"].createElement("h2", {
12688
12800
  ref: setRef,
12689
12801
  className: className
12690
- }, "0", parsed.suffix);
12802
+ }, content);
12691
12803
  }
12692
12804
  return /*#__PURE__*/React__default["default"].createElement("h2", {
12693
12805
  ref: refSetter,
@@ -12739,6 +12851,7 @@ function CounterSection({
12739
12851
  key: item._id || index,
12740
12852
  className: classes.counterItem
12741
12853
  }, value != null && value !== '' && /*#__PURE__*/React__default["default"].createElement(AnimatedCounter, {
12854
+ key: `${item._id || index}-${/<[^>]+>/.test(value || '') ? 'html' : 'plain'}`,
12742
12855
  value: value,
12743
12856
  className: classes.value,
12744
12857
  refSetter: getValueRefSetter(item)
@@ -12866,8 +12979,8 @@ const getFloatingButtonBase = theme => ({
12866
12979
  zIndex: 9999,
12867
12980
  display: 'inline-flex',
12868
12981
  alignItems: 'center',
12869
- gap: '10px',
12870
- padding: '12px 20px',
12982
+ gap: '8px',
12983
+ padding: '14px',
12871
12984
  backgroundColor: theme?.colors?.ctaColor || '#FFFFFF',
12872
12985
  border: `1px solid ${theme?.palette?.border?.secondary ?? '#E5E7EB'}`,
12873
12986
  borderRadius: '9999px',
@@ -12877,7 +12990,6 @@ const getFloatingButtonBase = theme => ({
12877
12990
  fontFamily: theme?.typography?.fontFamily ?? 'inherit',
12878
12991
  fontWeight: theme?.typography?.fontWeight?.bold ?? 700,
12879
12992
  fontSize: '14px',
12880
- textTransform: 'uppercase',
12881
12993
  letterSpacing: '0.02em',
12882
12994
  color: theme?.colors?.CtaTextColor || 'white',
12883
12995
  transition: 'box-shadow 0.2s ease, transform 0.2s ease'
@@ -12889,7 +13001,7 @@ const getFloatingButtonResponsive = theme => {
12889
13001
  ...base,
12890
13002
  bottom: 142,
12891
13003
  left: 16,
12892
- padding: '10px 16px',
13004
+ padding: '10px',
12893
13005
  gap: '8px',
12894
13006
  fontSize: '12px',
12895
13007
  background: theme?.colors?.ctaColor || '#FFFFFF'
@@ -13013,6 +13125,7 @@ function StickyCta({
13013
13125
  const contentList = node?.contentList?.components || [];
13014
13126
  const showEditHint = sectionData?.isDefaultEditor ?? false;
13015
13127
  sectionData?.bgSection?.components?.[0]?.sectionBgData?.metadata?.value;
13128
+ const redirectUrl = node?.subheading?.metadata?.value || '';
13016
13129
  const propsFromSection = node ? {
13017
13130
  type: node?.title?.metadata?.value ?? type,
13018
13131
  stickyMessage: contentList[1]?.contentPara?.metadata?.value ?? stickyMessage,
@@ -13035,7 +13148,7 @@ function StickyCta({
13035
13148
  whatsAppButtonBackgroundColor,
13036
13149
  iconImageUrl
13037
13150
  };
13038
- const whatsAppUrl = buildWhatsAppUrl(p?.whatsAppPhoneNumber) ?? p?.whatsAppHref ?? '#';
13151
+ buildWhatsAppUrl(p?.whatsAppPhoneNumber) ?? p?.whatsAppHref ?? '#';
13039
13152
  const theme = useTheme() || {};
13040
13153
  const [breakpoint, setBreakpoint] = React.useState(() => typeof window !== 'undefined' ? getBreakpoint(window.innerWidth, isMobile) : 'mobile');
13041
13154
  React.useEffect(() => {
@@ -13074,7 +13187,7 @@ function StickyCta({
13074
13187
  const iconSize = getWhatsAppIconSize(breakpoint);
13075
13188
  const iconColor = p.whatsAppIconColor ?? DEFAULT_WHATSAPP_ICON_COLOR;
13076
13189
  return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, isMobile ? null : editHintBlock, /*#__PURE__*/React__default["default"].createElement("a", {
13077
- href: whatsAppUrl,
13190
+ href: redirectUrl,
13078
13191
  target: "_blank",
13079
13192
  rel: "noopener noreferrer",
13080
13193
  "access-cta": "DIY",
@@ -13092,7 +13205,11 @@ function StickyCta({
13092
13205
  size: iconSize,
13093
13206
  color: iconColor,
13094
13207
  iconImageUrl: p.iconImageUrl
13095
- }), /*#__PURE__*/React__default["default"].createElement("span", null, p.whatsAppLabel)));
13208
+ }), p.whatsAppLabel ? /*#__PURE__*/React__default["default"].createElement("span", {
13209
+ dangerouslySetInnerHTML: {
13210
+ __html: p.whatsAppLabel
13211
+ }
13212
+ }) : null));
13096
13213
  }
13097
13214
 
13098
13215
  // type === 'sticky' – use primary Button (same as other sections) linking to WhatsApp
@@ -13103,12 +13220,15 @@ function StickyCta({
13103
13220
  role: "banner",
13104
13221
  "access-cta-sticky": "DIY"
13105
13222
  }, /*#__PURE__*/React__default["default"].createElement("p", {
13106
- style: textStyle
13107
- }, p.stickyMessage), /*#__PURE__*/React__default["default"].createElement(Button, {
13223
+ style: textStyle,
13224
+ dangerouslySetInnerHTML: {
13225
+ __html: p.stickyMessage || ''
13226
+ }
13227
+ }), /*#__PURE__*/React__default["default"].createElement(Button, {
13108
13228
  data: {
13109
13229
  isLink: 1,
13110
13230
  isExternal: 1,
13111
- link: whatsAppUrl,
13231
+ link: redirectUrl,
13112
13232
  value: p.stickyButtonText
13113
13233
  },
13114
13234
  type: "primary",