PageSourceSearch

https://awanutrition.com/wp-content/plugins/woolentor-addons/assets/js/woolentor-widgets-active.js?ver=3.5.2

js awanutrition.com collected 2026-10-05 18:19:21 UTC 46,689 bytes, 1,138 lines download raw bytes

1;(function($){
2"use strict";
3
4    /**
5     * Senitize HTML
6     */
7    var woolentorSanitizeHTML = function (str) {
8        if( str ){
9            return str.replace(/[&<>"']/g, function (c) {
10                switch (c) {
11                    case '&': return '&amp;';
12                    case '<': return '&lt;';
13                    case '>': return '&gt;';
14                    case '"': return '&quot;';
15                    case "'": return '&#39;';
16                    default: return c;
17                }
18            });
19        }else{
20            return '';
21        }
22    }
23
24    /**
25     * Sanitize Object
26     */
27    var woolentorSanitizeObject = function (inputObj) {
28        const sanitizedObj = {};
29    
30        for (let key in inputObj) {
31            if (inputObj.hasOwnProperty(key)) {
32                let value = inputObj[key];
33    
34                // Sanitize based on the value type
35                if (typeof value === 'string') {
36                    // Sanitize strings to prevent injection
37                    sanitizedObj[key] = woolentorSanitizeHTML(value);
38                } else if (typeof value === 'number') {
39                    // Ensure numbers are valid (you could also set limits if needed)
40                    sanitizedObj[key] = Number.isFinite(value) ? value : 0;
41                } else if (typeof value === 'boolean') {
42                    // Keep boolean values as they are
43                    sanitizedObj[key] = value;
44                } else {
45                    // Handle other types if needed (e.g., arrays, objects)
46                    sanitizedObj[key] = value;
47                }
48            }
49        }
50    
51        return sanitizedObj;
52    }
53
54   /* 
55    * Product Slider 
56    */
57    var WidgetProductSliderHandler = function ($scope, $) {
58
59        var slider_elem = $scope.find('.product-slider').eq(0);
60
61        if (slider_elem.length > 0) {
62
63            slider_elem[0].style.display='block';
64
65            var settings = woolentorSanitizeObject(slider_elem.data('settings'));
66            var arrows = settings['arrows'];
67            var dots = settings['dots'];
68            var autoplay = settings['autoplay'];
69            var infinite = settings.hasOwnProperty('infinite') ? settings['infinite'] : true;
70            var rtl = settings['rtl'];
71            var autoplay_speed = parseInt(settings['autoplay_speed']) || 3000;
72            var animation_speed = parseInt(settings['animation_speed']) || 300;
73            var fade = settings['fade'];
74            var pause_on_hover = settings['pause_on_hover'];
75            var display_columns = parseInt(settings['product_items']) || 4;
76            var scroll_columns = parseInt(settings['scroll_columns']) || 4;
77            var tablet_width = parseInt(settings['tablet_width']) || 800;
78            var tablet_display_columns = parseInt(settings['tablet_display_columns']) || 2;
79            var tablet_scroll_columns = parseInt(settings['tablet_scroll_columns']) || 2;
80            var mobile_width = parseInt(settings['mobile_width']) || 480;
81            var mobile_display_columns = parseInt(settings['mobile_display_columns']) || 1;
82            var mobile_scroll_columns = parseInt(settings['mobile_scroll_columns']) || 1;
83
84            slider_elem.not('.slick-initialized').slick({
85                arrows: arrows,
86                prevArrow: '<button type="button" class="slick-prev" aria-label="Previous slide"><i class="fa fa-angle-left" aria-hidden="true"></i></button>',
87                nextArrow: '<button type="button" class="slick-next" aria-label="Next slide"><i class="fa fa-angle-right" aria-hidden="true"></i></button>',
88                dots: dots,
89                infinite: infinite,
90                autoplay: autoplay,
91                autoplaySpeed: autoplay_speed,
92                speed: animation_speed,
93                fade: false,
94                pauseOnHover: pause_on_hover,
95                slidesToShow: display_columns,
96                slidesToScroll: scroll_columns,
97                rtl: rtl,
98                responsive: [
99                    {
100                        breakpoint: tablet_width,
101                        settings: {
102                            slidesToShow: tablet_display_columns,
103                            slidesToScroll: tablet_scroll_columns
104                        }
105                    },
106                    {
107                        breakpoint: mobile_width,
108                        settings: {
109                            slidesToShow: mobile_display_columns,
110                            slidesToScroll: mobile_scroll_columns
111                        }
112                    }
113                ]
114            });
115
116            // A11y fix: Slick adds role="listbox" to .slick-track which is semantically
117            // incorrect for a carousel and triggers a Lighthouse accessibility failure.
118            slider_elem.find('.slick-track').removeAttr('role');
119        };
120    };
121
122    /*
123    * Custom Tab
124    */
125    function woolentor_tabs( $tabmenus, $tabpane ){
126        $tabmenus.on('click', 'a', function(e){
127            e.preventDefault();
128            var $this = $(this),
129                $target = $this.attr('href');
130            $this.addClass('htactive').parent().siblings().children('a').removeClass('htactive');
131            $( $tabpane + $target ).addClass('htactive').siblings().removeClass('htactive');
132
133            // slick refresh
134            if( $('.slick-slider').length > 0 ){
135                var $id = $this.attr('href');
136                $( $id ).find('.slick-slider').slick('refresh');
137            }
138
139        });
140    }
141
142    /* 
143    * Universal product 
144    */
145    function productImageThumbnailsSlider( $slider ){
146        $slider.slick({
147            dots: true,
148            arrows: true,
149            prevArrow: '<button type="button" class="slick-prev" aria-label="Previous image"><i class="sli sli-arrow-left" aria-hidden="true"></i></button>',
150            nextArrow: '<button type="button" class="slick-next" aria-label="Next image"><i class="sli sli-arrow-right" aria-hidden="true"></i></button>',
151        });
152
153        // A11y fix: Slick adds role="listbox" to .slick-track which is semantically
154        // incorrect for a carousel and triggers a Lighthouse accessibility failure.
155        $slider.find('.slick-track').removeAttr('role');
156    }
157    if( $(".ht-product-image-slider").length > 0 ) {
158        productImageThumbnailsSlider( $(".ht-product-image-slider") );
159    }
160
161    var WidgetThumbnaisImagesHandler = function thumbnailsimagescontroller(){
162        woolentor_tabs( $(".ht-product-cus-tab-links"), '.ht-product-cus-tab-pane' );
163        woolentor_tabs( $(".ht-tab-menus"), '.ht-tab-pane' );
164
165        // Countdown
166        var finalTime, daysTime, hours, minutes, second;
167        $('.ht-product-countdown').each(function() {
168            var $this = $(this), finalDate = $(this).data('countdown');
169            var customlavel = $(this).data('customlavel');
170            $this.countdown(finalDate, function(event) {
171                $this.html(event.strftime('<div class="cd-single"><div class="cd-single-inner"><h3>%D</h3><p>'+woolentorSanitizeHTML(customlavel.daytxt)+'</p></div></div><div class="cd-single"><div class="cd-single-inner"><h3>%H</h3><p>'+woolentorSanitizeHTML(customlavel.hourtxt)+'</p></div></div><div class="cd-single"><div class="cd-single-inner"><h3>%M</h3><p>'+woolentorSanitizeHTML(customlavel.minutestxt)+'</p></div></div><div class="cd-single"><div class="cd-single-inner"><h3>%S</h3><p>'+woolentorSanitizeHTML(customlavel.secondstxt)+'</p></div></div>'));
172            });
173        });
174
175    }
176
177    /*
178    * Tool Tip
179    */
180    function woolentor_tool_tips(element, content) {
181        if ( content == 'html' ) {
182            var tipText = element.text();
183        } else {
184            var tipText = element.attr('title');
185        }
186        element.on('mouseover', function() {
187            if ( $('.woolentor-tip').length == 0 ) {
188                element.before('<span class="woolentor-tip">' + woolentorSanitizeHTML(tipText) + '</span>');
189                $('.woolentor-tip').css('transition', 'all 0.5s ease 0s');
190                $('.woolentor-tip').css('margin-left', 0);
191            }
192        });
193        element.on('mouseleave', function() {
194            $('.woolentor-tip').remove();
195        });
196    }
197
198    /*
199    * Tooltip Render
200    */
201    var WidgetWoolentorTooltipHandler = function woolentor_tool_tip(){
202        $('a.woolentor-compare').each(function() {
203            woolentor_tool_tips( $(this), 'title' );
204        });
205        $('.woolentor-cart a.add_to_cart_button,.woolentor-cart a.added_to_cart,.woolentor-cart a.button').each(function() {
206            woolentor_tool_tips( $(this), 'html');
207        });
208        $('a.woolentor-quick-checkout-button').each(function() {
209            woolentor_tool_tips( $(this), 'title' );
210        });
211    }
212
213    /*
214    * Product Tab
215    */
216    var  WidgetProducttabsHandler = woolentor_tabs( $(".ht-tab-menus"),'.ht-tab-pane' );
217
218    /*
219    * Single Product Video Gallery tab
220    */
221    var WidgetProductVideoGallery = function ( $scope, $ ){
222        var $tabs    = $scope.find('.woolentor-product-video-tabs'),
223            $gallery = $scope.find('.woolentor-product-gallery-video').eq(0);
224
225        woolentor_tabs( $tabs, '.video-cus-tab-pane' );
226
227        if ( ! $gallery.length ) {
228            return;
229        }
230
231        var defaultData = { srcfull: '', src: '', srcset: '' };
232
233        // show_variation fires on the add to cart widget's form, which lives outside this
234        // widget, so the listener has to be delegated from document. Namespacing it per
235        // instance keeps it from stacking when Elementor re-renders the widget.
236        var ns = '.wlvideogallery' + ( $scope.data('id') || '' );
237
238        var currentPane = function(){
239            return $gallery.find('.video-cus-tab-pane.htactive');
240        };
241
242        $(document).off( 'show_variation' + ns ).on( 'show_variation' + ns, '.single_variation_wrap', function ( event, variation ) {
243
244            // Active first tab. Selected by position rather than by #wlvideo-1, the markup
245            // repeats those ids for every widget instance on the page.
246            $gallery.find('.video-cus-tab-pane').removeClass('htactive').eq(0).addClass('htactive');
247            $tabs.find('li').children('a').removeClass('htactive');
248            $tabs.find('li').eq(0).children('a').addClass('htactive');
249
250            var $currentTab   = currentPane(),
251                $currentImage = $currentTab.find('img');
252
253            // Remember the original image so reset_variations can restore it
254            if ( ! defaultData.src && $currentImage.length ) {
255                defaultData.srcfull = $currentImage.attr('src');
256                defaultData.src     = $currentImage.attr('src');
257                defaultData.srcset  = $currentImage.attr('srcset');
258            }
259
260            if ( $currentImage.length === 0 ) {
261                $currentTab.children('.embed-responsive').css({ "display": "none" });
262                $currentTab.prepend('<img class="attachment-woocommerce_single size-woocommerce_single" src="' + variation.image.full_src + '" />');
263                $currentImage = $currentTab.children('img');
264            }
265
266            if ( $currentTab.children('.embed-responsive').length > 0 ) {
267                $currentTab.children('.embed-responsive').css({ "display": "none" });
268                $currentTab.children('img').css({ "display": "block" });
269            }
270
271            if ( $currentImage.length && $.fn.wc_set_variation_attr ) {
272                $currentImage.wc_set_variation_attr( 'src', variation.image.full_src );
273                $currentImage.wc_set_variation_attr( 'srcset', variation.image.srcset );
274                $currentImage.wc_set_variation_attr( 'src', variation.image.src );
275            }
276
277        });
278
279        // Bound once here. The original nested this inside show_variation, which stacked a
280        // fresh handler every time a variation was picked.
281        $(document).off( 'click' + ns ).on( 'click' + ns, '.variations .reset_variations', function(){
282
283            var $currentTab   = currentPane(),
284                $currentImage = $currentTab.children('img');
285
286            if ( $currentTab.children('.embed-responsive').length > 0 ) {
287                $currentTab.children('.embed-responsive').css({ "display": "block" });
288                $currentTab.children('img').css({ "display": "none" });
289            }
290
291            if ( $currentImage.length && $.fn.wc_set_variation_attr ) {
292                $currentImage.wc_set_variation_attr( 'src', defaultData.srcfull );
293                $currentImage.wc_set_variation_attr( 'srcset', defaultData.srcset );
294            }
295
296        });
297
298    }
299
300    /**
301     * WoolentorAccordion
302     */
303    var WoolentorAccordion = function ( $scope, $ ){
304        var accordion_elem = $scope.find('.htwoolentor-faq').eq(0);
305
306        var data_opt = accordion_elem.data('settings');
307
308        if ( accordion_elem.length > 0 ) {
309            var $id = accordion_elem.attr('id');
310            new Accordion('#' + $id, {
311                duration: 500,
312                showItem: data_opt.showitem,
313                elementClass: 'htwoolentor-faq-card',
314                questionClass: 'htwoolentor-faq-head',
315                answerClass: 'htwoolentor-faq-body',
316            });
317        }
318        
319    };
320
321
322    /**
323     * WoolentorProductStock
324     *
325     * show_variation fires on the add to cart widget's form, outside this widget, so the
326     * listener is delegated from document and namespaced per instance to avoid stacking
327     * on re-render. Only the element written to is scoped.
328     */
329    var WoolentorProductStock = function ( $scope, $ ){
330        var $status = $scope.find('.woolentor-variable-product-status').eq(0);
331
332        if ( ! $status.length ) {
333            return;
334        }
335
336        var ns = '.wlproductstock' + ( $scope.data('id') || '' );
337
338        $(document).off( 'show_variation' + ns ).on( 'show_variation' + ns, '.single_variation_wrap', function ( event, variation ) {
339            $status.html( ( variation && variation.availability_html ) ? variation.availability_html : '' );
340        });
341
342        $(document).off( 'click' + ns ).on( 'click' + ns, '.variations .reset_variations', function(){
343            $status.html('');
344        });
345
346    };
347
348
349    /**
350     * WoolentorProductAccordion
351     *
352     * Scoped through $scope, the widget wrapper Elementor passes to element_ready, so no
353     * generated per instance class has to be printed into the markup.
354     */
355    var WoolentorProductAccordion = function ( $scope, $ ){
356        var $accordion = $scope.find('.wl_product-accordion').eq(0);
357
358        if ( ! $accordion.length ) {
359            return;
360        }
361
362        $accordion.find('.wl_product-accordion-body').hide();
363        $accordion.find('.wl_product-accordion-card.active').children('.wl_product-accordion-body').slideDown();
364
365        $accordion.on('click', '.wl_product-accordion-head', function(e) {
366            e.preventDefault();
367
368            var $card = $(this).parent('.wl_product-accordion-card');
369
370            if ( $card.hasClass('active') ) {
371                $card.removeClass('active').children('.wl_product-accordion-body').slide
371Up();
372            } else {
373                $card.addClass('active').children('.wl_product-accordion-body').slideDown();
374                $card.siblings('.wl_product-accordion-card').removeClass('active')
375                     .children('.wl_product-accordion-body').slideUp();
376            }
377        });
378
379    };
380
381
382    /**
383     * WoolentorHorizontalFilter
384     *
385     * Everything is resolved from $scope, so no per instance id has to be interpolated
386     * into the script. The filter url comes from a data attribute and the preview flag
387     * from the localized settings.
388     */
389    var WoolentorHorizontalFilter = function ( $scope, $ ){
390        var $wrap = $scope.find('.woolentor-horizontal-filter-wrap').eq(0);
391
392        if ( ! $wrap.length ) {
393            return;
394        }
395
396        // The pro plugin registers a widget of the same name with its own markup and its own
397        // inline script. data-filter-url is only present on the markup this handler owns, so
398        // without it we must leave the widget alone rather than re-initialise select2 over it.
399        var filter_url = $wrap.attr('data-filter-url');
400
401        if ( typeof filter_url === 'undefined' ) {
402            return;
403        }
404
405        var current_url = filter_url + '?wlfilter=1';
406
407        var i18n = ( typeof woolentor_addons !== 'undefined' && woolentor_addons.i18n ) ? woolentor_addons.i18n : {},
408            selectTxt = i18n.select || 'select',
409            ofTxt     = i18n.of || 'of';
410
411        // Never navigate away while the widget is being edited or previewed.
412        var isPreviewMode = function(){
413            return ( typeof woolentor_addons !== 'undefined' && !! woolentor_addons.is_preview_mode );
414        };
415
416        var goTo = function( url ){
417            if ( url && ! isPreviewMode() ) {
418                window.location = url;
419            }
420        };
421
422        // Filter Toggle
423        $wrap.on('click', '.filter-icon', function(e){
424            e.preventDefault();
425            $wrap.find('.filter-item').slideToggle();
426        });
427
428        if ( $.fn.select2 ) {
429            var $singleDrop = $wrap.find('.woolentor-single-select-drop').eq(0),
430                $multiDrop  = $wrap.find('.woolentor-multiple-select-drop').eq(0);
431
432            // select2 only dereferences dropdownParent when the dropdown opens, so handing it
433            // an empty set fails later with an unhelpful error rather than here.
434            if ( $singleDrop.length ) {
435                $wrap.find('select.woolentor-onchange-single-item').select2({
436                    dropdownParent: $singleDrop,
437                });
438            }
439
440            if ( $multiDrop.length ) {
441                $wrap.find('select.woolentor-onchange-multiple-item').select2({
442                    // closeOnSelect : false,
443                    allowHtml: true,
444                    allowClear: true,
445                    dropdownParent: $multiDrop,
446                });
447            }
448        }
449
450        $wrap.on('change', '.woolentor-filter-single-item select', function (e) {
451            var output = $(this).siblings('span.select2').find('ul');
452            var total = e.currentTarget.length;
453            var count = output.find('li').length - 0;
454            if( count >= 3 ) {
455                output.html("<li>" + count + " " + ofTxt + " " + total + " " + selectTxt + "</li>");
456            }
457        });
458
459        // Filter product
460        $wrap.on('change', '.woolentor-filter-single-item select.woolentor-onchange-single-item', function () {
461            goTo( current_url + $(this).val() );
462            return false;
463        });
464
465        // Price Filter
466        $wrap.on('change', '.woolentor-filter-single-item select.woolentor-price-filter', function(){
467            var selected  = $(this).find('option:selected'),
468                min_price = selected.data('min_price'),
469                max_price = selected.data('max_price'),
470                location  = min_price + max_price;
471
472            if ( location ) {
473                goTo( current_url + location );
474            }
475        });
476
477        // Texanomies Filter
478        var previouslySelected = [];
479        $wrap.on('change', '.woolentor-filter-single-item select.woolentor-onchange-multiple-item', function () {
480
481            var currentlySelected = $(this).val();
482
483            if( currentlySelected != null ){
484
485                if( currentlySelected.length == 0 ){
486                    goTo( current_url );
487                }else{
488                    var newSelections = currentlySelected.filter(function (element) {
489                        return previouslySelected.indexOf(element) == -1;
490                    });
491                    previouslySelected = currentlySelected;
492
493                    var lastSelected;
494                    if (newSelections.length) {
495                        // If there are multiple new selections, we'll take the last in the list
496                        lastSelected = newSelections.reverse()[0];
497                    }
498                    if ( lastSelected ) {
499                        goTo( lastSelected );
500                    }
501                }
502
503            }else{
504                goTo( current_url );
505            }
506
507            return false;
508        });
509
510    };
511
512
513    /**
514     * WoolentorOnePageSlider
515     */
516    var WoolentorOnePageSlider = function ( $scope, $ ){
517
518        var slider_elem = $scope.find('.ht-full-slider-area').eq(0);
519
520        if ( slider_elem.length > 0 ) {
521
522            /* Jarallax active  */
523            $('.ht-parallax-active').jarallax({
524                speed: 0.4,
525            });
526            
527            $('#ht-nav').onePageNav({
528                currentClass: 'current',
529                changeHash: false,
530                scrollSpeed: 750,
531                scrollThreshold: 0.5,
532                filter: '',
533                easing: 'swing',
534            });
535            
536            /*------ Wow Active ----*/
537            new WOW().init();
538
539            /*---------------------
540            Video popup
541            --------------------- */
542            $('.ht-video-popup').magnificPopup({
543                type: 'iframe',
544                mainClass: 'mfp-fade',
545                removalDelay: 160,
546                preloader: false,
547                zoom: {
548                    enabled: true,
549                }
550            });
551    
552        }
553
554    };
555
556    /**
557     * LoadMore Product Ajax Action handeler
558     * @param {String} selectorBtn // LoadMore Button Selector
559     * @param {String} loadMoreWrapper // LoadMore Enable Track Class
560     */
561    var WooLentorLoadMore = function( selectorBtn, loadMoreWrapper ){
562
563        selectorBtn.on('click', function(e) {
564            e.preventDefault();
565    
566            const $button = selectorBtn;
567            const $loader = $button.siblings('.woolentor-ajax-loader');
568            const $grid = $('#' + $button.data('grid-id'));
569            const currentPage = parseInt($button.data('page'));
570            const maxPages = parseInt($button.data('max-pages'));
571            const dataLayout = $grid.attr('data-show-layout');
572    
573            if (currentPage > maxPages) {
574                return;
575            }
576    
577            $button.hide();
578            $loader.show();
579    
580           let settings = loadMoreWrapper.attr( 'data-wl-widget-settings' );
581    
582            // Prepare AJAX data
583            const ajaxData = {
584                action: 'woolentor_load_more_products',
585                nonce: typeof woolentor_addons !== 'undefined' ? woolentor_addons.ajax_nonce : '',
586                page: currentPage,
587                settings: settings,
588                viewlayout: typeof dataLayout === 'undefined' ? '' : dataLayout
589            };
590    
591            // AJAX request to load more products
592            $.ajax({
593                url: typeof woolentor_addons !== 'undefined' ? woolentor_addons.woolentorajaxurl : '',
594                type: 'POST',
595                data: ajaxData,
596                success: function(response) {
597                    if (response.success && response.data.html) {
598
599                        // Append new products
600                        const $newProducts = $(response.data.html);
601                        $grid.append($newProducts);
602                            
603                        // Update page counter
604                        $button.data('page', currentPage+1);
605    
606                        // Show button if more pages available
607                        if (currentPage < maxPages) {
608                            $button.show();
609                        } else {
610                            $button.text($button.data('complete-loadtxt')).prop('disabled', true).show();
611                        }
612                    }
613                    $loader.hide();
614                },
615                error: function(xhr, status, error) {
616                    $loader.hide();
617                    $button.show();
618                    console.log("Status:", status, "Error:", error);
619                }
620            });
621        });
622
623    }
624
625    var WooLentorInfiniteScroll = function(selectorBtn, productLoadWrapper ){
626
627        let isLoading = false;
628        const $loader = selectorBtn.find('.woolentor-ajax-loader');
629        const $grid = $('#' + selectorBtn.data('grid-id'));
630        const paginationArea = productLoadWrapper.find('.woolentor-pagination-infinite');
631
632        function loadMoreOnScroll() {
633            if (isLoading) return;
634
635            // Calculate trigger point based on product grid bottom position
636            const gridOffset = $grid.offset().top;
637            const gridHeight = $grid.outerHeight();
638            const gridBottom = gridOffset + gridHeight;
639            const scrollTop = $(window).scrollTop();
640            const windowHeight = $(window).height();
641            const triggerPoint = gridBottom - windowHeight - 100; // 100px before grid end
642
643            if (scrollTop >= triggerPoint) {
644                const currentPage = parseInt(selectorBtn.data('page'));
645                const maxPages = parseInt(selectorBtn.data('max-pages'));
646
647                if (currentPage > maxPages) {
648                    $(window).off('scroll', loadMoreOnScroll);
649                    return;
650                }
651
652                paginationArea.css('margin-top', '30px');
653                isLoading = true;
654                $loader.show();
655
656                let settings = productLoadWrapper.attr( 'data-wl-widget-settings' );
657                const dataLayout = $grid.attr('data-show-layout');
658
659                // AJAX request to load more products
660                $.ajax({
661                    url: typeof woolentor_addons !== 'undefined' ? woolentor_addons.woolentorajaxurl : '',
662                    type: 'POST',
663                    data: {
664                        action: 'woolentor_load_more_products',
665                        nonce: typeof woolentor_addons !== 'undefined' ? woolentor_addons.ajax_nonce : '',
666                        page: currentPage,
667                        settings: settings,
668                        viewlayout: typeof dataLayout === 'undefined' ? '' : dataLayout
669                    },
670                    success: function(response) {
671                        if (response.success && response.data.html) {
672                            // Append new products
673                            const $newProducts = $(response.data.html);
674                            $grid.append($newProducts);
675
676                            // Update page counter
677                            selectorBtn.data('page', currentPage + 1);
678
679                            // Check if we've reached the last page
680                            if (currentPage > maxPages) {
681                                $(window).off('scroll', loadMoreOnScroll);
682                                selectorBtn.remove();
683                            }
684                        }
685                    },
686                    complete: function() {
687                        $loader.hide();
688                        isLoading = false;
689                        paginationArea.css('margin-top', '0');
690                    },
691                    error: function() {
692                        $loader.hide();
693                        isLoading = false;
694                    }
695                });
696            }
697        }
698
699        // Bind scroll event
700        $(window).on('scroll', loadMoreOnScroll);
701
702    }
703
704    /**
705     * Quantaty Manager
706     */
707    var WooLentorQtnManager = function(){
708        $(document).on('click', '.woolentor-qty-minus', function(e) {
709            e.preventDefault();
710            const $input = $(this).siblings('.woolentor-qty-input');
711            const $qtnSelector = $(this).parent('.woolentor-quantity-selector').siblings('.add_to_cart_button');
712            const currentVal = parseInt($input.val()) || 1;
713            const minVal = parseInt($input.attr('min')) || 1;
714
715            if (currentVal > minVal) {
716                $input.val(currentVal - 1);
717                $qtnSelector.attr('data-quantity', currentVal - 1);
718                $input.trigger('change');
719            }
720        });
721
722        $(document).on('click', '.woolentor-qty-plus', function(e) {
723            e.preventDefault();
724            const $input = $(this).siblings('.woolentor-qty-input');
725            const $qtnSelector = $(this).parent('.woolentor-quantity-selector').siblings('.add_to_cart_button');
726            const currentVal = parseInt($input.val()) || 1;
727            const maxVal = parseInt($input.attr('max')) || 999;
728
729            if (currentVal < maxVal) {
730                $input.val(currentVal + 1);
731                $qtnSelector.attr('data-quantity', currentVal + 1);
732                $input.trigger('change');
733            }
734        });
735    }
736
737    /**
738     * Grid and View Mode Manager
739     */
740    var WooLentorViewModeManager = function($selector, $style = 'modern'){
741        $(document).on('click', '.woolentor-layout-btn', function(e){
742            e.preventDefault();
743
744            const $this = $(this);
745            const layout = $this.data('layout');
746            const $gridContainer = $this.closest('.woolentor-product-grid, .woolentor-filters-enabled').find($selector);
747
748            // Update active button state
749            $this.siblings().removeClass('woolentor-active');
750            $this.addClass('woolentor-active');
751
752            // Update grid container layout classes
753            if ($gridContainer.length > 0) {
754                // Remove existing layout classes from container
755                $gridContainer.removeClass('woolentor-layout-grid woolentor-layout-list');
756
757                // Add new layout class to container
758                $gridContainer.addClass('woolentor-layout-' + layout);
759                $gridContainer.attr('data-show-layout', layout);
760
761                // Update product card classes
762                const $productCards = $gridContainer.find('.woolentor-product-card');
763                $productCards.removeClass('woolentor-grid-card woolentor-list-card');
764
765                if (layout === 'grid') {
766                    if($style === 'editorial'){
767                        $productCards.removeClass('woolentor-editorial-list-card');
768                        $productCards.addClass('woolentor-editorial-grid-card');
769                    }else if($style === 'magazine'){
770                        $productCards.removeClass('woolentor-magazine-list-card');
771                        $productCards.addClass('woolentor-magazine-grid-card');
772                    }else{
773                        $productCards.addClass('woolentor-grid-card');
774                    }
775                } else if (layout === 'list') {
776                    if($style === 'editorial'){
777                        $productCards.removeClass('woolentor-editorial-grid-card');
778                        $productCards.addClass('woolentor-editorial-list-card');
779                    }else if($style === 'magazine'){
780                        $productCards.removeClass('woolentor-magazine-grid-card');
781                        $productCards.addClass('woolentor-magazine-list-card');
782                    }else{
783                        $productCards.addClass('woolentor-list-card');
784                    }
785                }
786            }
787        });
788    }
789
790    /**
791     * Grid Slider
792     *
793     * @param {jQuery} $container Element carrying data-wl-slider="true".
794     */
795    var WooLentorGridSlider = function( $container ){
796
797        if( !$container || !$container.length ){
798            return;
799        }
800
801        var defaults = {
802            arrows         : true,
803            dots           : true,
804            infinite       : true,
805            autoplay       : false,
806            autoplay_speed : 3000,
807            speed          : 600,
808            pause_on_hover : true,
809            items          : 4,
810            scroll         : 1,
811            tablet_width   : 750,
812            tablet_items   : 3,
813            tablet_scroll  : 1,
814            mobile_width   : 480,
815            mobile_items   : 1,
816            mobile_scroll  : 1,
817            rtl            : false
818        };
819
820        var s = $.extend( {}, defaults, $container.data('slider-settings') || {} );
821
822        // Re-rendering inside the Elementor editor would otherwise stack a
823        // second slick instance on the same element.
824        if( $container.hasClass('slick-initialized') ){
825            $container.slick('unslick');
826        }
827
828        $container.slick({
829            slidesToShow   : s.items,
830            slidesToScroll : s.scroll,
831            arrows         : s.arrows,
832            prevArrow      : '<button type="button" class="woolentor-grid-nav woolentor-grid-nav-prev" aria-label="Previous products"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg></button>',
833            nextArrow      : '<button type="button" class="woolentor-grid-nav woolentor-grid-nav-next" aria-label="Next products"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg></button>',
834            dots           : s.dots,
835            dotsClass      : 'woolentor-grid-dots',
836            infinite       : s.infinite,
837            autoplay       : s.autoplay,
838            autoplaySpeed  : s.autoplay_speed,
839            speed          : s.speed,
840            pauseOnHover   : s.pause_on_hover,
841            rtl            : s.rtl,
842            responsive     : [
843                {
844                    breakpoint : s.tablet_width,
845                    settings   : {
846                        slidesToShow   : s.tablet_items,
847                        slidesToScroll : s.tablet_scroll
848                    }
849                },
850                {
851                    breakpoint : s.mobile_width,
852                    settings   : {
853                        slidesToShow   : s.mobile_items,
854                        slidesToScroll : s.mobile_scroll
855                    }
856                }
857            ]
858        });
859
860        // A11y fix: Slick adds role="listbox" to .slick-track which is
861        // semantically incorrect for a carousel and fails a Lighthouse audit.
862        $container.find('.slick-track').removeAttr('role');
863    };
864
865    /**
866     * New Product Grid
867     * @param {*} $scope
868     * @param {*} $
869     */
870    var WoolentorProductGridModern = function ( $scope, $ ){
871        // Selector
872        let loadMoreWrapper = $scope.find('.woolentor-ajax-enabled').eq(0);
873        let loadMoreButton = $scope.find('.woolentor-load-more-btn').eq(0);
874        let infiniteScroll = $scope.find('.woolentor-infinite-scroll').eq(0);
875        let layoutList = $scope.find('.woolentor-layout-list').eq(0);
876
877        // LoadMore Button
878        if (loadMoreButton.length > 0) {
879            WooLentorLoadMore(loadMoreButton, loadMoreWrapper);
880        }
881
882        // Infinite Scroll
883        if (infiniteScroll.length > 0) {
884            WooLentorInfiniteScroll(infiniteScroll, loadMoreWrapper);
885        }
886
887        // Quantity selector - using event delegation to handle dynamically loaded products
888        if(layoutList.length > 0){
889            WooLentorQtnManager();
890        }
891
892        // View Manager
893        WooLentorViewModeManager('.woolentor-product-grid-modern');
894
895        // Slider layout
896        WooLentorGridSlider( $scope.find('[data-wl-slider="true"]').eq(0) );
897
898    }
899
900    /**
901     * New Product Grid - Editorial Style
902     * @param {*} $scope
903     * @param {*} $
904     */
905    var WoolentorProductGridEditorial = function ($scope, $){
906        // Selector
907        let loadMoreWrapper = $scope.find('.woolentor-ajax-enabled').eq(0);
908        let loadMoreButton = $scope.find('.woolentor-load-more-btn').eq(0);
909        let infiniteScroll = $scope.find('.woolentor-infinite-scroll').eq(0);
910
911        // LoadMore Button
912        if (loadMoreButton.length > 0) {
913            WooLentorLoadMore(loadMoreButton, loadMoreWrapper);
914        }
915
916        // Infinite Scroll
917        if (infiniteScroll.length > 0) {
918            WooLentorInfiniteScroll(infiniteScroll, loadMoreWrapper);
919        }
920
921        // View Manager
922        WooLentorViewModeManager('.woolentor-product-grid-editorial','editorial');
923
924        // Slider layout
925        WooLentorGridSlider( $scope.find('[data-wl-slider="true"]').eq(0) );
926
927    }
928
929    /**
930     * New Product Grid - Magazine Style
931     * @param {*} $scope
932     * @param {*} $
933     */
934    var WoolentorProductGridMagazine = function ($scope, $){
935        // Selector
936        let loadMoreWrapper = $scope.find('.woolentor-ajax-enabled').eq(0);
937        let loadMoreButton = $scope.find('.woolentor-load-more-btn').eq(0);
938        let infiniteScroll = $scope.find('.woolentor-infinite-scroll').eq(0);
939
940        // LoadMore Button
941        if (loadMoreButton.length > 0) {
942            WooLentorLoadMore(loadMoreButton, loadMoreWrapper);
943        }
944
945        // Infinite Scroll
946        if (infiniteScroll.length > 0) {
947            WooLentorInfiniteScroll(infiniteScroll, loadMoreWrapper);
948        }
949
950        // View Manager
951        WooLentorViewModeManager('.woolentor-product-grid-magazine','magazine');
952    }
953
954    /**
955     * Add To Cart widget quantity plus/minus.
956     *
957     * Delegated from document so it survives Elementor editor re-renders, and scoped to the
958     * widget class so it cannot double fire alongside the Gutenberg block handler, which
959     * binds the same markup in woolentor-blocks.
960     */
961    var WooLentorAddToCartQuantity = function(){
962        var widgetSelector = '.elementor-widget-wl-product-add-to-cart .wl-addto-cart form.cart ';
963
964        $(document).on( 'click', widgetSelector + 'span.wl-quantity-plus, ' + widgetSelector + 'span.wl-quantity-minus', function(){
965
966            var $this = $( this ),
967                // Grouped products render every row inside its own .wl-quantity-grouped-cal,
968                // so the container tells us which layout we are in without knowing the product type.
969                $grouped = $this.closest( '.wl-quantity-grouped-cal' ),
970                isGrouped = $grouped.length > 0,
971                // :visible matters on variable products, the form can hold more than one qty input.
972                qty = isGrouped ? $grouped.find( '.qty:visible' ) : $this.closest( 'form.cart' ).find( '.qty:visible' ),
973                min_val = isGrouped ? 0 : 1;
974
975            if( ! qty.length ){
976                return;
977            }
978
979            var val  = parseFloat( qty.val() );
980            var max  = parseFloat( qty.attr( 'max' ) );
981            var min  = parseFloat( qty.attr( 'min' ) );
982            var step = parseFloat( qty.attr( 'step' ) );
983
984            if( isNaN( val ) ){
985                val = isGrouped ? 0 : min_val;
986            }
987            if( isNaN( step ) ){
988                step = 1;
989            }
990
991            if ( $this.is( '.wl-quantity-plus' ) ) {
992                if ( max && ( max <= val ) ) {
993                    qty.val( max );
994                } else {
995                    qty.val( val + step );
996                }
997            } else {
998                if ( min && ( min >= val ) ) {
999                    qty.val( min );
1000                } else if ( val > min_val ) {
1001                    qty.val( val - step );
1002                }
1003            }
1004
1005            qty.trigger( 'change' );
1006
1007        });
1008    }
1009
1010    WooLentorAddToCartQuantity();
1011
1012    /**
1013     * Suggest Price form.
1014     *
1015     * Pure event binding, so a delegated handler is enough and it survives Elementor editor
1016     * re-renders. Scoped to the widget class because the Gutenberg suggest-price block emits
1017     * the same .wl-suggest-price markup with its own script, and an unscoped delegate would
1018     * submit that form twice.
1019     */
1020    var WooLentorSuggestPrice = function(){
1021        var root = '.elementor-widget-wl-product-suggest-price .wl-suggest-price';
1022
1023        $(document).on('click', root + ' .wlopen', function(){
1024            var $wrap = $(this).closest('.wl-suggest-price');
1025            $(this).hide();
1026            $wrap.find('.wlclose').show();
1027            $wrap.find('.wlsuggest-form').slideDown('slow');
1028        });
1029
1030        $(document).on('click', root + ' .wlclose', function(){
1031            var $wrap = $(this).closest('.wl-suggest-price');
1032            $(this).hide();
1033            $wrap.find('.wlopen').show();
1034            $wrap.find('.wlsuggest-form').slideUp('slow');
1035        });
1036
1037        $(document).on('submit', root + ' .wlsuggest-form', function(e){
1038            e.preventDefault();
1039
1040            var $form    = $(this),
1041                $wrap    = $form.closest('.wl-suggest-price'),
1042                $submit  = $form.find('.wlsuggest-submit'),
1043                $message = $wrap.find('.wlsendmessage'),
1044                // Read before it is swapped for the loading text, so it can be restored.
1045                submitText  = $submit.val(),
1046                loadingText = $form.data('loading-text') || submitText;
1047
1048            $.ajax({
1049                url: $form.attr('action'),
1050                type: 'POST',
1051                data: $form.serialize(),
1052
1053                beforeSend: function () {
1054                    $message.hide();
1055                    $submit.removeClass('added').addClass('loading').val(loadingText);
1056                },
1057
1058                complete: function () {
1059                    $submit.addClass('added').removeClass('loading').val(submitText);
1060                    $wrap.find('.wlopen').show();
1061                    $wrap.find('.wlclose').hide();
1062                    $form.slideUp('slow');
1063                },
1064
1065                success: function (response) {
1066                    var data = ( response && response.data ) ? response.data : {};
1067
1068                    $message.show().html( data.message );
1069
1070                    // Update form token for subsequent submissions (without page refresh)
1071                    if ( data.new_token ) {
1072                        $form.find('input[name="form_token"]').val( data.new_token );
1073                    }
1074
1075                    // Clear form fields after successful submission
1076                    if ( response && response.success && ! data.error ) {
1077                        $form.find('input[name="wlname"]').val('');
1078                        $form.find('input[name="wlemail"]').val('');
1079                        $form.find('textarea[name="wlmessage"]').val('');
1080                    }
1081                },
1082
1083            });
1084
1085        });
1086    }
1087
1088    WooLentorSuggestPrice();
1089
1090    /*
1091    * Run this code under Elementor.
1092    */
1093    $(window).on('elementor/frontend/init', function () {
1094
1095        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-product-tab.default', WidgetProductSliderHandler);
1096        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-product-tab.default', WidgetProducttabsHandler);
1097
1098        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-universal-product.default', WidgetProductSliderHandler);
1099        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-universal-product.default', WidgetWoolentorTooltipHandler);
1100        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-universal-product.default', WidgetThumbnaisImagesHandler);
1101
1102        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-cross-sell-product-custom.default', WidgetProductSliderHandler);
1103        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-cross-sell-product-custom.default', WidgetWoolentorTooltipHandler);
1104        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-cross-sell-product-custom.default', WidgetThumbnaisImagesHandler);
1105
1106        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-upsell-product-custom.default', WidgetProductSliderHandler);
1107        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-upsell-product-custom.default', WidgetWoolentorTooltipHandler);
1108        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-upsell-product-custom.default', WidgetThumbnaisImagesHandler);
1109
1110        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-related-product-custom.default', WidgetProductSliderHandler);
1111        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-related-product-custom.default', WidgetWoolentorTooltipHandler);
1112        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-related-product-custom.default', WidgetThumbnaisImagesHandler);
1113
1114        elementorFrontend.hooks.addAction( 'frontend/element_ready/wl-product-video-gallery.default', WidgetProductVideoGallery );
1115
1116        elementorFrontend.hooks.addAction( 'frontend/element_ready/wl-brand-logo.default', WidgetProductSliderHandler );
1117        elementorFrontend.hooks.addAction( 'frontend/element_ready/wl-faq.default', WoolentorAccordion );
1118        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-accordion-product.default', WoolentorProductAccordion );
1119        elementorFrontend.hooks.addAction( 'frontend/element_ready/wl-single-product-stock.default', WoolentorProductStock );
1120        elementorFrontend.hooks.addAction( 'frontend/element_ready/wl-product-horizontal-filter.default', WoolentorHorizontalFilter );
1121
1122        elementorFrontend.hooks.addAction( 'frontend/element_ready/wl-category-grid.default', WidgetProductSliderHandler );
1123        elementorFrontend.hooks.addAction( 'frontend/element_ready/wl-testimonial.default', WidgetProductSliderHandler );
1124        elementorFrontend.hooks.addAction( 'frontend/element_ready/wl-product-grid.default', WidgetProductSliderHandler );
1125        elementorFrontend.hooks.addAction( 'frontend/element_ready/wl-recently-viewed-products.default', WidgetProductSliderHandler );
1126        elementorFrontend.hooks.addAction( 'frontend/element_ready/wl-onepage-slider.default', WoolentorOnePageSlider );
1127
1128        elementorFrontend.hooks.addAction( 'frontend/element_ready/wl-customer-veview.default', WidgetProductSliderHandler );
1129
1130        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-product-grid-modern.default', WoolentorProductGridModern );
1131        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-product-grid-luxury.default', WoolentorProductGridModern );
1132        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-product-grid-editorial.default', WoolentorProductGridEditorial );
1133        elementorFrontend.hooks.addAction( 'frontend/element_ready/woolentor-product-grid-magazine.default', WoolentorProductGridMagazine );
1134
1135    });
1136
1137
1138})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.