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

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.6, at blocks/logo-carousel/index.js

877 lines 42.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 wp.domReady(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 useRef = wp.element.useRef;
7 var useEffect = wp.element.useEffect;
8
9 var registerBlockType = wp.blocks.registerBlockType;
10
11 var InspectorControls = wp.blockEditor.InspectorControls;
12 var BlockControls = wp.blockEditor.BlockControls;
13 var useBlockProps = wp.blockEditor.useBlockProps;
14
15 var PanelBody = wp.components.PanelBody;
16 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
17 var ToggleControl = wp.components.ToggleControl;
18 var RangeControl = wp.components.RangeControl;
19 var SelectControl = wp.components.SelectControl;
20 var TextControl = wp.components.TextControl;
21 var Button = wp.components.Button;
22 var ToolbarGroup = wp.components.ToolbarGroup;
23 var ToolbarButton = wp.components.ToolbarButton;
24
25 function clone(obj) {
26 var out = {};
27 for (var k in obj) out[k] = obj[k];
28 return out;
29 }
30
31 function openMedia(onSelect) {
32 if (!wp || !wp.media) return;
33 var frame = wp.media({
34 title: __('Select Logo', 'blockenberg'),
35 button: { text: __('Use logo', 'blockenberg') },
36 multiple: false
37 });
38
39 frame.on('select', function () {
40 var attachment = frame.state().get('selection').first().toJSON();
41 onSelect(attachment);
42 });
43
44 frame.open();
45 }
46
47 registerBlockType('blockenberg/logo-carousel', {
48 title: __('Logo Carousel', 'blockenberg'),
49 icon: 'images-alt2',
50 category: 'blockenberg',
51 description: __('Show client/partner logos in a responsive carousel or grid.', 'blockenberg'),
52
53 edit: function (props) {
54 var a = props.attributes;
55 var setAttributes = props.setAttributes;
56 var isSelected = props.isSelected;
57
58 var activeIndexState = useState(0);
59 var activeIndex = activeIndexState[0];
60 var setActiveIndex = activeIndexState[1];
61
62 var trackRef = useRef(null);
63 var activeSlideState = useState(0);
64 var activeSlide = activeSlideState[0];
65 var setActiveSlide = activeSlideState[1];
66
67 var progressState = useState(0);
68 var progressPct = progressState[0];
69 var setProgressPct = progressState[1];
70
71 function updateLogo(index, patch) {
72 var next = (a.logos || []).map(function (it, i) {
73 if (i !== index) return it;
74 var updated = clone(it || {});
75 for (var k in patch) updated[k] = patch[k];
76 return updated;
77 });
78 setAttributes({ logos: next });
79 }
80
81 function updateLogoImage(index, image) {
82 updateLogo(index, { image: image });
83 }
84
85 function addLogo() {
86 var next = (a.logos || []).slice();
87 next.push({ image: { id: 0, url: '', alt: '' }, link: '', newTab: true });
88 setAttributes({ logos: next });
89 setActiveIndex(next.length - 1);
90 }
91
92 function removeLogo(index) {
93 var arr = (a.logos || []).slice();
94 if (arr.length <= 1) return;
95 arr.splice(index, 1);
96 setAttributes({ logos: arr });
97 setActiveIndex(Math.max(0, Math.min(arr.length - 1, index - 1)));
98 }
99
100 function duplicateLogo(index) {
101 var arr = (a.logos || []).slice();
102 var it = clone(arr[index] || {});
103 arr.splice(index + 1, 0, it);
104 setAttributes({ logos: arr });
105 setActiveIndex(index + 1);
106 }
107
108 function moveLogo(index, dir) {
109 var arr = (a.logos || []).slice();
110 var to = index + dir;
111 if (to < 0 || to >= arr.length) return;
112 var tmp = arr[index];
113 arr[index] = arr[to];
114 arr[to] = tmp;
115 setAttributes({ logos: arr });
116 setActiveIndex(to);
117 }
118
119 function getSlides() {
120 if (!trackRef.current) return [];
121 return trackRef.current.querySelectorAll('.bkbg-lc-slide');
122 }
123
124 function scrollToIndex(i) {
125 if (!trackRef.current) return;
126 var slides = getSlides();
127 if (!slides.length) return;
128 var idx = Math.max(0, Math.min(slides.length - 1, i));
129 slides[idx].scrollIntoView({ behavior: 'smooth', inline: 'start', block: 'nearest' });
130 }
131
132 function scrollByOne(dir) {
133 if (!trackRef.current) return;
134 var slides = getSlides();
135 if (!slides.length) return;
136
137 var trackRect = trackRef.current.getBoundingClientRect();
138 var currentIndex = 0;
139 for (var i = 0; i < slides.length; i++) {
140 var r = slides[i].getBoundingClientRect();
141 if (r.left >= trackRect.left - 10) {
142 currentIndex = i;
143 break;
144 }
145 }
146
147 scrollToIndex(currentIndex + dir);
148 }
149
150 useEffect(function () {
151 if (!trackRef.current) return;
152 var track = trackRef.current;
153
154 function getScrollPct() {
155 var max = track.scrollWidth - track.clientWidth;
156 if (max <= 0) return 0;
157 var pct = (track.scrollLeft / max) * 100;
158 if (pct < 0) pct = 0;
159 if (pct > 100) pct = 100;
160 return pct;
161 }
162
163 function updateFromScroll() {
164 var slides = getSlides();
165 if (!slides.length) return;
166 var trackRect = track.getBoundingClientRect();
167 var idx = 0;
168 for (var i = 0; i < slides.length; i++) {
169 var r = slides[i].getBoundingClientRect();
170 if (r.left >= trackRect.left - 10) {
171 idx = i;
172 break;
173 }
174 }
175 setActiveSlide(idx);
176 setProgressPct(getScrollPct());
177 }
178
179 updateFromScroll();
180 track.addEventListener('scroll', updateFromScroll, { passive: true });
181 return function () {
182 track.removeEventListener('scroll', updateFromScroll);
183 };
184 }, [a.layoutStyle, (a.logos || []).length]);
185
186 function renderIndicators() {
187 if (a.carouselIndicators === 'none') return null;
188
189 if (a.carouselIndicators === 'progress') {
190 return el('div', { className: 'bkbg-lc-indicators' },
191 el('div', { className: 'bkbg-lc-progress' },
192 el('div', { className: 'bkbg-lc-progress-bar', style: { width: progressPct + '%' } })
193 )
194 );
195 }
196
197 if (a.carouselIndicators === 'fraction') {
198 var total = Math.max(1, (a.logos || []).length);
199 return el('div', { className: 'bkbg-lc-indicators' },
200 el('div', { className: 'bkbg-lc-fraction', 'aria-label': __('Carousel position', 'blockenberg') },
201 el('span', { className: 'bkbg-lc-fraction-current' }, String(activeSlide + 1)),
202 el('span', { 'aria-hidden': 'true' }, '/'),
203 el('span', { className: 'bkbg-lc-fraction-total' }, String(total))
204 )
205 );
206 }
207
208 // dots
209 return el('div', { className: 'bkbg-lc-indicators' },
210 el('div', { className: 'bkbg-lc-dots', role: 'tablist', 'aria-label': __('Logo Carousel', 'blockenberg') },
211 (a.logos || []).map(function (_, i) {
212 return el('button', {
213 key: i,
214 type: 'button',
215 className: 'bkbg-lc-dot' + (i === activeSlide ? ' is-active' : ''),
216 'aria-label': __('Go to slide', 'blockenberg') + ' ' + (i + 1),
217 onClick: function (e) { e.preventDefault(); e.stopPropagation(); scrollToIndex(i); }
218 });
219 })
220 )
221 );
222 }
223
224 function renderLogoItem(item, index) {
225 var img = item && item.image;
226 var hasImg = img && img.url;
227
228 var slideInnerClasses = ['bkbg-lc-slide-inner'];
229 if (index === activeIndex && isSelected) slideInnerClasses.push('is-active');
230
231 var inner = el('div', {
232 className: slideInnerClasses.join(' '),
233 onClick: function () { setActiveIndex(index); }
234 },
235 el('div', { className: 'bkbg-lc-card-actions' },
236 el(Button, {
237 icon: 'arrow-left-alt2',
238 label: __('Move Left', 'blockenberg'),
239 disabled: index === 0,
240 onClick: function (e) { e.preventDefault(); e.stopPropagation(); moveLogo(index, -1); }
241 }),
242 el(Button, {
243 icon: 'arrow-right-alt2',
244 label: __('Move Right', 'blockenberg'),
245 disabled: index === (a.logos || []).length - 1,
246 onClick: function (e) { e.preventDefault(); e.stopPropagation(); moveLogo(index, 1); }
247 }),
248 el(Button, {
249 icon: 'admin-page',
250 label: __('Duplicate', 'blockenberg'),
251 onClick: function (e) { e.preventDefault(); e.stopPropagation(); duplicateLogo(index); }
252 }),
253 el(Button, {
254 icon: 'trash',
255 label: __('Remove', 'blockenberg'),
256 isDestructive: true,
257 disabled: (a.logos || []).length <= 1,
258 onClick: function (e) { e.preventDefault(); e.stopPropagation(); removeLogo(index); }
259 })
260 ),
261
262 el('div', {
263 className: 'bkbg-lc-item',
264 onClick: function (e) {
265 e.preventDefault();
266 e.stopPropagation();
267 setActiveIndex(index);
268 openMedia(function (att) {
269 updateLogoImage(index, { id: att.id, url: att.url, alt: att.alt || '' });
270 });
271 },
272 title: hasImg ? __('Click to change logo', 'blockenberg') : __('Click to add logo', 'blockenberg')
273 },
274 hasImg
275 ? el('img', { src: img.url, alt: img.alt || '' })
276 : el('div', { className: 'bkbg-lc-placeholder' }, __('Add Logo', 'blockenberg'))
277 )
278 );
279
280 return el('div', { key: index, className: 'bkbg-lc-slide' }, inner);
281 }
282
283 function renderLogoGridCell(item, index) {
284 var img = item && item.image;
285 var hasImg = img && img.url;
286
287 var slideInnerClasses = ['bkbg-lc-slide-inner'];
288 if (index === activeIndex && isSelected) slideInnerClasses.push('is-active');
289
290 return el('div', {
291 key: index,
292 className: 'bkbg-lc-grid-cell',
293 onClick: function () { setActiveIndex(index); }
294 },
295 el('div', { className: 'bkbg-lc-card-actions' },
296 el(Button, {
297 icon: 'arrow-left-alt2',
298 label: __('Move Left', 'blockenberg'),
299 disabled: index === 0,
300 onClick: function (e) { e.preventDefault(); e.stopPropagation(); moveLogo(index, -1); }
301 }),
302 el(Button, {
303 icon: 'arrow-right-alt2',
304 label: __('Move Right', 'blockenberg'),
305 disabled: index === (a.logos || []).length - 1,
306 onClick: function (e) { e.preventDefault(); e.stopPropagation(); moveLogo(index, 1); }
307 }),
308 el(Button, {
309 icon: 'admin-page',
310 label: __('Duplicate', 'blockenberg'),
311 onClick: function (e) { e.preventDefault(); e.stopPropagation(); duplicateLogo(index); }
312 }),
313 el(Button, {
314 icon: 'trash',
315 label: __('Remove', 'blockenberg'),
316 isDestructive: true,
317 disabled: (a.logos || []).length <= 1,
318 onClick: function (e) { e.preventDefault(); e.stopPropagation(); removeLogo(index); }
319 })
320 ),
321 el('div', {
322 className: slideInnerClasses.join(' ')
323 },
324 el('div', {
325 className: 'bkbg-lc-item',
326 onClick: function (e) {
327 e.preventDefault();
328 e.stopPropagation();
329 setActiveIndex(index);
330 openMedia(function (att) {
331 updateLogoImage(index, { id: att.id, url: att.url, alt: att.alt || '' });
332 });
333 },
334 title: hasImg ? __('Click to change logo', 'blockenberg') : __('Click to add logo', 'blockenberg')
335 },
336 hasImg
337 ? el('img', { src: img.url, alt: img.alt || '' })
338 : el('div', { className: 'bkbg-lc-placeholder' }, __('Add Logo', 'blockenberg'))
339 )
340 )
341 );
342 }
343
344 var styleVars = {
345 '--bkbg-lc-columns': String(a.columns),
346 '--bkbg-lc-gap': a.gap + 'px',
347 '--bkbg-lc-carousel-visible': String(a.carouselVisible || 4),
348 '--bkbg-lc-peek': a.carouselPeekAmount || '20%',
349
350 '--bkbg-lc-logo-max-h': ((a.logoMaxHeight != null ? a.logoMaxHeight : 150)) + 'px',
351 '--bkbg-lc-logo-padding': (a.logoPadding || 14) + 'px',
352 '--bkbg-lc-logo-radius': (a.logoRadius || 12) + 'px',
353 '--bkbg-lc-logo-bg': a.logoBg,
354 '--bkbg-lc-logo-border-color': a.logoBorderColor,
355 '--bkbg-lc-logo-border-width': (a.logoBorderWidth || 0) + 'px',
356
357 '--bkbg-lc-logo-opacity': String(a.logoOpacity != null ? a.logoOpacity : 0.8),
358 '--bkbg-lc-logo-hover-opacity': String(a.logoHoverOpacity != null ? a.logoHoverOpacity : 1),
359 '--bkbg-lc-logo-filter': a.logoGrayscale ? 'grayscale(1)' : 'grayscale(0)',
360 '--bkbg-lc-logo-hover-filter': a.logoHoverGrayscale ? 'grayscale(1)' : 'grayscale(0)',
361 '--bkbg-lc-logo-fit': a.logoFit || 'contain',
362 '--bkbg-lc-logo-hover-scale': String(a.logoHoverScale || 1.03),
363
364 '--bkbg-lc-indicator-size': (a.carouselIndicatorSize || 8) + 'px',
365 '--bkbg-lc-indicator-color': a.carouselIndicatorColor,
366 '--bkbg-lc-indicator-active-color': a.carouselIndicatorActiveColor,
367 '--bkbg-lc-progress-height': (a.carouselProgressHeight || 6) + 'px',
368 '--bkbg-lc-progress-bg': a.carouselProgressBg,
369 '--bkbg-lc-progress-fg': a.carouselProgressFg,
370 '--bkbg-lc-fraction-color': a.carouselFractionColor,
371 '--bkbg-lc-fraction-size': (a.carouselFractionSize || 13) + 'px'
372 };
373
374 var classes = ['bkbg-lc-wrap'];
375 if (a.layoutStyle === 'carousel') {
376 classes.push('bkbg-lc-carousel');
377 if (a.carouselPeek) classes.push('bkbg-lc-carousel-peek');
378 classes.push('bkbg-lc-nav-' + (a.carouselNavStyle || 'overlay'));
379 classes.push('bkbg-lc-indicators-' + (a.carouselIndicators || 'none'));
380 classes.push('bkbg-lc-indicators-pos-' + (a.carouselIndicatorsPosition || 'below'));
381
382 if (a.carouselIndicators === 'dots') {
383 classes.push('bkbg-lc-dots-style-' + (a.carouselDotsStyle || 'default'));
384 }
385 if (a.carouselIndicators === 'progress') {
386 classes.push('bkbg-lc-progress-style-' + (a.carouselProgressStyle || 'default'));
387 }
388 }
389
390 var blockProps = useBlockProps({
391 className: 'bkbg-editor-wrap ' + classes.join(' '),
392 style: styleVars,
393 'data-block-label': 'Logo Carousel'
394 });
395
396 var blockControls = el(BlockControls, {},
397 el(ToolbarGroup, {},
398 el(ToolbarButton, {
399 icon: 'plus',
400 label: __('Add Logo', 'blockenberg'),
401 onClick: addLogo
402 })
403 )
404 );
405
406 var selected = (a.logos || [])[activeIndex] || { image: { id: 0, url: '', alt: '' }, link: '', newTab: true };
407
408 var inspector = el(InspectorControls, {},
409 el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: true },
410 el(SelectControl, {
411 label: __('Display', 'blockenberg'),
412 value: a.layoutStyle || 'carousel',
413 options: [
414 { label: __('Carousel', 'blockenberg'), value: 'carousel' },
415 { label: __('Grid', 'blockenberg'), value: 'grid' }
416 ],
417 onChange: function (v) { setAttributes({ layoutStyle: v }); }
418 }),
419 (a.layoutStyle === 'grid') && el(RangeControl, {
420 label: __('Columns', 'blockenberg'),
421 value: a.columns || 5,
422 min: 2,
423 max: 8,
424 onChange: function (v) { setAttributes({ columns: v }); }
425 }),
426 el(RangeControl, {
427 label: __('Gap', 'blockenberg'),
428 value: a.gap || 24,
429 min: 0,
430 max: 64,
431 onChange: function (v) { setAttributes({ gap: v }); }
432 })
433 ),
434
435 (a.layoutStyle === 'carousel') && el(PanelBody, { title: __('Carousel', 'blockenberg'), initialOpen: false },
436 el(RangeControl, {
437 label: __('Visible logos', 'blockenberg'),
438 value: a.carouselVisible || 4,
439 min: 1,
440 max: 8,
441 onChange: function (v) { setAttributes({ carouselVisible: v }); }
442 }),
443 el(ToggleControl, {
444 label: __('Peek next logos', 'blockenberg'),
445 checked: !!a.carouselPeek,
446 onChange: function (v) { setAttributes({ carouselPeek: !!v }); }
447 }),
448 a.carouselPeek && el(TextControl, {
449 label: __('Peek amount', 'blockenberg'),
450 help: __('CSS length, e.g. 20% or 120px', 'blockenberg'),
451 value: a.carouselPeekAmount || '20%',
452 onChange: function (v) { setAttributes({ carouselPeekAmount: v }); }
453 }),
454 el(ToggleControl, {
455 label: __('Show arrows', 'blockenberg'),
456 checked: !!a.carouselNav,
457 onChange: function (v) { setAttributes({ carouselNav: !!v }); }
458 }),
459 a.carouselNav && el(SelectControl, {
460 label: __('Arrow style', 'blockenberg'),
461 value: a.carouselNavStyle || 'overlay',
462 options: [
463 { label: __('Top Right', 'blockenberg'), value: 'top' },
464 { label: __('Bottom Center', 'blockenberg'), value: 'bottom' },
465 { label: __('Overlay Sides', 'blockenberg'), value: 'overlay' }
466 ],
467 onChange: function (v) { setAttributes({ carouselNavStyle: v }); }
468 }),
469 el(SelectControl, {
470 label: __('Indicators', 'blockenberg'),
471 value: a.carouselIndicators || 'none',
472 options: [
473 { label: __('None', 'blockenberg'), value: 'none' },
474 { label: __('Dots', 'blockenberg'), value: 'dots' },
475 { label: __('Progress', 'blockenberg'), value: 'progress' },
476 { label: __('Fraction', 'blockenberg'), value: 'fraction' }
477 ],
478 onChange: function (v) { setAttributes({ carouselIndicators: v }); }
479 }),
480 (a.carouselIndicators !== 'none') && el(SelectControl, {
481 label: __('Indicators position', 'blockenberg'),
482 value: a.carouselIndicatorsPosition || 'below',
483 options: [
484 { label: __('Below', 'blockenberg'), value: 'below' },
485 { label: __('Top', 'blockenberg'), value: 'top' }
486 ],
487 onChange: function (v) { setAttributes({ carouselIndicatorsPosition: v }); }
488 }),
489 (a.carouselIndicators === 'dots') && el(SelectControl, {
490 label: __('Dots style', 'blockenberg'),
491 value: a.carouselDotsStyle || 'default',
492 options: [
493 { label: __('Default', 'blockenberg'), value: 'default' },
494 { label: __('Active pill', 'blockenberg'), value: 'pill' }
495 ],
496 onChange: function (v) { setAttributes({ carouselDotsStyle: v }); }
497 }),
498 (a.carouselIndicators === 'progress') && el(SelectControl, {
499 label: __('Progress style', 'blockenberg'),
500 value: a.carouselProgressStyle || 'default',
501 options: [
502 { label: __('Default', 'blockenberg'), value: 'default' },
503 { label: __('Edge to edge', 'blockenberg'), value: 'edge' }
504 ],
505 onChange: function (v) { setAttributes({ carouselProgressStyle: v }); }
506 }),
507 el(ToggleControl, {
508 label: __('Infinite Loop', 'blockenberg'),
509 checked: a.infiniteScroll !== false,
510 onChange: function (v) { setAttributes({ infiniteScroll: !!v }); }
511 }),
512 el(ToggleControl, {
513 label: __('Auto-scroll (continuous)', 'blockenberg'),
514 help: __('Runs on the live site (frontend). Please check it there.', 'blockenberg'),
515 checked: !!a.autoScroll,
516 onChange: function (v) { setAttributes({ autoScroll: !!v }); }
517 }),
518 a.autoScroll && el(RangeControl, {
519 label: __('Auto-scroll speed (px/sec)', 'blockenberg'),
520 value: a.autoScrollSpeed || 40,
521 min: 5,
522 max: 200,
523 step: 1,
524 onChange: function (v) { setAttributes({ autoScrollSpeed: v }); }
525 }),
526 a.autoScroll && el(ToggleControl, {
527 label: __('Pause on hover', 'blockenberg'),
528 checked: a.autoScrollPauseOnHover !== false,
529 onChange: function (v) { setAttributes({ autoScrollPauseOnHover: !!v }); }
530 }),
531 el(ToggleControl, {
532 label: __('Autoplay', 'blockenberg'),
533 checked: !!a.autoplay,
534 onChange: function (v) { setAttributes({ autoplay: !!v }); }
535 }),
536 a.autoplay && el(RangeControl, {
537 label: __('Autoplay delay (ms)', 'blockenberg'),
538 value: a.autoplayDelay || 3000,
539 min: 800,
540 max: 10000,
541 step: 100,
542 onChange: function (v) { setAttributes({ autoplayDelay: v }); }
543 }),
544 a.autoplay && el(ToggleControl, {
545 label: __('Pause on hover', 'blockenberg'),
546 checked: a.autoplayPauseOnHover !== false,
547 onChange: function (v) { setAttributes({ autoplayPauseOnHover: !!v }); }
548 })
549 ),
550
551 el(PanelBody, { title: __('Logos', 'blockenberg'), initialOpen: false },
552 el(TextControl, {
553 label: __('Max logo height (px)', 'blockenberg'),
554 type: 'number',
555 value: (a.logoMaxHeight != null ? String(a.logoMaxHeight) : '150'),
556 min: 0,
557 onChange: function (v) {
558 var n = parseInt(v, 10);
559 if (isNaN(n)) n = 0;
560 setAttributes({ logoMaxHeight: n });
561 }
562 }),
563 el(RangeControl, {
564 label: __('Padding', 'blockenberg'),
565 value: a.logoPadding || 14,
566 min: 0,
567 max: 40,
568 onChange: function (v) { setAttributes({ logoPadding: v }); }
569 }),
570 el(RangeControl, {
571 label: __('Radius', 'blockenberg'),
572 value: a.logoRadius || 12,
573 min: 0,
574 max: 40,
575 onChange: function (v) { setAttributes({ logoRadius: v }); }
576 }),
577 el(SelectControl, {
578 label: __('Image fit', 'blockenberg'),
579 value: a.logoFit || 'contain',
580 options: [
581 { label: __('Contain', 'blockenberg'), value: 'contain' },
582 { label: __('Cover', 'blockenberg'), value: 'cover' }
583 ],
584 onChange: function (v) { setAttributes({ logoFit: v }); }
585 }),
586 el(RangeControl, {
587 label: __('Opacity', 'blockenberg'),
588 value: a.logoOpacity != null ? a.logoOpacity : 0.8,
589 min: 0.1,
590 max: 1,
591 step: 0.05,
592 onChange: function (v) { setAttributes({ logoOpacity: v }); }
593 }),
594 el(RangeControl, {
595 label: __('Hover opacity', 'blockenberg'),
596 value: a.logoHoverOpacity != null ? a.logoHoverOpacity : 1,
597 min: 0.1,
598 max: 1,
599 step: 0.05,
600 onChange: function (v) { setAttributes({ logoHoverOpacity: v }); }
601 }),
602 el(ToggleControl, {
603 label: __('Grayscale', 'blockenberg'),
604 checked: !!a.logoGrayscale,
605 onChange: function (v) { setAttributes({ logoGrayscale: !!v }); }
606 }),
607 el(ToggleControl, {
608 label: __('Grayscale on hover', 'blockenberg'),
609 checked: !!a.logoHoverGrayscale,
610 onChange: function (v) { setAttributes({ logoHoverGrayscale: !!v }); }
611 }),
612 el(RangeControl, {
613 label: __('Hover scale', 'blockenberg'),
614 value: a.logoHoverScale || 1.03,
615 min: 1,
616 max: 1.2,
617 step: 0.01,
618 onChange: function (v) { setAttributes({ logoHoverScale: v }); }
619 })
620 ),
621
622 el(PanelBody, { title: __('Selected Logo', 'blockenberg'), initialOpen: false },
623 el(TextControl, {
624 label: __('Link URL', 'blockenberg'),
625 value: selected.link || '',
626 placeholder: 'https://',
627 onChange: function (v) { updateLogo(activeIndex, { link: v }); }
628 }),
629 el(ToggleControl, {
630 label: __('Open in new tab', 'blockenberg'),
631 checked: selected.newTab !== false,
632 onChange: function (v) { updateLogo(activeIndex, { newTab: !!v }); }
633 })
634 ),
635
636 el(PanelColorSettings, {
637 title: __('Colors', 'blockenberg'),
638 colorSettings: [
639 {
640 label: __('Item background', 'blockenberg'),
641 value: a.logoBg,
642 onChange: function (v) { setAttributes({ logoBg: v }); }
643 },
644 {
645 label: __('Item border', 'blockenberg'),
646 value: a.logoBorderColor,
647 onChange: function (v) { setAttributes({ logoBorderColor: v }); }
648 },
649 {
650 label: __('Indicator', 'blockenberg'),
651 value: a.carouselIndicatorColor,
652 onChange: function (v) { setAttributes({ carouselIndicatorColor: v }); }
653 },
654 {
655 label: __('Indicator active', 'blockenberg'),
656 value: a.carouselIndicatorActiveColor,
657 onChange: function (v) { setAttributes({ carouselIndicatorActiveColor: v }); }
658 },
659 {
660 label: __('Progress background', 'blockenberg'),
661 value: a.carouselProgressBg,
662 onChange: function (v) { setAttributes({ carouselProgressBg: v }); }
663 },
664 {
665 label: __('Progress foreground', 'blockenberg'),
666 value: a.carouselProgressFg,
667 onChange: function (v) { setAttributes({ carouselProgressFg: v }); }
668 },
669 {
670 label: __('Fraction color', 'blockenberg'),
671 value: a.carouselFractionColor,
672 onChange: function (v) { setAttributes({ carouselFractionColor: v }); }
673 }
674 ]
675 })
676 );
677
678 var content;
679 if (a.layoutStyle === 'grid') {
680 content = el('div', { className: 'bkbg-lc-grid' },
681 (a.logos || []).map(function (it, idx) { return renderLogoGridCell(it, idx); })
682 );
683 } else {
684 var indicators = renderIndicators();
685 var nav = a.carouselNav && el('div', { className: 'bkbg-lc-nav' },
686 el('button', {
687 type: 'button',
688 className: 'bkbg-lc-nav-btn',
689 'data-bkbg-nav': 'prev',
690 'aria-label': __('Previous', 'blockenberg'),
691 onClick: function (e) { e.preventDefault(); e.stopPropagation(); scrollByOne(-1); }
692 }, el('span', { className: 'dashicons dashicons-arrow-left-alt2', 'aria-hidden': 'true' })),
693 el('button', {
694 type: 'button',
695 className: 'bkbg-lc-nav-btn',
696 'data-bkbg-nav': 'next',
697 'aria-label': __('Next', 'blockenberg'),
698 onClick: function (e) { e.preventDefault(); e.stopPropagation(); scrollByOne(1); }
699 }, el('span', { className: 'dashicons dashicons-arrow-right-alt2', 'aria-hidden': 'true' }))
700 );
701
702 content = el('div', { className: 'bkbg-lc-carousel' },
703 (a.carouselIndicatorsPosition === 'top') && indicators,
704 el('div', { className: 'bkbg-lc-carousel-inner' },
705 nav,
706 el('div', { className: 'bkbg-lc-track', ref: trackRef },
707 (a.logos || []).map(function (it, idx) { return renderLogoItem(it, idx); })
708 )
709 ),
710 (a.carouselIndicatorsPosition !== 'top') && indicators
711 );
712 }
713
714 return el(Fragment, {},
715 inspector,
716 blockControls,
717 el('div', blockProps,
718 content,
719 el('div', { className: 'bkbg-editor-actions' },
720 el(Button, { variant: 'secondary', icon: 'plus-alt2', onClick: addLogo }, __('Add Logo', 'blockenberg'))
721 )
722 )
723 );
724 },
725
726 save: function (props) {
727 var a = props.attributes;
728
729 var styleVars = {
730 '--bkbg-lc-columns': String(a.columns),
731 '--bkbg-lc-gap': a.gap + 'px',
732 '--bkbg-lc-carousel-visible': String(a.carouselVisible || 4),
733 '--bkbg-lc-peek': a.carouselPeekAmount || '20%',
734
735 '--bkbg-lc-logo-max-h': ((a.logoMaxHeight != null ? a.logoMaxHeight : 150)) + 'px',
736 '--bkbg-lc-logo-padding': (a.logoPadding || 14) + 'px',
737 '--bkbg-lc-logo-radius': (a.logoRadius || 12) + 'px',
738 '--bkbg-lc-logo-bg': a.logoBg,
739 '--bkbg-lc-logo-border-color': a.logoBorderColor,
740 '--bkbg-lc-logo-border-width': (a.logoBorderWidth || 0) + 'px',
741
742 '--bkbg-lc-logo-opacity': String(a.logoOpacity != null ? a.logoOpacity : 0.8),
743 '--bkbg-lc-logo-hover-opacity': String(a.logoHoverOpacity != null ? a.logoHoverOpacity : 1),
744 '--bkbg-lc-logo-filter': a.logoGrayscale ? 'grayscale(1)' : 'grayscale(0)',
745 '--bkbg-lc-logo-hover-filter': a.logoHoverGrayscale ? 'grayscale(1)' : 'grayscale(0)',
746 '--bkbg-lc-logo-fit': a.logoFit || 'contain',
747 '--bkbg-lc-logo-hover-scale': String(a.logoHoverScale || 1.03),
748
749 '--bkbg-lc-indicator-size': (a.carouselIndicatorSize || 8) + 'px',
750 '--bkbg-lc-indicator-color': a.carouselIndicatorColor,
751 '--bkbg-lc-indicator-active-color': a.carouselIndicatorActiveColor,
752 '--bkbg-lc-progress-height': (a.carouselProgressHeight || 6) + 'px',
753 '--bkbg-lc-progress-bg': a.carouselProgressBg,
754 '--bkbg-lc-progress-fg': a.carouselProgressFg,
755 '--bkbg-lc-fraction-color': a.carouselFractionColor,
756 '--bkbg-lc-fraction-size': (a.carouselFractionSize || 13) + 'px'
757 };
758
759 var classes = ['bkbg-lc-wrap'];
760 if (a.layoutStyle === 'carousel') {
761 classes.push('bkbg-lc-carousel');
762 if (a.carouselPeek) classes.push('bkbg-lc-carousel-peek');
763 classes.push('bkbg-lc-nav-' + (a.carouselNavStyle || 'overlay'));
764 classes.push('bkbg-lc-indicators-' + (a.carouselIndicators || 'none'));
765 classes.push('bkbg-lc-indicators-pos-' + (a.carouselIndicatorsPosition || 'below'));
766
767 if (a.carouselIndicators === 'dots') {
768 classes.push('bkbg-lc-dots-style-' + (a.carouselDotsStyle || 'default'));
769 }
770 if (a.carouselIndicators === 'progress') {
771 classes.push('bkbg-lc-progress-style-' + (a.carouselProgressStyle || 'default'));
772 }
773 }
774
775 var blockProps = useBlockProps.save({
776 className: classes.join(' '),
777 style: styleVars,
778 'data-bkbg-autoplay': a.autoplay ? '1' : '0',
779 'data-bkbg-autoplay-delay': String(a.autoplayDelay || 3000),
780 'data-bkbg-autoplay-hover': a.autoplayPauseOnHover === false ? '0' : '1',
781 'data-bkbg-infinite': a.infiniteScroll === false ? '0' : '1',
782 'data-bkbg-autoscroll': a.autoScroll ? '1' : '0',
783 'data-bkbg-autoscroll-speed': String(a.autoScrollSpeed || 40),
784 'data-bkbg-autoscroll-hover': a.autoScrollPauseOnHover === false ? '0' : '1'
785 });
786
787 function renderIndicators() {
788 if (a.carouselIndicators === 'none') return null;
789
790 if (a.carouselIndicators === 'progress') {
791 return el('div', { className: 'bkbg-lc-indicators' },
792 el('div', { className: 'bkbg-lc-progress' },
793 el('div', { className: 'bkbg-lc-progress-bar', style: { width: '0%' } })
794 )
795 );
796 }
797
798 if (a.carouselIndicators === 'fraction') {
799 var total = Math.max(1, (a.logos || []).length);
800 return el('div', { className: 'bkbg-lc-indicators' },
801 el('div', { className: 'bkbg-lc-fraction', 'aria-label': __('Carousel position', 'blockenberg') },
802 el('span', { className: 'bkbg-lc-fraction-current', 'data-bkbg-fraction': 'current' }, '1'),
803 el('span', { 'aria-hidden': 'true' }, '/'),
804 el('span', { className: 'bkbg-lc-fraction-total', 'data-bkbg-fraction': 'total' }, String(total))
805 )
806 );
807 }
808
809 return el('div', { className: 'bkbg-lc-indicators' },
810 el('div', { className: 'bkbg-lc-dots', role: 'tablist', 'aria-label': __('Logo Carousel', 'blockenberg') },
811 (a.logos || []).map(function (_, i) {
812 return el('button', {
813 key: i,
814 type: 'button',
815 className: 'bkbg-lc-dot' + (i === 0 ? ' is-active' : ''),
816 'data-bkbg-dot': String(i),
817 'aria-label': __('Go to slide', 'blockenberg') + ' ' + (i + 1)
818 });
819 })
820 )
821 );
822 }
823
824 function renderItem(it, idx) {
825 var img = it && it.image;
826 var hasImg = img && img.url;
827 var href = (it && it.link) ? String(it.link) : '';
828 var newTab = !(it && it.newTab === false);
829
830 var inner = hasImg
831 ? el('img', { src: img.url, alt: img.alt || '' })
832 : el('div', { className: 'bkbg-lc-placeholder', 'aria-hidden': 'true' }, '');
833
834 if (href) {
835 return el('a', {
836 key: idx,
837 className: 'bkbg-lc-item',
838 href: href,
839 target: newTab ? '_blank' : undefined,
840 rel: newTab ? 'noopener noreferrer' : undefined
841 }, inner);
842 }
843
844 return el('div', { key: idx, className: 'bkbg-lc-item' }, inner);
845 }
846
847 if (a.layoutStyle === 'grid') {
848 return el('div', blockProps,
849 el('div', { className: 'bkbg-lc-grid' }, (a.logos || []).map(renderItem))
850 );
851 }
852
853 var indicators = renderIndicators();
854
855 return el('div', blockProps,
856 (a.carouselIndicatorsPosition === 'top') && indicators,
857 el('div', { className: 'bkbg-lc-carousel-inner' },
858 a.carouselNav && el('div', { className: 'bkbg-lc-nav' },
859 el('button', { type: 'button', className: 'bkbg-lc-nav-btn', 'data-bkbg-nav': 'prev', 'aria-label': __('Previous', 'blockenberg') },
860 el('span', { className: 'dashicons dashicons-arrow-left-alt2', 'aria-hidden': 'true' })
861 ),
862 el('button', { type: 'button', className: 'bkbg-lc-nav-btn', 'data-bkbg-nav': 'next', 'aria-label': __('Next', 'blockenberg') },
863 el('span', { className: 'dashicons dashicons-arrow-right-alt2', 'aria-hidden': 'true' })
864 )
865 ),
866 el('div', { className: 'bkbg-lc-track' },
867 (a.logos || []).map(function (it, idx) {
868 return el('div', { key: idx, className: 'bkbg-lc-slide' }, renderItem(it, idx));
869 })
870 )
871 ),
872 (a.carouselIndicatorsPosition !== 'top') && indicators
873 );
874 }
875 });
876 });
877