PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.14
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.14
2.0.14 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 / waterfall-bar / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.14, at blocks/waterfall-bar/index.js

204 lines 12.4 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 } = window.wp.components;
6 const { __ } = window.wp.i18n;
7
8 // Resolve the shared components lazily without defining globals for each block.
9 function _tc(label, key, attrs, setA) {
10 var Control = window.bkbgTypographyControl;
11 return Control ? el(Control, {
12 label: label,
13 value: attrs[key] || {},
14 onChange: function (value) { var update = {}; update[key] = value; setA(update); }
15 }) : null;
16 }
17 function _tvf(vars, key, attrs, prefix) {
18 var cssVars = window.bkbgTypoCssVars;
19 if (cssVars) Object.assign(vars, cssVars(attrs[key] || {}, prefix));
20 return vars;
21 }
22 function getTypoControl(label, key, attrs, setA) { return _tc(label, key, attrs, setA); }
23 function getTypoCssVars(attrs) {
24 var v = {};
25 _tvf(v, 'titleTypo', attrs, '--bkwfb-tt-');
26 return v;
27 }
28
29 // ── Renderer ──────────────────────────────────────────────────────────────
30 function renderWaterfall( a ) {
31 const items = a.items || [];
32 const W = a.svgWidth;
33 const H = a.svgHeight;
34 const PT = a.padTop;
35 const PL = a.padLeft;
36 const PR = a.padRight;
37 const PB = a.padBottom;
38 const BW = a.barWidth;
39 const chartW = W - PL - PR;
40 const chartH = H - PT - PB;
41 const n = items.length;
42 if ( ! n ) return el( 'svg', { viewBox: `0 0 ${ W } ${ H }`, width: '100%' } );
43
44 // Compute running totals
45 const barData = [];
46 let running = 0;
47 items.forEach( ( it, i ) => {
48 if ( it.isTotal ) {
49 // auto-calc for last total, or use explicit value if non-zero
50 const total = ( it.value !== 0 ) ? it.value : running;
51 barData.push( { ...it, base: 0, end: total, displayVal: total } );
52 running = total;
53 } else {
54 const base = running;
55 const end = running + it.value;
56 barData.push( { ...it, base, end, displayVal: it.value } );
57 running = end;
58 }
59 } );
60
61 // Y range
62 const allVals = barData.flatMap( b => [ b.base, b.end ] );
63 const minV = Math.min( 0, ...allVals );
64 const maxV = Math.max( 0, ...allVals );
65 const range = ( maxV - minV ) || 1;
66 const pad = range * 0.1;
67
68 function yPos( v ) { return PT + chartH - ( ( v - ( minV - pad ) ) / ( range + pad * 2 ) ) * chartH; }
69 const y0 = yPos( 0 );
70
71 // X positions (bar centres)
72 const step = chartW / n;
73 function xCentre( i ) { return PL + i * step + step / 2; }
74
75 const svgEls = [];
76 svgEls.push( el( 'rect', { key: 'bg', x: 0, y: 0, width: W, height: H, fill: a.bgColor || '#ffffff', rx: 8 } ) );
77
78 // Grid lines
79 if ( a.showGridLines ) {
80 const ticks = 5;
81 for ( let t = 0; t <= ticks; t++ ) {
82 const gy = PT + ( t / ticks ) * chartH;
83 svgEls.push( el( 'line', { key: `gl${ t }`, x1: PL, y1: gy, x2: W - PR, y2: gy, stroke: a.gridColor || '#f3f4f6', strokeWidth: 1 } ) );
84 const yLbl = Math.round( ( minV - pad ) + ( 1 - t / ticks ) * ( range + pad * 2 ) );
85 svgEls.push( el( 'text', { key: `gy${ t }`, x: PL - 6, y: gy, textAnchor: 'end', dominantBaseline: 'middle', fill: a.labelColor, fontSize: a.labelFontSize - 1, fontFamily: 'inherit' }, yLbl ) );
86 }
87 }
88
89 // Zero baseline
90 svgEls.push( el( 'line', { key: 'zero', x1: PL, y1: y0, x2: W - PR, y2: y0, stroke: a.labelColor, strokeWidth: 1, strokeOpacity: 0.4 } ) );
91
92 // Bars + connectors
93 barData.forEach( ( b, i ) => {
94 const cx = xCentre( i );
95 const bx = cx - BW / 2;
96 const yTop = Math.min( yPos( b.base ), yPos( b.end ) );
97 const bH = Math.abs( yPos( b.end ) - yPos( b.base ) ) || 2;
98
99 const color = b.isTotal
100 ? ( a.totalColor || '#6366f1' )
101 : b.value >= 0
102 ? ( a.posColor || '#10b981' )
103 : ( a.negColor || '#ef4444' );
104
105 svgEls.push( el( 'rect', { key: `bar${ i }`, x: bx, y: yTop, width: BW, height: bH, fill: color, rx: 3 } ) );
106
107 // Connector to next bar
108 if ( a.showConnectors && i < n - 1 ) {
109 const connY = yPos( b.end );
110 svgEls.push( el( 'line', { key: `con${ i }`, x1: bx + BW, y1: connY, x2: xCentre( i + 1 ) - BW / 2, y2: connY, stroke: a.connectorColor || '#d1d5db', strokeWidth: 1.5, strokeDasharray: '4 3' } ) );
111 }
112
113 // Value label
114 if ( a.showValues ) {
115 const sign = b.displayVal > 0 ? '+' : '';
116 const lbl = b.isTotal ? String( b.displayVal ) : `${ sign }${ b.displayVal }`;
117 svgEls.push( el( 'text', { key: `vl${ i }`, x: cx, y: yTop - 5, textAnchor: 'middle', fill: color, fontSize: a.valueFontSize, fontWeight: 700, fontFamily: 'inherit' }, lbl ) );
118 }
119
120 // X label (rotated if needed)
121 if ( a.labelRotate ) {
122 svgEls.push( el( 'text', { key: `xl${ i }`, x: cx, y: H - PB + 14, textAnchor: 'end', fill: a.labelColor, fontSize: a.labelFontSize, fontFamily: 'inherit', transform: `rotate(-40,${ cx },${ H - PB + 14 })` }, b.label ) );
123 } else {
124 svgEls.push( el( 'text', { key: `xl${ i }`, x: cx, y: H - PB + 16, textAnchor: 'middle', fill: a.labelColor, fontSize: a.labelFontSize, fontFamily: 'inherit' }, b.label ) );
125 }
126 } );
127
128 return el( 'svg', { viewBox: `0 0 ${ W } ${ H }`, width: '100%', style: { display: 'block', maxWidth: W + 'px', margin: '0 auto' } }, ...svgEls );
129 }
130
131 function updItem( setAttributes, items, ii, field, val ) {
132 setAttributes( { items: items.map( ( it, i ) => i === ii ? { ...it, [field]: val } : it ) } );
133 }
134
135 registerBlockType( 'blockenberg/waterfall-bar', {
136 edit: function ( props ) {
137 const { attributes: a, setAttributes } = props;
138 const blockProps = useBlockProps( { className: 'bkbg-waterfall-wrap', style: getTypoCssVars( a ) } );
139
140 return el( 'div', blockProps,
141 el( InspectorControls, {},
142
143 el( PanelBody, { title: __( 'Chart Settings', 'blockenberg' ), initialOpen: true },
144 el( TextControl, { label: __( 'Title', 'blockenberg' ), value: a.title, onChange: v => setAttributes( { title: v } ) } ),
145 el( ToggleControl, { label: __( 'Show Title', 'blockenberg' ), checked: a.showTitle, onChange: v => setAttributes( { showTitle: v } ) } ),
146 el( ToggleControl, { label: __( 'Show Values', 'blockenberg' ), checked: a.showValues, onChange: v => setAttributes( { showValues: v } ) } ),
147 el( ToggleControl, { label: __( 'Show Grid Lines', 'blockenberg' ), checked: a.showGridLines, onChange: v => setAttributes( { showGridLines: v } ) } ),
148 el( ToggleControl, { label: __( 'Show Connectors', 'blockenberg' ), checked: a.showConnectors, onChange: v => setAttributes( { showConnectors: v } ) } ),
149 el( ToggleControl, { label: __( 'Rotate X Labels', 'blockenberg' ), checked: a.labelRotate, onChange: v => setAttributes( { labelRotate: v } ) } ),
150 ),
151
152 el( PanelBody, { title: __( 'Layout', 'blockenberg' ), initialOpen: false },
153 el( RangeControl, { label: __( 'Canvas Width', 'blockenberg' ), value: a.svgWidth, onChange: v => setAttributes( { svgWidth: v } ), min: 300, max: 1200, step: 20 } ),
154 el( RangeControl, { label: __( 'Canvas Height', 'blockenberg' ), value: a.svgHeight, onChange: v => setAttributes( { svgHeight: v } ), min: 200, max: 800 } ),
155 el( RangeControl, { label: __( 'Bar Width', 'blockenberg' ), value: a.barWidth, onChange: v => setAttributes( { barWidth: v } ), min: 10, max: 100 } ),
156 ),
157
158
159 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
160 el( RangeControl, { label: __( 'Label Font Size', 'blockenberg' ), value: a.labelFontSize, onChange: v => setAttributes( { labelFontSize: v } ), min: 8, max: 20 } ),
161 el( RangeControl, { label: __( 'Value Font Size', 'blockenberg' ), value: a.valueFontSize, onChange: v => setAttributes( { valueFontSize: v } ), min: 7, max: 18 } ),
162 getTypoControl( __( 'Title', 'blockenberg' ), 'titleTypo', a, setAttributes ),
163 ),
164 el( PanelColorSettings, {
165 title: __( 'Colors', 'blockenberg' ), initialOpen: false,
166 colorSettings: [
167 { label: __( 'Positive bars', 'blockenberg' ), value: a.posColor, onChange: v => setAttributes( { posColor: v || '#10b981' } ) },
168 { label: __( 'Negative bars', 'blockenberg' ), value: a.negColor, onChange: v => setAttributes( { negColor: v || '#ef4444' } ) },
169 { label: __( 'Total bars', 'blockenberg' ), value: a.totalColor, onChange: v => setAttributes( { totalColor: v || '#6366f1' } ) },
170 { label: __( 'Connector lines', 'blockenberg' ), value: a.connectorColor, onChange: v => setAttributes( { connectorColor: v || '#d1d5db' } ) },
171 { label: __( 'Background', 'blockenberg' ), value: a.bgColor, onChange: v => setAttributes( { bgColor: v || '#ffffff' } ) },
172 { label: __( 'Title', 'blockenberg' ), value: a.titleColor, onChange: v => setAttributes( { titleColor: v || '#111827' } ) },
173 { label: __( 'Labels', 'blockenberg' ), value: a.labelColor, onChange: v => setAttributes( { labelColor: v || '#374151' } ) },
174 ]
175 } ),
176
177 el( PanelBody, { title: __( 'Items', 'blockenberg' ), initialOpen: false },
178 el( Button, { variant: 'secondary', style: { marginBottom: 10 }, onClick: () => setAttributes( { items: [ ...a.items, { label: 'New Item', value: 100, isTotal: false } ] } ) }, __( '+ Add Item', 'blockenberg' ) ),
179 a.items.map( ( it, ii ) =>
180 el( PanelBody, { key: ii, title: it.label || `Item ${ ii + 1 }`, initialOpen: false },
181 el( TextControl, { label: __( 'Label', 'blockenberg' ), value: it.label, onChange: v => updItem( setAttributes, a.items, ii, 'label', v ) } ),
182 el( TextControl, { label: __( 'Value', 'blockenberg' ), value: String( it.value ), type: 'number', onChange: v => updItem( setAttributes, a.items, ii, 'value', parseFloat( v ) || 0 ) } ),
183 el( ToggleControl, { label: __( 'Is Total bar', 'blockenberg' ), checked: it.isTotal, onChange: v => updItem( setAttributes, a.items, ii, 'isTotal', v ) } ),
184 el( Button, { isDestructive: true, isSmall: true, onClick: () => setAttributes( { items: a.items.filter( ( _, x ) => x !== ii ) } ) }, __( 'Remove', 'blockenberg' ) ),
185 )
186 ),
187 ),
188 ),
189
190 a.showTitle && a.title && el( 'h3', { className: 'bkbg-waterfall-title', style: { color: a.titleColor } }, a.title ),
191 el( 'div', { className: 'bkbg-waterfall-svg' }, renderWaterfall( a ) ),
192 );
193 },
194
195 save: function ( { attributes: a } ) {
196 const blockProps = useBlockProps.save( { className: 'bkbg-waterfall-wrap', style: getTypoCssVars( a ) } );
197 return el( 'div', blockProps,
198 a.showTitle && a.title ? el( 'h3', { className: 'bkbg-waterfall-title', style: { color: a.titleColor } }, a.title ) : null,
199 el( 'div', { className: 'bkbg-waterfall-svg' }, renderWaterfall( a ) ),
200 );
201 },
202 } );
203 }() );
204