UNPKG

barcode-scan-js

Version:

JavaScript browser-based scanner keyboard input library for seamless integration of barcode and QR code scanning into web applications

141 lines (125 loc) 4.34 kB
type BarcodeScanConfiguration = { codeRegex?: RegExp | null; minLength?: number; maxLength?: number; scanEndsWithKey?: string; scanTimeoutMs?: number; ignoreOverElements?: string[]; }; type ScanDetected = { code: string; cleanedCode: string; length: number; isValid: boolean; timeTakenMs: number; config?: BarcodeScanConfiguration errorMessage?: string }; class BarcodeScanElement extends HTMLElement { private config: BarcodeScanConfiguration = { codeRegex: null, minLength: 1, maxLength: 14, scanEndsWithKey: "", scanTimeoutMs: 3000, ignoreOverElements: ["INPUT"] }; private inputKeys:string[] = []; private startTime: number | null = null; private timeout: any | null = null; constructor() { super(); } connectedCallback() { this.setupConfig(); document.addEventListener("keydown", this.onKeyDown.bind(this)); } disconnectedCallback() { document.removeEventListener("keydown", this.onKeyDown.bind(this)); } private setupConfig() { const inputConfig = this.getAttribute("config"); if (inputConfig) { try { this.config = Object.assign(this.config, JSON.parse(inputConfig)); this.config.codeRegex = this.config.codeRegex ?? new RegExp( `^${'(.'}${this.config.minLength!},${this.config.maxLength! - this.config.scanEndsWithKey!.length}${')'} ${this.config.scanEndsWithKey}$`, 'g' ); } catch (error) { console.error("Invalid configuration:", error); } } } private escapeRegExp(string: string) { return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } private onKeyDown(ev: KeyboardEvent) { if ( this.config.ignoreOverElements?.includes( (ev.target as HTMLElement).tagName ) ) { return; } if (this.startTime === null) { this.startTime = performance.now(); } this.inputKeys.push(ev.key); if (ev.key === this.config.scanEndsWithKey) { this.validateInput(); } if (this.timeout) { clearTimeout(this.timeout); } this.timeout = setTimeout(() => { this.validateInput(); }, this.config.scanTimeoutMs || 1000); } private validateInput() { if (!this.startTime) return; const endTime = performance.now(); const timeTakenMs = Math.round(endTime - this.startTime); const cleanedCode = this.inputKeys.filter(Boolean).filter(f => f != 'Shift' && f != 'Enter' && f != 'NumLock' && f != 'ArrowDown').join(''); let errorMessage = ''; switch (true) { case cleanedCode.length < this.config.minLength!: errorMessage = `${cleanedCode} length is < ${this.config.minLength}` case cleanedCode.length > this.config.maxLength!: errorMessage = `${cleanedCode} length is > ${this.config.maxLength}` case this.config.codeRegex!.test(cleanedCode): errorMessage = `${cleanedCode} doesn't pass regex ${this.config.codeRegex}` } if (!errorMessage) { this.dispatchEvent( new CustomEvent<ScanDetected>("scan", { detail: { code: cleanedCode, length, isValid: true, timeTakenMs, cleanedCode, }, }) ); } else { this.dispatchEvent( new CustomEvent<ScanDetected>("scan", { detail: { code: cleanedCode, length, isValid: false, config: this.config, timeTakenMs, cleanedCode, errorMessage: errorMessage }, }) ); } this.inputKeys = []; this.startTime = null; } } customElements.define("barcode-scan", BarcodeScanElement);