From cc200c46fcce1ccbc1e9176fcf303ad56859d025 Mon Sep 17 00:00:00 2001 From: Niklas Gruhn Date: Fri, 27 Dec 2024 21:13:20 +0100 Subject: [PATCH] feat: export component prop types See: #462 #460 --- .github/workflows/main.yml | 1 + docs/api/QrcodeStream.md | 9 +- package.json | 2 +- pnpm-lock.yaml | 142 +++++++++++++++++++++++++++++- src/components/QrcodeCapture.vue | 11 +-- src/components/QrcodeDropZone.vue | 17 ++-- src/components/QrcodeStream.vue | 67 +++++++------- src/index.ts | 25 ++++-- src/misc/errors.ts | 2 +- tsconfig.app.json | 2 +- 10 files changed, 220 insertions(+), 58 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 37bb4fbc..6099cefc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,7 @@ jobs: VITEPRESS_BASE: /vue-qrcode-reader/ run: | pnpm install + pnpm run type-check pnpm run build pnpm run docs:build - name: Deploy Docs diff --git a/docs/api/QrcodeStream.md b/docs/api/QrcodeStream.md index 1099d695..4bb3fefd 100644 --- a/docs/api/QrcodeStream.md +++ b/docs/api/QrcodeStream.md @@ -223,7 +223,7 @@ When you unpause the camera is restarted so the `camera-on` event is emitted aga ### `track` -- **Input Type:** `Function` +- **Input Type:** `(codes: DetectedBarcode[], ctx: CanvasRenderingContext2D) => void` - **Default:** `undefined` You can visually highlight detected QR codes in real-time. @@ -232,12 +232,13 @@ When a QR code is detected, its location is painted to the canvas. To enable this feature, pass a function to `track` that defines how this should look like. This function is called to produce each frame. -It receives the location object as the first argument and a `CanvasRenderingContext2D` instance as the second argument. +It receives an array of detected barcodes as the first argument and a `CanvasRenderingContext2D` instance as the second argument. For example check out [this demo](../demos/FullDemo.md). -Note that this scanning frequency has to be increased. -So if you want to go easy on your target device you might not want to enable tracking. +Note that the scanning frequency is increased when you provide a track function, +which might hurt performance perceptibly. +So if you want to go easy on your target device you might want to keep tracking disabled. ::: danger Avoid access to reactive properties in this function (like stuff in `data`, `computed` or your Vuex store). The function is called several times a second and might cause memory leaks. To be safe don't access `this` at all. diff --git a/package.json b/package.json index b0dcb058..26541968 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "vite-plugin-dts": "3.6.4", "vitepress": "1.0.0-rc.32", "vue": "3.3.13", - "vue-tsc": "1.8.25", + "vue-tsc": "2.1.10", "workbox-window": "7.0.0" }, "bugs": "https://github.com/gruhn/vue-qrcode-reader/issues", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e0278d55..052cbf1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,8 +73,8 @@ importers: specifier: 3.3.13 version: 3.3.13(typescript@5.5.3) vue-tsc: - specifier: 1.8.25 - version: 1.8.25(typescript@5.5.3) + specifier: 2.1.10 + version: 2.1.10(typescript@5.5.3) workbox-window: specifier: 7.0.0 version: 7.0.0 @@ -264,10 +264,18 @@ packages: resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.22.20': resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.22.15': resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==} engines: {node: '>=6.9.0'} @@ -294,6 +302,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.26.3': + resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.22.15': resolution: {integrity: sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==} engines: {node: '>=6.9.0'} @@ -726,6 +739,10 @@ packages: resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==} engines: {node: '>=6.9.0'} + '@babel/types@7.26.3': + resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + engines: {node: '>=6.9.0'} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -1317,33 +1334,51 @@ packages: '@volar/language-core@1.11.1': resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} + '@volar/language-core@2.4.11': + resolution: {integrity: sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==} + '@volar/source-map@1.10.4': resolution: {integrity: sha512-RxZdUEL+pV8p+SMqnhVjzy5zpb1QRZTlcwSk4bdcBO7yOu4rtEWqDGahVCEj4CcXour+0yJUMrMczfSCpP9Uxg==} '@volar/source-map@1.11.1': resolution: {integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==} + '@volar/source-map@2.4.11': + resolution: {integrity: sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==} + '@volar/typescript@1.11.1': resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==} + '@volar/typescript@2.4.11': + resolution: {integrity: sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==} + '@vue/compiler-core@3.3.13': resolution: {integrity: sha512-bwi9HShGu7uaZLOErZgsH2+ojsEdsjerbf2cMXPwmvcgZfVPZ2BVZzCVnwZBxTAYd6Mzbmf6izcUNDkWnBBQ6A==} '@vue/compiler-core@3.3.6': resolution: {integrity: sha512-2JNjemwaNwf+MkkatATVZi7oAH1Hx0B04DdPH3ZoZ8vKC1xZVP7nl4HIsk8XYd3r+/52sqqoz9TWzYc3yE9dqA==} + '@vue/compiler-core@3.5.13': + resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} + '@vue/compiler-dom@3.3.13': resolution: {integrity: sha512-EYRDpbLadGtNL0Gph+HoKiYqXLqZ0xSSpR5Dvnu/Ep7ggaCbjRDIus1MMxTS2Qm0koXED4xSlvTZaTnI8cYAsw==} '@vue/compiler-dom@3.3.6': resolution: {integrity: sha512-1MxXcJYMHiTPexjLAJUkNs/Tw2eDf2tY3a0rL+LfuWyiKN2s6jvSwywH3PWD8bKICjfebX3GWx2Os8jkRDq3Ng==} + '@vue/compiler-dom@3.5.13': + resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} + '@vue/compiler-sfc@3.3.13': resolution: {integrity: sha512-DQVmHEy/EKIgggvnGRLx21hSqnr1smUS9Aq8tfxiiot8UR0/pXKHN9k78/qQ7etyQTFj5em5nruODON7dBeumw==} '@vue/compiler-ssr@3.3.13': resolution: {integrity: sha512-d/P3bCeUGmkJNS1QUZSAvoCIW4fkOKK3l2deE7zrp0ypJEy+En2AcypIkqvcFQOcw3F0zt2VfMvNsA9JmExTaw==} + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + '@vue/devtools-api@6.5.1': resolution: {integrity: sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==} @@ -1380,6 +1415,14 @@ packages: typescript: optional: true + '@vue/language-core@2.1.10': + resolution: {integrity: sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@vue/reactivity-transform@3.3.13': resolution: {integrity: sha512-oWnydGH0bBauhXvh5KXUy61xr9gKaMbtsMHk40IK9M4gMuKPJ342tKFarY0eQ6jef8906m35q37wwA8DMZOm5Q==} @@ -1403,6 +1446,9 @@ packages: '@vue/shared@3.3.6': resolution: {integrity: sha512-Xno5pEqg8SVhomD0kTSmfh30ZEmV/+jZtyh39q6QflrjdJCXah5lrnOLi9KB6a5k5aAHXMXjoMnxlzUkCNfWLQ==} + '@vue/shared@3.5.13': + resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} + '@vue/tsconfig@0.5.1': resolution: {integrity: sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==} @@ -1491,6 +1537,9 @@ packages: algoliasearch@4.20.0: resolution: {integrity: sha512-y+UHEjnOItoNy0bYO+WWmLWBlPwDjKHW6mNHrPi0NkuhpQOOEbrkwQH/wgKFDLh7qlKjzoKeiRtlpewDPDG23g==} + alien-signals@0.2.2: + resolution: {integrity: sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==} + ansi-escapes@6.2.0: resolution: {integrity: sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==} engines: {node: '>=14.16'} @@ -2698,6 +2747,9 @@ packages: muggle-string@0.3.1: resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -3221,6 +3273,10 @@ packages: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -3637,6 +3693,9 @@ packages: postcss: optional: true + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + vue-demi@0.14.6: resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==} engines: {node: '>=12'} @@ -3663,6 +3722,12 @@ packages: peerDependencies: typescript: '*' + vue-tsc@2.1.10: + resolution: {integrity: sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + vue@3.3.13: resolution: {integrity: sha512-LDnUpQvDgsfc0u/YgtAgTMXJlJQqjkxW1PVcOnJA5cshPleULDjHi7U45pl2VJYazSSvLH8UKcid/kzH8I0a0Q==} peerDependencies: @@ -4050,8 +4115,12 @@ snapshots: '@babel/helper-string-parser@7.22.5': {} + '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-validator-identifier@7.22.20': {} + '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-option@7.22.15': {} '@babel/helper-wrap-function@7.22.20': @@ -4082,6 +4151,10 @@ snapshots: dependencies: '@babel/types': 7.23.0 + '@babel/parser@7.26.3': + dependencies: + '@babel/types': 7.26.3 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.22.15(@babel/core@7.23.2)': dependencies: '@babel/core': 7.23.2 @@ -4607,6 +4680,11 @@ snapshots: '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 + '@babel/types@7.26.3': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@colors/colors@1.5.0': optional: true @@ -5225,6 +5303,10 @@ snapshots: dependencies: '@volar/source-map': 1.11.1 + '@volar/language-core@2.4.11': + dependencies: + '@volar/source-map': 2.4.11 + '@volar/source-map@1.10.4': dependencies: muggle-string: 0.3.1 @@ -5233,11 +5315,19 @@ snapshots: dependencies: muggle-string: 0.3.1 + '@volar/source-map@2.4.11': {} + '@volar/typescript@1.11.1': dependencies: '@volar/language-core': 1.11.1 path-browserify: 1.0.1 + '@volar/typescript@2.4.11': + dependencies: + '@volar/language-core': 2.4.11 + path-browserify: 1.0.1 + vscode-uri: 3.0.8 + '@vue/compiler-core@3.3.13': dependencies: '@babel/parser': 7.23.6 @@ -5252,6 +5342,14 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.0.2 + '@vue/compiler-core@3.5.13': + dependencies: + '@babel/parser': 7.26.3 + '@vue/shared': 3.5.13 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + '@vue/compiler-dom@3.3.13': dependencies: '@vue/compiler-core': 3.3.13 @@ -5262,6 +5360,11 @@ snapshots: '@vue/compiler-core': 3.3.6 '@vue/shared': 3.3.6 + '@vue/compiler-dom@3.5.13': + dependencies: + '@vue/compiler-core': 3.5.13 + '@vue/shared': 3.5.13 + '@vue/compiler-sfc@3.3.13': dependencies: '@babel/parser': 7.23.6 @@ -5280,6 +5383,11 @@ snapshots: '@vue/compiler-dom': 3.3.13 '@vue/shared': 3.3.13 + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + '@vue/devtools-api@6.5.1': {} '@vue/eslint-config-prettier@8.0.0(eslint@8.56.0)(prettier@3.1.1)': @@ -5330,6 +5438,19 @@ snapshots: optionalDependencies: typescript: 5.5.3 + '@vue/language-core@2.1.10(typescript@5.5.3)': + dependencies: + '@volar/language-core': 2.4.11 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.13 + alien-signals: 0.2.2 + minimatch: 9.0.3 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.5.3 + '@vue/reactivity-transform@3.3.13': dependencies: '@babel/parser': 7.23.6 @@ -5363,6 +5484,8 @@ snapshots: '@vue/shared@3.3.6': {} + '@vue/shared@3.5.13': {} + '@vue/tsconfig@0.5.1': {} '@vueuse/core@10.7.0(vue@3.3.13(typescript@5.5.3))': @@ -5453,6 +5576,8 @@ snapshots: '@algolia/requester-node-http': 4.20.0 '@algolia/transporter': 4.20.0 + alien-signals@0.2.2: {} + ansi-escapes@6.2.0: dependencies: type-fest: 3.13.1 @@ -6752,6 +6877,8 @@ snapshots: muggle-string@0.3.1: {} + muggle-string@0.4.1: {} + nanoid@3.3.7: {} natural-compare@1.4.0: {} @@ -7243,6 +7370,8 @@ snapshots: source-map-js@1.0.2: {} + source-map-js@1.2.1: {} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -7673,6 +7802,8 @@ snapshots: - typescript - universal-cookie + vscode-uri@3.0.8: {} + vue-demi@0.14.6(vue@3.3.13(typescript@5.5.3)): dependencies: vue: 3.3.13(typescript@5.5.3) @@ -7702,6 +7833,13 @@ snapshots: semver: 7.5.4 typescript: 5.5.3 + vue-tsc@2.1.10(typescript@5.5.3): + dependencies: + '@volar/typescript': 2.4.11 + '@vue/language-core': 2.1.10(typescript@5.5.3) + semver: 7.5.4 + typescript: 5.5.3 + vue@3.3.13(typescript@5.5.3): dependencies: '@vue/compiler-dom': 3.3.13 diff --git a/src/components/QrcodeCapture.vue b/src/components/QrcodeCapture.vue index 51b7bda2..bc25b4d0 100644 --- a/src/components/QrcodeCapture.vue +++ b/src/components/QrcodeCapture.vue @@ -14,11 +14,12 @@ import { type PropType } from 'vue' import { processFile } from '../misc/scanner' import { type BarcodeFormat, type DetectedBarcode } from 'barcode-detector/pure' -const props = defineProps({ - formats: { - type: Array as PropType, - default: () => ['qr_code'] as BarcodeFormat[] - } +export interface QrcodeCaptureProps { + formats?: BarcodeFormat[] +} + +const props = withDefaults(defineProps(), { + formats: () => ['qr_code'] }) const emit = defineEmits<{ diff --git a/src/components/QrcodeDropZone.vue b/src/components/QrcodeDropZone.vue index c1442e42..41d7a9f1 100644 --- a/src/components/QrcodeDropZone.vue +++ b/src/components/QrcodeDropZone.vue @@ -13,19 +13,20 @@ import { type PropType } from 'vue' import { processFile, processUrl } from '../misc/scanner' import { type BarcodeFormat, type DetectedBarcode } from 'barcode-detector/pure' -import type { EmmitedError } from '@/misc/errors' +import type { EmittedError } from '@/misc/errors' -const props = defineProps({ - formats: { - type: Array as PropType, - default: () => ['qr_code'] as BarcodeFormat[] - } +export interface QrcodeDropZoneProps { + formats?: BarcodeFormat[] +} + +const props = withDefaults(defineProps(), { + formats: () => ['qr_code'] }) const emit = defineEmits<{ (e: 'detect', detectedCodes: DetectedBarcode[]): void (e: 'dragover', isDraggingOver: boolean): void - (e: 'error', error: EmmitedError): void + (e: 'error', error: EmittedError): void }>() // methods @@ -34,7 +35,7 @@ const onDetect = async (promise: Promise) => { const detectedCodes = await promise emit('detect', detectedCodes) } catch (error) { - emit('error', error as EmmitedError) + emit('error', error as EmittedError) } } diff --git a/src/components/QrcodeStream.vue b/src/components/QrcodeStream.vue index 1449b762..833121c4 100644 --- a/src/components/QrcodeStream.vue +++ b/src/components/QrcodeStream.vue @@ -51,49 +51,55 @@ import { import { keepScanning, setScanningFormats } from '../misc/scanner' import * as cameraController from '../misc/camera' import { assert } from '../misc/util' -import type { EmmitedError } from '@/misc/errors' +import type { EmittedError } from '@/misc/errors' -const props = defineProps({ - // in this file: don't use `props.constraints` directly. Use `constraintsCached`. +export interface QrcodeStreamProps { /** * Passes an object with various camera configuration options. */ - constraints: { - type: Object as PropType, - default() { - return { facingMode: 'environment' } as MediaTrackConstraints - } - }, - // in this file: don't use `props.formats` directly. Use `formatsCached`. + constraints?: MediaTrackConstraints + /** * Passes formats that will be recognized during detection. */ - formats: { - type: Array as PropType, - default: () => ['qr_code'] as BarcodeFormat[] - }, + formats?: BarcodeFormat[] + /** * Setting this prop to true freezes the camera. Set to false to resume. */ - paused: { - type: Boolean, - default: false - }, + paused?: boolean + /** * Enables or disables camera torch during detection. */ - torch: { - type: Boolean, - default: false - }, + torch?: boolean + /** - * Defines callback function that will be responsible for drawing detected code tracking rectangle + * A function responsible for visually highlighting detected QR codes in real-time. + * A transparent canvas overlays the camera stream. When a barcode is detected, its location is painted to the canvas. + * To enable this feature, pass a function to the `track` that defines how this should look like. + * The function is called to produce each frame. It receives an array of detected codes as the first argument and a + * `CanvasRenderingContext2D` instance as the second argument. + * + * NOTE: The scanning frequency is increased when you provide a track function, which might hurt performance perceptibly. + * + * WARN: Avoid access to reactive properties in this function (like stuff in data, computed or your Vuex store). + * The function is called several times a second and might cause memory leaks. To be safe don't access `this` at all. */ - track: { - type: Function as PropType< - (detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D | null) => void - > - } + track?: (detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) => void +} + +const props = withDefaults(defineProps(), { + // in this file: don't use `props.constraints` directly. Use `constraintsCached`. + constraints: () => ({ facingMode: 'environment' }), + // in this file: don't use `props.formats` directly. Use `formatsCached`. + formats: () => ['qr_code'], + paused: false, + torch: false, + // Could also use a NO-OP function as default here but `undefined` makes + // it clearer that no function is defined and when no tracking function is + // defined we lower the scanning frequency: + track: undefined, }) const emit = defineEmits<{ @@ -112,7 +118,7 @@ const emit = defineEmits<{ /** * Defines callback function called when error occures. */ - (e: 'error', error: EmmitedError): void + (e: 'error', error: EmittedError): void }>() // Props like `constraints` and `formats` which carry non-primitive values might receive @@ -247,7 +253,7 @@ watch( emit('camera-on', capabilities) } } catch (error: unknown) { - emit('error', error as EmmitedError) + emit('error', error as EmittedError) } } else { // stop camera @@ -405,6 +411,7 @@ const onLocate = (detectedCodes: DetectedBarcode[]) => { canvas.height = video.offsetHeight const ctx = canvas.getContext('2d') + assert(ctx !== null, 'canvas 2d context should always be non-null') props.track(adjustedCodes, ctx) } diff --git a/src/index.ts b/src/index.ts index a69713f2..6357df76 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,9 @@ import type { App, Plugin } from 'vue' -import QrcodeStream from './components/QrcodeStream.vue' -import QrcodeCapture from './components/QrcodeCapture.vue' -import QrcodeDropZone from './components/QrcodeDropZone.vue' +import QrcodeStream, { type QrcodeStreamProps } from './components/QrcodeStream.vue' +import QrcodeCapture, { type QrcodeCaptureProps } from './components/QrcodeCapture.vue' +import QrcodeDropZone, { type QrcodeDropZoneProps } from './components/QrcodeDropZone.vue' +import { type EmittedError } from './misc/errors' // Install the components export function install(app: App) { @@ -11,11 +12,23 @@ export function install(app: App) { app.component('qrcode-drop-zone', QrcodeDropZone) } -// Expose the components -export { QrcodeStream, QrcodeCapture, QrcodeDropZone } +// Expose the components: +export { + QrcodeStream, + QrcodeCapture, + QrcodeDropZone, + type QrcodeStreamProps, + type QrcodeCaptureProps, + type QrcodeDropZoneProps, + type EmittedError, +} // Expose some exports from "barcode-detector" -export { setZXingModuleOverrides, type BarcodeFormat } from 'barcode-detector/pure' +export { + setZXingModuleOverrides, + type BarcodeFormat, + type DetectedBarcode +} from 'barcode-detector/pure' // Plugin definition const plugin: Plugin = { install } diff --git a/src/misc/errors.ts b/src/misc/errors.ts index d1032b2d..13f40535 100644 --- a/src/misc/errors.ts +++ b/src/misc/errors.ts @@ -34,4 +34,4 @@ export class StreamLoadTimeoutError extends Error { } } -export type EmmitedError = DropImageFetchError | StreamApiNotSupportedError | InsecureContextError | StreamLoadTimeoutError | Error +export type EmittedError = DropImageFetchError | StreamApiNotSupportedError | InsecureContextError | StreamLoadTimeoutError | Error diff --git a/tsconfig.app.json b/tsconfig.app.json index 3e5b621e..66f6fd08 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,6 +1,6 @@ { "extends": "@vue/tsconfig/tsconfig.dom.json", - "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "docs/.vitepress/**/*.{ts,vue}"], "exclude": ["src/**/__tests__/*"], "compilerOptions": { "composite": true,