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.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),
|
|
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
|
|
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
|
|
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
|
-
|
|
12633
|
-
|
|
12634
|
-
|
|
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
|
|
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 =
|
|
12648
|
-
if (!parsed || !
|
|
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 (
|
|
12763
|
+
if (animSpanRef.current) {
|
|
12664
12764
|
const displayValue = Number.isInteger(end) ? Math.round(current) : parseFloat(current.toFixed(2));
|
|
12665
|
-
|
|
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(
|
|
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 =
|
|
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
|
-
},
|
|
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: '
|
|
12870
|
-
padding: '
|
|
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
|
|
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
|
-
|
|
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:
|
|
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",
|
|
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
|
-
|
|
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:
|
|
13231
|
+
link: redirectUrl,
|
|
13112
13232
|
value: p.stickyButtonText
|
|
13113
13233
|
},
|
|
13114
13234
|
type: "primary",
|