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 / bullet-chart / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/bullet-chart/index.js

228 lines 16.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 const el = window.wp.element.createElement;
3 const { registerBlockType } = window.wp.blocks;
4 const { InspectorControls, useBlockProps, PanelColorSettings } = window.wp.blockEditor;
5 const { PanelBody, RangeControl, ToggleControl, TextControl, Button, ColorPicker, Popover } = window.wp.components;
6 const { __ } = window.wp.i18n;
7 const { useState } = window.wp.element;
8
9 function getTypographyControl() {
10 return (window.bkbgTypographyControl || function () { return null; });
11 }
12
13 function _tv(typo, prefix) {
14 if (!typo) return {};
15 var s = {};
16 if (typo.family) s[prefix + 'font-family'] = "'" + typo.family + "', sans-serif";
17 if (typo.weight) s[prefix + 'font-weight'] = typo.weight;
18 if (typo.transform) s[prefix + 'text-transform'] = typo.transform;
19 if (typo.style) s[prefix + 'font-style'] = typo.style;
20 if (typo.decoration) s[prefix + 'text-decoration'] = typo.decoration;
21 var su = typo.sizeUnit || 'px';
22 if (typo.sizeDesktop !== '' && typo.sizeDesktop != null) s[prefix + 'font-size-d'] = typo.sizeDesktop + su;
23 if (typo.sizeTablet !== '' && typo.sizeTablet != null) s[prefix + 'font-size-t'] = typo.sizeTablet + su;
24 if (typo.sizeMobile !== '' && typo.sizeMobile != null) s[prefix + 'font-size-m'] = typo.sizeMobile + su;
25 var lhu = typo.lineHeightUnit || '';
26 if (typo.lineHeightDesktop !== '' && typo.lineHeightDesktop != null) s[prefix + 'line-height-d'] = typo.lineHeightDesktop + lhu;
27 if (typo.lineHeightTablet !== '' && typo.lineHeightTablet != null) s[prefix + 'line-height-t'] = typo.lineHeightTablet + lhu;
28 if (typo.lineHeightMobile !== '' && typo.lineHeightMobile != null) s[prefix + 'line-height-m'] = typo.lineHeightMobile + lhu;
29 var lsu = typo.letterSpacingUnit || 'px';
30 if (typo.letterSpacingDesktop !== '' && typo.letterSpacingDesktop != null) s[prefix + 'letter-spacing-d'] = typo.letterSpacingDesktop + lsu;
31 if (typo.letterSpacingTablet !== '' && typo.letterSpacingTablet != null) s[prefix + 'letter-spacing-t'] = typo.letterSpacingTablet + lsu;
32 if (typo.letterSpacingMobile !== '' && typo.letterSpacingMobile != null) s[prefix + 'letter-spacing-m'] = typo.letterSpacingMobile + lsu;
33 var wsu = typo.wordSpacingUnit || 'px';
34 if (typo.wordSpacingDesktop !== '' && typo.wordSpacingDesktop != null) s[prefix + 'word-spacing-d'] = typo.wordSpacingDesktop + wsu;
35 if (typo.wordSpacingTablet !== '' && typo.wordSpacingTablet != null) s[prefix + 'word-spacing-t'] = typo.wordSpacingTablet + wsu;
36 if (typo.wordSpacingMobile !== '' && typo.wordSpacingMobile != null) s[prefix + 'word-spacing-m'] = typo.wordSpacingMobile + wsu;
37 return s;
38 }
39
40 // ── Renderer ──────────────────────────────────────────────────────────────
41 function renderBullet( a ) {
42 const series = a.series || [];
43 const W = a.svgWidth;
44 const PT = a.padTop;
45 const PL = a.padLeft;
46 const PR = a.padRight;
47 const RH = a.rowHeight;
48 const BT = a.barThickness;
49 const TW = a.targetWidth;
50
51 const H = PT + series.length * RH + 20;
52 const chartW = W - PL - PR;
53
54 function scaleX( v, max ) { return PL + ( v / ( max || 1 ) ) * chartW; }
55
56 const svgEls = [];
57 svgEls.push( el( 'rect', { key: 'bg', x: 0, y: 0, width: W, height: H, fill: a.bgColor || '#ffffff', rx: 8 } ) );
58
59 // Grid ticks at top
60 if ( a.showGridLines ) {
61 const ticks = 5;
62 for ( let t = 0; t <= ticks; t++ ) {
63 const gx = PL + ( t / ticks ) * chartW;
64 svgEls.push( el( 'line', { key: `gt${ t }`, x1: gx, y1: PT - 4, x2: gx, y2: H - 10, stroke: a.gridColor || '#f3f4f6', strokeWidth: 1, strokeDasharray: '3 3' } ) );
65 }
66 }
67
68 series.forEach( ( s, i ) => {
69 const max = s.max || 100;
70 const cy = PT + i * RH + RH / 2;
71 const bh = BT;
72 const by = cy - bh / 2;
73 const clr = s.color || '#6366f1';
74
75 // Background bands: poor → sat → good → max
76 const bands = [
77 { from: 0, to: s.poor || 0, color: a.bandColorPoor || '#f3f4f6' },
78 { from: s.poor, to: s.sat || 0, color: a.bandColorSat || '#e5e7eb' },
79 { from: s.sat, to: s.good || 0, color: a.bandColorGood || '#d1d5db' },
80 { from: s.good, to: max, color: '#c8cdd5' },
81 ];
82 const bandH = RH * 0.72;
83 bands.forEach( ( band, bi ) => {
84 if ( band.to <= band.from ) return;
85 const bx = scaleX( band.from, max );
86 const bw = scaleX( band.to, max ) - bx;
87 svgEls.push( el( 'rect', { key: `band${ i }${ bi }`, x: bx, y: cy - bandH / 2, width: bw, height: bandH, fill: band.color, rx: bi === 0 ? 3 : 0 } ) );
88 } );
89
90 // Actual bar
91 const actualW = scaleX( s.actual || 0, max ) - PL;
92 svgEls.push( el( 'rect', { key: `bar${ i }`, x: PL, y: by, width: Math.max( 0, actualW ), height: bh, fill: clr, rx: 3 } ) );
93
94 // Target marker
95 const tx = scaleX( s.target || 0, max );
96 svgEls.push( el( 'rect', { key: `tgt${ i }`, x: tx - TW / 2, y: cy - bandH * 0.55, width: TW, height: bandH * 1.1, fill: a.targetColor || '#111827', rx: 1 } ) );
97
98 // Row label
99 svgEls.push( el( 'text', { key: `lbl${ i }`, x: PL - 8, y: cy, textAnchor: 'end', dominantBaseline: 'middle', fill: a.labelColor, fontSize: a.labelFontSize, fontFamily: 'inherit' }, s.label || '' ) );
100
101 // Value label (actual / target)
102 if ( a.showValues ) {
103 const dispA = parseFloat( ( s.actual || 0 ).toFixed( 1 ) );
104 const dispT = parseFloat( ( s.target || 0 ).toFixed( 1 ) );
105 svgEls.push( el( 'text', { key: `val${ i }`, x: W - PR + 6, y: cy, dominantBaseline: 'middle', fill: clr, fontSize: a.valueFontSize, fontWeight: 700, fontFamily: 'inherit' }, `${ dispA } / ${ dispT }` ) );
106 }
107 } );
108
109 // Legend row
110 const legY = H - 14;
111 [
112 { label: 'Poor', color: a.bandColorPoor || '#f3f4f6' },
113 { label: 'Satisfactory', color: a.bandColorSat || '#e5e7eb' },
114 { label: 'Good', color: a.bandColorGood || '#d1d5db' },
115 { label: 'Target', color: a.targetColor || '#111827', isLine: true },
116 ].forEach( ( it, li ) => {
117 const lx = PL + li * 130;
118 if ( it.isLine ) {
119 svgEls.push( el( 'rect', { key: `lgn${ li }`, x: lx, y: legY - 6, width: 4, height: 12, fill: it.color } ) );
120 } else {
121 svgEls.push( el( 'rect', { key: `lgn${ li }`, x: lx, y: legY - 5, width: 18, height: 10, fill: it.color, rx: 2 } ) );
122 }
123 svgEls.push( el( 'text', { key: `lgt${ li }`, x: lx + 22, y: legY, dominantBaseline: 'middle', fill: a.labelColor, fontSize: 10, fontFamily: 'inherit' }, it.label ) );
124 } );
125
126 return el( 'svg', { viewBox: `0 0 ${ W } ${ H }`, width: '100%', style: { display: 'block', maxWidth: W + 'px', margin: '0 auto' } }, ...svgEls );
127 }
128
129 function updSer( setAttributes, series, si, field, val ) {
130 setAttributes( { series: series.map( ( s, i ) => i === si ? { ...s, [field]: val } : s ) } );
131 }
132
133 /* ── colour-swatch + popover ── */
134 function BkbgColorSwatch(p) {
135 var st = useState(false), open = st[0], setOpen = st[1];
136 return el('div', { style:{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'4px 0', gap:'8px' } },
137 el('span', { style:{ fontSize:'12px', color:'#1e1e1e', flex:1, lineHeight:1.4 } }, p.label),
138 el('div', { style:{ position:'relative', flexShrink:0 } },
139 el('button', { type:'button', title: p.value||'none', onClick: function(){ setOpen(!open); },
140 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 } }),
141 open && el(Popover, { position:'bottom left', onClose: function(){ setOpen(false); } },
142 el('div', { style:{ padding:'8px' }, onMouseDown: function(e){ e.stopPropagation(); } },
143 el('div', { style:{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:'6px' } },
144 el('strong', { style:{ fontSize:'12px' } }, p.label),
145 el(Button, { icon:'no-alt', isSmall:true, onClick: function(){ setOpen(false); } })
146 ),
147 el(ColorPicker, { color: p.value, enableAlpha:true, onChange: p.onChange })
148 )
149 )
150 )
151 );
152 }
153
154 registerBlockType( 'blockenberg/bullet-chart', {
155 edit: function ( props ) {
156 const { attributes: a, setAttributes } = props;
157 const blockProps = useBlockProps( { className: 'bkbg-bullet-wrap', style: Object.assign( {}, _tv( a.typoTitle, '--bkbg-bullet-title-' ) ) } );
158
159 return el( 'div', blockProps,
160 el( InspectorControls, {},
161
162 el( PanelBody, { title: __( 'Chart Settings', 'blockenberg' ), initialOpen: true },
163 el( TextControl, { label: __( 'Title', 'blockenberg' ), value: a.title, onChange: v => setAttributes( { title: v } ) } ),
164 el( ToggleControl, { label: __( 'Show Title', 'blockenberg' ), checked: a.showTitle, onChange: v => setAttributes( { showTitle: v } ) } ),
165 el( ToggleControl, { label: __( 'Show Values', 'blockenberg' ), checked: a.showValues, onChange: v => setAttributes( { showValues: v } ) } ),
166 el( ToggleControl, { label: __( 'Show Grid Lines', 'blockenberg' ), checked: a.showGridLines, onChange: v => setAttributes( { showGridLines: v } ) } ),
167 ),
168
169 el( PanelBody, { title: __( 'Layout', 'blockenberg' ), initialOpen: false },
170 el( RangeControl, { label: __( 'Canvas Width', 'blockenberg' ), value: a.svgWidth, onChange: v => setAttributes( { svgWidth: v } ), min: 300, max: 1200, step: 20 } ),
171 el( RangeControl, { label: __( 'Row Height', 'blockenberg' ), value: a.rowHeight, onChange: v => setAttributes( { rowHeight: v } ), min: 28, max: 100 } ),
172 el( RangeControl, { label: __( 'Bar Thickness', 'blockenberg' ), value: a.barThickness, onChange: v => setAttributes( { barThickness: v } ), min: 4, max: 40 } ),
173 el( RangeControl, { label: __( 'Target Marker Width', 'blockenberg' ), value: a.targetWidth, onChange: v => setAttributes( { targetWidth: v } ), min: 2, max: 12 } ),
174 el( RangeControl, { label: __( 'Label Column Width', 'blockenberg' ), value: a.padLeft, onChange: v => setAttributes( { padLeft: v } ), min: 60, max: 300 } ),
175 ),
176
177
178 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
179 el(getTypographyControl(), { label: __('Title', 'blockenberg'), value: a.typoTitle, onChange: function (v) { setAttributes({ typoTitle: v }); } }),
180 el( RangeControl, { label: __( 'Label Font Size', 'blockenberg' ), value: a.labelFontSize, onChange: v => setAttributes( { labelFontSize: v } ), min: 9, max: 22 } ),
181 el( RangeControl, { label: __( 'Value Font Size', 'blockenberg' ), value: a.valueFontSize, onChange: v => setAttributes( { valueFontSize: v } ), min: 7, max: 16 } )
182 ),
183 el( PanelColorSettings, {
184 title: __( 'Colors', 'blockenberg' ), initialOpen: false,
185 colorSettings: [
186 { label: __( 'Band: Poor', 'blockenberg' ), value: a.bandColorPoor, onChange: v => setAttributes( { bandColorPoor: v || '#f3f4f6' } ) },
187 { label: __( 'Band: Satisfactory', 'blockenberg' ), value: a.bandColorSat, onChange: v => setAttributes( { bandColorSat: v || '#e5e7eb' } ) },
188 { label: __( 'Band: Good', 'blockenberg' ), value: a.bandColorGood, onChange: v => setAttributes( { bandColorGood: v || '#d1d5db' } ) },
189 { label: __( 'Target Marker', 'blockenberg' ), value: a.targetColor, onChange: v => setAttributes( { targetColor: v || '#111827' } ) },
190 { label: __( 'Background', 'blockenberg' ), value: a.bgColor, onChange: v => setAttributes( { bgColor: v || '#ffffff' } ) },
191 { label: __( 'Title Color', 'blockenberg' ), value: a.titleColor, onChange: v => setAttributes( { titleColor: v || '#111827' } ) },
192 { label: __( 'Label Color', 'blockenberg' ), value: a.labelColor, onChange: v => setAttributes( { labelColor: v || '#374151' } ) },
193 ]
194 } ),
195
196 el( PanelBody, { title: __( 'Series', 'blockenberg' ), initialOpen: false },
197 el( Button, { variant: 'secondary', style: { marginBottom: 10 }, onClick: () => setAttributes( { series: [ ...a.series, { label: 'New KPI', color: '#6366f1', actual: 60, target: 80, poor: 20, sat: 50, good: 80, max: 100 } ] } ) }, __( '+ Add KPI', 'blockenberg' ) ),
198 a.series.map( ( s, si ) =>
199 el( PanelBody, { key: si, title: s.label || `KPI ${ si + 1 }`, initialOpen: false },
200 el( TextControl, { label: __( 'Label', 'blockenberg' ), value: s.label, onChange: v => updSer( setAttributes, a.series, si, 'label', v ) } ),
201 el( BkbgColorSwatch, { label: __( 'Color', 'blockenberg' ), value: s.color, onChange: v => updSer( setAttributes, a.series, si, 'color', v ) } ),
202 el( RangeControl, { label: __( 'Actual Value', 'blockenberg' ), value: s.actual, onChange: v => updSer( setAttributes, a.series, si, 'actual', v ), min: 0, max: s.max || 500, step: 0.1 } ),
203 el( RangeControl, { label: __( 'Target', 'blockenberg' ), value: s.target, onChange: v => updSer( setAttributes, a.series, si, 'target', v ), min: 0, max: s.max || 500, step: 0.1 } ),
204 el( RangeControl, { label: __( 'Poor threshold', 'blockenberg' ), value: s.poor, onChange: v => updSer( setAttributes, a.series, si, 'poor', v ), min: 0, max: s.max || 500 } ),
205 el( RangeControl, { label: __( 'Satisfactory thresh.', 'blockenberg' ), value: s.sat, onChange: v => updSer( setAttributes, a.series, si, 'sat', v ), min: 0, max: s.max || 500 } ),
206 el( RangeControl, { label: __( 'Good threshold', 'blockenberg' ), value: s.good, onChange: v => updSer( setAttributes, a.series, si, 'good', v ), min: 0, max: s.max || 500 } ),
207 el( RangeControl, { label: __( 'Max (axis end)', 'blockenberg' ), value: s.max, onChange: v => updSer( setAttributes, a.series, si, 'max', v ), min: 1, max: 100000 } ),
208 el( Button, { isDestructive: true, isSmall: true, onClick: () => setAttributes( { series: a.series.filter( ( _, x ) => x !== si ) } ) }, __( 'Remove', 'blockenberg' ) ),
209 )
210 ),
211 ),
212 ),
213
214 a.showTitle && a.title && el( 'h3', { className: 'bkbg-bullet-title', style: { color: a.titleColor } }, a.title ),
215 el( 'div', { className: 'bkbg-bullet-svg' }, renderBullet( a ) ),
216 );
217 },
218
219 save: function ( { attributes: a } ) {
220 const blockProps = useBlockProps.save( { className: 'bkbg-bullet-wrap', style: Object.assign( {}, _tv( a.typoTitle, '--bkbg-bullet-title-' ) ) } );
221 return el( 'div', blockProps,
222 a.showTitle && a.title ? el( 'h3', { className: 'bkbg-bullet-title', style: { color: a.titleColor } }, a.title ) : null,
223 el( 'div', { className: 'bkbg-bullet-svg' }, renderBullet( a ) ),
224 );
225 },
226 } );
227 }() );
228