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 / before-after / index.js

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

967 lines 50.2 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 __ = wp.i18n.__;
5 var useState = wp.element.useState;
6 var useMemo = wp.element.useMemo;
7
8 var registerBlockType = wp.blocks.registerBlockType;
9 var InspectorControls = wp.blockEditor.InspectorControls;
10 var BlockControls = wp.blockEditor.BlockControls;
11 var useBlockProps = wp.blockEditor.useBlockProps;
12
13 var PanelBody = wp.components.PanelBody;
14 var ToggleControl = wp.components.ToggleControl;
15 var RangeControl = wp.components.RangeControl;
16 var SelectControl = wp.components.SelectControl;
17 var TextControl = wp.components.TextControl;
18 var Button = wp.components.Button;
19 var ToolbarGroup = wp.components.ToolbarGroup;
20 var ToolbarButton = wp.components.ToolbarButton;
21 var Popover = wp.components.Popover;
22 var ColorPicker = wp.components.ColorPicker;
23 var TabPanel = wp.components.TabPanel;
24
25 function getTypographyControl() {
26 return (typeof window.bkbgTypographyControl !== 'undefined') ? window.bkbgTypographyControl : null;
27 }
28 function getTypoCssVars() {
29 return (typeof window.bkbgTypoCssVars !== 'undefined') ? window.bkbgTypoCssVars : function() { return {}; };
30 }
31
32 // Full Dashicons list (300+ icons)
33 var dashicons = [
34 // Admin Menu
35 'menu', 'admin-site', 'admin-site-alt', 'admin-site-alt2', 'admin-site-alt3', 'dashboard',
36 'admin-post', 'admin-media', 'admin-links', 'admin-page', 'admin-comments',
37 'admin-appearance', 'admin-plugins', 'admin-users', 'admin-tools', 'admin-settings',
38 'admin-network', 'admin-home', 'admin-generic', 'admin-collapse', 'filter',
39 'admin-customizer', 'admin-multisite',
40 // Welcome Screen
41 'welcome-write-blog', 'welcome-add-page', 'welcome-view-site', 'welcome-widgets-menus',
42 'welcome-comments', 'welcome-learn-more',
43 // Post Formats
44 'format-aside', 'format-image', 'format-gallery', 'format-video', 'format-status',
45 'format-quote', 'format-chat', 'format-audio',
46 // Media
47 'camera', 'camera-alt', 'images-alt', 'images-alt2', 'video-alt', 'video-alt2',
48 'video-alt3', 'media-archive', 'media-audio', 'media-code', 'media-default',
49 'media-document', 'media-interactive', 'media-spreadsheet', 'media-text',
50 'media-video', 'playlist-audio', 'playlist-video', 'controls-play',
51 'controls-pause', 'controls-forward', 'controls-skipforward', 'controls-back',
52 'controls-skipback', 'controls-repeat', 'controls-volumeon', 'controls-volumeoff',
53 // Image Editing
54 'image-crop', 'image-rotate', 'image-rotate-left', 'image-rotate-right',
55 'image-flip-vertical', 'image-flip-horizontal', 'image-filter',
56 // Sorting & Misc UI
57 'align-left', 'align-right', 'align-center', 'align-none', 'align-pull-left', 'align-pull-right',
58 'align-full-width', 'align-wide',
59 'lock', 'unlock', 'calendar', 'calendar-alt', 'visibility', 'hidden',
60 'post-status', 'edit', 'trash', 'sticky',
61 // Arrows & Navigation
62 'arrow-up', 'arrow-down', 'arrow-right', 'arrow-left',
63 'arrow-up-alt', 'arrow-down-alt', 'arrow-right-alt', 'arrow-left-alt',
64 'arrow-up-alt2', 'arrow-down-alt2', 'arrow-right-alt2', 'arrow-left-alt2',
65 'sort', 'leftright', 'randomize', 'list-view', 'excerpt-view', 'grid-view',
66 'move', 'undo', 'redo',
67 // Social & Sharing
68 'share', 'share-alt', 'share-alt2', 'rss', 'email', 'email-alt', 'email-alt2',
69 'networking', 'amazon', 'facebook', 'facebook-alt', 'google',
70 'twitter', 'twitter-alt', 'instagram', 'linkedin', 'pinterest', 'podio',
71 'reddit', 'spotify', 'twitch', 'whatsapp', 'xing', 'youtube',
72 // WordPress.org Specific
73 'hammer', 'art', 'migrate', 'performance', 'universal-access',
74 'universal-access-alt', 'tickets', 'nametag', 'clipboard', 'heart', 'megaphone',
75 'schedule', 'tide', 'rest-api', 'code-standards',
76 // BuddyPress
77 'buddicons-activity', 'buddicons-bbpress-logo', 'buddicons-buddypress-logo',
78 'buddicons-community', 'buddicons-forums', 'buddicons-friends',
79 'buddicons-groups', 'buddicons-pm', 'buddicons-replies',
80 'buddicons-topics', 'buddicons-tracking',
81 // WordPress Core
82 'wordpress', 'wordpress-alt', 'pressthis', 'update', 'update-alt',
83 'screenoptions', 'info', 'cart', 'feedback', 'cloud', 'cloud-saved',
84 'cloud-upload', 'translation', 'tag', 'category', 'archive',
85 // Status & Alerts
86 'yes', 'yes-alt', 'no', 'no-alt', 'plus', 'plus-alt', 'plus-alt2',
87 'minus', 'dismiss', 'marker', 'star-filled', 'star-half', 'star-empty',
88 'flag', 'warning', 'thumbs-up', 'thumbs-down', 'smiley',
89 // Location & Places
90 'location', 'location-alt', 'vault', 'shield', 'shield-alt',
91 'sos', 'search', 'slides', 'text-page', 'analytics', 'chart-pie',
92 'chart-bar', 'chart-line', 'chart-area', 'groups', 'businessman', 'businesswoman',
93 'businessperson', 'id', 'id-alt', 'products', 'awards', 'forms', 'testimonial',
94 'portfolio', 'book', 'book-alt', 'download', 'upload', 'backup',
95 'clock', 'lightbulb', 'microphone', 'desktop', 'laptop', 'tablet',
96 'smartphone', 'phone', 'index-card', 'carrot', 'building', 'store',
97 'album', 'palmtree', 'tickets-alt', 'money', 'money-alt', 'bank',
98 // Misc Objects
99 'layout', 'paperclip', 'color-picker', 'edit-large', 'edit-page',
100 'airplane', 'beer', 'bell', 'bitcoin', 'calculator', 'car', 'coffee',
101 'database', 'database-add', 'database-export', 'database-import',
102 'database-remove', 'database-view', 'drumstick', 'ellipsis', 'exit',
103 'food', 'fullscreen-alt', 'fullscreen-exit-alt', 'games', 'hourglass',
104 'html', 'insert', 'insert-after', 'insert-before', 'open-folder',
105 'pdf', 'pets', 'plugins-checked', 'printer', 'privacy', 'remove',
106 'saved', 'shortcode', 'superhero', 'superhero-alt', 'text', 'external',
107 // Table Operations
108 'table-col-after', 'table-col-before', 'table-col-delete',
109 'table-row-after', 'table-row-before', 'table-row-delete',
110 // Editor Controls
111 'editor-aligncenter', 'editor-alignleft', 'editor-alignright', 'editor-bold',
112 'editor-break', 'editor-code', 'editor-contract', 'editor-customchar',
113 'editor-expand', 'editor-help', 'editor-indent', 'editor-insertmore',
114 'editor-italic', 'editor-justify', 'editor-kitchensink', 'editor-ltr',
115 'editor-ol', 'editor-ol-rtl', 'editor-outdent', 'editor-paragraph',
116 'editor-paste-text', 'editor-paste-word', 'editor-quote', 'editor-removeformatting',
117 'editor-rtl', 'editor-spellcheck', 'editor-strikethrough', 'editor-table',
118 'editor-textcolor', 'editor-ul', 'editor-underline', 'editor-unlink', 'editor-video'
119 ];
120
121 function parseIcon(iconValue) {
122 if (!iconValue) return { type: 'dashicon', value: 'leftright' };
123 if (typeof iconValue === 'string') return { type: 'dashicon', value: iconValue };
124 return iconValue;
125 }
126
127 function clamp(n, min, max) {
128 n = parseFloat(n);
129 if (isNaN(n)) n = min;
130 return Math.max(min, Math.min(max, n));
131 }
132
133 function colorToRgbaString(color) {
134 if (!color || !color.rgb) return '';
135 var a = (typeof color.rgb.a === 'number') ? color.rgb.a : 1;
136 return 'rgba(' + color.rgb.r + ',' + color.rgb.g + ',' + color.rgb.b + ',' + a + ')';
137 }
138
139 function openMedia(onSelect) {
140 if (!wp || !wp.media) return;
141 var frame = wp.media({
142 title: __('Select Image', 'blockenberg'),
143 button: { text: __('Use image', 'blockenberg') },
144 multiple: false,
145 library: { type: 'image' }
146 });
147 frame.on('select', function () {
148 var attachment = frame.state().get('selection').first().toJSON();
149 onSelect(attachment);
150 });
151 frame.open();
152 }
153
154 function normalizeImage(att) {
155 return {
156 id: att && att.id ? att.id : 0,
157 url: att && att.url ? att.url : '',
158 alt: att && att.alt ? att.alt : ''
159 };
160 }
161
162 registerBlockType('blockenberg/before-after', {
163 title: __('Before/After Slider', 'blockenberg'),
164 icon: 'image-flip-horizontal',
165 category: 'bkbg-media',
166 description: __('Compare two images with a draggable slider handle.', 'blockenberg'),
167
168 edit: function (props) {
169 var a = props.attributes;
170 var setAttributes = props.setAttributes;
171 var TC = getTypographyControl();
172 var isSelected = props.isSelected;
173
174 var dragState = useState(null);
175 var dragging = dragState[0];
176 var setDragging = dragState[1];
177
178 var colorPopoverState = useState(null);
179 var openColor = colorPopoverState[0];
180 var setOpenColor = colorPopoverState[1];
181
182 // Handle icon picker popover
183 var iconPickerState = useState(false);
184 var openHandleIconPicker = iconPickerState[0];
185 var setOpenHandleIconPicker = iconPickerState[1];
186
187 // Dashicon search
188 var searchState = useState('');
189 var iconSearch = searchState[0];
190 var setIconSearch = searchState[1];
191
192 var filteredDashicons = useMemo(function () {
193 if (!iconSearch) return dashicons;
194 var s = iconSearch.toLowerCase();
195 return dashicons.filter(function (name) {
196 return name.toLowerCase().includes(s);
197 });
198 }, [iconSearch]);
199
200 function setBeforeImage(att) {
201 setAttributes({ beforeImage: normalizeImage(att) });
202 }
203
204 function setAfterImage(att) {
205 setAttributes({ afterImage: normalizeImage(att) });
206 }
207
208 function swapImages() {
209 setAttributes({ beforeImage: a.afterImage, afterImage: a.beforeImage });
210 }
211
212 function setPos(next) {
213 setAttributes({ startPosition: clamp(next, 0, 100) });
214 }
215
216 function posFromEvent(e, containerEl) {
217 if (!containerEl) return a.startPosition;
218
219 var rect = containerEl.getBoundingClientRect();
220 var clientX = (e.touches && e.touches[0]) ? e.touches[0].clientX : e.clientX;
221 var clientY = (e.touches && e.touches[0]) ? e.touches[0].clientY : e.clientY;
222
223 if (a.orientation === 'vertical') {
224 var y = clientY - rect.top;
225 return clamp((y / rect.height) * 100, 0, 100);
226 }
227
228 var x = clientX - rect.left;
229 return clamp((x / rect.width) * 100, 0, 100);
230 }
231
232 function beginDrag(e) {
233 // Don't hijack clicks on controls (e.g., the Select buttons in empty states)
234 if (e && e.target && e.target.closest) {
235 if (e.target.closest('button, a, input, textarea, select, .components-button')) {
236 return false;
237 }
238 }
239 e.preventDefault();
240 setDragging(true);
241 return true;
242 }
243
244 function onMove(e, containerEl) {
245 if (!dragging) return;
246 e.preventDefault();
247 setPos(posFromEvent(e, containerEl));
248 }
249
250 function endDrag() {
251 setDragging(false);
252 }
253
254 var layoutClass = 'bkbg-ba-wrap bkbg-editor-wrap';
255 if (isSelected) layoutClass += ' bkbg-block-selected';
256 layoutClass += a.orientation === 'vertical' ? ' is-vertical' : ' is-horizontal';
257 layoutClass += a.shadow ? ' has-shadow' : '';
258 if (a.handleHoverAnim) {
259 layoutClass += ' has-handle-anim';
260 layoutClass += a.handleHoverAnimType ? (' is-handle-anim-' + a.handleHoverAnimType) : ' is-handle-anim-pulse';
261 }
262 if (a.handleHoverScale) {
263 layoutClass += ' has-handle-hover';
264 }
265
266 var _tv = getTypoCssVars();
267 var styleVars = {
268 '--bkbg-ba-pos': a.startPosition + '%',
269 '--bkbg-ba-radius': a.borderRadius + 'px',
270 '--bkbg-ba-handle-size': a.handleSize + 'px',
271 '--bkbg-ba-handle-icon-size': (a.handleIconSize || 18) + 'px',
272 '--bkbg-ba-handle-anim-dur': (a.handleHoverAnimDuration || 650) + 'ms',
273 '--bkbg-ba-handle-hover-scale': (a.handleHoverScaleAmount || 1.08),
274 '--bkbg-ba-handle-bg': a.handleBg,
275 '--bkbg-ba-handle-color': a.handleColor,
276 '--bkbg-ba-line-color': a.lineColor,
277 '--bkbg-ba-line-width': a.lineWidth + 'px',
278 '--bkbg-ba-label-bg': a.labelBg,
279 '--bkbg-ba-label-color': a.labelColor,
280 '--bkbg-ba-label-radius': a.labelRadius + 'px',
281 '--bkbg-ba-min-height': a.minHeight + 'px'
282 };
283 Object.assign(styleVars, _tv(a.labelTypo || {}, '--bkbg-ba-label-'));
284
285 function renderHandleIcon() {
286 var icon = parseIcon(a.handleIcon);
287 if (!icon || icon.type === 'none') return null;
288 if (icon.type === 'custom') {
289 return el('img', { className: 'bkbg-ba-handle-img', src: icon.value, alt: '' });
290 }
291 return el('span', { className: 'dashicons dashicons-' + icon.value + ' bkbg-ba-handle-icon' });
292 }
293
294 function renderHandleIconWrapped() {
295 return el('span', { className: 'bkbg-ba-handle-surface' },
296 el('span', { className: 'bkbg-ba-handle-anim' },
297 el('span', { className: 'bkbg-ba-handle-hover' },
298 renderHandleIcon()
299 )
300 )
301 );
302 }
303
304 function renderHandleIconPicker(position) {
305 var currentIcon = parseIcon(a.handleIcon);
306
307 var stop = function (e) {
308 if (e && e.stopPropagation) e.stopPropagation();
309 };
310
311 var openMediaLibrary = function () {
312 var frame = wp.media({
313 title: __('Select Icon Image', 'blockenberg'),
314 button: { text: __('Use as Icon', 'blockenberg') },
315 multiple: false,
316 library: { type: 'image' }
317 });
318 frame.on('select', function () {
319 var attachment = frame.state().get('selection').first().toJSON();
320 setAttributes({ handleIcon: { type: 'custom', value: attachment.url } });
321 setOpenHandleIconPicker(false);
322 });
323 frame.open();
324 };
325
326 return el(Popover, {
327 position: position || 'bottom center',
328 className: 'bkbg-ba-icon-popover',
329 onClose: function () {
330 setOpenHandleIconPicker(false);
331 setIconSearch('');
332 }
333 },
334 el('div', { className: 'bkbg-ba-icon-picker-modal', onMouseDown: stop, onClick: stop },
335 el('div', { className: 'bkbg-ba-icon-picker-header' },
336 el('h3', {}, __('Choose Handle Icon', 'blockenberg')),
337 el(Button, {
338 icon: 'no-alt',
339 onClick: function () {
340 setOpenHandleIconPicker(false);
341 setIconSearch('');
342 },
343 className: 'bkbg-ba-icon-picker-close'
344 })
345 ),
346 el(TabPanel, {
347 className: 'bkbg-ba-icon-tabs',
348 activeClass: 'is-active',
349 tabs: [
350 { name: 'dashicons', title: __('Dashicons', 'blockenberg'), className: 'bkbg-ba-tab-dashicons' },
351 { name: 'media', title: __('Media Library', 'blockenberg'), className: 'bkbg-ba-tab-media' }
352 ]
353 }, function (tab) {
354 if (tab.name === 'dashicons') {
355 return el('div', { className: 'bkbg-ba-dashicons-panel' },
356 el('div', { className: 'bkbg-ba-icon-actions' },
357 el(Button, {
358 variant: 'secondary',
359 onClick: function () {
360 setAttributes({ handleIcon: { type: 'none', value: '' } });
361 setOpenHandleIconPicker(false);
362 setIconSearch('');
363 }
364 }, __('No icon', 'blockenberg')),
365 el(Button, {
366 variant: 'secondary',
367 onClick: function () {
368 setAttributes({ handleIcon: { type: 'dashicon', value: 'leftright' } });
369 setOpenHandleIconPicker(false);
370 setIconSearch('');
371 }
372 }, __('Reset', 'blockenberg'))
373 ),
374 el(TextControl, {
375 placeholder: __('Search icons...', 'blockenberg'),
376 value: iconSearch,
377 onChange: setIconSearch,
378 className: 'bkbg-ba-icon-search'
379 }),
380 el('div', { className: 'bkbg-ba-dashicons-grid' },
381 filteredDashicons.length === 0
382 ? el('p', { className: 'bkbg-ba-no-icons' }, __('No icons found', 'blockenberg'))
383 : filteredDashicons.map(function (name) {
384 var isActive = currentIcon.type === 'dashicon' && currentIcon.value === name;
385 return el('button', {
386 key: name,
387 type: 'button',
388 className: 'bkbg-ba-dashicon-btn' + (isActive ? ' is-active' : ''),
389 onClick: function () {
390 setAttributes({ handleIcon: { type: 'dashicon', value: name } });
391 setOpenHandleIconPicker(false);
392 setIconSearch('');
393 },
394 title: name
395 },
396 el('span', { className: 'dashicons dashicons-' + name })
397 );
398 })
399 )
400 );
401 }
402
403 // Media tab
404 return el('div', { className: 'bkbg-ba-media-panel' },
405 currentIcon.type === 'custom' && el('div', { className: 'bkbg-ba-current-media' },
406 el('img', { src: currentIcon.value, alt: '' }),
407 el(Button, {
408 isDestructive: true,
409 variant: 'secondary',
410 onClick: function () {
411 setAttributes({ handleIcon: { type: 'dashicon', value: 'leftright' } });
412 }
413 }, __('Remove', 'blockenberg'))
414 ),
415 el(Button, {
416 variant: 'secondary',
417 onClick: openMediaLibrary,
418 className: 'bkbg-ba-media-btn'
419 },
420 el('span', { className: 'dashicons dashicons-upload' }),
421 ' ',
422 __('Select from Media Library', 'blockenberg')
423 ),
424 el('p', { className: 'bkbg-ba-media-hint' },
425 __('Upload or select an image to use as a custom handle icon.', 'blockenberg')
426 )
427 );
428 })
429 )
430 );
431 }
432
433 var blockProps = useBlockProps({
434 className: layoutClass,
435 style: styleVars,
436 'data-block-label': 'Before/After'
437 });
438
439 var inspector = el(InspectorControls, {},
440 el(PanelBody, { title: __('Images', 'blockenberg'), initialOpen: true },
441 el('div', { className: 'bkbg-ba-media-row' },
442 el('div', { className: 'bkbg-ba-media-col' },
443 el('div', { className: 'bkbg-ba-media-title' }, __('Before image', 'blockenberg')),
444 a.beforeImage && a.beforeImage.url
445 ? el('img', { className: 'bkbg-ba-media-thumb', src: a.beforeImage.url, alt: a.beforeImage.alt || '' })
446 : el('div', { className: 'bkbg-ba-media-placeholder' }, __('No image selected', 'blockenberg')),
447 el(Button, {
448 variant: 'secondary',
449 onClick: function () { openMedia(setBeforeImage); }
450 }, a.beforeImage && a.beforeImage.url ? __('Replace', 'blockenberg') : __('Select', 'blockenberg')),
451 (a.beforeImage && a.beforeImage.url) && el(Button, {
452 variant: 'tertiary',
453 isDestructive: true,
454 onClick: function () { setAttributes({ beforeImage: { id: 0, url: '', alt: '' } }); }
455 }, __('Remove', 'blockenberg'))
456 ),
457 el('div', { className: 'bkbg-ba-media-col' },
458 el('div', { className: 'bkbg-ba-media-title' }, __('After image', 'blockenberg')),
459 a.afterImage && a.afterImage.url
460 ? el('img', { className: 'bkbg-ba-media-thumb', src: a.afterImage.url, alt: a.afterImage.alt || '' })
461 : el('div', { className: 'bkbg-ba-media-placeholder' }, __('No image selected', 'blockenberg')),
462 el(Button, {
463 variant: 'secondary',
464 onClick: function () { openMedia(setAfterImage); }
465 }, a.afterImage && a.afterImage.url ? __('Replace', 'blockenberg') : __('Select', 'blockenberg')),
466 (a.afterImage && a.afterImage.url) && el(Button, {
467 variant: 'tertiary',
468 isDestructive: true,
469 onClick: function () { setAttributes({ afterImage: { id: 0, url: '', alt: '' } }); }
470 }, __('Remove', 'blockenberg'))
471 )
472 ),
473 el(Button, {
474 variant: 'secondary',
475 disabled: !(a.beforeImage && a.beforeImage.url && a.afterImage && a.afterImage.url),
476 onClick: swapImages
477 }, __('Swap images', 'blockenberg'))
478 ),
479
480 el(PanelBody, { title: __('Slider', 'blockenberg'), initialOpen: false },
481 el(SelectControl, {
482 label: __('Orientation', 'blockenberg'),
483 value: a.orientation,
484 options: [
485 { label: __('Horizontal', 'blockenberg'), value: 'horizontal' },
486 { label: __('Vertical', 'blockenberg'), value: 'vertical' }
487 ],
488 onChange: function (v) { setAttributes({ orientation: v }); }
489 }),
490 el(RangeControl, {
491 label: __('Start position (%)', 'blockenberg'),
492 value: a.startPosition,
493 onChange: setPos,
494 min: 0,
495 max: 100
496 }),
497 el(RangeControl, {
498 label: __('Minimum height (px)', 'blockenberg'),
499 value: a.minHeight,
500 onChange: function (v) { setAttributes({ minHeight: v }); },
501 min: 120,
502 max: 900
503 }),
504 el(SelectControl, {
505 label: __('Aspect ratio', 'blockenberg'),
506 value: a.aspect,
507 options: [
508 { label: __('Auto', 'blockenberg'), value: 'auto' },
509 { label: '1:1', value: '1/1' },
510 { label: '4:3', value: '4/3' },
511 { label: '3:2', value: '3/2' },
512 { label: '16:9', value: '16/9' },
513 { label: '21:9', value: '21/9' }
514 ],
515 onChange: function (v) { setAttributes({ aspect: v }); }
516 })
517 ),
518
519 el(PanelBody, { title: __('Labels', 'blockenberg'), initialOpen: false },
520 el(ToggleControl, {
521 label: __('Show labels', 'blockenberg'),
522 checked: a.showLabels,
523 __nextHasNoMarginBottom: true,
524 onChange: function (v) { setAttributes({ showLabels: v }); }
525 }),
526 a.showLabels && el(SelectControl, {
527 label: __('Label position', 'blockenberg'),
528 value: a.labelPosition,
529 options: [
530 { label: __('Top', 'blockenberg'), value: 'top' },
531 { label: __('Bottom', 'blockenberg'), value: 'bottom' }
532 ],
533 onChange: function (v) { setAttributes({ labelPosition: v }); }
534 }),
535 a.showLabels && el(TextControl, {
536 label: __('Before label', 'blockenberg'),
537 value: a.beforeLabel,
538 onChange: function (v) { setAttributes({ beforeLabel: v }); }
539 }),
540 a.showLabels && el(TextControl, {
541 label: __('After label', 'blockenberg'),
542 value: a.afterLabel,
543 onChange: function (v) { setAttributes({ afterLabel: v }); }
544 }),
545 a.showLabels && el('div', { className: 'bkbg-ba-color-row' },
546 el('span', {}, __('Label background', 'blockenberg')),
547 el('button', {
548 className: 'bkbg-ba-color-btn',
549 style: { backgroundColor: a.labelBg },
550 onClick: function () { setOpenColor(openColor === 'labelBg' ? null : 'labelBg'); }
551 }),
552 openColor === 'labelBg' && el(Popover, { position: 'bottom left', onClose: function () { setOpenColor(null); } },
553 el(ColorPicker, {
554 color: a.labelBg,
555 onChangeComplete: function (c) {
556 var rgba = colorToRgbaString(c);
557 setAttributes({ labelBg: rgba || (c.hex ? c.hex : a.labelBg) });
558 }
559 })
560 )
561 ),
562 a.showLabels && el('div', { className: 'bkbg-ba-color-row' },
563 el('span', {}, __('Label text', 'blockenberg')),
564 el('button', {
565 className: 'bkbg-ba-color-btn',
566 style: { backgroundColor: a.labelColor },
567 onClick: function () { setOpenColor(openColor === 'labelColor' ? null : 'labelColor'); }
568 }),
569 openColor === 'labelColor' && el(Popover, { position: 'bottom left', onClose: function () { setOpenColor(null); } },
570 el(ColorPicker, {
571 color: a.labelColor,
572 onChangeComplete: function (c) { setAttributes({ labelColor: c.hex }); }
573 })
574 )
575 ),
576 a.showLabels && el(RangeControl, {
577 label: __('Label radius', 'blockenberg'),
578 value: a.labelRadius,
579 onChange: function (v) { setAttributes({ labelRadius: v }); },
580 min: 0,
581 max: 30
582 })
583 ),
584
585 el(PanelBody, { title: __('Handle', 'blockenberg'), initialOpen: false },
586 el(SelectControl, {
587 label: __('Handle style', 'blockenberg'),
588 value: a.handleStyle,
589 options: [
590 { label: __('Circle', 'blockenberg'), value: 'circle' },
591 { label: __('Square', 'blockenberg'), value: 'square' }
592 ],
593 onChange: function (v) { setAttributes({ handleStyle: v }); }
594 }),
595 el(RangeControl, {
596 label: __('Handle size', 'blockenberg'),
597 value: a.handleSize,
598 onChange: function (v) { setAttributes({ handleSize: v }); },
599 min: 28,
600 max: 80
601 }),
602
603 el('div', { className: 'bkbg-ba-icon-row' },
604 el('span', { className: 'bkbg-ba-icon-row-label' }, __('Handle icon', 'blockenberg')),
605 el('button', {
606 type: 'button',
607 className: 'bkbg-ba-icon-picker-btn',
608 onMouseDown: function (e) { e.stopPropagation(); },
609 onClick: function (e) {
610 e.preventDefault();
611 e.stopPropagation();
612 setOpenHandleIconPicker(!openHandleIconPicker);
613 }
614 },
615 (function () {
616 var icon = parseIcon(a.handleIcon);
617 if (icon.type === 'custom') {
618 return el('img', { className: 'bkbg-ba-icon-btn-img', src: icon.value, alt: '' });
619 }
620 if (icon.type === 'none') {
621 return el('span', { className: 'bkbg-ba-icon-btn-none' }, __('None', 'blockenberg'));
622 }
623 return el('span', { className: 'dashicons dashicons-' + icon.value });
624 })()
625 ),
626 openHandleIconPicker && el('div', { className: 'bkbg-ba-icon-popover-anchor' },
627 renderHandleIconPicker('middle left')
628 )
629 ),
630 el(RangeControl, {
631 label: __('Handle icon size', 'blockenberg'),
632 value: a.handleIconSize || 18,
633 onChange: function (v) { setAttributes({ handleIconSize: v }); },
634 min: 10,
635 max: 34
636 }),
637 el(RangeControl, {
638 label: __('Divider line width', 'blockenberg'),
639 value: a.lineWidth,
640 onChange: function (v) { setAttributes({ lineWidth: v }); },
641 min: 1,
642 max: 8
643 }),
644 el('div', { className: 'bkbg-ba-color-row' },
645 el('span', {}, __('Handle background', 'blockenberg')),
646 el('button', {
647 className: 'bkbg-ba-color-btn',
648 style: { backgroundColor: a.handleBg },
649 onClick: function () { setOpenColor(openColor === 'handleBg' ? null : 'handleBg'); }
650 }),
651 openColor === 'handleBg' && el(Popover, { position: 'bottom left', onClose: function () { setOpenColor(null); } },
652 el(ColorPicker, {
653 color: a.handleBg,
654 onChangeComplete: function (c) { setAttributes({ handleBg: c.hex }); }
655 })
656 )
657 ),
658 el('div', { className: 'bkbg-ba-color-row' },
659 el('span', {}, __('Handle icon', 'blockenberg')),
660 el('button', {
661 className: 'bkbg-ba-color-btn',
662 style: { backgroundColor: a.handleColor },
663 onClick: function () { setOpenColor(openColor === 'handleColor' ? null : 'handleColor'); }
664 }),
665 openColor === 'handleColor' && el(Popover, { position: 'bottom left', onClose: function () { setOpenColor(null); } },
666 el(ColorPicker, {
667 color: a.handleColor,
668 onChangeComplete: function (c) { setAttributes({ handleColor: c.hex }); }
669 })
670 )
671 ),
672 el('div', { className: 'bkbg-ba-color-row' },
673 el('span', {}, __('Line color', 'blockenberg')),
674 el('button', {
675 className: 'bkbg-ba-color-btn',
676 style: { backgroundColor: a.lineColor },
677 onClick: function () { setOpenColor(openColor === 'lineColor' ? null : 'lineColor'); }
678 }),
679 openColor === 'lineColor' && el(Popover, { position: 'bottom left', onClose: function () { setOpenColor(null); } },
680 el(ColorPicker, {
681 color: a.lineColor,
682 onChangeComplete: function (c) {
683 var rgba = colorToRgbaString(c);
684 setAttributes({ lineColor: rgba || c.hex });
685 }
686 })
687 )
688 ),
689
690 el(ToggleControl, {
691 label: __('Animate handle', 'blockenberg'),
692 checked: !!a.handleHoverAnim,
693 __nextHasNoMarginBottom: true,
694 onChange: function (v) { setAttributes({ handleHoverAnim: v }); }
695 }),
696 a.handleHoverAnim && el(SelectControl, {
697 label: __('Animation', 'blockenberg'),
698 value: a.handleHoverAnimType || 'pulse',
699 options: [
700 { label: __('Pulse', 'blockenberg'), value: 'pulse' },
701 { label: __('Wiggle', 'blockenberg'), value: 'wiggle' },
702 { label: __('Bounce', 'blockenberg'), value: 'bounce' },
703 { label: __('Glow', 'blockenberg'), value: 'glow' }
704 ],
705 onChange: function (v) { setAttributes({ handleHoverAnimType: v }); }
706 }),
707 a.handleHoverAnim && el(RangeControl, {
708 label: __('Animation duration (ms)', 'blockenberg'),
709 value: a.handleHoverAnimDuration || 650,
710 onChange: function (v) { setAttributes({ handleHoverAnimDuration: v }); },
711 min: 150,
712 max: 2000
713 }),
714
715 el(ToggleControl, {
716 label: __('Enlarge icon area on hover', 'blockenberg'),
717 checked: !!a.handleHoverScale,
718 __nextHasNoMarginBottom: true,
719 onChange: function (v) { setAttributes({ handleHoverScale: v }); }
720 }),
721 a.handleHoverScale && el(RangeControl, {
722 label: __('Hover scale', 'blockenberg'),
723 value: a.handleHoverScaleAmount || 1.08,
724 onChange: function (v) { setAttributes({ handleHoverScaleAmount: v }); },
725 min: 1,
726 max: 1.25,
727 step: 0.01
728 })
729 ),
730
731 el(PanelBody, { title: __('Frame', 'blockenberg'), initialOpen: false },
732 el(RangeControl, {
733 label: __('Border radius', 'blockenberg'),
734 value: a.borderRadius,
735 onChange: function (v) { setAttributes({ borderRadius: v }); },
736 min: 0,
737 max: 40
738 }),
739 el(ToggleControl, {
740 label: __('Shadow', 'blockenberg'),
741 checked: a.shadow,
742 __nextHasNoMarginBottom: true,
743 onChange: function (v) { setAttributes({ shadow: v }); }
744 })
745 ),
746 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
747 a.showLabels && TC && el(TC, { label: __('Label', 'blockenberg'), value: a.labelTypo || {}, onChange: function(v){ setAttributes({labelTypo: v}); } })
748 )
749 );
750
751 var toolbar = el(BlockControls, {},
752 el(ToolbarGroup, {},
753 el(ToolbarButton, {
754 icon: 'image-flip-horizontal',
755 label: __('Swap images', 'blockenberg'),
756 disabled: !(a.beforeImage && a.beforeImage.url && a.afterImage && a.afterImage.url),
757 onClick: swapImages
758 }),
759 el(ToolbarButton, {
760 icon: 'image-rotate',
761 label: __('Reset position', 'blockenberg'),
762 onClick: function () { setPos(50); }
763 })
764 )
765 );
766
767 function renderInner() {
768 var hasBefore = a.beforeImage && a.beforeImage.url;
769 var hasAfter = a.afterImage && a.afterImage.url;
770
771 // Empty / partial state: show two side-by-side panels so selectors never overlap.
772 if (!hasBefore || !hasAfter) {
773 return el('div', { className: 'bkbg-ba-inner bkbg-ba-empty-inner' },
774 el('div', { className: 'bkbg-ba-empty-grid' },
775 el('div', { className: 'bkbg-ba-empty-panel is-before' },
776 el('div', { className: 'bkbg-ba-empty-panel-title' }, __('Before', 'blockenberg')),
777 hasBefore
778 ? el('img', { className: 'bkbg-ba-empty-thumb', src: a.beforeImage.url, alt: a.beforeImage.alt || '' })
779 : el('div', { className: 'bkbg-ba-empty-copy' }, __('Select the BEFORE image', 'blockenberg')),
780 el(Button, {
781 variant: 'secondary',
782 onMouseDown: function (e) { e.stopPropagation(); },
783 onTouchStart: function (e) { e.stopPropagation(); },
784 onClick: function () { openMedia(setBeforeImage); }
785 }, hasBefore ? __('Replace Before', 'blockenberg') : __('Select Before', 'blockenberg'))
786 ),
787 el('div', { className: 'bkbg-ba-empty-panel is-after' },
788 el('div', { className: 'bkbg-ba-empty-panel-title' }, __('After', 'blockenberg')),
789 hasAfter
790 ? el('img', { className: 'bkbg-ba-empty-thumb', src: a.afterImage.url, alt: a.afterImage.alt || '' })
791 : el('div', { className: 'bkbg-ba-empty-copy' }, __('Select the AFTER image', 'blockenberg')),
792 el(Button, {
793 variant: 'secondary',
794 onMouseDown: function (e) { e.stopPropagation(); },
795 onTouchStart: function (e) { e.stopPropagation(); },
796 onClick: function () { openMedia(setAfterImage); }
797 }, hasAfter ? __('Replace After', 'blockenberg') : __('Select After', 'blockenberg'))
798 )
799 )
800 );
801 }
802
803 return el('div', {
804 className: 'bkbg-ba-inner',
805 onMouseMove: function (e) { onMove(e, e.currentTarget); },
806 onTouchMove: function (e) { onMove(e, e.currentTarget); },
807 onMouseUp: endDrag,
808 onMouseLeave: endDrag,
809 onTouchEnd: endDrag,
810 onTouchCancel: endDrag
811 },
812 el('div', { className: 'bkbg-ba-layer bkbg-ba-before' },
813 el('img', { src: a.beforeImage.url, alt: a.beforeImage.alt || '' })
814 ),
815 el('div', { className: 'bkbg-ba-layer bkbg-ba-after' },
816 el('img', { src: a.afterImage.url, alt: a.afterImage.alt || '' })
817 ),
818 el('div', { className: 'bkbg-ba-divider' },
819 el('div', {
820 className: 'bkbg-ba-handle' + (a.handleStyle === 'square' ? ' is-square' : ' is-circle'),
821 onMouseDown: function (e) {
822 if (beginDrag(e)) {
823 setPos(posFromEvent(e, e.currentTarget.closest('.bkbg-ba-inner')));
824 }
825 },
826 onTouchStart: function (e) {
827 if (beginDrag(e)) {
828 setPos(posFromEvent(e, e.currentTarget.closest('.bkbg-ba-inner')));
829 }
830 }
831 },
832 renderHandleIconWrapped()
833 )
834 ),
835 a.showLabels && el('div', { className: 'bkbg-ba-labels is-' + a.labelPosition },
836 el('div', { className: 'bkbg-ba-label bkbg-ba-label-before' }, a.beforeLabel || __('Before', 'blockenberg')),
837 el('div', { className: 'bkbg-ba-label bkbg-ba-label-after' }, a.afterLabel || __('After', 'blockenberg'))
838 ),
839 // Accessible range input
840 el('input', {
841 className: 'bkbg-ba-range',
842 type: 'range',
843 min: 0,
844 max: 100,
845 value: a.startPosition,
846 onChange: function (e) { setPos(e.target.value); },
847 'aria-label': __('Before/After position', 'blockenberg')
848 })
849 );
850 }
851
852 var content = el('div', blockProps,
853 el('div', {
854 className: 'bkbg-ba-aspect' + (a.aspect !== 'auto' ? ' has-aspect' : ''),
855 style: a.aspect !== 'auto' ? { aspectRatio: a.aspect } : {}
856 },
857 renderInner()
858 )
859 );
860
861 return el(Fragment, {},
862 toolbar,
863 inspector,
864 content
865 );
866 },
867
868 save: function (props) {
869 var a = props.attributes;
870
871 var cls = 'bkbg-ba-wrap';
872 cls += a.orientation === 'vertical' ? ' is-vertical' : ' is-horizontal';
873 if (a.shadow) cls += ' has-shadow';
874 if (a.handleHoverAnim) {
875 cls += ' has-handle-anim';
876 cls += a.handleHoverAnimType ? (' is-handle-anim-' + a.handleHoverAnimType) : ' is-handle-anim-pulse';
877 }
878 if (a.handleHoverScale) {
879 cls += ' has-handle-hover';
880 }
881
882 var _tv = getTypoCssVars();
883 var styleVars = {
884 '--bkbg-ba-pos': (a.startPosition || 50) + '%',
885 '--bkbg-ba-radius': (a.borderRadius || 0) + 'px',
886 '--bkbg-ba-handle-size': (a.handleSize || 44) + 'px',
887 '--bkbg-ba-handle-icon-size': (a.handleIconSize || 18) + 'px',
888 '--bkbg-ba-handle-anim-dur': (a.handleHoverAnimDuration || 650) + 'ms',
889 '--bkbg-ba-handle-hover-scale': (a.handleHoverScaleAmount || 1.08),
890 '--bkbg-ba-handle-bg': a.handleBg,
891 '--bkbg-ba-handle-color': a.handleColor,
892 '--bkbg-ba-line-color': a.lineColor,
893 '--bkbg-ba-line-width': (a.lineWidth || 2) + 'px',
894 '--bkbg-ba-label-bg': a.labelBg,
895 '--bkbg-ba-label-color': a.labelColor,
896 '--bkbg-ba-label-radius': (a.labelRadius || 6) + 'px',
897 '--bkbg-ba-min-height': (a.minHeight || 260) + 'px'
898 };
899 Object.assign(styleVars, _tv(a.labelTypo || {}, '--bkbg-ba-label-'));
900
901 function renderSavedHandleIcon() {
902 var icon = parseIcon(a.handleIcon);
903 if (!icon || icon.type === 'none') return null;
904 if (icon.type === 'custom') {
905 return el('img', { className: 'bkbg-ba-handle-img', src: icon.value, alt: '' });
906 }
907 return el('span', { className: 'dashicons dashicons-' + icon.value + ' bkbg-ba-handle-icon' });
908 }
909
910 function renderSavedHandleIconWrapped() {
911 return el('span', { className: 'bkbg-ba-handle-surface' },
912 el('span', { className: 'bkbg-ba-handle-anim' },
913 el('span', { className: 'bkbg-ba-handle-hover' },
914 renderSavedHandleIcon()
915 )
916 )
917 );
918 }
919
920 var aspectStyle = (a.aspect && a.aspect !== 'auto') ? { aspectRatio: a.aspect } : {};
921
922 var blockProps = useBlockProps.save({
923 className: cls,
924 style: styleVars,
925 'data-pos': String(a.startPosition || 50),
926 'data-orientation': a.orientation || 'horizontal',
927 'data-show-labels': a.showLabels ? '1' : '0',
928 'data-label-position': a.labelPosition || 'top'
929 });
930
931 return el('div', blockProps,
932 el('div', Object.assign({ className: 'bkbg-ba-aspect' + (a.aspect !== 'auto' ? ' has-aspect' : '') }, aspectStyle),
933 el('div', { className: 'bkbg-ba-inner' },
934 el('div', { className: 'bkbg-ba-layer bkbg-ba-before' },
935 a.beforeImage && a.beforeImage.url
936 ? el('img', { src: a.beforeImage.url, alt: (a.beforeImage.alt || '') })
937 : null
938 ),
939 el('div', { className: 'bkbg-ba-layer bkbg-ba-after' },
940 a.afterImage && a.afterImage.url
941 ? el('img', { src: a.afterImage.url, alt: (a.afterImage.alt || '') })
942 : null
943 ),
944 el('div', { className: 'bkbg-ba-divider' },
945 el('div', { className: 'bkbg-ba-handle' + (a.handleStyle === 'square' ? ' is-square' : ' is-circle') },
946 renderSavedHandleIconWrapped()
947 )
948 ),
949 a.showLabels && el('div', { className: 'bkbg-ba-labels is-' + (a.labelPosition || 'top') },
950 el('div', { className: 'bkbg-ba-label bkbg-ba-label-before' }, a.beforeLabel || __('Before', 'blockenberg')),
951 el('div', { className: 'bkbg-ba-label bkbg-ba-label-after' }, a.afterLabel || __('After', 'blockenberg'))
952 ),
953 el('input', {
954 className: 'bkbg-ba-range',
955 type: 'range',
956 min: 0,
957 max: 100,
958 value: a.startPosition || 50,
959 'aria-label': __('Before/After position', 'blockenberg')
960 })
961 )
962 )
963 );
964 }
965 });
966 }() );
967