# king-addons/51.1.49/includes/widgets/Steps_Process_Timeline/script.js

King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder, version 51.1.49. 389 lines.

- Page: https://pluginprobe.com/plugins/king-addons/51.1.49/code/includes/widgets/Steps_Process_Timeline/script.js
- Raw: https://pluginprobe.com/plugins/king-addons/51.1.49/raw/includes/widgets/Steps_Process_Timeline/script.js
- Modified: 2026-01-06T16:52:16+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/king-addons/51.1.49/code/includes/widgets/Steps_Process_Timeline/script.js#L10-L20`.

```javascript
"use strict";
(function ($) {
    const clamp = (value, min, max) => Math.min(Math.max(value, min), max);

    const initStepsTimeline = ($scope) => {
        const root = $scope.find('.king-addons-steps-timeline')[0];
        if (!root) {
            return;
        }

        let options = {};
        try {
            options = JSON.parse(root.dataset.options || '{}');
        } catch (e) {
            options = {};
        }

        const hasFeatures = !!(
            options.activeOnScroll ||
            options.anchorLinking ||
            options.anchorSync ||
            options.sticky ||
            options.reveal
        );

        if (!hasFeatures) {
            return;
        }

        const prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
        const isEditor = window.elementorFrontend && elementorFrontend.isEditMode && elementorFrontend.isEditMode();

        if (isEditor) {
            root.classList.remove('king-addons-steps--sticky');
            options.sticky = false;
        }

        const steps = Array.from(root.querySelectorAll('.king-addons-step'));
        const list = root.querySelector('.king-addons-steps__list');
        const track = root.querySelector('.king-addons-steps__track');
        const line = root.querySelector('.king-addons-steps__line');
        const progress = root.querySelector('.king-addons-steps__progress');

        if (!steps.length || !list || !track || !line || !progress) {
            return;
        }

        const layout = options.layout || 'vertical';
        let activeIndex = -1;
        let ticking = false;

        const isHorizontalLayout = () => window.getComputedStyle(list).display === 'flex';

        const updateLineVisibility = () => {
            const hideLine = options.wrap && isHorizontalLayout();
            const display = hideLine ? 'none' : '';

            line.style.display = display;
            progress.style.display = display;

            return !hideLine;
        };

        const anchorSteps = steps
            .map((step, index) => {
                const anchorId = step.dataset.anchor || '';
                const target = anchorId ? document.getElementById(anchorId) : null;
                return { step, anchorId, target, index };
            })
            .filter((item) => item.anchorId);

        const getScrollTop = () => window.pageYOffset || document.documentElement.scrollTop || 0;

        const getOffsetTop = (element) => {
            const rect = element.getBoundingClientRect();
            return rect.top + getScrollTop();
        };

        const updateLineBounds = () => {
            if (!line || steps.length < 2) {
                return;
            }

            if (!updateLineVisibility()) {
                return;
            }

            const firstMarker = steps[0].querySelector('.king-addons-step__marker');
            const lastMarker = steps[steps.length - 1].querySelector('.king-addons-step__marker');

            if (!firstMarker || !lastMarker) {
                return;
            }

            const trackRect = track.getBoundingClientRect();
            const firstRect = firstMarker.getBoundingClientRect();
            const lastRect = lastMarker.getBoundingClientRect();

            if (isHorizontalLayout()) {
                const top = firstRect.top - trackRect.top + firstRect.height / 2;
                const left = firstRect.left - trackRect.left + firstRect.width / 2;
                const width = Math.max(lastRect.left - firstRect.left, 0);

                line.style.top = `${top}px`;
                line.style.left = `${left}px`;
                line.style.width = `${width}px`;
                line.style.height = '';
            } else {
                const left = firstRect.left - trackRect.left + firstRect.width / 2;
                const top = firstRect.top - trackRect.top + firstRect.height / 2;
                const height = Math.max(lastRect.top - firstRect.top, 0);

                line.style.left = `${left}px`;
                line.style.top = `${top}px`;
                line.style.height = `${height}px`;
                line.style.width = '';
            }
        };

        const getProgressRatio = () => {
            if (!steps.length) {
                return 0;
            }

            const scrollTop = getScrollTop();
            const viewportCenter = scrollTop + window.innerHeight * 0.5;
            let start = getOffsetTop(steps[0]);
            let end = getOffsetTop(steps[steps.length - 1]);

            if (options.progressMode === 'widget') {
                start = getOffsetTop(root);
                end = getOffsetTop(root) + root.offsetHeight;
            }

            if (end <= start) {
                return 0;
            }

            return clamp((viewportCenter - start) / (end - start), 0, 1);
        };

        const updateProgress = () => {
            if (!progress || !line) {
                return;
            }

            if (!updateLineVisibility()) {
                return;
            }

            const ratio = getProgressRatio();
            const lineRect = line.getBoundingClientRect();

            if (isHorizontalLayout()) {
                progress.style.width = `${lineRect.width * ratio}px`;
                progress.style.height = '100%';
            } else {
                progress.style.height = `${lineRect.height * ratio}px`;
                progress.style.width = '100%';
            }
        };

        const getClosestEnabledIndex = (index) => {
            if (!steps.length) {
                return 0;
            }

            for (let i = index; i < steps.length; i += 1) {
                if (!steps[i].classList.contains('is-disabled')) {
                    return i;
                }
            }

            for (let i = index - 1; i >= 0; i -= 1) {
                if (!steps[i].classList.contains('is-disabled')) {
                    return i;
                }
            }

            return index;
        };

        const setActive = (index) => {
            const safeIndex = clamp(index, 0, steps.length - 1);
            const nextIndex = getClosestEnabledIndex(safeIndex);

            if (nextIndex === activeIndex) {
                return;
            }

            activeIndex = nextIndex;

            steps.forEach((step, idx) => {
                step.classList.toggle('is-active', idx === activeIndex);
                step.classList.toggle('is-complete', idx < activeIndex);

                if (idx === activeIndex) {
                    step.setAttribute('aria-current', 'step');
                } else {
                    step.removeAttribute('aria-current');
                }

                if (options.reveal && options.revealTrigger === 'active') {
                    if (idx <= activeIndex) {
                        step.classList.add('is-revealed');
                    }
                }
            });
        };

        const getActiveIndex = () => {
            const targetList = options.anchorSync && anchorSteps.length
                ? anchorSteps.filter((item) => item.target)
                : steps.map((step, index) => ({ target: step, index }));

            const scrollTop = getScrollTop();
            const threshold = scrollTop + window.innerHeight * 0.35 + (options.anchorOffset || 0);
            let active = 0;

            targetList.forEach((item, idx) => {
                const target = item.target || item;
                const stepIndex = item.index !== undefined ? item.index : idx;

                if (!target) {
                    return;
                }

                const top = getOffsetTop(target);
                if (top <= threshold) {
                    active = stepIndex;
                }
            });

            return active;
        };

        const handleScroll = () => {
            if (ticking) {
                return;
            }

            ticking = true;

            window.requestAnimationFrame(() => {
                if (options.activeOnScroll || options.anchorSync) {
                    setActive(getActiveIndex());
                }

                updateProgress();
                ticking = false;
            });
        };

        const scrollToTarget = (target, anchorId) => {
            if (!target) {
                return;
            }

            if (isEditor) {
                return;
            }

            const offset = options.anchorOffset || 0;
            const targetTop = getOffsetTop(target) - offset;
            const behavior = prefersReduced ? 'auto' : 'smooth';

            window.scrollTo({
                top: targetTop,
                behavior,
            });

            if (anchorId) {
                try {
                    window.history.pushState(null, '', `#${anchorId}`);
                } catch (e) {
                    window.location.hash = anchorId;
                }
            }
        };

        if (options.anchorLinking && anchorSteps.length) {
            anchorSteps.forEach((item) => {
                item.step.addEventListener('click', (event) => {
                    if (item.step.classList.contains('is-disabled')) {
                        return;
                    }

                    if (event.target.closest('a')) {
                        return;
                    }

                    if (!item.target) {
                        return;
                    }

                    event.preventDefault();
                    setActive(item.index);
                    scrollToTarget(item.target, item.anchorId);
                });

                item.step.addEventListener('keydown', (event) => {
                    if (event.key !== 'Enter' && event.key !== ' ') {
                        return;
                    }

                    if (item.step.classList.contains('is-disabled')) {
                        return;
                    }

                    if (!item.target) {
                        return;
                    }

                    event.preventDefault();
                    setActive(item.index);
                    scrollToTarget(item.target, item.anchorId);
                });
            });
        }

        if (options.reveal) {
            const canRevealOnActive = options.revealTrigger === 'active' && (options.activeOnScroll || options.anchorSync);

            root.classList.add(`king-addons-steps-reveal-${options.revealType || 'fade-up'}`);
            steps.forEach((step, idx) => {
                step.classList.add('is-reveal');
                if (options.revealStagger && !prefersReduced) {
                    step.style.transitionDelay = `${idx * options.revealStagger}ms`;
                }
            });

            if (prefersReduced) {
                steps.forEach((step) => step.classList.add('is-revealed'));
            } else if (!canRevealOnActive && options.revealTrigger === 'viewport' && 'IntersectionObserver' in window) {
                const revealObserver = new IntersectionObserver((entries) => {
                    entries.forEach((entry) => {
                        if (entry.isIntersecting) {
                            entry.target.classList.add('is-revealed');
                            revealObserver.unobserve(entry.target);
                        }
                    });
                }, { threshold: 0.2 });

                steps.forEach((step) => revealObserver.observe(step));
            } else if (!canRevealOnActive && options.revealTrigger === 'viewport') {
                steps.forEach((step) => step.classList.add('is-revealed'));
            } else if (!canRevealOnActive) {
                steps.forEach((step) => step.classList.add('is-revealed'));
            }
        }

        if (options.deepLink && options.anchorLinking && window.location.hash) {
            const hash = window.location.hash.replace('#', '');
            const match = anchorSteps.find((item) => item.anchorId === hash);
            if (match && match.target) {
                setTimeout(() => {
                    setActive(match.index);
                    scrollToTarget(match.target, match.anchorId);
                }, 150);
            }
        }

        updateLineBounds();
        updateProgress();

        if (options.activeOnScroll || options.anchorSync) {
            setActive(getActiveIndex());
        } else {
            setActive(0);
        }

        if (options.activeOnScroll || options.anchorSync || options.sticky) {
            window.addEventListener('scroll', handleScroll, { passive: true });
        }

        window.addEventListener('resize', () => {
            updateLineBounds();
            updateProgress();
        });
    };

    $(window).on('elementor/frontend/init', () => {
        elementorFrontend.hooks.addAction(
            'frontend/element_ready/king-addons-steps-process-timeline.default',
            initStepsTimeline
        );
    });
})(jQuery);

```
