1((document, Drupal) => { 2 let rootMargin = '0px'; 3 4 const desktopBP = window.matchMedia('(min-width: 1440px)'); 5 const largeTabletBP = window.matchMedia('(min-width: 1200px)'); 6 const tabletBP = window.matchMedia('(min-width: 768px)'); 7 const mobileBP = window.matchMedia('(min-width: 330px)'); 8 9 if (mobileBP.matches) { 10 rootMargin = '0px 0px -13% 0px'; 11 } 12 if (tabletBP.matches) { 13 rootMargin = '0px 0px -13% 0px'; 14 } 15 if (largeTabletBP.matches) { 16 rootMargin = '0px 0px -27% 0px'; 17 } 18 if (desktopBP.matches) { 19 rootMargin = '0px 0px -27% 0px'; 20 } 21 22 const observer = new IntersectionObserver( 23 // eslint-disable-next-line no-shadow 24 (entries, observer) => { 25 entries.forEach((entry) => { 26 if (entry.isIntersecting) { 27 const stats = entry.target.querySelectorAll( 28 '.dxp-stats__stat-element__inner__number', 29 ); 30 const animateSpeed = entry.target.dataset.animateSpeed 31 ? entry.target.dataset.animateSpeed 32 : 2000; 33 const easingType = entry.target.dataset.animateEasing 34 ? entry.target.dataset.animateEasing 35 : 'linear'; 36 const { locale } = entry.target.dataset; 37 // eslint-disable-next-line no-use-before-define 38 const easingFunction = chooseEasingFunction(easingType);
39 stats.forEach((stat) => { 40 // eslint-disable-next-line no-use-before-define 41 animateNumber( 42 stat, 43 0, 44 stat.dataset.number, 45 stat.dataset.suffix, 46 animateSpeed, 47 easingFunction, 48 locale, 49 stat.dataset.decimalCount, 50 ); 51 }); 52 observer.unobserve(entry.target); 53 } 54 }); 55 }, 56 { 57 rootMargin, 58 threshold: 0.001, 59 }, 60 ); 61 62 function init() { 63 const dxpStats = document.querySelectorAll('.js--dxp-stats-animated'); 64 const defaultLocale = 'en-US'; 65 66 if (dxpStats.length > 0) { 67 dxpStats.forEach((dxpStat) => { 68 if (!dxpStat.dataset.animationStarted) { 69 const stats = dxpStat.querySelectorAll( 70 '.dxp-stats__stat-element__inner__number', 71 ); 72 const locale = dxpStat.dataset.locale 73 ? dxpStat.dataset.locale 74 : defaultLocale; 75 stats.forEach((stat) => { 76 // eslint-disable-next-line no-use-before-define 77 resetNumber(stat, locale); 78 }); 79 observer.observe(dxpStat); 80 dxpStat.dataset.animationStarted = '1'; 81 } 82 }); 83 } 84 } 85 86 Drupal.behaviors.statsAnimation = { 87 attach() { 88 init(); 89 }, 90 }; 91 92 document.addEventListener( 93 'DOMContentLoaded', 94 // eslint-disable-next-line func-names 95 function () { 96 init(); 97 }, 98 { once: true }, 99 ); 100 101 function chooseEasingFunction(easingType) { 102 switch (easingType) { 103 case 'linear': 104 // eslint-disable-next-line no-use-before-define 105 return linear; 106 case 'easeOut': 107 // eslint-disable-next-line no-use-before-define 108 return easeOutCubic; 109 case 'easeIn': 110 // eslint-disable-next-line no-use-before-define 111 return easeInCubic; 112 case 'easeInOutCubic': 113 // eslint-disable-next-line no-use-before-define 114 return easeInOutCubic; 115 default: 116 // eslint-disable-next-line no-use-before-define 117 return linear; // Default to linear if not specified 118 } 119 } 120 121 function linear(t) { 122 return t; 123 } 124 125 function easeOutCubic(t) { 126 return 1 - (1 - t) ** 3; 127 } 128 129 function easeInCubic(t) { 130 return t * t * t; 131 } 132 133 function easeInOutCubic(t) { 134 return t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2; 135 } 136 137 function parseNumber(content, locale) { 138 // eslint-disable-next-line no-use-before-define 139 const { decimalSeparator, thousandSeparator } = detectSeparators(locale); 140 // Escape special characters for regex use 141 const escapedThousand = thousandSeparator.replace( 142 /[.*+?^${}()|[\]\\]/g, 143 '\\$&', 144 ); 145 const escapedDecimal = decimalSeparator.replace( 146 /[.*+?^${}()|[\]\\]/g, 147 '\\$&', 148 ); 149 let decimalCount = 0; 150 151 // Regex to find the numeric part with potential formatting and an optional suffix 152 const numberRegex = new RegExp( 153 `([\\d${escapedThousand}${escapedDecimal}]+)([^\\d]*)`, 154 ); 155 156 // Extract the numeric part and any suffix 157 const result = content.match(numberRegex); 158 let value = 0; 159 let suffix = ''; 160 161 if (result) { 162 let numberPart = result[1]; 163 // eslint-disable-next-line prefer-destructuring 164 suffix = result[2]; 165 166 // Replace thousand separator and fix decimal separator for parsing 167 numberPart = numberPart.replace(new RegExp(escapedThousand, 'g'), ''); // Remove thousand separators 168 numberPart = numberPart.replace(new RegExp(escapedDecimal, 'g'), '.'); // Ensure correct decimal separator for parsing 169 decimalCount = numberPart.includes('.') 170 ? numberPart.split('.')[1].length 171 : 0; 172 173 // Parse the number 174 value = parseFloat(numberPart); 175 } 176 return { value, suffix, decimalCount }; 177 } 178 179 function animateNumber( 180 element, 181 start, 182 end, 183 suffix, 184 duration, 185 easingFunc, 186 locale, 187 digits, 188 ) { 189 let startTime = null; 190 191 function animation(currentTime) { 192 if (!startTime) startTime = currentTime; 193 const elapsedTime = currentTime - startTime; 194 const progress = Math.min(elapsedTime / duration, 1); 195 const easedProgress = easingFunc(progress); // Apply the easing function 196 const currentNumber = easedProgress * (end - start) + start; 197 198 // element.textContent = customFormatNumber(currentNumber, { locale: 'de-DE' }) + suffix; 199 element.textContent = 200 // eslint-disable-next-line no-use-before-define 201 customFormatNumber(currentNumber, { 202 locale, 203 minimumFractionDigits: digits, 204 maximumFractionDigits: digits, 205 }) + suffix; 206 207 if (elapsedTime < duration) { 208 requestAnimationFrame(animation); 209 } 210 } 211 212 requestAnimationFrame(animation); 213 } 214 215 function customFormatNumber(value, config) { 216 const { 217 locale, 218 useGrouping = true, 219 minimumFractionDigits = 0, 220 maximumFractionDigits = 0, 221 } = config; 222 const options = { 223 style: 'decimal', 224 useGrouping, 225 minimumFractionDigits, 226 maximumFractionDigits, 227 }; 228 229 const formatter = new Intl.NumberFormat(locale, options); 230 return formatter.format(value); 231 } 232 233 function resetNumber(element, locale) { 234 const originalContent = element.textContent; 235 const parsedNumber = parseNumber(originalContent, locale); 236 element.style.opacity = '1'; 237 element.dataset.number = parsedNumber.value.toString(); 238 element.dataset.decimalCount = parsedNumber.decimalCount.toString(); 239 element.dataset.suffix = parsedNumber.suffix; 240 // eslint-disable-next-line no-use-before-define 241 element.textContent = formatNumber(0) + parsedNumber.suffix; 242 } 243 244 function formatNumber(number) {
245 return number.toLocaleString(); 246 } 247 248 function detectSeparators(locale) { 249 const number = 1234567.89; // Use a sample number that includes both a thousand and decimal separator 250 const formattedNumber = new Intl.NumberFormat(locale).format(number); 251 252 // Extract the non-numeric characters from the formatted string 253 // Assuming the formatted number includes a decimal and thousands separator 254 const match = formattedNumber.match(/(\D+)/g); 255 256 // Heuristic: Assume the last group of non-digit characters is the decimal separator, 257 // and the first (if any) is the thousand separator 258 if (match && match.length > 0) { 259 const separators = { 260 thousandSeparator: match.length > 1 ? match[0] : '', 261 decimalSeparator: match[match.length - 1], 262 }; 263 264 // Clean up any extra characters that might be in the separators 265 separators.thousandSeparator = separators.thousandSeparator.replace( 266 /\s/g, 267 '', 268 ); // Remove any whitespace 269 separators.decimalSeparator = separators.decimalSeparator.replace( 270 /\s/g, 271 '', 272 ); // Remove any whitespace 273 274 return separators; 275 } 276 277 return { 278 thousandSeparator: '', 279 decimalSeparator: '', 280 }; 281 } 282})(document, Drupal);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.