UNPKG

use-scan-detection

Version:

A react hook for detecting barcode scanner input.

132 lines (120 loc) 4.16 kB
import { useCallback, useEffect, useRef } from "react"; interface bufferCharacter { time: number, char: String } interface buffer { current: Array<bufferCharacter> } interface config { /** Time to wait from last character to then trigger an evaluation of the buffer. */ timeToEvaluate?: number, /** Average time between characters in milliseconds. Used to determine if input is from keyboard or a scanner. Defaults to 50ms.*/ averageWaitTime?: number, /** Character that barcode scanner prefixes input with.*/ startCharacter?: Array<number>, /** Character that barcode scanner suffixes input with. Defaults to line return.*/ endCharacter?: Array<number>, /** Callback to use on complete scan input.*/ onComplete: (code: String) => void, /** Callback to use on error. */ onError?: (error: String) => void, /** Minimum length a scanned code should be. Defaults to 0.*/ minLength?: number, /** Ignore scan input if this node is focused.*/ ignoreIfFocusOn?: Node, /** Stop propagation on keydown event. Defaults to false.*/ stopPropagation?: Boolean, /** Prevent default on keydown event. Defaults to false.*/ preventDefault?: Boolean, /** Bind keydown event to this node. Defaults to document.*/ container?: Node } /** * Checks for scan input within a container and sends the output to a callback function. * @param param0 Config object */ const useScanDetection = ({ timeToEvaluate = 100, averageWaitTime = 50, startCharacter = [], endCharacter = [13, 27], onComplete, onError, minLength = 1, ignoreIfFocusOn, stopPropagation = false, preventDefault = false, container = document }: config) => { const buffer: buffer = useRef([]) const timeout: any = useRef(false) const clearBuffer = () => { buffer.current = [] } const evaluateBuffer = () => { clearTimeout(timeout.current) const sum = buffer.current .map(({ time }, k, arr) => k > 0 ? time - arr[k - 1].time : 0) .slice(1) .reduce((total, delta) => total + delta, 0) const avg = sum / (buffer.current.length - 1) const code = buffer.current .slice(startCharacter.length > 0 ? 1 : 0) .map(({ char }) => char) .join("") if ( avg <= averageWaitTime && buffer.current.slice(startCharacter.length > 0 ? 1 : 0).length >= minLength ) { onComplete(code) } else { avg <= averageWaitTime && !!onError && onError(code) } clearBuffer() } const onKeyDown: Function = useCallback((event: KeyboardEvent) => { if (event.currentTarget !== ignoreIfFocusOn) { if (endCharacter.includes(event.keyCode)) { evaluateBuffer() } if (buffer.current.length > 0 || startCharacter.includes(event.keyCode) || startCharacter.length === 0) { clearTimeout(timeout.current) timeout.current = setTimeout(evaluateBuffer, timeToEvaluate) buffer.current.push({ time: performance.now(), char: event.key }) } } if (stopPropagation) { event.stopPropagation() } if (preventDefault) { event.preventDefault() } }, [ startCharacter, endCharacter, timeToEvaluate, onComplete, onError, minLength, ignoreIfFocusOn, stopPropagation, preventDefault ]) useEffect(() => { return () => { clearTimeout(timeout.current) } }, []) useEffect(() => { container.addEventListener("keydown", (onKeyDown) as EventListener) return () => { container.removeEventListener("keydown", (onKeyDown) as EventListener) } }, [onKeyDown]) } export default useScanDetection