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

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.13, at blocks/section/index.js

378 lines 17.9 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 useEffect = wp.element.useEffect;
4 var useState = wp.element.useState;
5 var __ = wp.i18n.__;
6 var registerBlockType = wp.blocks.registerBlockType;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var BlockControls = wp.blockEditor.BlockControls;
9 var BlockAlignmentToolbar = wp.blockEditor.BlockAlignmentToolbar;
10 var useBlockProps = wp.blockEditor.useBlockProps;
11 var InnerBlocks = wp.blockEditor.InnerBlocks;
12 var PanelBody = wp.components.PanelBody;
13 var SelectControl = wp.components.SelectControl;
14 var TextControl = wp.components.TextControl;
15 var ToolbarGroup = wp.components.ToolbarGroup;
16 var ToolbarButton = wp.components.ToolbarButton;
17 var ColorPalette = wp.components.ColorPalette;
18 var BaseControl = wp.components.BaseControl;
19
20 // Background presets
21 var backgroundOptions = [
22 { label: __('None', 'blockenberg'), value: 'none' },
23 { label: __('Light', 'blockenberg'), value: 'light' },
24 { label: __('Dark', 'blockenberg'), value: 'dark' },
25 { label: __('Accent', 'blockenberg'), value: 'accent' },
26 { label: __('Custom', 'blockenberg'), value: 'custom' }
27 ];
28
29 // Max width options
30 var maxWidthOptions = [
31 { label: '1024px', value: '1024px' },
32 { label: '1200px', value: '1200px' },
33 { label: '1440px', value: '1440px' }
34 ];
35
36 var spacingUnitOptions = [
37 { label: 'px', value: 'px' },
38 { label: '%', value: '%' },
39 { label: 'em', value: 'em' },
40 { label: 'rem', value: 'rem' },
41 { label: 'vw', value: 'vw' },
42 { label: 'vh', value: 'vh' },
43 { label: __('Custom', 'blockenberg'), value: 'custom' }
44 ];
45
46 function cssLength(value) {
47 if (value === undefined || value === null) return undefined;
48 var v = String(value).trim();
49 if (!v) return undefined;
50 // Back-compat: if stored as a number ("1200"), treat as px.
51 if (/^\d+(?:\.\d+)?$/.test(v)) return v + 'px';
52 return v;
53 }
54
55 function getSpacingSideValue(attrs, type, side) {
56 var cap = side.charAt(0).toUpperCase() + side.slice(1);
57 var value = attrs[type + cap];
58 var unit = attrs[type + cap + 'Unit'] || 'px';
59 var customUnit = attrs[type + cap + 'CustomUnit'] || '';
60
61 if (value === undefined || value === null) return undefined;
62 var raw = String(value).trim();
63 if (!raw) return undefined;
64
65 if (unit === 'custom') {
66 return customUnit ? raw + customUnit : raw;
67 }
68
69 return raw + unit;
70 }
71
72 function getSpacingStyles(attrs) {
73 return {
74 paddingTop: getSpacingSideValue(attrs, 'padding', 'top'),
75 paddingRight: getSpacingSideValue(attrs, 'padding', 'right'),
76 paddingBottom: getSpacingSideValue(attrs, 'padding', 'bottom'),
77 paddingLeft: getSpacingSideValue(attrs, 'padding', 'left'),
78 marginTop: getSpacingSideValue(attrs, 'margin', 'top'),
79 marginRight: getSpacingSideValue(attrs, 'margin', 'right'),
80 marginBottom: getSpacingSideValue(attrs, 'margin', 'bottom'),
81 marginLeft: getSpacingSideValue(attrs, 'margin', 'left')
82 };
83 }
84
85 // Section icon
86 var sectionIcon = el('svg', {
87 width: 24,
88 height: 24,
89 viewBox: '0 0 24 24',
90 xmlns: 'http://www.w3.org/2000/svg'
91 },
92 el('path', {
93 d: 'M3 5h18v2H3V5zm0 12h18v2H3v-2zm0-6h18v2H3v-2z',
94 fill: 'currentColor'
95 })
96 );
97
98 registerBlockType('blockenberg/section', {
99 title: __('Section', 'blockenberg'),
100 icon: sectionIcon,
101 category: 'bkbg-layout',
102 description: __('A container section for organizing content with rows and columns.', 'blockenberg'),
103
104 edit: function (props) {
105 var attributes = props.attributes;
106 var setAttributes = props.setAttributes;
107 var a = attributes;
108
109 // Back-compat: if existing content used core align, map it to sectionWidth.
110 // (We keep this local to avoid mutating saved content unless user changes settings.)
111 var effectiveSectionWidth = a.sectionWidth;
112 if (!effectiveSectionWidth) {
113 if (a.align === 'full') effectiveSectionWidth = 'full';
114 else if (a.align === 'wide') effectiveSectionWidth = 'wide';
115 else effectiveSectionWidth = 'default';
116 }
117
118 // Keep core alignment in sync so the editor wrapper can actually change width.
119 // For "boxed" we intentionally use "wide" to give the block room in the editor,
120 // while our own max-width controls the final visual width.
121 var nextAlign = (effectiveSectionWidth === 'full')
122 ? 'full'
123 : (effectiveSectionWidth === 'wide' || effectiveSectionWidth === 'boxed')
124 ? 'wide'
125 : undefined;
126
127 useEffect(function () {
128 if (a.align !== nextAlign) {
129 setAttributes({ align: nextAlign });
130 }
131 }, [effectiveSectionWidth]);
132
133 var blockProps = useBlockProps({
134 className: [
135 'bkbg-section',
136 'bkbg-section--section-' + effectiveSectionWidth,
137 'bkbg-section--content-' + a.contentWidth,
138 'bkbg-section--padding-' + a.paddingY,
139 'bkbg-section--bg-' + a.background
140 ].join(' '),
141 style: Object.assign({}, {
142 '--bkbg-section-section-max-width': cssLength(a.sectionBoxedMaxWidth || a.boxedMaxWidth),
143 '--bkbg-section-max-width': cssLength(a.boxedMaxWidth),
144 '--bkbg-section-custom-bg': a.backgroundColor || undefined
145 }, getSpacingStyles(a))
146 });
147
148 function renderSpacingControl(type, label) {
149 var sides = [
150 { key: 'Top', label: __('Top', 'blockenberg') },
151 { key: 'Left', label: __('Left', 'blockenberg') },
152 { key: 'Bottom', label: __('Bottom', 'blockenberg') },
153 { key: 'Right', label: __('Right', 'blockenberg') }
154 ];
155
156 return el('div', { className: 'bkbg-spacing-control' },
157 el('div', { className: 'bkbg-spacing-control__title' }, label),
158 el('div', { className: 'bkbg-spacing-control__grid' },
159 sides.map(function (side) {
160 var valueKey = type + side.key;
161 var unitKey = type + side.key + 'Unit';
162 var customUnitKey = type + side.key + 'CustomUnit';
163
164 return el('div', { className: 'bkbg-spacing-control__item', key: valueKey },
165 el('label', { className: 'bkbg-spacing-control__label' }, side.label),
166 el('div', { className: 'bkbg-spacing-control__row' },
167 el('input', {
168 type: 'text',
169 className: 'components-text-control__input',
170 value: a[valueKey] || '',
171 placeholder: '0',
172 onChange: function (e) {
173 var next = {};
174 next[valueKey] = e.target.value;
175 setAttributes(next);
176 }
177 }),
178 el('select', {
179 className: 'components-select-control__input',
180 value: a[unitKey] || 'px',
181 onChange: function (e) {
182 var next = {};
183 next[unitKey] = e.target.value;
184 setAttributes(next);
185 }
186 }, spacingUnitOptions.map(function (opt) {
187 return el('option', { key: opt.value, value: opt.value }, opt.label);
188 }))
189 ),
190 (a[unitKey] || 'px') === 'custom' && el('input', {
191 type: 'text',
192 className: 'components-text-control__input bkbg-spacing-control__custom-unit',
193 value: a[customUnitKey] || '',
194 placeholder: __('unit, e.g. ch', 'blockenberg'),
195 onChange: function (e) {
196 var next = {};
197 next[customUnitKey] = e.target.value;
198 setAttributes(next);
199 }
200 })
201 );
202 })
203 )
204 );
205 }
206
207 // Max width UI modes (preset vs custom)
208 var presetMaxWidthValues = maxWidthOptions.map(function (o) { return o.value; });
209 var sectionMaxWidthValue = a.sectionBoxedMaxWidth || a.boxedMaxWidth || '';
210 var contentMaxWidthValue = a.boxedMaxWidth || '';
211
212 var initialSectionMode = presetMaxWidthValues.indexOf(sectionMaxWidthValue) !== -1 ? 'preset' : 'custom';
213 var initialContentMode = presetMaxWidthValues.indexOf(contentMaxWidthValue) !== -1 ? 'preset' : 'custom';
214
215 var _sectionModeState = useState(initialSectionMode);
216 var sectionMaxWidthMode = _sectionModeState[0];
217 var setSectionMaxWidthMode = _sectionModeState[1];
218
219 var _contentModeState = useState(initialContentMode);
220 var contentMaxWidthMode = _contentModeState[0];
221 var setContentMaxWidthMode = _contentModeState[1];
222
223 // If something external changes the value to a non-preset, ensure UI switches to custom.
224 useEffect(function () {
225 if (presetMaxWidthValues.indexOf(sectionMaxWidthValue) === -1 && sectionMaxWidthMode !== 'custom') {
226 setSectionMaxWidthMode('custom');
227 }
228 }, [sectionMaxWidthValue]);
229
230 useEffect(function () {
231 if (presetMaxWidthValues.indexOf(contentMaxWidthValue) === -1 && contentMaxWidthMode !== 'custom') {
232 setContentMaxWidthMode('custom');
233 }
234 }, [contentMaxWidthValue]);
235
236 // Toolbar controls - Section alignment + Content width
237 var toolbarAlignValue = (effectiveSectionWidth === 'full' || effectiveSectionWidth === 'wide')
238 ? effectiveSectionWidth
239 : undefined;
240 var toolbar = el(BlockControls, { group: 'block' },
241 el(BlockAlignmentToolbar, {
242 value: toolbarAlignValue,
243 onChange: function (v) {
244 // Only wide/full are controlled in the toolbar.
245 // Clearing alignment goes to "default".
246 var next = v ? v : 'default';
247 setAttributes({ sectionWidth: next });
248 },
249 controls: ['wide', 'full']
250 })
251 );
252
253 // Inspector controls
254 var inspector = el(InspectorControls, {},
255 el(PanelBody, { title: __('Section Settings', 'blockenberg'), initialOpen: true },
256 el(SelectControl, {
257 label: __('Section Width', 'blockenberg'),
258 help: __('Controls the width of the section itself (background). Default uses the theme content area. Wide uses the theme\'s wide width (wideSize).', 'blockenberg'),
259 value: effectiveSectionWidth,
260 options: [
261 { label: __('Full Width', 'blockenberg'), value: 'full' },
262 { label: __('Wide', 'blockenberg'), value: 'wide' },
263 { label: __('Boxed', 'blockenberg'), value: 'boxed' },
264 { label: __('Default', 'blockenberg'), value: 'default' }
265 ],
266 onChange: function (v) { setAttributes({ sectionWidth: v }); }
267 }),
268 effectiveSectionWidth === 'boxed' && el(wp.element.Fragment, {},
269 el(SelectControl, {
270 label: __('Section Max Width', 'blockenberg'),
271 value: sectionMaxWidthMode === 'custom' ? 'custom' : sectionMaxWidthValue,
272 options: maxWidthOptions.concat([
273 { label: __('Custom', 'blockenberg'), value: 'custom' }
274 ]),
275 onChange: function (v) {
276 if (v === 'custom') {
277 setSectionMaxWidthMode('custom');
278 return;
279 }
280 setSectionMaxWidthMode('preset');
281 setAttributes({ sectionBoxedMaxWidth: v });
282 }
283 }),
284 sectionMaxWidthMode === 'custom' && el(TextControl, {
285 label: __('Custom Section Max Width', 'blockenberg'),
286 help: __('Any CSS length, e.g. 1200px, 72rem, 90%, 80vw', 'blockenberg'),
287 value: sectionMaxWidthValue,
288 onChange: function (v) {
289 setSectionMaxWidthMode('custom');
290 setAttributes({ sectionBoxedMaxWidth: v });
291 }
292 })
293 ),
294 el(SelectControl, {
295 label: __('Content Width', 'blockenberg'),
296 help: __('Controls the width of the content inside the section.', 'blockenberg'),
297 value: a.contentWidth,
298 options: [
299 { label: __('Boxed', 'blockenberg'), value: 'boxed' },
300 { label: __('Full Width', 'blockenberg'), value: 'full' }
301 ],
302 onChange: function (v) { setAttributes({ contentWidth: v }); }
303 }),
304 a.contentWidth === 'boxed' && el(wp.element.Fragment, {},
305 el(SelectControl, {
306 label: __('Content Max Width', 'blockenberg'),
307 value: contentMaxWidthMode === 'custom' ? 'custom' : contentMaxWidthValue,
308 options: maxWidthOptions.concat([
309 { label: __('Custom', 'blockenberg'), value: 'custom' }
310 ]),
311 onChange: function (v) {
312 if (v === 'custom') {
313 setContentMaxWidthMode('custom');
314 return;
315 }
316 setContentMaxWidthMode('preset');
317 setAttributes({ boxedMaxWidth: v });
318 }
319 }),
320 contentMaxWidthMode === 'custom' && el(TextControl, {
321 label: __('Custom Content Max Width', 'blockenberg'),
322 help: __('Any CSS length, e.g. 1200px, 72rem, 90%, 80vw', 'blockenberg'),
323 value: contentMaxWidthValue,
324 onChange: function (v) {
325 setContentMaxWidthMode('custom');
326 setAttributes({ boxedMaxWidth: v });
327 }
328 })
329 ),
330 )
331 );
332
333 // Inner blocks template - only allow Row blocks
334 var innerBlocksTemplate = [
335 ['blockenberg/row', { columnsCount: 1 }]
336 ];
337 var allowedBlocks = ['blockenberg/row'];
338
339 return el('div', blockProps,
340 toolbar,
341 inspector,
342 el('div', { className: 'bkbg-section__inner' },
343 el(InnerBlocks, {
344 template: innerBlocksTemplate,
345 allowedBlocks: allowedBlocks,
346 templateLock: false,
347 renderAppender: false
348 })
349 )
350 );
351 },
352
353 save: function (props) {
354 var a = props.attributes;
355 var blockProps = useBlockProps.save({
356 className: [
357 'bkbg-section',
358 'bkbg-section--section-' + (a.sectionWidth || (a.align ? (a.align === 'full' ? 'full' : (a.align === 'wide' ? 'wide' : 'default')) : 'full')),
359 'bkbg-section--content-' + a.contentWidth,
360 'bkbg-section--padding-' + a.paddingY,
361 'bkbg-section--bg-' + a.background
362 ].join(' '),
363 style: Object.assign({}, {
364 '--bkbg-section-section-max-width': cssLength(a.sectionBoxedMaxWidth || a.boxedMaxWidth),
365 '--bkbg-section-max-width': cssLength(a.boxedMaxWidth),
366 '--bkbg-section-custom-bg': a.backgroundColor || undefined
367 }, getSpacingStyles(a))
368 });
369
370 return el('div', blockProps,
371 el('div', { className: 'bkbg-section__inner' },
372 el(InnerBlocks.Content)
373 )
374 );
375 }
376 });
377 }() );
378