// searchAutocomplete.js — universal search dropdown for the scan/search input. import { searchLocal } from './assetBrowser.js'; let _onLocalSelect = null; let _onRemoteSearch = null; let _input = null; let _dropdown = null; let _items = []; // { type:'asset'|'syncro', asset?, serial?, contact?, customerName?, query? } let _activeIdx = -1; let _inputTimer = null; let _blurTimer = null; // ── Init ────────────────────────────────────────────────────────────────────── export function initSearchAutocomplete({ onLocalSelect, onRemoteSearch }) { _onLocalSelect = onLocalSelect; _onRemoteSearch = onRemoteSearch; _input = document.getElementById('scan-input'); if (!_input) return; // Append dropdown inside scan-input-wrap so it's positioned relative to it _dropdown = document.createElement('div'); _dropdown.id = 'search-autocomplete'; _dropdown.className = 'search-autocomplete'; _dropdown.hidden = true; _input.closest('.scan-input-wrap').appendChild(_dropdown); _input.addEventListener('input', _onInput); _input.addEventListener('blur', () => { _blurTimer = setTimeout(_close, 150); }); _input.addEventListener('focus', () => { clearTimeout(_blurTimer); if (_input.value.trim().length >= 2) _onInput(); }); // Prevent blur when clicking a dropdown item _dropdown.addEventListener('mousedown', e => e.preventDefault()); _dropdown.addEventListener('click', e => { const item = e.target.closest('.ac-item'); if (item) _selectIdx(Number(item.dataset.idx)); }); } // ── Key handler — called by scanner's key interceptor ───────────────────────── export function handleAutocompleteKey(e) { if (_dropdown.hidden) return false; if (e.key === 'ArrowDown') { e.preventDefault(); _setActive(Math.min(_activeIdx + 1, _items.length - 1)); return true; } if (e.key === 'ArrowUp') { e.preventDefault(); _setActive(Math.max(_activeIdx - 1, 0)); return true; } if (e.key === 'Enter') { if (_activeIdx >= 0) { e.preventDefault(); _selectIdx(_activeIdx); return true; } _close(); // close but let scanner handle Enter return false; } if (e.key === 'Escape') { _close(); return true; // prevent scanner's clearInput } return false; } // ── Internal ────────────────────────────────────────────────────────────────── function _onInput() { clearTimeout(_inputTimer); _inputTimer = setTimeout(() => { const query = _input.value.trim(); if (query.length < 2) { _close(); return; } _renderDropdown(query); }, 200); } function _renderDropdown(query) { const localResults = searchLocal(query); _items = [ ...localResults.map(r => ({ type: 'asset', ...r })), { type: 'syncro', query }, ]; _activeIdx = -1; _dropdown.innerHTML = _items.map((item, i) => { if (item.type === 'syncro') { return `
Search Syncro for ${_esc(item.query)}
`; } const { asset, serial, contact, customerName } = item; const meta = [customerName, serial, contact].filter(Boolean).join(' · '); return `
${_esc(asset.name ?? `Asset ${asset.id}`)}
${meta ? `
${_esc(meta)}
` : ''}
`; }).join(''); _dropdown.hidden = false; } function _setActive(idx) { _activeIdx = idx; _dropdown.querySelectorAll('.ac-item').forEach((el, i) => { el.classList.toggle('ac-active', i === idx); if (i === idx) el.scrollIntoView({ block: 'nearest' }); }); } function _selectIdx(idx) { const item = _items[idx]; if (!item) return; _close(); _input.value = ''; _input.dispatchEvent(new Event('input')); // sync clear-btn visibility if (item.type === 'asset') { _onLocalSelect?.(item.asset); } else { _onRemoteSearch?.(item.query); } } function _close() { _dropdown.hidden = true; _activeIdx = -1; _items = []; } function _esc(s) { return String(s ?? '').replace(/&/g, '&').replace(//g, '>'); }