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 / animated-text / index.js

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

388 lines 21.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 var el = wp.element.createElement;
3 var Fragment = wp.element.Fragment;
4 var useState = wp.element.useState;
5 var useEffect = wp.element.useEffect;
6 var useRef = wp.element.useRef;
7 var __ = wp.i18n.__;
8 var registerBlockType = wp.blocks.registerBlockType;
9 var InspectorControls = wp.blockEditor.InspectorControls;
10 var useBlockProps = wp.blockEditor.useBlockProps;
11 var RichText = wp.blockEditor.RichText;
12 var PanelBody = wp.components.PanelBody;
13 var Button = wp.components.Button;
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 ColorPicker = wp.components.ColorPicker;
19 var Popover = wp.components.Popover;
20
21 // Lazy lookup so the typography control is resolved at render time
22 function getTypographyControl() {
23 return (typeof window.bkbgTypographyControl !== 'undefined') ? window.bkbgTypographyControl : null;
24 }
25 function getTypoCssVars() {
26 return (typeof window.bkbgTypoCssVars !== 'undefined') ? window.bkbgTypoCssVars : function() { return {}; };
27 }
28
29 // ── CSS vars ──────────────────────────────────────────────────────────────
30 function buildWrapStyle(a) {
31 var style = {
32 '--bkbg-at-static-color' : a.staticColor,
33 '--bkbg-at-animated-color' : a.animatedColor,
34 '--bkbg-at-cursor-color' : a.cursorColor,
35 '--bkbg-at-font-size' : a.fontSize + 'px',
36 '--bkbg-at-font-weight' : a.fontWeight,
37 '--bkbg-at-line-height' : a.lineHeight,
38 '--bkbg-at-letter-spacing' : a.letterSpacing + 'px',
39 '--bkbg-at-text-align' : a.textAlign,
40 '--bkbg-at-cursor-blink' : a.cursorBlinkSpeed + 'ms',
41 '--bkbg-at-highlight-bg' : a.highlightBg || 'transparent',
42 '--bkbg-at-highlight-pad' : a.highlightPadding + 'px',
43 '--bkbg-at-highlight-radius': a.highlightRadius + 'px',
44 '--bkbg-at-pad-top' : a.wrapperPaddingTop + 'px',
45 '--bkbg-at-pad-bottom' : a.wrapperPaddingBottom + 'px'
46 };
47 if (a.maxWidth) { style['--bkbg-at-max-width'] = a.maxWidth + 'px'; }
48 var _tv = getTypoCssVars();
49 Object.assign(style, _tv(a.headingTypo || {}, '--bkbg-at-heading-'));
50 return style;
51 }
52
53 // ── Color swatch ──────────────────────────────────────────────────────────
54 function renderColorControl(key, label, value, onChange, openKey, setOpenKey) {
55 var isOpen = openKey === key;
56 return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } },
57 el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label),
58 el('div', { style: { position: 'relative', flexShrink: 0 } },
59 el('button', {
60 type: 'button',
61 title: value || 'none',
62 onClick: function () { setOpenKey(isOpen ? null : key); },
63 style: {
64 width: '28px', height: '28px', borderRadius: '4px',
65 border: isOpen ? '2px solid #007cba' : '2px solid #ddd',
66 cursor: 'pointer', padding: 0, display: 'block',
67 background: value || '#fff'
68 }
69 }),
70 isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } },
71 el('div', { style: { padding: '8px' } },
72 el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange })
73 )
74 )
75 )
76 );
77 }
78
79 // ── Live preview typewriter in the editor ─────────────────────────────────
80 function EditorPreview(props) {
81 var a = props.attributes;
82 var displayWordState = useState(a.words[0] || '');
83 var displayWord = displayWordState[0];
84 var setDisplayWord = displayWordState[1];
85 var indexRef = useRef(0);
86 var timerRef = useRef(null);
87
88 useEffect(function () {
89 if (a.effect !== 'typewriter' || a.words.length < 2) return;
90 function cycle() {
91 var next = (indexRef.current + 1) % a.words.length;
92 indexRef.current = next;
93 setDisplayWord(a.words[next] || '');
94 timerRef.current = setTimeout(cycle, 1800);
95 }
96 timerRef.current = setTimeout(cycle, 1800);
97 return function () { clearTimeout(timerRef.current); };
98 }, [a.words, a.effect]);
99
100 var wordToShow = a.effect === 'typewriter' ? displayWord : (a.words[0] || '');
101
102 return el('span', { className: 'bkbg-at-animated-word bkbg-at-effect-' + a.effect },
103 el('span', { className: 'bkbg-at-word-text' }, wordToShow),
104 a.showCursor && el('span', { className: 'bkbg-at-cursor', 'aria-hidden': 'true' }, a.cursorChar)
105 );
106 }
107
108 // ── Register ──────────────────────────────────────────────────────────────
109 registerBlockType('blockenberg/animated-text', {
110 title: __('Animated Text', 'blockenberg'),
111 icon: 'editor-textcolor',
112 category: 'bkbg-effects',
113 description: __('Animated cycling headline with typewriter, fade, slide, or flip effects.', 'blockenberg'),
114
115 edit: function (props) {
116 var a = props.attributes;
117 var setAttributes = props.setAttributes;
118
119 var openColorKeyState = useState(null);
120 var openColorKey = openColorKeyState[0];
121 var setOpenColorKey = openColorKeyState[1];
122
123 function cc(key, label, attrKey) {
124 return renderColorControl(key, label, a[attrKey], function (val) {
125 var upd = {}; upd[attrKey] = val; setAttributes(upd);
126 }, openColorKey, setOpenColorKey);
127 }
128
129 function updateWord(i, val) {
130 var w = a.words.slice();
131 w[i] = val;
132 setAttributes({ words: w });
133 }
134
135 function addWord() { setAttributes({ words: a.words.concat(['new word']) }); }
136 function removeWord(i) {
137 if (a.words.length <= 1) return;
138 setAttributes({ words: a.words.filter(function (_, idx) { return idx !== i; }) });
139 }
140 function moveWord(i, dir) {
141 var ni = i + dir;
142 if (ni < 0 || ni >= a.words.length) return;
143 var w = a.words.slice(); var tmp = w[i]; w[i] = w[ni]; w[ni] = tmp;
144 setAttributes({ words: w });
145 }
146
147 var blockProps = useBlockProps({ className: 'bkbg-at-wrapper bkbg-at-align-' + a.textAlign, style: buildWrapStyle(a) });
148
149 // ── Inspector ─────────────────────────────────────────────────────
150 var inspector = el(InspectorControls, {},
151 // Words
152 el(PanelBody, { title: __('Words / Phrases', 'blockenberg'), initialOpen: true },
153 el('p', { style: { fontSize: '12px', color: '#757575', marginTop: 0 } },
154 __('Each word/phrase will be animated in cycle.', 'blockenberg')
155 ),
156 a.words.map(function (word, i) {
157 return el('div', {
158 key: i,
159 style: { display: 'flex', gap: '6px', alignItems: 'center', marginBottom: '8px' }
160 },
161 el(TextControl, {
162 value: word, style: { flex:1, margin: 0 },
163 onChange: function (v) { updateWord(i, v); }
164 }),
165 el(Button, { isSmall: true, variant: 'tertiary', onClick: function () { moveWord(i, -1); }, disabled: i === 0 }, ''),
166 el(Button, { isSmall: true, variant: 'tertiary', onClick: function () { moveWord(i, 1); }, disabled: i === a.words.length - 1 }, ''),
167 el(Button, { isSmall: true, isDestructive: true, variant: 'tertiary', onClick: function () { removeWord(i); }, disabled: a.words.length <= 1 }, '')
168 );
169 }),
170 el(Button, { variant: 'secondary', onClick: addWord, style: { width: '100%', justifyContent: 'center' } },
171 '+ ' + __('Add Word', 'blockenberg')
172 )
173 ),
174
175 // Animation
176 el(PanelBody, { title: __('Animation', 'blockenberg'), initialOpen: false },
177 el(SelectControl, {
178 label: __('Effect', 'blockenberg'), value: a.effect,
179 options: [
180 { label: __('Typewriter', 'blockenberg'), value: 'typewriter' },
181 { label: __('Fade', 'blockenberg'), value: 'fade' },
182 { label: __('Slide Up', 'blockenberg'), value: 'slide-up' },
183 { label: __('Slide Down', 'blockenberg'), value: 'slide-down' },
184 { label: __('Flip / Rotate', 'blockenberg'), value: 'flip' },
185 { label: __('Zoom', 'blockenberg'), value: 'zoom' },
186 { label: __('Blur', 'blockenberg'), value: 'blur' }
187 ],
188 onChange: function (v) { setAttributes({ effect: v }); }
189 }),
190 a.effect === 'typewriter' && el(Fragment, {},
191 el(RangeControl, {
192 label: __('Typing Speed (ms/char)', 'blockenberg'), value: a.typingSpeed, min: 20, max: 300,
193 onChange: function (v) { setAttributes({ typingSpeed: v }); }
194 }),
195 el(RangeControl, {
196 label: __('Deleting Speed (ms/char)', 'blockenberg'), value: a.deletingSpeed, min: 10, max: 200,
197 onChange: function (v) { setAttributes({ deletingSpeed: v }); }
198 })
199 ),
200 el(RangeControl, {
201 label: __('Pause Duration (ms)', 'blockenberg'), value: a.pauseDuration, min: 500, max: 6000, step: 100,
202 onChange: function (v) { setAttributes({ pauseDuration: v }); }
203 }),
204 el(ToggleControl, {
205 label: __('Loop Continuously', 'blockenberg'), checked: a.loop,
206 onChange: function (v) { setAttributes({ loop: v }); }
207 }),
208 el(ToggleControl, {
209 label: __('Show Cursor', 'blockenberg'), checked: a.showCursor,
210 onChange: function (v) { setAttributes({ showCursor: v }); }
211 }),
212 a.showCursor && el(Fragment, {},
213 el(TextControl, {
214 label: __('Cursor Character', 'blockenberg'), value: a.cursorChar,
215 onChange: function (v) { setAttributes({ cursorChar: v }); }
216 }),
217 el(RangeControl, {
218 label: __('Cursor Blink Speed (ms)', 'blockenberg'), value: a.cursorBlinkSpeed, min: 200, max: 1500,
219 onChange: function (v) { setAttributes({ cursorBlinkSpeed: v }); }
220 }),
221 cc('cursorColor', __('Cursor Color', 'blockenberg'), 'cursorColor')
222 )
223 ),
224
225 // Text layout
226 el(PanelBody, { title: __('Text & Layout', 'blockenberg'), initialOpen: false },
227 el(SelectControl, {
228 label: __('HTML Tag', 'blockenberg'), value: a.tag,
229 options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div'].map(function (t) { return { label: t.toUpperCase(), value: t }; }),
230 onChange: function (v) { setAttributes({ tag: v }); }
231 }),
232 el(SelectControl, {
233 label: __('Text Alignment', 'blockenberg'), value: a.textAlign,
234 options: [
235 { label: __('Left', 'blockenberg'), value: 'left' },
236 { label: __('Center', 'blockenberg'), value: 'center' },
237 { label: __('Right', 'blockenberg'), value: 'right' }
238 ],
239 onChange: function (v) { setAttributes({ textAlign: v }); }
240 }),
241 el(SelectControl, {
242 label: __('Word Placement', 'blockenberg'), value: a.wordPlacement,
243 options: [
244 { label: __('Inline (same line)', 'blockenberg'), value: 'inline' },
245 { label: __('New Line (block)', 'blockenberg'), value: 'block' }
246 ],
247 onChange: function (v) { setAttributes({ wordPlacement: v }); }
248 }),
249 el(RangeControl, {
250 label: __('Max Width (px, 0 = none)', 'blockenberg'), value: a.maxWidth, min: 0, max: 1400,
251 onChange: function (v) { setAttributes({ maxWidth: v }); }
252 }),
253 el(RangeControl, {
254 label: __('Padding Top (px)', 'blockenberg'), value: a.wrapperPaddingTop, min: 0, max: 120,
255 onChange: function (v) { setAttributes({ wrapperPaddingTop: v }); }
256 }),
257 el(RangeControl, {
258 label: __('Padding Bottom (px)', 'blockenberg'), value: a.wrapperPaddingBottom, min: 0, max: 120,
259 onChange: function (v) { setAttributes({ wrapperPaddingBottom: v }); }
260 }),
261 cc('staticColor', __('Static Text Color', 'blockenberg'), 'staticColor'),
262 cc('animatedColor', __('Animated Word Color', 'blockenberg'), 'animatedColor')
263 ),
264
265 // Word Highlight
266 el(PanelBody, { title: __('Word Highlight', 'blockenberg'), initialOpen: false },
267 el('p', { style: { fontSize: '12px', color: '#757575', marginTop: 0 } },
268 __('Optionally add a background highlight behind the animated word.', 'blockenberg')
269 ),
270 cc('highlightBg', __('Highlight Background', 'blockenberg'), 'highlightBg'),
271 el(RangeControl, {
272 label: __('Highlight Padding (px)', 'blockenberg'), value: a.highlightPadding, min: 0, max: 24,
273 onChange: function (v) { setAttributes({ highlightPadding: v }); }
274 }),
275 el(RangeControl, {
276 label: __('Highlight Border Radius (px)', 'blockenberg'), value: a.highlightRadius, min: 0, max: 20,
277 onChange: function (v) { setAttributes({ highlightRadius: v }); }
278 })
279 ),
280 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
281 (function () {
282 var TC = getTypographyControl();
283 if (!TC) return el('p', null, 'Typography control not loaded.');
284 return el(TC, { label: __('Heading Typography', 'blockenberg'), value: a.headingTypo || {}, onChange: function (v) { setAttributes({ headingTypo: v }); } });
285 })()
286 )
287 );
288
289 // ── Block render ──────────────────────────────────────────────────
290 var isBlock = a.wordPlacement === 'block';
291 var headingContent = isBlock
292 ? el(Fragment, {},
293 a.beforeText && el(RichText, {
294 tagName: 'span', className: 'bkbg-at-static bkbg-at-before',
295 value: a.beforeText, onChange: function (v) { setAttributes({ beforeText: v }); },
296 placeholder: __('Before text…', 'blockenberg')
297 }),
298 el('br', { className: 'bkbg-at-break' }),
299 el(EditorPreview, { attributes: a }),
300 a.afterText && el(Fragment, {},
301 el('br', { className: 'bkbg-at-break' }),
302 el(RichText, {
303 tagName: 'span', className: 'bkbg-at-static bkbg-at-after',
304 value: a.afterText, onChange: function (v) { setAttributes({ afterText: v }); },
305 placeholder: __('After text…', 'blockenberg')
306 })
307 )
308 )
309 : el(Fragment, {},
310 a.beforeText && el(RichText, {
311 tagName: 'span', className: 'bkbg-at-static bkbg-at-before',
312 value: a.beforeText, onChange: function (v) { setAttributes({ beforeText: v }); },
313 placeholder: __('Before text…', 'blockenberg')
314 }),
315 a.beforeText && el('span', {}, ' '),
316 el(EditorPreview, { attributes: a }),
317 a.afterText && el('span', {}, ' '),
318 a.afterText && el(RichText, {
319 tagName: 'span', className: 'bkbg-at-static bkbg-at-after',
320 value: a.afterText, onChange: function (v) { setAttributes({ afterText: v }); },
321 placeholder: __('After text…', 'blockenberg')
322 })
323 );
324
325 return el(Fragment, {},
326 inspector,
327 el('div', blockProps,
328 el(a.tag, { className: 'bkbg-at-heading' }, headingContent)
329 )
330 );
331 },
332
333 // ── Save ─────────────────────────────────────────────────────────────
334 save: function (props) {
335 var a = props.attributes;
336 var isBlock = a.wordPlacement === 'block';
337
338 var headingContent = isBlock
339 ? [
340 a.beforeText && el(RichText.Content, { key: 'before', tagName: 'span', className: 'bkbg-at-static bkbg-at-before', value: a.beforeText }),
341 el('br', { key: 'br1', className: 'bkbg-at-break' }),
342 el('span', {
343 key: 'anim',
344 className: 'bkbg-at-animated-word bkbg-at-effect-' + a.effect,
345 'data-words': JSON.stringify(a.words),
346 'data-effect': a.effect,
347 'data-typing-speed': a.typingSpeed,
348 'data-deleting-speed': a.deletingSpeed,
349 'data-pause': a.pauseDuration,
350 'data-loop': a.loop ? '1' : '0',
351 'data-cursor': a.showCursor ? '1' : '0',
352 'data-cursor-char': a.cursorChar
353 },
354 el('span', { className: 'bkbg-at-word-text' }, a.words[0] || ''),
355 a.showCursor && el('span', { className: 'bkbg-at-cursor', 'aria-hidden': 'true' }, a.cursorChar)
356 ),
357 a.afterText && el('br', { key: 'br2', className: 'bkbg-at-break' }),
358 a.afterText && el(RichText.Content, { key: 'after', tagName: 'span', className: 'bkbg-at-static bkbg-at-after', value: a.afterText })
359 ]
360 : [
361 a.beforeText && el(RichText.Content, { key: 'before', tagName: 'span', className: 'bkbg-at-static bkbg-at-before', value: a.beforeText }),
362 a.beforeText && el('span', { key: 'sp1' }, ' '),
363 el('span', {
364 key: 'anim',
365 className: 'bkbg-at-animated-word bkbg-at-effect-' + a.effect,
366 'data-words': JSON.stringify(a.words),
367 'data-effect': a.effect,
368 'data-typing-speed': a.typingSpeed,
369 'data-deleting-speed': a.deletingSpeed,
370 'data-pause': a.pauseDuration,
371 'data-loop': a.loop ? '1' : '0',
372 'data-cursor': a.showCursor ? '1' : '0',
373 'data-cursor-char': a.cursorChar
374 },
375 el('span', { className: 'bkbg-at-word-text' }, a.words[0] || ''),
376 a.showCursor && el('span', { className: 'bkbg-at-cursor', 'aria-hidden': 'true' }, a.cursorChar)
377 ),
378 a.afterText && el('span', { key: 'sp2' }, ' '),
379 a.afterText && el(RichText.Content, { key: 'after', tagName: 'span', className: 'bkbg-at-static bkbg-at-after', value: a.afterText })
380 ];
381
382 return el('div', wp.blockEditor.useBlockProps.save({ className: 'bkbg-at-wrapper bkbg-at-align-' + a.textAlign, style: buildWrapStyle(a) }),
383 el(a.tag, { className: 'bkbg-at-heading' }, headingContent)
384 );
385 }
386 });
387 }() );
388