| 1 |
( function () { |
| 2 |
var el = window.wp.element.createElement; |
| 3 |
var { registerBlockType } = window.wp.blocks; |
| 4 |
var { InspectorControls, PanelColorSettings } = window.wp.blockEditor; |
| 5 |
var { PanelBody, RangeControl, SelectControl, TextControl, TextareaControl, ToggleControl, Button, ColorPicker, Popover } = window.wp.components; |
| 6 |
var { __ } = window.wp.i18n; |
| 7 |
var { useEffect, useRef, useState } = window.wp.element; |
| 8 |
|
| 9 |
var LEG_POS = [{label:'Top',value:'top'},{label:'Bottom',value:'bottom'},{label:'Left',value:'left'},{label:'Right',value:'right'}]; |
| 10 |
|
| 11 |
function parseHex(hex, alpha) { |
| 12 |
hex = (hex||'#6c3fb5').replace('#',''); |
| 13 |
if (hex.length===3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]; |
| 14 |
var r=parseInt(hex.substring(0,2),16),g=parseInt(hex.substring(2,4),16),b=parseInt(hex.substring(4,6),16); |
| 15 |
return 'rgba('+r+','+g+','+b+','+(alpha/100).toFixed(2)+')'; |
| 16 |
} |
| 17 |
|
| 18 |
function parseBubbles(text) { |
| 19 |
return (text||'').split('\n').filter(Boolean).map(function(line) { |
| 20 |
var parts = line.split(',').map(function(s){return parseFloat(s)||0;}); |
| 21 |
return { x: parts[0]||0, y: parts[1]||0, r: parts[2]||8 }; |
| 22 |
}); |
| 23 |
} |
| 24 |
|
| 25 |
function BubblePreview(a) { |
| 26 |
var canvasRef = useRef(null); |
| 27 |
var chartRef = useRef(null); |
| 28 |
useEffect(function() { |
| 29 |
var canvas = canvasRef.current; if (!canvas) return; |
| 30 |
function render(ChartJS) { |
| 31 |
if (chartRef.current) { chartRef.current.destroy(); chartRef.current=null; } |
| 32 |
var datasets; |
| 33 |
try { datasets = JSON.parse(a.datasetsJson||'[]'); } catch(e){ datasets=[]; } |
| 34 |
var dsData = datasets.map(function(ds) { |
| 35 |
return { |
| 36 |
label: ds.label||'', |
| 37 |
data: parseBubbles(ds.bubbles), |
| 38 |
backgroundColor: parseHex(ds.color||'#6c3fb5', a.fillAlpha), |
| 39 |
borderColor: ds.color||'#6c3fb5', |
| 40 |
borderWidth: 2 |
| 41 |
}; |
| 42 |
}); |
| 43 |
var h = a.chartHeight; |
| 44 |
canvas.style.height = h+'px'; |
| 45 |
chartRef.current = new ChartJS(canvas, { |
| 46 |
type: 'bubble', |
| 47 |
data: { datasets: dsData }, |
| 48 |
options: { |
| 49 |
responsive: true, maintainAspectRatio: false, animation: false, |
| 50 |
plugins: { |
| 51 |
legend: { display: !!a.showLegend, position: a.legendPos, labels: { font: { size: a.labelFontSize||12 } } }, |
| 52 |
title: { display: !!(a.showTitle&&a.chartTitle), text: a.chartTitle, font: { size: a.titleFontSize||16 } } |
| 53 |
}, |
| 54 |
scales: { |
| 55 |
x: { grid: { display: !!a.showGrid } }, |
| 56 |
y: { grid: { display: !!a.showGrid } } |
| 57 |
} |
| 58 |
} |
| 59 |
}); |
| 60 |
} |
| 61 |
if (window.Chart) { render(window.Chart); return function(){if(chartRef.current){chartRef.current.destroy();chartRef.current=null;}};} |
| 62 |
var s = document.createElement('script'); |
| 63 |
s.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js'; |
| 64 |
s.onload = function(){ render(window.Chart); }; |
| 65 |
document.head.appendChild(s); |
| 66 |
return function(){ if(chartRef.current){chartRef.current.destroy();chartRef.current=null;} }; |
| 67 |
}, [a.datasetsJson, a.fillAlpha, a.chartHeight, a.showLegend, a.legendPos, a.showTitle, a.chartTitle, a.showGrid, a.titleFontSize, a.labelFontSize]); |
| 68 |
|
| 69 |
return el('div',{style:{background:a.bgColor,borderRadius:a.borderRadius+'px',padding:'24px'}}, |
| 70 |
el('div',{style:{position:'relative',height:a.chartHeight+'px'}}, |
| 71 |
el('canvas',{ref:canvasRef,style:{height:a.chartHeight+'px'}}))); |
| 72 |
} |
| 73 |
|
| 74 |
function DatasetEditor(a, set) { |
| 75 |
var datasets; |
| 76 |
try { datasets = JSON.parse(a.datasetsJson||'[]'); } catch(e){ datasets=[]; } |
| 77 |
function save(ds) { set({datasetsJson: JSON.stringify(ds)}); } |
| 78 |
return el('div',null, |
| 79 |
datasets.map(function(ds,i) { |
| 80 |
return el('div',{key:i,style:{border:'1px solid #e5e7eb',borderRadius:'8px',padding:'12px',marginBottom:'12px'}}, |
| 81 |
el('div',{style:{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:'8px'}}, |
| 82 |
el('strong',null,(ds.label||__('Dataset '+(i+1)))), |
| 83 |
el(Button,{isDestructive:true,isSmall:true,onClick:function(){var d=[...datasets];d.splice(i,1);save(d);}},__('Remove')), |
| 84 |
), |
| 85 |
el(TextControl,{label:__('Label'),value:ds.label||'',onChange:function(v){var d=[...datasets];d[i]={...d[i],label:v};save(d);}}), |
| 86 |
el(BkbgColorSwatch,{label:__('Color'),value:ds.color||'#6c3fb5',onChange:function(v){var d=[...datasets];d[i]={...d[i],color:v};save(d);}}), |
| 87 |
el('p',{style:{margin:'0 0 4px',fontSize:'11px',color:'#757575'}},__('Bubbles: x,y,radius — one per line')), |
| 88 |
el(TextareaControl,{label:__('Bubbles (x,y,r per line)'),value:ds.bubbles||'',rows:5,onChange:function(v){var d=[...datasets];d[i]={...d[i],bubbles:v};save(d);}}), |
| 89 |
); |
| 90 |
}), |
| 91 |
el(Button,{variant:'secondary',onClick:function(){var d=[...datasets,{label:'Dataset '+(datasets.length+1),color:'#10b981',bubbles:'10,10,8\n20,30,15'}];save(d);}},__('+ Add Dataset')), |
| 92 |
); |
| 93 |
} |
| 94 |
|
| 95 |
/* ── colour-swatch + popover ── */ |
| 96 |
function BkbgColorSwatch(p) { |
| 97 |
var st = useState(false), open = st[0], setOpen = st[1]; |
| 98 |
return el('div', { style:{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'4px 0', gap:'8px' } }, |
| 99 |
el('span', { style:{ fontSize:'12px', color:'#1e1e1e', flex:1, lineHeight:1.4 } }, p.label), |
| 100 |
el('div', { style:{ position:'relative', flexShrink:0 } }, |
| 101 |
el('button', { type:'button', title: p.value||'none', onClick: function(){ setOpen(!open); }, |
| 102 |
style:{ width:'28px', height:'28px', borderRadius:'4px', border: open ? '2px solid #007cba' : '2px solid #ddd', cursor:'pointer', padding:0, display:'block', background: p.value||'#ffffff', flexShrink:0 } }), |
| 103 |
open && el(Popover, { position:'bottom left', onClose: function(){ setOpen(false); } }, |
| 104 |
el('div', { style:{ padding:'8px' }, onMouseDown: function(e){ e.stopPropagation(); } }, |
| 105 |
el('div', { style:{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:'6px' } }, |
| 106 |
el('strong', { style:{ fontSize:'12px' } }, p.label), |
| 107 |
el(Button, { icon:'no-alt', isSmall:true, onClick: function(){ setOpen(false); } }) |
| 108 |
), |
| 109 |
el(ColorPicker, { color: p.value, enableAlpha:true, onChange: p.onChange }) |
| 110 |
) |
| 111 |
) |
| 112 |
) |
| 113 |
); |
| 114 |
} |
| 115 |
|
| 116 |
registerBlockType('blockenberg/bubble-chart', { |
| 117 |
icon: el('svg', { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', width: 24, height: 24 }, |
| 118 |
el('circle', { cx: 8, cy: 14, r: 5, fill: 'currentColor', opacity: 0.3 }), |
| 119 |
el('circle', { cx: 16, cy: 8, r: 4, fill: 'currentColor', opacity: 0.5 }), |
| 120 |
el('circle', { cx: 14, cy: 16, r: 3, fill: 'currentColor', opacity: 0.7 }), |
| 121 |
el('circle', { cx: 6, cy: 7, r: 2, fill: 'currentColor' }) |
| 122 |
), |
| 123 |
edit: function(props) { |
| 124 |
var a = props.attributes; |
| 125 |
var set = props.setAttributes; |
| 126 |
return el('div',null, |
| 127 |
el(InspectorControls,null, |
| 128 |
el(PanelBody,{title:__('Datasets'),initialOpen:true}, |
| 129 |
DatasetEditor(a, set), |
| 130 |
), |
| 131 |
el(PanelBody,{title:__('Chart Options'),initialOpen:false}, |
| 132 |
el(ToggleControl,{label:__('Show Title'),checked:a.showTitle,onChange:v=>set({showTitle:v}),__nextHasNoMarginBottom:true}), |
| 133 |
a.showTitle&&el(TextControl,{label:__('Title'),value:a.chartTitle,onChange:v=>set({chartTitle:v})}), |
| 134 |
el(RangeControl,{label:__('Chart Height (px)'),value:a.chartHeight,min:200,max:700,onChange:v=>set({chartHeight:v})}), |
| 135 |
el(RangeControl,{label:__('Fill Opacity %'),value:a.fillAlpha,min:10,max:100,onChange:v=>set({fillAlpha:v})}), |
| 136 |
el(ToggleControl,{label:__('Show Grid'),checked:a.showGrid,onChange:v=>set({showGrid:v}),__nextHasNoMarginBottom:true}), |
| 137 |
el(ToggleControl,{label:__('Show Legend'),checked:a.showLegend,onChange:v=>set({showLegend:v}),__nextHasNoMarginBottom:true}), |
| 138 |
a.showLegend&&el(SelectControl,{label:__('Legend Position'),value:a.legendPos,options:LEG_POS,onChange:v=>set({legendPos:v})}), |
| 139 |
el(RangeControl,{label:__('Border Radius (px)'),value:a.borderRadius,min:0,max:32,onChange:v=>set({borderRadius:v})}), |
| 140 |
), |
| 141 |
el(PanelBody,{title:__('Typography','blockenberg'),initialOpen:false}, |
| 142 |
el(RangeControl,{label:__('Title Font Size (px)','blockenberg'),value:a.titleFontSize,min:12,max:36,onChange:v=>set({titleFontSize:v})}), |
| 143 |
el(RangeControl,{label:__('Legend Font Size (px)','blockenberg'),value:a.labelFontSize,min:10,max:24,onChange:v=>set({labelFontSize:v})}), |
| 144 |
), |
| 145 |
el(PanelColorSettings,{title:__('Colors'),initialOpen:false,colorSettings:[ |
| 146 |
{label:__('Card Background'),value:a.bgColor,onChange:v=>set({bgColor:v||'#ffffff'})}, |
| 147 |
]}), |
| 148 |
), |
| 149 |
el(BubblePreview,a), |
| 150 |
); |
| 151 |
}, |
| 152 |
|
| 153 |
save: function({attributes:a}) { |
| 154 |
var chartData = {datasetsJson:a.datasetsJson,fillAlpha:a.fillAlpha,showLegend:a.showLegend,legendPos:a.legendPos,showTitle:a.showTitle,chartTitle:a.chartTitle,showGrid:a.showGrid}; |
| 155 |
return el('div',{className:'bkbbl-wrap','data-chart':JSON.stringify(chartData),'data-height':a.chartHeight,style:{background:a.bgColor,borderRadius:a.borderRadius+'px',padding:'24px'}}, |
| 156 |
el('div',{style:{position:'relative',height:a.chartHeight+'px'}}, |
| 157 |
el('canvas',{className:'bkbbl-canvas',style:{height:a.chartHeight+'px'}}))); |
| 158 |
} |
| 159 |
}); |
| 160 |
}() ); |
| 161 |
|