/* ===================================================================== Car Search Tree — standalone widget Paste into the store's Custom JS (Theme > Custom JS), or host it and load it with */ if (window.voltStandaloneCarSearchConfig && typeof window.voltStandaloneCarSearchConfig === 'object') { Object.keys(window.voltStandaloneCarSearchConfig).forEach(function (key) { CONFIG[key] = window.voltStandaloneCarSearchConfig[key]; }); } /* Class-name prefix — single source of truth, mirrors the CSS file. */ var NS = 'volt-standalone-'; var HIDDEN = NS + 'hidden'; var ACTIVE = NS + 'is-active'; /* ================================================================= Helpers ================================================================= */ function esc(value) { return String(value === null || value === undefined ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function kids(node) { return node && Array.isArray(node.sub_categories) ? node.sub_categories : []; } function findById(list, id) { if (!Array.isArray(list)) return null; for (var i = 0; i < list.length; i++) { if (String(list[i].id) === String(id)) return list[i]; } return null; } /* ================================================================= Widget ================================================================= */ var state = { brand: null, model: null, year: null }; var el = {}; var root = null; var isDropdown = CONFIG.use_dropdown_layout === true; var ROW_OF = { brand: 'brands', model: 'models', year: 'years' }; function titleStyle() { var size = CONFIG.title_size > 0 ? CONFIG.title_size : 36; var out = 'font-size: clamp(' + Math.round(size * 0.6) + 'px, 6vw, ' + size + 'px);'; if (CONFIG.title_color_type === 'primary') out += ' color: var(--' + NS + 'primary);'; else if (CONFIG.title_color_type === 'custom') out += ' color: ' + CONFIG.title_color + ';'; if (CONFIG.title_font) out += ' font-family: ' + CONFIG.title_font + ';'; return out; } function subtitleStyle() { var size = CONFIG.sub_title_size > 0 ? CONFIG.sub_title_size : 18; return 'font-size: clamp(' + Math.round(size * 0.8) + 'px, 2.5vw, ' + size + 'px);'; } function selectBlock(level, label) { var id = NS + level + '-select'; return '' + '
' + '' + '' + '
'; } function template() { return '' + '
' + '
' + '
' + '

' + esc(CONFIG.badge) + '

' + '

' + esc(CONFIG.title) + '

' + (CONFIG.enable_title_underline ? '' : '') + '

' + esc(CONFIG.description) + '

' + '
' + '
' + '' + esc(CONFIG.selection_label) + '' + '' + '
' + '
' + '' + '' + '
' + selectBlock('brand', CONFIG.brand_label) + selectBlock('model', CONFIG.model_label) + selectBlock('year', CONFIG.year_label) + '
' + (CONFIG.show_search_button ? '
' + '' + '
' : '') + '
'; } function show(node) { if (node) node.classList.remove(HIDDEN); } function hide(node) { if (node) node.classList.add(HIDDEN); } /* ---- Options ---------------------------------------------------- */ function optionTemplate(item, level) { var name = item && item.name ? item.name : ''; var title = name; var subtitle = ''; if (level === 'year' && name.indexOf('(') !== -1) { var idx = name.indexOf('('); title = name.substring(0, idx).trim(); subtitle = name.substring(idx).trim(); } var isYear = level === 'year'; var thumbClass = NS + 'thumb'; var thumb; if (isYear) { return '' + ''; } else if (item && item.image) { thumb = '' + esc(name || 'category') + ''; } else { thumbClass += ' ' + NS + 'thumb--fallback'; thumb = '' + esc(name.charAt(0) || '?') + ''; } var hideLabel = level === 'brand' && CONFIG.hide_brand_labels; return '' + ''; } function setupMobileScrollbar(container) { if (!container || isDropdown) return; var wrapper = container.parentNode; if (!wrapper) return; wrapper.classList.add(NS + 'mobile-scroll-wrap'); var bar = wrapper.querySelector('.' + NS + 'mobile-scrollbar'); if (!bar) { bar = document.createElement('div'); bar.className = NS + 'mobile-scrollbar'; bar.innerHTML = ''; wrapper.appendChild(bar); container.addEventListener('scroll', function () { updateMobileScrollbar(container, bar); }, { passive: true }); window.addEventListener('resize', function () { updateMobileScrollbar(container, bar); }); } updateMobileScrollbar(container, bar); } function updateMobileScrollbar(container, bar) { if (!container || !bar) return; var thumb = bar.querySelector('.' + NS + 'mobile-scroll-thumb'); if (!thumb) return; var viewport = container.clientWidth; var content = container.scrollWidth; var maxScroll = Math.max(0, content - viewport); if (maxScroll <= 2) { bar.style.display = 'none'; return; } bar.style.display = 'block'; var trackWidth = bar.clientWidth || viewport; var minThumb = 34; var thumbWidth = Math.max(minThumb, Math.round(trackWidth * viewport / content)); thumbWidth = Math.min(trackWidth, thumbWidth); var distanceFromRight; if (container.scrollLeft < 0) { distanceFromRight = Math.abs(container.scrollLeft); } else { distanceFromRight = maxScroll - container.scrollLeft; } distanceFromRight = Math.max(0, Math.min(maxScroll, distanceFromRight)); var travel = Math.max(0, trackWidth - thumbWidth); var position = maxScroll ? travel - ((distanceFromRight / maxScroll) * travel) : travel; thumb.style.width = thumbWidth + 'px'; thumb.style.transform = 'translateX(' + position + 'px)'; } function renderOptions(level, items) { if (isDropdown) return renderSelectOptions(level, items); var container = el.rows[ROW_OF[level]]; if (!container) return; var list = Array.isArray(items) ? items : []; if (!list.length) { container.innerHTML = ''; hide(el.wrappers[ROW_OF[level]]); return; } show(el.wrappers[ROW_OF[level]]); container.classList.add('leaving'); setTimeout(function () { container.innerHTML = list.map(function (item) { return optionTemplate(item, level); }).join(''); container.querySelectorAll('.' + NS + 'option').forEach(function (button) { button.addEventListener('click', function () { select(level, button.dataset.id); }); }); container.classList.remove('leaving'); container.classList.add('entering'); setupMobileScrollbar(container); requestAnimationFrame(function () { container.classList.remove('entering'); }); }, 180); } function placeholderFor(level) { return { brand: CONFIG.brand_label, model: CONFIG.model_label, year: CONFIG.year_label }[level] || ''; } function renderSelectOptions(level, items) { var select = el.selects[level]; if (!select) return; var list = Array.isArray(items) ? items : []; select.innerHTML = '' + list.map(function (item) { return ''; }).join(''); setSelectDisabled(level, !list.length); select.value = ''; } function clearSelect(level) { var select = el.selects[level]; if (!select) return; select.innerHTML = ''; setSelectDisabled(level, level !== 'brand'); select.value = ''; } function setSelectDisabled(level, disabled) { if (el.selects[level]) el.selects[level].disabled = !!disabled; var wrapper = el.selectWrappers[level]; if (wrapper) wrapper.classList.toggle(NS + 'select--disabled', !!disabled); } function clearLevel(level) { if (isDropdown) return clearSelect(level); var container = el.rows[ROW_OF[level]]; if (container) container.innerHTML = ''; hide(el.wrappers[ROW_OF[level]]); } function setActive(level, id) { if (isDropdown) return; var container = el.rows[ROW_OF[level]]; if (!container) return; container.querySelectorAll('.' + NS + 'option').forEach(function (button) { button.classList.toggle(ACTIVE, String(button.dataset.id) === String(id)); }); } /* ---- Selection ------------------------------------------------- */ function select(level, id) { if (!id) return; if (level === 'brand') return selectBrand(id); if (level === 'model') return selectModel(id); if (level === 'year') return selectYear(id); } function selectBrand(id) { var brand = findById(CONFIG.categories, id); if (!brand) return; state.brand = brand; state.model = null; state.year = null; setActive('brand', id); renderOptions('model', kids(brand)); clearLevel('year'); updateSelectionPath(); if (kids(brand).length) { show(el.wrappers.models); } else if (brand.url) { window.location.href = brand.url; } } function selectModel(id) { if (!state.brand) return; var model = findById(kids(state.brand), id); if (!model) return; state.model = model; state.year = null; setActive('model', id); renderOptions('year', kids(model)); updateSelectionPath(); if (!kids(model).length && model.url) { window.location.href = model.url; return; } } function selectYear(id) { if (!state.model) return; var year = findById(kids(state.model), id); if (!year) return; state.year = year; setActive('year', id); updateSelectionPath(); if (year.url) { window.location.href = year.url; } } function updateSelectionPath() { var parts = []; if (state.brand) parts.push(state.brand.name); if (state.model) parts.push(state.model.name); if (state.year) parts.push(state.year.name); el.selection.textContent = parts.length ? parts.join(' / ') : '—'; if (el.submitBtn) el.submitBtn.disabled = !state.brand; } /* ---- Mount ----------------------------------------------------- */ function cacheRefs() { var q = function (sel) { return root.querySelector(sel); }; el = { rows: { brands: q('[data-row="brands"]'), models: q('[data-row="models"]'), years: q('[data-row="years"]'), }, wrappers: { brands: q('[data-wrapper="brands"]'), models: q('[data-wrapper="models"]'), years: q('[data-wrapper="years"]') }, selects: { brand: q('[data-select="brand"]'), model: q('[data-select="model"]'), year: q('[data-select="year"]') }, selectWrappers: { brand: q('[data-select-wrapper="brand"]'), model: q('[data-select-wrapper="model"]'), year: q('[data-select-wrapper="year"]') }, empty: q('[data-empty]'), selection: q('[data-selected-path]'), submitBtn: q('[data-submit-btn]') }; } /** Apply the CONFIG colours as CSS custom properties on the root. */ function applyTheme() { if (CONFIG.primary_color) root.style.setProperty('--' + NS + 'primary', CONFIG.primary_color); if (CONFIG.accent_color) root.style.setProperty('--' + NS + 'accent', CONFIG.accent_color); if (CONFIG.border_radius) root.style.setProperty('--' + NS + 'radius', CONFIG.border_radius); // 'auto' needs no class — the CSS follows the store's own dark-mode class if (['light', 'dark', 'system'].indexOf(CONFIG.color_scheme) > -1) { root.classList.add(NS + 'scheme-' + CONFIG.color_scheme); } } /** Warn (once) if the companion stylesheet was never added to the store. */ function verifyStylesheet() { var padding = window.getComputedStyle(root).paddingTop; if (padding === '0px' || padding === '') { console.warn( '[volt-standalone-car-search] stylesheet not detected — ' + 'add volt-standalone-car-search.css to the store\'s Custom CSS.' ); } } function mount() { if (document.querySelector('.' + NS + 'root')) return; // already mounted var slug = (window.salla && window.salla.config && window.salla.config.get) ? window.salla.config.get('page.slug') : null; if (Array.isArray(CONFIG.pages) && CONFIG.pages.length && CONFIG.pages.indexOf(slug) === -1) return; var slider = document.querySelector('section.advanced-slider'); /* إذا لم نجد السلايدر لا نعرض القالب */ if (!slider) { return; } var host = slider.parentNode; root = document.createElement('section'); root.className = NS + 'root'; root.setAttribute('dir', document.documentElement.dir || 'rtl'); root.innerHTML = template(); slider.insertAdjacentElement('afterend', root); applyTheme(); cacheRefs(); verifyStylesheet(); if (isDropdown) { el.selects.brand && el.selects.brand.addEventListener('change', function (e) { select('brand', e.target.value); }); el.selects.model && el.selects.model.addEventListener('change', function (e) { select('model', e.target.value); }); el.selects.year && el.selects.year.addEventListener('change', function (e) { select('year', e.target.value); }); clearSelect('model'); clearSelect('year'); } if (el.submitBtn) el.submitBtn.addEventListener('click', submitSearch); if (!Array.isArray(CONFIG.categories) || !CONFIG.categories.length) { show(el.empty); return; } renderOptions('brand', CONFIG.categories); if (!isDropdown) show(el.wrappers.brands); if (!CONFIG.show_search_button) applyUrlPreselection(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', mount); } else { mount(); } }());