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 / frontend.js

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

379 lines 13.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 function init(root) {
3 if (!root) return;
4 var track = root.querySelector('.bkbg-lc-track');
5 if (!track) return;
6
7 var infiniteEnabled = root.getAttribute('data-bkbg-infinite') !== '0';
8 var autoScrollEnabled = root.getAttribute('data-bkbg-autoscroll') === '1';
9 var autoScrollSpeed = parseFloat(root.getAttribute('data-bkbg-autoscroll-speed') || '40');
10 if (!isFinite(autoScrollSpeed) || autoScrollSpeed < 0) autoScrollSpeed = 40;
11 var autoScrollPauseOnHover = root.getAttribute('data-bkbg-autoscroll-hover') !== '0';
12
13 var dots = root.querySelectorAll('[data-bkbg-dot]');
14 var progressBar = root.querySelector('.bkbg-lc-progress-bar');
15 var fractionCurrent = root.querySelector('[data-bkbg-fraction="current"]');
16 var fractionTotal = root.querySelector('[data-bkbg-fraction="total"]');
17
18 var prev = root.querySelector('[data-bkbg-nav="prev"]');
19 var next = root.querySelector('[data-bkbg-nav="next"]');
20
21 var baseCount = 0;
22 var didClone = false;
23 var isNormalizing = false;
24 var segmentStart = 0;
25 var segmentWidth = 0;
26
27 function disableTabbing(el) {
28 try {
29 var focusables = el.querySelectorAll('a, button, input, textarea, select');
30 for (var i = 0; i < focusables.length; i++) {
31 focusables[i].setAttribute('tabindex', '-1');
32 }
33 } catch (e) { }
34 }
35
36 function setupInfinite() {
37 if (!infiniteEnabled) return;
38
39 var slides = getSlides();
40 baseCount = slides.length;
41 if (baseCount < 2) return;
42 if (didClone) return;
43
44 var originals = Array.prototype.slice.call(slides);
45 var before = [];
46 var after = [];
47
48 for (var i = 0; i < originals.length; i++) {
49 var c1 = originals[i].cloneNode(true);
50 c1.classList.add('is-clone');
51 c1.setAttribute('aria-hidden', 'true');
52 disableTabbing(c1);
53 after.push(c1);
54
55 var c2 = originals[i].cloneNode(true);
56 c2.classList.add('is-clone');
57 c2.setAttribute('aria-hidden', 'true');
58 disableTabbing(c2);
59 before.push(c2);
60 }
61
62 // Prepend in reverse order so the sequence stays correct.
63 for (var j = before.length - 1; j >= 0; j--) {
64 track.insertBefore(before[j], track.firstChild);
65 }
66 for (var k = 0; k < after.length; k++) {
67 track.appendChild(after[k]);
68 }
69
70 didClone = true;
71
72 // Jump to the middle copy.
73 window.requestAnimationFrame(function () {
74 measureInfinite(true);
75 syncIndicators();
76 });
77 }
78
79 function measureInfinite(recenter) {
80 if (!infiniteEnabled || !didClone || !baseCount) return;
81 var slides = getSlides();
82 if (!slides || slides.length < baseCount * 3) return;
83
84 var middleFirst = slides[baseCount];
85 var afterFirst = slides[baseCount * 2];
86 if (!middleFirst || !afterFirst) return;
87
88 var nextStart = middleFirst.offsetLeft;
89 var nextWidth = afterFirst.offsetLeft - middleFirst.offsetLeft;
90 if (!isFinite(nextStart) || !isFinite(nextWidth) || nextWidth <= 0) return;
91
92 if (recenter) {
93 // Preserve local offset when dimensions change.
94 var local = track.scrollLeft - segmentStart;
95 if (segmentWidth > 0) {
96 local = local % segmentWidth;
97 if (local < 0) local += segmentWidth;
98 } else {
99 local = 0;
100 }
101 segmentStart = nextStart;
102 segmentWidth = nextWidth;
103 track.scrollLeft = segmentStart + local;
104 } else {
105 segmentStart = nextStart;
106 segmentWidth = nextWidth;
107 }
108 }
109
110 function normalizeInfiniteScroll() {
111 if (!infiniteEnabled || !didClone) return;
112 if (!segmentWidth || !isFinite(segmentWidth)) {
113 measureInfinite(false);
114 }
115 if (!segmentWidth) return;
116
117 // Keep scrollLeft inside the middle copy to avoid hitting the max scroll clamp.
118 if (track.scrollLeft < (segmentStart - 1) || track.scrollLeft >= (segmentStart + segmentWidth + 1)) {
119 var local = track.scrollLeft - segmentStart;
120 local = local % segmentWidth;
121 if (local < 0) local += segmentWidth;
122 isNormalizing = true;
123 track.scrollLeft = segmentStart + local;
124 isNormalizing = false;
125 }
126 }
127
128 function getSlides() {
129 return track.querySelectorAll('.bkbg-lc-slide');
130 }
131
132 function getCurrentIndex() {
133 var slides = getSlides();
134 if (!slides.length) return 0;
135
136 var trackRect = track.getBoundingClientRect();
137 var currentIndex = 0;
138 for (var i = 0; i < slides.length; i++) {
139 var r = slides[i].getBoundingClientRect();
140 if (r.left >= trackRect.left - 10) {
141 currentIndex = i;
142 break;
143 }
144 }
145 return currentIndex;
146 }
147
148 function scrollToIndex(index) {
149 var slides = getSlides();
150 if (!slides.length) return;
151 var i = Math.max(0, Math.min(slides.length - 1, index));
152 slides[i].scrollIntoView({ behavior: 'smooth', inline: 'start', block: 'nearest' });
153 }
154
155 function scrollToBaseIndex(index) {
156 if (!infiniteEnabled || !didClone || !baseCount) {
157 scrollToIndex(index);
158 return;
159 }
160
161 var slides = getSlides();
162 if (!slides.length) return;
163 var idx = index % baseCount;
164 if (idx < 0) idx += baseCount;
165 var target = baseCount + idx;
166 if (target < 0) target = 0;
167 if (target > slides.length - 1) target = slides.length - 1;
168 slides[target].scrollIntoView({ behavior: 'smooth', inline: 'start', block: 'nearest' });
169 }
170
171 function scrollByOne(dir) {
172 var slides = getSlides();
173 if (!slides.length) return;
174 var currentIndex = getCurrentIndex();
175 scrollToIndex(currentIndex + dir);
176 }
177
178 function syncIndicators() {
179 var slides = getSlides();
180 if (!slides.length) return;
181
182 var idx = getCurrentIndex();
183 var total = slides.length;
184 if (infiniteEnabled && didClone && baseCount) {
185 total = baseCount;
186 idx = idx % baseCount;
187 if (idx < 0) idx += baseCount;
188 }
189
190 if (dots && dots.length) {
191 for (var i = 0; i < dots.length; i++) {
192 if (i === idx) dots[i].classList.add('is-active');
193 else dots[i].classList.remove('is-active');
194 }
195 }
196
197 if (progressBar) {
198 var pct = 0;
199 if (infiniteEnabled && didClone) {
200 if (!segmentWidth) measureInfinite(false);
201 var local = track.scrollLeft - segmentStart;
202 if (segmentWidth > 0) {
203 local = local % segmentWidth;
204 if (local < 0) local += segmentWidth;
205 }
206 var maxLocal = segmentWidth - track.clientWidth;
207 pct = maxLocal > 0 ? (local / maxLocal) * 100 : 0;
208 } else {
209 var max = track.scrollWidth - track.clientWidth;
210 pct = max > 0 ? (track.scrollLeft / max) * 100 : 0;
211 }
212 if (pct < 0) pct = 0;
213 if (pct > 100) pct = 100;
214 progressBar.style.width = pct + '%';
215 }
216
217 if (fractionCurrent) fractionCurrent.textContent = String(idx + 1);
218 if (fractionTotal) fractionTotal.textContent = String(total);
219 }
220
221 if (prev) prev.addEventListener('click', function (e) { e.preventDefault(); scrollByOne(-1); });
222 if (next) next.addEventListener('click', function (e) { e.preventDefault(); scrollByOne(1); });
223
224 if (dots && dots.length) {
225 for (var i = 0; i < dots.length; i++) {
226 (function (btn) {
227 btn.addEventListener('click', function (e) {
228 e.preventDefault();
229 var idx = parseInt(btn.getAttribute('data-bkbg-dot') || '0', 10);
230 scrollToBaseIndex(idx);
231 });
232 })(dots[i]);
233 }
234 }
235
236 // Autoplay (optional)
237 var autoplay = root.getAttribute('data-bkbg-autoplay') === '1';
238 var delay = parseInt(root.getAttribute('data-bkbg-autoplay-delay') || '3000', 10);
239 var pauseOnHover = root.getAttribute('data-bkbg-autoplay-hover') !== '0';
240 var timer = null;
241 var paused = false;
242
243 function start() {
244 if (!autoplay) return;
245 if (timer) return;
246 if (!delay || delay < 800) delay = 800;
247 timer = window.setInterval(function () {
248 if (paused) return;
249 var slides = getSlides();
250 if (!slides.length) return;
251 var idx = getCurrentIndex();
252 var nextIdx = idx + 1;
253 if (nextIdx >= slides.length) nextIdx = 0;
254 scrollToIndex(nextIdx);
255 }, delay);
256 }
257
258 function stop() {
259 if (timer) {
260 window.clearInterval(timer);
261 timer = null;
262 }
263 }
264
265 if (autoplay) {
266 start();
267 if (pauseOnHover) {
268 root.addEventListener('mouseenter', function () { paused = true; });
269 root.addEventListener('mouseleave', function () { paused = false; });
270 }
271 document.addEventListener('visibilitychange', function () {
272 if (document.hidden) stop();
273 else start();
274 });
275 }
276
277 // Auto-scroll (continuous, optional). Takes precedence over autoplay.
278 var rafId = null;
279 var lastTs = 0;
280 var autoPaused = false;
281 var originalSnap = track.style.scrollSnapType;
282
283 function tick(ts) {
284 if (!autoScrollEnabled) return;
285 if (!lastTs) lastTs = ts;
286 var dt = ts - lastTs;
287 lastTs = ts;
288
289 if (!document.hidden && !autoPaused) {
290 var d = (autoScrollSpeed * dt) / 1000;
291 track.scrollLeft += d;
292 normalizeInfiniteScroll();
293 syncIndicators();
294 }
295
296 rafId = window.requestAnimationFrame(tick);
297 }
298
299 function startAutoScroll() {
300 if (!autoScrollEnabled) return;
301 if (rafId) return;
302
303 // Disable snap so the motion stays smooth.
304 track.style.scrollSnapType = 'none';
305 lastTs = 0;
306 rafId = window.requestAnimationFrame(tick);
307 }
308
309 function stopAutoScroll() {
310 if (rafId) {
311 window.cancelAnimationFrame(rafId);
312 rafId = null;
313 }
314 track.style.scrollSnapType = originalSnap;
315 lastTs = 0;
316 }
317
318 if (autoScrollEnabled) {
319 // Disable step autoplay if continuous auto-scroll is on.
320 autoplay = false;
321 stop();
322 startAutoScroll();
323
324 if (autoScrollPauseOnHover) {
325 root.addEventListener('mouseenter', function () { autoPaused = true; });
326 root.addEventListener('mouseleave', function () { autoPaused = false; });
327 }
328 document.addEventListener('visibilitychange', function () {
329 if (document.hidden) stopAutoScroll();
330 else startAutoScroll();
331 });
332 }
333
334 setupInfinite();
335
336 // Re-measure after images load / layout changes to avoid a visible jump.
337 try {
338 var imgs = track.querySelectorAll('img');
339 for (var ii = 0; ii < imgs.length; ii++) {
340 (function (img) {
341 if (!img) return;
342 if (img.complete) return;
343 img.addEventListener('load', function () {
344 window.requestAnimationFrame(function () {
345 measureInfinite(true);
346 syncIndicators();
347 });
348 }, { passive: true });
349 })(imgs[ii]);
350 }
351 } catch (e) { }
352
353 window.addEventListener('resize', function () {
354 window.requestAnimationFrame(function () {
355 measureInfinite(true);
356 syncIndicators();
357 });
358 }, { passive: true });
359
360 syncIndicators();
361 track.addEventListener('scroll', function () {
362 normalizeInfiniteScroll();
363 if (isNormalizing) return;
364 syncIndicators();
365 }, { passive: true });
366 }
367
368 function boot() {
369 var blocks = document.querySelectorAll('.wp-block-blockenberg-logo-carousel');
370 for (var i = 0; i < blocks.length; i++) init(blocks[i]);
371 }
372
373 if (document.readyState === 'loading') {
374 document.addEventListener('DOMContentLoaded', boot);
375 } else {
376 boot();
377 }
378 })();
379