├── .babelrc ├── .eslintrc.json ├── .gitignore ├── .storybook └── main.js ├── LICENSE ├── README.md ├── docs ├── favicon.ico ├── iframe.html ├── index.html ├── main.5da119d0a62d15786474.bundle.js ├── main.f784816d1f43c2dfc5c8.bundle.js ├── main.f784816d1f43c2dfc5c8.bundle.js.map ├── runtime~main.8bfece81a1ead32e094c.bundle.js ├── runtime~main.f784816d1f43c2dfc5c8.bundle.js ├── runtime~main.f784816d1f43c2dfc5c8.bundle.js.map ├── sb_dll │ ├── storybook_ui-manifest.json │ ├── storybook_ui_dll.LICENCE │ └── storybook_ui_dll.js ├── vendors~main.e8135d03a47b34cf111e.bundle.js ├── vendors~main.f784816d1f43c2dfc5c8.bundle.js ├── vendors~main.f784816d1f43c2dfc5c8.bundle.js.LICENSE.txt └── vendors~main.f784816d1f43c2dfc5c8.bundle.js.map ├── examples └── example.js ├── gulpfile.js ├── lib └── index.js ├── package-lock.json ├── package.json ├── src └── index.js └── story.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-syntax-dynamic-import", 8 | "@babel/plugin-syntax-import-meta", 9 | "@babel/plugin-proposal-class-properties", 10 | "@babel/plugin-proposal-json-strings", 11 | [ 12 | "@babel/plugin-proposal-decorators", 13 | { 14 | "legacy": true 15 | } 16 | ], 17 | "@babel/plugin-proposal-function-sent", 18 | "@babel/plugin-proposal-export-namespace-from", 19 | "@babel/plugin-proposal-numeric-separator", 20 | "@babel/plugin-proposal-throw-expressions", 21 | "@babel/plugin-proposal-export-default-from", 22 | "@babel/plugin-proposal-logical-assignment-operators", 23 | "@babel/plugin-proposal-optional-chaining", 24 | [ 25 | "@babel/plugin-proposal-pipeline-operator", 26 | { 27 | "proposal": "minimal" 28 | } 29 | ], 30 | "@babel/plugin-proposal-nullish-coalescing-operator", 31 | "@babel/plugin-proposal-do-expressions", 32 | "@babel/plugin-proposal-function-bind", 33 | "@babel/plugin-transform-modules-commonjs" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "es6": true, 5 | "node": true, 6 | "browser": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "ecmaVersion": 7, 11 | "sourceType": "module", 12 | "ecmaFeatures": { 13 | "jsx": true, 14 | "experimentalObjectRestSpread": true, 15 | "classes": true 16 | } 17 | }, 18 | "plugins": [ 19 | "react" 20 | ], 21 | "rules": { 22 | "indent": [ 23 | "error", 24 | 2 25 | ], 26 | "quotes": [ 27 | "error", 28 | "single" 29 | ], 30 | "linebreak-style": [ 31 | "error", 32 | "unix" 33 | ], 34 | "semi": [ 35 | "error", 36 | "never" 37 | ], 38 | "comma-dangle": [ 39 | "warn", 40 | "always-multiline" 41 | ], 42 | "constructor-super": "error", 43 | "no-confusing-arrow": "error", 44 | "no-constant-condition": "error", 45 | "no-class-assign": "error", 46 | "no-const-assign": "error", 47 | "no-dupe-class-members": "error", 48 | "no-var": "warn", 49 | "no-this-before-super": "error", 50 | "no-extra-boolean-cast": "off", 51 | "object-shorthand": [ 52 | "error", 53 | "always" 54 | ], 55 | "prefer-spread": "warn", 56 | "prefer-template": "warn", 57 | "require-yield": "error", 58 | "jsx-quotes": "warn", 59 | "react/jsx-boolean-value": "warn", 60 | "react/jsx-curly-spacing": "warn", 61 | "react/jsx-no-duplicate-props": "warn", 62 | "react/jsx-no-undef": "warn", 63 | "react/jsx-uses-react": "warn", 64 | "react/jsx-uses-vars": "warn", 65 | "react/no-danger": "warn", 66 | "react/no-did-mount-set-state": "warn", 67 | "react/no-did-update-set-state": "warn", 68 | "react/no-direct-mutation-state": "warn", 69 | "react/no-multi-comp": "warn", 70 | "react/no-set-state": "warn", 71 | "react/no-unknown-property": "warn", 72 | "react/prefer-es6-class": "warn", 73 | "react/prop-types": "warn", 74 | "react/react-in-jsx-scope": "warn", 75 | "react/self-closing-comp": "warn", 76 | "react/sort-comp": "warn" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | 36 | lib/worker.js -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../story.js'], 3 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Thomas Billiet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/react-barcode-reader.svg)](https://badge.fury.io/js/react-barcode-reader) 2 | 3 | ## Introduction 4 | A [React](https://facebook.github.io/react/) component for reading barcode and QR codes from devices that are represent as keyboard to the system. 5 | 6 | ## Demo 7 | [kybarg.github.io/react-barcode-reader/](https://kybarg.github.io/react-barcode-reader/) 8 | 9 | ## Install 10 | `npm install --save react-barcode-reader` 11 | 12 | ## Example 13 | 14 | ```js 15 | import React, { Component } from 'react' 16 | import BarcodeReader from 'react-barcode-reader' 17 | 18 | class Test extends Component { 19 | constructor(props){ 20 | super(props) 21 | this.state = { 22 | result: 'No result', 23 | } 24 | 25 | this.handleScan = this.handleScan.bind(this) 26 | } 27 | handleScan(data){ 28 | this.setState({ 29 | result: data, 30 | }) 31 | } 32 | handleError(err){ 33 | console.error(err) 34 | } 35 | render(){ 36 | 37 | return( 38 |
39 | 43 |

