// scanner.js — manages the always-focused scan input in the header. // Barcode scanners act as USB HID keyboard wedges; they type fast and send Enter. let _onScan = null; let _idleTimer = null; let _onIdle = null; let _input = null; let _clearBtn = null; let _timerDuration = null; // null = off, or ms duration // ── Init ────────────────────────────────────────────────────────────────────── let _canFocus = () => true; let _paused = false; let _keyInterceptor = null; export function setKeyInterceptor(fn) { _keyInterceptor = fn; } export function pause() { _paused = true; _input?.blur(); } export function resume() { _paused = false; focusScanInput(); } export function initScanner({ onScan, onIdle, canFocus }) { _onScan = onScan; _onIdle = onIdle; if (canFocus) _canFocus = canFocus; _input = document.getElementById('scan-input'); _clearBtn = document.getElementById('scan-clear'); _input.addEventListener('keydown', _handleKeydown); _input.addEventListener('input', _handleInput); _clearBtn.addEventListener('click', () => { clearInput(); focusScanInput(); }); // Re-focus when clicking dead space — only when scan mode is active and no text is selected document.addEventListener('click', (e) => { if (!_canFocus()) return; if (window.getSelection()?.toString()) return; const interactive = e.target.closest( 'button, a, input, select, textarea, [data-no-refocus]' ); if (!interactive) focusScanInput(); }); // Activity detection — mouse/pointer/key resets idle timer while asset is shown document.addEventListener('mousemove', _onActivity, { passive: true }); document.addEventListener('pointerdown', _onActivity, { passive: true }); document.addEventListener('keydown', _onActivity, { passive: true }); focusScanInput(); } // ── Timer toggle ────────────────────────────────────────────────────────────── export function setTimerDuration(ms) { _timerDuration = ms ?? null; if (!_timerDuration) clearTimeout(_idleTimer); } export function getTimerDuration() { return _timerDuration; } // ── Activity ────────────────────────────────────────────────────────────────── function _onActivity() { const assetView = document.getElementById('view-asset'); if (assetView?.classList.contains('active')) { resetIdleTimer(); } } // ── Scan input ──────────────────────────────────────────────────────────────── function _handleKeydown(e) { if (_paused) return; if (_keyInterceptor?.(e)) return; if (e.key === 'Enter') { e.preventDefault(); const value = _input.value.trim(); if (value) _fireScanned(value); } if (e.key === 'Escape') clearInput(); } function _handleInput() { _clearBtn.hidden = !_input.value.trim(); } function _fireScanned(value) { clearInput(); resetIdleTimer(); if (_onScan) _onScan(value); } // ── Exports ─────────────────────────────────────────────────────────────────── export function focusScanInput() { if (_input && document.activeElement !== _input) { _input.focus(); } } export function clearInput() { if (_input) _input.value = ''; if (_clearBtn) _clearBtn.hidden = true; } export function resetIdleTimer() { if (!_timerDuration) return; clearTimeout(_idleTimer); _idleTimer = setTimeout(() => { if (_onIdle) _onIdle(); }, _timerDuration); } export function cancelIdleTimer() { clearTimeout(_idleTimer); }