├── .changeset
├── README.md
└── config.json
├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── LICENSE
├── README.md
├── eslint.config.js
├── index.html
├── package-lock.json
├── package.json
├── packages
├── docs
│ ├── CHANGELOG.md
│ ├── middleware.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _meta.en-US.json
│ │ ├── _meta.zh-CN.json
│ │ ├── docs
│ │ │ ├── _meta.en-US.json
│ │ │ ├── _meta.zh-CN.json
│ │ │ ├── api.en-US.mdx
│ │ │ ├── api.zh-CN.mdx
│ │ │ ├── demo.en-US.mdx
│ │ │ ├── demo.zh-CN.mdx
│ │ │ ├── guide.en-US.mdx
│ │ │ ├── guide.zh-CN.mdx
│ │ │ ├── install.en-US.mdx
│ │ │ └── install.zh-CN.mdx
│ │ ├── index.en-US.mdx
│ │ └── index.zh-CN.mdx
│ ├── public
│ │ ├── apple-touch-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ └── logo.png
│ └── theme.config.jsx
└── react-barcode-scanner
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── components
│ │ ├── barcode-scanner.tsx
│ │ └── index.ts
│ ├── helper
│ │ ├── shimGetUserMedia.ts
│ │ └── utils.ts
│ ├── hooks
│ │ ├── index.ts
│ │ ├── use-atom.tsx
│ │ ├── use-camera.tsx
│ │ ├── use-scanning.tsx
│ │ ├── use-stream-state.tsx
│ │ └── use-torch.tsx
│ ├── index.ts
│ ├── polyfill.ts
│ ├── shims
│ │ ├── media-stream.d.ts
│ │ └── webrtc-adapter.d.ts
│ └── types.ts
│ └── tsconfig.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── public
└── logo.png
├── renovate.json
└── tsconfig.json
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "restricted",
8 | "baseBranch": "master",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
12 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Package
2 | on:
3 | push:
4 | branches:
5 | - master
6 | paths:
7 | - '.changeset/**'
8 | env:
9 | CI: true
10 | PNPM_CACHE_FOLDER: .pnpm-store
11 | jobs:
12 | release:
13 | timeout-minutes: 15
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Checkout code repository
17 | uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0
20 | - name: Setup node.js
21 | uses: actions/setup-node@v4
22 | with:
23 | node-version: 20
24 | - name: Install pnpm
25 | run: npm i pnpm@latest -g
26 | - name: Setup pnpm config
27 | run: pnpm config set store-dir $PNPM_CACHE_FOLDER
28 | - name: Install dependencies
29 | run: pnpm install --no-frozen-lockfile
30 | - name: Create and publish versions
31 | uses: changesets/action@v1
32 | with:
33 | version: pnpm ci:version
34 | commit: "chore: update versions"
35 | title: "chore: update versions"
36 | publish: pnpm ci:publish
37 | env:
38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | # node
11 | node_modules
12 |
13 | # prod packages
14 | lib
15 | esm
16 |
17 | *.local
18 |
19 | # Editor directories and files
20 | .vscode/*
21 | !.vscode/extensions.json
22 | .idea
23 | .DS_Store
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 |
30 | # changeset release
31 | .pnpm-store
32 | .npmrc
33 |
34 | # next
35 | .next
36 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | npx --no-install commitlint --edit "$1"
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npx tsc --noEmit && npx lint-staged
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022-present, Ted Lin
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 |
15 |
16 | ## Introduction
17 | A lightweight barcode scanner base on `Barcode Detection API`, and polyfill use `zbar.wasm`
18 |
19 | ## Usage
20 | ```tsx
21 | import { BarcodeScanner } from 'react-barcode-scanner'
22 | import "react-barcode-scanner/polyfill"
23 |
24 | export default () => {
25 | return
26 | }
27 | ```
28 |
29 | ## Detail
30 | [Documentation](https://reactbarcodescanner.vercel.app/)
31 |
32 | ## License
33 | MIT
34 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { ted } from 'eslint-config-ted'
2 |
3 | export default ted(
4 | [
5 | {
6 | ignores: [
7 | '**/esm/',
8 | '**/lib/',
9 | '**/.next/'
10 | ]
11 | }
12 | ],
13 | {
14 | react: true,
15 | vue: false
16 | }
17 | )
18 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-barcode-scanner/monorepo",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "Monorepo for React Barcode Scanner",
6 | "type": "module",
7 | "keywords": [],
8 | "license": "MIT",
9 | "homepage": "https://reactbarcodescanner.vercel.app/",
10 | "author": "Ted Lin (https://github.com/preflower)",
11 | "main": "index.js",
12 | "scripts": {
13 | "prepare": "husky",
14 | "preinstall": "npx only-allow pnpm",
15 | "docs:build": "pnpm --filter docs build",
16 | "docs:dev": "pnpm --filter docs dev",
17 | "build": "pnpm --filter react-barcode-scanner run build",
18 | "ci:version": "pnpm changeset version && pnpm install --lockfile-only",
19 | "ci:publish": "pnpm build && pnpm changeset publish"
20 | },
21 | "commitlint": {
22 | "extends": [
23 | "@commitlint/config-conventional"
24 | ]
25 | },
26 | "dependencies": {
27 | "@preflower/utils": "^1.1.1"
28 | },
29 | "devDependencies": {
30 | "@changesets/cli": "^2.29.4",
31 | "@commitlint/cli": "^19.8.1",
32 | "@commitlint/config-conventional": "^19.8.1",
33 | "eslint": "^9.22.0",
34 | "eslint-config-ted": "^4.0.9",
35 | "husky": "^9.1.7",
36 | "lint-staged": "^15.5.2",
37 | "typescript": "^5.8.2"
38 | },
39 | "lint-staged": {
40 | "*.{js,ts,tsx}": [
41 | "eslint --fix"
42 | ]
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/docs/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # docs
2 |
3 | ## 1.0.8
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies [4e87938]
8 | - react-barcode-scanner@4.0.0
9 |
10 | ## 1.0.7
11 |
12 | ### Patch Changes
13 |
14 | - Updated dependencies [100f173]
15 | - react-barcode-scanner@3.2.1
16 |
17 | ## 1.0.6
18 |
19 | ### Patch Changes
20 |
21 | - Updated dependencies [59cc0f4]
22 | - react-barcode-scanner@3.2.0
23 |
24 | ## 1.0.5
25 |
26 | ### Patch Changes
27 |
28 | - Updated dependencies [bb1a4a1]
29 | - react-barcode-scanner@3.1.0
30 |
31 | ## 1.0.4
32 |
33 | ### Patch Changes
34 |
35 | - Updated dependencies [f8ce96e]
36 | - react-barcode-scanner@3.0.1
37 |
38 | ## 1.0.3
39 |
40 | ### Patch Changes
41 |
42 | - Updated dependencies [10b4a95]
43 | - react-barcode-scanner@3.0.0
44 |
45 | ## 1.0.2
46 |
47 | ### Patch Changes
48 |
49 | - Updated dependencies [fc69f41]
50 | - react-barcode-scanner@2.1.0
51 |
52 | ## 1.0.1
53 |
54 | ### Patch Changes
55 |
56 | - Updated dependencies [318a31f]
57 | - react-barcode-scanner@2.0.2
58 |
--------------------------------------------------------------------------------
/packages/docs/middleware.js:
--------------------------------------------------------------------------------
1 | export { locales as middleware } from 'nextra/locales'
2 |
--------------------------------------------------------------------------------
/packages/docs/next.config.js:
--------------------------------------------------------------------------------
1 | const withNextra = require('nextra')({
2 | theme: 'nextra-theme-docs',
3 | themeConfig: './theme.config.jsx'
4 | })
5 |
6 | module.exports = withNextra({
7 | i18n: {
8 | locales: ['en-US', 'zh-CN'],
9 | defaultLocale: 'en-US'
10 | }
11 | })
12 |
13 | // If you have other Next.js configurations, you can pass them as the parameter:
14 | // module.exports = withNextra({ /* other next.js config */ })
15 |
--------------------------------------------------------------------------------
/packages/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "version": "1.0.8",
4 | "private": true,
5 | "description": "",
6 | "keywords": [],
7 | "license": "ISC",
8 | "author": "",
9 | "main": "index.js",
10 | "scripts": {
11 | "dev": "pnpm --filter react-barcode-scanner run build && next",
12 | "build": "pnpm --filter react-barcode-scanner run build && next build",
13 | "start": "next start"
14 | },
15 | "dependencies": {
16 | "lucide-react": "^0.473.0",
17 | "next": "^14.2.29",
18 | "nextra": "^2.13.4",
19 | "nextra-theme-docs": "^2.13.4",
20 | "react": "^18.3.1",
21 | "react-barcode-scanner": "workspace:*",
22 | "react-dom": "^18.3.1"
23 | },
24 | "devDependencies": {
25 | "@types/react": "^18.3.22"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/docs/pages/_meta.en-US.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": {
3 | "title": "Introduction",
4 | "type": "page",
5 | "display": "hidden"
6 | },
7 | "docs": {
8 | "title": "Docs",
9 | "type": "page"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/docs/pages/_meta.zh-CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": {
3 | "title": "简介",
4 | "type": "page",
5 | "display": "hidden"
6 | },
7 | "docs": {
8 | "title": "文档",
9 | "type": "page"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/docs/pages/docs/_meta.en-US.json:
--------------------------------------------------------------------------------
1 | {
2 | "install": "Install",
3 | "guide": "Getting Start",
4 | "api": "API",
5 | "demo": "Demo"
6 | }
7 |
--------------------------------------------------------------------------------
/packages/docs/pages/docs/_meta.zh-CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "install": "安装",
3 | "guide": "快速入门",
4 | "api": "API",
5 | "demo": "Demo"
6 | }
7 |
--------------------------------------------------------------------------------
/packages/docs/pages/docs/api.en-US.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: API
3 | ---
4 |
5 | ## BarcodeScanner
6 | | Name | Type | Default Value | Description |
7 | |----------- |----------------------------------- |--------------------------------------- |----------------------------------------------------------- |
8 | | options | [ScanOptions](#scanoptions) | [DEFAULT_OPTIONS](#default_options) | / |
9 | | onCapture | (barcodes: DetectedBarcode[]) => any | / | Triggered when the specified QR code is scanned |
10 | | trackConstraints | MediaTrackConstraints | [DEFAULT_CONSTRAINTS](#default_constraints) | Based on the [MediaTrackConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints) standard |
11 | | paused | Boolean | false | Set the camera to pause scanning |
12 |
13 | ## useCamera
14 | | Name | Type | Default Value | Description |
15 | |-------------------- |----------------------------- |--------------------------------------------- |------------------------------------------------------------------------------------------------------------------------ |
16 | | ref | `RefObject` | Required* | HTMLVideoElement instance |
17 | | trackConstraints | MediaTrackConstraints | [DEFAULT_CONSTRAINTS](#default_constraints) | Based on the [MediaTrackConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints) standard |
18 |
19 | ### DEFAULT_CONSTRAINTS
20 | ```js
21 | {
22 | width: { min: 640, ideal: 1280 },
23 | height: { min: 480, ideal: 720 },
24 | facingMode: {
25 | ideal: 'environment'
26 | },
27 | advanced: [
28 | { width: 1920, height: 1280 },
29 | { aspectRatio: 1.333 }
30 | ]
31 | }
32 | ```
33 |
34 | ## useScanning
35 |
36 | | Name | Type | Default Value | Description |
37 | |-------------------- |----------------------------- |------------------------------------- |----------------------------------------------------- |
38 | | ref | `RefObject` | Required* | HTMLVideoElement instance |
39 | | options | [ScanOptions](#scanoptions) | [DEFAULT_OPTIONS](#default_options) | delay: scan interval
formats: barcode format, [support formats](./guide#barcode-format) |
40 |
41 | ### DEFAULT_OPTIONS
42 | ```js
43 | {
44 | delay: 1000,
45 | formats: ['qr_code']
46 | }
47 | ```
48 |
49 | ## useTorch
50 |
51 | | Name | Type | Default Value | Description |
52 | |------ |--------- |--------------- |-------------------- |
53 | | defaultTorchOn | Boolean | false | Enabled by default |
54 |
55 | ## useStreamState
56 | Manage `stream` obtained from `useCamera`
57 |
58 | ```ts
59 | function useStreamState (): [MediaStream | undefined, (newState: MediaStream) => void]
60 | ```
61 |
62 | ## Types
63 |
64 | ### ScanOptions
65 | ```ts
66 | interface ScanOptions {
67 | delay?: number
68 | formats?: string[]
69 | }
70 | ```
71 |
--------------------------------------------------------------------------------
/packages/docs/pages/docs/api.zh-CN.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: API
3 | order: 2
4 | toc: menu
5 | nav:
6 | order: 2
7 | ---
8 |
9 | ## BarcodeScanner
10 | | Name | Type | Default Value | Description |
11 | |----------- |----------------------------------- |--------------------------------------- |----------------------------- |
12 | | options | [ScanOptions](#scanoptions) | [DEFAULT_OPTIONS](#default_options) | / |
13 | | onCapture | (barcodes: DetectedBarcode[]) => any | / | 当检测到指定类型的码时触发 |
14 | | trackConstraints | MediaTrackConstraints | [DEFAULT_CONSTRAINTS](#default_constraints) | 基于[MediaTrackConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints)标准 |
15 | | paused | Boolean | false | 设置相机暂停扫描 |
16 |
17 | ## useCamera
18 | | Name | Type | Default Value | Description |
19 | |-------------------- |----------------------------- |--------------------------------------------- |-------------------------------------------------------------------------------------------------------- |
20 | | ref | `RefObject` | Required* | HTMLVideoElement实例 |
21 | | trackConstraints | MediaTrackConstraints | [DEFAULT_CONSTRAINTS](#default_constraints) | 基于[MediaTrackConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints)标准 |
22 |
23 | ### DEFAULT_CONSTRAINTS
24 | ```js
25 | {
26 | width: { min: 640, ideal: 1280 },
27 | height: { min: 480, ideal: 720 },
28 | facingMode: {
29 | ideal: 'environment'
30 | },
31 | advanced: [
32 | { width: 1920, height: 1280 },
33 | { aspectRatio: 1.333 }
34 | ]
35 | }
36 | ```
37 |
38 | ## useScanning
39 |
40 | | Name | Type | Default Value | Description |
41 | |-------------------- |----------------------------- |------------------------------------- |---------------------------------------------------- |
42 | | ref | `RefObject` | Required* | HTMLVideoElement实例 |
43 | | options | [ScanOptions](#scanoptions) | [DEFAULT_OPTIONS](#default_options) | delay: 扫描间隔
formats: 扫描的条码格式, [支持格式](./guide#barcode-format) |
44 |
45 | ### DEFAULT_OPTIONS
46 | ```js
47 | {
48 | delay: 1000,
49 | formats: ['qr_code']
50 | }
51 | ```
52 |
53 | ## useTorch
54 |
55 | | Name | Type | Default Value | Description |
56 | |------ |--------- |--------------- |-------------------- |
57 | | defaultTorchOn | Boolean | false | 是否默认启用闪光灯 |
58 |
59 | ## useStreamState
60 | 管理通过`useCamera`获取到的`stream`
61 |
62 | ```ts
63 | function useStreamState (): [MediaStream | undefined, (newState: MediaStream) => void]
64 | ```
65 |
66 | ## Types
67 |
68 | ### ScanOptions
69 | ```ts
70 | interface ScanOptions {
71 | delay?: number
72 | formats?: string[]
73 | }
74 | ```
75 |
--------------------------------------------------------------------------------
/packages/docs/pages/docs/demo.en-US.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Demo
3 | ---
4 |
5 | ## Demo
6 |
7 | 'use client'
8 |
9 | import React, { useState, useEffect, useMemo, useCallback } from "react"
10 | import { BarcodeScanner, useTorch, useStreamState } from "react-barcode-scanner"
11 | import "react-barcode-scanner/polyfill"
12 |
13 | export const BarcodeFormatSelector = ({ selected, onSelectFormats }) => {
14 | const formats = [
15 | 'code_128',
16 | 'code_39',
17 | 'code_93',
18 | 'codabar',
19 | 'ean_13',
20 | 'ean_8',
21 | 'itf',
22 | 'qr_code',
23 | 'upc_a',
24 | 'upc_e'
25 | ]
26 | const [selectedFormats, setSelectedFormats] = useState(selected)
27 |
28 | const toggleFormat = (format) => {
29 | setSelectedFormats(prevSelected => {
30 | const newSelected = prevSelected.includes(format)
31 | ? prevSelected.filter(f => f !== format)
32 | : [...prevSelected, format]
33 | onSelectFormats(newSelected)
34 | return newSelected
35 | })
36 | }
37 |
38 | return (
39 |
40 | {formats.map(format => (
41 |
49 | ))}
50 |
51 | )
52 | }
53 |
54 | export default () => {
55 | const { isTorchSupported, setIsTorchOn } = useTorch()
56 | const [formats, setFormats] = useState(['qr_code'])
57 | const [delay, setDelay] = useState('500')
58 | const [paused, setPaused] = useState(false)
59 |
60 | const options = useMemo(() => ({ delay: Number(delay), formats }), [delay, formats])
61 |
62 | const onDelayChange = (e) => {
63 | const value = e.target.value.replace(/[^\d]/g,'')
64 | setDelay(value)
65 | }
66 |
67 | const onCapture = useCallback((barcodes) => {
68 | if (barcodes) {
69 | window.alert(barcodes.map(barcode => barcode.rawValue).join('\n'))
70 | }
71 | }, [])
72 |
73 | const onPause = () => {
74 | setPaused(!paused)
75 | }
76 |
77 | return (
78 | <>
79 | Props
80 |
81 |
options.delay:
82 |
83 |
84 |
85 |
options.formats:
86 |
87 |
88 |
options.paused:
89 |
90 |
91 | Result
92 |
93 |
94 | {isTorchSupported ? (
95 |
96 | ) : null}
97 |
98 | >
99 | )
100 | }
101 |
--------------------------------------------------------------------------------
/packages/docs/pages/docs/demo.zh-CN.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Demo
3 | ---
4 |
5 | ## Demo
6 |
7 | 'use client'
8 |
9 | import React, { useState, useEffect, useMemo, useCallback } from "react"
10 | import { BarcodeScanner, useTorch, useStreamState } from "react-barcode-scanner"
11 | import "react-barcode-scanner/polyfill"
12 |
13 | export const BarcodeFormatSelector = ({ selected, onSelectFormats }) => {
14 | const formats = [
15 | 'code_128',
16 | 'code_39',
17 | 'code_93',
18 | 'codabar',
19 | 'ean_13',
20 | 'ean_8',
21 | 'itf',
22 | 'qr_code',
23 | 'upc_a',
24 | 'upc_e'
25 | ]
26 | const [selectedFormats, setSelectedFormats] = useState(selected)
27 |
28 | const toggleFormat = (format) => {
29 | setSelectedFormats(prevSelected => {
30 | const newSelected = prevSelected.includes(format)
31 | ? prevSelected.filter(f => f !== format)
32 | : [...prevSelected, format]
33 | onSelectFormats(newSelected)
34 | return newSelected
35 | })
36 | }
37 |
38 | return (
39 |
40 | {formats.map(format => (
41 |
49 | ))}
50 |
51 | )
52 | }
53 |
54 | export default () => {
55 | const { isTorchSupported, setIsTorchOn } = useTorch()
56 | const [formats, setFormats] = useState(['qr_code'])
57 | const [delay, setDelay] = useState('500')
58 | const [paused, setPaused] = useState(false)
59 |
60 | const options = useMemo(() => ({ delay: Number(delay), formats }), [delay, formats])
61 |
62 | const onDelayChange = (e) => {
63 | const value = e.target.value.replace(/[^\d]/g,'')
64 | setDelay(value)
65 | }
66 |
67 | const onCapture = useCallback((barcodes) => {
68 | if (barcodes) {
69 | window.alert(barcodes.map(barcode => barcode.rawValue).join('\n'))
70 | }
71 | }, [])
72 |
73 | const onPause = () => {
74 | setPaused(!paused)
75 | }
76 |
77 | return (
78 | <>
79 | Props
80 |
81 |
options.delay:
82 |
83 |
84 |
85 |
options.formats:
86 |
87 |
88 |
89 |
options.paused:
90 |
91 |
92 | Result
93 |
94 |
95 | {isTorchSupported ? (
96 |
97 | ) : null}
98 |
99 | >
100 | )
101 | }
102 |
--------------------------------------------------------------------------------
/packages/docs/pages/docs/guide.en-US.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Getting Start
3 | ---
4 | import { Callout } from 'nextra/components'
5 |
6 | ## Quick Start
7 |
8 | ```jsx
9 | import React from 'react'
10 | import { BarcodeScanner } from 'react-barcode-scanner'
11 | import 'react-barcode-scanner/polyfill'
12 |
13 | export default () => {
14 | return (
15 |
16 | )
17 | }
18 | ```
19 |
20 | ### Barcode format
21 | `BarcodeScanner` support `qr_code` format by default, if needed to support other formats, use config `formats` option:
22 |
23 | ```tsx
24 |
25 | ```
26 |
27 |
28 | Project based on `Barcode Detection API`, Support [Barcode Formats](https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API#supported_barcode_formats) except `aztec`, `data_matrix` and `pdf417` (zbar polyfill not supported)
29 |
30 |
31 | ### Scanning delay
32 | For performance consider `BarcodeScanner` set default `1000ms` to scan barcode, if need more sensitive, use config `delay` option:
33 |
34 | ```tsx
35 |
36 | ```
37 |
38 | ### Torch
39 | To consider more scenarios, `BarcodeScanner` abstract `useTorch` hook for ease of use
40 |
41 | ```jsx
42 | import React from 'react'
43 | import { BarcodeScanner, useTorch } from 'react-barcode-scanner'
44 |
45 | export default () => {
46 | const { isTorchSupported, isTorchOn, setIsTorchOn } = useTorch()
47 |
48 | const onTorchSwitch = () => {
49 | setIsTorchOn(!isTorchOn)
50 | }
51 |
52 | return (
53 |
54 |
55 | {isTorchSupported
56 | ?
57 | : null}
58 |
59 | )
60 | }
61 | ```
62 |
63 | > Currently, torch only work in some browser, see detail: [MediaTrackConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#torch)
64 |
65 | ## With framework
66 |
67 | ### Next.js
68 |
69 | `React Barcode Scanner` is based on browser `Barcode Detection API` library, it's only suitable for browser environment; so if want to use it in `Next.js`, user need use `next/dynamic` to import library
70 |
71 | ```jsx
72 | import dynamic from 'next/dynamic'
73 |
74 | const BarcodeScanner = dynamic(() => {
75 | import('react-barcode-scanner/polyfill')
76 | return import('react-barcode-scanner').then(mod => mod.BarcodeScanner)
77 | }, { ssr: false })
78 | ```
79 |
--------------------------------------------------------------------------------
/packages/docs/pages/docs/guide.zh-CN.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 快速入门
3 | ---
4 | import { Callout } from 'nextra/components'
5 |
6 |
7 | ## 快速开始
8 |
9 | ```jsx
10 | import React from 'react'
11 | import { BarcodeScanner } from 'react-barcode-scanner'
12 | import 'react-barcode-scanner/polyfill'
13 |
14 | export default () => {
15 | return (
16 |
17 | )
18 | }
19 | ```
20 |
21 | ### 条形码格式
22 | `BarcodeScanner` 默认配置支持 `qr_code`, 若需要支持其他的条码格式, 需要配置`formats`字段:
23 |
24 | ```tsx
25 |
26 | ```
27 |
28 |
29 | 项目基于`Barcode Detection API`, 支持除`aztec`, `data_matrix`和`pdf417`(zbar polyfill目前未支持)外的[Barcode Formats](https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API#supported_barcode_formats)
30 |
31 |
32 | ### 扫码感应延迟
33 | `BarcodeScanner`为了性能考虑, 默认设置成了`1000ms`识别一次, 若需要更灵敏的扫码感应, 需要配置`delay`字段:
34 |
35 | ```tsx
36 |
37 | ```
38 |
39 | ### 闪光灯
40 | 为考虑更多场景, `BarcodeScanner` 抽象出了 `useTorch` Hook方便用户使用
41 |
42 | ```jsx
43 | import React from 'react'
44 | import { BarcodeScanner, useTorch } from 'react-barcode-scanner'
45 |
46 | export default () => {
47 | const { isTorchSupported, isTorchOn, setIsTorchOn } = useTorch()
48 |
49 | const onTorchSwitch = () => {
50 | setIsTorchOn(!isTorchOn)
51 | }
52 |
53 | return (
54 |
55 |
56 | {isTorchSupported
57 | ?
58 | : null}
59 |
60 | )
61 | }
62 | ```
63 |
64 | > 闪光灯目前只在部分浏览器可用, 具体参考: [MediaTrackConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#torch)
65 |
66 | ## 与框架使用
67 |
68 | ### Next.js
69 |
70 | `React Barcode Scanner` 是一款基于浏览器功能的扫码库, 只适用于浏览器环境, 若需要在`Next.js`项目中应用此库, 需要以`next/dynamic`的方式导入
71 |
72 | ```jsx
73 | import dynamic from 'next/dynamic'
74 |
75 | const BarcodeScanner = dynamic(() => {
76 | import('react-barcode-scanner/polyfill')
77 | return import('react-barcode-scanner').then(mod => mod.BarcodeScanner)
78 | }, { ssr: false })
79 | ```
80 |
--------------------------------------------------------------------------------
/packages/docs/pages/docs/install.en-US.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Install
3 | ---
4 |
5 | ## Install
6 |
7 | ### pnpm
8 | ```shell
9 | pnpm install react-barcode-scanner
10 | ```
11 |
12 | ### yarn
13 | ```shell
14 | yarn install react-barcode-scanner
15 | ```
16 |
17 | ### npm
18 | ```shell
19 | npm install react-barcode-scanner
20 | ```
21 |
22 | ## Scaffold
23 |
24 | ### Vite
25 | We recommend using the latest Vite version; if developer's Vite version is lower than 5, please follow this configuration
26 |
27 | ```js
28 | export default defineConfig({
29 | optimizeDeps: {
30 | exclude: ['@preflower/barcode-detector-polyfill']
31 | }
32 | })
33 | ```
34 |
35 | - [Related issue](https://github.com/vitejs/vite/issues/13530)
36 |
37 | > 🙏 Thank [@louispotok](https://github.com/preflower/react-barcode-scanner/issues/231) for help to this chapter
38 |
--------------------------------------------------------------------------------
/packages/docs/pages/docs/install.zh-CN.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 安装
3 | ---
4 |
5 | ## 安装
6 |
7 | ### pnpm
8 | ```shell
9 | pnpm install react-barcode-scanner
10 | ```
11 |
12 | ### yarn
13 | ```shell
14 | yarn install react-barcode-scanner
15 | ```
16 |
17 | ### npm
18 | ```shell
19 | npm install react-barcode-scanner
20 | ```
21 |
22 | ## 脚手架
23 |
24 | ### Vite
25 | 我们建议使用最新的Vite版本; 如果开发人员使用低于5以下的Vite版本, 请遵循以下配置
26 |
27 | ```js
28 | export default defineConfig({
29 | optimizeDeps: {
30 | exclude: ['@preflower/barcode-detector-polyfill']
31 | }
32 | })
33 | ```
34 |
35 | - [相关issue](https://github.com/vitejs/vite/issues/13530)
36 |
37 | > 🙏 感谢 [@louispotok](https://github.com/preflower/react-barcode-scanner/issues/231) 对本章节的帮助
38 |
--------------------------------------------------------------------------------
/packages/docs/pages/index.en-US.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | ---
4 |
5 | ## React Barcode Scanner
6 |
7 | A lightweight barcode scanner base on Barcode Detection API, and polyfill use zbar.wasm
8 |
9 | ### Features
10 |
11 | - Base on `Barcode Detector API`
12 | - Polyfill use `zbar.wasm` library, size only ~230kb
13 | - Base on React Hooks, high customization
14 | - Written in TypeScript with predictable static types
15 |
16 | ### Feedback
17 |
18 | Please visit [GitHub](https://github.com/preflower/react-barcode-scanner) to commit issue or PR
19 |
--------------------------------------------------------------------------------
/packages/docs/pages/index.zh-CN.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 介绍
3 | ---
4 |
5 | ## React Barcode Scanner
6 |
7 | 一款基于 `Barcode Detection API` 和 `zbar.wasm` 的轻量化扫码器
8 |
9 | ### 特性
10 |
11 | - 基于浏览器`Barcode Detection API`规范
12 | - Polyfill 使用 `zbar wasm` 库,大小仅~230kb
13 | - 基于React Hooks, 提供高自定义能力
14 | - 使用 TypeScript 构建,提供完整的类型定义文件
15 |
16 | ### 反馈与共建
17 |
18 | 请访问 [GitHub](https://github.com/preflower/react-barcode-scanner/issues) 创建 issue 或 PR
19 |
--------------------------------------------------------------------------------
/packages/docs/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preflower/react-barcode-scanner/ab5cf8b0ed7d67f6a3a630a4450da4f20ddbac88/packages/docs/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/packages/docs/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preflower/react-barcode-scanner/ab5cf8b0ed7d67f6a3a630a4450da4f20ddbac88/packages/docs/public/favicon-16x16.png
--------------------------------------------------------------------------------
/packages/docs/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preflower/react-barcode-scanner/ab5cf8b0ed7d67f6a3a630a4450da4f20ddbac88/packages/docs/public/favicon-32x32.png
--------------------------------------------------------------------------------
/packages/docs/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preflower/react-barcode-scanner/ab5cf8b0ed7d67f6a3a630a4450da4f20ddbac88/packages/docs/public/favicon.ico
--------------------------------------------------------------------------------
/packages/docs/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preflower/react-barcode-scanner/ab5cf8b0ed7d67f6a3a630a4450da4f20ddbac88/packages/docs/public/logo.png
--------------------------------------------------------------------------------
/packages/docs/theme.config.jsx:
--------------------------------------------------------------------------------
1 | export default {
2 | logo: (
3 | <>
4 |
5 | React Barcode Scanner
6 | >
7 | ),
8 | project: {
9 | link: 'https://github.com/preflower/react-barcode-scanner'
10 | },
11 | useNextSeoProps () {
12 | return {
13 | titleTemplate: '%s – React Barcode Scanner'
14 | }
15 | },
16 | footer: {
17 | text: Copyright © {new Date().getFullYear()} React Barcode Scanner
18 | },
19 | head: (
20 | <>
21 |
22 |
28 |
34 | >
35 | ),
36 | darkMode: false,
37 | i18n: [
38 | { locale: 'en-US', text: 'English' },
39 | { locale: 'zh-CN', text: '简体中文' }
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # react-barcode-scanner
2 |
3 | ## 4.0.0
4 |
5 | ### Major Changes
6 |
7 | - 4e87938: Refactor useCamera, useScanning, useTorch now return objects instead of arrays and add some error handling
8 |
9 | ## 3.2.1
10 |
11 | ### Patch Changes
12 |
13 | - 100f173: fix video.play exception that triggered while the camera was still being prepared
14 |
15 | ## 3.2.0
16 |
17 | ### Minor Changes
18 |
19 | - 59cc0f4: Add paused prop support
20 |
21 | ## 3.1.0
22 |
23 | ### Minor Changes
24 |
25 | - bb1a4a1: fix open status inconsist error, replace switchTorch with setOpen for DDV idea
26 |
27 | ## 3.0.1
28 |
29 | ### Patch Changes
30 |
31 | - f8ce96e: fix: version number be occupied error
32 |
33 | ## 3.0.0
34 |
35 | ### Major Changes
36 |
37 | - 10b4a95: Update onCapture function api to support detect multiple barcodes
38 |
39 | ## 2.1.0
40 |
41 | ### Minor Changes
42 |
43 | - fc69f41: BarcodeScanner support trackConstraints props
44 |
45 | ## 2.0.2
46 |
47 | ### Patch Changes
48 |
49 | - 318a31f: Export type and fix warning error in Next.js
50 |
51 | ## 2.0.1
52 |
53 | ### Patch Changes
54 |
55 | - a5fbfe8: Fix camera not be correct stopped error
56 |
57 | ## 2.0.0
58 |
59 | ### Major Changes
60 |
61 | - a548c40: Breaking change: polyfill will not auto import anymore to support SSR, user need import it manually
62 |
63 | ## 1.0.4
64 |
65 | ### Patch Changes
66 |
67 | - d381b23: fix @undecaf/zbar-wasm@0.9.16 package error, update to latest package
68 |
69 | ## 1.0.3
70 |
71 | ### Patch Changes
72 |
73 | - 934c972: fix type not export error
74 |
75 | ## 1.0.2
76 |
77 | ### Patch Changes
78 |
79 | - 98b03c5: fix production package export abnormal error
80 |
81 | ## 1.0.1
82 |
83 | ### Patch Changes
84 |
85 | - 29a4d28: release v1.0.1
86 |
87 | ## 1.0.0
88 |
89 | ### Major Changes
90 |
91 | - e9114e8: Product available version
92 |
93 | ## 0.0.4
94 |
95 | ### Patch Changes
96 |
97 | - 64fd836: fix iOS black screen error
98 |
99 | ## 0.0.3
100 |
101 | ### Patch Changes
102 |
103 | - 798a774: fix README
104 |
105 | ## 0.0.2
106 |
107 | ### Patch Changes
108 |
109 | - 49f5c9e: fix pkg 404 error
110 |
111 | ## 0.0.1
112 |
113 | ### Patch Changes
114 |
115 | - 2e4af8aa: fix: output useStreamState
116 | - 2cccd27c: chore: publish
117 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/README.md:
--------------------------------------------------------------------------------
1 |
15 |
16 | ## Introduction
17 | A lightweight barcode scanner base on `Barcode Detection API`, and polyfill use `zbar.wasm`
18 |
19 | ## Usage
20 | ```tsx
21 | import { BarcodeScanner } from 'react-barcode-scanner'
22 | import "react-barcode-scanner/polyfill"
23 |
24 | export default () => {
25 | return
26 | }
27 | ```
28 |
29 | ## Detail
30 | [Documentation](https://reactbarcodescanner.vercel.app/)
31 |
32 | ## License
33 | MIT
34 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-barcode-scanner",
3 | "version": "4.0.0",
4 | "description": "A barcode scanner base on Barcode Detector",
5 | "keywords": [
6 | "react",
7 | "barcode detector",
8 | "barcode-detector",
9 | "barcode scanner",
10 | "barcode-scanner",
11 | "qrcode"
12 | ],
13 | "license": "MIT",
14 | "homepage": "https://reactbarcodescanner.vercel.app/",
15 | "bugs": {
16 | "url": "https://github.com/preflower/react-barcode-scanner/issues"
17 | },
18 | "repository": "https://github.com/preflower/react-barcode-scanner",
19 | "author": "Ted Lin (https://github.com/preflower)",
20 | "files": [
21 | "esm/",
22 | "lib/"
23 | ],
24 | "main": "lib/index.js",
25 | "module": "esm/index.js",
26 | "types": "lib/index.d.ts",
27 | "exports": {
28 | ".": {
29 | "require": {
30 | "types": "./lib/index.d.ts",
31 | "default": "./lib/index.js"
32 | },
33 | "import": {
34 | "types": "./esm/index.d.ts",
35 | "default": "./esm/index.js"
36 | }
37 | },
38 | "./polyfill": {
39 | "require": {
40 | "types": "./lib/polyfill.d.ts",
41 | "default": "./lib/polyfill.js"
42 | },
43 | "import": {
44 | "types": "./esm/polyfill.d.ts",
45 | "default": "./esm/polyfill.js"
46 | }
47 | }
48 | },
49 | "typings": "lib/index.d.ts",
50 | "publishConfig": {
51 | "access": "public"
52 | },
53 | "scripts": {
54 | "build:cjs": "tsc",
55 | "build:es": "tsc -m esNext --outDir esm",
56 | "build": "pnpm build:cjs && pnpm build:es"
57 | },
58 | "peerDependencies": {
59 | "react": "*",
60 | "react-dom": "*"
61 | },
62 | "dependencies": {
63 | "@preflower/barcode-detector-polyfill": "^0.9.21",
64 | "tslib": "^2.6.3",
65 | "webrtc-adapter": "^9.0.3"
66 | },
67 | "devDependencies": {
68 | "@types/react": "^18.3.22",
69 | "@types/react-dom": "^18.3.7",
70 | "react": "^18.3.1",
71 | "react-dom": "^18.3.1",
72 | "typescript": "^5.8.2"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/components/barcode-scanner.tsx:
--------------------------------------------------------------------------------
1 | import { type FunctionComponent, useEffect, useRef } from 'react'
2 | import { useCamera, useScanning, type ScanOptions } from '../hooks'
3 | import { type DetectedBarcode } from '../types'
4 |
5 | interface ScannerProps extends React.DetailedHTMLProps, HTMLVideoElement> {
6 | options?: ScanOptions
7 | onCapture?: (barcodes: DetectedBarcode[]) => void
8 | trackConstraints?: MediaTrackConstraints;
9 | paused?: boolean;
10 | }
11 |
12 | const BarcodeScanner: FunctionComponent = ({
13 | options,
14 | onCapture,
15 | trackConstraints,
16 | paused = false,
17 | ...props
18 | }) => {
19 | const instance = useRef(null)
20 | const { isCameraReady } = useCamera(instance, trackConstraints)
21 | const {
22 | detectedBarcodes,
23 | startScan,
24 | stopScan
25 | } = useScanning(instance, options)
26 |
27 | useEffect(() => {
28 | if (isCameraReady && !paused) {
29 | startScan()
30 | } else {
31 | stopScan()
32 | }
33 | }, [stopScan, isCameraReady, startScan, paused])
34 |
35 | useEffect(() => {
36 | if (detectedBarcodes !== undefined) {
37 | onCapture?.(detectedBarcodes)
38 | }
39 | }, [detectedBarcodes, onCapture])
40 |
41 | useEffect(() => {
42 | const video = instance.current
43 | if (!video) return
44 | if (isCameraReady && !paused) {
45 | video.play().catch(console.error)
46 | } else {
47 | video.pause()
48 | }
49 | }, [paused, isCameraReady])
50 |
51 | return (
52 |
68 | )
69 | }
70 |
71 | export default BarcodeScanner
72 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export { default as BarcodeScanner } from './barcode-scanner'
2 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/helper/shimGetUserMedia.ts:
--------------------------------------------------------------------------------
1 | import { shimGetUserMedia as chromeShim } from 'webrtc-adapter/src/js/chrome/getusermedia'
2 | import { shimGetUserMedia as edgeShim } from 'webrtc-adapter/src/js/edge/getusermedia'
3 | import { shimGetUserMedia as firefoxShim } from 'webrtc-adapter/src/js/firefox/getusermedia'
4 | import { shimGetUserMedia as safariShim } from 'webrtc-adapter/src/js/safari/safari_shim'
5 | import { detectBrowser } from 'webrtc-adapter/src/js/utils'
6 |
7 | import { idempotent } from '@preflower/utils'
8 |
9 | export default idempotent(() => {
10 | const { browser } = detectBrowser(window)
11 |
12 | switch (browser) {
13 | case 'chrome':
14 | chromeShim(window)
15 | break
16 | case 'firefox':
17 | firefoxShim(window)
18 | break
19 | case 'edge':
20 | edgeShim(window)
21 | break
22 | case 'safari':
23 | safariShim(window)
24 | break
25 | default:
26 | throw new Error('[react-barcode-scanner]: MediaStream is not supported')
27 | }
28 | })
29 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/helper/utils.ts:
--------------------------------------------------------------------------------
1 | export const eventListener = async (target: T, event: string, errorEvent = 'error'): Promise => {
2 | let $resolve: (value: unknown) => void,
3 | $reject: (evt?: Event) => void
4 |
5 | const promise = new Promise((resolve, reject) => {
6 | $resolve = resolve
7 | $reject = reject
8 |
9 | target.addEventListener(event, $resolve)
10 | target.addEventListener(errorEvent, $reject)
11 | }).finally(() => {
12 | target.removeEventListener(event, $resolve)
13 | target.removeEventListener(errorEvent, $reject)
14 | })
15 |
16 | return await promise
17 | }
18 |
19 | export const timeout = async (milliseconds: number): Promise => {
20 | await new Promise(
21 | resolve => setTimeout(resolve, milliseconds)
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './use-camera'
2 |
3 | export * from './use-scanning'
4 |
5 | export * from './use-torch'
6 |
7 | export * from './use-stream-state'
8 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/hooks/use-atom.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | type SubscriberFunc = (newState: S) => void
4 |
5 | export interface Atom {
6 | set (newValue: S): void
7 | subscriptions: Array>
8 | }
9 |
10 | export function createAtom (): Atom {
11 | const subscriptions: Array> = []
12 |
13 | function set (newValue: S): void {
14 | setTimeout(() => {
15 | subscriptions.forEach((c) => { c(newValue) })
16 | })
17 | }
18 |
19 | return {
20 | set,
21 | subscriptions
22 | }
23 | }
24 |
25 | export function useAtom (atom: Atom, initialState: S | (() => S)): [S, SubscriberFunc]
26 | export function useAtom (atom: Atom): [S | undefined, SubscriberFunc]
27 | export function useAtom (atom: Atom, initialState?: S | undefined): [S | undefined, SubscriberFunc] {
28 | const [state, setState] = useState(initialState)
29 |
30 | useEffect(() => {
31 | const index = atom.subscriptions.push(setState)
32 | return () => {
33 | atom.subscriptions.splice(index, 1)
34 | }
35 | }, [atom])
36 |
37 | return [state, atom.set]
38 | }
39 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/hooks/use-camera.tsx:
--------------------------------------------------------------------------------
1 | import { type RefObject, useEffect, useMemo, useState } from 'react'
2 | import { eventListener, timeout } from '../helper/utils'
3 | import { useStreamState } from './use-stream-state'
4 |
5 | const DEFAULT_CONSTRAINTS: MediaTrackConstraints = {
6 | width: { min: 640, ideal: 1280 },
7 | height: { min: 480, ideal: 720 },
8 | facingMode: {
9 | ideal: 'environment'
10 | },
11 | advanced: [
12 | { width: 1920, height: 1280 },
13 | { aspectRatio: 1.333 }
14 | ]
15 | }
16 |
17 | /**
18 | * Manage camera stream state.
19 | * @param ref a RefObject of HTMLVideoElement
20 | * @param trackConstraints a MediaTrackConstraints object, provide advanced options
21 | * @returns {object} { isCameraReady, error }
22 | * - isCameraReady: Whether the camera is ready
23 | * - error: Error object
24 | * @example
25 | * import { type RefObject } from 'react'
26 | * import { useCamera } from 'react-barcode-scanner'
27 | *
28 | * function App () {
29 | * const ref = useRef(null)
30 | * const { isCameraReady, error } = useCamera(ref)
31 | *
32 | * useEffect(() => {
33 | * if (isCameraReady) {
34 | * console.log('Camera is ready')
35 | * }
36 | * }, [isCameraReady])
37 | *
38 | * return (
39 | *
40 | *
41 | * {error &&
{error.message}
}
42 | *
43 | * )
44 | * }
45 | */
46 | export function useCamera (ref: RefObject, trackConstraints?: MediaTrackConstraints): { isCameraReady: boolean, error: Error | undefined } {
47 | const [isCameraReady, setIsCameraReady] = useState(false)
48 | const [error, setError] = useState()
49 |
50 | const [, setStream] = useStreamState()
51 |
52 | useEffect(() => {
53 | if (!window.isSecureContext) {
54 | setError(
55 | new Error(`[react-barcode-scanner]:
56 | Browser ask for secure origin (such as https) when use getUserMedia,
57 | reference: https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins
58 | `)
59 | )
60 | }
61 | }, [])
62 |
63 | const constraints = useMemo(() => {
64 | const videoConstraints = Object.assign({}, DEFAULT_CONSTRAINTS, trackConstraints)
65 | return {
66 | audio: false,
67 | video: videoConstraints
68 | }
69 | }, [trackConstraints])
70 |
71 | useEffect(() => {
72 | let cancelled = false
73 | let stream: MediaStream
74 | const _ = async (): Promise => {
75 | setError(undefined)
76 |
77 | const target = ref.current
78 | if (target == null) return
79 |
80 | stream = await navigator.mediaDevices.getUserMedia(constraints)
81 |
82 | // Firefox need use `moz` prefix before v58
83 | // reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject#browser_compatibility
84 | if (target.mozSrcObject !== undefined) {
85 | target.mozSrcObject = stream
86 | } else {
87 | target.srcObject = stream
88 | }
89 |
90 | // According to: https://oberhofer.co/mediastreamtrack-and-its-capabilities/#queryingcapabilities
91 | // On some devices, getCapabilities only returns a non-empty object after
92 | // some delay. There is no appropriate event so we have to add a constant timeout
93 | await eventListener(target, 'loadeddata')
94 | await timeout(500)
95 |
96 | setIsCameraReady(true)
97 |
98 | setStream(stream)
99 | }
100 |
101 | const close = () => {
102 | stream?.getTracks().forEach(track => { track.stop() })
103 | }
104 |
105 | void _().then(() => {
106 | if (cancelled) {
107 | close()
108 | }
109 | }).catch(err => {
110 | setError(err)
111 | })
112 |
113 | return () => {
114 | cancelled = true
115 | close()
116 | }
117 | }, [ref, constraints, setStream])
118 |
119 | return { isCameraReady, error }
120 | }
121 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/hooks/use-scanning.tsx:
--------------------------------------------------------------------------------
1 | import { type RefObject, useCallback, useEffect, useMemo, useState } from 'react'
2 | import { BarcodeFormat, type DetectedBarcode } from '../types'
3 |
4 | export interface ScanOptions {
5 | delay?: number
6 | formats?: Array
7 | }
8 |
9 | const DEFAULT_OPTIONS = {
10 | delay: 1000,
11 | formats: ['qr_code']
12 | }
13 |
14 | /**
15 | * Use barcode scanning based on Barcode Detection API.
16 | * @param ref a RefObject of HTMLVideoElement
17 | * @param provideOptions a ScanOptions object, provide delay and formats
18 | * @returns a tuple of detected barcodes, startScan function and stopScan function
19 | * @example
20 | * import { type RefObject } from 'react'
21 | * import { useScanning } from 'react-barcode-scanner'
22 | *
23 | * function App () {
24 | * const ref = useRef(null)
25 | * const { detectedBarcodes, startScan, stopScan } = useScanning(ref)
26 | *
27 | * useEffect(() => {
28 | * if (detectedBarcodes) {
29 | * console.log(detectedBarcodes)
30 | * }
31 | * }, [detectedBarcodes])
32 | *
33 | * return (
34 | *
35 | *
36 | *
37 | *
38 | *
39 | * )
40 | * }
41 | */
42 | export function useScanning (ref: RefObject, provideOptions?: ScanOptions): {
43 | detectedBarcodes: DetectedBarcode[] | undefined,
44 | startScan: () => void,
45 | stopScan: () => void
46 | } {
47 | const [detectedBarcodes, setDetectedBarcodes] = useState()
48 | const [start, setStart] = useState(false)
49 | const options = useMemo(() => {
50 | return Object.assign({}, DEFAULT_OPTIONS, provideOptions)
51 | }, [provideOptions])
52 |
53 | const scan = useCallback(async () => {
54 | const target = ref.current
55 | const detector = new BarcodeDetector({
56 | formats: options.formats
57 | })
58 | const detected = await detector.detect(target!)
59 | if (detected !== undefined && detected.length > 0) {
60 | setDetectedBarcodes(detected)
61 | }
62 | }, [ref, options.formats])
63 |
64 | useEffect(() => {
65 | const target = ref.current
66 | if (target == null || !start) return
67 |
68 | /**
69 | * provide `cancelled` tag to prevent `frame` has been
70 | * triggered but `scan` not fulfilled when call cancelAnimationFrame
71 | */
72 | let cancelled = false
73 | let timer: number
74 | const frame = async (): Promise => {
75 | await scan()
76 | if (!cancelled) {
77 | timer = window.setTimeout(frame, options.delay)
78 | }
79 | }
80 | frame()
81 | return () => {
82 | clearTimeout(timer)
83 | cancelled = true
84 | }
85 | }, [start, ref, options.delay, scan])
86 |
87 | useEffect(() => {
88 | if (options.formats.length === 0) {
89 | setStart(false)
90 | }
91 | }, [options.formats])
92 |
93 | const startScan = useCallback(() => {
94 | setStart(true)
95 | }, [])
96 |
97 | const stopScan = useCallback(() => {
98 | setStart(false)
99 | }, [])
100 |
101 | return {
102 | detectedBarcodes,
103 | startScan,
104 | stopScan
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/hooks/use-stream-state.tsx:
--------------------------------------------------------------------------------
1 | import { createAtom, useAtom } from './use-atom'
2 |
3 | const streamAtom = createAtom()
4 |
5 | export function useStreamState (): [MediaStream | undefined, (newState: MediaStream) => void] {
6 | const [stream, setStream] = useAtom(streamAtom)
7 |
8 | return [stream, setStream]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/hooks/use-torch.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useMemo, useEffect, useCallback } from 'react'
2 | import { useStreamState } from './use-stream-state'
3 | import { createAtom, useAtom } from './use-atom'
4 |
5 | const torchAtom = createAtom()
6 |
7 | /**
8 | * Control torch of camera
9 | * @param {boolean} defaultTorchOn Whether torch is on by default
10 | * @returns {object} { isTorchSupported, isTorchOn, setIsTorchOn }
11 | * - isTorchSupported: Whether device supports torch
12 | * - error: Error object
13 | * - isTorchOn: Whether torch is on
14 | * - setIsTorchOn: Set boolean state of torch
15 | *
16 | * @example
17 | * import React from 'react'
18 | * import { useTorch } from 'react-barcode-scanner'
19 | *
20 | * export default () => {
21 | * const { isTorchSupported, error, isTorchOn, setIsTorchOn } = useTorch()
22 | *
23 | * if (error) {
24 | * return {error.message}
25 | * }
26 | *
27 | * return (
28 | *
29 | *
30 | *
31 | * )
32 | * }
33 | */
34 | export function useTorch (defaultTorchOn = false): {
35 | isTorchSupported: boolean,
36 | error: Error | undefined,
37 | isTorchOn: boolean,
38 | setIsTorchOn: (torch: boolean) => void
39 | } {
40 | const [isTorchOn, setIsTorchOn] = useAtom(torchAtom, defaultTorchOn)
41 | const [isTorchSupported, setIsTorchSupported] = useState(false)
42 | const [error, setError] = useState()
43 | const [stream] = useStreamState()
44 | const track = useMemo(() => {
45 | return stream?.getVideoTracks()[0]
46 | }, [stream])
47 |
48 | useEffect(() => {
49 | if (track == null) return
50 | const capabilities = track.getCapabilities()
51 | if (capabilities.torch !== undefined) {
52 | setIsTorchSupported(true)
53 | }
54 | }, [track])
55 |
56 | const setTorch = useCallback(async (torch: boolean) => {
57 | setError(undefined)
58 |
59 | try {
60 | if (!isTorchSupported) return
61 | await track?.applyConstraints({
62 | advanced: [{
63 | torch
64 | }]
65 | })
66 | } catch (err) {
67 | console.warn(err)
68 | setError(err as Error)
69 | }
70 | }, [track, isTorchSupported])
71 |
72 | useEffect(() => {
73 | setTorch(isTorchOn)
74 | }, [isTorchOn, setTorch])
75 |
76 | return { isTorchSupported, error, isTorchOn, setIsTorchOn }
77 | }
78 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './hooks'
2 | export * from './components'
3 | export * from './types'
4 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/polyfill.ts:
--------------------------------------------------------------------------------
1 | import { BarcodeDetectorPolyfill } from '@preflower/barcode-detector-polyfill'
2 |
3 | if (typeof window !== 'undefined') {
4 | try {
5 | // @ts-expect-error fix BarcodeDetector is not supported error
6 | window.BarcodeDetector.getSupportedFormats()
7 | } catch {
8 | // @ts-expect-error fix BarcodeDetector is not contain in window error
9 | window.BarcodeDetector = BarcodeDetectorPolyfill
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/shims/media-stream.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | // standard MediaTrackConstraintSet not support torch
3 | // but some browser support torch, so we need expand it
4 | interface MediaTrackConstraintSet {
5 | torch?: boolean
6 | }
7 |
8 | interface MediaTrackCapabilities {
9 | torch?: boolean
10 | }
11 |
12 | // Firefox need use `moz` prefix before v58
13 | // reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject#browser_compatibility
14 | interface HTMLVideoElement {
15 | mozSrcObject?: MediaStream
16 | }
17 | }
18 |
19 | export {}
20 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/shims/webrtc-adapter.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'webrtc-adapter/src/js/utils' {
2 | interface detectBrowserResult {
3 | browser: string | null
4 | version: string | null
5 | }
6 |
7 | function detectBrowser (window: Window): detectBrowserResult
8 | }
9 |
10 | declare module 'webrtc-adapter/src/js/chrome/getusermedia'
11 | declare module 'webrtc-adapter/src/js/edge/getusermedia'
12 | declare module 'webrtc-adapter/src/js/firefox/getusermedia'
13 | declare module 'webrtc-adapter/src/js/safari/safari_shim' {
14 | function shimGetUserMedia (window: Window): void
15 | }
16 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/src/types.ts:
--------------------------------------------------------------------------------
1 | export enum BarcodeFormat {
2 | CODE_128 = 'code_128',
3 | CODE_39 = 'code_39',
4 | CODE_93 = 'code_93',
5 | CODABAR = 'codabar',
6 | EAN_13 = 'ean_13',
7 | EAN_8 = 'ean_8',
8 | ITF = 'itf',
9 | QR_CODE = 'qr_code',
10 | UPC_A = 'upc_a',
11 | UPC_E = 'upc_e',
12 | }
13 |
14 | export interface Point {
15 | x: number
16 | y: number
17 | }
18 |
19 | // reference: https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector
20 | export interface DetectedBarcode {
21 | boundingBox: DOMRectReadOnly
22 | cornerPoints: Point[]
23 | format: string
24 | rawValue: string
25 | }
26 |
27 | declare global {
28 | class BarcodeDetector {
29 | constructor (options?: {
30 | formats: string[]
31 | })
32 |
33 | static getSupportedFormats (): Promise
34 | detect (target: ImageBitmapSource): Promise
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/react-barcode-scanner/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES5",
4 | "jsx": "react-jsx",
5 | "lib": [
6 | "DOM",
7 | "DOM.Iterable",
8 | "ESNext"
9 | ],
10 | "useDefineForClassFields": true,
11 | "module": "CommonJS",
12 | "moduleResolution": "Node",
13 | "resolveJsonModule": true,
14 | "allowJs": false,
15 | "strict": true,
16 | "declaration": true,
17 | "importHelpers": true,
18 | "outDir": "lib",
19 | "removeComments": true,
20 | "allowSyntheticDefaultImports": true,
21 | "esModuleInterop": false,
22 | "forceConsistentCasingInFileNames": true,
23 | "isolatedModules": true,
24 | "skipLibCheck": true
25 | },
26 | "include": [
27 | "src"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | # all packages in direct subdirs of packages/
3 | - 'packages/*'
4 | # all packages in subdirs of components/
5 | - 'components/**'
6 | # exclude packages that are inside test directories
7 | - '!**/test/**'
8 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preflower/react-barcode-scanner/ab5cf8b0ed7d67f6a3a630a4450da4f20ddbac88/public/logo.png
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "packageRules": [
3 | {
4 | "matchPackagePatterns": ["^eslint", "^@typescript-eslint", "^react", "^types"],
5 | "groupName": "eslint related"
6 | },
7 | {
8 | "matchPackagePatterns": ["*"],
9 | "rangeStrategy": "bump"
10 | },
11 | {
12 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"],
13 | "automerge": true
14 | }
15 | ],
16 | "extends": ["config:base"],
17 | "ignoreTests": true,
18 | "schedule": [
19 | "before 3am on Monday"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES5",
4 | "jsx": "react-jsx",
5 | "lib": [
6 | "DOM",
7 | "DOM.Iterable",
8 | "ESNext"
9 | ],
10 | "useDefineForClassFields": true,
11 | "module": "CommonJS",
12 | "moduleResolution": "Node",
13 | "resolveJsonModule": true,
14 | "allowJs": false,
15 | "strict": true,
16 | "declaration": true,
17 | "importHelpers": true,
18 | "outDir": "lib",
19 | "removeComments": true,
20 | "allowSyntheticDefaultImports": true,
21 | "esModuleInterop": false,
22 | "forceConsistentCasingInFileNames": true,
23 | "isolatedModules": true,
24 | "skipLibCheck": true
25 | },
26 | "include": [
27 | "packages"
28 | ],
29 | "exclude": [
30 | "**/lib",
31 | "**/esm"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------