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

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

256 lines 18.0 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 RichText = wp.blockEditor.RichText;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var PanelBody = wp.components.PanelBody;
11 var Button = wp.components.Button;
12 var ToggleControl = wp.components.ToggleControl;
13 var RangeControl = wp.components.RangeControl;
14 var SelectControl = wp.components.SelectControl;
15 var TextControl = wp.components.TextControl;
16 var ColorPicker = wp.components.ColorPicker;
17 var Popover = wp.components.Popover;
18
19 var _TypographyControl, _typoCssVars;
20 function getTypographyControl() { return _TypographyControl || (_TypographyControl = window.bkbgTypographyControl); }
21 function getTypoCssVars() { return _typoCssVars || (_typoCssVars = window.bkbgTypoCssVars); }
22
23 var LEGEND_POS_OPTIONS = [
24 { label: __('Top', 'blockenberg'), value: 'top' },
25 { label: __('Bottom', 'blockenberg'), value: 'bottom' },
26 { label: __('Left', 'blockenberg'), value: 'left' },
27 { label: __('Right', 'blockenberg'), value: 'right' },
28 ];
29
30 function makeId() { return 'ac' + Math.random().toString(36).substr(2, 5); }
31
32 function buildWrapStyle(a) {
33 var _tv = getTypoCssVars();
34 var s = {
35 '--bkbg-ac-bg': a.bgColor || '#ffffff',
36 '--bkbg-ac-card-r': a.cardRadius + 'px',
37 '--bkbg-ac-title-color': a.titleColor,
38 '--bkbg-ac-title-sz': a.titleSize + 'px',
39 '--bkbg-ac-subtitle-color':a.subtitleColor,
40 paddingTop: a.paddingTop + 'px',
41 paddingBottom: a.paddingBottom + 'px',
42 backgroundColor: a.bgColor || undefined,
43 };
44 Object.assign(s, _tv(a.titleTypo || {}, '--bkbg-ac-title-'));
45 Object.assign(s, _tv(a.subtitleTypo || {}, '--bkbg-ac-subtitle-'));
46 return s;
47 }
48
49 function renderColorControl(key, label, value, onChange, openKey, setOpenKey) {
50 var isOpen = openKey === key;
51 return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } },
52 el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
53 el('div', { style: { position: 'relative', flexShrink: 0 } },
54 el('button', { type: 'button', onClick: function () { setOpenKey(isOpen ? null : key); }, style: { width: '28px', height: '28px', borderRadius: '4px', border: isOpen ? '2px solid #007cba' : '2px solid #ddd', cursor: 'pointer', padding: 0, background: value || '#ccc' } }),
55 isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } },
56 el('div', { style: { padding: '8px' } },
57 el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange })
58 )
59 )
60 )
61 );
62 }
63
64 /* ── Dataset editor row ─────────────────────────────────────────── */
65 function DatasetRow(props) {
66 var ds = props.ds;
67 var onUpdate = props.onUpdate;
68 var onRemove = props.onRemove;
69 var openKey = props.openKey;
70 var setOpenKey = props.setOpenKey;
71
72 return el('div', { style: { border: '1px solid #e0e0e0', borderRadius: '8px', padding: '10px', marginBottom: '10px', background: '#fafafa' } },
73 el(TextControl, { label: __('Label', 'blockenberg'), value: ds.label, onChange: function (v) { onUpdate('label', v); } }),
74 el(TextControl, { label: __('Data (CSV)', 'blockenberg'), value: ds.data, onChange: function (v) { onUpdate('data', v); }, help: __('e.g. 40,55,72,65', 'blockenberg') }),
75 renderColorControl('ds-' + ds.id + '-color', __('Color', 'blockenberg'), ds.color, function (v) { onUpdate('color', v); }, openKey, setOpenKey),
76 el(ToggleControl, { label: __('Fill area', 'blockenberg'), checked: ds.fill, onChange: function (v) { onUpdate('fill', v); }, __nextHasNoMarginBottom: true }),
77 el(RangeControl, { label: __('Line tension', 'blockenberg'), value: ds.tension, min: 0, max: 0.9, step: 0.05, onChange: function (v) { onUpdate('tension', v); } }),
78 el(Button, { isDestructive: true, variant: 'tertiary', size: 'compact', onClick: onRemove, style: { marginTop: '6px' } }, __('Remove dataset', 'blockenberg'))
79 );
80 }
81
82 registerBlockType('blockenberg/area-chart', {
83 title: __('Area Chart', 'blockenberg'),
84 icon: 'chart-area',
85 category: 'bkbg-charts',
86
87 edit: function (props) {
88 var a = props.attributes;
89 var setAttributes = props.setAttributes;
90
91 var openColorKeyState = useState(null);
92 var openColorKey = openColorKeyState[0];
93 var setOpenColorKey = openColorKeyState[1];
94
95 var blockProps = useBlockProps({ style: buildWrapStyle(a) });
96
97 function updateDataset(id, key, val) {
98 setAttributes({ datasets: a.datasets.map(function (ds) {
99 if (ds.id !== id) return ds;
100 var u = Object.assign({}, ds); u[key] = val; return u;
101 }) });
102 }
103
104 function addDataset() {
105 var idx = a.datasets.length + 1;
106 setAttributes({ datasets: a.datasets.concat([{
107 id: makeId(), label: 'Series ' + idx,
108 data: '0,0,0,0,0,0,0,0', color: '#' + Math.floor(Math.random()*16777215).toString(16).padStart(6,'0'),
109 fill: true, tension: 0.45
110 }]) });
111 }
112
113 function removeDataset(id) {
114 setAttributes({ datasets: a.datasets.filter(function (ds) { return ds.id !== id; }) });
115 }
116
117 /* Sparkline SVG preview */
118 function buildSparklinePath(data, W, H, padX, padY) {
119 var pts = data.split(',').map(function (v) { return parseFloat(v.trim()) || 0; });
120 if (pts.length < 2) return '';
121 var mn = Math.min.apply(null, pts);
122 var mx = Math.max.apply(null, pts);
123 var rng = mx - mn || 1;
124 var xs = pts.map(function (_, i) { return padX + i * (W - 2*padX) / (pts.length - 1); });
125 var ys = pts.map(function (v) { return padY + (1 - (v-mn)/rng) * (H - 2*padY); });
126 var d = 'M ' + xs[0] + ' ' + ys[0];
127 for (var i = 1; i < xs.length; i++) {
128 var cpx = (xs[i-1] + xs[i]) / 2;
129 d += ' C ' + cpx + ' ' + ys[i-1] + ' ' + cpx + ' ' + ys[i] + ' ' + xs[i] + ' ' + ys[i];
130 }
131 return d;
132 }
133
134 var svgW = 560; var svgH = a.height;
135 var padX = 32; var padY = 24;
136
137 return el(Fragment, null,
138 el(InspectorControls, null,
139 el(PanelBody, { title: __('Data', 'blockenberg'), initialOpen: true },
140 el(TextControl, { label: __('X-Axis Labels (CSV)', 'blockenberg'), value: a.labels, onChange: function (v) { setAttributes({ labels: v }); }, help: __('e.g. Jan,Feb,Mar,Apr', 'blockenberg') }),
141 a.datasets.map(function (ds) {
142 return el(DatasetRow, {
143 key: ds.id, ds: ds, openKey: openColorKey, setOpenKey: setOpenColorKey,
144 onUpdate: function (k, v) { updateDataset(ds.id, k, v); },
145 onRemove: function () { removeDataset(ds.id); },
146 });
147 }),
148 el(Button, { variant: 'secondary', onClick: addDataset, style: { width: '100%', justifyContent: 'center', marginTop: '4px' } }, '+ ' + __('Add Dataset', 'blockenberg'))
149 ),
150 el(PanelBody, { title: __('Chart Options', 'blockenberg'), initialOpen: false },
151 el(RangeControl, { label: __('Height (px)', 'blockenberg'), value: a.height, min: 160, max: 700, onChange: function (v) { setAttributes({ height: v }); } }),
152 el(ToggleControl, { label: __('Gradient fill', 'blockenberg'), checked: a.gradientFill, onChange: function (v) { setAttributes({ gradientFill: v }); }, __nextHasNoMarginBottom: true }),
153 a.gradientFill && el(RangeControl, { label: __('Gradient alpha %', 'blockenberg'), value: a.gradientAlpha, min: 5, max: 60, onChange: function (v) { setAttributes({ gradientAlpha: v }); } }),
154 el(ToggleControl, { label: __('Show grid', 'blockenberg'), checked: a.showGrid, onChange: function (v) { setAttributes({ showGrid: v }); }, __nextHasNoMarginBottom: true }),
155 el(ToggleControl, { label: __('Show legend', 'blockenberg'), checked: a.showLegend, onChange: function (v) { setAttributes({ showLegend: v }); }, __nextHasNoMarginBottom: true }),
156 a.showLegend && el(SelectControl, { label: __('Legend position', 'blockenberg'), value: a.legendPos, options: LEGEND_POS_OPTIONS, onChange: function (v) { setAttributes({ legendPos: v }); } }),
157 el(ToggleControl, { label: __('Show data points', 'blockenberg'), checked: a.showPoints, onChange: function (v) { setAttributes({ showPoints: v }); }, __nextHasNoMarginBottom: true }),
158 a.showPoints && el(RangeControl, { label: __('Point size (px)', 'blockenberg'), value: a.pointSize, min: 2, max: 10, onChange: function (v) { setAttributes({ pointSize: v }); } }),
159 el(RangeControl, { label: __('Line width (px)', 'blockenberg'), value: a.lineWidth, min: 1, max: 6, onChange: function (v) { setAttributes({ lineWidth: v }); } }),
160 el(ToggleControl, { label: __('Animate on load', 'blockenberg'), checked: a.animate, onChange: function (v) { setAttributes({ animate: v }); }, __nextHasNoMarginBottom: true }),
161 a.animate && el(RangeControl, { label: __('Animation duration (ms)', 'blockenberg'), value: a.animDuration, min: 200, max: 2000, step: 100, onChange: function (v) { setAttributes({ animDuration: v }); } })
162 ),
163 el(PanelBody, { title: __('Axes', 'blockenberg'), initialOpen: false },
164 el(TextControl, { label: __('X Axis label', 'blockenberg'), value: a.xLabel, onChange: function (v) { setAttributes({ xLabel: v }); } }),
165 el(TextControl, { label: __('Y Axis label', 'blockenberg'), value: a.yLabel, onChange: function (v) { setAttributes({ yLabel: v }); } }),
166 el(TextControl, { label: __('Y Min (leave blank for auto)', 'blockenberg'), value: a.yMin, onChange: function (v) { setAttributes({ yMin: v }); } }),
167 el(TextControl, { label: __('Y Max (leave blank for auto)', 'blockenberg'), value: a.yMax, onChange: function (v) { setAttributes({ yMax: v }); } })
168 ),
169 el(PanelBody, { title: __('Card Style', 'blockenberg'), initialOpen: false },
170 renderColorControl('bgColor', __('Card background', 'blockenberg'), a.bgColor, function (v) { setAttributes({ bgColor: v }); }, openColorKey, setOpenColorKey),
171 el(RangeControl, { label: __('Card radius (px)', 'blockenberg'), value: a.cardRadius, min: 0, max: 32, onChange: function (v) { setAttributes({ cardRadius: v }); } })
172 ),
173 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
174 el(getTypographyControl(), { label: __('Title Typography', 'blockenberg'), value: a.titleTypo || {}, onChange: function (v) { setAttributes({ titleTypo: v }); } }),
175 el(getTypographyControl(), { label: __('Subtitle Typography', 'blockenberg'), value: a.subtitleTypo || {}, onChange: function (v) { setAttributes({ subtitleTypo: v }); } })
176 ),
177 el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
178 renderColorControl('titleColor', __('Title color', 'blockenberg'), a.titleColor, function (v) { setAttributes({ titleColor: v }); }, openColorKey, setOpenColorKey),
179 renderColorControl('subtitleColor', __('Subtitle color', 'blockenberg'), a.subtitleColor, function (v) { setAttributes({ subtitleColor: v }); }, openColorKey, setOpenColorKey)
180 ),
181 el(PanelBody, { title: __('Spacing', 'blockenberg'), initialOpen: false },
182 el(RangeControl, { label: __('Padding top (px)', 'blockenberg'), value: a.paddingTop, min: 0, max: 200, onChange: function (v) { setAttributes({ paddingTop: v }); } }),
183 el(RangeControl, { label: __('Padding bottom (px)', 'blockenberg'), value: a.paddingBottom, min: 0, max: 200, onChange: function (v) { setAttributes({ paddingBottom: v }); } })
184 )
185 ),
186
187 el('div', blockProps,
188 el('div', { className: 'bkbg-ac-card', style: { background: a.bgColor || '#fff', borderRadius: a.cardRadius + 'px', padding: '24px', boxSizing: 'border-box' } },
189 (a.title || a.subtitle) && el('div', { className: 'bkbg-ac-header', style: { marginBottom: '16px' } },
190 el(RichText, { tagName: 'h3', className: 'bkbg-ac-title', value: a.title, onChange: function (v) { setAttributes({ title: v }); }, placeholder: __('Chart title…', 'blockenberg'), style: { color: a.titleColor, margin: '0 0 4px' } }),
191 el(RichText, { tagName: 'p', className: 'bkbg-ac-subtitle', value: a.subtitle, onChange: function (v) { setAttributes({ subtitle: v }); }, placeholder: __('Optional subtitle…', 'blockenberg'), style: { color: a.subtitleColor, margin: 0 } })
192 ),
193 /* SVG sparkline preview */
194 el('div', { className: 'bkbg-ac-preview', style: { position: 'relative', height: a.height + 'px', background: '#f9fafb', borderRadius: '8px', overflow: 'hidden' } },
195 el('svg', { viewBox: '0 0 ' + svgW + ' ' + svgH, preserveAspectRatio: 'none', style: { position: 'absolute', inset: 0, width: '100%', height: '100%' } },
196 a.datasets.map(function (ds) {
197 var path = buildSparklinePath(ds.data, svgW, svgH, padX, padY);
198 if (!path) return null;
199 return el(Fragment, { key: ds.id },
200 ds.fill && el('path', { d: path + ' L ' + (svgW-padX) + ' ' + (svgH-padY) + ' L ' + padX + ' ' + (svgH-padY) + ' Z', fill: ds.color, opacity: 0.15 }),
201 el('path', { d: path, fill: 'none', stroke: ds.color, strokeWidth: a.lineWidth, strokeLinejoin: 'round', strokeLinecap: 'round' })
202 );
203 })
204 ),
205 el('div', { style: { position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '12px', pointerEvents: 'none' } },
206 a.datasets.map(function (ds) {
207 return el('div', { key: ds.id, style: { display: 'flex', alignItems: 'center', gap: '6px', fontSize: '12px', color: '#374151', background: 'rgba(255,255,255,0.85)', padding: '4px 10px', borderRadius: '99px' } },
208 el('span', { style: { width: '10px', height: '10px', borderRadius: '50%', background: ds.color, flexShrink: 0 } }),
209 ds.label
210 );
211 })
212 )
213 )
214 )
215 )
216 );
217 },
218
219 save: function (props) {
220 var a = props.attributes;
221 return el('div', wp.blockEditor.useBlockProps.save({ className: 'bkbg-ac-wrapper', style: buildWrapStyle(a) }),
222 el('div', {
223 className: 'bkbg-ac-card',
224 'data-type': 'area',
225 'data-labels': a.labels,
226 'data-datasets': JSON.stringify(a.datasets),
227 'data-height': a.height,
228 'data-gradient': a.gradientFill ? 'true' : 'false',
229 'data-gradient-alpha': a.gradientAlpha,
230 'data-grid': a.showGrid ? '1' : '0',
231 'data-legend': a.showLegend ? '1' : '0',
232 'data-legend-pos': a.legendPos,
233 'data-points': a.showPoints ? '1' : '0',
234 'data-point-size': a.pointSize,
235 'data-line-width': a.lineWidth,
236 'data-animate': a.animate ? '1' : '0',
237 'data-anim-duration': a.animDuration,
238 'data-y-min': a.yMin,
239 'data-y-max': a.yMax,
240 'data-x-label': a.xLabel,
241 'data-y-label': a.yLabel,
242 style: { background: a.bgColor || '#fff', borderRadius: a.cardRadius + 'px', padding: '24px', boxSizing: 'border-box' },
243 },
244 (a.title || a.subtitle) && el('div', { className: 'bkbg-ac-header' },
245 a.title && el(RichText.Content, { tagName: 'h3', className: 'bkbg-ac-title', value: a.title }),
246 a.subtitle && el(RichText.Content, { tagName: 'p', className: 'bkbg-ac-subtitle', value: a.subtitle })
247 ),
248 el('div', { className: 'bkbg-ac-chart-wrap' },
249 el('canvas', { className: 'bkbg-ac-canvas', style: { height: a.height + 'px' } })
250 )
251 )
252 );
253 }
254 });
255 }() );
256