import { t, i18n } from '../i18n.js'; const CATEGORIES = { electronics: ['phones', 'computers', 'tv_audio', 'gaming', 'appliances'], vehicles: ['cars', 'motorcycles', 'bikes', 'parts'], furniture: ['living', 'bedroom', 'office', 'outdoor_furniture'], clothing: ['women', 'men', 'kids', 'shoes', 'accessories'], sports: ['fitness', 'outdoor', 'winter', 'water', 'team_sports'], books: ['fiction', 'nonfiction', 'textbooks', 'music_movies'], garden: ['plants', 'tools', 'outdoor_living', 'decoration'], other: ['collectibles', 'art', 'handmade', 'services'] }; const COUNTRIES = ['ch', 'de', 'at', 'fr', 'it', 'li']; const RADIUS_OPTIONS = [5, 10, 20, 50, 100, 200]; class SearchBox extends HTMLElement { static get observedAttributes() { return ['compact', 'category', 'subcategory', 'country', 'query']; } constructor() { super(); this.loadFiltersFromStorage(); } loadFiltersFromStorage() { const saved = localStorage.getItem('searchFilters'); if (saved) { try { const filters = JSON.parse(saved); this.selectedCategory = filters.category || ''; this.selectedSubcategory = filters.subcategory || ''; this.selectedCountry = filters.country || 'ch'; this.selectedRadius = filters.radius || 50; this.useCurrentLocation = filters.useCurrentLocation || false; this.searchQuery = filters.query || ''; } catch (e) { this.resetFilters(); } } else { this.resetFilters(); } } resetFilters() { this.selectedCategory = ''; this.selectedSubcategory = ''; this.selectedCountry = 'ch'; this.selectedRadius = 50; this.useCurrentLocation = false; this.searchQuery = ''; this.geoLoading = false; this.currentLat = null; this.currentLng = null; } saveFiltersToStorage() { const filters = { category: this.selectedCategory, subcategory: this.selectedSubcategory, country: this.selectedCountry, radius: this.selectedRadius, useCurrentLocation: this.useCurrentLocation, query: this.searchQuery }; localStorage.setItem('searchFilters', JSON.stringify(filters)); } connectedCallback() { // Override from attributes if provided if (this.hasAttribute('category')) { this.selectedCategory = this.getAttribute('category'); } if (this.hasAttribute('subcategory')) { this.selectedSubcategory = this.getAttribute('subcategory'); } if (this.hasAttribute('country')) { this.selectedCountry = this.getAttribute('country'); } if (this.hasAttribute('query')) { this.searchQuery = this.getAttribute('query'); } this.render(); this.setupEventListeners(); this.unsubscribe = i18n.subscribe(() => { this.render(); this.setupEventListeners(); }); } disconnectedCallback() { if (this.unsubscribe) this.unsubscribe(); if (this._closeDropdown) { document.removeEventListener('click', this._closeDropdown); } } attributeChangedCallback(name, oldValue, newValue) { if (oldValue === newValue) return; switch (name) { case 'category': this.selectedCategory = newValue || ''; break; case 'subcategory': this.selectedSubcategory = newValue || ''; break; case 'country': this.selectedCountry = newValue || 'ch'; break; case 'query': this.searchQuery = newValue || ''; break; } if (this.isConnected) { this.render(); this.setupEventListeners(); } } get isCompact() { return this.hasAttribute('compact'); } render() { const compact = this.isCompact; this.innerHTML = /* html */ ` `; } renderFilters() { // Track which category accordion is expanded this._expandedCategory = this._expandedCategory || ''; return /* html */ `
${Object.keys(CATEGORIES).map(cat => `
${CATEGORIES[cat].map(sub => ` `).join('')}
`).join('')}
`; } setupEventListeners() { const form = this.querySelector('#search-form'); const queryInput = this.querySelector('#search-query'); // Desktop selects const countrySelect = this.querySelector('#country-select'); const radiusSelect = this.querySelector('#radius-select'); // Mobile selects const countrySelectMobile = this.querySelector('#country-select-mobile'); const radiusSelectMobile = this.querySelector('#radius-select-mobile'); // Accordion dropdown const categoryTrigger = this.querySelector('#category-trigger'); const categoryMenu = this.querySelector('#category-menu'); form?.addEventListener('submit', (e) => { e.preventDefault(); this.handleSearch(); }); queryInput?.addEventListener('input', (e) => { this.searchQuery = e.target.value; }); // Toggle dropdown if (categoryTrigger && categoryMenu) { categoryTrigger.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); categoryMenu.classList.toggle('open'); }); } // Close dropdown on outside click this._closeDropdown = (e) => { if (!this.contains(e.target)) { categoryMenu?.classList.remove('open'); } }; document.addEventListener('click', this._closeDropdown); // Category accordion headers - toggle expand this.querySelectorAll('.category-accordion > .category-item').forEach(item => { item.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const cat = item.dataset.category; const accordion = item.closest('.category-accordion'); // Toggle this accordion if (this._expandedCategory === cat) { this._expandedCategory = ''; accordion?.classList.remove('expanded'); } else { // Close other accordions this.querySelectorAll('.category-accordion.expanded').forEach(el => { el.classList.remove('expanded'); }); this._expandedCategory = cat; accordion?.classList.add('expanded'); } }); }); // "All categories" button this.querySelector('.category-item--all')?.addEventListener('click', (e) => { e.stopPropagation(); this.selectedCategory = ''; this.selectedSubcategory = ''; this._expandedCategory = ''; this.saveFiltersToStorage(); categoryMenu?.classList.remove('open'); this.render(); this.setupEventListeners(); }); // Subcategory items - select category + subcategory this.querySelectorAll('.subcategory-item').forEach(item => { item.addEventListener('click', (e) => { e.stopPropagation(); this.selectedCategory = item.dataset.category; this.selectedSubcategory = item.dataset.subcategory; this._expandedCategory = ''; this.saveFiltersToStorage(); categoryMenu?.classList.remove('open'); this.render(); this.setupEventListeners(); }); }); // Country select handler (both desktop and mobile) const handleCountryChange = (e) => { if (e.target.value === 'current') { this.useCurrentLocation = true; this.requestGeolocation(); } else { this.useCurrentLocation = false; this.selectedCountry = e.target.value; } this.saveFiltersToStorage(); this.render(); this.setupEventListeners(); }; countrySelect?.addEventListener('change', handleCountryChange); countrySelectMobile?.addEventListener('change', handleCountryChange); // Radius select handler (both desktop and mobile) const handleRadiusChange = (e) => { this.selectedRadius = parseInt(e.target.value); this.saveFiltersToStorage(); }; radiusSelect?.addEventListener('change', handleRadiusChange); radiusSelectMobile?.addEventListener('change', handleRadiusChange); // Adjust select width to selected option (desktop only) this.adjustSelectWidth(countrySelect); this.adjustSelectWidth(radiusSelect); } adjustSelectWidth(select) { if (!select) return; // Only apply fixed width on desktop (768px+) if (window.innerWidth < 768) { select.style.width = ''; return; } // Create hidden span to measure text width const measurer = document.createElement('span'); measurer.style.cssText = 'position:absolute;visibility:hidden;white-space:nowrap;font:inherit;'; select.parentElement.appendChild(measurer); const selectedOption = select.options[select.selectedIndex]; measurer.textContent = selectedOption ? selectedOption.textContent : ''; // Add padding for arrow, icon and buffer select.style.width = (measurer.offsetWidth + 90) + 'px'; measurer.remove(); } handleSearch() { const params = new URLSearchParams(); if (this.searchQuery) params.set('q', this.searchQuery); if (this.selectedCategory) params.set('category', this.selectedCategory); if (this.selectedSubcategory) params.set('sub', this.selectedSubcategory); if (this.useCurrentLocation && this.currentLat && this.currentLng) { params.set('lat', this.currentLat); params.set('lng', this.currentLng); params.set('radius', this.selectedRadius); } else if (!this.useCurrentLocation) { params.set('country', this.selectedCountry); } this.saveFiltersToStorage(); // Emit custom event const event = new CustomEvent('search', { bubbles: true, cancelable: true, detail: { query: this.searchQuery, category: this.selectedCategory, subcategory: this.selectedSubcategory, country: this.selectedCountry, useCurrentLocation: this.useCurrentLocation, lat: this.currentLat, lng: this.currentLng, radius: this.selectedRadius, params: params.toString() } }); const cancelled = !this.dispatchEvent(event); // Navigate to search page unless event was cancelled if (!cancelled && !this.hasAttribute('no-navigate')) { const url = '#/search' + (params.toString() ? '?' + params.toString() : ''); window.location.hash = url; } } requestGeolocation() { if (!('geolocation' in navigator)) { this.handleGeoError(); return; } this.geoLoading = true; this.updateGeoButton(); navigator.geolocation.getCurrentPosition( (position) => { this.currentLat = position.coords.latitude; this.currentLng = position.coords.longitude; this.geoLoading = false; this.updateGeoButton(); }, (error) => { console.warn('Geolocation error:', error); this.handleGeoError(); }, { timeout: 10000, enableHighAccuracy: false } ); } handleGeoError() { // Keep useCurrentLocation = true, just stop loading indicator // User can still search by current location (backend will handle it) this.geoLoading = false; this.updateGeoButton(); } updateGeoButton() { const countrySelect = this.querySelector('#country-select'); if (!countrySelect) return; if (this.geoLoading) { countrySelect.disabled = true; const currentOption = countrySelect.querySelector('option[value="current"]'); if (currentOption) { currentOption.textContent = `⏳ ${t('search.locating')}`; } } else { countrySelect.disabled = false; const currentOption = countrySelect.querySelector('option[value="current"]'); if (currentOption) { currentOption.textContent = `📍 ${t('search.currentLocation')}`; } } } // Public API getFilters() { return { query: this.searchQuery, category: this.selectedCategory, subcategory: this.selectedSubcategory, country: this.selectedCountry, useCurrentLocation: this.useCurrentLocation, lat: this.currentLat, lng: this.currentLng, radius: this.selectedRadius }; } setFilters(filters) { if (filters.query !== undefined) this.searchQuery = filters.query; if (filters.category !== undefined) this.selectedCategory = filters.category; if (filters.subcategory !== undefined) this.selectedSubcategory = filters.subcategory; if (filters.country !== undefined) this.selectedCountry = filters.country; if (filters.radius !== undefined) this.selectedRadius = filters.radius; if (filters.useCurrentLocation !== undefined) this.useCurrentLocation = filters.useCurrentLocation; this.saveFiltersToStorage(); this.render(); this.setupEventListeners(); } clearFilters() { this.resetFilters(); localStorage.removeItem('searchFilters'); this.render(); this.setupEventListeners(); } } customElements.define('search-box', SearchBox); const style = document.createElement('style'); style.textContent = /* css */ ` search-box { display: block; } search-box .search-box { max-width: 800px; width: 100%; margin: 0 auto; background: var(--color-bg); border: 2px solid var(--color-border); border-radius: var(--radius-lg); box-sizing: border-box; } search-box .search-row { display: flex; border-bottom: 1px solid var(--color-border); } search-box .search-row:last-child { border-bottom: none; } search-box .search-field { flex: 1; display: flex; align-items: center; position: relative; } search-box .search-field + .search-field { border-left: 1px solid var(--color-border); } search-box .search-field.hidden, search-box .search-row.hidden { display: none; } search-box .field-icon { position: absolute; left: var(--space-md); color: var(--color-text-muted); pointer-events: none; } search-box .search-field input, search-box .search-field select { width: 100%; padding: var(--space-md); padding-left: 2.75rem; border: none; background: transparent; font-size: var(--font-size-base); color: var(--color-text); } search-box .search-field select { padding-left: var(--space-md); cursor: pointer; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236B5B95' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right var(--space-md) center; padding-right: 2.5rem; } search-box .search-field-country select { padding-left: 2.75rem; } search-box .search-field input:focus, search-box .search-field select:focus { background: var(--color-bg-secondary); } search-box .search-field input:focus-visible, search-box .search-field select:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; } search-box .search-field input::placeholder { color: var(--color-text-muted); } search-box .search-row-submit { padding: var(--space-sm); background: var(--color-bg-secondary); } search-box .btn-search { width: 100%; padding: var(--space-md); gap: var(--space-sm); } /* Mobile: full width for location/radius */ search-box .search-row-location .search-field, search-box .search-row-radius .search-field { flex: 1; } search-box .search-row-location .search-field select, search-box .search-row-radius .search-field select { width: 100%; } /* Mobile: hide desktop-only filter elements */ search-box .filter-location, search-box .filter-radius { display: none; } /* Desktop: two-row layout */ @media (min-width: 768px) { /* Hide mobile-only rows on desktop */ search-box .mobile-only { display: none !important; } /* Show inline filters on desktop */ search-box .filter-location, search-box .filter-radius { display: flex; border-left: 1px solid var(--color-border); } search-box .filter-radius.hidden { display: none; } search-box .filter-location .search-field, search-box .filter-radius .search-field { flex: 0 0 auto; } search-box .filter-location select, search-box .filter-radius select { width: auto; } search-box .search-box:not(.search-box--compact) { display: grid; grid-template-columns: 1fr auto; grid-template-rows: auto auto; border-radius: var(--radius-xl); } search-box .search-box:not(.search-box--compact) .search-row { border-bottom: none; } /* Row 1: Search field + Button */ search-box .search-box:not(.search-box--compact) .search-row-query { grid-column: 1; grid-row: 1; border-right: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border); } search-box .search-box:not(.search-box--compact) .search-row-submit { grid-column: 2; grid-row: 1; padding: var(--space-xs); background: transparent; border-bottom: 1px solid var(--color-border); } /* Row 2: Filters */ search-box .search-box:not(.search-box--compact) .search-row-filters { grid-column: 1 / -1; grid-row: 2; display: flex; border-right: none; } search-box .search-box:not(.search-box--compact) .btn-search { width: auto; padding: var(--space-md); border-radius: var(--radius-md); } search-box .search-box:not(.search-box--compact) .btn-search-text { display: none; } /* Category truncation */ search-box .search-box:not(.search-box--compact) .category-dropdown-label { max-width: 250px; } } /* Category Dropdown (Desktop) */ search-box .category-dropdown { position: relative; flex: 1; min-width: 0; } search-box .category-dropdown-trigger { width: 100%; display: flex; align-items: center; justify-content: space-between; padding: var(--space-md); background: transparent; border: none; font-size: var(--font-size-base); color: var(--color-text); cursor: pointer; text-align: left; transition: background var(--transition-fast); } search-box .category-dropdown-trigger:hover { background: var(--color-bg-secondary); } search-box .category-dropdown-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } search-box .category-dropdown-arrow { flex-shrink: 0; margin-left: var(--space-sm); color: var(--color-text-muted); transition: transform var(--transition-fast); } search-box .category-menu.open + .category-dropdown-arrow, search-box .category-dropdown-trigger:focus .category-dropdown-arrow { transform: rotate(180deg); } search-box .category-menu { position: absolute; top: 100%; left: 0; right: 0; background: var(--color-bg); border: 1px solid var(--color-border); border-radius: var(--radius-md); box-shadow: var(--shadow-lg); opacity: 0; visibility: hidden; transform: translateY(-8px); transition: all var(--transition-fast); z-index: var(--z-dropdown); max-width: calc(100vw - 2 * var(--space-md)); } search-box .category-menu.open { opacity: 1; visibility: visible; transform: translateY(4px); } search-box .category-menu-inner { padding: var(--space-xs); max-height: 400px; overflow-y: auto; } search-box .category-item, search-box .category-item--all { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm); padding: var(--space-sm) var(--space-md); background: transparent; border: none; border-radius: var(--radius-sm); font-size: var(--font-size-sm); color: var(--color-text); cursor: pointer; text-align: left; transition: background var(--transition-fast); } search-box .category-item span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } search-box .category-item:hover, search-box .category-item--all:hover { background: var(--color-bg-secondary); } search-box .category-item.active, search-box .category-item--all.active { background: var(--color-primary-light); color: var(--color-primary); } search-box .category-item-arrow { flex-shrink: 0; color: var(--color-text-muted); transition: transform var(--transition-fast); } /* Accordion */ search-box .category-accordion { border-top: 1px solid var(--color-border); } search-box .category-accordion:first-of-type { border-top: none; } search-box .category-accordion .category-item-arrow { transform: rotate(0deg); } search-box .category-accordion.expanded .category-item-arrow { transform: rotate(180deg); } search-box .subcategory-list { display: none; padding: 0 var(--space-xs) var(--space-xs); } search-box .category-accordion.expanded .subcategory-list { display: block; } search-box .subcategory-item { width: 100%; display: block; padding: var(--space-sm) var(--space-md); padding-left: var(--space-xl); background: transparent; border: none; border-radius: var(--radius-sm); font-size: var(--font-size-sm); color: var(--color-text-secondary); cursor: pointer; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: background var(--transition-fast), color var(--transition-fast); } search-box .subcategory-item:hover { background: var(--color-bg-secondary); color: var(--color-text); } search-box .subcategory-item.active { background: var(--color-primary-light); color: var(--color-primary); } /* Compact mode */ search-box .search-box--compact { max-width: 500px; display: flex; border-radius: var(--radius-xl); } search-box .search-box--compact .search-row { border-bottom: none; } search-box .search-box--compact .search-row-query { flex: 1; } search-box .search-box--compact .search-row-submit { flex: 0 0 auto; padding: var(--space-xs); background: transparent; } search-box .search-box--compact .btn-search { width: auto; padding: var(--space-sm) var(--space-md); border-radius: var(--radius-md); } search-box .search-box--compact .btn-search-text { display: none; } `; document.head.appendChild(style); export { SearchBox, CATEGORIES, COUNTRIES, RADIUS_OPTIONS };