PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.49
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.49
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / widgets / Steps_Process_Timeline / script.js

script.js in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.49, at includes/widgets/Steps_Process_Timeline/script.js

389 lines 12.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 "use strict";
2 (function ($) {
3 const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
4
5 const initStepsTimeline = ($scope) => {
6 const root = $scope.find('.king-addons-steps-timeline')[0];
7 if (!root) {
8 return;
9 }
10
11 let options = {};
12 try {
13 options = JSON.parse(root.dataset.options || '{}');
14 } catch (e) {
15 options = {};
16 }
17
18 const hasFeatures = !!(
19 options.activeOnScroll ||
20 options.anchorLinking ||
21 options.anchorSync ||
22 options.sticky ||
23 options.reveal
24 );
25
26 if (!hasFeatures) {
27 return;
28 }
29
30 const prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
31 const isEditor = window.elementorFrontend && elementorFrontend.isEditMode && elementorFrontend.isEditMode();
32
33 if (isEditor) {
34 root.classList.remove('king-addons-steps--sticky');
35 options.sticky = false;
36 }
37
38 const steps = Array.from(root.querySelectorAll('.king-addons-step'));
39 const list = root.querySelector('.king-addons-steps__list');
40 const track = root.querySelector('.king-addons-steps__track');
41 const line = root.querySelector('.king-addons-steps__line');
42 const progress = root.querySelector('.king-addons-steps__progress');
43
44 if (!steps.length || !list || !track || !line || !progress) {
45 return;
46 }
47
48 const layout = options.layout || 'vertical';
49 let activeIndex = -1;
50 let ticking = false;
51
52 const isHorizontalLayout = () => window.getComputedStyle(list).display === 'flex';
53
54 const updateLineVisibility = () => {
55 const hideLine = options.wrap && isHorizontalLayout();
56 const display = hideLine ? 'none' : '';
57
58 line.style.display = display;
59 progress.style.display = display;
60
61 return !hideLine;
62 };
63
64 const anchorSteps = steps
65 .map((step, index) => {
66 const anchorId = step.dataset.anchor || '';
67 const target = anchorId ? document.getElementById(anchorId) : null;
68 return { step, anchorId, target, index };
69 })
70 .filter((item) => item.anchorId);
71
72 const getScrollTop = () => window.pageYOffset || document.documentElement.scrollTop || 0;
73
74 const getOffsetTop = (element) => {
75 const rect = element.getBoundingClientRect();
76 return rect.top + getScrollTop();
77 };
78
79 const updateLineBounds = () => {
80 if (!line || steps.length < 2) {
81 return;
82 }
83
84 if (!updateLineVisibility()) {
85 return;
86 }
87
88 const firstMarker = steps[0].querySelector('.king-addons-step__marker');
89 const lastMarker = steps[steps.length - 1].querySelector('.king-addons-step__marker');
90
91 if (!firstMarker || !lastMarker) {
92 return;
93 }
94
95 const trackRect = track.getBoundingClientRect();
96 const firstRect = firstMarker.getBoundingClientRect();
97 const lastRect = lastMarker.getBoundingClientRect();
98
99 if (isHorizontalLayout()) {
100 const top = firstRect.top - trackRect.top + firstRect.height / 2;
101 const left = firstRect.left - trackRect.left + firstRect.width / 2;
102 const width = Math.max(lastRect.left - firstRect.left, 0);
103
104 line.style.top = `${top}px`;
105 line.style.left = `${left}px`;
106 line.style.width = `${width}px`;
107 line.style.height = '';
108 } else {
109 const left = firstRect.left - trackRect.left + firstRect.width / 2;
110 const top = firstRect.top - trackRect.top + firstRect.height / 2;
111 const height = Math.max(lastRect.top - firstRect.top, 0);
112
113 line.style.left = `${left}px`;
114 line.style.top = `${top}px`;
115 line.style.height = `${height}px`;
116 line.style.width = '';
117 }
118 };
119
120 const getProgressRatio = () => {
121 if (!steps.length) {
122 return 0;
123 }
124
125 const scrollTop = getScrollTop();
126 const viewportCenter = scrollTop + window.innerHeight * 0.5;
127 let start = getOffsetTop(steps[0]);
128 let end = getOffsetTop(steps[steps.length - 1]);
129
130 if (options.progressMode === 'widget') {
131 start = getOffsetTop(root);
132 end = getOffsetTop(root) + root.offsetHeight;
133 }
134
135 if (end <= start) {
136 return 0;
137 }
138
139 return clamp((viewportCenter - start) / (end - start), 0, 1);
140 };
141
142 const updateProgress = () => {
143 if (!progress || !line) {
144 return;
145 }
146
147 if (!updateLineVisibility()) {
148 return;
149 }
150
151 const ratio = getProgressRatio();
152 const lineRect = line.getBoundingClientRect();
153
154 if (isHorizontalLayout()) {
155 progress.style.width = `${lineRect.width * ratio}px`;
156 progress.style.height = '100%';
157 } else {
158 progress.style.height = `${lineRect.height * ratio}px`;
159 progress.style.width = '100%';
160 }
161 };
162
163 const getClosestEnabledIndex = (index) => {
164 if (!steps.length) {
165 return 0;
166 }
167
168 for (let i = index; i < steps.length; i += 1) {
169 if (!steps[i].classList.contains('is-disabled')) {
170 return i;
171 }
172 }
173
174 for (let i = index - 1; i >= 0; i -= 1) {
175 if (!steps[i].classList.contains('is-disabled')) {
176 return i;
177 }
178 }
179
180 return index;
181 };
182
183 const setActive = (index) => {
184 const safeIndex = clamp(index, 0, steps.length - 1);
185 const nextIndex = getClosestEnabledIndex(safeIndex);
186
187 if (nextIndex === activeIndex) {
188 return;
189 }
190
191 activeIndex = nextIndex;
192
193 steps.forEach((step, idx) => {
194 step.classList.toggle('is-active', idx === activeIndex);
195 step.classList.toggle('is-complete', idx < activeIndex);
196
197 if (idx === activeIndex) {
198 step.setAttribute('aria-current', 'step');
199 } else {
200 step.removeAttribute('aria-current');
201 }
202
203 if (options.reveal && options.revealTrigger === 'active') {
204 if (idx <= activeIndex) {
205 step.classList.add('is-revealed');
206 }
207 }
208 });
209 };
210
211 const getActiveIndex = () => {
212 const targetList = options.anchorSync && anchorSteps.length
213 ? anchorSteps.filter((item) => item.target)
214 : steps.map((step, index) => ({ target: step, index }));
215
216 const scrollTop = getScrollTop();
217 const threshold = scrollTop + window.innerHeight * 0.35 + (options.anchorOffset || 0);
218 let active = 0;
219
220 targetList.forEach((item, idx) => {
221 const target = item.target || item;
222 const stepIndex = item.index !== undefined ? item.index : idx;
223
224 if (!target) {
225 return;
226 }
227
228 const top = getOffsetTop(target);
229 if (top <= threshold) {
230 active = stepIndex;
231 }
232 });
233
234 return active;
235 };
236
237 const handleScroll = () => {
238 if (ticking) {
239 return;
240 }
241
242 ticking = true;
243
244 window.requestAnimationFrame(() => {
245 if (options.activeOnScroll || options.anchorSync) {
246 setActive(getActiveIndex());
247 }
248
249 updateProgress();
250 ticking = false;
251 });
252 };
253
254 const scrollToTarget = (target, anchorId) => {
255 if (!target) {
256 return;
257 }
258
259 if (isEditor) {
260 return;
261 }
262
263 const offset = options.anchorOffset || 0;
264 const targetTop = getOffsetTop(target) - offset;
265 const behavior = prefersReduced ? 'auto' : 'smooth';
266
267 window.scrollTo({
268 top: targetTop,
269 behavior,
270 });
271
272 if (anchorId) {
273 try {
274 window.history.pushState(null, '', `#${anchorId}`);
275 } catch (e) {
276 window.location.hash = anchorId;
277 }
278 }
279 };
280
281 if (options.anchorLinking && anchorSteps.length) {
282 anchorSteps.forEach((item) => {
283 item.step.addEventListener('click', (event) => {
284 if (item.step.classList.contains('is-disabled')) {
285 return;
286 }
287
288 if (event.target.closest('a')) {
289 return;
290 }
291
292 if (!item.target) {
293 return;
294 }
295
296 event.preventDefault();
297 setActive(item.index);
298 scrollToTarget(item.target, item.anchorId);
299 });
300
301 item.step.addEventListener('keydown', (event) => {
302 if (event.key !== 'Enter' && event.key !== ' ') {
303 return;
304 }
305
306 if (item.step.classList.contains('is-disabled')) {
307 return;
308 }
309
310 if (!item.target) {
311 return;
312 }
313
314 event.preventDefault();
315 setActive(item.index);
316 scrollToTarget(item.target, item.anchorId);
317 });
318 });
319 }
320
321 if (options.reveal) {
322 const canRevealOnActive = options.revealTrigger === 'active' && (options.activeOnScroll || options.anchorSync);
323
324 root.classList.add(`king-addons-steps-reveal-${options.revealType || 'fade-up'}`);
325 steps.forEach((step, idx) => {
326 step.classList.add('is-reveal');
327 if (options.revealStagger && !prefersReduced) {
328 step.style.transitionDelay = `${idx * options.revealStagger}ms`;
329 }
330 });
331
332 if (prefersReduced) {
333 steps.forEach((step) => step.classList.add('is-revealed'));
334 } else if (!canRevealOnActive && options.revealTrigger === 'viewport' && 'IntersectionObserver' in window) {
335 const revealObserver = new IntersectionObserver((entries) => {
336 entries.forEach((entry) => {
337 if (entry.isIntersecting) {
338 entry.target.classList.add('is-revealed');
339 revealObserver.unobserve(entry.target);
340 }
341 });
342 }, { threshold: 0.2 });
343
344 steps.forEach((step) => revealObserver.observe(step));
345 } else if (!canRevealOnActive && options.revealTrigger === 'viewport') {
346 steps.forEach((step) => step.classList.add('is-revealed'));
347 } else if (!canRevealOnActive) {
348 steps.forEach((step) => step.classList.add('is-revealed'));
349 }
350 }
351
352 if (options.deepLink && options.anchorLinking && window.location.hash) {
353 const hash = window.location.hash.replace('#', '');
354 const match = anchorSteps.find((item) => item.anchorId === hash);
355 if (match && match.target) {
356 setTimeout(() => {
357 setActive(match.index);
358 scrollToTarget(match.target, match.anchorId);
359 }, 150);
360 }
361 }
362
363 updateLineBounds();
364 updateProgress();
365
366 if (options.activeOnScroll || options.anchorSync) {
367 setActive(getActiveIndex());
368 } else {
369 setActive(0);
370 }
371
372 if (options.activeOnScroll || options.anchorSync || options.sticky) {
373 window.addEventListener('scroll', handleScroll, { passive: true });
374 }
375
376 window.addEventListener('resize', () => {
377 updateLineBounds();
378 updateProgress();
379 });
380 };
381
382 $(window).on('elementor/frontend/init', () => {
383 elementorFrontend.hooks.addAction(
384 'frontend/element_ready/king-addons-steps-process-timeline.default',
385 initStepsTimeline
386 );
387 });
388 })(jQuery);
389