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

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

542 lines 28.3 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 useState = wp.element.useState;
5 var __ = wp.i18n.__;
6 var registerBlockType = wp.blocks.registerBlockType;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var useBlockProps = wp.blockEditor.useBlockProps;
9 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
10 var PanelBody = wp.components.PanelBody;
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
17 var _tc, _tvf;
18 Object.defineProperty(window, '_tc', { get: function () { return _tc || (_tc = window.bkbgTypographyControl); } });
19 Object.defineProperty(window, '_tvf', { get: function () { return _tvf || (_tvf = window.bkbgTypoCssVars); } });
20 function getTypoControl(label, key, attrs, setA) { return _tc(label, key, attrs, setA); }
21 function getTypoCssVars(attrs) {
22 var v = {};
23 _tvf(v, 'titleTypo', attrs, '--bkwfc-tt-');
24 return v;
25 }
26
27 /* ── updateItem (ES5 safe) ── */
28 function updateItem(arr, idx, field, val) {
29 return arr.map(function (item, i) {
30 if (i !== idx) return item;
31 var p = {}; p[field] = val;
32 return Object.assign({}, item, p);
33 });
34 }
35
36 /* ── abbreviate large numbers ── */
37 function abbr(n, prefix, suffix, doAbbr) {
38 var abs = Math.abs(n);
39 var sign = n < 0 ? '-' : '';
40 var str;
41 if (!doAbbr || abs < 1000) {
42 str = abs.toLocaleString();
43 } else if (abs < 1000000) {
44 str = (abs / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
45 } else if (abs < 1000000000) {
46 str = (abs / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
47 } else {
48 str = (abs / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B';
49 }
50 return sign + prefix + str + suffix;
51 }
52
53 /* ── type label ── */
54 var TYPE_LABELS = {
55 start: __('Starting value', 'blockenberg'),
56 increase: __('Increase (+)', 'blockenberg'),
57 decrease: __('Decrease (−)', 'blockenberg'),
58 total: __('Total / Net', 'blockenberg')
59 };
60 var TYPE_OPTIONS = [
61 { label: __('Starting value', 'blockenberg'), value: 'start' },
62 { label: __('Increase (+)', 'blockenberg'), value: 'increase' },
63 { label: __('Decrease (−)', 'blockenberg'), value: 'decrease' },
64 { label: __('Total / Net', 'blockenberg'), value: 'total' }
65 ];
66
67 /* ── color for type ── */
68 function typeColor(type, a) {
69 if (type === 'start') return a.startColor;
70 if (type === 'increase') return a.increaseColor;
71 if (type === 'decrease') return a.decreaseColor;
72 return a.totalColor;
73 }
74
75 /* ── compute bar segments for preview ── */
76 /* Returns array of { label, type, base, top, barColor, displayValue } */
77 function computeSegments(items, a) {
78 var running = 0;
79 return items.map(function (item) {
80 var v = parseFloat(item.value) || 0;
81 var type = item.type || 'increase';
82 var base, top;
83
84 if (type === 'start') {
85 base = 0; top = v; running = v;
86 } else if (type === 'total') {
87 base = 0; top = running; /* running stays same */
88 } else if (type === 'increase') {
89 base = running; top = running + v; running = top;
90 } else { /* decrease */
91 top = running; base = running + v; running = base;
92 }
93
94 return {
95 label: item.label,
96 type: type,
97 base: base,
98 top: top,
99 barColor: typeColor(type, a),
100 displayValue: v,
101 runningAfter: running
102 };
103 });
104 }
105
106 /* ── SVG bar chart preview in editor ── */
107 function WaterfallPreview(props) {
108 var a = props.attributes;
109 var items = a.items;
110 if (!items || !items.length) return el('div', {}, 'No data.');
111
112 var segs = computeSegments(items, a);
113 var allVals = segs.map(function (s) { return s.top; }).concat(segs.map(function (s) { return s.base; }));
114 var maxVal = Math.max.apply(null, allVals);
115 var minVal = Math.min.apply(null, allVals.concat([0]));
116 var range = maxVal - minVal || 1;
117
118 var svgW = 600, svgH = Math.min(a.height, 320);
119 var padLeft = 55, padRight = 16, padTop = 20, padBottom = 36;
120 var chartW = svgW - padLeft - padRight;
121 var chartH = svgH - padTop - padBottom;
122 var barW = Math.min(a.barThickness, Math.floor(chartW / items.length) - 6);
123 var step = chartW / items.length;
124
125 function yPx(v) { return padTop + chartH - ((v - minVal) / range) * chartH; }
126
127 var bars = segs.map(function (s, i) {
128 var x = padLeft + i * step + (step - barW) / 2;
129 var y1 = yPx(Math.max(s.base, s.top));
130 var y2 = yPx(Math.min(s.base, s.top));
131 var bh = Math.max(y2 - y1, 2);
132 return { x: x, y: y1, width: barW, height: bh, seg: s, cx: x + barW / 2 };
133 });
134
135 var gridLines = [];
136 var gridCount = 5;
137 for (var g = 0; g <= gridCount; g++) {
138 var gv = minVal + (range / gridCount) * g;
139 var gy = yPx(gv);
140 gridLines.push(el('line', { key: 'gl' + g, x1: padLeft, x2: svgW - padRight, y1: gy, y2: gy, stroke: a.gridColor, strokeWidth: 1, strokeDasharray: '4,3' }));
141 gridLines.push(el('text', { key: 'glt' + g, x: padLeft - 4, y: gy + 4, textAnchor: 'end', fontSize: 9, fill: a.labelColor },
142 abbr(gv, a.valuePrefix, a.valueSuffix, a.abbreviate)
143 ));
144 }
145
146 var connectors = [];
147 if (a.showConnectors) {
148 for (var ci = 0; ci < bars.length - 1; ci++) {
149 var curr = bars[ci];
150 var next = bars[ci + 1];
151 var connY = yPx(segs[ci].type === 'decrease' ? segs[ci].base : segs[ci].top);
152 connectors.push(el('line', {
153 key: 'conn' + ci,
154 x1: curr.x + curr.width, x2: next.x,
155 y1: connY, y2: connY,
156 stroke: a.connectorColor, strokeWidth: 1.5, strokeDasharray: '4,3'
157 }));
158 }
159 }
160
161 return el('div', { style: { background: a.bgColor, borderRadius: '8px' } },
162 a.showTitle && a.title ? el('div', {
163 className: 'bkbg-wfc-title',
164 style: { color: a.titleColor, padding: '0 0 12px' }
165 }, a.title) : null,
166 el('svg', { width: '100%', viewBox: '0 0 600 ' + svgH, style: { display: 'block', maxWidth: '100%' } },
167 a.showGrid ? el('g', {}, gridLines) : null,
168 el('line', { x1: padLeft, x2: svgW - padRight, y1: yPx(0), y2: yPx(0), stroke: a.gridColor, strokeWidth: 1.5 }),
169 el('g', {}, connectors),
170 el('g', {},
171 bars.map(function (b, i) {
172 return el('g', { key: i },
173 el('rect', {
174 x: b.x, y: b.y, width: b.width, height: b.height,
175 fill: b.seg.barColor, rx: a.borderRadius
176 }),
177 a.showDataLabels ? el('text', {
178 x: b.cx, y: b.y - 4, textAnchor: 'middle',
179 fontSize: a.valueSize, fill: b.seg.barColor, fontWeight: 700, fontFamily: 'system-ui,sans-serif'
180 }, abbr(b.seg.displayValue, a.valuePrefix, a.valueSuffix, a.abbreviate)) : null,
181 el('text', {
182 x: b.cx, y: svgH - 4, textAnchor: 'middle',
183 fontSize: a.labelSize, fill: a.labelColor, fontFamily: 'system-ui,sans-serif'
184 }, b.seg.label.length > 10 ? b.seg.label.slice(0, 9) + '' : b.seg.label)
185 );
186 })
187 )
188 ),
189 a.showLegend ? el('div', {
190 style: { display: 'flex', flexWrap: 'wrap', gap: '10px 18px', padding: '10px 0 0', fontFamily: 'system-ui,sans-serif' }
191 },
192 [['start', 'Start', a.startColor], ['increase', 'Increase', a.increaseColor], ['decrease', 'Decrease', a.decreaseColor], ['total', 'Total', a.totalColor]].map(function (row) {
193 return el('div', { key: row[0], style: { display: 'flex', alignItems: 'center', gap: '5px' } },
194 el('span', { style: { width: '12px', height: '12px', borderRadius: '3px', background: row[2], display: 'inline-block', flexShrink: 0 } }),
195 el('span', { style: { fontSize: '12px', color: a.labelColor } }, row[1])
196 );
197 })
198 ) : null
199 );
200 }
201
202 /* ── item list editor ── */
203 function ItemEditor(props) {
204 var items = props.items;
205 var setItems = props.setItems;
206 var expanded = props.expanded;
207 var setExpanded = props.setExpanded;
208
209 return el('div', {},
210 items.map(function (item, i) {
211 var isOpen = expanded === i;
212 var col = {start:'#6366f1',increase:'#10b981',decrease:'#ef4444',total:'#8b5cf6'}[item.type] || '#6b7280';
213 return el('div', {
214 key: i,
215 style: { border: '1px solid #e5e7eb', borderRadius: '6px', marginBottom: '5px', overflow: 'hidden' }
216 },
217 el('div', {
218 onClick: function () { setExpanded(isOpen ? -1 : i); },
219 style: {
220 padding: '7px 10px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px',
221 background: isOpen ? '#f9fafb' : '#fff',
222 borderBottom: isOpen ? '1px solid #e5e7eb' : 'none'
223 }
224 },
225 el('span', { style: { width: '10px', height: '10px', borderRadius: '2px', background: col, flexShrink: 0 } }),
226 el('span', { style: { flex: 1, fontSize: '12px', color: '#374151', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } },
227 item.label || '(empty)'
228 ),
229 el('span', { style: { fontSize: '11px', color: '#9ca3af', marginRight: '4px' } },
230 (item.value > 0 ? '+' : '') + (item.value || 0)
231 ),
232 el('button', {
233 onClick: function (e) {
234 e.stopPropagation();
235 if (items.length <= 2) return;
236 setItems(items.filter(function (_, j) { return j !== i; }));
237 setExpanded(-1);
238 },
239 style: { background: 'none', border: 'none', cursor: 'pointer', color: '#ef4444', fontSize: '14px', padding: '0 2px', lineHeight: 1 }
240 }, '×'),
241 el('span', { style: { color: '#9ca3af', fontSize: '10px' } }, isOpen ? '' : '')
242 ),
243 isOpen ? el('div', { style: { padding: '10px' } },
244 el(TextControl, {
245 __nextHasNoMarginBottom: true,
246 label: __('Label', 'blockenberg'),
247 value: item.label,
248 onChange: function (v) { setItems(updateItem(items, i, 'label', v)); }
249 }),
250 el('div', { style: { marginTop: '8px' } },
251 el(TextControl, {
252 __nextHasNoMarginBottom: true,
253 label: __('Value (negative = decrease)', 'blockenberg'),
254 type: 'number',
255 value: item.value,
256 onChange: function (v) { setItems(updateItem(items, i, 'value', parseFloat(v) || 0)); }
257 })
258 ),
259 el('div', { style: { marginTop: '8px' } },
260 el(SelectControl, {
261 __nextHasNoMarginBottom: true,
262 label: __('Type', 'blockenberg'),
263 value: item.type,
264 options: TYPE_OPTIONS,
265 onChange: function (v) { setItems(updateItem(items, i, 'type', v)); }
266 })
267 ),
268 el('div', { style: { display: 'flex', gap: '6px', marginTop: '8px' } },
269 i > 0 ? el(Button, {
270 variant: 'secondary', style: { fontSize: '11px' },
271 onClick: function () {
272 var arr = items.slice(); var tmp = arr[i - 1]; arr[i - 1] = arr[i]; arr[i] = tmp;
273 setItems(arr); setExpanded(i - 1);
274 }
275 }, '') : null,
276 i < items.length - 1 ? el(Button, {
277 variant: 'secondary', style: { fontSize: '11px' },
278 onClick: function () {
279 var arr = items.slice(); var tmp = arr[i + 1]; arr[i + 1] = arr[i]; arr[i] = tmp;
280 setItems(arr); setExpanded(i + 1);
281 }
282 }, '') : null,
283 el(Button, {
284 variant: 'secondary', style: { fontSize: '11px' },
285 onClick: function () {
286 var copy = Object.assign({}, item, { label: item.label + ' (copy)' });
287 var arr = items.slice(); arr.splice(i + 1, 0, copy);
288 setItems(arr); setExpanded(i + 1);
289 }
290 }, __('Dup', 'blockenberg'))
291 )
292 ) : null
293 );
294 }),
295 el('div', { style: { marginTop: '8px', display: 'flex', gap: '6px' } },
296 el(Button, {
297 variant: 'primary', style: { flex: 1, justifyContent: 'center' },
298 onClick: function () {
299 var arr = items.slice();
300 arr.push({ label: 'New item', value: 0, type: 'increase' });
301 setItems(arr); setExpanded(arr.length - 1);
302 }
303 }, __('+ Add Item', 'blockenberg')),
304 el(Button, {
305 variant: 'secondary', style: { justifyContent: 'center' },
306 onClick: function () {
307 var arr = items.slice();
308 arr.push({ label: 'Total', value: 0, type: 'total' });
309 setItems(arr); setExpanded(arr.length - 1);
310 }
311 }, __('+ Total', 'blockenberg'))
312 )
313 );
314 }
315
316 /* ── register ── */
317 registerBlockType('blockenberg/waterfall-chart', {
318 edit: function (props) {
319 var a = props.attributes;
320 var setAttributes = props.setAttributes;
321
322 var expState = useState(-1);
323 var expanded = expState[0];
324 var setExpanded = expState[1];
325
326 function setItems(v) { setAttributes({ items: v }); }
327
328 var blockProps = useBlockProps({ className: 'bkbg-wfc-editor-wrap' });
329
330 return el(Fragment, {},
331
332 el(InspectorControls, {},
333
334 /* ── Data ── */
335 el(PanelBody, { title: __('Chart Data', 'blockenberg'), initialOpen: true },
336 el(ItemEditor, {
337 items: a.items,
338 setItems: setItems,
339 expanded: expanded,
340 setExpanded: setExpanded
341 })
342 ),
343
344 /* ── Labels ── */
345 el(PanelBody, { title: __('Labels & Format', 'blockenberg'), initialOpen: false },
346 el(TextControl, {
347 __nextHasNoMarginBottom: true,
348 label: __('Chart title', 'blockenberg'),
349 value: a.title,
350 onChange: function (v) { setAttributes({ title: v }); }
351 }),
352 el('div', { style: { marginTop: '10px' } },
353 el(ToggleControl, {
354 __nextHasNoMarginBottom: true,
355 label: __('Show title', 'blockenberg'),
356 checked: a.showTitle,
357 onChange: function (v) { setAttributes({ showTitle: v }); }
358 })
359 ),
360 el('div', { style: { marginTop: '10px', display: 'flex', gap: '8px' } },
361 el('div', { style: { flex: 1 } },
362 el(TextControl, {
363 __nextHasNoMarginBottom: true,
364 label: __('Value prefix', 'blockenberg'),
365 value: a.valuePrefix,
366 onChange: function (v) { setAttributes({ valuePrefix: v }); }
367 })
368 ),
369 el('div', { style: { flex: 1 } },
370 el(TextControl, {
371 __nextHasNoMarginBottom: true,
372 label: __('Value suffix', 'blockenberg'),
373 value: a.valueSuffix,
374 onChange: function (v) { setAttributes({ valueSuffix: v }); }
375 })
376 )
377 ),
378 el('div', { style: { marginTop: '10px' } },
379 el(ToggleControl, {
380 __nextHasNoMarginBottom: true,
381 label: __('Abbreviate large values (K, M, B)', 'blockenberg'),
382 checked: a.abbreviate,
383 onChange: function (v) { setAttributes({ abbreviate: v }); }
384 })
385 )
386 ),
387
388 /* ── Appearance ── */
389 el(PanelBody, { title: __('Appearance', 'blockenberg'), initialOpen: false },
390 el(RangeControl, {
391 __nextHasNoMarginBottom: true,
392 label: __('Chart height (px)', 'blockenberg'),
393 value: a.height, min: 200, max: 700,
394 onChange: function (v) { setAttributes({ height: v }); }
395 }),
396 el('div', { style: { marginTop: '10px' } },
397 el(RangeControl, {
398 __nextHasNoMarginBottom: true,
399 label: __('Bar max thickness (px)', 'blockenberg'),
400 value: a.barThickness, min: 20, max: 100,
401 onChange: function (v) { setAttributes({ barThickness: v }); }
402 })
403 ),
404 el('div', { style: { marginTop: '10px' } },
405 el(RangeControl, {
406 __nextHasNoMarginBottom: true,
407 label: __('Bar border radius (px)', 'blockenberg'),
408 value: a.borderRadius, min: 0, max: 20,
409 onChange: function (v) { setAttributes({ borderRadius: v }); }
410 })
411 ),
412 el('div', { style: { marginTop: '10px' } },
413 el(RangeControl, {
414 __nextHasNoMarginBottom: true,
415 label: __('Max width (px)', 'blockenberg'),
416 value: a.maxWidth, min: 400, max: 1400,
417 onChange: function (v) { setAttributes({ maxWidth: v }); }
418 })
419 ),
420 el('div', { style: { marginTop: '10px' } },
421 el(ToggleControl, {
422 __nextHasNoMarginBottom: true,
423 label: __('Show connector lines', 'blockenberg'),
424 checked: a.showConnectors,
425 onChange: function (v) { setAttributes({ showConnectors: v }); }
426 })
427 ),
428 el('div', { style: { marginTop: '4px' } },
429 el(ToggleControl, {
430 __nextHasNoMarginBottom: true,
431 label: __('Show data labels', 'blockenberg'),
432 checked: a.showDataLabels,
433 onChange: function (v) { setAttributes({ showDataLabels: v }); }
434 })
435 ),
436 el('div', { style: { marginTop: '4px' } },
437 el(ToggleControl, {
438 __nextHasNoMarginBottom: true,
439 label: __('Show grid lines', 'blockenberg'),
440 checked: a.showGrid,
441 onChange: function (v) { setAttributes({ showGrid: v }); }
442 })
443 ),
444 el('div', { style: { marginTop: '4px' } },
445 el(ToggleControl, {
446 __nextHasNoMarginBottom: true,
447 label: __('Show legend', 'blockenberg'),
448 checked: a.showLegend,
449 onChange: function (v) { setAttributes({ showLegend: v }); }
450 })
451 ),
452 el('div', { style: { marginTop: '10px', display: 'flex', gap: '8px' } },
453 el('div', { style: { flex: 1 } },
454 el(RangeControl, {
455 __nextHasNoMarginBottom: true,
456 label: __('Padding top', 'blockenberg'),
457 value: a.paddingTop, min: 0, max: 120,
458 onChange: function (v) { setAttributes({ paddingTop: v }); }
459 })
460 ),
461 el('div', { style: { flex: 1 } },
462 el(RangeControl, {
463 __nextHasNoMarginBottom: true,
464 label: __('Padding bottom', 'blockenberg'),
465 value: a.paddingBottom, min: 0, max: 120,
466 onChange: function (v) { setAttributes({ paddingBottom: v }); }
467 })
468 )
469 )
470 ),
471
472 /* ── Colors ── */
473
474 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
475 getTypoControl( __( 'Title', 'blockenberg' ), 'titleTypo', a, setAttributes ),
476 el('div', { style: { marginTop: '10px', display: 'flex', gap: '8px' } },
477 el('div', { style: { flex: 1 } },
478 el(RangeControl, {
479 __nextHasNoMarginBottom: true,
480 label: __('Label size', 'blockenberg'),
481 value: a.labelSize, min: 8, max: 18,
482 onChange: function (v) { setAttributes({ labelSize: v }); }
483 })
484 ),
485 el('div', { style: { flex: 1 } },
486 el(RangeControl, {
487 __nextHasNoMarginBottom: true,
488 label: __('Value size', 'blockenberg'),
489 value: a.valueSize, min: 8, max: 18,
490 onChange: function (v) { setAttributes({ valueSize: v }); }
491 })
492 )
493 )
494 ),
495 el(PanelColorSettings, {
496 title: __('Colors', 'blockenberg'),
497 initialOpen: false,
498 colorSettings: [
499 { label: __('Background', 'blockenberg'), value: a.bgColor, onChange: function (v) { setAttributes({ bgColor: v || '#ffffff' }); } },
500 { label: __('Title', 'blockenberg'), value: a.titleColor, onChange: function (v) { setAttributes({ titleColor: v || '#111827' }); } },
501 { label: __('Labels / axis', 'blockenberg'), value: a.labelColor, onChange: function (v) { setAttributes({ labelColor: v || '#6b7280' }); } },
502 { label: __('Grid lines', 'blockenberg'), value: a.gridColor, onChange: function (v) { setAttributes({ gridColor: v || '#e5e7eb' }); } },
503 { label: __('Connectors', 'blockenberg'), value: a.connectorColor, onChange: function (v) { setAttributes({ connectorColor: v || '#9ca3af' }); } },
504 { label: __('Start bars', 'blockenberg'), value: a.startColor, onChange: function (v) { setAttributes({ startColor: v || '#6366f1' }); } },
505 { label: __('Increase bars', 'blockenberg'), value: a.increaseColor, onChange: function (v) { setAttributes({ increaseColor: v || '#10b981' }); } },
506 { label: __('Decrease bars', 'blockenberg'), value: a.decreaseColor, onChange: function (v) { setAttributes({ decreaseColor: v || '#ef4444' }); } },
507 { label: __('Total bars', 'blockenberg'), value: a.totalColor, onChange: function (v) { setAttributes({ totalColor: v || '#8b5cf6' }); } }
508 ]
509 })
510 ),
511
512 /* ── canvas ── */
513 el('div', blockProps,
514 el('div', {
515 style: {
516 background: a.bgColor,
517 borderRadius: '10px',
518 padding: '24px',
519 paddingTop: a.paddingTop ? a.paddingTop + 'px' : '24px',
520 paddingBottom: a.paddingBottom ? a.paddingBottom + 'px' : '24px',
521 maxWidth: a.maxWidth + 'px',
522 margin: '0 auto',
523 fontFamily: 'system-ui, sans-serif'
524 }
525 },
526 el(WaterfallPreview, { attributes: a })
527 )
528 )
529 );
530 },
531
532 save: function (props) {
533 return el('div', useBlockProps.save({ style: getTypoCssVars(props.attributes) }),
534 el('div', {
535 className: 'bkbg-wfc-app',
536 'data-opts': JSON.stringify(props.attributes)
537 })
538 );
539 }
540 });
541 }() );
542