asset_browser/public/modules/scanner.js
2026-03-27 09:18:39 -04:00

128 lines
4 KiB
JavaScript
Executable file

// 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);
}