128 lines
4 KiB
JavaScript
Executable file
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);
|
|
}
|