PageSourceSearch

https://www.silex.co.uk/wp-content/plugins/yith-woocommerce-advanc…/assets/js/front.js?ver=4.29.0

js silex.co.uk collected 2026-10-04 13:29:56 UTC 103,136 bytes, 3,145 lines download raw bytes

1/**
2 * Front JS
3 */
4
5jQuery( document ).ready(
6	function ( $ ) {
7
8		/**
9		 * Init the colorpicker input
10		 */
11		initColorpicker = function() {
12			// Customizable args for wpColorPicker function.
13			var colorPicker_opt = {
14				color: false, // If Iris is attached to an input element, it will first try to pick up its value attribute. Otherwise, you can supply a color of any type that Color.js supports. (Hex, rgb, and hsl are good bets.).
15				mode: 'hsl', // Iris can sport a variety of looks. It supports hsl and ‘hsv’ modes depending on your needs.
16				controls: {
17					horiz: 's', // horizontal defaults to saturation.
18					vert: 'l', // vertical defaults to lightness.
19					strip: 'h' // right strip defaults to hue.
20				},
21				hide: true, // Hide colorpickers by default.
22				target: false, // a DOM element / jQuery selector that the element will be appended within. Only used when called on an input.
23				width: 180, // the width of the collection of UI elements.
24				palettes: false, // show a palette of basic colors beneath the square.
25				change: function(event, ui) {
26					let pickerContainer    = $( this ).closest( '.wp-picker-container' );
27					let pickerInputWrap    = pickerContainer.find( '.wp-picker-input-wrap' );
28					let placeholderElement = pickerContainer.find( '.wp-picker-custom-placeholder' );
29					let clearElement       = pickerContainer.find( '.wp-picker-default-custom' );
30					let colorPickerShow    = pickerContainer.find( '.wp-color-picker' ).data( 'addon-colorpicker-show' );
31					let defaultColor       = pickerContainer.find( '.wp-color-picker' ).data( 'default-color' );
32					let colorPickerInput = pickerInputWrap.find( 'input.wp-color-picker' );
33					let addonOpt = $(colorPickerInput[0]);
34
35
36					// Placeholder option to hide or not the necessary elements.
37					if ( 'placeholder' === colorPickerShow ) {
38						if ( '' !== ui.color.toString() || 'undefined' !== ui.color.toString() ) {
39							pickerInputWrap.find( '.wp-color-picker' ).show();
40							placeholderElement.hide();
41							clearElement.show();
42							placeholderElement.css( 'line-height', '3.0' );
43						}
44					}
45
46					clearElement.removeClass( 'default_color' );
47					if ( defaultColor !== ui.color.toString() ) {
48						clearElement.addClass( 'default_color' );
49					}
50
51					$( document ).trigger( 'wapo-colorpicker-change', addonOpt );
52
53				},
54				clear: function(event, ui) {
55
56					let pickerContainer    = $( this ).closest( '.wp-picker-container' );
57					let pickerInputWrap    = pickerContainer.find( '.wp-picker-input-wrap' );
58					let placeholderElement = pickerContainer.find( '.wp-picker-custom-placeholder' );
59					let clearElement       = pickerContainer.find( '.wp-picker-default-custom' );
60					let colorPickerShow    = pickerContainer.find( '.wp-color-picker' ).data( 'addon-colorpicker-show' );
61					let colorPickerInput   = pickerInputWrap.find( 'input.wp-color-picker' );
62					let addonOpt = $(colorPickerInput[0]);
63
64					// Placeholder option to hide or not the necessary elements.
65					if ( 'placeholder' === colorPickerShow ) {
66						pickerInputWrap.find( '.wp-color-picker' ).hide();
67						placeholderElement.show();
68						clearElement.hide();
69						placeholderElement.css( 'line-height', '0' );
70					}
71
72					$( document ).trigger( 'wapo-colorpicker-clear', addonOpt[0] );
73
74				}
75			};
76
77			function inicializeAddonColorpickers() {
78
79				// Initialize each colorpicker with wpColorPicker function.
80				$( '.yith-wapo-block .yith-wapo-addon-type-colorpicker .wp-color-picker' ).each(
81					function() {
82						$( this ).wpColorPicker( colorPicker_opt );
83
84						let pickerContainer = $( this ).closest( '.wp-picker-container' );
85						let pickerText      = pickerContainer.find( 'button .wp-color-result-text' );
86						let clearButton     = pickerContainer.find( '.wp-picker-default' );
87						let pickerInputWrap = pickerContainer.find( '.wp-picker-input-wrap' );
88						let colorPickerShow = $( this ).data( 'addon-colorpicker-show' );
89						let placeholder     = $( this ).data( 'addon-placeholder' );
90
91						// Hide always the picker text
92						pickerText.html( '' );
93
94						// Create an custom element to show the custom Clear button.
95						let wrap_main1 = $( this ).parents( '.wp-picker-container' ),
96							wrap1          = wrap_main1.find( '.wp-picker-input-wrap' );
97
98						if ( ! wrap_main1.hasClass( 'yith-wapo-colorpicker-initialized' ) ) {
99							wrap_main1.addClass( 'yith-wapo-colorpicker-initialized' );
100						}
101
102						if ( ! wrap1.find( '.wp-picker-default-custom' ).length ) {
103							var button = $( '<span/>' ).attr(
104								{
105									class: 'wp-picker-default-custom'
106								}
107							);
108							wrap1.find( '.wp-picker-default, .wp-picker-clear' ).wrap( button );
109						}
110
111						// If it's placeholder option, create a custom element to show the placeholder label.
112						if ( 'placeholder' === colorPickerShow ) {
113							pickerInputWrap.find( '.wp-color-picker' ).hide();
114							if ( ! pickerInputWrap.find( '.wp-picker-custom-placeholder' ).length ) {
115								var placeholder_el = $( '<span/>' ).attr(
116									{
117										class: 'wp-picker-custom-placeholder',
118									}
119								);
120								placeholder_el.html( placeholder );
121								pickerInputWrap.find( '.screen-reader-text' ).before( placeholder_el );
122							}
123							let clearElement       = pickerContainer.find( '.wp-picker-default-custom' );
124							let placeholderElement = pickerContainer.find( '.wp-picker-custom-placeholder' );
125
126							clearElement.hide();
127							placeholderElement.css( 'line-height', '0' );
128						}
129
130						clearButton.trigger( 'click' );
131
132					}
133				);
134			}
135
136			$( document ).on( 'yith-wapo-after-reload-addons', inicializeAddonColorpickers );
137			$( document ).on( 'yith-wapo-after-reload-addons', initDatePickers );
138
139			checkColorPickerOnInput = function() {
140				$( document ).on(
141					'click',
142					function (e) {
143						if ( ! $( e.target ).is( '.yith-wapo-colorpicker-container .iris-picker, .yith-wapo-colorpicker-container .iris-picker-inner' ) ) {
144							let initializedColorPickers = $( '.yith-wapo-colorpicker-container .yith-wapo-colorpicker-initialized .wp-color-picker' );
145							if ( initializedColorPickers.length > 0 ) {
146								initializedColorPickers.iris( 'hide' );
147							}
148							return;
149						}
150					}
151				);
152				$( '.yith-wapo-colorpicker-container .yith-wapo-colorpicker-initialized .wp-color-picker' ).click(
153					function ( event ) {
154						$( this ).iris( 'show' );
155						return;
156					}
157				);
158			};
159
160			inicializeAddonColorpickers();
161			checkColorPickerOnInput();
162
163		};
164
165		/**
166		 * Init the datepicker input
167		 */
168		initDatePickers = function() {
169
170			// Initialize each colorpicker with wpColorPicker function.
171			$( '.yith-wapo-block .yith_wapo_date.datepicker' ).each(
172				function() {
173					let datepicker_input = $( this );
174					initDatePicker( datepicker_input );
175				}
176			);
177
178
179		};
180
181		initTimePicker = function( datepicker_input ) {
182			let params             = datepicker_input.data( 'params' ),
183				timeData           = params.time_data ? params.time_data : '',
184				showTimeSelector    = params.show_time_selector ? params.show_time_selector : '';
185
186			if ( typeof timeData === 'object' && timeData !== null ) {
187				timeData = Object.values(timeData);
188			}
189			if ( showTimeSelector ) {
190				setTimeout( function() {
191					if ( ! $('#wapo-datepicker-time').length ) {
192						var timeDataHTML = '',
193							tempTimeEl = datepicker_input.closest( '.date-container' ).find( '.temp-time' ).text();
194						$( timeData ).each(function( index, value ) {
195							if ( value !== tempTimeEl ) {
196								timeDataHTML += '<option>' + value + '</option>'
197							} else {
198								timeDataHTML += '<option selected>' + value + '</option>'
199							}
200						} );
201						var timeHTML = '<div id="wapo-datepicker-time"><label>' + yith_wapo.i18n.datepickerSetTime + '</label><select id="wapo-datepicker-time-select">' +
202							timeDataHTML
203							+ '</select></div>'
204							+ '<div id="wapo-datepicker-save"><button>' + yith_wapo.i18n.datepickerSaveButton + '</button></div>';
205
206						$( timeHTML ).appendTo('#ui-datepicker-div');
207					}
208				}, 10 );
209			}
210		}
211
212		initDatePicker = function( datepicker_input ) {
213
214			var params = datepicker_input.data( 'params' ),
215				minimumDate         = '',
216				maximumDate         = '',
217				startYear           = params.start_year ? params.start_year : '',
218				endYear             = params.end_year ? params.end_year : '',
219				defaultDateSelected = params.default_date ? params.default_date : '',
220				dateFormat          = params.date_format ? params.date_format : '',
221				additional_opts 	= params.additional_opts ? params.additional_opts : '';
222
223			if ( startYear ) {
224				minimumDate = new Date( params.start_year, '00', '01' );
225			}
226			if ( endYear ) {
227				maximumDate = new Date( params.end_year, '11', '31' );
228			}
229
230			// datepicker options: https://api.jqueryui.com/datepicker/
231
232			var datePicker_opts = {
233				minDate: minimumDate,
234				maxDate: maximumDate,
235				defaultDate: defaultDateSelected,
236				dateFormat: dateFormat,
237				beforeShowDay: function( date ) {
238					let params         = datepicker_input.data( 'params' ),
239						selectableDaysOpt  = params.selectable_days_opt ? params.selectable_days_opt : '',
240						selectableDays     = params.selectable_days ? params.selectable_days : '',
241						selectedItems      = params.selected_items ? params.selected_items : '',
242						enabled            = params.enable_disable_date_rules ? params.enable_disable_date_rules : '',
243						returnValue    	   = true;
244
245					enabled            = ( 'enable' === enabled ) ? 1 : 0;
246
247					if ( enabled ) {
248						returnValue = false;
249					}
250
251					// Selectable days (MIN/MAX)
252					if ( 'days' === selectableDaysOpt || 'date' === selectableDaysOpt ) {
253						let currentDate = date.getDate() + '-' + ( date.getMonth() + 1 ) + '-' + date.getFullYear();
254						if ( -1 === $.inArray( currentDate, selectableDays ) ) {
255							returnValue = false;
256							return false;
257						}
258						//Disable days before current day.
259					} else if ( 'before' === selectableDaysOpt ) {
260						let currentDate = date.getTime();
261						let todayDate   = new Date();
262						let currentHour = new Date().getHours();
263						todayDate.setHours( 0, 0, 0, 0 ); // Set date to midnight
264						todayDate       = todayDate.getTime();
265						if ( currentDate < todayDate || ( yith_wapo.disableCurrentDayBasedOnTime && currentDate <= todayDate && currentHour >= yith_wapo.datepickerHourToCheck ) ){
266							return false;
267						}
268					}
269
270					// Selected Items (Specific days)
271					if ( selectedItems.length > 0 ) {
272						selectedItems = JSON.parse( selectedItems );
273						$.each( selectedItems, function( i, items ) {
274							if ( 'days' === i ) {
275								let currentDate = new Date( date );
276								$.each( items, function ( i, item ) {
277									let [ item_y, item_m, item_d ] = item.split('-');
278									let selectedDay = new Date( parseInt(item_y), parseInt(item_m) - 1, parseInt(item_d) );
279									if (currentDate.toDateString() === selectedDay.toDateString()) {
280										returnValue = !! enabled;
281										return false;
282									}
283								});
284							} else if ( 'daysweek' === i ) {
285								let dayWeek = date.getDay();
286								$.each( items, function ( i, item ) {
287									$.each( item, function (e, day) {
288										if (dayWeek == day) {
289											returnValue = !! enabled;
290											return false;
291										}
292									});
293								});
294							} else if ( 'months' === i ) {
295								let dateMonth = date.getMonth();
296								$.each( items, function( i, item ) {
297									$.each( item, function( e, month ) {
298										if ( dateMonth == month -1 ) {
299											returnValue = !! enabled;
300											return false;
301										}
302									} );
303								} );
304							} else if ( 'years' === i ) {
305								let dateYear = date.getFullYear();
306								$.each( items, function( i, item ) {
307									$.each( item, function( e, year ) {
308										if ( dateYear == year ) {
309											returnValue = !! enabled;
310											return false;
311										}
312									} );
313								} );
314							}
315						});
316					}
317
318					// Enable or disable specific date using WP-Hooks
319					const { addonId, optionId } = datepicker_input.attr( 'name' ).match( /\[(?<addonId>[\d]+)-(?<optionId>[\d]+)\]$/ ).groups;
320					returnValue = wp.hooks.applyFilters( 'yith_wapo_addon_datepicker_before_show_day', returnValue, date, addonId, optionId );
321
322					if ( returnValue ) {
323						return [true];
324					}
325					return [false];
326				},
327				beforeShow: function ( datepicker ) {
328					initTimePicker( datepicker_input );
329				},
330				onSelect: function( dateText, obj ) {
331					if ( $( obj.dpDiv ).find( '#wapo-datepicker-time-select' ).length > 0 ) {
332						let timeSelected = $( obj.dpDiv ).find( '#wapo-datepicker-time-select' ).val(),
333							tempTimeEl = $( this ).closest( '.date-container' ).find( '.temp-time' );
334						$( this ).val( dateText + ' ' + timeSelected );
335						tempTimeEl.text( timeSelected );
336					}
337
338					let addonOpt = $( '#' + obj.id );
339					addonOpt.change();
340
341				},
342				onChangeMonthYear: function( year, month, inst ) {
343					initTimePicker( datepicker_input );
344				},
345				onUpdateDatepicker: function( ins ) {
346					$( '#ui-datepicker-div' ).attr( 'wapo-option-id', ins.id );
347				},
348				onClose: function( date, ins ) {
349					$( this ).trigger( 'yith_wapo_date_field_updated', date );
350				},
351			}
352
353			// additional parameters added with the yith_wapo_datepicker_options filter
354			datePicker_opts = Object.assign( datePicker_opts, additional_opts );
355
356			datepicker_input.datepicker(
357				datePicker_opts
358			);
359
360		}
361
362		initColorpicker();
363		initDatePickers();
364	}
365);
366
367jQuery( document ).ready(
368	function ($) {
369		var firstVariationLoading = false;
370		var wapoDOM = {
371			editProductCartLink : '.yith-wapo-edit-product-cart',
372			individualAddons    : '.yith-wapo-individual-addons',
373			cartPopup           : '.yith-wapo-popup',
374			popupOverlay		: '.yith-wapo-overlay',
375			popupClose		    : '.yith-wapo-close',
376			popupWrapper		: '.yith-wapo-wrapper',
377			popupContent		: '.yith-wapo-content',
378			popupFooter		    : '.yith-wapo-footer',
379			addToCartButton		: '.yith-wapo-popup .single_add_to_cart_button',
380			popupForm		    : '.yith-wapo-popup form.cart',
381			popupVariationForm  : '.yith-wapo-popup form.variations_form',
382			addonsContainer		: '#yith-wapo-container',
383			hiddenItemKey		: '.yith-wapo-cart-item-key',
384			addonImageInput     : '#yith_wapo_product_img',
385			formCart			: 'form.cart',
386			totalPriceTable		: '#wapo-total-price-table',
387			wcProductGallery    : '.woocommerce-product-gallery',
388			variationForm       : 'form.variations_form',
389			totalsBox			: {
390				productPrice : '#wapo-total-product-price',
391				optionsPrice : '#wapo-total-options-price',
392				orderPrice	 : '#wapo-total-order-price'
393			}
394		}
395
396		var wapoArgs = {
397			numDecimals : parseInt(yith_wapo.number_decimals)
398		}
399
400		var pa_modal = $( wapoDOM.cartPopup );
401
402		initJS 			= function() {
403			if ( $( wapoDOM.variationForm ).length < 1 ) { // If it's a variable product, we don't calculate price initially. Only on found_variation trigger.
404				calculateTotalAddonsPrice();
405			}
406			checkDefaultOptionsOnLoad();
407			moveEditProductLinkOnCart();
408
409			$( document ).on( 'updated_wc_div', moveEditProductLinkOnCart ); // Move link after updating cart.
410			$( document ).on( 'click', wapoDOM.addToCartButton, addToCart );
411			$( document ).on( 'click', wapoDOM.editProductCartLink, openEditAddonsModal );
412			$( document ).on( 'click', wapoDOM.popupClose + ',' + wapoDOM.popupOverlay, closeModal );
413			$( window ).on( "resize", centerEditModal );
414
415		}
416
417		addToCart			= function(e) {
418
419			e.preventDefault();
420			e.stopPropagation();
421
422			if ( ! checkAddonsRequirements() ) {
423				return false;
424			}
425
426			var cartItemKey   = $( wapoDOM.hiddenItemKey ).val();
427			var productId     = $( wapoDOM.popupVariationForm ).find( '.product_id' ).val();
428			var variationId   = $( wapoDOM.popupVariationForm ).find( '.variation_id' ).val();
429
430			var individualCartItems = $( '.yith-wapo-individual-addons.parent-key-' + cartItemKey );
431			var individualCartKeys  = [];
432
433			$.each( individualCartItems, function() {
434				individualCartKeys.push( $(this).data( 'cart-item-key' ) );
435			} );
436
437			var data     = {
438				'action'	           : 'ywapo_update_addons_on_cart_item',
439				'cart_item_key'        : cartItemKey,
440				'individual_item_key'  : individualCartKeys,
441				'product_id'           : productId,
442				'variation_id'         : variationId,
443				'addons' 		       : $( wapoDOM.popupForm ).serializeArray(),
444				'security'             : yith_wapo.addons_nonce,
445			}
446
447			$.ajax(
448				{
449					url: yith_wapo.ajaxurl,
450					type: 'post',
451					data: data,
452					beforeSend: function(){
453
454					},
455					error: function (xhr, status, error) {
456						// Code to handle errors.
457						console.error('AJAX request failed: ', status, error);
458					},
459					success: function (response) {
460						if (response) {
461							if ( response['success'] ) {
462								closeModal();
463
464								if ( yith_wapo.wc_blocks.has_cart_block ) {
465									// WC Blocks cart update.
466									const CART_STORE_KEY = window.wc.wcBlocksData.CART_STORE_KEY;
467
468									// Returns an action object used in signalling that the resolution cache should be invalidated for the entire store.
469									window.wp.data.dispatch( CART_STORE_KEY ).invalidateResolutionForStore();
470									// Return and update cart data.
471									window.wp.data.select( CART_STORE_KEY ).getCartData();
472
473								} else {
474									$( document ).trigger( 'wc_update_cart' );
475								}
476							}
477						}
478					}
479				}
480			);
481		}
482
483		centerEditModal		= function() {
484
485			var t        = $( wapoDOM.popupWrapper ),
486				window_w = $(window).width(),
487				window_h = $(window).height(),
488				width    = yith_wapo.isMobile ? $(window).width() : $(window).width() / 1.5,
489				height   = yith_wapo.isMobile ? $(window).height() : $(window).height() / 1.3;
490
491			t.css({
492				'left'      : ( ( window_w/2 ) - ( width/2 ) ),
493				'top'       : ( ( window_h/2 ) - ( height/2 ) ),
494				'width'     : width + 'px',
495				'height'    : height + 'px'
496			});
497		};
498
499		loadProduct		= function( link ) {
500
501
502			$( wapoDOM.popupWrapper ).block({ message: null, overlayCSS: { opacity: 0 } });
503
504			var productId      = link.data( 'product_id' );
505			var variationId    = link.data( 'variation_id' );
506			var cartItemKey    = link.data( 'cart-item-key' );
507
508			var data   = {
509				'action'	     : 'ywapo_load_product_template',
510				'product_id'     : productId,
511				'variation_id'   : variationId,
512				'cart_item_key'  : cartItemKey,
513				'security'       : yith_wapo.addons_nonce,
514			}
515
516			$.ajax(
517				{
518					url : yith_wapo.ajaxurl,
519					type : 'post',
520					data : data,
521					error: function(xhr, status, error) {
522						// Code to handle errors
523						console.error('AJAX request failed: ', status, error);
524					},
525					beforeSend: function() {
526						link.block({
527							message   : null,
528							overlayCSS: {
529								background: '#fff url(' + yith_wapo.loader + ') no-repeat center/30%',
530								opacity   : 0.5,
531								cursor    : 'none'
532							}
533						});
534					},
535					success : function( response ) {
536						if ( response && response['success'] ) {
537
538							centerEditModal();
539
540							var variation      = response['variation'] && '' !== response['variation'] ? response['variation'] : undefined;
541							var quantity       = response['quantity'] ?? '';
542							var wcclData       = response['wccl-data'] ?? '';
543							var addonImageURL  = response['addon_image'] ?? '';
544							var quantities     = response['quantities'] ?? '';
545							var addonsSelected = response['addons'] ?? '';
546
547							// Add the product page to the content.
548							$( wapoDOM.popupContent ).html( response['html'] );
549
550							if ( typeof wc_add_to_cart_variation_params !== 'undefined' ) {
551								$(wapoDOM.popupVariationForm).each(function () {
552									$(this).wc_variation_form();
553								});
554							}
555
556							// Init product gallery.
557							$( wapoDOM.wcProductGallery ).wc_product_gallery();
558
559							// Set variations.
560							if ( variation != undefined ) {
561								setVariation( variation );
562							}
563
564							// Init datepickers.
565							initDatePickers();
566
567							// Init colorpickers.
568							initColorpicker();
569
570							// Color and Label module or plugin.
571							if ( wcclData !== '' ) {
572								// TODO: Fix wccl.
573								$.yith_wccl(wcclData);
574							}
575
576							moveAddToCartButton();
577
578							var itemKey = link.data( 'cart-item-key' );
579							$( wapoDOM.hiddenItemKey ).val( itemKey );
580
581							addonsSelection( addonsSelected );
582
583							if ( quantities ) {
584								addonsQuantity( quantities );
585							}
586
587							if ( quantity > 0 ) {
588								setQuantity( quantity );
589							}
590
591							if ( addonImageURL ) {
592								replaceAddonImage( addonImageURL );
593							}
594
595							// Calculate add-on prices.
596							calculateTotalAddonsPrice();
597
598						} else {
599							console.log( response );
600						}
601					},
602					complete : function ( e, response ) {
603						if ( 'success' === response ) {
604							// Open modal at the end of process.
605							pa_modal.addClass('open');
606							$( 'body' ).addClass( 'yith-wapo-modal-is-open' );
607						}
608
609						$( wapoDOM.popupWrapper ).unblock({ message: null });
610						link.unblock({ message: null });
611					}
612				}
613			);
614		}
615
616		moveAddToCartButton				= function() {
617			var button = $( wapoDOM.popupContent ).find( '.single_add_to_cart_button' );
618			if ( button ) {
619				$( wapoDOM.popupFooter ).find( '.yith-wapo-add-to-cart' ).prepend( button );
620			}
621		}
622
623		replaceAddonImage               = function( addonImageURL ) {
624
625			var defaultPath     = yith_wapo.replace_image_path;
626			var zoomMagnifier   = '.yith_magnifier_zoom_magnifier, .zoomWindowContainer .zoomWindow';;
627
628			$( defaultPath ).attr( 'src', addonImageURL );
629			$( defaultPath ).attr( 'srcset', addonImageURL );
630			$( defaultPath ).attr( 'data-src', addonImageURL );
631			$( defaultPath ).attr( 'data-large_image', addonImageURL );
632			$( zoomMagnifier ).css( 'background-image', 'url(' + addonImageURL + ')' );
633
634			$( wapoDOM.addonImageInput ).val( addonImageURL );
635
636			// Reset gallery position.
637			if ( $( '.woocommerce-product-gallery .woocommerce-product-gallery__image' ).length > 0 ) {
638				$( wapoDOM.wcProductGallery ).trigger( 'woocommerce_gallery_reset_slide_position' );
639			}
640
641			$( wapoDOM.wcProductGallery ).trigger( 'woocommerce_gallery_init_zoom' );
642
643			$( document ).trigger( 'yith-wapo-after-replace-image' );
644
645		}
646
647		openEditAddonsModal 			= function(e) {
648			e.preventDefault();
649			e.stopPropagation();
650
651			loadProduct( $(this) );
652
653		}
654
655		closeModal 			= function() {
656			pa_modal.removeClass('open');
657			$( 'body' ).removeClass( 'yith-wapo-modal-is-open' );
658
659			setTimeout( // Wait for closing transition and then we clear the HTML.
660				function() {
661					$( wapoDOM.popupContent ).html('');
662					$( wapoDOM.popupFooter ).find( '.yith-wapo-add-to-cart' ).html('');
663				},
664				400
665			)
666		}
667
668		moveEditProductLinkOnCart		= function() {
669			$( wapoDOM.editProductCartLink ).each( function() {
670				var link = $(this);
671				var productNameRow = link.closest( '.product-name' );
672
673				productNameRow.append( link );
674
675			} );
676		}
677
678		setVariation 					= function(t_variation) {
679			var form = $( 'form.variations_form.cart' );
680
681			for ( let attribute in t_variation.attributes ) {
682
683				let attribute_sel = $("select[name='" + attribute + "']");
684				let value         = t_variation.attributes[attribute];
685				if ( value && attribute_sel.length > 0 ) {
686					attribute_sel.val( value );
687				}
688			}
689		}
690
691		addonsSelection				= function( addons ) {
692			var filesIndex = 0;
693			for ( var i in addons ) {
694				var addonObj = addons[i];
695
696				for ( var addonIndex in addonObj ) {
697					var addonVal  = addonObj[addonIndex];
698					var addonOpts = addonIndex.split('-');
699
700					let addonID     = addonOpts[0] ?? '';
701					let addonOption = addonOpts[1] ?? '';
702
703					var addon     = $( '#yith-wapo-addon-' + addonID ).get(0);
704					var addonType = $( addon ).data( 'addon-type' );
705
706					if ( '' === addonVal ) {
707						continue;
708					}
709
710					var addonOpt = '';
711
712					switch (addonType) {
713
714						case 'checkbox':
715						case 'label':
716						case 'color':
717						case 'product':
718							addonOpt = $( addon ).find( '#yith-wapo-' + addonID + '-' + addonOption );
719							if ( ! addonOpt.is(':checked') ) {
720								$(addonOpt).prop('checked', true).trigger('change');
721							}
722							break;
723						case 'radio':
724							addonOpt = $( addon ).find( '#yith-wapo-' + addonID + '-' + addonVal );
725							addonOpt.trigger('click');
726							break;
727						case 'text':
728						case 'textarea':
729						case 'number':
730						case 'date':
731							addonOpt = $( addon ).find( '#yith-wapo-' + addonID + '-' + addonOption );
732							addonOpt.val( addonVal );
733							break;
734						case 'colorpicker':
735							addonOpt = $( addon ).find( '#yith-wapo-' + addonID + '-' + addonOption );
736							addonOpt.val( addonVal );
737							addonOpt.trigger('change');
738							break;
739						case 'select':
740							addonOpt = addonOpt = $( addon ).find( '#yith-wapo-' + addonID );
741							addonOpt.val( addonVal );
742							break;
743						case 'file':
744							var option = $( '#yith-wapo-option-' + addonID + '-' + addonOption );
745
746							loadUploadedFile(
747								{
748									'addon'        : $(addon),
749									'option'       : option,
750									'addonVal'     : addonVal,
751									'addonIndex'   : addonIndex,
752									'index'        : filesIndex,
753									'fileSize'     : ''
754								}
755							);
756
757							filesIndex++;
758							break;
759					}
760				} // end second for loop
761			} // end first for loop
762
763			maybeHideImageUploaded();
764
765		}
766
767		loadUploadedFile					= function(data) {
768
769			let addon       = data.addon ?? '';
770			let option      = data.option ?? '';
771			let addonVal    = data.addonVal ?? '';
772			let addonIndex  = data.addonIndex ?? '';
773			let index       = data.index ?? '';
774			let size        = data.fileSize ?? '';
775
776			addonVal    = $.isArray( addonVal ) ? addonVal[0] : addonVal;
777
778			let name    = addonVal.split('/').reverse()[0] ?? '';
779			let isImage = addonVal.endsWith( 'jpeg' ) || addonVal.endsWith( 'jpg' ) || addonVal.endsWith( 'png' );
780
781			var fileTemplate          = wp.template( 'yith-wapo-uploaded-file-template' );
782			var uploadedFileContainer = $( option ).find( '.yith-wapo-uploaded-file' );
783
784			var fileData = {
785				'fileIndex': index,
786				'fileName' : name,
787				'fileSize' : size,
788				'optionId' : addonIndex,
789				'image'    : isImage ? addonVal : '',
790				'addonVal' : addonVal,
791			};
792
793			uploadedFileContainer.append(
794				fileTemplate( fileData )
795			);
796
797			uploadedFileContainer.closest( '.file-container' ).find( '.upload-parent' ).val(1);
798
799			uploadedFileContainer.show();
800			maybeHideUploadButton( option );
801
802		}
803		maybeHideImageUploaded				= function() {
804
805			$( '.yith-wapo-addon-type-file .yith-wapo-img-uploaded' ).each( function() {
806				if ( ! $(this).attr('src') ) {
807					$(this).hide();
808				}
809			} );
810
811		}
812		maybeHideUploadButton				= function( option ) {
813
814			let maxMultiple = option.data( 'max-multiple' );
815
816			if ( option.find( '.yith-wapo-uploaded-file-element' ).length >= maxMultiple ) {
817				option.find( '.yith-wapo-ajax-uploader-container' ).hide();
818			}
819
820		}
821
822		checkAddonsRequirements				= function() {
823			let numbersCheck = checkNumbersTotalValues(),
824				minMaxResult = checkRequiredMinMax();
825
826			if ( ! numbersCheck ) { // if it's not true, do not allow to add to cart.
827				return false;
828			}
829
830			if ( ! minMaxResult && ! yith_wapo.disable_scroll_on_required_mix_max ) {
831				$( 'html, body, .yith-wapo-content' ).animate( { scrollTop: $( wapoDOM.addonsContainer ).offset().top - 20 }, 500 );
832			}
833
834			return minMaxResult;
835		},
836			checkRequiredFields = function (action) {
837
838				var isRequired = false;
839				var hideButton = false;
840				var buttonClasses = yith_wapo.dom.single_add_to_cart_button;
841				$('form.cart .yith-wapo-addon:not(.hidden):visible input, form.cart .yith-wapo-addon:not(.hidden):visible select, form.cart .yith-wapo-addon:not(.hidden):visible textarea').each(
842					function () {
843						let element = $(this),
844							elementType = element.attr('type');
845						let parent = element.closest('.yith-wapo-option');
846						let addon = element.closest('.yith-wapo-addon');
847						let toggle_addon = element.closest('div.yith-wapo-addon.wapo-toggle');
848						let toggle_addon_title = toggle_addon.find('.wapo-addon-title.toggle-closed');
849						let addonTitle = addon.find('.wapo-addon-title');
850
851						if ('file' === element.attr('type') || element.hasClass('wapo-product-qty')) {
852							return;
853						}
854
855						if (
856							element.attr('required') &&
857							('checkbox' === elementType || 'radio' === elementType )
858							&& !element.closest('.yith-wapo-option').hasClass('selected')
859							|| element.attr('required')
860							&& 'checkbox' !== elementType
861							&& 'radio' !== elementType
862							&& (element.val() == '' || element.val() == 'Required')
863						) {
864							if (action === 'highlight') {
865								// Add required message.
866								showRequiredMessage(element);
867								addonTitle.addClass('wapo-error');
868
869
870								// Open toggle.
871								if (toggle_addon_title) {
872									toggle_addon_title.click();
873								}
874							}
875
876							hideButton = true;
877							isRequired = true;
878						} else {
879
880							if ( ( 'text' === elementType || 'textarea' === elementType ) && element.val() != '' ) {
881								isRequired = !checkTextInputLimit(element); // Check min/max length for inputs texts and textarea ONLY if the input IS NOT EMPTY.
882							}
883
884							// Restart default required status.
885							restartRequiredElement(element);
886						}
887					}
888				);
889
890				return !isRequired;
891			},
892
893			/** Print the required element with the message and colors */
894			showRequiredMessage = function (element) {
895				let option = element.closest('.yith-wapo-option');
896				if (option.find('.required-error').length < 1) {
897					option.append(
898						'<div class="required-error">' +
899						'<small class="required-message">' + yith_wapo.messages.requiredMessage + '</small>' +
900						'</div>'
901					);
902
903					option.addClass('required-color');
904				}
905
906			},
907
908			/** Restart the required element (removing it) and remove Color classes */
909			restartRequiredElement = function( element ) {
910				let option = element.closest( '.yith-wapo-option' );
911				element.closest( '.yith-wapo-option' ).find( '.required-error' ).remove();
912				option.removeClass( 'required-color' );
913			},
914
915			/**
916			 * Conditional Logic
917			 */
918			conditionalLogicCheck 	  = function( lastFinalConditions = {} ) {
919				var finalConditions = {};
920
921				$( 'form.cart .yith-wapo-addon.conditional_logic' ).each(
922					function() {
923
924						var AddonConditionSection = false,
925							AddonVariationSection = false;
926
927						var logicDisplay = $(this).data('conditional_logic_display'); // show / hide
928
929						// Applied to conditions.
930						var logicDisplayIf = $(this).data('conditional_logic_display_if'); // all / any
931
932						var ruleAddon = String($(this).data('conditional_rule_addon')),
933							ruleAddonIs = String($(this).data('conditional_rule_addon_is')),
934							ruleVariation = String($(this).data('conditional_rule_variations'));
935
936						ruleAddon = (typeof ruleAddon !== 'undefined' && ruleAddon !== "0" && ruleAddon !== '') ? ruleAddon.split('|') : false; // addon number
937						ruleAddonIs = (typeof ruleAddonIs !== 'undefined' && ruleAddonIs !== '') ? ruleAddonIs.split('|') : false; // selected / not-selected / empty / not-empty
938						ruleVariation = (typeof ruleVariation !== 'undefined' && ruleVariation !== '') ? ruleVariation.split('|') : false; // variations number
939
940						if (!ruleVariation && (!ruleAddon || !ruleAddonIs)) {  // Show addon if no variations conditions or addons conditions.
941
942							AddonConditionSection = true;
943							AddonVariationSection = true;
944							logicDisplay = 'show';
945
946						} else {
947
948							// ConditionLogic.
949							if (ruleAddon && ruleAddonIs) {
950
951								switch (logicDisplayIf) {
952
953									case 'all':
954										AddonConditionSection = conditionalLogicAllRules(ruleAddon, ruleAddonIs);
955										break;
956
957									case 'any':
958										AddonConditionSection = conditionalLogicAnyRules(ruleAddon, ruleAddonIs);
959										break;
960
961								}
962
963							} else {
964								AddonConditionSection = true;
965							}
966
967							if (AddonConditionSection && ruleVariation) { // Prevent check variations if addons condition fails.
968								var variationProduct = $('.variation_id').val();
969								if (-1 !== $.inArray(String(variationProduct), ruleVariation)) {
970									AddonVariationSection = true;
971								}
972
973							} else {
974								AddonVariationSection = true;
975							}
976
977						}
978
979						switch (logicDisplay) {
980
981							case 'show' :
982
983								if (AddonVariationSection && AddonConditionSection) { // Both conditions true --> apply logic Display
984									finalConditions[$(this).attr('id')] = 'not-hidden';
985								} else {
986									finalConditions[$(this).attr('id')] = 'hidden';
987								}
988								break;
989
990							case 'hide' :
991								if (AddonVariationSection && AddonConditionSection) {  // Both conditions true --> apply logic Display
992									finalConditions[$(this).attr('id')] = 'hidden';
993								} else {
994									finalConditions[$(this).attr('id')] = 'not-hidden';
995								}
996						}
997					}
998				);
999
1000				$.each(
1001					finalConditions,
1002					function( id, mode ) {
1003						let element = $( '#' + id );
1004
1005						var isHidden = element.hasClass( 'hidden' );
1006
1007						if ( 'not-hidden' === mode ) {
1008
1009							// Todo: We avoid out of stock to change disabled value.
1010							if( yith_wapo.conditionalDisplayEffect === 'slide' ){
1011								element.slideDown().removeClass( 'hidden' ).find( '.yith-wapo-option:not(.out-of-stock) .yith-wapo-option-value' ).attr( 'disabled', false );
1012							}else{
1013								element.fadeIn().removeClass( 'hidden' ).find( '.yith-wapo-option:not(.out-of-stock) .yith-wapo-option-value' ).attr( 'disabled', false );
1014							}
1015
1016							// Re-enable select add-ons if it was hidden
1017							if ( element.hasClass( 'yith-wapo-addon-type-select' ) ){
1018								element.find( '.yith-wapo-option-value' ).attr( 'disabled', false );
1019							}
1020
1021							// Check the min_max after disable value.
1022							checkMinMax( element );
1023
1024						} else if ( yith_wapo.conditionalDisplayEffect === 'slide' ) {
1025							element.slideUp().addClass( 'hidden' ).find( '.yith-wapo-option-value' ).attr( 'disabled', true );
1026						} else {
1027							element.hide().addClass( 'hidden' ).find( '.yith-wapo-option-value' ).attr( 'disabled', true );
1028						}
1029
1030					}
1031				);
1032
1033				if ( JSON.stringify( finalConditions ) !== JSON.stringify( lastFinalConditions ) ) {
1034					conditionalLogicCheck( finalConditions );
1035				}
1036
1037				$(document).trigger('wapo_after_conditional_logic_check');
1038
1039			}
1040
1041		/**
1042		 * Conditional Rule AND
1043		 *
1044		 * @param ruleAddon
1045		 * @param ruleAddonIs
1046		 * @returns {boolean}
1047		 */
1048		conditionalLogicAllRules 			= function( ruleAddon, ruleAddonIs ) {
1049			var conditional = true;
1050
1051			for ( var x = 0; x < ruleAddon.length; x++ ) {
1052
1053				if ( ruleAddon[x] == 0 || ! ruleAddon[x] ) {
1054					continue;
1055				}
1056
1057				var ruleAddonSplit = ruleAddon[x].split( '-' );
1058				var AddonSelected  = false;
1059				var AddonNotEmpty  = false;
1060
1061				// variation check
1062				if ( typeof ruleAddonSplit[1] != 'undefined' ) {
1063
1064					AddonSelected = ( // Selector or checkbox
1065						$( '#yith-wapo-' + ruleAddonSplit[0] + '-' + ruleAddonSplit[1] ).is( ':checked' )
1066						|| $( 'select#yith-wapo-' + ruleAddonSplit[0] ).val() == ruleAddonSplit[1]
1067					) && ! $( '#yith-wapo-addon-' + ruleAddonSplit[0] ).hasClass( 'hidden' );
1068
1069					var typeText     = $( 'input#yith-wapo-' + ruleAddonSplit[0] + '-' + ruleAddonSplit[1] ).val();			// text
1070					var typeTextarea = $( 'textarea#yith-wapo-' + ruleAddonSplit[0] + '-' + ruleAddonSplit[1] ).val();		// textarea
1071					AddonNotEmpty    = (
1072						( typeof typeText != 'undefined' && typeText !== '' )
1073						|| ( typeof typeTextarea != 'undefined' && typeTextarea !== '' )
1074					) && ! $( '#yith-wapo-addon-' + ruleAddonSplit[0] ).hasClass( 'hidden' );
1075
1076					// addon check
1077				} else {
1078					AddonSelected = (
1079						$( '#yith-wapo-addon-' + ruleAddon[x] + ' input:checkbox:checked' ).length > 0
1080						|| $( '#yith-wapo-addon-' + ruleAddon[x] + ' input:radio:checked' ).length > 0
1081						|| $( '#yith-wapo-addon-' + ruleAddon[x] + ' option:selected' ).length > 0
1082						&& 'default' != $( '#yith-wapo-addon-' + ruleAddon[x] + ' option:selected' ).val() // Check if not default value of Select Add-on
1083					);
1084					AddonSelected = AddonSelected && ! $( '#yith-wapo-addon-' + ruleAddon[x] ).hasClass( 'hidden' );
1085
1086					var typeText = 'undefined';
1087					$( '#yith-wapo-addon-' + ruleAddonSplit[0] + ' input, #yith-wapo-addon-' + ruleAddonSplit[0] + ' textarea' ).each(
1088						function( index ){
1089							if ( $( this ).val() !== '' ) {
1090								typeText = true;
1091								return;
1092							}
1093						}
1094					);
1095					AddonNotEmpty = (
1096						( typeText != 'undefined' && typeText !== '')
1097						// || (typeof typeTextarea != 'undefined' && typeTextarea !== '')
1098					) && ! $( '#yith-wapo-addon-' + ruleAddonSplit[0] ).hasClass( 'hidden' );
1099				}
1100
1101				switch ( ruleAddonIs[x]  ) {
1102					case 'selected' :
1103						if ( ! AddonSelected ) {
1104							conditional = false;
1105						}
1106						break;
1107					case 'not-selected':
1108						if ( AddonSelected ) {
1109							conditional = false;
1110						}
1111						break;
1112
1113					case 'empty' :
1114						if ( AddonNotEmpty ) {
1115							conditional = false;
1116						}
1117						break;
1118
1119					case 'not-empty' :
1120						if ( ! AddonNotEmpty ) {
1121							conditional = false;
1122						}
1123
1124						break;
1125				}
1126
1127				if ( ! conditional ) {
1128					break;
1129				}
1130
1131			}
1132
1133			return conditional;
1134
1135		}
1136
1137		/**
1138		 * Conditional Rule OR
1139		 *
1140		 * @param ruleAddon
1141		 * @param ruleAddonIs
1142		 * @returns {boolean}
1143		 */
1144		conditionalLogicAnyRules = function (ruleAddon, ruleAddonIs) {
1145
1146			var conditional = false;
1147
1148			for (var x = 0; x < ruleAddon.length; x++) {
1149
1150				if (ruleAddon[x] == 0 || !ruleAddon[x]) {
1151					continue;
1152				}
1153				var ruleAddonSplit = ruleAddon[x].split('-');
1154
1155				// variation check
1156				if (typeof ruleAddonSplit[1] != 'undefined') {
1157
1158					AddonSelected = ( // Selector or checkbox
1159						$('#yith-wapo-' + ruleAddonSplit[0] + '-' + ruleAddonSplit[1]).is(':checked')
1160						|| $('select#yith-wapo-' + ruleAddonSplit[0]).val() == ruleAddonSplit[1]
1161					) && !$('#yith-wapo-addon-' + ruleAddonSplit[0]).hasClass('hidden');
1162
1163					var typeText = $('input#yith-wapo-' + ruleAddonSplit[0] + '-' + ruleAddonSplit[1]).val();			// text
1164					var typeTextarea = $('textarea#yith-wapo-' + ruleAddonSplit[0] + '-' + ruleAddonSplit[1]).val();		// textarea
1165					AddonNotEmpty = (
1166						(typeof typeText != 'undefined' && typeText !== '')
1167						|| (typeof typeTextarea != 'undefined' && typeTextarea !== '')
1168					) && !$('#yith-wapo-addon-' + ruleAddonSplit[0]).hasClass('hidden');
1169
1170					// addon check
1171				} else {
1172					AddonSelected = (
1173						$('#yith-wapo-addon-' + ruleAddon[x] + ' input:checkbox:checked').length > 0
1174						|| $('#yith-wapo-addon-' + ruleAddon[x] + ' input:radio:checked').length > 0
1175						|| $('#yith-wapo-addon-' + ruleAddon[x] + ' option:selected').length > 0
1176						&& 'default' != $('#yith-wapo-addon-' + ruleAddon[x] + ' option:selected').val() // Check if not default value of Select Add-on
1177					);
1178					AddonSelected = AddonSelected && !$('#yith-wapo-addon-' + ruleAddon[x]).hasClass('hidden');
1179
1180					var typeText = 'undefined';
1181					$('#yith-wapo-addon-' + ruleAddonSplit[0] + ' input, #yith-wapo-addon-' + ruleAddonSplit[0] + ' textarea').each(
1182						function (index) {
1183							if ($(this).val() !== '') {
1184								typeText = true;
1185								return;
1186							}
1187						}
1188					);
1189					AddonNotEmpty = (
1190						(typeText != 'undefined' && typeText !== '')
1191						// || (typeof typeTextarea != 'undefined' && typeTextarea !== '')
1192					) && !$('#yith-wapo-addon-' + ruleAddonSplit[0]).hasClass('hidden');
1193				}
1194
1195				switch (ruleAddonIs[x]) {
1196					case 'selected' :
1197						if (AddonSelected) {
1198							conditional = true;
1199						}
1200						break;
1201					case 'not-selected':
1202						if (!AddonSelected) {
1203							conditional = true;
1204						}
1205						break;
1206
1207					case 'empty' :
1208						if (!AddonNotEmpty) {
1209							conditional = true;
1210						}
1211						break;
1212
1213					case 'not-empty' :
1214						if (AddonNotEmpty) {
1215							conditional = true;
1216						}
1217
1218						break;
1219				}
1220				if (conditional) {
1221					break;
1222				}
1223			}
1224
1225			return conditional;
1226		}
1227
1228		calculateAddonsPrice 			= function () {
1229			var firstFreeOptions = 0,
1230				currentAddonID = 0,
1231				totalPrice = 0,
1232				totalPriceDefault = 0,
1233				quantity = $(yith_wapo.productQuantitySelector).val(), // Quantity of the Add to cart form.
1234				totals = {};
1235
1236			if (!quantity > 0) {
1237				quantity = 1;
1238			}
1239
1240			$('form.cart .yith-wapo-addon:not(.hidden):visible input, form.cart .yith-wapo-addon:not(.hidden):visible select, form.cart .yith-wapo-addon:not(.hidden):visible textarea').each(
1241				function () {
1242
1243					let option = $(this),
1244						defaultProductPrice = parseFloat($(wapoDOM.addonsContainer).attr('data-product-price')),
1245						optionID = option.data('addon-id');
1246
1247					if (optionID) {
1248						let optionType        = option.attr('type'),
1249							priceMethod       = option.data('price-method'),
1250							price             = 0,
1251							defaultTotalPrice = 0,
1252							priceType         = '',
1253							addon             = option.parents('.yith-wapo-addon'),
1254							addonType         = addon.data('addon-type'),
1255							addonQty          = 1;
1256
1257						if ('number' === optionType && 0 == option.val()) {
1258							return totalPrice;
1259						}
1260
1261						if (option.is('textarea')) {
1262							optionType = 'textarea';
1263						}
1264
1265						if (option.is(':checked') || option.find(':selected').is('option')
1266							|| (option.is('input:not([type=checkbox])') && option.is('input:not([type=radio])') && option.val() != '')
1267							|| (option.is('textarea') && option.val() != '')
1268						) {
1269
1270							if (option.is('select')) {
1271								option = option.find(':selected');
1272							}
1273
1274							if ('number' === optionType) {
1275
1276								cleanError( option.closest( '.yith-wapo-option' ) );
1277
1278								let dataMin   = parseInt( option.attr( 'min' ) );
1279								let dataMax   = parseInt( option.attr( 'max' ) );
1280								let numberVal = parseInt( option.val() );
1281
1282								if ( numberVal > dataMax || numberVal < dataMin ) {
1283									let message = yith_wapo.messages.moreThanMax + ' ' + dataMax;
1284
1285									if ( numberVal < dataMin ) {
1286										message = yith_wapo.messages.lessThanMin + ' ' + dataMin;
1287									}
1288
1289									setError( option.closest( '.yith-wapo-option' ), message );
1290
1291									return;
1292								}
1293
1294								checkMultipliedPrice(option);
1295							}
1296
1297							if ('text' === optionType || 'textarea' === optionType) {
1298								checkMultipliedLength(option);
1299							}
1300
1301							if (currentAddonID != optionID) {
1302								currentAddonID = option.data('addon-id');
1303								firstFreeOptions = option.data('first-free-options');
1304							}
1305
1306							if (option.data('first-free-enabled') == 'yes' && firstFreeOptions > 0) {
1307								firstFreeOptions--;
1308							} else {
1309								if (typeof option.data('price-type') != 'undefined' && '' !== option.data('price-type')) {
1310									priceType = option.data('price-type'); // Percentage or fixed.
1311								}
1312
1313								let	dataPriceSale    = option.attr('data-price-sale'),
1314									dataPrice        = option.attr('data-price'),
1315									defaultSalePrice = option.attr('data-default-sale-price'),
1316									defaultPrice     = option.attr('data-default-price');
1317
1318								if (typeof dataPriceSale != 'undefined' && '' !== dataPriceSale && dataPriceSale >= 0 && 'multiplied' !== priceType) {
1319									price             = parseFloat(dataPriceSale);
1320									defaultTotalPrice = parseFloat(defaultSalePrice);
1321								} else if (typeof dataPrice != 'undefined' && '' !== dataPrice) {
1322									price             = parseFloat( dataPrice );
1323									defaultTotalPrice = parseFloat( defaultPrice );
1324								}
1325
1326								if ('percentage' === priceType && 'discount' !== priceMethod) {
1327									price             = (price * defaultProductPrice) / 100;
1328									defaultTotalPrice = (defaultTotalPrice * defaultProductPrice) / 100;
1329								}
1330
1331								if ('product' === addonType) {
1332									if (!option.hasClass('.yith-wapo-option')) {
1333										option = option.parents('.yith-wapo-option');
1334										addonQty = option.find('.wapo-product-qty');
1335										if (addonQty) {
1336											addonQty = addonQty.val();
1337											if (addonQty > 1) {
1338												price = price * addonQty;
1339												defaultTotalPrice = defaultTotalPrice * addonQty;
1340											}
1341										}
1342									}
1343								}
1344
1345								// Multiply price by quantity. Not multiplied for Sell individually add-ons ( it will be 1 on cart ).
1346								if (quantity > 1 && !addon.hasClass('sell_individually')) {
1347									price = price * quantity;
1348									defaultTotalPrice = defaultTotalPrice * quantity;
1349								}
1350
1351								price = parseFloat( price );
1352
1353								totalPrice += price;
1354								totalPriceDefault += defaultTotalPrice;
1355							}
1356						}
1357					}
1358				}
1359			);
1360
1361			totals = {
1362				'totalPrice'       : parseFloat(totalPrice.toFixed(yith_wapo.number_decimals)),
1363				'totalPriceDefault': parseFloat(totalPriceDefault.toFixed(yith_wapo.number_decimals))
1364			};
1365
1366			return totals;
1367		};
1368
1369		setTotalBoxPrices 			= function (defaultProductPrice, totalPrice, replacePrice = true, totalDefaultOptionsPrice = 0) {
1370			var quantity = $(yith_wapo.productQuantitySelector).val();
1371
1372			if (!quantity > 0) {
1373				quantity = 1;
1374			}
1375
1376			var totalProductPrice = defaultProductPrice * quantity,
1377				totalOptionsPrice = parseFloat(totalPrice),
1378				totalOrderPrice   = (parseFloat(totalProductPrice) + totalOptionsPrice).toFixed( wapoArgs.numDecimals );
1379
1380			// Price without formatting.
1381			var total_ProductPrice = totalProductPrice,
1382				total_OptionsPrice = totalOptionsPrice;
1383
1384			if ( ! yith_wapo.includeShortcodePriceSuffix ) {
1385
1386				var totalOptionsPriceFormatted = floatToWcPrice(totalOptionsPrice); // Format price.
1387				var totalOrderPriceFormatted   = floatToWcPrice(totalOrderPrice); // Format price.
1388
1389				$(wapoDOM.totalsBox.optionsPrice).html(totalOptionsPriceFormatted + ' ' + yith_wapo.priceSuffix );
1390				$(wapoDOM.totalsBox.orderPrice).html(totalOrderPriceFormatted + ' ' + yith_wapo.priceSuffix );
1391
1392				var response = {
1393					'order_price_suffix': totalOrderPriceFormatted,
1394					'order_price_raw' : parseInt( $(wapoDOM.addonsContainer).attr('data-default-product-price') ) + parseInt( totalOptionsPrice ),
1395				};
1396
1397				replaceProductPrice(totalOrderPrice, totalOrderPriceFormatted);
1398
1399				$(document).trigger('wapo-after-calculate-product-price', response);
1400
1401
1402			}
1402 else { // Do the ajax calculation only if customer has {price_including_tax} / {price_excluding_tax} in WooCommerce > Tax > Price display suffix option.
1403
1404				let savedOrderPrice = $(wapoDOM.addonsContainer).attr('data-order-price');
1405
1406				// If previous order price is equal to the current calculated order price, it won't process the Ajax Calls.
1407				if (yith_wapo.preventAjaxCallOnUnchangedTotals && parseFloat(savedOrderPrice) === parseFloat(totalOrderPrice)) {
1408					$(wapoDOM.totalPriceTable).css('opacity', '1');
1409					return false;
1410				}
1411
1412				var suffixData = {
1413					'product_id': parseInt($(wapoDOM.addonsContainer).attr('data-product-id')),
1414					'options_price': totalOptionsPrice,
1415					'options_default_price': totalDefaultOptionsPrice,
1416					'total_order_price': totalOrderPrice,
1417					'currency': yith_wapo.woocommerce_currency
1418				};
1419
1420
1421				// Update totals and table (AJAX call).
1422				calculateProductPrice(suffixData);
1423			}
1424
1425			$(wapoDOM.totalPriceTable).css('opacity', '1');
1426
1427			$(document).trigger('yith_wapo_product_price_updated', [total_ProductPrice + total_OptionsPrice]);
1428		},
1429
1430			replaceProductPrice = function (totalOrderPrice, totalOrderPriceHtml) {
1431
1432				if ('yes' !== yith_wapo.replace_price_in_product_without_addons && (!$(wapoDOM.addonsContainer).length || !$(wapoDOM.addonsContainer).find('.yith-wapo-block').length)) {
1433					return;
1434				}
1435
1436				if ( yith_wapo.hide_order_price_if_zero && 0 === totalOrderPrice ) {
1437					totalOrderPriceHtml = '';
1438				}
1439
1440				if ('yes' === yith_wapo.replace_product_price && !isNaN(parseFloat(totalOrderPrice)) && $(yith_wapo.replace_product_price_class).length > 0) {
1441					$(yith_wapo.replace_product_price_class).html('<span class="woocommerce-Price-amount amount"><bdi>' + totalOrderPriceHtml + '</bdi></span>');
1442				}
1443			},
1444
1445			calculateProductPrice 			= function (suffixData) {
1446
1447				$(document).trigger('wapo-before-calculate-product-price');
1448
1449				var data_price_suffix = {
1450					'action': 'update_totals_with_suffix',
1451					'data': suffixData,
1452					'security': yith_wapo.addons_nonce,
1453				};
1454
1455				$.ajax(
1456					{
1457						url: yith_wapo.ajaxurl,
1458						type: 'post',
1459						data: data_price_suffix,
1460						success: function (response) {
1461							if (response) {
1462								let totalProductPrice = response['price_html'],
1463									totalOptionsPriceHTML = response['options_price_suffix'],
1464									totalOrderPriceHTML = response['order_price_suffix'],
1465									totalOrderPrice = suffixData.total_order_price;
1466
1467								$(wapoDOM.totalsBox.productPrice).html(totalProductPrice);
1468								$(wapoDOM.totalsBox.optionsPrice).html(totalOptionsPriceHTML);
1469								$(wapoDOM.totalsBox.orderPrice).html(totalOrderPriceHTML);
1470
1471								$(wapoDOM.addonsContainer).attr('data-order-price', totalOrderPrice);
1472
1473								replaceProductPrice(totalOrderPrice, totalOrderPriceHTML);
1474
1475								$(document).trigger('wapo-after-calculate-product-price', response);
1476							}
1477						}
1478					}
1479				);
1480			},
1481
1482			calculateTotalAddonsPrice 		= function (replacePrice = true) {
1483
1484				//Check logical conditions before calculate prices.
1485				conditionalLogicCheck();
1486
1487				if ('yes' === yith_wapo.hide_button_required) {
1488					var buttonClasses  = yith_wapo.dom.single_add_to_cart_button;
1489
1490					$(buttonClasses).hide();
1491
1492					var requiredFields = checkRequiredFields('hide'); // First check required options. True means all required options are already selected.
1493					var requiredMinMax = checkRequiredMinMax('hide');
1494
1495					if ( requiredFields && requiredMinMax ) {
1496						$(buttonClasses).fadeIn();
1497					}
1498				}
1499
1500				$(wapoDOM.totalPriceTable).css('opacity', '0.5');
1501
1502				var totalPrice = 0;
1503				var totalDefaultPrice = 0;
1504				var defaultProductPrice = parseFloat($(wapoDOM.addonsContainer).attr('data-product-price'));
1505				var totalPriceBoxOption = yith_wapo.total_price_box_option;
1506
1507				let selectedGifCardAmountButton = $('button.ywgc-amount-buttons.selected_button');
1508
1509				if (selectedGifCardAmountButton.length > 0) {
1510					defaultProductPrice = selectedGifCardAmountButton.data('price');
1511				}
1512
1513				var pricesCalculated = calculateAddonsPrice();
1514
1515				totalPrice        = pricesCalculated.totalPrice;
1516				totalDefaultPrice = pricesCalculated.totalPriceDefault;
1517
1518				// Plugin option "Total price box".
1519				if ('hide_options' === totalPriceBoxOption) {
1520					if (0 !== totalPrice) {
1521						$('#wapo-total-price-table .hide_options tr.wapo-total-options').fadeIn();
1522					} else {
1523						$('#wapo-total-price-table .hide_options tr.wapo-total-options').hide();
1524					}
1525				}
1526
1527				setTotalBoxPrices(defaultProductPrice, totalPrice, replacePrice, totalDefaultPrice);
1528
1529			};
1530
1531		productQuantityChange 			= function () {
1532			let inputNumber = $(this),
1533				inputVal = inputNumber.val(),
1534				productId = inputNumber.closest('.yith-wapo-option').data('product-id'),
1535				addToCartLink = inputNumber.closest('.option-add-to-cart').find('.add_to_cart_button'),
1536				productQuantity = 1,
1537				hrefCreated = '';
1538
1539			if (addToCartLink.length && productId) {
1540				if (inputVal > 1) {
1541					productQuantity = inputVal;
1542				}
1543
1544				hrefCreated = '?add-to-cart=' + productId + '&quantity=' + productQuantity;
1545
1546				addToCartLink.attr('href', hrefCreated);
1547			}
1548
1549		};
1550
1551		floatToWcPrice						= function ( price ){
1552
1553			var default_args = {
1554				decimal_sep: yith_wapo.decimal_sep,
1555				currency_position: yith_wapo.currency_position,
1556				currency_symbol: yith_wapo.currency_symbol,
1557				trim_zeros: yith_wapo.total_thousand_sep,
1558				num_decimals: parseInt(yith_wapo.number_decimals),
1559				html: true
1560			};
1561
1562			if (default_args.num_decimals > 0) {
1563				var wc_price_length = parseInt(price).toString().length;
1564				var wc_int_end_sep = parseInt(wc_price_length) + parseInt(default_args.num_decimals);
1565				price = price.toString().substr(0, wc_int_end_sep + 1);
1566				price = parseFloat(price).toFixed(yith_wapo.number_decimals);
1567			} else {
1568				price = parseInt(price);
1569			}
1570
1571			price = price.toString().replace('.', default_args.decimal_sep).replace( /(\d)(?=(\d{3})+(?!\d))/g, '$1' + default_args.trim_zeros );
1572
1573			var formatted_price = price;
1574			var formatted_symbol = default_args.html ? '<span class="woocommerce-Price-currencySymbol">' + default_args.currency_symbol + '</span>' : default_args.currency_symbol;
1575			if ('left' === default_args.currency_position) {
1576				formatted_price = formatted_symbol + formatted_price;
1577			} else if ('right' === default_args.currency_position) {
1578				formatted_price = formatted_price + formatted_symbol;
1579			} else if ('left_space' === default_args.currency_position) {
1580				formatted_price = formatted_symbol + ' ' + formatted_price;
1581			} else if ('right_space' === default_args.currency_position) {
1582				formatted_price = formatted_price + ' ' + formatted_symbol;
1583			}
1584			formatted_price = default_args.html ? '<span class="woocommerce-Price-amount amount">' + formatted_price + '</span>' : formatted_price;
1585
1586			return formatted_price;
1587
1588		}
1589
1590		wcPriceToFloat 					= function (wc_price) {
1591			let price = wc_price.replace(/(?![\.\,])\D/g, '')
1592				.replace(yith_wapo.total_thousand_sep, '')
1593				.replace(yith_wapo.decimal_sep, '.');
1594
1595			return parseFloat(price);
1596		},
1597			getDefaultProductPrice 			= function () {
1598				if ( yith_wapo.enableGetDefaultVariationPrice ) {
1599					let product_id = $( '.variations_form.cart' ).data( 'product_id' );
1600					let data = {
1601						'action' : 'get_default_variation_price',
1602						'product_id' : parseInt( product_id ),
1603						'security'  : yith_wapo.addons_nonce,
1604					};
1605					$.ajax(
1606						{
1607							url : yith_wapo.ajaxurl,
1608							type : 'post',
1609							data : data,
1610							success : function( response ) {
1611								if ( response ) {
1612									let defaultProductPrice = response['price_html'];
1613									let container = $( wapoDOM.addonsContainer );
1614									container.attr( 'data-product-price', response['current_price'] );
1615									container.attr( 'data-product-id', product_id );
1616
1617									if ( 'yes' === yith_wapo.replace_product_price && container.find('.yith-wapo-block').length ) {
1618										$( yith_wapo.replace_product_price_class ).html( defaultProductPrice );
1619									}
1620
1621								}
1622							},
1623							complete: function (){
1624							}
1625						}
1626					);
1627				}
1628			},
1629
1630			/**
1631			 * Check the default options selected on load page to replace the image.
1632			 */
1633			checkDefaultOptionsOnLoad 	= function () {
1634				let optionsSelected = $('.yith-wapo-addon:not(.conditional_logic):not(.hidden) .yith-wapo-option.selected');
1635				$(optionsSelected).each(
1636					function () {
1637						let option = $(this);
1638						replaceImageAction(option);
1639					}
1640				);
1641			},
1642
1643			resetAddons 			= function (event, params) {
1644
1645				if ('yith_wccl' === params) {
1646					return;
1647				}
1648
1649				if (!firstVariationLoading) {
1650					firstVariationLoading = true;
1651					return;
1652				}
1653
1654				getDefaultProductPrice();
1655
1656				$(document).trigger('yith-wapo-reset-addons');
1657
1658			},
1659
1660			foundVariation = function (event, variation) {
1661				// Skip found_variation in Composite products or YITH Color and Label loop form
1662				if (event.target.classList.value === 'variations_form cart in_loop initialized' || $('form.cart.ywcp').length) return;
1663
1664				updateContainerProductPrice(variation);
1665
1666				$(document).trigger('yith-wapo-reload-addons');
1667			},
1668
1669			reloadAddons 			= function (event, productPrice = '') {
1670				var addons = $(wapoDOM.formCart).serializeArray(),
1671					data = {
1672						'action'          : 'live_print_blocks',
1673						'addons'          : addons,
1674						'currency'        : yith_wapo.woocommerce_currency,
1675						'current_language': yith_wapo.currentLanguage,
1676						'security'        : yith_wapo.addons_nonce,
1677					};
1678
1679				if (productPrice != '') {
1680					data.price = productPrice;
1681				}
1682
1683				$.ajax(
1684					{
1685						url: yith_wapo.ajaxurl,
1686						type: 'post',
1687						data: data,
1688						beforeSend: function () {
1689							$(wapoDOM.addonsContainer).css('opacity', '0.5');
1690						},
1691						error: function (xhr, status, error) {
1692							// Code to handle errors.
1693							console.error('AJAX request failed: ', status, error);
1694						},
1695						success: function (response) {
1696							let html = response['html'] ?? '';
1697							var quantities = response['quantities'] ?? '';
1698
1699							if (html !== '') {
1700								$(wapoDOM.addonsContainer).html(html);
1701							}
1702
1703							let addonsSelected = response['addons'] ?? '';
1704							if (addonsSelected !== '') {
1705								addonsSelection(addonsSelected);
1706							}
1707							if (quantities) {
1708								addonsQuantity(quantities);
1709							}
1710
1711							$( 'form.cart #yith-wapo-container select' ).trigger('change');
1712
1713							$('form.cart').trigger('yith-wapo-after-reload-addons');
1714
1715						},
1716						complete: function (event) {
1717
1718							$(wapoDOM.addonsContainer).attr('data-order-price', 0);
1719							calculateTotalAddonsPrice();
1720
1721							$(wapoDOM.addonsContainer).css('opacity', '1');
1722
1723						}
1724					}
1725				);
1726			},
1727
1728			addonsQuantity 				= function (quantities) {
1729
1730				$.each($(wapoDOM.addonsContainer).find('.qty.wapo-product-qty'), function () {
1731					let separator = 'yith_wapo_product_qty';
1732					let qtyID = $(this).attr('id');
1733					let identificator = qtyID.replace(separator, '').replace('[', '').replace(']', '');
1734
1735					if (identificator in quantities) {
1736						let qty = quantities[identificator];
1737						$(this).val(qty).trigger('change');
1738					}
1739				});
1740
1741			},
1742
1743			setQuantity 				= function (quantity) {
1744				$( 'div.quantity input.input-text.qty:not(.wapo-product-qty) ' ).val( quantity );
1745			}
1746
1747		removeUploadedFile 			= function (ev) {
1748			let element               = ev.target,
1749				uploadedFileContainer = $(element).closest('.yith-wapo-uploaded-file-element'),
1750				maxMultiple           = $(element).closest('.yith-wapo-option').data('max-multiple'),
1751				lengthUploads         = $(element).closest('.yith-wapo-ajax-uploader').find('.yith-wapo-uploaded-file-element').length,
1752				uploaderContainer     = $(element).closest('.yith-wapo-ajax-uploader').find('.yith-wapo-ajax-uploader-container'),
1753				parentInputHidden     = $(element).closest('.yith-wapo-option').find('input[type="hidden"].upload-parent'),
1754				fileInput             = $(element).closest('.yith-wapo-option').find('input[type="file"]');
1755
1756			uploadedFileContainer.remove();
1757
1758			fileInput.val('');
1759
1760			if ('undefined' === typeof maxMultiple || lengthUploads - 1 < maxMultiple) { // If max is not defined or length is less than max, show the Upload button.
1761				uploaderContainer.fadeIn();
1762			}
1763			if (lengthUploads - 1 <= 0) {
1764				parentInputHidden.val('');
1765				calculateTotalAddonsPrice();
1766			}
1767
1768		},
1769
1770			maybeCalculateTotals 			= function(addonOpt) {
1771
1772				let isQuantityButton = false;
1773				if (addonOpt.is('input[type="number"]')) {
1774					isQuantityButton = addonOpt.attr('name').indexOf('yith_wapo_product_qty') == 0 && addonOpt.hasClass('qty') || // Is quantity field from add-on type Product.
1775					'quantity' === addonOpt.attr('name') && addonOpt.hasClass('qty') // Is WooCommerce quantity field.
1776						? true : false;
1777				}
1778
1779				let selectOpt = '';
1780				if (addonOpt.is('select')) {
1781					selectOpt = addonOpt.closest('.yith-wapo-addon.yith-wapo-addon-type-select');
1782				}
1783
1784				// Has price or is the WC Qty Button or is a Select add-on (selector is unique).
1785				if (addonOptHasPrice(addonOpt) || isQuantityButton || (selectOpt && 'select' === selectOpt.data('addon-type'))) {
1786					calculateTotalAddonsPrice();
1787				}
1788			},
1789
1790			addonOptHasPrice 			= function(addonOpt = null) {
1791
1792				let price = getOptionPrice(addonOpt),
1793					sale_price = getOptionSalePrice(addonOpt);
1794
1795				if ((typeof price === 'undefined' || '' === price) && (typeof sale_price === 'undefined' || '' === sale_price)) {
1796					return false;
1797				}
1798
1799				return true;
1800			},
1801
1802			/** Get price of an option */
1803			getOptionPrice 				= function(addonOpt) {
1804
1805				let price = addonOpt.data('price');
1806
1807				return price;
1808
1809			},
1810
1811			/** Get sale price of an option */
1812			getOptionSalePrice 			= function(addonOpt) {
1813
1814				let price = addonOpt.data('sale-price');
1815
1816				return price;
1817
1818			},
1819
1820			/**
1821			 * Check min and max values for the sum of add-ons type Number.
1822			 */
1823			checkNumbersTotalValues 	 = function () {
1824
1825				var numberAddons = $('#yith-wapo-container .yith-wapo-addon-type-number:not(.hidden).numbers-check'),
1826					isError = false;
1827
1828				numberAddons.each(function (index) {
1829
1830					let numberAddon = $(this),
1831						numberMin = numberAddon.data('numbers-min'),
1832						numberMax = numberAddon.data('numbers-max'),
1833						totalCount = 0,
1834						errorCheck = false,
1835						errorMessage = '',
1836						optionsElement = numberAddon.find('.options');
1837
1838					// Reset
1839					if (optionsElement.hasClass('error-message')) {
1840						optionsElement.removeClass('error-message');
1841					}
1842					numberAddon.find('.yith-wapo-numbers-error-message').remove();
1843
1844					numberAddon.find('input[type="number"]').each(function () {
1845						let number = $(this).val();
1846						if ('undefined' === number || '' === number) {
1847							return true; // continue
1848						}
1849						totalCount += parseFloat(number);
1850					});
1851
1852					if ('undefined' !== typeof numberMin && totalCount < numberMin) {
1853						errorCheck = true;
1854						errorMessage = yith_wapo.messages.minErrorMessage + ' ' + numberMin;
1855					}
1856					;
1857
1858					if ('undefined' !== typeof numberMax && totalCount > numberMax) {
1859						errorCheck = true;
1860						errorMessage = yith_wapo.messages.maxErrorMessage + ' ' + numberMax;
1861					}
1862					;
1863
1864					if (errorCheck) {
1865						optionsElement.addClass('error-message');
1866						numberAddon.append($('<small class="yith-wapo-numbers-error-message">' + errorMessage + '</small>'));
1867						isError = true;
1868						$('html, body').animate({scrollTop: numberAddon.offset().top - 50}, 500);
1869					}
1870
1871				});
1872
1873				$(document).trigger('yith_wapo_check_number_total_values');
1874
1875				if (isError) {
1876					return false;
1877				}
1878
1879				return true;
1880
1881			};
1882
1883		replaceImageAction 			= function( optionWrapper, reset = false ) {
1884
1885			var defaultPath     = yith_wapo.replace_image_path;
1886			var zoomMagnifier   = '.yith_magnifier_zoom_magnifier, .zoomWindowContainer .zoomWindow';
1887			var replaceImageURL = optionWrapper.data( 'replace-image' );
1888
1889			if ( null === replaceImageURL || ! reset && $( defaultPath ).attr( 'src' ) === replaceImageURL ) {
1890				return;
1891			}
1892
1893			if ( typeof optionWrapper.data( 'replace-image' ) !== 'undefined' && optionWrapper.data( 'replace-image' ) != '' ) {
1894
1895				// save original image for the reset
1896				if ( typeof( $( defaultPath ).attr( 'wapo-original-img' ) ) == 'undefined' ) {
1897					$( defaultPath ).attr( 'wapo-original-img', $( defaultPath ).attr( 'src' ) );
1898					if ( $( zoomMagnifier ).length ) {
1899						$( zoomMagnifier ).attr( 'wapo-original-img', $( zoomMagnifier ).css( 'background-image' ).slice( 4, -1 ).replace( /"/g, "" ) );
1900					}
1901				}
1902
1903				$( wapoDOM.addonImageInput ).val( replaceImageURL );
1904
1905				$( zoomMagnifier ).css( 'background-image', 'url(' + replaceImageURL + ')' );
1906
1907				replaceAddonImage( replaceImageURL );
1908
1909			}
1910
1911			if ( reset && typeof( $( defaultPath ).attr( 'wapo-original-img' ) ) != 'undefined' ) {
1912
1913				var originalImage = $( defaultPath ).attr( 'wapo-original-img' );
1914				var originalZoom  = $( zoomMagnifier ).attr( 'wapo-original-img' );
1915
1916				var currentImage = $( wapoDOM.addonImageInput ).val();
1917
1918				$( wapoDOM.addonImageInput ).val( '' );
1919
1920				$( wapoDOM.addonsContainer ).find( '.yith-wapo-addon:not(.yith-wapo-addon-type-select):not(.conditional_logic.hidden) .yith-wapo-option.selected, .yith-wapo-addon-type-select:not(.conditional_logic.hidden) .yith-wapo-option-value' ).each(
1921					function( index, element ) {
1922						let option = $( element );
1923						option = option.is( 'select' ) ? option.find( ':selected' ) : option;
1924						// Check if one option is still selected and has a image to replace, then do not change to default image.
1925						if ( option.data( 'replace-image' ) && ( option.hasClass( 'selected' ) || option.is( 'option' ) ) ) {
1926							originalImage = option.data( 'replace-image' );
1927							$( wapoDOM.addonImageInput ).val( originalImage );
1928						}
1929					}
1930				);
1931
1932				if ( currentImage === originalImage ) { // If the replaced image is the same, we stop the execution.
1933					return;
1934				}
1935
1936				$( zoomMagnifier ).css( 'background-image', 'url(' + originalZoom + ')' );
1937
1938				replaceAddonImage( originalImage );
1939
1940			}
1941		}
1942
1943
1944		checkMaxSelected 			= function (element) {
1945
1946			var option = element.closest('.yith-wapo-option'),
1947				addon = element.closest('.yith-wapo-addon'),
1948				maxVal = addon.data('max'),
1949				optionsSelected = addon.find('.yith-wapo-option.selected').length;
1950
1951			if ('' === maxVal || 0 === maxVal) {
1952				return true;
1953			}
1954
1955			if (option.hasClass('selected')) {
1956				optionsSelected--;
1957			} else {
1958				optionsSelected++;
1959			}
1960
1961			if (optionsSelected > maxVal) {
1962				return false;
1963			}
1964
1965			return true;
1966
1967		},
1968
1969		addonImageClicked = function () {
1970			var addon  = $(this).closest( '.yith-wapo-addon' );
1971			if ( 'label' === addon.data( 'addon-type' ) ) {
1972				return;
1973			}
1974			var option = $(this).closest( '.yith-wapo-option' );
1975
1976			option.find( "input[name^='yith_wapo']" ).click();
1977		},
1978
1979		checkboxOnChange				= function() {
1980			let checkboxInput   = $( this );
1981			let checkboxButton  = checkboxInput.closest( '.checkboxbutton' );
1982			let checkboxOption  = checkboxInput.closest( '.yith-wapo-option' );
1983			let checkboxOptions = checkboxOption.parent();
1984
1985			let resetImage      = false;
1986
1987			if ( checkboxOption.data( 'replace-image' ).length <= 0 ) {
1988				resetImage = true;
1989			}
1990
1991			let isChecked = checkboxInput.attr( 'checked' );
1992
1993			if ( 'checked' !== isChecked ) {
1994
1995				// Single selection
1996				if ( checkboxOption.hasClass( 'selection-single' ) ) {
1997					// Disable all.
1998					checkboxOptions.find( 'input' ).attr( 'checked', false ).prop( 'checked', false );;
1999					checkboxOptions.find( '.selected, .checked' ).removeClass( 'selected checked' );
2000				}
2001
2002				// Enable only the current option.
2003				checkboxInput.attr( 'checked', true ).prop( 'checked', true );
2004				checkboxOption.addClass( 'selected' );
2005				checkboxButton.addClass( 'checked' );
2006
2007			} else {
2008				checkboxInput.attr( 'checked', false ).prop( 'checked', false );
2009				checkboxOption.removeClass( 'selected' );
2010				checkboxButton.removeClass( 'checked' );
2011
2012				resetImage = true;
2013			}
2014
2015			replaceImageAction( checkboxOption, resetImage );
2016
2017		}
2018
2019		inputOnChange				= function() {
2020
2021			var input = $(this);
2022			var option = input.closest( '.yith-wapo-option' );
2023			var disabled = false;
2024
2025			// Selection type single or multiple.
2026
2027			if ( '' !== input.val() && option.hasClass( 'selection-single' ) ) {
2028				disabled = true;
2029				option.siblings().find( 'input' ).prop( 'disabled', true );
2030			}
2031
2032			option.siblings().find( 'input' ).prop( 'disabled', disabled );
2033
2034		}
2035
2036		textareaOnChange				= function() {
2037
2038			var input = $(this);
2039			var option = input.closest( '.yith-wapo-option' );
2040			var disabled = false;
2041
2042			// Selection type single or multiple.
2043
2044			if ( '' !== input.val() && option.hasClass( 'selection-single' ) ) {
2045				disabled = true;
2046				option.siblings().find( 'textarea' ).prop( 'disabled', true );
2047			}
2048
2049			option.siblings().find( 'textarea' ).prop( 'disabled', disabled );
2050
2051		}
2052
2053		labelsOnChange 				= function() {
2054			let reset = false;
2055			/**
2056			 * Check max value available.
2057			 * @type {boolean}
2058			 */
2059			var maxSelected = checkMaxSelected($(this));
2060
2061			if (!maxSelected) {
2062				$(this).prop('checked', false);
2063				return false;
2064			}
2065
2066			var optionWrapper = $(this).closest('.yith-wapo-option');
2067
2068			if ( optionWrapper.data( 'replace-image' ).length <= 0 ) {
2069				reset = true;
2070			}
2071
2072			if ($(this).is(':checked')) {
2073
2074
2075				// Single selection
2076				if (optionWrapper.hasClass('selection-single')) {
2077					// Disable all
2078					optionWrapper.parent().find('input').prop('checked', false);
2079					optionWrapper.parent().find('.selected').removeClass('selected');
2080				}
2081				optionWrapper.addClass('selected');
2082				// Check checkbox of selected one.
2083				optionWrapper.find('input').prop('checked', true);
2084
2085			} else {
2086				reset = true;
2087				optionWrapper.removeClass('selected');
2088			}
2089
2090			// Replace image
2091			replaceImageAction(optionWrapper, reset);
2092
2093		}
2094
2095		colorOnChange 			= function() {
2096			let reset = false;
2097			var optionWrapper = $(this).closest('.yith-wapo-option');
2098
2099			if ( optionWrapper.data( 'replace-image' ).length <= 0 ) {
2100				reset = true;
2101			}
2102
2103			if ( $( this ).is( ':checked' ) ) {
2104				optionWrapper.addClass( 'selected' );
2105
2106				// Single selection
2107				if ( optionWrapper.hasClass( 'selection-single' ) ) {
2108					// Disable all
2109					optionWrapper.parent().find( 'input' ).prop( 'checked', false );
2110					optionWrapper.parent().find( '.selected' ).removeClass( 'selected' );
2111					// Enable only the current option
2112					optionWrapper.find( 'input' ).prop( 'checked', true );
2113					optionWrapper.addClass( 'selected' );
2114				}
2115
2116			} else {
2117				reset = true;
2118				optionWrapper.removeClass( 'selected' );
2119			}
2120			// Reset image.
2121			replaceImageAction( optionWrapper, reset );
2122
2123		},
2124			productOnChange 		= function() {
2125
2126				var optionWrapper = $(this).closest('.yith-wapo-option');
2127
2128				if ( $( this ).is( ':checked' ) ) {
2129
2130					optionWrapper.addClass( 'selected' );
2131
2132					// Single selection.
2133					if ( optionWrapper.hasClass( 'selection-single' ) ) {
2134						// Disable all.
2135						optionWrapper.parent().find( 'input' ).prop( 'checked', false );
2136						optionWrapper.parent().find( '.selected' ).removeClass( 'selected' );
2137						// Enable only the current option.
2138						optionWrapper.find( 'input' ).prop( 'checked', true );
2139						optionWrapper.addClass( 'selected' );
2140					}
2141
2142				} else {
2143					optionWrapper.removeClass( 'selected' );
2144				}
2145
2146			},
2147
2148			radioOnChange			= function() {
2149				let reset = false;
2150				var optionWrapper = $(this).closest('.yith-wapo-option');
2151
2152				if ( optionWrapper.data( 'replace-image' ).length <= 0 ) {
2153					reset = true;
2154				}
2155
2156				optionWrapper.addClass( 'selected' );
2157
2158				// Remove selected siblings
2159				optionWrapper.siblings().removeClass( 'selected' );
2160
2161				// Replace image
2162				replaceImageAction( optionWrapper, reset );
2163
2164			},
2165
2166			selectOnChange			= function() {
2167				let optionWrapper    = $( this ).closest( '.yith-wapo-addon' );
2168				let selectedOption   = $( this ).find( 'option:selected' );
2169				let optionImageBlock = optionWrapper.find( 'div.image-container' );
2170				let resetImage 		 = false;
2171
2172				// Description & Image.
2173				var optionImage       = selectedOption.data( 'image' );
2174				var optionDescription = selectedOption.data( 'description' );
2175				var option_desc       = optionWrapper.find( '.option-description' );
2176
2177				if ( typeof optionImage !== 'undefined' && optionImage ) {
2178					optionImage = '<img src="' + optionImage + '" style="max-width: 100%">';
2179					optionImageBlock.html( optionImage );
2180				}
2181
2182				if ( 'default' === selectedOption.val() || '' === optionImage ) {
2183					optionImageBlock.hide();
2184				} else {
2185					optionImageBlock.fadeIn();
2186				}
2187
2188				if ( 'undefined' === typeof optionDescription ) {
2189					option_desc.empty();
2190				} else {
2191					option_desc.html( optionDescription );
2192				}
2193
2194				// Replace image
2195				if ( ! selectedOption.data( 'replace-image' ) ){
2196					resetImage = true;
2197				}
2198
2199				replaceImageAction( selectedOption, resetImage );
2200
2201			},
2202
2203			fileOnChange = function (e) {
2204				$(this).closest('.yith-wapo-ajax-uploader').css('opacity', '1');
2205
2206				var input = $(this),
2207					uploaderElement = input.closest('.yith-wapo-option').find('.yith-wapo-ajax-uploader'),
2208					fileList = input[0].files,
2209					correctFiles = checkBeforeUploadFiles(uploaderElement, fileList); //Boolean
2210
2211				if (correctFiles) {
2212					uploadFiles(fileList, uploaderElement);
2213				}
2214			}
2215
2216		toggleElement 		= function (e) {
2217			e.preventDefault();
2218			let addon_title = $(this).find('.wapo-addon-title');
2219			let addon_el = addon_title.closest('.yith-wapo-addon');
2220
2221			if (addon_el.hasClass('toggle-open')) {
2222				addon_el.removeClass('toggle-open').addClass('toggle-closed');
2223			} else {
2224				addon_el.removeClass('toggle-closed').addClass('toggle-open');
2225			}
2226			if (addon_title.hasClass('toggle-open')) {
2227				addon_title.removeClass('toggle-open').addClass('toggle-closed');
2228			} else {
2229				addon_title.removeClass('toggle-closed').addClass('toggle-open');
2230			}
2231
2232			addon_el.find('.options-container').toggle('fast');
2233
2234			$(document).trigger('yith_proteo_inizialize_html_elements');
2235		},
2236
2237			updateContainerProductPrice = function (variation) {
2238
2239				// Do not allow updating the price if product bundle form exists.
2240				if ($('.cart.yith-wcpb-bundle-form').length || variation.variation_id !== parseInt($('.variation_id').val())) {
2241					return;
2242				}
2243
2244				let container = $(wapoDOM.addonsContainer),
2245					new_product_price = 0;
2246				if (typeof (variation.display_price) !== 'undefined') {
2247					new_product_price = variation.display_price;
2248					// Check if variation price and price_html are different, use the last one
2249					if ('yes' === yith_wapo.use_price_html_on_variations && typeof (variation.price_html) !== 'undefined') {
2250						let new_product_price_html = $(variation.price_html).find('> .amount bdi').text();
2251						new_product_price_html = wcPriceToFloat(new_product_price_html);
2252						if (!isNaN(new_product_price_html) && new_product_price !== new_product_price_html) {
2253							new_product_price = new_product_price_html;
2254						}
2255					}
2256				}
2257				container.attr('data-product-price', new_product_price);
2258				container.attr( 'data-default-product-price', variation.default_variation_price);
2259				container.attr('data-product-id', variation.variation_id);
2260
2261			},
2262
2263			yithWcMeasurementPC_Compatibility = function() {
2264				var price = $( '#price_calculator.wc-measurement-price-calculator-price-table .product_price .amount' ).text();
2265				price     = wcPriceToFloat( price );
2266
2267				if ( ! isNaN( price ) ) {
2268					let container = $( wapoDOM.addonsContainer );
2269
2270					container.attr( 'data-product-price', price );
2271					$( document ).trigger( 'yith-wapo-reload-addons', [ price ] );
2272				}
2273			},
2274			ajaxUploaderOnDrop 		= function (e) {
2275				e.stopPropagation();
2276				e.preventDefault();
2277
2278				$(this).css('opacity', '1');
2279
2280				var input = $(this).closest('.yith-wapo-option').find('input.file'),
2281					uploaderElement = $(this),
2282					fileList = e.originalEvent.dataTransfer.files,
2283					correctFiles = checkBeforeUploadFiles(uploaderElement, fileList); //Boolean
2284
2285				if (correctFiles) {
2286					uploadFiles(fileList, uploaderElement);
2287				}
2288			},
2289			checkBeforeUploadFiles 	= function (uploaderElement, fileList) {
2290				let countAlreadyUploaded = $(uploaderElement).find('.yith-wapo-uploaded-file-element').length,
2291					totalUploads = countAlreadyUploaded + fileList.length,
2292					maxUploadsAllowed = $(uploaderElement).closest('.yith-wapo-option').data('max-multiple'),
2293					allowMultiple = $(uploaderElement).closest('.yith-wapo-option').hasClass('allow-multiple');
2294
2295				if (!allowMultiple && totalUploads > 1) {
2296					alert(yith_wapo.messages.maxFilesAllowed + '1');
2297					return false;
2298				}
2299
2300				if ('undefined' !== typeof maxUploadsAllowed && totalUploads > maxUploadsAllowed) {
2301					alert(yith_wapo.messages.maxFilesAllowed + maxUploadsAllowed);
2302					return false;
2303				}
2304
2305				for (var item in fileList) {
2306					if ($.isNumeric(item)) {
2307
2308						let file = fileList[item],
2309							message = '',
2310							isError = false;
2311
2312						if (!yith_wapo.upload_allowed_file_types.includes(file.name.split('.').pop().toLowerCase())) {
2313							//message = 'Error - not supported extension!';
2314							message = yith_wapo.messages.noSupportedExtension;
2315							isError = true;
2316						}
2317
2318						if (parseFloat(file.size) >= parseFloat(yith_wapo.upload_max_file_size * 1024 * 1024)) {
2319							//message = 'Error - file size for ' + file.name + ' - max ' + yith_wapo.upload_max_file_size + ' MB allowed!';
2320							message = wapoSprintf(yith_wapo.messages.maxFileSize, file.name, yith_wapo.upload_max_file_size);
2321							isError = true;
2322						}
2323
2324						if (isError) {
2325							alert(message);
2326							return false;
2327						}
2328
2329					}
2330				}
2331
2332				return true;
2333			},
2334
2335			wapoSprintf 		= function (format, ...values) {
2336				return format.replace(/%([sd])/g, function (match, tipo) {
2337					if (tipo === 's') {
2338						return values.shift();
2339					} else if (tipo === 'd') {
2340						const valor = values.shift();
2341						return Number.isInteger(valor) ? valor.toString() : '';
2342					}
2343					return match;
2344				});
2345			},
2346
2347			uploadFiles 		= function (fileList, uploaderElement) {
2348
2349				for (var count = 0; count < fileList.length; count++) {
2350					let uploadedFileContainer = uploaderElement.find('.yith-wapo-uploaded-file'),
2351						currentIndex = uploaderElement.find('.yith-wapo-uploaded-file-element').last().data('index') + 1;
2352					if (isNaN(currentIndex) || 'undefined' == typeof currentIndex) {
2353						currentIndex = 0;
2354					}
2355					appendNewUploadedFile(count, fileList, uploadedFileContainer, currentIndex);
2356
2357					if (count == fileList.length - 1) {
2358						uploadedFileContainer.show();
2359
2360						uploadSingleFile(fileList, 0, uploaderElement);
2361					}
2362				}
2363
2364			},
2365			appendNewUploadedFile 	= function ( count, fileList, uploadedFileContainer, currentIndex ) {
2366				let exactSize = calculateExactFileSize( fileList[count] ),
2367					fileName = fileList[count].name,
2368					optionId = $( uploadedFileContainer ).closest( '.yith-wapo-option' ).data( 'option-id' );
2369
2370				var newElement =
2371					'<div class="yith-wapo-uploaded-file-element uploaded-file-'+currentIndex+'" data-index="' + currentIndex + '">' +
2372					'<div class="yith-wapo-uploaded-file-info">' +
2373					'<span class="info">' +
2374					'<label class="file-name"><span>' + fileName + '</span></label>' +
2375					'<span class="file-size">' + exactSize + '</span>' +
2376					'</span>' +
2377					'<i class="remove yith-plugin-fw__action-button__icon yith-icon yith-icon-trash" style="display:none"></i>' +
2378					'</div>' +
2379					'<div class="yith-wapo-loader-container" id="progressbar'+currentIndex+'">' +
2380					'<div class="yith-wapo-loader-label"></div>' +
2381					'<div class="yith-wapo-loader" role="progressbar"></div>' +
2382					'</div>' +
2383					'<input type="hidden" id="yith-wapo-' + optionId + '" class="option yith-wapo-option-value" name="yith_wapo[][' + optionId + '][]" >' +
2384					'</div>';
2385
2386				uploadedFileContainer.append( newElement );
2387			},
2388			uploadSingleFile 		= function (fileList, fileCount, uploaderElement, isFinalUpload = false, currentIndex = 0) {
2389
2390				if (0 === parseInt(currentIndex) && $(uploaderElement).find('.yith-wapo-uploaded-file-element.completed').length) {
2391					currentIndex = $(uploaderElement).find('.yith-wapo-uploaded-file-element.completed').last().data('index') + 1;
2392				}
2393
2394				var fileLength = fileList.length - 1,
2395					currentFile = fileList[fileCount],
2396					data = new FormData(),
2397					option = $(uploaderElement).closest('.yith-wapo-option'),
2398					maxMultiple = option.data('max-multiple'),
2399					uploadedFileContainer = $(uploaderElement).find('.yith-wapo-uploaded-file'),
2400					uploaderContainer = $(uploaderElement).find('.yith-wapo-ajax-uploader-container'),
2401					uploadedFileElement = $(uploaderElement).find('.yith-wapo-uploaded-file-element[data-index="' + currentIndex + '"]'),
2402					removeIcon = $(uploaderElement).find('.yith-wapo-uploaded-file-info .remove'),
2403					progressLabel = $(uploadedFileElement).find('.yith-wapo-loader-container .yith-wapo-loader-label'),
2404					progressbar = $(uploadedFileElement).find('.yith-wapo-loader-container .yith-wapo-loader');
2405
2406				data.append('action', 'yith_wapo_upload_file');
2407				data.append( 'security', yith_wapo.addons_nonce);
2408				data.append('currentFile', currentFile);
2409
2410				$.ajax(
2411					{
2412						url: yith_wapo.ajaxurl,
2413						type: 'POST',
2414						contentType: false,
2415						processData: false,
2416						async: true,
2417						dataType: 'json',
2418						data: data,
2419						xhr: function () {
2420							var xhr = $.ajaxSettings.xhr();
2421							if (xhr.upload) {
2422								xhr.upload.addEventListener('progress', function (event) {
2423									var currentPercent = 0;
2424									if (event.lengthComputable) {
2425										currentPercent = Math.ceil(event.loaded / event.total * 100);
2426									}
2427									progressbar.progressbar({
2428										value: currentPercent,
2429									});
2430
2431								}, false);
2432								xhr.addEventListener("progress", function (e) {
2433									if (fileList.length == 1 || isFinalUpload) {
2434										if (option.hasClass('allow-multiple')) {
2435											let countUploadedFiles = $(uploaderElement).find('.yith-wapo-uploaded-file-element').length;
2436											if ('undefined' === typeof maxMultiple || 'undefined' !== typeof maxMultiple && countUploadedFiles < maxMultiple) {
2437												uploaderContainer.fadeIn();
2438											}
2439										}
2440
2441										removeIcon.fadeIn();
2442										let parentInputHidden = $(uploaderElement).closest('.yith-wapo-option').find('input[type="hidden"].upload-parent');
2443										parentInputHidden.val(1);
2444
2445										//Calculate prices after all the uploads.
2446										calculateTotalAddonsPrice();
2447
2448									}
2449								}, false);
2450								return xhr;
2451							}
2452							return xhr;
2453						},
2454						beforeSend: function () {
2455							uploaderContainer.hide();
2456							uploadedFileContainer.show();
2457
2458							progressbar.progressbar({
2459								change: function () {
2460									progressLabel.text(progressbar.progressbar('value') + "%" + ' ' + yith_wapo.i18n.uploadPercentageDoneString);
2461								},
2462								complete: function (e) {
2463									$(e.target).closest('.yith-wapo-loader-container').fadeOut();
2464								}
2465							});
2466							progressbar.show();
2467							$('.cart').block({message: null, overlayCSS: {opacity: 0}});
2468
2469						},
2470						success: function (res, status) {
2471							if (status == 'success') {
2472
2473
2474								let hiddenElement = uploadedFileElement.find('input[type="hidden"]'),
2475									infoElement = uploadedFileElement.find('.yith-wapo-uploaded-file-info span.info'),
2476									resType = res.type;
2477
2478
2479								hiddenElement.val(res.url); // Add the value to the hidden input for $_POST['yith_wapo']
2480
2481								if ('image/jpeg' === resType || 'image/jpg' === resType || 'image/png' === resType || 'image/gif' === resType || 'image/svg+xml' === resType) {
2482									infoElement.append('<img src="' + res.url + '" class="yith-wapo-img-uploaded" alt="Image uploaded from YITH Product Add-ons uploader">').fadeIn(); // Show the image preview.
2483								}
2484
2485								if (fileCount < fileLength) {
2486									if (fileCount == fileLength - 1) {
2487										isFinalUpload = true;
2488									}
2489									uploadSingleFile(fileList, fileCount + 1, uploaderElement, isFinalUpload, currentIndex + 1);
2490								}
2491							}
2492						},
2493						complete: function () {
2494							$(uploadedFileElement).addClass('completed');
2495							console.log('Single file upload completed!');
2496							$('.cart').unblock({message: null});
2497						},
2498						error: function (xhr, textStatus, errorThrown) {
2499							let userMessage = 'Error uploading the file.';
2500
2501							if (xhr.status === 400 && xhr.responseText.includes('Content-Length')) {
2502								userMessage = 'Error with the size of the file. ';
2503							}
2504
2505							$( '.yith-wapo-uploaded-file-element.uploaded-file-' + currentIndex ).find( 'i.remove' ).trigger( 'click' );
2506
2507							console.error('AJAX error status:', textStatus);
2508							console.error('Error thrown:', errorThrown);
2509							console.error('Response text:', xhr.responseText);
2510
2511							alert(userMessage);
2512						},
2513
2514
2515					});
2516
2517			},
2518			calculateExactFileSize 		= function( file ) {
2519				let exactSize  = 0;
2520				let file_size  = file.size;
2521				let file_types = ['Bytes', 'KB', 'MB', 'GB'],
2522					i              = 0;
2523				while ( file_size > 900 ) {
2524					file_size /= 1024;
2525					i++;
2526				}
2527				exactSize = ( Math.round( file_size * 100 ) / 100 ) + ' ' + file_types[i];
2528
2529				return exactSize;
2530			},
2531			checkRequiredMinMax 		= function (action = '') {
2532
2533				let canProceed = true;
2534
2535				// Check force user selection for add-on type Select.
2536				if (!checkRequiredSelect(action)) {
2537					canProceed = false;
2538				}
2539
2540				// Required feature.
2541				if (action !== 'hide') {
2542
2543					let required = checkRequiredFields('highlight');
2544					if (!required) {
2545						canProceed = false;
2546					}
2547				}
2548				var requiredOptions = 0;
2549				var checkMinMaxVal = '';
2550				var apply_submit = action !== 'hide';
2551				$('form.cart .yith-wapo-addon:not(.hidden)').each(
2552					function () {
2553						checkMinMaxVal = checkMinMax($(this), apply_submit);
2554						if (checkMinMaxVal > 0) {
2555							requiredOptions += checkMinMaxVal;
2556						}
2557					}
2558				);
2559				if (action !== 'hide') {
2560					if (requiredOptions > 0) {
2561						canProceed = false;
2562					}
2563				}
2564
2565				return canProceed;
2566			},
2567			/**
2568			 * Check min/max for each option
2569			 * @param addon
2570			 * @param submit
2571			 * @returns {number}
2572			 */
2573			checkMinMax 		= function(addon, submit = false) {
2574
2575				var addonType = addon.data('addon-type');
2576				var minValue = addon.data('min');
2577				var maxValue = addon.data('max');
2578				var exaValue = addon.data('exa');
2579				var errorMessage = addon.find('.min-error-message'),
2580					addonTitle = addon.find('.wapo-addon-title'),
2581					numberOfChecked = 0;
2582
2583				let toggle_addon_title = addon.find('.wapo-addon-title.toggle-closed');
2584				addonTitle.removeClass('wapo-error');
2585
2586				if ('select' === addonType || ('' === minValue && '' === exaValue && '' === maxValue)) {
2587					return;
2588				}
2589
2590				// Number / Text / TextArea
2591				if ('number' === addonType || 'text' === addonType || 'textarea' === addonType) {
2592					$(addon).find('.yith-wapo-option-value').each(
2593						function (index) {
2594							let numberValue = $(this).val();
2595							if (numberValue.length) {
2596								numberOfChecked++; // Summing number of filled.
2597							}
2598						}
2599					);
2600
2601					if (maxValue && numberOfChecked > maxValue) {
2602						let optionsElement = $(addon).find('.options-container');
2603						if (!optionsElement.find('.max-selected-error').length) {
2604							optionsElement.append('<p class="max-selected-error">' + yith_wapo.i18n.maxOptionsSelectedMessage + '</p>');
2605							addonTitle.addClass('wapo-error');
2606						}
2607						return 1;
2608					}
2609
2610				} else if ( 'file' === addonType ) {
2611					numberOfChecked = $(addon).find( '.yith-wapo-uploaded-file' ).has('.yith-wapo-uploaded-file-element').length;
2612				} else {
2613					// Checkbox / Radio
2614					numberOfChecked = addon.find('input:checkbox:checked, input:radio:checked').length; // Sum of number of checked.
2615				}
2616
2617				// Exactly Values
2618				if (exaValue > 0) {
2619
2620					let optionsToSelect = 0;
2621
2622					if (exaValue == numberOfChecked) {
2623						addon.removeClass('required-min').find('.min-error').hide();
2624						addon.find('input:checkbox').not(':checked');
2625					} else {
2626						// If click on add to cart button.
2627						if (submit) {
2628							optionsToSelect = exaValue - numberOfChecked;
2629							addon.addClass('required-min');
2630							addon.find('.min-error').show();
2631							addonTitle.addClass('wapo-error');
2632
2633							let errorMessageText = yith_wapo.i18n.selectOptions.replace('%d', exaValue)
2634							if (1 === exaValue) {
2635								errorMessageText = yith_wapo.i18n.selectAnOption;
2636							}
2637
2638							errorMessage.text(errorMessageText);
2639
2640							if (toggle_addon_title) {
2641								toggle_addon_title.click();
2642							}
2643						}
2644						addon.find('.yith-wapo-option:not(.out-of-stock) input:checkbox').not(':checked').attr('disabled', false);
2645					}
2646
2647					return Math.abs( optionsToSelect );
2648
2649				} else {
2650
2651					// At least values.
2652					if (minValue > 0) {
2653						let optionsToSelect = minValue - numberOfChecked;
2654						if (minValue <= numberOfChecked) {
2655							addon.removeClass('required-min').find('.min-error').hide();
2656						} else {
2657							// If click on add to cart button.
2658							if (submit) {
2659								let minMessage = yith_wapo.i18n.selectAnOption;
2660								if (minValue > 1) {
2661									minMessage = yith_wapo.i18n.selectAtLeast.replace('%d', minValue)
2662								}
2663
2664								addon.addClass('required-min');
2665								addon.find('.min-error').show();
2666								addonTitle.addClass('wapo-error');
2667								errorMessage.text(minMessage);
2668
2669								if (toggle_addon_title) {
2670									toggle_addon_title.click();
2671								}
2672							}
2673							return optionsToSelect;
2674						}
2675					}
2676
2677					// Max values.
2678					if (!maxValue || maxValue >= numberOfChecked) {
2679						addon.removeClass('required-min').find('.max-selected-error').hide();
2680					} else {
2681						// If click on add to cart button.
2682						if (submit) {
2683							addon.addClass('required-min');
2684							let optionsElement = $(addon).find('.options-container');
2685							if (!optionsElement.find('.max-selected-error').length) {
2686								optionsElement.append('<small class="max-selected-error">' + yith_wapo.i18n.maxOptionsSelectedMessage + '</small>');
2687								addonTitle.addClass('wapo-error');
2688							}
2689						}
2690						return 1;
2691					}
2692
2693				}
2694			},
2695			/** Check force user select an option for add-on type Selector */
2696			checkRequiredSelect 		= function (action = '') {
2697
2698				let value = true;
2699
2700				$('.yith-wapo-addon.yith-wapo-addon-type-select select').each(
2701					function () {
2702						let currentSelect = $(this);
2703						if (currentSelect.is(':required')) {
2704							let addon = currentSelect.closest('.yith-wapo-addon');
2705							let errorMessage = addon.find('.min-error-message'),
2706								addonTitle = addon.find('.wapo-addon-title');
2707							let selectValue = currentSelect.val();
2708							errorMessage.text('');
2709							addonTitle.removeClass('wapo-error');
2710							addon.removeClass('required-min');
2711
2712							if ('default' === selectValue && !addon.hasClass('hidden')) {
2713								value = false;
2714								if (!value && 'hide' !== action) {
2715									let error_el = addon.find('.min-error');
2716									let toggle_addon = currentSelect.closest('div.yith-wapo-addon.wapo-toggle');
2717									let toggle_addon_title = toggle_addon.find('.wapo-addon-title.toggle-closed');
2718									addon.addClass('required-min');
2719
2720									if (toggle_addon_title) {
2721										toggle_addon_title.click();
2722									}
2723									addonTitle.addClass('wapo-error');
2724									errorMessage.text(yith_wapo.i18n.selectAnOption.replace('%d', 1));
2725									error_el.show();
2726								}
2727							}
2728						}
2729					}
2730				);
2731
2732				return value;
2733			},
2734			checkTextInputLimit		 = function (input) {
2735				let valid = true,
2736					currentInput = $(input),
2737					currentValue = currentInput.val(),
2738					minLength = currentInput.attr('minlength'),
2739					maxLength = currentInput.attr('maxlength'),
2740					errorMessage = currentInput.closest( '.yith-wapo-option' ).find('.length-error-message');
2741				if ((minLength !== '' && currentValue.length < minLength) || (maxLength !== '' && currentValue.length > maxLength)) {
2742					currentInput.addClass('length-error');
2743					errorMessage.show();
2744					valid = false;
2745				} else {
2746					errorMessage.hide();
2747					currentInput.removeClass('length-error');
2748				}
2749				return valid;
2750			},
2751			numberOnChange 			= function() {
2752				var inputElementValue = $(this).val(),
2753					optionWrapper = $(this).closest('.yith-wapo-option'),
2754					resetImage = false;
2755
2756				if ('' == inputElementValue) {
2757					resetImage = true;
2758				}
2759
2760				replaceImageAction(optionWrapper, resetImage);
2761				checkMultipliedPrice($(this));
2762			},
2763			checkMultipliedPrice 		= function (addon) {
2764				let price = addon.attr('data-price');
2765				let sale_price = addon.attr('data-price-sale');
2766				let defaultPrice = addon.attr('data-default-price');
2767				let priceType = addon.data('price-type');
2768				let priceMethod = addon.data('price-method');
2769				let default_attr = 'price';
2770				let final_price = 0;
2771				let addon_value = addon.val();
2772
2773				if (!defaultPrice > 0) {
2774					if (sale_price > 0 && ('number' !== addon.attr('type') && 'multiplied' === priceType)) {
2775						price = sale_price;
2776						default_attr = 'price-sale';
2777					}
2778					defaultPrice = price;
2779					addon.data('default-price', defaultPrice);
2780				}
2781				if (priceMethod == 'value_x_product') {
2782					var productPrice = parseFloat($(wapoDOM.addonsContainer).attr('data-product-price'));
2783					final_price = addon_value * productPrice;
2784				} else if (priceType == 'multiplied') {
2785					final_price = addon_value * defaultPrice;
2786				}
2787
2788				if (final_price > 0 || priceMethod == 'decrease' || (final_price < 0 && priceType === 'multiplied')) {
2789					addon.attr( 'data-' + default_attr, final_price);
2790				}
2791
2792			},
2793			// Multiply add-on price by length
2794			checkMultipliedLength 		= function (addon) {
2795
2796				let price = addon.data('price');
2797				let defaultPrice = addon.data('default-price');
2798				let priceType = addon.data('price-type');
2799
2800				if (!defaultPrice > 0) {
2801					defaultPrice = price;
2802					addon.data('default-price', defaultPrice);
2803				}
2804				if ('characters' === priceType) {
2805					let remove_spaces = addon.data('remove-spaces');
2806					let addonLength = addon.val().length;
2807					if (remove_spaces) {
2808						addonLength = addon.val().replace(/\s+/g, '').length;
2809					}
2810					addon.attr('data-price', addonLength * defaultPrice);
2811				}
2812			},
2813			productQtyOnKeyUp 			= function () {
2814				var productID = $(this).data('product-id');
2815				var productQTY = $(this).val();
2816				var productURL = '?add-to-cart=' + productID + '&quantity=' + productQTY;
2817				$(this).parent().find('a').attr('href', productURL);
2818			},
2819
2820			datepickerSaveButton 		= function(ev) {
2821				ev.preventDefault();
2822
2823				var elementToClick = $('#ui-datepicker-div .ui-state-active');
2824
2825				if (0 == elementToClick.length) {
2826					elementToClick = $('#ui-datepicker-div .ui-datepicker-today');
2827				}
2828
2829				elementToClick.click();
2830				$('.hasDatepicker').datepicker('hide');
2831			},
2832			datepickertimeOnChange 		= function(ev) {
2833				ev.preventDefault();
2834				var option_id = $('#ui-datepicker-div').attr('wapo-option-id');
2835				var tempTime = $('#' + option_id).closest('.date-container').find('.temp-time');
2836
2837				tempTime.text($(this).val());
2838			}
2839
2840		requestAQuoteButton 		= function (e) {
2841			e.preventDefault();
2842
2843			if (typeof yith_wapo_general === 'undefined') {
2844				yith_wapo_general = {do_submit: true};
2845			}
2846			if (!checkRequiredMinMax()) {
2847				yith_wapo_general.do_submit = false;
2848			} else {
2849				yith_wapo_general.do_submit = true;
2850			}
2851		},
2852
2853			compositePriceUpdated 		= function (event, total) {
2854				let global_qty = parseFloat($('form.cart.ywcp > div.quantity input.qty').val());
2855				let price = global_qty ? total / global_qty : total;
2856				let addonsContainer = $(wapoDOM.addonsContainer);
2857
2858				addonsContainer.attr('data-product-price', price);
2859				calculateTotalAddonsPrice();
2860			},
2861			setError					= function ( option, message ) {
2862				option.find( '.yith-wapo-error' ).remove();
2863				let label = option.find( '.yith-wapo-addon-label' );
2864
2865				label.addClass( 'wapo-error' );
2866
2867				option.append( '<small class="yith-wapo-error">' + message + '</small>' );
2868
2869			},
2870			cleanError					= function( option ) {
2871				let label = option.find( '.yith-wapo-addon-label' );
2872
2873				option.find( '.yith-wapo-error' ).remove();
2874				label.removeClass( 'wapo-error' );
2875			}
2876
2877
2878
2879
2880
2881
2882		// ***** EVENTS ******
2883
2884		// Calculate Add-ons price triggers
2885		$(document).on(
2886			'ywgc-amount-changed',
2887			function (e, button_amount) {
2888				let price = button_amount.data('price');
2889				let container = $(wapoDOM.addonsContainer);
2890
2891				container.attr('data-product-price', price);
2892				calculateTotalAddonsPrice();
2893			}
2894		);
2895
2896		/**
2897		 * Since 4.0.0 - Allow external plugins the possibility to recalculate totals, after having changed the price.
2898		 *
2899		 * Price param is necessary.
2900		 */
2901		$(document).on(
2902			'yith-wapo-product-price-updated',
2903			function (e, price) {
2904				if (typeof price !== 'undefined') {
2905					$(wapoDOM.addonsContainer).attr('data-product-price', price);
2906				}
2907				calculateTotalAddonsPrice();
2908			}
2909		);
2910
2911		// YITH WC Gift Cards integration.
2912		$(document).on('change', '.gift-cards-list .ywgc-manual-amount-container input.ywgc-manual-amount', function (e) {
2913			let t = $(this),
2914				price = t.val();
2915
2916			let container = $(wapoDOM.addonsContainer);
2917
2918			container.attr('data-product-price', price);
2919			calculateTotalAddonsPrice();
2920		});
2921
2922		// WC Product Bundles integration.
2923		$(document).on('woocommerce-product-bundle-updated-totals', function (e, bundle) {
2924			let bundlePrice = bundle.price_data.subtotals.price;
2925			let bundlePriceH = $(bundle.$bundle_price);
2926			let bundlePriceHtml = bundlePriceH.find('.amount').html();
2927
2928			let container = $(wapoDOM.addonsContainer);
2929
2930			container.attr('data-product-price', bundlePrice);
2931			calculateTotalAddonsPrice();
2932
2933		});
2934
2935		// YITH Dynamic integration.
2936		$(document).on(
2937			'ywdpd_price_html_updated',
2938			function (e, html_price) {
2939				let price = $(html_price).children('.amount bdi').text();
2940				price = wcPriceToFloat(price);
2941
2942				if (!isNaN(price)) {
2943					let container = $(wapoDOM.addonsContainer);
2944
2945					container.attr('data-product-price', price);
2946					calculateTotalAddonsPrice();
2947				}
2948			}
2949		);
2950		// YITH WC Product Bundles integration.
2951		$(document).on(
2952			'yith_wcpb_ajax_update_price_request',
2953			function (e, response) {
2954
2955				let price = response.price;
2956
2957				if (!isNaN(price)) {
2958
2959					$(wapoDOM.addonsContainer).attr('data-product-price', price);
2960
2961					$( document ).trigger( 'yith-wapo-reload-addons', price );
2962				}
2963			}
2964		);
2965
2966		$(document).on(
2967			'change',
2968			'form.cart div.yith-wapo-addon, form.cart .quantity input[type=number]',
2969			function (e) {
2970				if ($(e.delegateTarget.activeElement).hasClass('yith-wapo-edit-product-cart')) {
2971					return false;
2972				}
2973
2974				let addonOpt = $(e.target);
2975				maybeCalculateTotals(addonOpt)
2976			}
2977		);
2978		$(document).on(
2979			'keyup',
2980			'form.cart .yith-wapo-addon-type-number input[type="number"], form.cart .yith-wapo-addon-type-text input[type="text"], form.cart .yith-wapo-addon-type-textarea textarea',
2981			function (e) {
2982				if ($(e.delegateTarget.activeElement).hasClass('yith-wapo-edit-product-cart')) {
2983					return false;
2984				}
2985
2986				let addonOpt = $(this);
2987
2988				maybeCalculateTotals(addonOpt)
2989			}
2990		);
2991		$(document).on(
2992			'wapo-colorpicker-change wapo-colorpicker-clear',
2993			function (e, addonOpt) {
2994				if ($(e.delegateTarget.activeElement).hasClass('yith-wapo-edit-product-cart')) {
2995					return false;
2996				}
2997				maybeCalculateTotals($(addonOpt))
2998			}
2999		);
3000
3001		/** Product quantity change */
3002		$( document ).on( 'change keyup', '.yith-wapo-option .wapo-product-qty', productQuantityChange );
3003		$( document ).on( 'reset_data', resetAddons );
3004		$( document ).on( 'found_variation', foundVariation );
3005
3006		/** ajax reload addons **/
3007		$( document ).on( 'yith-wapo-reload-addons', reloadAddons );
3008
3009		/** Uploads */
3010		$( document ).on( 'click', '.yith-wapo-uploaded-file .remove', removeUploadedFile );
3011
3012		/** Checkboxes click */
3013		$( 'form.cart' ).on('click', 'span.checkboxbutton', function() {
3014			if ( $( this ).find( 'input' ).is( ':checked' ) ) {
3015				$( this ).addClass( 'checked' );
3016			} else {
3017				$( this ).removeClass( 'checked' );
3018			}
3019		});
3020		/** Checkboxes change */
3021		$( document ).on( 'change', '.yith-wapo-addon-type-checkbox input', checkboxOnChange );
3022
3023		/** Labels onChange */
3024		$( document ).on('click', '.yith-wapo-addon-type-label .yith-wapo-option div.label', function() {
3025			$( this ).closest( '.yith-wapo-option' ).find( '.yith-proteo-standard-checkbox' ).click();
3026		} );
3027		$( document ).on( 'change', '.yith-wapo-addon-type-label input', labelsOnChange );
3028
3029		/** Input text or textarea change */
3030		$(document).on('change keyup', '.yith-wapo-addon-type-text input', inputOnChange);
3031		$(document).on('change keyup', '.yith-wapo-addon-type-textarea textarea', textareaOnChange);
3032
3033		/** Color click */
3034		$( document ).on('click', '.yith-wapo-addon-type-color .yith-wapo-option div.label', function() {
3035			$( this ).closest( '.yith-wapo-option' ).find( '.yith-proteo-standard-checkbox' ).click();
3036		} );
3037		$( document ).on('change', '.yith-wapo-addon-type-color input', colorOnChange );
3038
3039		/** Product click */
3040		$( document ).on( 'click change', '.yith-wapo-addon-type-product .quantity input', function (e) {
3041				e.stopPropagation();
3042			}
3043		);
3044		$( document ).on('click', '.yith-wapo-addon-type-product .yith-wapo-option .product-container',
3045			function() {
3046				$( this ).closest( '.yith-wapo-option' ).find( '.yith-proteo-standard-checkbox' ).click();
3047			}
3048		);
3049		$( document ).on('change', '.yith-wapo-addon-type-product .yith-wapo-option input.yith-proteo-standard-checkbox', productOnChange );
3050
3051		/** Add-on image click */
3052		$( document ).on( 'click', '.yith-wapo-option .image-container', addonImageClicked );
3053
3054		/** Radio click */
3055		$('form.cart').on('click', 'span.radiobutton', function () {
3056			if ($(this).find('input').is(':checked')) {
3057				$(this).closest('.yith-wapo-addon.yith-wapo-addon-type-radio').find('span.radiobutton.checked').removeClass('checked');
3058				$(this).addClass('checked');
3059			}
3060		});
3061		/** Radio change */
3062		$( document ).on('click', '.yith-wapo-addon-type-radio input', radioOnChange);
3063
3064		/** Select click */
3065		$( document ).on('change', '.yith-wapo-addon-type-select select', selectOnChange);
3066
3067		/** Input file */
3068		$( document ).on('change', '.yith-wapo-addon-type-file input.file', fileOnChange);
3069		$( document ).on('click', '.yith-wapo-ajax-uploader .button, .yith-wapo-ajax-uploader .link', function() {
3070			$( this ).closest( '.yith-wapo-option').find( 'input.file' ).click();
3071		});
3072		/** Number change */
3073		$(document).on('change keyup', '.yith-wapo-addon-type-number input', numberOnChange);
3074
3075		/** Datepicker change */
3076		$( document ).on('click', '#wapo-datepicker-save button', datepickerSaveButton);
3077		$( document ).on('change', '#wapo-datepicker-time-select', datepickertimeOnChange);
3078
3079		/** Toggle feature */
3080		$( document ).on('click', '.yith-wapo-addon.wapo-toggle .addon-header', toggleElement);
3081
3082		/** Product quantity on key up */
3083		$( document ).on( 'keyup', '.wapo-product-qty', productQtyOnKeyUp );
3084
3085		// WooCommerce Measurement Price Calculator (compatibility)
3086		$( 'form.cart' ).on('change', '#price_calculator.wc-measurement-price-calculator-price-table', yithWcMeasurementPC_Compatibility );
3087
3088		// YITH WooCommerce Composite. Recalculate add-ons with the composite price.
3089		$( document ).on( 'yith_wcp_price_updated', function( ev, finalPrice ){
3090			$( document ).trigger( 'yith-wapo-reload-addons', finalPrice );
3091		});
3092
3093		/** Ajax upload file */
3094		// preventing page from redirecting.
3095		$('html').on('dragover, drop', function (e) {
3096			e.preventDefault();
3097			e.stopPropagation();
3098		});
3099		// drag enter
3100		$(document).on('dragenter', '.yith-wapo-ajax-uploader', function (e) {
3101			e.stopPropagation();
3102			e.preventDefault();
3103			$(this).css('opacity', '0.5');
3104		});
3105		// drag over
3106		$(document).on('dragover', '.yith-wapo-ajax-uploader', function (e) {
3107			e.stopPropagation();
3108			e.preventDefault();
3109		});
3110
3111		// drag leave
3112		$(document).on('dragleave', '.yith-wapo-ajax-uploader', function (e) {
3113				e.stopPropagation();
3114				e.preventDefault();
3115				if ($(e.target).hasClass('yith-wapo-ajax-uploader')) {
3116					$(this).css('opacity', '1');
3117				}
3118			}
3119		);
3120
3121		$( 'body' ).on('drop', '.yith-wapo-ajax-uploader', ajaxUploaderOnDrop);
3122
3123		/** Min max rules */
3124		$(document).on('change', '.yith-wapo-addon-type-checkbox, .yith-wapo-addon-type-color, .yith-wapo-addon-type-label, .yith-wapo-addon-type-product', function () {
3125			checkMinMax($(this));
3126		});
3127
3128		// Check required fields before adding to cart (Required select and min/max values).
3129		$(document).on('click', 'form.cart button', function () {
3130			return checkAddonsRequirements();
3131		});
3132
3133
3134		/** YITH Composite */
3135		$( document ).on( 'yith_wcp_price_updated', compositePriceUpdated );
3136
3137		/** YITH RAQ */
3138		$( document ).on('click', '.add-request-quote-button', requestAQuoteButton);
3139
3140
3141		/** Init JS */
3142		initJS();
3143
3144	}
3145);

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.