{this.state.result}

44 |
45 | ) 46 | } 47 | } 48 | ``` 49 | 50 | ## Props 51 | | Prop | Type | Default Value | Description | 52 | |---|---|---|---| 53 | | onScan | func | | Callback after detection of a successfull scanning (scanned string in parameter) | 54 | | onError | func | | Callback after detection of a unsuccessfull scanning (scanned string in parameter) | 55 | | HonReceive | func | | Callback after receiving and processing a char (scanned char in parameter) 56 | | onKeyDetect | func | | Callback after detecting a keyDown (key char in parameter) - in contrast to onReceive, this fires for non-character keys like tab, arrows, etc. too! 57 | | timeBeforeScanTest | number | 100 | Wait duration (ms) after keypress event to check if scanning is finished 58 | | avgTimeByChar | number | 30 | Average time (ms) between 2 chars. Used to do difference between keyboard typing and scanning 59 | | minLength | number | 6 | Minimum length for a scanning 60 | | endChar | [number] | [9, 13] | Chars to remove and means end of scanning 61 | | startChar | [number] | [] | Chars to remove and means start of scanning 62 | | scanButtonKeyCode | number | | Key code of the scanner hardware button (if the scanner button a acts as a key itself) 63 | | scanButtonLongPressThreshold | number | 3 | How many times the hardware button should issue a pressed event before a barcode is read to detect a longpress 64 | | onScanButtonLongPressed | func | | Callback after detection of a successfull scan while the scan button was pressed and held down 65 | | stopPropagation | bool | false | Stop immediate propagation on keypress event 66 | | preventDefault | bool | false | Prevent default action on keypress event 67 | | testCode | string | | Test string for simulating 68 | 69 | ## Dev 70 | 71 | ### Install dependencies 72 | `npm install` 73 | 74 | ### Build 75 | `npm run build` 76 | 77 | ### Demo 78 | `npm run storybook` 79 | 80 | ### Test 81 | `npm test` 82 | 83 | ### Linting 84 | `npm run lint` 85 | 86 | ## License 87 | The MIT License (MIT) 88 | 89 | Copyright (c) 2017 Thomas Billiet 90 | 91 | Permission is hereby granted, free of charge, to any person obtaining a copy 92 | of this software and associated documentation files (the "Software"), to deal 93 | in the Software without restriction, including without limitation the rights 94 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 95 | copies of the Software, and to permit persons to whom the Software is 96 | furnished to do so, subject to the following conditions: 97 | 98 | The above copyright notice and this permission notice shall be included in all 99 | copies or substantial portions of the Software. 100 | 101 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 102 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 103 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 104 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 105 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 106 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 107 | SOFTWARE. 108 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kybarg/react-barcode-reader/3e1b1c2e635dc4d63dd5c19fb5858b4dcb17d0d6/docs/favicon.ico -------------------------------------------------------------------------------- /docs/iframe.html: -------------------------------------------------------------------------------- 1 | Storybook

No Preview

Sorry, but you either have no stories or none are selected somehow.

If the problem persists, check the browser console, or the terminal you've run Storybook from.

-------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | Storybook
-------------------------------------------------------------------------------- /docs/main.5da119d0a62d15786474.bundle.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[0],{401:function(n,o,p){p(402),n.exports=p(545)},464:function(n,o){}},[[401,1,2]]]); -------------------------------------------------------------------------------- /docs/main.f784816d1f43c2dfc5c8.bundle.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[0],{270:function(module,exports,__webpack_require__){__webpack_require__(271),__webpack_require__(417),module.exports=__webpack_require__(418)},335:function(module,exports){},418:function(module,exports,__webpack_require__){"use strict";(function(module){var _react=__webpack_require__(214);module._StorybookPreserveDecorators=!0,(0,_react.configure)([__webpack_require__(606)],module)}).call(this,__webpack_require__(57)(module))},606:function(module,exports,__webpack_require__){var map={"./story.js":607};function webpackContext(req){var id=webpackContextResolve(req);return __webpack_require__(id)}function webpackContextResolve(req){if(!__webpack_require__.o(map,req)){var e=new Error("Cannot find module '"+req+"'");throw e.code="MODULE_NOT_FOUND",e}return map[req]}webpackContext.keys=function webpackContextKeys(){return Object.keys(map)},webpackContext.resolve=webpackContextResolve,module.exports=webpackContext,webpackContext.id=606},607:function(module,exports,__webpack_require__){"use strict";(function(module){__webpack_require__(16),__webpack_require__(18),__webpack_require__(19),__webpack_require__(17),__webpack_require__(34),__webpack_require__(112),__webpack_require__(52),__webpack_require__(81),__webpack_require__(113),__webpack_require__(14),__webpack_require__(267),__webpack_require__(35),__webpack_require__(20),__webpack_require__(21);var _react=function _interopRequireWildcard(obj){if(obj&&obj.__esModule)return obj;if(null===obj||"object"!==_typeof(obj)&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache();if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if(Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(__webpack_require__(66)),_react2=__webpack_require__(214),_addonActions=__webpack_require__(608),_lib=function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}(__webpack_require__(621));function _getRequireWildcardCache(){if("function"!=typeof WeakMap)return null;var cache=new WeakMap;return _getRequireWildcardCache=function(){return cache},cache}function _typeof(obj){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(obj){return typeof obj}:function(obj){return obj&&"function"==typeof Symbol&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj})(obj)}function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor))throw new TypeError("Cannot call a class as a function")}function _defineProperties(target,props){for(var descriptor,i=0;i=minLength&&_this.lastCharTime-_this.firstCharTime<_this.stringWriting.length*avgTimeByChar)return onScanButtonLongPressed&&_this.scanButtonCounter>scanButtonLongPressThreshold?onScanButtonLongPressed(_this.stringWriting,_this.scanButtonCounter):onScan&&onScan(_this.stringWriting,_this.scanButtonCounter),_this.initScannerDetection(),!0;var errorMsg="";return _this.stringWriting.length_this.stringWriting.length*avgTimeByChar&&(errorMsg="Average key character time should be less or equal ".concat(avgTimeByChar,"ms")),onError&&onError(_this.stringWriting,errorMsg),_this.initScannerDetection(),!1})),_defineProperty(_assertThisInitialized(_this),"handleKeyPress",(function(e){var _this$props2=_this.props,onKeyDetect=_this$props2.onKeyDetect,onReceive=_this$props2.onReceive,scanButtonKeyCode=_this$props2.scanButtonKeyCode,stopPropagation=_this$props2.stopPropagation,preventDefault=_this$props2.preventDefault,endChar=_this$props2.endChar,startChar=_this$props2.startChar,timeBeforeScanTest=_this$props2.timeBeforeScanTest,target=e.target;target instanceof window.HTMLElement&&function isInput(element){if(!element)return!1;var tagName=element.tagName,editable=function isContentEditable(element){return"function"==typeof element.getAttribute&&!!element.getAttribute("contenteditable")}(element);return"INPUT"===tagName||"TEXTAREA"===tagName||editable}(target)||(scanButtonKeyCode&&e.which===scanButtonKeyCode&&(_this.scanButtonCounter+=1,e.preventDefault(),e.stopImmediatePropagation()),onKeyDetect&&onKeyDetect(e),stopPropagation&&e.stopImmediatePropagation(),preventDefault&&e.preventDefault(),_this.firstCharTime&&-1!==endChar.indexOf(e.which)?(e.preventDefault(),e.stopImmediatePropagation(),_this.callIsScanner=!0):_this.firstCharTime||-1===startChar.indexOf(e.which)?(void 0!==e.which&&(_this.stringWriting+=String.fromCharCode(e.which)),_this.callIsScanner=!1):(e.preventDefault(),e.stopImmediatePropagation(),_this.callIsScanner=!1),_this.firstCharTime||(_this.firstCharTime=Date.now()),_this.lastCharTime=Date.now(),_this.testTimer&&clearTimeout(_this.testTimer),_this.callIsScanner?(_this.scannerDetectionTest(),_this.testTimer=!1):_this.testTimer=setTimeout(_this.scannerDetectionTest,timeBeforeScanTest),onReceive&&onReceive(e))})),_this.firstCharTime=0,_this.lastCharTime=0,_this.stringWriting="",_this.callIsScanner=!1,_this.testTimer=!1,_this.scanButtonCounter=0,_this}return function _createClass(Constructor,protoProps,staticProps){return protoProps&&_defineProperties(Constructor.prototype,protoProps),staticProps&&_defineProperties(Constructor,staticProps),Constructor}(BarcodeScanner,[{key:"componentDidMount",value:function(){inIframe&&window.parent.document.addEventListener("keypress",this.handleKeyPress),window.document.addEventListener("keypress",this.handleKeyPress)}},{key:"componentWillUnmount",value:function(){inIframe&&window.parent.document.removeEventListener("keypress",this.handleKeyPress),window.document.removeEventListener("keypress",this.handleKeyPress)}},{key:"render",value:function(){return this.props.testCode&&this.scannerDetectionTest(this.props.testCode),null}}]),BarcodeScanner}(_react.default.Component);BarcodeScanner.propTypes={onScan:_propTypes.default.func,onError:_propTypes.default.func,onReceive:_propTypes.default.func,onKeyDetect:_propTypes.default.func,timeBeforeScanTest:_propTypes.default.number,avgTimeByChar:_propTypes.default.number,minLength:_propTypes.default.number,endChar:_propTypes.default.arrayOf(_propTypes.default.number),startChar:_propTypes.default.arrayOf(_propTypes.default.number),scanButtonKeyCode:_propTypes.default.number,scanButtonLongPressThreshold:_propTypes.default.number,onScanButtonLongPressed:_propTypes.default.func,stopPropagation:_propTypes.default.bool,preventDefault:_propTypes.default.bool,testCode:_propTypes.default.string},BarcodeScanner.defaultProps={timeBeforeScanTest:100,avgTimeByChar:30,minLength:6,endChar:[9,13],startChar:[],scanButtonLongPressThreshold:3,stopPropagation:!1,preventDefault:!1};var _default=BarcodeScanner;exports.default=_default}},[[270,1,2]]]); 2 | //# sourceMappingURL=main.f784816d1f43c2dfc5c8.bundle.js.map -------------------------------------------------------------------------------- /docs/main.f784816d1f43c2dfc5c8.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"main.f784816d1f43c2dfc5c8.bundle.js","sources":["webpack:///main.f784816d1f43c2dfc5c8.bundle.js"],"mappings":"AAAA","sourceRoot":""} -------------------------------------------------------------------------------- /docs/runtime~main.8bfece81a1ead32e094c.bundle.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,l,f=r[0],i=r[1],a=r[2],c=0,s=[];c 40 | * 41 | * Copyright (c) 2014-2017, Jon Schlinkert. 42 | * Released under the MIT License. 43 | */ 44 | 45 | /** 46 | * @license 47 | * Lodash 48 | * Copyright OpenJS Foundation and other contributors 49 | * Released under MIT license 50 | * Based on Underscore.js 1.8.3 51 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 52 | */ 53 | 54 | /** @license React v0.18.0 55 | * scheduler.production.min.js 56 | * 57 | * Copyright (c) Facebook, Inc. and its affiliates. 58 | * 59 | * This source code is licensed under the MIT license found in the 60 | * LICENSE file in the root directory of this source tree. 61 | */ 62 | 63 | /** @license React v16.12.0 64 | * react-dom.production.min.js 65 | * 66 | * Copyright (c) Facebook, Inc. and its affiliates. 67 | * 68 | * This source code is licensed under the MIT license found in the 69 | * LICENSE file in the root directory of this source tree. 70 | */ 71 | 72 | /** @license React v16.12.0 73 | * react-is.production.min.js 74 | * 75 | * Copyright (c) Facebook, Inc. and its affiliates. 76 | * 77 | * This source code is licensed under the MIT license found in the 78 | * LICENSE file in the root directory of this source tree. 79 | */ 80 | 81 | /** @license React v16.12.0 82 | * react.production.min.js 83 | * 84 | * Copyright (c) Facebook, Inc. and its affiliates. 85 | * 86 | * This source code is licensed under the MIT license found in the 87 | * LICENSE file in the root directory of this source tree. 88 | */ 89 | 90 | /**! 91 | * @fileOverview Kickass library to create and place poppers near their reference elements. 92 | * @version 1.16.1 93 | * @license 94 | * Copyright (c) 2016 Federico Zivolo and contributors 95 | * 96 | * Permission is hereby granted, free of charge, to any person obtaining a copy 97 | * of this software and associated documentation files (the "Software"), to deal 98 | * in the Software without restriction, including without limitation the rights 99 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 100 | * copies of the Software, and to permit persons to whom the Software is 101 | * furnished to do so, subject to the following conditions: 102 | * 103 | * The above copyright notice and this permission notice shall be included in all 104 | * copies or substantial portions of the Software. 105 | * 106 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 107 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 108 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 109 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 110 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 111 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 112 | * SOFTWARE. 113 | */ 114 | -------------------------------------------------------------------------------- /docs/vendors~main.f784816d1f43c2dfc5c8.bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | * https://github.com/es-shims/es5-shim 9 | * @license es5-shim Copyright 2009-2020 by contributors, MIT License 10 | * see https://github.com/es-shims/es5-shim/blob/master/LICENSE 11 | */ 12 | 13 | /*! 14 | * https://github.com/paulmillr/es6-shim 15 | * @license es6-shim Copyright 2013-2016 by Paul Miller (http://paulmillr.com) 16 | * and contributors, MIT License 17 | * es6-shim: v0.35.4 18 | * see https://github.com/paulmillr/es6-shim/blob/0.35.3/LICENSE 19 | * Details and documentation: 20 | * https://github.com/paulmillr/es6-shim/ 21 | */ 22 | 23 | /*! 24 | * is-plain-object 25 | * 26 | * Copyright (c) 2014-2017, Jon Schlinkert. 27 | * Released under the MIT License. 28 | */ 29 | 30 | /*! 31 | * isobject 32 | * 33 | * Copyright (c) 2014-2017, Jon Schlinkert. 34 | * Released under the MIT License. 35 | */ 36 | 37 | /** @license React v0.19.1 38 | * scheduler.production.min.js 39 | * 40 | * Copyright (c) Facebook, Inc. and its affiliates. 41 | * 42 | * This source code is licensed under the MIT license found in the 43 | * LICENSE file in the root directory of this source tree. 44 | */ 45 | 46 | /** @license React v16.13.1 47 | * react-dom.production.min.js 48 | * 49 | * Copyright (c) Facebook, Inc. and its affiliates. 50 | * 51 | * This source code is licensed under the MIT license found in the 52 | * LICENSE file in the root directory of this source tree. 53 | */ 54 | 55 | /** @license React v16.13.1 56 | * react.production.min.js 57 | * 58 | * Copyright (c) Facebook, Inc. and its affiliates. 59 | * 60 | * This source code is licensed under the MIT license found in the 61 | * LICENSE file in the root directory of this source tree. 62 | */ 63 | -------------------------------------------------------------------------------- /docs/vendors~main.f784816d1f43c2dfc5c8.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"vendors~main.f784816d1f43c2dfc5c8.bundle.js","sources":["webpack:///vendors~main.f784816d1f43c2dfc5c8.bundle.js"],"mappings":";AAAA","sourceRoot":""} -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import BarcodeReader from 'react-barcode-reader' 3 | 4 | class Example extends Component { 5 | constructor(props){ 6 | super(props) 7 | this.state = { 8 | result: 'No result', 9 | } 10 | 11 | this.handleScan = this.handleScan.bind(this) 12 | } 13 | handleScan(result){ 14 | if(result){ 15 | this.setState({ result }) 16 | } 17 | } 18 | handleError(err){ 19 | console.error(err) 20 | } 21 | render(){ 22 | return( 23 |
24 | 28 |

{this.state.result}

29 |
30 | ) 31 | } 32 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | const fs = require('fs') 3 | const del = require('del') 4 | const inlineStr = require('gulp-inline-str') 5 | const babel = require('gulp-babel') 6 | 7 | const babelOptions = JSON.parse(fs.readFileSync('./.babelrc', 'utf8')) 8 | 9 | const paths = { 10 | scripts: [ 'src/index.js'], 11 | destination: './lib', 12 | } 13 | 14 | gulp.task('clean', function() { 15 | return del([ paths.destination + '/*.js' ]) 16 | }) 17 | 18 | gulp.task('build', function() { 19 | return gulp 20 | .src(paths.scripts) 21 | .pipe(inlineStr({ basePath: paths.destination })) 22 | .pipe(babel(babelOptions)) 23 | .pipe(gulp.dest(paths.destination)) 24 | }) 25 | 26 | // Rerun the task when a file changes 27 | gulp.task('watch', function() { 28 | gulp.watch(paths.scripts, gulp.series([ 'build' ])) 29 | }) 30 | 31 | // The default task (called when you run `gulp` from cli) 32 | gulp.task('default', gulp.series([ 'build' ])) 33 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | var _propTypes = _interopRequireDefault(require("prop-types")); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 13 | 14 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 15 | 16 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 17 | 18 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 19 | 20 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 21 | 22 | function _createSuper(Derived) { return function () { var Super = _getPrototypeOf(Derived), result; if (_isNativeReflectConstruct()) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } 23 | 24 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 25 | 26 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 27 | 28 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } 29 | 30 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 31 | 32 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 33 | 34 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 35 | 36 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 37 | 38 | function isContentEditable(element) { 39 | if (typeof element.getAttribute !== 'function') { 40 | return false; 41 | } 42 | 43 | return !!element.getAttribute('contenteditable'); 44 | } 45 | 46 | function isInput(element) { 47 | if (!element) { 48 | return false; 49 | } 50 | 51 | var tagName = element.tagName; 52 | var editable = isContentEditable(element); 53 | return tagName === 'INPUT' || tagName === 'TEXTAREA' || editable; 54 | } 55 | 56 | function inIframe() { 57 | try { 58 | return window.self !== window.top; 59 | } catch (e) { 60 | return true; 61 | } 62 | } 63 | 64 | var BarcodeScanner = /*#__PURE__*/function (_React$Component) { 65 | _inherits(BarcodeScanner, _React$Component); 66 | 67 | var _super = _createSuper(BarcodeScanner); 68 | 69 | function BarcodeScanner(props) { 70 | var _this; 71 | 72 | _classCallCheck(this, BarcodeScanner); 73 | 74 | _this = _super.call(this, props); 75 | 76 | _defineProperty(_assertThisInitialized(_this), "initScannerDetection", function () { 77 | _this.firstCharTime = 0; 78 | _this.stringWriting = ''; 79 | _this.scanButtonCounter = 0; 80 | }); 81 | 82 | _defineProperty(_assertThisInitialized(_this), "scannerDetectionTest", function (s) { 83 | var _this$props = _this.props, 84 | minLength = _this$props.minLength, 85 | avgTimeByChar = _this$props.avgTimeByChar, 86 | onScanButtonLongPressed = _this$props.onScanButtonLongPressed, 87 | scanButtonLongPressThreshold = _this$props.scanButtonLongPressThreshold, 88 | onScan = _this$props.onScan, 89 | onError = _this$props.onError; // If string is given, test it 90 | 91 | if (s) { 92 | _this.firstCharTime = 0; 93 | _this.lastCharTime = 0; 94 | _this.stringWriting = s; 95 | } 96 | 97 | if (!_this.scanButtonCounter) { 98 | _this.scanButtonCounter = 1; 99 | } // If all condition are good (length, time...), call the callback and re-initialize the plugin for next scanning 100 | // Else, just re-initialize 101 | 102 | 103 | if (_this.stringWriting.length >= minLength && _this.lastCharTime - _this.firstCharTime < _this.stringWriting.length * avgTimeByChar) { 104 | if (onScanButtonLongPressed && _this.scanButtonCounter > scanButtonLongPressThreshold) onScanButtonLongPressed(_this.stringWriting, _this.scanButtonCounter);else if (onScan) onScan(_this.stringWriting, _this.scanButtonCounter); 105 | 106 | _this.initScannerDetection(); 107 | 108 | return true; 109 | } 110 | 111 | var errorMsg = ''; 112 | 113 | if (_this.stringWriting.length < minLength) { 114 | errorMsg = "String length should be greater or equal ".concat(minLength); 115 | } else { 116 | if (_this.lastCharTime - _this.firstCharTime > _this.stringWriting.length * avgTimeByChar) { 117 | errorMsg = "Average key character time should be less or equal ".concat(avgTimeByChar, "ms"); 118 | } 119 | } 120 | 121 | if (onError) onError(_this.stringWriting, errorMsg); 122 | 123 | _this.initScannerDetection(); 124 | 125 | return false; 126 | }); 127 | 128 | _defineProperty(_assertThisInitialized(_this), "handleKeyPress", function (e) { 129 | var _this$props2 = _this.props, 130 | onKeyDetect = _this$props2.onKeyDetect, 131 | onReceive = _this$props2.onReceive, 132 | scanButtonKeyCode = _this$props2.scanButtonKeyCode, 133 | stopPropagation = _this$props2.stopPropagation, 134 | preventDefault = _this$props2.preventDefault, 135 | endChar = _this$props2.endChar, 136 | startChar = _this$props2.startChar, 137 | timeBeforeScanTest = _this$props2.timeBeforeScanTest; 138 | var target = e.target; 139 | 140 | if (target instanceof window.HTMLElement && isInput(target)) { 141 | return; 142 | } // If it's just the button of the scanner, ignore it and wait for the real input 143 | 144 | 145 | if (scanButtonKeyCode && e.which === scanButtonKeyCode) { 146 | _this.scanButtonCounter += 1; // Cancel default 147 | 148 | e.preventDefault(); 149 | e.stopImmediatePropagation(); 150 | } // Fire keyDetect event in any case! 151 | 152 | 153 | if (onKeyDetect) onKeyDetect(e); 154 | if (stopPropagation) e.stopImmediatePropagation(); 155 | if (preventDefault) e.preventDefault(); 156 | 157 | if (_this.firstCharTime && endChar.indexOf(e.which) !== -1) { 158 | e.preventDefault(); 159 | e.stopImmediatePropagation(); 160 | _this.callIsScanner = true; 161 | } else if (!_this.firstCharTime && startChar.indexOf(e.which) !== -1) { 162 | e.preventDefault(); 163 | e.stopImmediatePropagation(); 164 | _this.callIsScanner = false; 165 | } else { 166 | if (typeof e.which !== 'undefined') { 167 | _this.stringWriting += String.fromCharCode(e.which); 168 | } 169 | 170 | _this.callIsScanner = false; 171 | } 172 | 173 | if (!_this.firstCharTime) { 174 | _this.firstCharTime = Date.now(); 175 | } 176 | 177 | _this.lastCharTime = Date.now(); 178 | if (_this.testTimer) clearTimeout(_this.testTimer); 179 | 180 | if (_this.callIsScanner) { 181 | _this.scannerDetectionTest(); 182 | 183 | _this.testTimer = false; 184 | } else { 185 | _this.testTimer = setTimeout(_this.scannerDetectionTest, timeBeforeScanTest); 186 | } 187 | 188 | if (onReceive) onReceive(e); 189 | }); 190 | 191 | _this.firstCharTime = 0; 192 | _this.lastCharTime = 0; 193 | _this.stringWriting = ''; 194 | _this.callIsScanner = false; 195 | _this.testTimer = false; 196 | _this.scanButtonCounter = 0; 197 | return _this; 198 | } 199 | 200 | _createClass(BarcodeScanner, [{ 201 | key: "componentDidMount", 202 | value: function componentDidMount() { 203 | if (inIframe) window.parent.document.addEventListener('keypress', this.handleKeyPress); 204 | window.document.addEventListener('keypress', this.handleKeyPress); 205 | } 206 | }, { 207 | key: "componentWillUnmount", 208 | value: function componentWillUnmount() { 209 | if (inIframe) window.parent.document.removeEventListener('keypress', this.handleKeyPress); 210 | window.document.removeEventListener('keypress', this.handleKeyPress); 211 | } 212 | }, { 213 | key: "render", 214 | value: function render() { 215 | if (this.props.testCode) this.scannerDetectionTest(this.props.testCode); 216 | return null; 217 | } 218 | }]); 219 | 220 | return BarcodeScanner; 221 | }(_react["default"].Component); 222 | 223 | BarcodeScanner.propTypes = { 224 | onScan: _propTypes["default"].func, 225 | // Callback after detection of a successfull scanning (scanned string in parameter) 226 | onError: _propTypes["default"].func, 227 | // Callback after detection of a unsuccessfull scanning (scanned string in parameter) 228 | onReceive: _propTypes["default"].func, 229 | // Callback after receiving and processing a char (scanned char in parameter) 230 | onKeyDetect: _propTypes["default"].func, 231 | // Callback after detecting a keyDown (key char in parameter) - in contrast to onReceive, this fires for non-character keys like tab, arrows, etc. too! 232 | timeBeforeScanTest: _propTypes["default"].number, 233 | // Wait duration (ms) after keypress event to check if scanning is finished 234 | avgTimeByChar: _propTypes["default"].number, 235 | // Average time (ms) between 2 chars. Used to do difference between keyboard typing and scanning 236 | minLength: _propTypes["default"].number, 237 | // Minimum length for a scanning 238 | endChar: _propTypes["default"].arrayOf(_propTypes["default"].number), 239 | // Chars to remove and means end of scanning 240 | startChar: _propTypes["default"].arrayOf(_propTypes["default"].number), 241 | // Chars to remove and means start of scanning 242 | scanButtonKeyCode: _propTypes["default"].number, 243 | // Key code of the scanner hardware button (if the scanner button a acts as a key itself) 244 | scanButtonLongPressThreshold: _propTypes["default"].number, 245 | // How many times the hardware button should issue a pressed event before a barcode is read to detect a longpress 246 | onScanButtonLongPressed: _propTypes["default"].func, 247 | // Callback after detection of a successfull scan while the scan button was pressed and held down 248 | stopPropagation: _propTypes["default"].bool, 249 | // Stop immediate propagation on keypress event 250 | preventDefault: _propTypes["default"].bool, 251 | // Prevent default action on keypress event 252 | testCode: _propTypes["default"].string // Test string for simulating 253 | 254 | }; 255 | BarcodeScanner.defaultProps = { 256 | timeBeforeScanTest: 100, 257 | avgTimeByChar: 30, 258 | minLength: 6, 259 | endChar: [9, 13], 260 | startChar: [], 261 | scanButtonLongPressThreshold: 3, 262 | stopPropagation: false, 263 | preventDefault: false 264 | }; 265 | var _default = BarcodeScanner; 266 | exports["default"] = _default; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-barcode-reader", 3 | "version": "0.0.2", 4 | "description": "A react component for reading barcodes and Qr codes from keybord input devices.", 5 | "main": "./lib/index.js", 6 | "exports": { 7 | ".": "./lib/index.js" 8 | }, 9 | "module": "commonjs", 10 | "scripts": { 11 | "storybook": "start-storybook -p 9001", 12 | "storybook:export": "build-storybook -c .storybook -o ./docs", 13 | "build": "gulp", 14 | "build:watch": "gulp watch", 15 | "lint": "eslint -c ./.eslintrc.json ./src" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/kybarg/react-barcode-reader.git" 20 | }, 21 | "keywords": [ 22 | "react", 23 | "qr", 24 | "qrcode", 25 | "qrreader", 26 | "qrscanner", 27 | "barcode", 28 | "barcode-reader" 29 | ], 30 | "author": "Ruslan Kyba ", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/kybarg/react-barcode-reader/issues" 34 | }, 35 | "homepage": "https://github.com/kybarg/react-barcode-reader#readme", 36 | "devDependencies": { 37 | "@babel/cli": "^7.8.4", 38 | "@babel/core": "^7.9.0", 39 | "@babel/plugin-proposal-class-properties": "^7.8.3", 40 | "@babel/plugin-proposal-decorators": "^7.8.3", 41 | "@babel/plugin-proposal-do-expressions": "^7.8.3", 42 | "@babel/plugin-proposal-export-default-from": "^7.8.3", 43 | "@babel/plugin-proposal-export-namespace-from": "^7.8.3", 44 | "@babel/plugin-proposal-function-bind": "^7.8.3", 45 | "@babel/plugin-proposal-function-sent": "^7.8.3", 46 | "@babel/plugin-proposal-json-strings": "^7.8.3", 47 | "@babel/plugin-proposal-logical-assignment-operators": "^7.8.3", 48 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", 49 | "@babel/plugin-proposal-numeric-separator": "^7.8.3", 50 | "@babel/plugin-proposal-optional-chaining": "^7.9.0", 51 | "@babel/plugin-proposal-pipeline-operator": "^7.8.3", 52 | "@babel/plugin-proposal-throw-expressions": "^7.8.3", 53 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 54 | "@babel/plugin-syntax-import-meta": "^7.8.3", 55 | "@babel/plugin-transform-modules-commonjs": "^7.9.0", 56 | "@babel/preset-env": "^7.9.0", 57 | "@babel/preset-react": "^7.9.4", 58 | "@storybook/addon-actions": "^5.3.18", 59 | "@storybook/cli": "^5.3.18", 60 | "@storybook/react": "^5.3.18", 61 | "babel-core": "^7.0.0-bridge.0", 62 | "babel-eslint": "^10.1.0", 63 | "del": "^5.1.0", 64 | "eslint": "^6.8.0", 65 | "eslint-plugin-react": "^7.19.0", 66 | "gulp": "^4.0.2", 67 | "gulp-babel": "^8.0.0", 68 | "gulp-butternut": "^1.0.0", 69 | "gulp-concat": "^2.6.1", 70 | "gulp-inline-str": "^0.1.0" 71 | }, 72 | "dependencies": { 73 | "babel-loader": "^8.1.0", 74 | "prop-types": "^15.7.2", 75 | "react": "^16.13.1", 76 | "react-dom": "^16.13.1" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | function isContentEditable(element) { 5 | if (typeof element.getAttribute !== 'function') { 6 | return false 7 | } 8 | 9 | return !!element.getAttribute('contenteditable') 10 | } 11 | 12 | function isInput(element) { 13 | if (!element) { 14 | return false 15 | } 16 | 17 | const { tagName } = element 18 | const editable = isContentEditable(element) 19 | 20 | return tagName === 'INPUT' || tagName === 'TEXTAREA' || editable 21 | } 22 | 23 | function inIframe() { 24 | try { 25 | return window.self !== window.top 26 | } catch (e) { 27 | return true 28 | } 29 | } 30 | 31 | class BarcodeScanner extends React.Component { 32 | constructor(props) { 33 | super(props) 34 | 35 | this.firstCharTime = 0 36 | this.lastCharTime = 0 37 | this.stringWriting = '' 38 | this.callIsScanner = false 39 | this.testTimer = false 40 | this.scanButtonCounter = 0 41 | } 42 | 43 | componentDidMount() { 44 | if (inIframe) window.parent.document.addEventListener('keypress', this.handleKeyPress) 45 | window.document.addEventListener('keypress', this.handleKeyPress) 46 | } 47 | 48 | componentWillUnmount() { 49 | if (inIframe) window.parent.document.removeEventListener('keypress', this.handleKeyPress) 50 | window.document.removeEventListener('keypress', this.handleKeyPress) 51 | } 52 | 53 | initScannerDetection = () => { 54 | this.firstCharTime = 0 55 | this.stringWriting = '' 56 | this.scanButtonCounter = 0 57 | } 58 | 59 | scannerDetectionTest = (s) => { 60 | const { 61 | minLength, avgTimeByChar, onScanButtonLongPressed, scanButtonLongPressThreshold, onScan, onError, 62 | } = this.props 63 | // If string is given, test it 64 | if (s) { 65 | this.firstCharTime = 0 66 | this.lastCharTime = 0 67 | this.stringWriting = s 68 | } 69 | 70 | if (!this.scanButtonCounter) { 71 | this.scanButtonCounter = 1 72 | } 73 | 74 | // If all condition are good (length, time...), call the callback and re-initialize the plugin for next scanning 75 | // Else, just re-initialize 76 | if (this.stringWriting.length >= minLength && this.lastCharTime - this.firstCharTime < this.stringWriting.length * avgTimeByChar) { 77 | if (onScanButtonLongPressed && this.scanButtonCounter > scanButtonLongPressThreshold) onScanButtonLongPressed(this.stringWriting, this.scanButtonCounter) 78 | else if (onScan) onScan(this.stringWriting, this.scanButtonCounter) 79 | 80 | this.initScannerDetection() 81 | return true 82 | } 83 | 84 | let errorMsg = '' 85 | if (this.stringWriting.length < minLength) { 86 | errorMsg = `String length should be greater or equal ${minLength}` 87 | } else { 88 | if (this.lastCharTime - this.firstCharTime > this.stringWriting.length * avgTimeByChar) { 89 | errorMsg = `Average key character time should be less or equal ${avgTimeByChar}ms` 90 | } 91 | } 92 | 93 | if (onError) onError(this.stringWriting, errorMsg) 94 | this.initScannerDetection() 95 | return false 96 | } 97 | 98 | handleKeyPress = (e) => { 99 | const { 100 | onKeyDetect, onReceive, scanButtonKeyCode, stopPropagation, preventDefault, endChar, startChar, timeBeforeScanTest, 101 | } = this.props 102 | 103 | const { target } = e 104 | 105 | if (target instanceof window.HTMLElement && isInput(target)) { 106 | return 107 | } 108 | 109 | // If it's just the button of the scanner, ignore it and wait for the real input 110 | if (scanButtonKeyCode && e.which === scanButtonKeyCode) { 111 | this.scanButtonCounter += 1 112 | // Cancel default 113 | e.preventDefault() 114 | e.stopImmediatePropagation() 115 | } 116 | // Fire keyDetect event in any case! 117 | if (onKeyDetect) onKeyDetect(e) 118 | 119 | if (stopPropagation) e.stopImmediatePropagation() 120 | if (preventDefault) e.preventDefault() 121 | 122 | if (this.firstCharTime && endChar.indexOf(e.which) !== -1) { 123 | e.preventDefault() 124 | e.stopImmediatePropagation() 125 | this.callIsScanner = true 126 | } else if (!this.firstCharTime && startChar.indexOf(e.which) !== -1) { 127 | e.preventDefault() 128 | e.stopImmediatePropagation() 129 | this.callIsScanner = false 130 | } else { 131 | if (typeof (e.which) !== 'undefined') { 132 | this.stringWriting += String.fromCharCode(e.which) 133 | } 134 | this.callIsScanner = false 135 | } 136 | 137 | if (!this.firstCharTime) { 138 | this.firstCharTime = Date.now() 139 | } 140 | this.lastCharTime = Date.now() 141 | 142 | if (this.testTimer) clearTimeout(this.testTimer) 143 | if (this.callIsScanner) { 144 | this.scannerDetectionTest() 145 | this.testTimer = false 146 | } else { 147 | this.testTimer = setTimeout(this.scannerDetectionTest, timeBeforeScanTest) 148 | } 149 | 150 | if (onReceive) onReceive(e) 151 | } 152 | 153 | render() { 154 | if (this.props.testCode) this.scannerDetectionTest(this.props.testCode) 155 | return null 156 | } 157 | } 158 | 159 | 160 | BarcodeScanner.propTypes = { 161 | onScan: PropTypes.func, // Callback after detection of a successfull scanning (scanned string in parameter) 162 | onError: PropTypes.func, // Callback after detection of a unsuccessfull scanning (scanned string in parameter) 163 | onReceive: PropTypes.func, // Callback after receiving and processing a char (scanned char in parameter) 164 | onKeyDetect: PropTypes.func, // Callback after detecting a keyDown (key char in parameter) - in contrast to onReceive, this fires for non-character keys like tab, arrows, etc. too! 165 | timeBeforeScanTest: PropTypes.number, // Wait duration (ms) after keypress event to check if scanning is finished 166 | avgTimeByChar: PropTypes.number, // Average time (ms) between 2 chars. Used to do difference between keyboard typing and scanning 167 | minLength: PropTypes.number, // Minimum length for a scanning 168 | endChar: PropTypes.arrayOf(PropTypes.number), // Chars to remove and means end of scanning 169 | startChar: PropTypes.arrayOf(PropTypes.number), // Chars to remove and means start of scanning 170 | scanButtonKeyCode: PropTypes.number, // Key code of the scanner hardware button (if the scanner button a acts as a key itself) 171 | scanButtonLongPressThreshold: PropTypes.number, // How many times the hardware button should issue a pressed event before a barcode is read to detect a longpress 172 | onScanButtonLongPressed: PropTypes.func, // Callback after detection of a successfull scan while the scan button was pressed and held down 173 | stopPropagation: PropTypes.bool, // Stop immediate propagation on keypress event 174 | preventDefault: PropTypes.bool, // Prevent default action on keypress event 175 | testCode: PropTypes.string, // Test string for simulating 176 | } 177 | 178 | BarcodeScanner.defaultProps = { 179 | timeBeforeScanTest: 100, 180 | avgTimeByChar: 30, 181 | minLength: 6, 182 | endChar: [9, 13], 183 | startChar: [], 184 | scanButtonLongPressThreshold: 3, 185 | stopPropagation: false, 186 | preventDefault: false, 187 | } 188 | 189 | export default BarcodeScanner -------------------------------------------------------------------------------- /story.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { storiesOf } from '@storybook/react' 3 | import { action } from '@storybook/addon-actions' 4 | import Reader from './lib' 5 | 6 | class Wrapper extends Component { 7 | render() { 8 | return ( 9 |
10 | 14 |
15 | ) 16 | } 17 | } 18 | 19 | storiesOf('Barcode scanner', module) 20 | .add('Default', () => ) 21 | --------------------------------------------------------------------------------