PageSourceSearch

https://gastrogirl.com/wp-content/plugins/swatchly/assets/js/frontend.js?ver=1.4.16

js gastrogirl.com collected 2026-10-02 15:02:16 UTC 37,658 bytes, 1,048 lines download raw bytes

1/**
2 * Swatchly Frontend JS
3 */
4;( function ( $ ) {
5	"use strict";
6
7	if ( typeof swatchly_params === 'undefined' ) {
8		return false;
9	}
10
11	/* Builder Compatibility Utilities
12	======================================================= */
13	var builderCompat = {
14		isOxygen: function() {
15			return Boolean(Number(swatchly_params.is_oxygen_builder));
16		},
17
18		findProductContainer: function($element) {
19			// Try standard .product first
20			var $container = $element.closest('.product');
21			if ($container.length) {
22				return $container;
23			}
24
25			// Oxygen Builder: look for ct-div-block with product-related class
26			if (this.isOxygen()) {
27				$container = $element.closest('.ct-div-block[class*="roduct"]');
28				if ($container.length) {
29					return $container;
30				}
31				$container = $element.closest('.ct-div-block');
32				if ($container.length) {
33					return $container;
34				}
35			}
36
37			// Final fallback
38			return $element.closest('.product');
39		}
40	};
41
42	/* Tooltip JS
43	======================================================= */
44	function addTooltip(){
45		var image_src = $(this).data('tooltip_image'),
46			text = $(this).data('tooltip_text');
47
48		if( !image_src && !text ){
49			return;
50		}
51
52		var $tooltip = $('<div class="swatchly-tooltip"></div>');
53
54		if(text){
55			$tooltip.append( $('<span class="swatchly-tooltip-text"></span>').text( text ) );
56		}
57
58		if(image_src){
59			$tooltip.append( $('<img />').attr( 'src', image_src ) );
60		}
61
62		$(this).append($tooltip);
63	}
64
65	function removeTooltip(e){
66		$('.swatchly-tooltip').remove();
67	}
68
69	$(document).ready(function() {
70		$(document).on( 'mouseover', 'div.swatchly-swatch,a.swatchly-swatch', addTooltip );
71		$(document).on('mouseleave', 'div.swatchly-swatch,a.swatchly-swatch', removeTooltip);
72	});
73	
74	/* For both Single & Product loop
75	======================================================= */
76	$(document).ready(function(){
77		$('.swatchly-hide-this-variation-row').closest('tr').addClass('swatchly_d_none');
78	});
79
80	/* Product Loop JS
81	======================================================= */
82	var product_loop = {
83	    /* 
84		 * Some thems make the full product loop item linked to the product details. 
85		 * Preventing the user from clicking on the product loop item so the swatches will be clickable.
86		 */
87		prevent_click: function(){
88			$('.swatchly_loop_variation_form').on( 'click', function(e){
89				if( $(e.target).is('.swatchly-more-button') || $(e.target).closest('.swatchly-more-button').length ){
90					return;
91				}
92				
93				e.preventDefault();
94				e.stopPropagation();
95				e.stopImmediatePropagation();
96			});
97		},
98		init_variation_form: function(){
99			var enable_swatches = Boolean(Number(swatchly_params.enable_swatches));
100
101		    if(enable_swatches){
102		        $( '.swatchly_loop_variation_form' ).swatchly_loop_variation_form();
103		    }
104		}
105	}
106
107	$(document).ready(function(){
108		$('.swatchly_loop_variation_form').addClass('swatchly_loaded_on_ready');
109		product_loop.prevent_click();
110	});
111
112	// Reset the changes
113	$.fn.reset = function (event){
114		$(this).find('.swatchly_ajax_add_to_cart').removeClass('alt disabled wc-variation-is-unavailable swatchly_found_variation added');
115
116		if(event === 'click'){
117			var $button_text = $(this).find('.swatchly_ajax_add_to_cart').data('select_options_text')
118			$(this).find('.swatchly_ajax_add_to_cart').text($button_text);
119		}
120
121		// remove veiw cart button
122		$(this).find('.added_to_cart.wc-forward').remove();
123		$(this).remove_out_of_stock();
124
125		// hide reset button
126		$(this).find('.reset_variations').attr('style', 'display: none !important');
127	};
128
129	// Reset to the initial price
130	$.fn.reset_to_default_price = function(){
131		$(this).find('.price, .wc-block-components-product-price').first().removeClass('swatchly_d_none');
132		$(this).find('.swatchly_price').remove();
133	};
134
135	// Don't show the out of stock element
136	$.fn.remove_out_of_stock = function (){
137		if($(this).find('.swatchly_pl.swatchly_out_of_stock')){
138			$(this).find('.swatchly_pl.swatchly_out_of_stock').remove();
139		}
140	}
141
142	// Find the product image selector
143	$.fn.get_product_image_selector = function(){
144		var $product_thumbnail = '',
145			product_thumbnail_selector = swatchly_params.product_thumbnail_selector,
146			$productContainer = builderCompat.findProductContainer($(this));
147
148		// custom selector priority first
149		if(product_thumbnail_selector){
150			$product_thumbnail = $(this).find(product_thumbnail_selector);
151
152			if( !$product_thumbnail.length && $productContainer.find(product_thumbnail_selector).length ){
153				$product_thumbnail = $productContainer.find(product_thumbnail_selector);
154			}
155
156			return $product_thumbnail;
157		}
158
159		// Check if product has slider
160		var has_slider = false;
161		if($(this).find('.gallery-image').length){
162			has_slider = true;
163			return;
164		}
165
166		// look for default wc image selector
167		$product_thumbnail = $(this).find('img.attachment-woocommerce_thumbnail');
168
169		// ocean theme support
170		if($product_thumbnail.length === 0){
171			$product_thumbnail = $(this).find('img.woo-entry-image-main');
172
173			// look for other default seletors
174			if($product_thumbnail.length === 0){
175				$product_thumbnail = $(this).find('img.attachment-woocommerce_thumbnail');
176
177				if($product_thumbnail.length === 0){
178					$product_thumbnail = $(this).find('img.wp-post-image');
179
180					// Oxygen builder support
181					if($product_thumbnail.length === 0 && builderCompat.isOxygen()){
182						$product_thumbnail = $productContainer.find('.ct-image');
183					}
184
185					if($product_thumbnail.length === 0){
186						$product_thumbnail = $productContainer.find('img').first();
187					}
188				}
189			}
190		}
191
192		// Support WL Universal grid image slider
193		if($(this).find('.ht-product-image-slider').length !== 0 ){
194			$product_thumbnail = $(this).find('.slick-slide.ht-slider-first-item img');
195		}
196
197		return $product_thumbnail;
198	};
199
200	// Backup product image properties
201	// Needed when reset variation & show the default image
202	$.fn.backup_product_image = function(){
203		var $product_thumbnail = $(this).get_product_image_selector();
204
205		// If the product thumbail does not exists, do nothing
206		if(!$product_thumbnail){
207			return;   
208		}
209
210		// Clone & set default image's properties
211		var backup_attributes = {
212			"data-backup_alt": $product_thumbnail.attr('alt'),
213			"data-backup_src": $product_thumbnail.attr('src'),
214			"data-backup_width": $product_thumbnail.attr('width'),
215			"data-backup_height": $product_thumbnail.attr('height')
216		}
217
218		if( $product_thumbnail.attr('srcset') ) {
219		    backup_attributes["data-backup_srcset"] = $product_thumbnail.attr( 'srcset' );
220		    backup_attributes["data-backup_sizes"] = $product_thumbnail.attr( 'sizes' );
221		}
222
223		$product_thumbnail.attr(backup_attributes);
224	};
225
226	// Change the product image when variation found
227	$.fn.change_image = function(variation){
228		// image selector
229		var $product_thumbnail = $(this).get_product_image_selector();
230
231		// If the product thumbail does not exists, do nothing
232		if(!$product_thumbnail){
233			return;   
234		}
235
236		var attributes = {
237		    alt: variation.image.alt,
238		    src: variation.image.thumb_src,
239		    width: variation.image.thumb_src_w,
240		    height: variation.image.thumb_src_h
241		};
242
243		if( $product_thumbnail.attr('srcset') ) {
244		    attributes.srcset = variation.image.srcset;
245		    attributes.sizes = variation.image.sizes;
246		}
247
248		// Finally change/update image
249		$product_thumbnail.attr(attributes);
250
251		// Support WL Universal grid image slider
252		if( $(this).find('.ht-product-image-slider').length !== 0 ){
253			$(this).find('.ht-product-image-slider').slick('slickGoTo', 0);
254		}
255	};
256
257	// Reset to the default image when click on "reset" button
258	$.fn.reset_to_default_image = function(){
259		// Image selector
260		var $product_thumbnail = $(this).get_product_image_selector();
261
262		// Get backup attributes before reset
263		var backup_attributes = {
264			alt: $product_thumbnail.attr('data-backup_alt'),
265			src: $product_thumbnail.attr('data-backup_src'),
266			width: $product_thumbnail.attr('data-backup_width'),
267			height: $product_thumbnail.attr('data-backup_height')
268		}
269
270		if( $product_thumbnail.attr('srcset') ) {
271			backup_attributes["srcset"] = $product_thumbnail.attr( 'data-backup_srcset' );
272			backup_attributes["sizes"]  = $product_thumbnail.attr( 'data-backup_sizes' );
273		}
274
275		// Finally reset the image
276		$product_thumbnail.attr(backup_attributes);
277
278		// Support WL Universal grid image slider
279		if( $(this).find('.ht-product-image-slider').length !== 0 ){
280			$(this).find('.ht-product-image-slider').slick('slickGoTo', 0);
281		}
282	};
283
284	// Change add to cart button text
285	$.fn.change_add_to_cart_text = function(){
286		var original_cart_text = $(this).text(),
287			new_cart_text = $(this).data('add_to_cart_text');
288
289		$(this).html($(this).html().replace(original_cart_text, new_cart_text));
290	};
291
292	// Function for each loop variation form
293	$.fn.swatchly_loop_variation_form = function(){
294		// Classic themes use `.price`; block themes' Product Price block uses `.wc-block-components-product-price`.
295		var $price_selector = '.price, .wc-block-components-product-price';
296
297		return this.each( function(){
298			var $el_variation_form = $( this ),
299				$el_product = builderCompat.findProductContainer($el_variation_form),
300				$el_ajax_add_to_cart = $el_product.find('.swatchly_ajax_add_to_cart');
301
302			$el_product.backup_product_image();
303
304			// hide reset button by default
305			$el_product.find('.reset_variations').attr('style', 'display: none !important');
306
307			$el_variation_form.on('found_variation', function(e, variation){
308				$el_product.reset();
309
310				var availability_html = variation.availability_html,
311					is_in_stock = variation.is_in_stock;
312
313				// If out of stcok
314				if(!is_in_stock){
315					$(this).append(`<div class="swatchly_pl swatchly_out_of_stock">${availability_html}</div>`);
316				} else {
317					$el_product.remove_out_of_stock();
318				}
319
320				// show reset buton once a vaiation found
321				$el_product.find('.reset_variations').attr('style', '');
322
323				// Update price
324				if( !$el_product.find('.swatchly_price').length ){
325					if($(variation.price_html).length){
326						$el_product.find($price_selector).addClass('swatchly_d_none').after( $(variation.price_html).addClass('swatchly_price') );
327					}
328				} else {
329					$el_product.find('.swatchly_price').remove();
330					$el_product.find($price_selector).addClass('swatchly_d_none').after($(variation.price_html).addClass('swatchly_price'));
331				}
332
333				// Update cart text
334				if( $el_ajax_add_to_cart.length ){
335					$el_ajax_add_to_cart.change_add_to_cart_text('found_variation');
336				}
337
338				// Update Image
339				$el_product.change_image(variation);
340
341				// For ajax add to cart
342				// Manually generate selected variation attributes
343				var selected_variation = {},
344					variations = $(this).find( 'select[name^=attribute]' );
345				if ( !variations.length) {
346				    variations = $(this).find( '[name^=attribute]:checked' );
347				}
348				if ( !variations.length) {
349				    variations = $(this).find( 'input[name^=attribute]' );
350				}
351
352				variations.each( function() {
353				    var $this_item = $( this ),
354				        attribute_name = $this_item.attr( 'name' ),
355				        attribute_value = $this_item.val(),
356				        index,
357				        attribute_tax_name;
358				        $this_item.removeClass( 'error' );
359				    if ( attribute_value.length === 0 ) {
360				        index = attribute_name.lastIndexOf( '_' );
361				        attribute_tax_name = attribute_name.substring( index + 1 );
362				        $this_item.addClass( 'required error' );
363				    } else {
364				        selected_variation[attribute_name] = attribute_value;
365				    }
366				});
367
368				if($el_ajax_add_to_cart.length){
369					$el_ajax_add_to_cart.addClass('swatchly_found_variation');
370				}
371				$el_ajax_add_to_cart.attr('data-variation_id', variation.variation_id);
372				$el_ajax_add_to_cart.attr('data-variation', JSON.stringify(selected_variation));
373
374				if($el_ajax_add_to_cart.hasClass('added')){
375					$el_ajax_add_to_cart.removeClass('added');
376				}
377
378			}).on('click','.reset_variations', function(e){
379				$el_product.reset('click');
380				$el_product.remove_out_of_stock();
381				$el_product.reset_to_default_price();
382				$el_product.reset_to_default_image();
383
384			});
385		});
386	};
387
388	// Product loop init
389	$(window).on('load', function(){
390	    product_loop.init_variation_form();
391	} );
392
393	/*=====  End of Product Loop JS  ======*/
394	
395
396	/* Single product page JS
397	======================================================= */
398	var single_product = {
399	    init: function(){
400			var enable_swatches           	  = Boolean(Number(swatchly_params.enable_swatches)),
401				sp_override_global            = Boolean(Number(swatchly_params.sp_override_global)),
402				pl_override_global            = Boolean(Number(swatchly_params.pl_override_global)),
403				enable_variation_url          = Boolean(Number(swatchly_params.enable_variation_url)),
404				enable_rv_variations          = Boolean(Number(swatchly_params.enable_rv_variations)),
405				enable_sp_variation_url       = Boolean(Number(swatchly_params.enable_sp_variation_url)),
406				enable_sp_rv_variations       = Boolean(Number(swatchly_params.enable_sp_rv_variations)),
407				enable_pl_variation_url       = Boolean(Number(swatchly_params.enable_pl_variation_url)),
408				enable_pl_rv_variations       = Boolean(Number(swatchly_params.enable_pl_rv_variations)),
409				is_product            		  = Boolean(Number(swatchly_params.is_product)),
410				deselect_on_click             = Boolean(Number(swatchly_params.deselect_on_click)),
411				show_selected_attribute_name  = Boolean(Number(swatchly_params.show_selected_attribute_name)),
412				variation_label_separator     = swatchly_params.variation_label_separator;
413
414			const rv_variations_config = {
415				storage_key: 'swatchly_rv_variations',
416			};
417
418			const loopVariationUrlParams = ({event, value, paramName}) => {
419				const product = builderCompat.findProductContainer($(event.target))[0];
420				if (!product) return;
421
422				// Get the product URL from data attribute (theme-agnostic approach)
423				const variationForm = product.querySelector('.swatchly_loop_variation_form') ||
424					product.querySelector('.variations_form') ||
425					event.target.closest('.swatchly_loop_variation_form');
426				const productUrlFromData = variationForm ? variationForm.getAttribute('data-product_url') : null;
427
428				// Fallback to selector-based approach for backwards compatibility
429				const productLink = product.querySelector('.woocommerce-loop-product__link') ||
430					product.querySelector('.bundled_product_permalink') ||
431					product.querySelector('a[href*="/product/"]') ||
432					product.querySelector('a');
433
434				// Determine the product URL pathname
435				let productUrlPathname = null;
436				if (productUrlFromData) {
437					try {
438						productUrlPathname = new URL(productUrlFromData).pathname;
439					} catch (e) {
440						// Invalid URL from data attribute, will use fallback
441					}
442				}
443				if (!productUrlPathname && productLink) {
444					try {
445						productUrlPathname = new URL(productLink.href).pathname;
446					} catch (e) {
447						// Invalid URL from link
448					}
449				}
450
451				const links = product.querySelectorAll('a');
452				Array.from(links).forEach(link => {
453					if(!$(link)?.attr('href')) return;
454					try {
455						const url = new URL(link.href);
456						// Match links by comparing pathname OR if link contains /product/ (backwards compat)
457						const shouldUpdate = productUrlPathname
458							? (url.pathname === productUrlPathname)
459							: (url.href.indexOf('/product/') !== -1);
460
461						if(shouldUpdate) {
462							if(value) {
463								if(!url.searchParams.get(paramName)) {
464									url.searchParams.append(paramName, value);
465								} else {
466									url.searchParams.set(paramName, value)
467								}
468							} else {
469								url.searchParams.delete(paramName)
470							}
471							link.href = url.toString();
472						}
473					} catch (e) {
474						// Ignore invalid URLs
475					}
476				});
477			},
478			singleVariationUrlParams = ({value, paramName}) => {
479				const url = new URL(location);
480				if(value) {
481					if(!url.searchParams.get(paramName)) {
482						url.searchParams.append(paramName, value);
483					} else {
484						url.searchParams.set(paramName, value)
485					}
486				} else {
487					url.searchParams.delete(paramName)
488				}
489				history.pushState({}, '', url.href);
490			};
491
492			/**
493			 * Recently Viewed Variations
494			 * Update selected variations from local storage
495			 */
496			const updateRecentlyViewedVariations = ($el_variation_form) => {
497				const rv_variations = localStorage.getItem(rv_variations_config?.storage_key);
498				if(rv_variations){
499					const parsed_rv_variations = JSON.parse(rv_variations);
500					if(parsed_rv_variations[$el_variation_form.data('product_id')]){
501						const selected_variations = parsed_rv_variations[$el_variation_form.data('product_id')];
502						$el_variation_form.find('select').each(function(){
503							const $el_default_select = $(this);
504							const product_attributes = $el_default_select[0].name;
505							if(selected_variations[product_attributes]){
506								$el_default_select.val(selected_variations[product_attributes]);
507								$el_default_select.change();
508								const swatchly_swatch = $el_default_select.closest('td.value').find('.swatchly-swatch[data-attr_value="' + selected_variations[product_attributes] + '"]');
509								if(swatchly_swatch.length){
510									swatchly_swatch.addClass('swatchly-selected').siblings('div.swatchly-swatch').removeClass('swatchly-selected');
511								}
512							}
513						});
514					}
515				}
516			};
517
518			if( enable_swatches){
519				$.fn.swatchly_variation_form = function(){
520					return this.each( function(){
521						var $el_variation_form = $( this );
522
523						/**
524						 * Recently Viewed Variations
525						 * Update selected variations from local storage
526						 */
527						// Global setting (when overrides are not active)
528						if(enable_rv_variations && !sp_override_global && !pl_override_global){
529							updateRecentlyViewedVariations($el_variation_form);
530						} 
531						// Single product page with override active
532						else if(enable_sp_rv_variations && is_product && sp_override_global){
533							updateRecentlyViewedVariations($el_variation_form);
534						} 
535						// Product listing page with override active
536						else if(enable_pl_rv_variations && !is_product && pl_override_global){
537							updateRecentlyViewedVariations($el_variation_form);
538						}
539
540						// Actions while select a swatch
541						$el_variation_form.on( 'click', 'div.swatchly-swatch', function ( e ) {
542							var $el_swatch = $( this ),
543								$el_default_select = $el_swatch.closest( '.value' ).find( 'select' ),
544								value   = $el_swatch.attr( 'data-attr_value' );
545								
546							if( !deselect_on_click ){
547								// Add selected class & remove siblings selected class
548								$el_swatch.addClass('swatchly-selected').siblings('div.swatchly-swatch').removeClass('swatchly-selected');
549
550								// Show selected variation name
551								if( show_selected_attribute_name ){
552									if($el_swatch.closest('tr').find('.swatchly_selected_variation_name').length){
553										$el_swatch.closest('tr').find('.swatchly_selected_variation_name').text( variation_label_separator + $el_swatch.data('attr_label') );
554									} else {
555										$el_swatch.closest('tr').find('.label label').append( $('<span class="swatchly_selected_variation_name"></span>').text( variation_label_separator + $el_swatch.data('attr_label') ) );
556									}
557								}
558
559							} else {
560
561								if($el_swatch.hasClass('swatchly-selected')){
562
563									// Remove selection
564									$el_swatch.removeClass('swatchly-selected');
565
566									// Change select field value to empty
567									value = '';
568
569									// Remove selected variation name
570									$el_swatch.closest('tr').find('.swatchly_selected_variation_name').text( '' );
571
572								} else {
573
574									// Add selected class & remove siblings selected class
575									$el_swatch.addClass('swatchly-selected').siblings('div.swatchly-swatch').removeClass('swatchly-selected');
576
577									// Show selected variation name
578									if( show_selected_attribute_name ){
579										if($el_swatch.closest('tr').find('.swatchly_selected_variation_name').length){
580											$el_swatch.closest('tr').find('.swatchly_selected_variation_name').text( variation_label_separator + $el_swatch.data('attr_label') );
581										} else {
582											$el_swatch.closest('tr').find('.label label').append( $('<span class="swatchly_selected_variation_name"></span>').text( variation_label_separator + $el_swatch.data('attr_label') ) );
583										}
584									}
585
586								}
587							}
588
589							/**
590							 * Recently Viewed Variations
591							 * Save selected variations in local storage
592							 */
593							if((enable_rv_variations && !sp_override_global && !pl_override_global) ||
594							(enable_sp_rv_variations && is_product && sp_override_global) ||
595							(enable_pl_rv_variations && !is_product && pl_override_global)){
596								const product_attributes = $el_default_select[0].name;
597								const product_id = $el_variation_form.data('product_id');
598								const rv_variations = localStorage.getItem(rv_variations_config?.storage_key);
599								if(rv_variations){
600									const parsed_rv_variations = JSON.parse(rv_variations);
601									if(parsed_rv_variations[product_id]){
602										const selected_variations = parsed_rv_variations[product_id];
603										localStorage.setItem(rv_variations_config?.storage_key, JSON.stringify({
604											...parsed_rv_variations,
605											[product_id]: {
606												...selected_variations,
607												[product_attributes]: value
608											}
609										}));
610									} else {
611										localStorage.setItem(rv_variations_config?.storage_key, JSON.stringify({
612											...parsed_rv_variations,
613											[product_id]: {
614												[product_attributes]: value
615											}
616										}));
617									}
618								} else {
619									localStorage.setItem(rv_variations_config?.storage_key, JSON.stringify({
620										[product_id]: {
621											[product_attributes]: value
622										}
623									}));
624								}
625							}
626
627							$el_default_select.val(value);
628							$el_default_select.change();
629							
630						})
631						.on('change', '.value select', function(e) {
632							const QUICKVIEW_SELECTORS = [
633								'.woosq-popup',
634								'.qqvfw',
635								'.acoqvw_quickview',
636								'.quickviewwoo-product',
637								'.adfy-quick-view-modal-content',
638								'.swal2-content',
639								'.merchant-quick-view-content',
640								'.cawqv-modal',
641								'.xoo-qv-modal',
642								'.woolentor-quickview-modal',
643								'#wcqv_contend',
644								'.yith-quick-view',
645								'.quickswish-modal',
646								'.wd-popup',
647							];
648
649							const element = $(this)[0];
650							const paramName = element.dataset.attribute_name;
651							const value = element.value;
652
653							// Handle product loop variation URL
654							if ((pl_override_global && enable_pl_variation_url) || (!pl_override_global && enable_variation_url)) {
655								const isInProductLoop = e.target.closest('.products') && !e.target.closest('.summary');
656								const isBundleProduct = e.target.closest('.bundled_product');
657								// Fallback: check if inside swatchly_loop_variation_form (works regardless of theme structure)
658								const isInSwatchlyLoopForm = e.target.closest('.swatchly_loop_variation_form') && !e.target.closest('.summary');
659
660								if ((isInProductLoop || isBundleProduct || isInSwatchlyLoopForm) && !e.target.closest(QUICKVIEW_SELECTORS.join(', '))) {
661									loopVariationUrlParams({
662										event: e,
663										value,
664										paramName,
665									});
666								}
667							}
668
669							// Handle single product variation URL
670							if ((sp_override_global && enable_sp_variation_url) || (!sp_override_global && enable_variation_url)) {
671								const isInSingleProduct = is_product && e.target.closest('.summary');
672								if (isInSingleProduct && !e.target.closest([...QUICKVIEW_SELECTORS, '.bundle_form'].join(', '))) {
673									singleVariationUrlParams({
674										value,
675										paramName,
676									});
677								}
678							}
679						})
680						.on( 'woocommerce_update_variation_values', function() {
681							setTimeout( function() {
682
683								// Loop through each variation row
684								$el_variation_form.find( 'tbody tr' ).each( function() {
685									var $tr = $(this),
686									values = [];
687
688									// Set default attribute label
689									if( show_selected_attribute_name && !$tr.find('.swatchly_selected_variation_name').length ){
690										var default_attr_label = $tr.find('.swatchly-type-wrap').attr('data-default_attr_value');
691
692										if(default_attr_label){
693											$tr.find('.label label').append( $('<span class="swatchly_selected_variation_name"></span>').text( variation_label_separator + default_attr_label ) );
694										} else {
695											$tr.find('.label label').append('<span class="swatchly_selected_variation_name"></span>');
696										}
697										
698									}
699
700									var $select = $tr.find('select');
701									var attribute_name = $select.data('attribute_name') || $select.attr('name');
702									var variationData = $el_variation_form.data('product_variations');
703
704									// Collect chosen values of OTHER attributes so we respect multi-attribute constraints
705									var chosenAttributes = {};
706									$el_variation_form.find('.variations select').each(function(){
707										var $s = $(this),
708											name = $s.data('attribute_name') || $s.attr('name'),
709											val = $s.val() || '';
710										if( name && name !== attribute_name ){
711											chosenAttributes[ name ] = val;
712										}
713									});
714
715									// Primary path: consult product_variations JSON for combo validity/purchasability.
716									// Out-of-stock combos stay disabled by default (previous/existing behavior) unless
717									// the merchant opted into "enable_out_of_stock_swatch_selection", in which case
718									// out-of-stock-but-valid combos stay clickable so customers can still click through
719									// to see price + "out of stock" message, matching core WooCommerce.
720									if( Array.isArray(variationData) && variationData.length ){
721										var allowOutOfStockSelection = window.swatchly_params ? Boolean(Number(swatchly_params.enable_out_of_stock_swatch_selection)) : false;
722										$select.find('option').each(function(index, option){
723											if( option.value === '' ){ return; }
724											var value = option.value;
725											var hasAvailable = false;
726											for( var i = 0; i < variationData.length; i++ ){
727												var v = variationData[i];
728												if( !v || !v.attributes ){ continue; }
729												if( !v.is_purchasable ){ continue; }
730												if( !allowOutOfStockSelection && !v.is_in_stock ){ continue; }
731												// Variation's value for THIS attribute ('' means "Any")
732												var vAttrVal = v.attributes[ attribute_name ];
733												if( vAttrVal !== '' && vAttrVal !== value ){ continue; }
734												// Other chosen attributes must be compatible
735												var mismatch = false;
736												for( var otherName in chosenAttributes ){
737													var chosen = chosenAttributes[ otherName ];
738													if( chosen === '' ){ continue; }
739													var otherVal = v.attributes[ otherName ];
740													if( otherVal !== '' && otherVal !== chosen ){ mismatch = true; break; }
741												}
742												if( !mismatch ){ hasAvailable = true; break; }
743											}
744											if( hasAvailable ){
745												values.push( value );
746											}
747										});
748									} else {
749										// Fallback (e.g. AJAX variation mode): use the select's current options,
750										// excluding ones WooCommerce has disabled.
751										$select.find('option:not(:disabled)').each(function(index, option){
752											if( option.value !== '' ){
753												values.push( option.value );
754											}
755										});
756
757										// Secondary fallback: if select has no options (stripped by another plugin),
758										// read from the stored attribute_html reference set.
759										if( values.length === 0 ){
760											var attrHtml = $select.data('attribute_html');
761											if( attrHtml ){
762												$(attrHtml).filter('option').each(function(index, option){
763													if( option.value !== '' ){
764														values.push( option.value );
765													}
766												});
767											}
768										}
769									}
770
771									// If values is STILL empty, don't disable anything -
772									// showing all enabled is better than all disabled
773									if( values.length === 0 ){
774										$tr.find( 'div.swatchly-swatch' ).removeClass('swatchly-disabled');
775									} else {
776										// Disable unavailable swatches
777										$tr.find( 'div.swatchly-swatch' ).each( function() {
778											var $el_swatch = $( this ),
779												value = $el_swatch.attr( 'data-attr_value' );
780
781											if( values.indexOf( value ) == -1 ){
782												$el_swatch.addClass('swatchly-disabled');
783											} else {
784												$el_swatch.removeClass('swatchly-disabled');
785											}
786										});
787									}
788
789								}); // tbody tr each
790							}, 100 ); // timeout
791
792							// Update price for product loop
793							var $price_selector = '.price, .wc-block-components-product-price',
794								$el_product = builderCompat.findProductContainer($el_variation_form);
795							if(!swatchly_params.is_product){
796								if($el_product.find('.swatchly_price').length){
797									$el_product.find($price_selector).removeClass('swatchly_d_none');
798									$el_product.find('.swatchly_price').remove();
799								}
800							}
801						})
802						.on('found_variation', function(e, variation){
803							// some user use single product add to cart button in the product loop
804							// so we need to change the image for the product loop
805							if( !swatchly_params.is_product ){
806								var $product_thumbnail = $(this).get_product_image_selector();
807
808								let $el_product = builderCompat.findProductContainer($(this));
809								$el_product.backup_product_image();
810	
811								$product_thumbnail.attr('src', variation.image.url);
812								$product_thumbnail.attr('srcset', variation.image.srcset);
813							}
814						})
815						.on( 'click', '.reset_variations', function () {
816							const url = new URL(location);
817							$el_variation_form.find( '.value select' ).each(function() {
818								url.searchParams.delete($(this)[0].name);
819							})
820							history.pushState({}, '', url.href);
821							$el_variation_form.find( '.swatchly-selected' ).removeClass( 'swatchly-selected' );
822							$el_variation_form.find( '.swatchly-disabled' ).removeClass( 'swatchly-disabled' );
823							$el_variation_form.find('.swatchly_selected_variation_name').text( '' );
824
825							// some user use single product add to cart button in the product loop
826							// so we need to reset the image for the product loop
827							if( !swatchly_params.is_product ){
828								let $el_product = builderCompat.findProductContainer($(this));
829								$el_product.reset_to_default_image()
830							}
831
832							/**
833							 * Recently Viewed Variations
834							 * reset selected variations from local storage
835							 */
836							if((enable_rv_variations && !sp_override_global && !pl_override_global) ||
837							(enable_sp_rv_variations && is_product && sp_override_global) ||
838							(enable_pl_rv_variations && !is_product && pl_override_global)){
839								const rv_variations = localStorage.getItem(rv_variations_config?.storage_key);
840								if(rv_variations){
841									const parsed_rv_variations = JSON.parse(rv_variations);
842									if(parsed_rv_variations[$el_variation_form.data('product_id')]){
843										delete parsed_rv_variations[$el_variation_form.data('product_id')];
844										localStorage.setItem(rv_variations_config?.storage_key, JSON.stringify(parsed_rv_variations));
845									}
846								}
847							}
848						}); // on click div.swatchly-swatch
849					});
850				}
851
852				const initVariationForms = () => {
853					const variationForm = $('.variations_form:not(.swatchly_initialized):not(.bundled_item_cart_content)');
854					const bundleForm = $('.bundle_form:not(.swatchly_initialized)');
855					if(variationForm.length) {
856						variationForm
857							.addClass('swatchly_variation_form swatchly_initialized')
858							.swatchly_variation_form();
859					}
860					if(bundleForm.length) {
861						bundleForm
862							.addClass('swatchly_variation_form swatchly_initialized')
863							.swatchly_variation_form();
864					}
865				};
866
867				// Do stuffs for each variations form
868				$(document).ready(initVariationForms)
869
870				// All major quick view plugin support
871				const QUICKVIEW_KEYWORDS = [
872					'woosq_quickview',
873					'quickswish_product',
874					'wqv_popup_content',
875					'acoqvw_get_quickview',
876					'qode-quick-view-for-woocommerce',
877					'quickviewwoo',
878					'ca-quick-view',
879					'xoo_qv_ajax',
880					'wcqv_get_product',
881				];
882				$(document).ajaxComplete(function (event, request, settings) {
883					if(QUICKVIEW_KEYWORDS.some(word => settings?.url?.includes(word) || settings?.data?.includes(word))) {
884						initVariationForms();
885					}
886				});
887				const QUICKVIEW_EVENTS = [
888					'addonifyQuickViewModalContentLoaded',
889					'gpls-arcw-quick-view-buy-now-for-woocommerce-quick-view-popup-rendered',
890					'yith_quick_view_loaded',
891					'merchant.quickview.ajax.loaded',
892					'wdQuickViewOpen',
893				];
894				// Handle event-based quick view plugins
895				QUICKVIEW_EVENTS.forEach(eventName => {
896					const target = eventName.startsWith('merchant') ? window : document;
897					$(target).on(eventName, initVariationForms);
898				});
899			}
900		}
901	}
902
903	// Single product init
904	single_product.init();
905	
906
907	/* Third party plugin/theme's compatibility
908	======================================================= */
909
910	/**
911	 * 1. annasta Woocommerce Product Filters
912	 */
913	 $( document ).on( 'awf_after_ajax_products_update', function() {
914		product_loop.prevent_click();
915		product_loop.init_variation_form();
916		single_product.init();
917	});
918
919	/**
920	 * 2. Jet Smart Filters
921	 */
922	 $( document ).on( 'jet-filter-content-rendered', function() {
923		product_loop.prevent_click();
924		product_loop.init_variation_form();
925		single_product.init();
926	});
927
928	/**
929	 * 3. Woolentor Support
930	 */
931	$( document ).on('woolentor_quick_view_rendered', function(){
932		product_loop.prevent_click();
933		product_loop.init_variation_form();
934		single_product.init();
935	});
936
937	/**
938	 * 4. infiniteScroll Support
939	 * It's oly work under document.ready and on 'append.infiniteScroll'
940	 */
941	$(document).ready(function(){
942		$('.products').on('append.infiniteScroll', function(){
943			product_loop.prevent_click();
944			product_loop.init_variation_form();
945			single_product.init();
946		});
947	});
948
949	/**
950	 * 5. YITH infiniteScroll Support
951	 */
952	$(document).on('yith_infs_added_elem', function(){
953		product_loop.prevent_click();
954		product_loop.init_variation_form();
955		single_product.init();
956	});
957
958	/**
959	 * 6. Barn2 WCF Product Filter
960	 */
961	$(window).on('load', function(){
962		if( $('.wcf-filter-form').length && typeof window.wp.hooks.addAction !== 'undefined' ){
963			window.wp.hooks.addAction( 'wcpf.filteringDone', 'namespace', function( config, filteredDOM ){
964				product_loop.prevent_click();
965				product_loop.init_variation_form();
966				single_product.init();
967			} );
968		}
969	});
970
971	/**
972	 * 7. Astra Pro quick view Support
973	 */
974	$( document ).on('ast_quick_view_loader_stop', function(){
975		product_loop.prevent_click();
976		product_loop.init_variation_form();
977		single_product.init();
978	});
979
980	/**
981	 * Themes compatibility
982	 * 1. Airi theme
983	 * It is intended to add compatibility for the airi theme's infinite scroll
984	 */
985	$( document ).on('ajaxComplete', function (event, jqxhr, settings) {
986		$('.swatchly_loop_variation_form:not(.swatchly_loaded_on_ready)').each(function(){
987			product_loop.prevent_click();
988			product_loop.init_variation_form();
989			single_product.init();
990		});
991	});
992
993	/**
994	 * 8. Block-theme Query Loop pagination (WP Interactivity API) + generic fetch-based DOM replacement.
995	 *    jQuery's ajaxComplete does not fire for native fetch(), so new swatches on paginated
996	 *    pages never get initialized. Watch the DOM for added variation/loop forms and reinit.
997	 */
998	if ( typeof window.MutationObserver !== 'undefined' ) {
999		var swatchlyReinitScheduled = false;
1000		var swatchlyReinit = function() {
1001			swatchlyReinitScheduled = false;
1002			if ( ! document.querySelector(
1003				'.variations_form:not(.swatchly_initialized), .swatchly_loop_variation_form:not(.swatchly_loaded_on_ready), .variations_form:not(.swatchly_wc_initialized)'
1004			) ) {
1005				return;
1006			}
1007			try {
1008				// Initialize WooCommerce's own VariationForm on any new .variations_form
1009				// (needed for native-fetch pagination since ajaxComplete does not fire).
1010				if ( typeof window.wc_add_to_cart_variation_params !== 'undefined' ) {
1011					$( '.variations_form:not(.swatchly_wc_initialized)' ).each( function() {
1012						$( this ).addClass( 'swatchly_wc_initialized' ).wc_variation_form();
1013					});
1014				}
1015				product_loop.prevent_click();
1016				product_loop.init_variation_form();
1017				single_product.init();
1018			} catch ( e ) { /* noop */ }
1019		};
1020		var swatchlyScheduleReinit = function() {
1021			if ( swatchlyReinitScheduled ) { return; }
1022			swatchlyReinitScheduled = true;
1023			( window.requestAnimationFrame || function( fn ){ setTimeout( fn, 16 ); } )( swatchlyReinit );
1024		};
1025		var swatchlyDomObserver = new MutationObserver( function( mutations ) {
1026			for ( var i = 0; i < mutations.length; i++ ) {
1027				var added = mutations[ i ].addedNodes;
1028				if ( ! added || ! added.length ) { continue; }
1029				for ( var j = 0; j < added.length; j++ ) {
1030					var node = added[ j ];
1031					if ( node.nodeType !== 1 ) { continue; }
1032					if (
1033						( node.matches && node.matches( '.variations_form, .swatchly_loop_variation_form' ) ) ||
1034						( node.querySelector && node.querySelector( '.variations_form, .swatchly_loop_variation_form' ) )
1035					) {
1036						swatchlyScheduleReinit();
1037						return;
1038					}
1039				}
1040			}
1041		});
1042		$( document ).ready( function() {
1043			if ( document.body ) {
1044				swatchlyDomObserver.observe( document.body, { childList: true, subtree: true } );
1045			}
1046		});
1047	}
1048})(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.