PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / kpi-tiles / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/kpi-tiles/frontend.js

231 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3 var _typoKeys = {
4 family:'font-family', weight:'font-weight', style:'font-style',
5 decoration:'text-decoration', transform:'text-transform',
6 sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m',
7 lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m',
8 letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m',
9 wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m'
10 };
11 function typoCssVarsForEl(el, obj, prefix) {
12 if (!obj || typeof obj !== 'object') return;
13 Object.keys(_typoKeys).forEach(function (k) {
14 var v = obj[k];
15 if (v === undefined || v === '' || v === null) return;
16 if (k === 'sizeDesktop' || k === 'sizeTablet' || k === 'sizeMobile') v = v + (obj.sizeUnit || 'px');
17 else if (k === 'lineHeightDesktop' || k === 'lineHeightTablet' || k === 'lineHeightMobile') v = v + (obj.lineHeightUnit || '');
18 else if (k === 'letterSpacingDesktop' || k === 'letterSpacingTablet' || k === 'letterSpacingMobile') v = v + (obj.letterSpacingUnit || 'px');
19 else if (k === 'wordSpacingDesktop' || k === 'wordSpacingTablet' || k === 'wordSpacingMobile') v = v + (obj.wordSpacingUnit || 'px');
20 el.style.setProperty(prefix + _typoKeys[k], String(v));
21 });
22 }
23 /* ── Sparkline SVG builder ─────────────────────────────────────── */
24 function buildSparkline(container, sparkStr, color, fill, width, height) {
25 var nums = (sparkStr || '').split(',').map(Number).filter(function (n) { return !isNaN(n); });
26 if (nums.length < 2) return;
27 var minV = Math.min.apply(null, nums);
28 var maxV = Math.max.apply(null, nums);
29 var range = maxV - minV || 1;
30 var h = height; var w = width;
31 var step = w / (nums.length - 1);
32 var pts = nums.map(function (n, i) {
33 return (i * step).toFixed(1) + ',' + ((1 - (n - minV) / range) * (h - 4) + 2).toFixed(1);
34 });
35 var polyPts = pts.join(' ');
36 var fillD = 'M ' + pts.join(' L ') + ' L ' + w + ',' + h + ' L 0,' + h + ' Z';
37
38 var ns = 'http://www.w3.org/2000/svg';
39 var svg = document.createElementNS(ns, 'svg');
40 svg.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
41 svg.setAttribute('width', w);
42 svg.setAttribute('height', h);
43 svg.classList.add('bkbg-kpi-spark');
44 svg.style.overflow = 'visible';
45
46 var path = document.createElementNS(ns, 'path');
47 path.setAttribute('d', fillD);
48 path.setAttribute('fill', fill);
49 path.setAttribute('stroke', 'none');
50 svg.appendChild(path);
51
52 var poly = document.createElementNS(ns, 'polyline');
53 poly.setAttribute('points', polyPts);
54 poly.setAttribute('fill', 'none');
55 poly.setAttribute('stroke', color);
56 poly.setAttribute('stroke-width', '2');
57 poly.setAttribute('stroke-linecap', 'round');
58 poly.setAttribute('stroke-linejoin', 'round');
59 svg.appendChild(poly);
60
61 container.appendChild(svg);
62 }
63
64 /* ── Trend helpers ─────────────────────────────────────────────── */
65 function trendIcon(dir) {
66 return dir === 'up' ? '' : dir === 'down' ? '' : dir === 'down-good' ? '' : '';
67 }
68 function trendClass(dir) {
69 return dir === 'up' ? 'bkbg-kpi-trend-up' :
70 dir === 'down' ? 'bkbg-kpi-trend-down' :
71 dir === 'down-good' ? 'bkbg-kpi-trend-downgood' : 'bkbg-kpi-trend-neutral';
72 }
73 function trendBgColor(dir, o) {
74 return dir === 'up' ? o.upColor :
75 dir === 'down' ? o.downColor :
76 dir === 'down-good' ? o.downGoodColor : '#64748b';
77 }
78
79 /* ── Number animation counter ──────────────────────────────────── */
80 function animateValue(el, endStr, duration) {
81 /* Extract leading/trailing non-numeric for display */
82 var match = endStr.match(/^([^0-9]*)([0-9,\.]+)([^0-9]*)$/);
83 if (!match) return; /* not a simple number */
84 var pre = match[1];
85 var numStr = match[2].replace(/,/g, '');
86 var suf = match[3];
87 var end = parseFloat(numStr);
88 if (isNaN(end)) return;
89 var isInt = Number.isInteger(end);
90 var start = 0;
91 var startTime = null;
92 function step(timestamp) {
93 if (!startTime) startTime = timestamp;
94 var progress = Math.min((timestamp - startTime) / duration, 1);
95 var eased = 1 - Math.pow(1 - progress, 3);
96 var cur = start + (end - start) * eased;
97 var display = isInt ? Math.round(cur).toLocaleString() : cur.toFixed(1);
98 el.textContent = pre + display + suf;
99 if (progress < 1) requestAnimationFrame(step);
100 }
101 requestAnimationFrame(step);
102 }
103
104 /* ── Build one tile ────────────────────────────────────────────── */
105 function buildTile(tile, o) {
106 var dir = tile.direction || 'up';
107 var tileEl = document.createElement('div');
108 tileEl.className = 'bkbg-kpi-tile';
109 tileEl.style.cssText = [
110 'background:' + o.cardBg,
111 'border-radius:' + o.cardRadius + 'px',
112 'padding:' + o.cardPadding + 'px',
113 'border:1px solid ' + (o.borderColor || 'transparent'),
114 'box-shadow:0 2px 16px ' + (o.cardShadow || 'rgba(0,0,0,0.08)')
115 ].join(';');
116
117 /* Top row */
118 var top = document.createElement('div');
119 top.className = 'bkbg-kpi-top';
120
121 if (o.showIcon !== false && tile.icon) {
122 var icon = document.createElement('span');
123 icon.className = 'bkbg-kpi-icon';
124 icon.style.fontSize = o.iconSize + 'px';
125 var _IP = window.bkbgIconPicker;
126 var _iType = tile.iconType || 'custom-char';
127 if (_IP && _iType !== 'custom-char') {
128 var _in = _IP.buildFrontendIcon(_iType, tile.icon, tile.iconDashicon, tile.iconImageUrl, tile.iconDashiconColor);
129 if (_in) icon.appendChild(_in); else icon.textContent = tile.icon;
130 } else { icon.textContent = tile.icon; }
131 top.appendChild(icon);
132 }
133
134 if (o.showTrend !== false && tile.trend) {
135 var trend = document.createElement('span');
136 trend.className = 'bkbg-kpi-trend ' + trendClass(dir);
137 trend.style.color = trendBgColor(dir, o);
138 trend.textContent = trendIcon(dir) + ' ' + tile.trend;
139 top.appendChild(trend);
140 }
141 tileEl.appendChild(top);
142
143 /* Value */
144 var val = document.createElement('div');
145 val.className = 'bkbg-kpi-value';
146 val.style.color = o.valueColor || '#0f172a';
147 val.textContent = tile.value || '';
148 tileEl.appendChild(val);
149
150 /* Label */
151 var lbl = document.createElement('div');
152 lbl.className = 'bkbg-kpi-label';
153 lbl.style.color = o.labelColor || '#64748b';
154 lbl.textContent = tile.label || '';
155 tileEl.appendChild(lbl);
156
157 /* Sparkline */
158 if (o.showSparkline !== false && tile.sparkline) {
159 buildSparkline(tileEl, tile.sparkline,
160 o.sparklineColor || '#6366f1',
161 o.sparklineFill || 'rgba(99,102,241,0.12)',
162 o.sparklineWidth || 120,
163 o.sparklineHeight || 48
164 );
165 }
166
167 return { el: tileEl, valEl: val, tile: tile };
168 }
169
170 /* ── Intersection observer for number animation ─────────────────── */
171 function observeTiles(items, o) {
172 if (!o.animateNumbers || !window.IntersectionObserver) return;
173 var io = new IntersectionObserver(function (entries) {
174 entries.forEach(function (entry) {
175 if (!entry.isIntersecting) return;
176 var target = entry.target;
177 var idx = parseInt(target.dataset.kpiIdx, 10);
178 if (!isNaN(idx) && items[idx]) {
179 animateValue(items[idx].valEl, items[idx].tile.value, 1200);
180 }
181 io.unobserve(target);
182 });
183 }, { threshold: 0.3 });
184
185 items.forEach(function (item, idx) {
186 item.el.dataset.kpiIdx = idx;
187 io.observe(item.el);
188 });
189 }
190
191 /* ── Mount ─────────────────────────────────────────────────────── */
192 function initBlock(root) {
193 var app = root.querySelector('.bkbg-kpi-app');
194 if (!app) return;
195
196 var raw = app.getAttribute('data-opts') || '{}';
197 var o;
198 try { o = JSON.parse(raw); } catch (e) { o = {}; }
199
200 var tiles = Array.isArray(o.tiles) ? o.tiles : [];
201 var cols = Number(o.columns) || 4;
202
203 typoCssVarsForEl(root, o.valueTypo, '--bkbg-kpi-v-');
204 typoCssVarsForEl(root, o.labelTypo, '--bkbg-kpi-l-');
205 typoCssVarsForEl(root, o.trendTypo, '--bkbg-kpi-tr-');
206
207 var grid = document.createElement('div');
208 grid.className = 'bkbg-kpi-grid';
209 grid.style.gridTemplateColumns = 'repeat(' + cols + ', 1fr)';
210
211 var items = tiles.map(function (tile) {
212 var item = buildTile(tile, o);
213 grid.appendChild(item.el);
214 return item;
215 });
216
217 app.appendChild(grid);
218 observeTiles(items, o);
219 }
220
221 function init() {
222 document.querySelectorAll('.wp-block-blockenberg-kpi-tiles').forEach(initBlock);
223 }
224
225 if (document.readyState === 'loading') {
226 document.addEventListener('DOMContentLoaded', init);
227 } else {
228 init();
229 }
230 })();
231