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

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

517 lines 25.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 useEffect = wp.element.useEffect;
7
8 var registerBlockType = wp.blocks.registerBlockType;
9
10 var InspectorControls = wp.blockEditor.InspectorControls;
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 Popover = wp.components.Popover;
20 var ColorPicker = wp.components.ColorPicker;
21
22 var _tc, _tv;
23 function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
24 function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
25
26 function clampMin(n, min) {
27 n = parseFloat(n);
28 if (isNaN(n)) return min;
29 return Math.max(min, n);
30 }
31
32 function clone(obj) {
33 var out = {};
34 for (var k in obj) out[k] = obj[k];
35 return out;
36 }
37
38 function colorToHex(c) {
39 if (!c) return '';
40 if (c.hex) return c.hex;
41 if (c.rgb) {
42 var r = c.rgb;
43 var a = typeof r.a === 'number' ? r.a : 1;
44 if (a < 1) {
45 return 'rgba(' + r.r + ',' + r.g + ',' + r.b + ',' + a + ')';
46 }
47 return '#' + [r.r, r.g, r.b].map(function (x) {
48 var h = x.toString(16);
49 return h.length === 1 ? '0' + h : h;
50 }).join('');
51 }
52 return c;
53 }
54
55 var SEPARATOR_OPTIONS = [
56 { label: '', value: '' },
57 { label: '|', value: '|' },
58 { label: '', value: '' },
59 { label: '�
60 ', value: '�
61 ' },
62 { label: '', value: '' },
63 { label: '/', value: '/' },
64 { label: __('None', 'blockenberg'), value: '' }
65 ];
66
67 var TRANSFORM_OPTIONS = [
68 { label: __('None', 'blockenberg'), value: 'none' },
69 { label: __('Uppercase', 'blockenberg'), value: 'uppercase' },
70 { label: __('Lowercase', 'blockenberg'), value: 'lowercase' },
71 { label: __('Capitalize', 'blockenberg'), value: 'capitalize' }
72 ];
73
74 registerBlockType('blockenberg/marquee', {
75 title: __('Marquee / Announcement Bar', 'blockenberg'),
76 icon: 'megaphone',
77 category: 'bkbg-effects',
78 description: __('Scrolling announcement bar with pause on hover and accessibility support.', 'blockenberg'),
79
80 edit: function (props) {
81 var a = props.attributes;
82 var setAttributes = props.setAttributes;
83 var isSelected = props.isSelected;
84
85 useEffect(function () {
86 if (!a.instanceId) {
87 setAttributes({ instanceId: 'bkbg-mq-' + Math.random().toString(36).slice(2, 10) });
88 }
89 }, [a.instanceId]);
90
91 var colorPopoverState = useState(null);
92 var openColor = colorPopoverState[0];
93 var setOpenColor = colorPopoverState[1];
94
95 function updateItem(index, patch) {
96 var next = (a.items || []).map(function (it, i) {
97 if (i !== index) return it;
98 var updated = clone(it || {});
99 for (var k in patch) updated[k] = patch[k];
100 return updated;
101 });
102 setAttributes({ items: next });
103 }
104
105 function addItem() {
106 var next = (a.items || []).slice();
107 next.push({ text: __('New announcement', 'blockenberg'), link: '', newTab: false });
108 setAttributes({ items: next });
109 }
110
111 function removeItem(index) {
112 var arr = (a.items || []).slice();
113 if (arr.length <= 1) return;
114 arr.splice(index, 1);
115 setAttributes({ items: arr });
116 }
117
118 function moveItem(index, dir) {
119 var arr = (a.items || []).slice();
120 var to = index + dir;
121 if (to < 0 || to >= arr.length) return;
122 var tmp = arr[index];
123 arr[index] = arr[to];
124 arr[to] = tmp;
125 setAttributes({ items: arr });
126 }
127
128 var wrapperClass = 'bkbg-marquee-wrap bkbg-editor-wrap';
129 if (a.shadow) wrapperClass += ' has-shadow';
130 if (a.borderTop) wrapperClass += ' has-border-top';
131 if (a.borderBottom) wrapperClass += ' has-border-bottom';
132 if (a.reduceMotion) wrapperClass += ' respects-motion';
133
134 var blockProps = useBlockProps((function () {
135 var tv = getTypoCssVars();
136 var s = {
137 '--bkbg-mq-bg': a.backgroundColor,
138 '--bkbg-mq-color': a.textColor,
139 '--bkbg-mq-link-hover': a.linkHoverColor,
140 '--bkbg-mq-py': a.paddingY + 'px',
141 '--bkbg-mq-px': a.paddingX + 'px',
142 '--bkbg-mq-font-size': a.fontSize + 'px',
143 '--bkbg-mq-font-weight': a.fontWeight,
144 '--bkbg-mq-letter-spacing': a.letterSpacing + 'px',
145 '--bkbg-mq-speed': (a.durationSeconds && a.durationSeconds > 0
146 ? clampMin(a.durationSeconds, 5)
147 : clampMin((150 - (a.speed || 50)), 5)
148 ) + 's',
149 '--bkbg-mq-sep-spacing': a.separatorSpacing + 'px',
150 '--bkbg-mq-border-color': a.borderColor,
151 '--bkbg-mq-border-width': a.borderWidth + 'px',
152 '--bkbg-mq-close-color': a.closeButtonColor
153 };
154 Object.assign(s, tv(a.textTypo, '--bkbg-mq-tx-'));
155 return {
156 className: wrapperClass,
157 style: s,
158 'data-block-label': 'Marquee',
159 'data-instance-id': a.instanceId || undefined,
160 'data-direction': a.direction,
161 'data-pause-hover': a.pauseOnHover ? '1' : '0'
162 };
163 })());
164
165 function ColorButton(colorKey, label) {
166 return el('div', { className: 'bkbg-mq-color-row', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' } },
167 el('span', {}, label),
168 el('button', {
169 style: { width: 28, height: 28, borderRadius: '50%', border: '2px solid #fff', boxShadow: '0 0 0 1px rgba(0,0,0,0.15)', backgroundColor: a[colorKey], cursor: 'pointer' },
170 onClick: function () { setOpenColor(openColor === colorKey ? null : colorKey); }
171 }),
172 openColor === colorKey && el(Popover, { position: 'bottom left', onClose: function () { setOpenColor(null); } },
173 el(ColorPicker, {
174 color: a[colorKey],
175 onChangeComplete: function (c) {
176 var obj = {};
177 obj[colorKey] = colorToHex(c);
178 setAttributes(obj);
179 }
180 })
181 )
182 );
183 }
184
185 var inspector = el(InspectorControls, {},
186 el(PanelBody, { title: __('Announcements', 'blockenberg'), initialOpen: true },
187 el('div', { className: 'bkbg-marquee-editor-items' },
188 (a.items || []).map(function (item, index) {
189 return el('div', { key: index, className: 'bkbg-marquee-editor-item' },
190 el('div', { className: 'bkbg-marquee-editor-item-fields' },
191 el(TextControl, {
192 label: __('Text', 'blockenberg'),
193 value: item.text || '',
194 onChange: function (v) { updateItem(index, { text: v }); }
195 }),
196 el(TextControl, {
197 label: __('Link (optional)', 'blockenberg'),
198 value: item.link || '',
199 onChange: function (v) { updateItem(index, { link: v }); },
200 type: 'url'
201 }),
202 item.link && el(ToggleControl, {
203 label: __('Open in new tab', 'blockenberg'),
204 checked: !!item.newTab,
205 __nextHasNoMarginBottom: true,
206 onChange: function (v) { updateItem(index, { newTab: v }); }
207 })
208 ),
209 el('div', { className: 'bkbg-marquee-editor-item-actions' },
210 el(Button, { isSmall: true, icon: 'arrow-up-alt2', label: __('Move up', 'blockenberg'), disabled: index === 0, onClick: function () { moveItem(index, -1); } }),
211 el(Button, { isSmall: true, icon: 'arrow-down-alt2', label: __('Move down', 'blockenberg'), disabled: index === a.items.length - 1, onClick: function () { moveItem(index, 1); } }),
212 el(Button, { isSmall: true, icon: 'trash', label: __('Remove', 'blockenberg'), isDestructive: true, disabled: a.items.length <= 1, onClick: function () { removeItem(index); } })
213 )
214 );
215 })
216 ),
217 el(Button, { variant: 'secondary', onClick: addItem }, __('+ Add Announcement', 'blockenberg'))
218 ),
219
220 el(PanelBody, { title: __('Animation', 'blockenberg'), initialOpen: false },
221 el(SelectControl, {
222 label: __('Direction', 'blockenberg'),
223 value: a.direction,
224 options: [
225 { label: __('Left', 'blockenberg'), value: 'left' },
226 { label: __('Right', 'blockenberg'), value: 'right' }
227 ],
228 onChange: function (v) { setAttributes({ direction: v }); }
229 }),
230 el(TextControl, {
231 label: __('Duration (seconds per loop)', 'blockenberg'),
232 type: 'number',
233 value: (typeof a.durationSeconds === 'number' && a.durationSeconds > 0)
234 ? a.durationSeconds
235 : clampMin((150 - (a.speed || 50)), 5),
236 min: 5,
237 step: 0.5,
238 onChange: function (v) { setAttributes({ durationSeconds: clampMin(v, 5) }); },
239 help: __('Lower = faster. Minimum 5s.', 'blockenberg')
240 }),
241 el(ToggleControl, {
242 label: __('Pause on hover', 'blockenberg'),
243 checked: !!a.pauseOnHover,
244 __nextHasNoMarginBottom: true,
245 onChange: function (v) { setAttributes({ pauseOnHover: v }); }
246 }),
247 el(ToggleControl, {
248 label: __('Pause on click', 'blockenberg'),
249 checked: !!a.pauseOnClick,
250 __nextHasNoMarginBottom: true,
251 onChange: function (v) { setAttributes({ pauseOnClick: v }); }
252 }),
253 el(ToggleControl, {
254 label: __('Respect reduced motion preference', 'blockenberg'),
255 checked: !!a.reduceMotion,
256 __nextHasNoMarginBottom: true,
257 onChange: function (v) { setAttributes({ reduceMotion: v }); },
258 help: __('Stops animation when user has enabled reduced motion in system settings.', 'blockenberg')
259 })
260 ),
261
262 el(PanelBody, { title: __('Separator', 'blockenberg'), initialOpen: false },
263 el(ToggleControl, {
264 label: __('Show separator', 'blockenberg'),
265 checked: !!a.showSeparator,
266 __nextHasNoMarginBottom: true,
267 onChange: function (v) { setAttributes({ showSeparator: v }); }
268 }),
269 a.showSeparator && el(SelectControl, {
270 label: __('Separator style', 'blockenberg'),
271 value: a.separator,
272 options: SEPARATOR_OPTIONS,
273 onChange: function (v) { setAttributes({ separator: v }); }
274 }),
275 a.showSeparator && el(RangeControl, {
276 label: __('Separator spacing', 'blockenberg'),
277 value: a.separatorSpacing,
278 onChange: function (v) { setAttributes({ separatorSpacing: v }); },
279 min: 0,
280 max: 100
281 })
282 ),
283
284 el(PanelBody, { title: __('Colors', 'blockenberg'), initialOpen: false },
285 ColorButton('backgroundColor', __('Background', 'blockenberg')),
286 ColorButton('textColor', __('Text', 'blockenberg')),
287 ColorButton('linkHoverColor', __('Link hover', 'blockenberg'))
288 ),
289
290 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
291 getTypoControl() && el(getTypoControl(), { label: __('Text', 'blockenberg'), value: a.textTypo || {}, onChange: function (v) { set({ textTypo: v }); } })
292 ),
293
294 el(PanelBody, { title: __('Spacing', 'blockenberg'), initialOpen: false },
295 el(RangeControl, {
296 label: __('Vertical padding', 'blockenberg'),
297 value: a.paddingY,
298 onChange: function (v) { setAttributes({ paddingY: v }); },
299 min: 4,
300 max: 40
301 }),
302 el(RangeControl, {
303 label: __('Horizontal padding', 'blockenberg'),
304 value: a.paddingX,
305 onChange: function (v) { setAttributes({ paddingX: v }); },
306 min: 0,
307 max: 60
308 })
309 ),
310
311 el(PanelBody, { title: __('Border & Shadow', 'blockenberg'), initialOpen: false },
312 el(ToggleControl, {
313 label: __('Border top', 'blockenberg'),
314 checked: !!a.borderTop,
315 __nextHasNoMarginBottom: true,
316 onChange: function (v) { setAttributes({ borderTop: v }); }
317 }),
318 el(ToggleControl, {
319 label: __('Border bottom', 'blockenberg'),
320 checked: !!a.borderBottom,
321 __nextHasNoMarginBottom: true,
322 onChange: function (v) { setAttributes({ borderBottom: v }); }
323 }),
324 (a.borderTop || a.borderBottom) && el(RangeControl, {
325 label: __('Border width', 'blockenberg'),
326 value: a.borderWidth,
327 onChange: function (v) { setAttributes({ borderWidth: v }); },
328 min: 1,
329 max: 5
330 }),
331 (a.borderTop || a.borderBottom) && ColorButton('borderColor', __('Border color', 'blockenberg')),
332 el(ToggleControl, {
333 label: __('Shadow', 'blockenberg'),
334 checked: !!a.shadow,
335 __nextHasNoMarginBottom: true,
336 onChange: function (v) { setAttributes({ shadow: v }); }
337 })
338 ),
339
340 el(PanelBody, { title: __('Close Button', 'blockenberg'), initialOpen: false },
341 el(ToggleControl, {
342 label: __('Show close button', 'blockenberg'),
343 checked: !!a.showCloseButton,
344 __nextHasNoMarginBottom: true,
345 onChange: function (v) { setAttributes({ showCloseButton: v }); }
346 }),
347 a.showCloseButton && ColorButton('closeButtonColor', __('Close button color', 'blockenberg')),
348 a.showCloseButton && el(ToggleControl, {
349 label: __('Remember dismissal', 'blockenberg'),
350 checked: !!a.closePersist,
351 __nextHasNoMarginBottom: true,
352 onChange: function (v) { setAttributes({ closePersist: v }); },
353 help: __('Uses a cookie to remember when visitor closes the bar.', 'blockenberg')
354 }),
355 a.showCloseButton && a.closePersist && el(RangeControl, {
356 label: __('Remember for (days)', 'blockenberg'),
357 value: a.closePersistDays,
358 onChange: function (v) { setAttributes({ closePersistDays: v }); },
359 min: 1,
360 max: 365
361 })
362 ),
363
364 el(PanelBody, { title: __('Sticky Position', 'blockenberg'), initialOpen: false },
365 el(ToggleControl, {
366 label: __('Sticky', 'blockenberg'),
367 checked: !!a.sticky,
368 __nextHasNoMarginBottom: true,
369 onChange: function (v) { setAttributes({ sticky: v }); }
370 }),
371 a.sticky && el(SelectControl, {
372 label: __('Position', 'blockenberg'),
373 value: a.stickyPosition,
374 options: [
375 { label: __('Top', 'blockenberg'), value: 'top' },
376 { label: __('Bottom', 'blockenberg'), value: 'bottom' }
377 ],
378 onChange: function (v) { setAttributes({ stickyPosition: v }); }
379 }),
380 a.sticky && el(RangeControl, {
381 label: __('Z-index', 'blockenberg'),
382 value: a.stickyZIndex,
383 onChange: function (v) { setAttributes({ stickyZIndex: v }); },
384 min: 1,
385 max: 9999
386 })
387 ),
388
389 el(PanelBody, { title: __('Accessibility', 'blockenberg'), initialOpen: false },
390 el(TextControl, {
391 label: __('ARIA label', 'blockenberg'),
392 value: a.ariaLabel,
393 onChange: function (v) { setAttributes({ ariaLabel: v }); },
394 help: __('Describes the region for screen readers.', 'blockenberg')
395 })
396 )
397 );
398
399 function renderItems() {
400 return (a.items || []).map(function (item, index) {
401 var content = el('span', { className: 'bkbg-marquee-item', key: index }, item.text || '');
402 if (a.showSeparator && index < a.items.length - 1) {
403 // When separator is "None" we still keep spacing for readability.
404 return el(Fragment, { key: index },
405 content,
406 a.separator
407 ? el('span', { className: 'bkbg-marquee-separator' }, a.separator)
408 : el('span', { className: 'bkbg-marquee-gap', 'aria-hidden': 'true' })
409 );
410 }
411 return content;
412 });
413 }
414
415 var content = el('div', blockProps,
416 el('div', { className: 'bkbg-marquee-inner' },
417 el('div', { className: 'bkbg-marquee-track' },
418 el('div', { className: 'bkbg-marquee-content' }, renderItems()),
419 el('div', { className: 'bkbg-marquee-content', 'aria-hidden': 'true' }, renderItems())
420 ),
421 a.showCloseButton && el('button', { type: 'button', className: 'bkbg-marquee-close', 'aria-label': __('Close announcement', 'blockenberg') },
422 el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
423 el('path', { d: 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z' })
424 )
425 )
426 )
427 );
428
429 return el(Fragment, {}, inspector, content);
430 },
431
432 save: function (props) {
433 var a = props.attributes;
434 var set = props.setAttributes;
435
436 var wrapperClass = 'bkbg-marquee-wrap';
437 if (a.shadow) wrapperClass += ' has-shadow';
438 if (a.borderTop) wrapperClass += ' has-border-top';
439 if (a.borderBottom) wrapperClass += ' has-border-bottom';
440 if (a.sticky) wrapperClass += ' is-sticky is-sticky-' + a.stickyPosition;
441 if (a.reduceMotion) wrapperClass += ' respects-motion';
442
443 var blockProps = useBlockProps.save((function () {
444 var tv = getTypoCssVars();
445 var s = {
446 '--bkbg-mq-bg': a.backgroundColor,
447 '--bkbg-mq-color': a.textColor,
448 '--bkbg-mq-link-hover': a.linkHoverColor,
449 '--bkbg-mq-py': a.paddingY + 'px',
450 '--bkbg-mq-px': a.paddingX + 'px',
451 '--bkbg-mq-font-size': a.fontSize + 'px',
452 '--bkbg-mq-font-weight': a.fontWeight,
453 '--bkbg-mq-letter-spacing': a.letterSpacing + 'px',
454 '--bkbg-mq-speed': (a.durationSeconds && a.durationSeconds > 0
455 ? clampMin(a.durationSeconds, 5)
456 : clampMin((150 - (a.speed || 50)), 5)
457 ) + 's',
458 '--bkbg-mq-sep-spacing': a.separatorSpacing + 'px',
459 '--bkbg-mq-border-color': a.borderColor,
460 '--bkbg-mq-border-width': a.borderWidth + 'px',
461 '--bkbg-mq-close-color': a.closeButtonColor,
462 '--bkbg-mq-z-index': a.stickyZIndex
463 };
464 Object.assign(s, tv(a.textTypo, '--bkbg-mq-tx-'));
465 return {
466 className: wrapperClass,
467 style: s,
468 'data-instance-id': a.instanceId || undefined,
469 'data-direction': a.direction,
470 'data-pause-hover': a.pauseOnHover ? '1' : '0',
471 'data-pause-click': a.pauseOnClick ? '1' : '0',
472 'data-close-persist': a.closePersist ? '1' : '0',
473 'data-close-persist-days': String(a.closePersistDays),
474 'role': 'region',
475 'aria-label': a.ariaLabel || 'Announcements'
476 };
477 })());
478
479 function renderItems() {
480 return (a.items || []).map(function (item, index) {
481 var Tag = item.link ? 'a' : 'span';
482 var linkProps = item.link ? {
483 href: item.link,
484 target: item.newTab ? '_blank' : undefined,
485 rel: item.newTab ? 'noopener noreferrer' : undefined
486 } : {};
487
488 var content = el(Tag, Object.assign({ className: 'bkbg-marquee-item', key: index }, linkProps), item.text || '');
489
490 if (a.showSeparator && index < a.items.length - 1) {
491 return el(Fragment, { key: index },
492 content,
493 a.separator
494 ? el('span', { className: 'bkbg-marquee-separator', 'aria-hidden': 'true' }, a.separator)
495 : el('span', { className: 'bkbg-marquee-gap', 'aria-hidden': 'true' })
496 );
497 }
498 return content;
499 });
500 }
501
502 return el('div', blockProps,
503 el('div', { className: 'bkbg-marquee-inner' },
504 el('div', { className: 'bkbg-marquee-track' },
505 el('div', { className: 'bkbg-marquee-content' }, renderItems())
506 ),
507 a.showCloseButton && el('button', { type: 'button', className: 'bkbg-marquee-close', 'aria-label': __('Close announcement', 'blockenberg') },
508 el('svg', { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' },
509 el('path', { d: 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z' })
510 )
511 )
512 )
513 );
514 }
515 });
516 }() );
517