PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.11
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.11
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 / progress-circle / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.11, at blocks/progress-circle/index.js

511 lines 33.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 var el = wp.element.createElement;
3 var Fragment = wp.element.Fragment;
4 var __ = wp.i18n.__;
5 var useState = wp.element.useState;
6 var registerBlockType = wp.blocks.registerBlockType;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var useBlockProps = wp.blockEditor.useBlockProps;
9 var PanelBody = wp.components.PanelBody;
10 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
11 var ToggleControl = wp.components.ToggleControl;
12 var RangeControl = wp.components.RangeControl;
13 var SelectControl = wp.components.SelectControl;
14 var TextControl = wp.components.TextControl;
15 var Button = wp.components.Button;
16 var ColorPicker = wp.components.ColorPicker;
17 var Popover = wp.components.Popover;
18
19 var _tc; function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
20 var _tv; function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
21
22 /* ── SVG ring helper ──────────────────────────────────────────────────────── */
23 function CircleRing(props) {
24 var size = props.size, sw = props.sw, pct = props.pct, color = props.color,
25 track = props.track, linecap = props.linecap, gradient = props.gradient, gradEnd = props.gradEnd, shadow = props.shadow;
26 var r = (size - sw) / 2;
27 var cx = size / 2, cy = size / 2;
28 var circ = 2 * Math.PI * r;
29 var dash = circ * (pct / 100);
30 var uid = 'bkpc-g-' + color.replace('#', '');
31
32 return el('svg', {
33 width: size, height: size, viewBox: '0 0 ' + size + ' ' + size,
34 style: { display: 'block', margin: '0 auto', filter: shadow ? 'drop-shadow(0 4px 12px rgba(0,0,0,0.15))' : 'none' }
35 },
36 gradient && el('defs', null,
37 el('linearGradient', { id: uid, x1: '0%', y1: '0%', x2: '100%', y2: '100%' },
38 el('stop', { offset: '0%', stopColor: color }),
39 el('stop', { offset: '100%', stopColor: gradEnd })
40 )
41 ),
42 el('circle', { cx: cx, cy: cy, r: r, fill: 'none', stroke: track, strokeWidth: sw }),
43 el('circle', {
44 cx: cx, cy: cy, r: r, fill: 'none',
45 stroke: gradient ? 'url(#' + uid + ')' : color,
46 strokeWidth: sw,
47 strokeLinecap: linecap,
48 strokeDasharray: circ,
49 strokeDashoffset: circ - dash,
50 transform: 'rotate(-90 ' + cx + ' ' + cy + ')'
51 })
52 );
53 }
54
55 /* ── single card ─────────────────────────────────────────────────────────── */
56 function CircleCard(props) {
57 var a = props.attr, item = props.item, idx = props.idx, isEdit = props.isEdit;
58 var onUpdate = props.onUpdate, onRemove = props.onRemove;
59 var colorState = useState(false);
60 var showColorPicker = colorState[0], setShowColorPicker = colorState[1];
61
62 var linecap = a.ringStyle === 'round' ? 'round' : a.ringStyle === 'square' ? 'square' : 'butt';
63
64 var cardStyle = {};
65 if (a.showBackground) {
66 cardStyle = {
67 background: a.backgroundColor,
68 borderRadius: a.backgroundRadius + 'px',
69 padding: a.backgroundPadding + 'px',
70 boxShadow: a.backgroundShadow ? '0 4px 20px rgba(0,0,0,0.08)' : 'none',
71 };
72 }
73
74 return el('div', { className: 'bkpc-item', style: { textAlign: a.textAlign, ...cardStyle } },
75 isEdit && el('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: '4px', marginBottom: '4px' } },
76 el(Button, {
77 isSmall: true, variant: 'tertiary', style: { position: 'relative' },
78 onClick: function () { setShowColorPicker(!showColorPicker); }
79 }, '🎨'),
80 showColorPicker && el(Popover, { onClose: function () { setShowColorPicker(false); }, placement: 'bottom-start' },
81 el('div', { style: { padding: '8px' } },
82 el(ColorPicker, { color: item.color, onChange: function (v) { onUpdate(idx, 'color', v); } })
83 )
84 ),
85 el(Button, {
86 isSmall: true, variant: 'tertiary', isDestructive: true,
87 onClick: function () { onRemove(idx); }
88 }, '×')
89 ),
90 el(CircleRing, {
91 size: a.size, sw: a.strokeWidth, pct: item.percentage,
92 color: item.color, track: a.trackColor, linecap: linecap,
93 gradient: a.showGradient, gradEnd: a.gradientEnd, shadow: a.showShadow
94 }),
95 el('div', { className: 'bkpc-center-text', style: { textAlign: a.textAlign } },
96 isEdit
97 ? el('div', { style: {
98 position: 'relative', marginTop: '-' + (a.size / 2 + 16) + 'px',
99 height: (a.size / 2 + 16) + 'px', display: 'flex',
100 alignItems: 'center', justifyContent: 'center', pointerEvents: 'none'
101 }},
102 a.showPercentage && el('div', {
103 className: 'bkpc-pct',
104 style: { color: a.percentageColor }
105 }, item.percentage + (a.suffix || '%'))
106 )
107 : null,
108 isEdit && el(RangeControl, {
109 label: __('Percentage', 'blockenberg'), value: item.percentage, min: 0, max: 100,
110 onChange: function (v) { onUpdate(idx, 'percentage', v); }
111 }),
112 isEdit && el(TextControl, {
113 label: __('Label', 'blockenberg'), value: item.label,
114 onChange: function (v) { onUpdate(idx, 'label', v); }
115 }),
116 isEdit && a.showSubtext && el(TextControl, {
117 label: __('Subtext', 'blockenberg'), value: item.subtext || '',
118 onChange: function (v) { onUpdate(idx, 'subtext', v); }
119 })
120 ),
121 !isEdit && a.showLabel && a.labelPosition === 'below' && el('p', {
122 className: 'bkpc-label',
123 style: { color: a.labelColor, margin: '8px 0 0' }
124 }, item.label),
125 !isEdit && a.showSubtext && item.subtext && el('p', {
126 className: 'bkpc-subtext',
127 style: { color: a.subtextColor, margin: '2px 0 0' }
128 }, item.subtext)
129 );
130 }
131
132 registerBlockType('blockenberg/progress-circle', {
133 title: __('Progress Circle', 'blockenberg'),
134 icon: 'chart-pie',
135 category: 'bkbg-content',
136 description: __('Animated SVG circular progress rings.', 'blockenberg'),
137
138 edit: function (props) {
139 var attributes = props.attributes;
140 var setAttributes = props.setAttributes;
141 var a = attributes;
142
143 function updateItem(idx, key, val) {
144 var arr = a.items.slice();
145 arr[idx] = Object.assign({}, arr[idx]);
146 arr[idx][key] = val;
147 setAttributes({ items: arr });
148 }
149 function addItem() {
150 var palette = ['#6c3fb5','#3b82f6','#10b981','#f59e0b','#ef4444','#ec4899'];
151 setAttributes({ items: a.items.concat([{ label: 'New Skill', percentage: 70, color: palette[a.items.length % palette.length], subtext: '' }]) });
152 }
153 function removeItem(idx) {
154 setAttributes({ items: a.items.filter(function (_, i) { return i !== idx; }) });
155 }
156
157 var ringStyleOptions = [
158 { label: __('Round caps', 'blockenberg'), value: 'round' },
159 { label: __('Square caps', 'blockenberg'), value: 'square' },
160 { label: __('Flat caps', 'blockenberg'), value: 'flat' },
161 ];
162 var easingOptions = [
163 { label: 'ease-in-out', value: 'ease-in-out' },
164 { label: 'ease-out', value: 'ease-out' },
165 { label: 'ease-in', value: 'ease-in' },
166 { label: 'linear', value: 'linear' },
167 ];
168 var alignOptions = [
169 { label: __('Left', 'blockenberg'), value: 'left' },
170 { label: __('Center', 'blockenberg'), value: 'center' },
171 ];
172 var labelPosOptions = [
173 { label: __('Below ring', 'blockenberg'), value: 'below' },
174 { label: __('Inside ring', 'blockenberg'), value: 'inside' },
175 ];
176 var TC = getTypoControl();
177
178 var blockProps = useBlockProps((function () {
179 var _tv = getTypoCssVars();
180 var s = {};
181 if (_tv) {
182 Object.assign(s, _tv(a.pctTypo || {}, '--bkpc-pct-'));
183 Object.assign(s, _tv(a.labelTypo || {}, '--bkpc-lb-'));
184 Object.assign(s, _tv(a.subtextTypo || {}, '--bkpc-st-'));
185 }
186 return { className: 'bkpc-wrap', style: s };
187 })());
188
189 return el(Fragment, null,
190 el(InspectorControls, null,
191
192 /* — Items — */
193 el(PanelBody, { title: __('Items', 'blockenberg'), initialOpen: true },
194 el('p', { style: { fontSize: '12px', color: '#6b7280', margin: '0 0 12px' } },
195 __('Edit each ring directly in the canvas below. Use the + to add more.', 'blockenberg')
196 ),
197 el(Button, { variant: 'primary', onClick: addItem, style: { width: '100%' } },
198 __('+ Add Ring', 'blockenberg'))
199 ),
200
201 /* — Layout — */
202 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: false },
203 el(RangeControl, { label: __('Columns', 'blockenberg'), value: a.columns, min: 1, max: 8, onChange: function (v) { setAttributes({ columns: v }); } }),
204 el(RangeControl, { label: __('Ring Size (px)', 'blockenberg'), value: a.size, min: 60, max: 300, onChange: function (v) { setAttributes({ size: v }); } }),
205 el(RangeControl, { label: __('Stroke Width', 'blockenberg'), value: a.strokeWidth, min: 2, max: 40, onChange: function (v) { setAttributes({ strokeWidth: v }); } }),
206 el(SelectControl, { label: __('Ring Style', 'blockenberg'), value: a.ringStyle, options: ringStyleOptions, onChange: function (v) { setAttributes({ ringStyle: v }); } }),
207 el(SelectControl, { label: __('Text Align', 'blockenberg'), value: a.textAlign, options: alignOptions, onChange: function (v) { setAttributes({ textAlign: v }); } }),
208 el(RangeControl, { label: __('Column Gap (px)', 'blockenberg'), value: a.gap, min: 8, max: 80, onChange: function (v) { setAttributes({ gap: v }); } }),
209 el(TextControl, { label: __('Suffix (e.g. % or pts)', 'blockenberg'), value: a.suffix, onChange: function (v) { setAttributes({ suffix: v }); } })
210 ),
211
212 /* — Typography — */
213 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
214 el(ToggleControl, { label: __('Show Percentage', 'blockenberg'), checked: a.showPercentage, onChange: function (v) { setAttributes({ showPercentage: v }); }, __nextHasNoMarginBottom: true }),
215 el('hr', { style: { margin: '12px 0' } }),
216 el(ToggleControl, { label: __('Show Label', 'blockenberg'), checked: a.showLabel, onChange: function (v) { setAttributes({ showLabel: v }); }, __nextHasNoMarginBottom: true }),
217 a.showLabel && el(SelectControl, { label: __('Label Position', 'blockenberg'), value: a.labelPosition, options: labelPosOptions, onChange: function (v) { setAttributes({ labelPosition: v }); } }),
218 el('hr', { style: { margin: '12px 0' } }),
219 el(ToggleControl, { label: __('Show Subtext', 'blockenberg'), checked: a.showSubtext, onChange: function (v) { setAttributes({ showSubtext: v }); }, __nextHasNoMarginBottom: true }),
220 TC && a.showPercentage && el(TC, { label: __('Percentage', 'blockenberg'), value: a.pctTypo || {}, onChange: function (v) { setAttributes({ pctTypo: v }); } }),
221 TC && a.showLabel && el(TC, { label: __('Label', 'blockenberg'), value: a.labelTypo || {}, onChange: function (v) { setAttributes({ labelTypo: v }); } }),
222 TC && a.showSubtext && el(TC, { label: __('Subtext', 'blockenberg'), value: a.subtextTypo || {}, onChange: function (v) { setAttributes({ subtextTypo: v }); } })
223 ),
224
225 /* — Style — */
226 el(PanelBody, { title: __('Ring Style', 'blockenberg'), initialOpen: false },
227 el(ToggleControl, { label: __('Gradient Fill', 'blockenberg'), checked: a.showGradient, onChange: function (v) { setAttributes({ showGradient: v }); }, __nextHasNoMarginBottom: true }),
228 el(ToggleControl, { label: __('Drop Shadow', 'blockenberg'), checked: a.showShadow, onChange: function (v) { setAttributes({ showShadow: v }); }, __nextHasNoMarginBottom: true })
229 ),
230
231 /* — Card Background — */
232 el(PanelBody, { title: __('Card Background', 'blockenberg'), initialOpen: false },
233 el(ToggleControl, { label: __('Show Card Background', 'blockenberg'), checked: a.showBackground, onChange: function (v) { setAttributes({ showBackground: v }); }, __nextHasNoMarginBottom: true }),
234 a.showBackground && el(RangeControl, { label: __('Corner Radius', 'blockenberg'), value: a.backgroundRadius, min: 0, max: 40, onChange: function (v) { setAttributes({ backgroundRadius: v }); } }),
235 a.showBackground && el(RangeControl, { label: __('Padding', 'blockenberg'), value: a.backgroundPadding, min: 0, max: 60, onChange: function (v) { setAttributes({ backgroundPadding: v }); } }),
236 a.showBackground && el(ToggleControl, { label: __('Card Shadow', 'blockenberg'), checked: a.backgroundShadow, onChange: function (v) { setAttributes({ backgroundShadow: v }); }, __nextHasNoMarginBottom: true })
237 ),
238
239 /* — Animation — */
240 el(PanelBody, { title: __('Animation', 'blockenberg'), initialOpen: false },
241 el(ToggleControl, { label: __('Animate on Scroll', 'blockenberg'), checked: a.animateOnScroll, onChange: function (v) { setAttributes({ animateOnScroll: v }); }, __nextHasNoMarginBottom: true }),
242 el(ToggleControl, { label: __('Counter Animation', 'blockenberg'), checked: a.counterAnimation, onChange: function (v) { setAttributes({ counterAnimation: v }); }, __nextHasNoMarginBottom: true }),
243 el(RangeControl, { label: __('Duration (ms)', 'blockenberg'), value: a.animationDuration, min: 300, max: 3000, step: 100, onChange: function (v) { setAttributes({ animationDuration: v }); } }),
244 el(SelectControl, { label: __('Easing', 'blockenberg'), value: a.animationEasing, options: easingOptions, onChange: function (v) { setAttributes({ animationEasing: v }); } })
245 ),
246
247 /* — Colors — */
248 el(PanelColorSettings, {
249 title: __('Colors', 'blockenberg'),
250 initialOpen: false,
251 colorSettings: [
252 { label: __('Track Color', 'blockenberg'), value: a.trackColor, onChange: function (v) { setAttributes({ trackColor: v || '#e5e7eb' }); } },
253 { label: __('Percentage Color', 'blockenberg'), value: a.percentageColor, onChange: function (v) { setAttributes({ percentageColor: v || '#1e1e1e' }); } },
254 { label: __('Label Color', 'blockenberg'), value: a.labelColor, onChange: function (v) { setAttributes({ labelColor: v || '#374151' }); } },
255 { label: __('Subtext Color', 'blockenberg'), value: a.subtextColor, onChange: function (v) { setAttributes({ subtextColor: v || '#9ca3af' }); } },
256 { label: __('Gradient End Color', 'blockenberg'), value: a.gradientEnd, onChange: function (v) { setAttributes({ gradientEnd: v || '#ec4899' }); } },
257 { label: __('Card Background', 'blockenberg'), value: a.backgroundColor, onChange: function (v) { setAttributes({ backgroundColor: v || '#ffffff' }); } },
258 ]
259 })
260 ),
261
262 /* ── Canvas ─────────────────────────────────────────────────── */
263 el('div', blockProps,
264 el('div', {
265 className: 'bkpc-grid',
266 style: { display: 'grid', gridTemplateColumns: 'repeat(' + a.columns + ', 1fr)', gap: a.gap + 'px' }
267 },
268 a.items.map(function (item, idx) {
269 return el(CircleCard, { key: idx, attr: a, item: item, idx: idx, isEdit: true, onUpdate: updateItem, onRemove: removeItem });
270 })
271 ),
272 el('div', { style: { textAlign: 'center', marginTop: '12px' } },
273 el(Button, { variant: 'primary', onClick: addItem }, __('+ Add Ring', 'blockenberg'))
274 )
275 )
276 );
277 },
278
279 save: function (props) {
280 var a = props.attributes;
281 var blockProps = wp.blockEditor.useBlockProps.save((function () {
282 var _tv = getTypoCssVars();
283 var s = {};
284 if (_tv) {
285 Object.assign(s, _tv(a.pctTypo || {}, '--bkpc-pct-'));
286 Object.assign(s, _tv(a.labelTypo || {}, '--bkpc-lb-'));
287 Object.assign(s, _tv(a.subtextTypo || {}, '--bkpc-st-'));
288 }
289 return { className: 'bkpc-wrap', style: s };
290 })());
291 var linecap = a.ringStyle === 'round' ? 'round' : a.ringStyle === 'square' ? 'square' : 'butt';
292
293 return el('div', blockProps,
294 el('div', {
295 className: 'bkpc-grid',
296 'data-animate': a.animateOnScroll ? '1' : '0',
297 'data-duration': a.animationDuration,
298 'data-easing': a.animationEasing,
299 'data-counter': a.counterAnimation ? '1' : '0',
300 'data-suffix': a.suffix,
301 style: { display: 'grid', gridTemplateColumns: 'repeat(' + a.columns + ', 1fr)', gap: a.gap + 'px' }
302 },
303 a.items.map(function (item, idx) {
304 var cardStyle = {};
305 if (a.showBackground) {
306 cardStyle = {
307 background: a.backgroundColor,
308 borderRadius: a.backgroundRadius + 'px',
309 padding: a.backgroundPadding + 'px',
310 boxShadow: a.backgroundShadow ? '0 4px 20px rgba(0,0,0,0.08)' : 'none',
311 };
312 }
313 var circ = 2 * Math.PI * ((a.size - a.strokeWidth) / 2);
314 var uid = 'bkpc-g-' + idx + '-' + item.color.replace('#', '');
315 return el('div', {
316 key: idx, className: 'bkpc-item',
317 'data-pct': item.percentage, 'data-linecap': linecap,
318 'data-size': a.size, 'data-sw': a.strokeWidth,
319 'data-color': item.color, 'data-track': a.trackColor,
320 'data-gradient': a.showGradient ? '1' : '0',
321 'data-grad-end': a.gradientEnd, 'data-shadow': a.showShadow ? '1' : '0',
322 style: { textAlign: a.textAlign, ...cardStyle }
323 },
324 /* SVG ring */
325 el('svg', {
326 className: 'bkpc-svg',
327 width: a.size, height: a.size, viewBox: '0 0 ' + a.size + ' ' + a.size,
328 style: { display: 'block', margin: '0 auto', filter: a.showShadow ? 'drop-shadow(0 4px 12px rgba(0,0,0,0.15))' : 'none' }
329 },
330 a.showGradient && el('defs', null,
331 el('linearGradient', { id: uid, x1: '0%', y1: '0%', x2: '100%', y2: '100%' },
332 el('stop', { offset: '0%', stopColor: item.color }),
333 el('stop', { offset: '100%', stopColor: a.gradientEnd })
334 )
335 ),
336 el('circle', { className: 'bkpc-track', cx: a.size/2, cy: a.size/2, r: (a.size - a.strokeWidth)/2, fill: 'none', stroke: a.trackColor, strokeWidth: a.strokeWidth }),
337 el('circle', {
338 className: 'bkpc-ring',
339 cx: a.size/2, cy: a.size/2, r: (a.size - a.strokeWidth)/2,
340 fill: 'none',
341 stroke: a.showGradient ? 'url(#' + uid + ')' : item.color,
342 strokeWidth: a.strokeWidth,
343 strokeLinecap: linecap,
344 strokeDasharray: circ,
345 strokeDashoffset: a.animateOnScroll ? circ : circ - circ * (item.percentage / 100),
346 transform: 'rotate(-90 ' + a.size/2 + ' ' + a.size/2 + ')',
347 style: { transition: 'stroke-dashoffset ' + (a.animationDuration/1000) + 's ' + a.animationEasing }
348 })
349 ),
350 /* center text overlay — absolutely positioned via CSS */
351 a.showPercentage && a.labelPosition !== 'inside' && el('div', {
352 className: 'bkpc-pct',
353 style: { color: a.percentageColor, marginTop: '8px' }
354 }, item.percentage + (a.suffix || '%')),
355 a.labelPosition === 'inside' && el('div', {
356 className: 'bkpc-inside',
357 style: { marginTop: '-' + (a.size / 2 + a.strokeWidth) + 'px', height: a.size + 'px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' }
358 },
359 a.showPercentage && el('span', {
360 className: 'bkpc-pct-inner',
361 style: { color: a.percentageColor }
362 }, item.percentage + (a.suffix || '%')),
363 a.showLabel && el('span', {
364 className: 'bkpc-label-inner',
365 style: { color: a.labelColor, marginTop: '4px' }
366 }, item.label)
367 ),
368 a.showLabel && a.labelPosition === 'below' && el('p', {
369 className: 'bkpc-label',
370 style: { color: a.labelColor, margin: '8px 0 0' }
371 }, item.label),
372 a.showSubtext && item.subtext && el('p', {
373 className: 'bkpc-subtext',
374 style: { color: a.subtextColor, margin: '2px 0 0' }
375 }, item.subtext)
376 );
377 })
378 )
379 );
380 },
381
382 deprecated: [{
383 attributes: {
384 items: { type: 'array', default: [{ label: 'Design', percentage: 90, color: '#6c3fb5' }, { label: 'Development', percentage: 82, color: '#3b82f6' }, { label: 'Marketing', percentage: 74, color: '#10b981' }, { label: 'Strategy', percentage: 68, color: '#f59e0b' }] },
385 columns: { type: 'number', default: 4 },
386 size: { type: 'number', default: 140 },
387 strokeWidth: { type: 'number', default: 10 },
388 ringStyle: { type: 'string', default: 'round' },
389 trackColor: { type: 'string', default: '#e5e7eb' },
390 showPercentage: { type: 'boolean', default: true },
391 percentageColor: { type: 'string', default: '#1e1e1e' },
392 percentageSize: { type: 'number', default: 26 },
393 percentageWeight: { type: 'number', default: 700 },
394 showLabel: { type: 'boolean', default: true },
395 labelColor: { type: 'string', default: '#374151' },
396 labelSize: { type: 'number', default: 14 },
397 labelWeight: { type: 'number', default: 600 },
398 labelPosition: { type: 'string', default: 'below' },
399 showSubtext: { type: 'boolean', default: false },
400 subtextColor: { type: 'string', default: '#9ca3af' },
401 subtextSize: { type: 'number', default: 12 },
402 gap: { type: 'number', default: 32 },
403 animateOnScroll: { type: 'boolean', default: true },
404 animationDuration: { type: 'number', default: 1200 },
405 animationEasing: { type: 'string', default: 'ease-in-out' },
406 showGradient: { type: 'boolean', default: false },
407 gradientEnd: { type: 'string', default: '#ec4899' },
408 showShadow: { type: 'boolean', default: false },
409 showBackground: { type: 'boolean', default: false },
410 backgroundColor: { type: 'string', default: '#ffffff' },
411 backgroundRadius: { type: 'number', default: 16 },
412 backgroundPadding: { type: 'number', default: 20 },
413 backgroundShadow: { type: 'boolean', default: true },
414 textAlign: { type: 'string', default: 'center' },
415 counterAnimation: { type: 'boolean', default: true },
416 suffix: { type: 'string', default: '%' }
417 },
418 save: function (props) {
419 var a = props.attributes;
420 var blockProps = wp.blockEditor.useBlockProps.save({ className: 'bkpc-wrap' });
421 var linecap = a.ringStyle === 'round' ? 'round' : a.ringStyle === 'square' ? 'square' : 'butt';
422 return el('div', blockProps,
423 el('div', {
424 className: 'bkpc-grid',
425 'data-animate': a.animateOnScroll ? '1' : '0',
426 'data-duration': a.animationDuration,
427 'data-easing': a.animationEasing,
428 'data-counter': a.counterAnimation ? '1' : '0',
429 'data-suffix': a.suffix,
430 style: { display: 'grid', gridTemplateColumns: 'repeat(' + a.columns + ', 1fr)', gap: a.gap + 'px' }
431 },
432 a.items.map(function (item, idx) {
433 var cardStyle = {};
434 if (a.showBackground) {
435 cardStyle = {
436 background: a.backgroundColor,
437 borderRadius: a.backgroundRadius + 'px',
438 padding: a.backgroundPadding + 'px',
439 boxShadow: a.backgroundShadow ? '0 4px 20px rgba(0,0,0,0.08)' : 'none',
440 };
441 }
442 var circ = 2 * Math.PI * ((a.size - a.strokeWidth) / 2);
443 var uid = 'bkpc-g-' + idx + '-' + item.color.replace('#', '');
444 return el('div', {
445 key: idx, className: 'bkpc-item',
446 'data-pct': item.percentage, 'data-linecap': linecap,
447 'data-size': a.size, 'data-sw': a.strokeWidth,
448 'data-color': item.color, 'data-track': a.trackColor,
449 'data-gradient': a.showGradient ? '1' : '0',
450 'data-grad-end': a.gradientEnd, 'data-shadow': a.showShadow ? '1' : '0',
451 style: { textAlign: a.textAlign, ...cardStyle }
452 },
453 el('svg', {
454 className: 'bkpc-svg',
455 width: a.size, height: a.size, viewBox: '0 0 ' + a.size + ' ' + a.size,
456 style: { display: 'block', margin: '0 auto', filter: a.showShadow ? 'drop-shadow(0 4px 12px rgba(0,0,0,0.15))' : 'none' }
457 },
458 a.showGradient && el('defs', null,
459 el('linearGradient', { id: uid, x1: '0%', y1: '0%', x2: '100%', y2: '100%' },
460 el('stop', { offset: '0%', stopColor: item.color }),
461 el('stop', { offset: '100%', stopColor: a.gradientEnd })
462 )
463 ),
464 el('circle', { className: 'bkpc-track', cx: a.size/2, cy: a.size/2, r: (a.size - a.strokeWidth)/2, fill: 'none', stroke: a.trackColor, strokeWidth: a.strokeWidth }),
465 el('circle', {
466 className: 'bkpc-ring',
467 cx: a.size/2, cy: a.size/2, r: (a.size - a.strokeWidth)/2,
468 fill: 'none',
469 stroke: a.showGradient ? 'url(#' + uid + ')' : item.color,
470 strokeWidth: a.strokeWidth,
471 strokeLinecap: linecap,
472 strokeDasharray: circ,
473 strokeDashoffset: a.animateOnScroll ? circ : circ - circ * (item.percentage / 100),
474 transform: 'rotate(-90 ' + a.size/2 + ' ' + a.size/2 + ')',
475 style: { transition: 'stroke-dashoffset ' + (a.animationDuration/1000) + 's ' + a.animationEasing }
476 })
477 ),
478 a.showPercentage && a.labelPosition !== 'inside' && el('div', {
479 className: 'bkpc-pct',
480 style: { fontSize: a.percentageSize + 'px', fontWeight: a.percentageWeight, color: a.percentageColor, marginTop: '8px', lineHeight: 1 }
481 }, item.percentage + (a.suffix || '%')),
482 a.labelPosition === 'inside' && el('div', {
483 className: 'bkpc-inside',
484 style: { marginTop: '-' + (a.size / 2 + a.strokeWidth) + 'px', height: a.size + 'px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' }
485 },
486 a.showPercentage && el('span', {
487 className: 'bkpc-pct-inner',
488 style: { fontSize: a.percentageSize + 'px', fontWeight: a.percentageWeight, color: a.percentageColor, lineHeight: 1 }
489 }, item.percentage + (a.suffix || '%')),
490 a.showLabel && el('span', {
491 className: 'bkpc-label-inner',
492 style: { fontSize: a.labelSize + 'px', fontWeight: a.labelWeight, color: a.labelColor, marginTop: '4px', lineHeight: 1 }
493 }, item.label)
494 ),
495 a.showLabel && a.labelPosition === 'below' && el('p', {
496 className: 'bkpc-label',
497 style: { fontSize: a.labelSize + 'px', fontWeight: a.labelWeight, color: a.labelColor, margin: '8px 0 0' }
498 }, item.label),
499 a.showSubtext && item.subtext && el('p', {
500 className: 'bkpc-subtext',
501 style: { fontSize: a.subtextSize + 'px', color: a.subtextColor, margin: '2px 0 0' }
502 }, item.subtext)
503 );
504 })
505 )
506 );
507 }
508 }]
509 });
510 }() );
511