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

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

319 lines 11.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-ts-carousel-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-ts-progress-bar');
15 var fractionCurrent = root.querySelector('[data-bkbg-fraction="current"]');
16 var fractionTotal = root.querySelector('[data-bkbg-fraction="total"]');
17
18 var baseCount = 0;
19 var didClone = false;
20 var isNormalizing = false;
21 var segmentStart = 0;
22 var segmentWidth = 0;
23
24 function disableTabbing(el) {
25 try {
26 var focusables = el.querySelectorAll('a, button, input, textarea, select');
27 for (var i = 0; i < focusables.length; i++) {
28 focusables[i].setAttribute('tabindex', '-1');
29 }
30 } catch (e) { }
31 }
32
33 function setupInfinite() {
34 if (!infiniteEnabled) return;
35 var slides = getSlides();
36 baseCount = slides.length;
37 if (baseCount < 2) return;
38 if (didClone) return;
39
40 var originals = Array.prototype.slice.call(slides);
41 var before = [];
42 var after = [];
43 for (var i = 0; i < originals.length; i++) {
44 var c1 = originals[i].cloneNode(true);
45 c1.classList.add('is-clone');
46 c1.setAttribute('aria-hidden', 'true');
47 disableTabbing(c1);
48 after.push(c1);
49
50 var c2 = originals[i].cloneNode(true);
51 c2.classList.add('is-clone');
52 c2.setAttribute('aria-hidden', 'true');
53 disableTabbing(c2);
54 before.push(c2);
55 }
56
57 for (var j = before.length - 1; j >= 0; j--) {
58 track.insertBefore(before[j], track.firstChild);
59 }
60 for (var k = 0; k < after.length; k++) {
61 track.appendChild(after[k]);
62 }
63
64 didClone = true;
65 window.requestAnimationFrame(function () {
66 measureInfinite(true);
67 syncIndicators();
68 });
69 }
70
71 function measureInfinite(recenter) {
72 if (!infiniteEnabled || !didClone || !baseCount) return;
73 var slides = getSlides();
74 if (!slides || slides.length < baseCount * 3) return;
75 var middleFirst = slides[baseCount];
76 var afterFirst = slides[baseCount * 2];
77 if (!middleFirst || !afterFirst) return;
78
79 var nextStart = middleFirst.offsetLeft;
80 var nextWidth = afterFirst.offsetLeft - middleFirst.offsetLeft;
81 if (!isFinite(nextStart) || !isFinite(nextWidth) || nextWidth <= 0) return;
82
83 if (recenter) {
84 var local = track.scrollLeft - segmentStart;
85 if (segmentWidth > 0) {
86 local = local % segmentWidth;
87 if (local < 0) local += segmentWidth;
88 } else {
89 local = 0;
90 }
91 segmentStart = nextStart;
92 segmentWidth = nextWidth;
93 track.scrollLeft = segmentStart + local;
94 } else {
95 segmentStart = nextStart;
96 segmentWidth = nextWidth;
97 }
98 }
99
100 function normalizeInfiniteScroll() {
101 if (!infiniteEnabled || !didClone) return;
102 if (!segmentWidth || !isFinite(segmentWidth)) {
103 measureInfinite(false);
104 }
105 if (!segmentWidth) return;
106
107 if (track.scrollLeft < (segmentStart - 1) || track.scrollLeft >= (segmentStart + segmentWidth + 1)) {
108 var local = track.scrollLeft - segmentStart;
109 local = local % segmentWidth;
110 if (local < 0) local += segmentWidth;
111 isNormalizing = true;
112 track.scrollLeft = segmentStart + local;
113 isNormalizing = false;
114 }
115 }
116
117 function getSlides() {
118 return track.querySelectorAll('.bkbg-ts-slide');
119 }
120
121 function getCurrentIndex() {
122 var slides = getSlides();
123 if (!slides.length) return 0;
124
125 var trackRect = track.getBoundingClientRect();
126 var currentIndex = 0;
127 for (var i = 0; i < slides.length; i++) {
128 var r = slides[i].getBoundingClientRect();
129 if (r.left >= trackRect.left - 10) {
130 currentIndex = i;
131 break;
132 }
133 }
134 return currentIndex;
135 }
136
137 function scrollToIndex(index) {
138 var slides = getSlides();
139 if (!slides.length) return;
140 var i = Math.max(0, Math.min(slides.length - 1, index));
141 slides[i].scrollIntoView({ behavior: 'smooth', inline: 'start', block: 'nearest' });
142 }
143
144 function scrollToBaseIndex(index) {
145 if (!infiniteEnabled || !didClone || !baseCount) {
146 scrollToIndex(index);
147 return;
148 }
149 var slides = getSlides();
150 if (!slides.length) return;
151 var idx = index % baseCount;
152 if (idx < 0) idx += baseCount;
153 var target = baseCount + idx;
154 if (target < 0) target = 0;
155 if (target > slides.length - 1) target = slides.length - 1;
156 slides[target].scrollIntoView({ behavior: 'smooth', inline: 'start', block: 'nearest' });
157 }
158
159 function syncIndicators() {
160 var slides = getSlides();
161 if (!slides.length) return;
162
163 var idx = getCurrentIndex();
164 var total = slides.length;
165 if (infiniteEnabled && didClone && baseCount) {
166 total = baseCount;
167 idx = idx % baseCount;
168 if (idx < 0) idx += baseCount;
169 }
170
171 if (dots && dots.length) {
172 for (var i = 0; i < dots.length; i++) {
173 if (i === idx) dots[i].classList.add('is-active');
174 else dots[i].classList.remove('is-active');
175 }
176 }
177
178 if (progressBar) {
179 var pct = 0;
180 if (infiniteEnabled && didClone) {
181 if (!segmentWidth) measureInfinite(false);
182 var local = track.scrollLeft - segmentStart;
183 if (segmentWidth > 0) {
184 local = local % segmentWidth;
185 if (local < 0) local += segmentWidth;
186 }
187 var maxLocal = segmentWidth - track.clientWidth;
188 pct = maxLocal > 0 ? (local / maxLocal) * 100 : 0;
189 } else {
190 var max = track.scrollWidth - track.clientWidth;
191 pct = max > 0 ? (track.scrollLeft / max) * 100 : 0;
192 }
193 if (pct < 0) pct = 0;
194 if (pct > 100) pct = 100;
195 progressBar.style.width = pct + '%';
196 }
197
198 if (fractionCurrent) fractionCurrent.textContent = String(idx + 1);
199 if (fractionTotal) fractionTotal.textContent = String(total);
200 }
201
202 function scrollByOne(dir) {
203 var slides = getSlides();
204 if (!slides.length) return;
205
206 var currentIndex = getCurrentIndex();
207 scrollToIndex(currentIndex + dir);
208 }
209
210 var prev = root.querySelector('[data-bkbg-nav="prev"], [data-ts-nav="prev"]');
211 var next = root.querySelector('[data-bkbg-nav="next"], [data-ts-nav="next"]');
212 if (prev) prev.addEventListener('click', function (e) { e.preventDefault(); scrollByOne(-1); });
213 if (next) next.addEventListener('click', function (e) { e.preventDefault(); scrollByOne(1); });
214
215 if (dots && dots.length) {
216 for (var i = 0; i < dots.length; i++) {
217 (function (btn) {
218 btn.addEventListener('click', function (e) {
219 e.preventDefault();
220 var idx = parseInt(btn.getAttribute('data-bkbg-dot') || '0', 10);
221 scrollToBaseIndex(idx);
222 });
223 })(dots[i]);
224 }
225 }
226
227 // Auto-scroll (continuous)
228 var rafId = null;
229 var lastTs = 0;
230 var autoPaused = false;
231 var originalSnap = track.style.scrollSnapType;
232
233 function tick(ts) {
234 if (!autoScrollEnabled) return;
235 if (!lastTs) lastTs = ts;
236 var dt = ts - lastTs;
237 lastTs = ts;
238 if (!document.hidden && !autoPaused) {
239 track.scrollLeft += (autoScrollSpeed * dt) / 1000;
240 normalizeInfiniteScroll();
241 syncIndicators();
242 }
243 rafId = window.requestAnimationFrame(tick);
244 }
245
246 function startAutoScroll() {
247 if (!autoScrollEnabled) return;
248 if (rafId) return;
249 track.style.scrollSnapType = 'none';
250 lastTs = 0;
251 rafId = window.requestAnimationFrame(tick);
252 }
253
254 function stopAutoScroll() {
255 if (rafId) {
256 window.cancelAnimationFrame(rafId);
257 rafId = null;
258 }
259 track.style.scrollSnapType = originalSnap;
260 lastTs = 0;
261 }
262
263 if (autoScrollEnabled) {
264 startAutoScroll();
265 if (autoScrollPauseOnHover) {
266 root.addEventListener('mouseenter', function () { autoPaused = true; });
267 root.addEventListener('mouseleave', function () { autoPaused = false; });
268 }
269 document.addEventListener('visibilitychange', function () {
270 if (document.hidden) stopAutoScroll();
271 else startAutoScroll();
272 });
273 }
274
275 setupInfinite();
276
277 try {
278 var imgs = track.querySelectorAll('img');
279 for (var ii = 0; ii < imgs.length; ii++) {
280 (function (img) {
281 if (!img) return;
282 if (img.complete) return;
283 img.addEventListener('load', function () {
284 window.requestAnimationFrame(function () {
285 measureInfinite(true);
286 syncIndicators();
287 });
288 }, { passive: true });
289 })(imgs[ii]);
290 }
291 } catch (e) { }
292
293 window.addEventListener('resize', function () {
294 window.requestAnimationFrame(function () {
295 measureInfinite(true);
296 syncIndicators();
297 });
298 }, { passive: true });
299
300 syncIndicators();
301 track.addEventListener('scroll', function () {
302 normalizeInfiniteScroll();
303 if (isNormalizing) return;
304 syncIndicators();
305 }, { passive: true });
306 }
307
308 function boot() {
309 var blocks = document.querySelectorAll('.wp-block-blockenberg-testimonials');
310 for (var i = 0; i < blocks.length; i++) init(blocks[i]);
311 }
312
313 if (document.readyState === 'loading') {
314 document.addEventListener('DOMContentLoaded', boot);
315 } else {
316 boot();
317 }
318 })();
319