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 +146 -26
- package/build/index.es.js.map +1 -1
- package/build/index.js +146 -26
- package/build/index.js.map +1 -1
- package/package.json +1 -1
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),
|
|
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
|
|
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
|
|
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
|
-
|
|
12619
|
-
|
|
12620
|
-
|
|
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
|
|
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 =
|
|
12634
|
-
if (!parsed || !
|
|
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 (
|
|
12749
|
+
if (animSpanRef.current) {
|
|
12650
12750
|
const displayValue = Number.isInteger(end) ? Math.round(current) : parseFloat(current.toFixed(2));
|
|
12651
|
-
|
|
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(
|
|
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 =
|
|
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
|
-
},
|
|
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: '
|
|
12856
|
-
padding: '
|
|
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
|
|
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
|
-
|
|
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:
|
|
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",
|
|
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
|
-
|
|
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:
|
|
13217
|
+
link: redirectUrl,
|
|
13098
13218
|
value: p.stickyButtonText
|
|
13099
13219
|
},
|
|
13100
13220
|
type: "primary",
|