/** * Woo Product Variations widget behavior. * * Enhances variation selects with swatches and keeps WooCommerce's * variation resolver in sync when the form lives in a split builder widget. */ (function ($) { "use strict"; const INIT_FLAG = "kaWooVariationsInit"; const initVariations = (wrap) => { if (!wrap || !wrap.dataset) return; if (wrap.dataset[INIT_FLAG] === "1") return; const form = wrap.querySelector('form.variations_form'); if (!form) return; wrap.dataset[INIT_FLAG] = "1"; let map = {}; try { map = wrap.dataset.swatchesMap ? JSON.parse(wrap.dataset.swatchesMap) : {}; } catch (e) { map = {}; } const unavailableBehavior = map.unavailable || 'disable'; const showSelected = map.show_selected === 'yes'; const shape = map.shape || 'square'; const attrMap = (map.map) || {}; const source = map.source || 'auto'; const selects = form.querySelectorAll('select'); const updateCallbacks = []; const runAll = () => updateCallbacks.forEach((cb) => cb()); // Woo leaves out-of-stock options enabled in the native select when // "hide out of stock" is off. Hide/disable would then be a no-op, // so also read the variation JSON Woo already prints on the form. let productVariations = []; try { const raw = form.getAttribute('data-product_variations'); productVariations = raw && raw !== 'false' ? JSON.parse(raw) : []; } catch (e) { productVariations = []; } const priceRoots = () => document.querySelectorAll('.ka-woo-product-price'); priceRoots().forEach((el) => { if (!el.dataset.kaOriginalPrice) { el.dataset.kaOriginalPrice = el.innerHTML; } }); const setAtcNotice = (hide) => { document.querySelectorAll('.ka-woo-product-atc__notice').forEach((el) => { el.hidden = !!hide; }); }; const applyVariationPrice = (variation) => { priceRoots().forEach((el) => { if (!variation || !variation.price_html) { if (el.dataset.kaOriginalPrice) { el.innerHTML = el.dataset.kaOriginalPrice; } return; } const discount = el.querySelector('.ka-woo-product-price__discount'); const discountHtml = discount ? discount.outerHTML : ''; el.innerHTML = variation.price_html + discountHtml; }); }; const ensureWcVariationForm = () => { if (!window.jQuery || typeof window.jQuery.fn.wc_variation_form !== 'function') { return false; } const $form = window.jQuery(form); if (!$form.data('wc_variation_form')) { $form.wc_variation_form(); } return true; }; const findMatchedVariation = () => { if (!Array.isArray(productVariations) || !productVariations.length) { return null; } const selected = {}; let hasEmpty = false; form.querySelectorAll('select[name^="attribute_"]').forEach((sel) => { selected[sel.name] = sel.value; if (!sel.value) { hasEmpty = true; } }); if (hasEmpty) { return null; } return productVariations.find((variation) => { const attrs = variation.attributes || {}; return Object.keys(attrs).every((key) => { const want = attrs[key]; return !want || want === selected[key]; }); }) || null; }; const applyFallbackVariation = () => { if (window.jQuery && window.jQuery(form).data('wc_variation_form')) { return; } const match = findMatchedVariation(); const varInput = form.querySelector('input.variation_id, input[name="variation_id"]'); const single = form.querySelector('.single_variation'); if (match) { if (varInput) { varInput.value = String(match.variation_id || ''); } if (single) { single.innerHTML = (match.price_html ? '
' + match.price_html + '
' : '') + (match.availability_html ? '
' + match.availability_html + '
' : ''); } applyVariationPrice(match); setAtcNotice(true); } else { if (varInput) { varInput.value = '0'; } if (single) { single.innerHTML = ''; } applyVariationPrice(null); setAtcNotice(false); } }; const wcReady = ensureWcVariationForm(); if (window.jQuery) { const $form = window.jQuery(form); $form.on('found_variation', function (event, variation) { applyVariationPrice(variation); setAtcNotice(!!(variation && variation.variation_id)); }); $form.on('reset_data hide_variation', function () { applyVariationPrice(null); setAtcNotice(false); }); $form.on('woocommerce_update_variation_values woocommerce_variation_has_changed reset_data found_variation hide_variation', runAll); } if (!wcReady) { form.addEventListener('change', applyFallbackVariation); applyFallbackVariation(); if (window.jQuery) { window.jQuery(function () { if (ensureWcVariationForm()) { const selected = form.querySelector('select[name^="attribute_"]'); if (selected && selected.value) { window.jQuery(selected).trigger('change'); } } else { applyFallbackVariation(); } }); } } if (wrap.dataset.swatches !== 'yes') { return; } const attributeMatches = (variationAttr, selected) => { if (!variationAttr) { return true; } return variationAttr === selected; }; const valueIsAvailable = (attrName, val) => { if (!Array.isArray(productVariations) || !productVariations.length) { const opt = form.querySelector(`select[name="${attrName}"] option[value="${val}"]`); return !(opt && opt.disabled); } return productVariations.some((variation) => { if (!variation || variation.is_in_stock === false) { return false; } const attrs = variation.attributes || {}; if (!attributeMatches(attrs[attrName], val)) { return false; } return Array.from(selects).every((other) => { if (other.name === attrName || !other.value) { return true; } return attributeMatches(attrs[other.name], other.value); }); }); }; selects.forEach((select) => { const attrName = select.name; const swatchesInfo = attrMap[attrName]; if (!swatchesInfo) { return; } const container = document.createElement('div'); container.className = 'ka-woo-variations__swatches'; const selectedText = document.createElement('div'); selectedText.className = 'ka-woo-variations__selected'; const options = Array.from(select.options).filter((opt) => opt.value !== ''); options.forEach((opt) => { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'ka-woo-variations__swatch'; if (shape === 'round') { btn.classList.add('ka-woo-variations__swatch--round'); } btn.dataset.value = opt.value; const info = swatchesInfo[opt.value] || {}; if (source === 'text') { const span = document.createElement('span'); span.className = 'ka-woo-variations__swatch-text'; span.textContent = info.label || opt.text; btn.appendChild(span); } else if (info.image) { const img = document.createElement('img'); img.src = info.image; img.alt = info.label || opt.text; img.className = 'ka-woo-variations__swatch-img'; btn.appendChild(img); } else if (info.color) { const span = document.createElement('span'); span.className = 'ka-woo-variations__swatch-color'; span.style.background = info.color; btn.appendChild(span); } else { const span = document.createElement('span'); span.className = 'ka-woo-variations__swatch-text'; span.textContent = info.label || opt.text; btn.appendChild(span); } btn.addEventListener('click', () => { if (!valueIsAvailable(attrName, opt.value)) { return; } select.value = opt.value; select.dispatchEvent(new Event('change', { bubbles: true })); if (window.jQuery) { window.jQuery(select).trigger('check_variations'); window.jQuery(select).trigger('change'); } applyFallbackVariation(); runAll(); }); container.appendChild(btn); }); select.style.display = 'none'; select.insertAdjacentElement('afterend', container); if (showSelected) { container.insertAdjacentElement('afterend', selectedText); } const updateStates = () => { const current = select.value; Array.from(container.children).forEach((btn) => { const val = btn.dataset.value; const opt = select.querySelector(`option[value="${val}"]`); const unavailable = !valueIsAvailable(attrName, val) || !!(opt && opt.disabled); btn.classList.toggle('is-active', current === val); btn.classList.toggle('is-disabled', unavailable); if (unavailable && unavailableBehavior === 'hide') { btn.style.display = 'none'; } else { btn.style.display = ''; } }); if (showSelected) { const label = (swatchesInfo[current] && swatchesInfo[current].label) || current || ''; const prefix = select.getAttribute('aria-label') || ''; selectedText.textContent = label ? `${prefix}: ${label}` : ''; } }; select.addEventListener('change', runAll); updateCallbacks.push(updateStates); }); runAll(); const resetBtn = form.querySelector('.reset_variations'); if (resetBtn) { resetBtn.addEventListener('click', () => { setTimeout(() => { runAll(); applyFallbackVariation(); }, 50); }); } }; const initAll = (root) => { const ctx = root && root.querySelectorAll ? root : document; ctx.querySelectorAll(".ka-woo-variations").forEach(initVariations); }; document.addEventListener("DOMContentLoaded", () => initAll(document)); $(window).on("elementor/frontend/init", function () { elementorFrontend.hooks.addAction( "frontend/element_ready/woo_product_variations.default", function ($scope) { initAll($scope && $scope[0] ? $scope[0] : document); } ); }); })(jQuery);