1/** 2 * Blaek Statistics Highlight Widget 3 * 4 * Handles: 5 * 1. Chart.js doughnut chart initialisation 6 * 2. Full-page-width breakout (same pattern as servicelinks-widget) 7 */ 8( function ( $ ) { 9 'use strict'; 10 11 // Track widgets already initialised by the Elementor element_ready hook so the 12 // window.load fallback does not re-run applyPageWidth a second time. 13 var initializedByHook = {}; 14 15 /* ââ External tooltip (frosted glass pill) ââââââââââââââ */ 16 17 function getOrCreateTooltip( chart ) { 18 var tooltipEl = chart.canvas.parentNode.querySelector( '.blaek-statistics-highlight__tooltip' ); 19 20 if ( ! tooltipEl ) { 21 tooltipEl = document.createElement( 'div' ); 22 tooltipEl.className = 'blaek-statistics-highlight__tooltip'; 23 24 var inner = document.createElement( 'div' ); 25 inner.className = 'blaek-statistics-highlight__tooltip-inner'; 26 tooltipEl.appendChild( inner ); 27 28 chart.canvas.parentNode.appendChild( tooltipEl ); 29 } 30 31 return tooltipEl; 32 } 33 34 function externalTooltipHandler( context ) { 35 var chart = context.chart; 36 var tooltip = context.tooltip; 37 var tooltipEl = getOrCreateTooltip( chart ); 38 39 // Hide if no tooltip 40 if ( tooltip.opacity === 0 ) { 41 tooltipEl.style.opacity = '0'; 42 tooltipEl.style.pointerEvents = 'none'; 43 return; 44 } 45 46 // Build text 47 if ( tooltip.body ) { 48 var bodyLines = tooltip.body.map( function ( b ) { return b.lines; } ); 49 var innerEl = tooltipEl.querySelector( '.blaek-statistics-highlight__tooltip-inner' ); 50 innerEl.textContent = ''; 51 52 bodyLines.forEach( function ( lines ) { 53 lines.forEach( function ( line ) { 54 var span = document.createElement( 'span' ); 55 span.textContent = line; 56 innerEl.appendChild( span ); 57 } ); 58 } ); 59 } 60 61 // Position: place tooltip relative to the chart canvas parent 62 var canvasRect = chart.canvas.getBoundingClientRect(); 63 var parentRect = chart.canvas.parentNode.getBoundingClientRect(); 64 65 // Tooltip position from Chart.js (relative to canvas) 66 var left = tooltip.caretX; 67 var top = tooltip.caretY; 68 69 // Offset from canvas to parent 70 var offsetLeft = canvasRect.left - parentRect.left; 71 var offsetTop = canvasRect.top - parentRect.top; 72 73 tooltipEl.style.opacity = '1'; 74 tooltipEl.style.left = ( offsetLeft + left ) + 'px'; 75 tooltipEl.style.top = ( offsetTop + top ) + 'px'; 76 } 77 78 /* ââ Chart initialisation âââââââââââââââââââââââââââââââ */ 79 80 function initChart( $chartEl ) { 81 var $canvas = $chartEl.find( '.blaek-statistics-highlight__canvas' ); 82 if ( ! $canvas.length ) { 83 return; 84 } 85 86 if ( typeof Chart === 'undefined' ) { 87 return; 88 } 89 90 // Destroy existing instance (Elementor editor re-renders) 91 var existing = Chart.getChart( $canvas[ 0 ] ); 92 if ( existing ) { 93 existing.destroy(); 94 } 95 96 // Remove old tooltip element if it exists 97 var oldTooltip = $chartEl[ 0 ].querySelector( '.blaek-statistics-highlight__tooltip' ); 98 if ( oldTooltip ) { 99 oldTooltip.remove(); 100 } 101 102 var chartData = $chartEl.data( 'chart' ); 103 if ( ! chartData || ! chartData.labels ) { 104 return; 105 } 106 107 var cutout = $chartEl.data( 'chart-cutout' ) || 65; 108 var borderWidth = $chartEl.data( 'chart-border-width' ); 109 var borderColor = $chartEl.data( 'chart-border-color' ) || 'transparent'; 110 111 if ( typeof borderWidth === 'undefined' || borderWidth === null ) { 112 borderWidth = 2; 113 } 114 115 new Chart( $canvas[ 0 ], { 116 type: 'doughnut', 117 data: { 118 labels: chartData.labels, 119 datasets: [ { 120 data: chartData.values, 121 backgroundColor: chartData.colors, 122 borderWidth: borderWidth, 123 borderColor: borderColor, 124 hoverOffset: 8 125 } ] 126 }, 127 options: { 128 responsive: true, 129 maintainAspectRatio: true, 130 cutout: cutout + '%', 131 plugins: { 132 legend: { 133 display: false 134 }, 135 tooltip: { 136 enabled: false, 137 external: externalTooltipHandler, 138 callbacks: { 139 label: function ( context ) { 140 var total = context.dataset.data.reduce( function ( a, b ) { 141 return a + b; 142 }, 0 ); 143 var pct = ( ( context.parsed / total ) * 100 ).toFixed( 2 ).replace( '.', ',' ); 144 return context.label + ': ' + pct + ' %'; 145 } 146 } 147 } 148 } 149 } 150 } ); 151 } 152 153 /* ââ Full-page-width breakout âââââââââââââââââââââââââââ */ 154 155 function applyPageWidth( $wrapper ) { 156 if ( $wrapper.data( 'page-width' ) !== 'true' && $wrapper.data( 'page-width' ) !== true ) { 157 return; 158 } 159 160 var $target = $wrapper.closest( '.elementor-widget-blaek-statistics-highlight' ); 161 if ( ! $target.length ) { 162 $target = $wrapper; 163 } 164 165 var vw = document.documentElement.clientWidth; 166 167 // Use (vw - containerWidth) / 2 to derive margin-left. 168 // offsetWidth is unaffected by CSS transforms (e.g. Elementor entrance 169 // animations) and by any inline styles on $target itself, so it always 170 // reflects the column's natural layout width regardless of timing. 171 var $parent = $target.parent(); 172 var containerWidth = $parent.length ? $parent[ 0 ].offsetWidth : vw; 173 var marginLeft = -( ( vw - containerWidth ) / 2 ); 174 175 $target.css( { 176 width : vw + 'px', 177 'max-width' : vw + 'px', 178 'margin-left': marginLeft + 'px' 179 } ); 180 } 181 182 /* ââ Widget init ââââââââââââââââââââââââââââââââââââââââ */ 183 184 function initWidget( $scope ) { 185 var $wrappers; 186 187 if ( $scope && $scope.length ) { 188 if ( $scope.hasClass( 'blaek-statistics-highlight' ) ) { 189 $wrappers = $scope; 190 } else if ( $scope.find( '.blaek-statistics-highlight' ).length ) { 191 $wrappers = $scope.find( '.blaek-statistics-highlight' ); 192 } else if ( $scope.closest( '.blaek-statistics-highlight' ).length ) { 193 $wrappers = $scope.closest( '.blaek-statistics-highlight' ); 194 } else { 195 $wrappers = $(); 196 } 197 } else { 198 $wrappers = $( '.blaek-statistics-highlight' ); 199 } 200 201 if ( ! $wrappers.length ) { 202 return; 203 } 204 205 $wrappers.each( function () { 206 var $wrapper = $( this ); 207 var widgetId = $wrapper.closest( '.elementor-widget' ).attr( 'data-id' ); 208 209 if ( widgetId ) { 210 initializedByHook[ widgetId ] = true; 211 } 212 213 // Page width 214 applyPageWidth( $wrapper ); 215 $( window ).off( 'resize.blaekShPageWidth-' + widgetId ); 216 $( window ).on( 'resize.blaekShPageWidth-' + widgetId, function () { 217 applyPageWidth( $wrapper ); 218 } ); 219 220 // Chart 221 var $chartEl = $wrapper.find( '.blaek-statistics-highlight__chart' ); 222 if ( $chartEl.length ) { 223 initChart( $chartEl ); 224 } 225 } ); 226 } 227 228 /* ââ Elementor frontend hook ââââââââââââââââââââââââââââ */ 229 230 $( window ).on( 'elementor/frontend/init', function () { 231 if ( typeof elementorFrontend !== 'undefined' && elementorFrontend.hooks ) { 232 elementorFrontend.hooks.addAction( 233 'frontend/element_ready/blaek-statistics-highlight.default', 234 initWidget 235 ); 236 } 237 } ); 238 239 /* ââ Standard page-load fallback ââââââââââââââââââââââââ */ 240 // Skip any widget already handled by the element_ready hook above. 241 242 $( window ).on( 'load', function () { 243 setTimeout( function () { 244 $( '.blaek-statistics-highlight' ).each( function () { 245 var $wrapper = $( this ); 246 var widgetId = $wrapper.closest( '.elementor-widget' ).attr( 'data-id' ); 247 if ( ! initializedByHook[ widgetId ] ) { 248 initWidget( $wrapper ); 249 } 250 } ); 251 }, 200 ); 252 } ); 253 254}( jQuery ) );
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.