diff --git a/.changeset/gentle-lobsters-knock.md b/.changeset/gentle-lobsters-knock.md
new file mode 100644
index 0000000..389d6a1
--- /dev/null
+++ b/.changeset/gentle-lobsters-knock.md
@@ -0,0 +1,5 @@
+---
+"barqode": patch
+---
+
+Initial release
diff --git a/docs/package.json b/docs/package.json
index d31cddf..69e64b8 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -14,20 +14,21 @@
"check:watch": "pnpm build:content && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
- "@svecodocs/kit": "^0.0.5",
+ "@svecodocs/kit": "^0.1.1",
"@sveltejs/adapter-cloudflare": "^4.8.0",
- "@sveltejs/kit": "^2.0.0",
- "@sveltejs/vite-plugin-svelte": "^4.0.0",
+ "@sveltejs/kit": "^2.9.0",
+ "@sveltejs/vite-plugin-svelte": "^4.0.2",
"@tailwindcss/vite": "4.0.0-beta.4",
+ "barqode": "workspace:^",
"mdsx": "^0.0.6",
"phosphor-svelte": "^3.0.0",
- "svelte": "^5.2.11",
- "svelte-check": "^4.0.0",
+ "svelte": "^5.4.0",
+ "svelte-check": "^4.1.0",
"svelte-preprocess": "^6.0.3",
"tailwindcss": "4.0.0-beta.4",
- "typescript": "^5.0.0",
+ "typescript": "^5.7.2",
"velite": "^0.2.1",
- "vite": "^5.0.11"
+ "vite": "^5.4.11"
},
"private": true
}
diff --git a/docs/src/content/components/barqode-dropzone.md b/docs/src/content/components/barqode-dropzone.md
new file mode 100644
index 0000000..63f2a02
--- /dev/null
+++ b/docs/src/content/components/barqode-dropzone.md
@@ -0,0 +1,108 @@
+---
+title: BarqodeDropzone
+description: Click to upload images, drag & drop or use your camera to scan.
+section: Components
+---
+
+
+
+This component functions as a file input with the `capture` attribute set to `environment` which allows users to take a picture with their camera. You can also drag-and-drop image files from your desktop or images embedded into other web pages anywhere in the area the component occupies. The images are directly scanned and positive results are indicated by the `onDetect` callback.
+
+## Demo
+
+
+
+## Usage
+
+```svelte
+
+
+
+
+
Click to upload or drop an image here
+
+
+
+Last detected: {result}
+
+
+```
+
+## Props
+
+### `formats`
+
+Type: [`BarcodeFormat[]`](https://github.com/Sec-ant/barcode-detector?tab=readme-ov-file#barcode-detector)
+
+Default: `["qr_code"]`
+
+Configure the barcode formats to detect. By default, only QR codes are detected.
+
+If you want to detect multiple formats, pass an array of formats:
+
+```svelte
+
+```
+
+Under the hood, the standard [BarcodeDetector API](https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector) is used. Support varies across devices, operating systems and browsers. All components will prefer to use the native implementation if available and otherwise falls back to a polyfill implementation.
+
+Note that even if the native implementation is available, the component still might use the polyfill. For example, if the native implementation only supports the format `'qr_code'` but the you select the formats `['qr_code', 'aztec']`.
+
+### `onDetect`
+
+Type: `(detectedCodes: DetectedBarcode[]) => void`
+
+Callback function that is called when a barcode is detected.
+
+It receives an array of [detected barcodes](https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector/detect#return_value), one callback per image.
+
+If not barcode is detected, the array will be empty.
+
+### `onDragover`
+
+Type: `(isDraggingOver: boolean) => void`
+
+Callback function that is called when a file is dragged over the drop zone.
+
+### `onError`
+
+Type: `(error: Error) => void`
+
+Callback function that is called when an error occurs.
+
+TODO: insert link to errors.
+
+### Other props
+
+The `BarqodeDropzone` component accepts all attributes that a standard `input` element accepts.
+
+By default, the following attributes are set:
+
+- `type="file"`. This is required to make the input a file input. You should not change this.
+- `name="image"`. This is the name of the file input.
+- `accept="image/*"`. This restricts the file types that can be uploaded to images.
+- `capture="environment"`. This tells the browser to open the camera when the input is clicked on mobile devices. You can choose between `user` and `environment`, which opens the front and back camera respectively. You can also disable this functionality by setting it to `null`.
+- `multiple`. This allows the user to upload multiple files at once. You can disable this by settings this to `false`.
+
+## Browser Support
+
+This component depends on the [File Reader API](https://developer.mozilla.org/en-US/docs/Web/API/FileReader) which is widely supported in modern browsers.
diff --git a/docs/src/content/components/barqode-stream.md b/docs/src/content/components/barqode-stream.md
new file mode 100644
index 0000000..1d11c44
--- /dev/null
+++ b/docs/src/content/components/barqode-stream.md
@@ -0,0 +1,203 @@
+---
+title: BarqodeStream
+description: Continuously scans frames from a camera stream.
+section: Components
+---
+
+
+
+The `BarqodeStream` component continuously scans frames from a camera stream and detects barcodes in real-time.
+
+## Demo
+
+
+
+## Usage
+
+```svelte
+
+
+
+
+ {#if loading}
+
Loading...
+ {/if}
+
+
+
+Last detected: {result}
+```
+
+## Props
+
+### `constraints`
+
+Type: [`MediaTrackConstraints`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints)
+
+Default: `{ video: { facingMode: "environment" } }`
+
+Configure the the various camera options, for example whether to use front or rear camera.
+
+The object must be of type `MediaTrackConstraints`.
+
+The object is passed as-is to `getUserMedia`, which is the API call for requesting a camera stream:
+
+```js
+navigator.mediaDevices.getUserMedia({
+ audio: false,
+ video: the_constraint_object_you_provide,
+});
+```
+
+When `constraints` is updated, a new camera stream is requested which triggers the `onCameraOn` callback again. You can catch errors with the `onError` callback. An error can occur when you try to use the front camera on a device that doesn't have one for example.
+
+### `formats`
+
+Type: [`BarcodeFormat[]`](https://github.com/Sec-ant/barcode-detector?tab=readme-ov-file#barcode-detector)
+
+Default: `["qr_code"]`
+
+Configure the barcode formats to detect. By default, only QR codes are detected.
+
+If you want to detect multiple formats, pass an array of formats:
+
+```svelte
+
+```
+
+
+
+Don't select more barcode formats than needed.
+
+Scanning becomes more expensive the more formats you select.
+
+
+
+Under the hood, the standard [BarcodeDetector API](https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector) is used. Support varies across devices, operating systems and browsers. All components will prefer to use the native implementation if available and otherwise falls back to a polyfill implementation.
+
+Note that even if the native implementation is available, the component still might use the polyfill. For example, if the native implementation only supports the format `'qr_code'` but the you select the formats `['qr_code', 'aztec']`.
+
+### `paused`
+
+Type: `boolean` (bindable)
+
+Default: `false`
+
+Whether the camera stream is or should be paused. Bindable, which means that you can pause/unpause the stream from outside the component.
+
+Pausing the stream by setting `paused` to `true` is useful if you want to show some microinteraction after successful scans. When the you set it to `false`, the camera stream will be restarted and the `onCameraOn` callback function will be triggered again.
+
+### `torch`
+
+Type: `boolean`
+
+Default: `false`
+
+Turn the camera flashlight on or off.
+
+This is not consistently supported by all devices and browsers. Support can even vary on the same device with the same browser. For example the rear camera often has a flashlight but the front camera does not.
+
+We can only tell if flashlight control is supported once the camera is loaded and the `onCameraOn` callback has been called. At the moment, enabling the torch may silently fail on unsupported devices, but in the `onCameraOn` callback payload you can access the `MediaTrackCapabilities` object, from which you can determine if the torch is supported.
+
+The camera stream must be reloaded when turning the torch on or off. That means the `onCameraOn` event will be emitted again.
+
+### `track`
+
+Type: `(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) => void`
+
+Callback function that can be used to visually highlight detected barcodes.
+
+A transparent canvas is overlaid on top of the camera stream. The `track` function is used to draw on this canvas.
+
+It receives an array of [detected barcodes](https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector/detect#return_value) and a [CanvasRenderingContext2D](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) as the second argument.
+
+Note that when `track` is set the scanning frequency has to be increased. So if you want to go easy on your target device you might not want to enable tracking.
+
+
+
+The `track` function is called for every frame. It is important to keep the function as performant as possible.
+
+This can lead to performance issues on low-end devices and memory leaks if not handled correctly.
+
+
+
+### `onCameraOn`
+
+Type: `(capabilities: MediaTrackCapabilities) => void`
+
+Callback function that is called when the camera stream is successfully loaded.
+
+It might take a while before the component is ready and the scanning process starts. The user has to be asked for camera access permission first and the camera stream has to be loaded.
+
+If you want to show a loading indicator, you can wait for this callback to be called. It is called as soon as the camera start streaming.
+
+The callback receives the a promise which resolves with the cameras [`MediaTrackCapabilities`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackCapabilities) when everything is ready.
+
+### `onError`
+
+Type: `(error: Error) => void`
+
+Callback function that is called when an error occurs.
+
+TODO: insert link to errors.
+
+### `onCameraOff`
+
+Type: `() => void`
+
+Callback function that is called when the camera stream is stopped.
+
+This can happen when the camera constraints are modified, for example when switching between front and rear camera or when turning the torch on or off.
+
+### `onDetect`
+
+Type: `(detectedCodes: DetectedBarcode[]) => void`
+
+Callback function that is called when a barcode is detected.
+
+It receives an array of [detected barcodes](https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector/detect#return_value).
+
+
+
+If you scan the same barcode multiple times in a row, `onDetect` is still only called once. When you hold a barcode in the camera, frames are actually decoded multiple times a second but you don't want to be flooded with callbacks that often. That's why the last decoded QR code is always cached and only new results are propagated. However, changing the value of `paused` resets this internal cache.
+
+
+
+## Browser Support
+
+This component depends on the [Media Capture and Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Media_Capture_and_Streams_API) which is widely supported in modern browsers.
diff --git a/docs/src/content/components/button.md b/docs/src/content/components/button.md
deleted file mode 100644
index 6aaa66f..0000000
--- a/docs/src/content/components/button.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: Button
-description: A button component to use in examples and documentation.
-section: Components
----
-
-
-
-## Usage
-
-```svelte title="document.md"
-
-
-
-
-
-
-
-
-```
-
-## Example
-
-### Default Size
-
-
-
-
-
-
-
-
-
-
-
-### Small Size
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/src/content/components/callout.md b/docs/src/content/components/callout.md
deleted file mode 100644
index a7457ff..0000000
--- a/docs/src/content/components/callout.md
+++ /dev/null
@@ -1,88 +0,0 @@
----
-title: Callout
-description: A callout component to highlight important information.
-section: Components
----
-
-
-
-Callouts (also known as _admonitions_) are used to highlight a block of text. There are five types of callouts available: `'note'`, `'warning'`, `'danger'`, `'tip'`, and `'success'`.
-
-You can override the default icon for the callout by passing a component via the `icon` prop.
-
-## Usage
-
-```svelte title="document.md"
-
-
-
-
- This is a note, used to highlight important information or provide additional context. You can use
- markdown in here as well! Just ensure you include a space between the component and the content in
- your Markdown file.
-
-
-```
-
-## Examples
-
-### Warning
-
-
-
-This is an example of a warning callout.
-
-
-
-### Note
-
-
-
-This is an example of a note callout.
-
-
-
-### Danger
-
-
-
-This is an example of a danger callout.
-
-
-
-### Tip
-
-
-
-This is an example of a tip callout.
-
-
-
-### Success
-
-
-
-This is an example of a success callout.
-
-
-
-### Custom Icon
-
-
-
-This is an example of a note callout with a custom icon.
-
-
-
-### Custom Title
-
-
-
-This is an example of a warning callout with a custom title.
-
-
diff --git a/docs/src/content/configuration/navigation.md b/docs/src/content/configuration/navigation.md
deleted file mode 100644
index 506ec69..0000000
--- a/docs/src/content/configuration/navigation.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-title: Navigation
-description: Learn how to customize the navigation in your Svecodocs project.
-section: Configuration
----
-
-Navigation is a key component of every site, documenting the structure of your site and providing a clear path for users to navigate through your content.
-
-Svecodocs comes with a navigation structure that is designed to be flexible and customizable. Each page in your site should have a corresponding navigation item, and the navigation items should be nested according to their hierarchy.
-
-## Navigation Structure
-
-### Anchors
-
-Anchors are links that are displayed at the top of the sidebar and typically used to either highlight important information or provide quick access to linked content.
-
-### Sections
-
-Sections are used to group related navigation items together. They are typically used to organize content into different categories, such as "Components", "Configuration", and "Utilities".
diff --git a/docs/src/content/configuration/theme.md b/docs/src/content/configuration/theme.md
deleted file mode 100644
index 49b248b..0000000
--- a/docs/src/content/configuration/theme.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-title: Theme
-description: Learn how to customize the theme in your Svecodocs project.
-section: Configuration
----
-
-The theme determines the branded color scheme for your site. A theme for each of the TailwindCSS colors is provided by the `@svecodocs/kit` package. Each theme has been designed to present well in both light and dark mode.
-
-## Using a theme
-
-To use a theme, import the theme file into your `src/app.css` file _before_ importing the `@svecodocs/kit/globals.css` file.
-
-```css
-/* @import "@svecodocs/kit/theme-orange.css"; */
-@import "@svecodocs/kit/theme-emerald.css";
-@import "@svecodocs/kit/globals.css";
-```
-
-It's not recommended to customize the theme to maintain consistency across the UI components that are provided by Svecodocs and align with the provided themes.
-
-## Available themes
-
-| Theme name | Import path |
-| ---------- | ---------------------------------- |
-| orange | `@svecodocs/kit/theme-orange.css` |
-| green | `@svecodocs/kit/theme-green.css` |
-| blue | `@svecodocs/kit/theme-blue.css` |
-| purple | `@svecodocs/kit/theme-purple.css` |
-| pink | `@svecodocs/kit/theme-pink.css` |
-| lime | `@svecodocs/kit/theme-lime.css` |
-| yellow | `@svecodocs/kit/theme-yellow.css` |
-| cyan | `@svecodocs/kit/theme-cyan.css` |
-| teal | `@svecodocs/kit/theme-teal.css` |
-| violet | `@svecodocs/kit/theme-violet.css` |
-| amber | `@svecodocs/kit/theme-amber.css` |
-| red | `@svecodocs/kit/theme-red.css` |
-| sky | `@svecodocs/kit/theme-sky.css` |
-| emerald | `@svecodocs/kit/theme-emerald.css` |
-| fuchsia | `@svecodocs/kit/theme-fuchsia.css` |
-| rose | `@svecodocs/kit/theme-rose.css` |
-
-## Tailwind Variables
-
-Svecodocs uses TailwindCSS to style the UI components and provides a set of Tailwind variables that can be used to style your examples/custom components.
-
-### Gray
-
-We override the TailwindCSS `gray` color scale to provide our own grays.
-
-### Brand
-
-You can use the `brand` color to use the brand color of your project.
diff --git a/docs/src/content/demos/full-demo.md b/docs/src/content/demos/full-demo.md
new file mode 100644
index 0000000..838c3c1
--- /dev/null
+++ b/docs/src/content/demos/full-demo.md
@@ -0,0 +1,253 @@
+---
+title: Full demo
+description: Kitchen sink demo of the BarqodeStream component.
+section: Demos
+---
+
+
+
+Modern mobile phones often have a variety of different cameras installed (e.g. front, rear,
+wide-angle, infrared, desk-view). The one picked by default is sometimes not the best
+choice. For more fine-grained control, you can select a camera by device constraints or by
+the device ID.
+
+Detected codes are visually highlighted in real-time. In this demo you can use the track function dropdown to change the flavor.
+
+By default only QR-codes are detected but a variety of other barcode formats are also supported.
+You can select one or multiple but the more you select the more expensive scanning becomes.
+
+## Demo
+
+
+
+## Usage
+
+```svelte
+
+
+
+
+
+
+
+
+
+{#each Object.keys(barcodeFormats) as option}
+ {@const barcodeOption = option as BarcodeFormat}
+
+
+
+
+{/each}
+
+{#if error}
+ {error}
+{/if}
+
+
+
+
+
+Last result: {result}
+```
diff --git a/docs/src/content/getting-started.md b/docs/src/content/getting-started.md
index 564de98..a0957f1 100644
--- a/docs/src/content/getting-started.md
+++ b/docs/src/content/getting-started.md
@@ -1,82 +1,43 @@
---
title: Getting Started
-description: A quick guide to get started using Svecodocs
+description: A quick guide to get started using barqode
section: Overview
---
-
-
-The following guide will walk you through the process of getting a Svecodocs project up and running.
+The following guide will walk you through installing and setting up the barqode components in your Svelte project.
-## Clone the starter template
+## Installation
-Clone the Svecodocs starter template:
+Install the package using your preferred package manager:
```bash
-pnpx degit svecosystem/svecodocs/starter
-```
-
-## Navigation
-
-The starter template comes with a basic navigation structure to get your started. To customize the navigation, adjust the `src/lib/navigation.ts` file.
-
-```ts
-import { createNavigation } from "@svecodocs/kit";
-
-export const navigation = createNavigation({
- // Customize the navigation here
-});
-```
-
-## Site config
-
-The site config is used to configure site-wide settings, such as the title, description, keywords, ogImage, and other metadata.
-
-The config is located in the `src/lib/site-config.ts` file.
-
-```ts
-import { defineSiteConfig } from "@svecodocs/kit";
-
-export const siteConfig = defineSiteConfig({
- title: "Svecodocs",
- description: "A SvelteKit docs starter template",
- keywords: ["sveltekit", "docs", "starter", "template"],
- ogImage: {
- url: "https://docs.sveco.dev/og.png",
- height: 630,
- width: 1200,
- },
-});
+npm install barqode
```
-### Per-Route Site Config
-
-You can override any part of the site config on a per-route basis using the `useSiteConfig` hook.
+## Basic Usage
-
-This feature is still being worked on.
-
+The simplest way to use the library is with the `BarqodeStream` component for live scanning:
-## Theme
+```svelte
+
-```css {1-2}
-/* @import "@svecodocs/kit/themes/orange.css"; */
-@import "@svecodocs/kit/themes/emerald.css";
-@import "@svecodocs/kit/globals.css";
+
+
+
+
+
```
-## Logo
-
-To customize the logo displayed in the sidebar header, head to the `src/routes/(docs)/+layout.svelte` file and adjust the contents of the `logo` snippet. If the logo has a light and dark version, ensure to handle those similarly to the default Svecosystem logo.
-
-```svelte title="src/routes/(docs)/+layout.svelte"
-{#snippet logo()}
-
-
- The project name here
-{/snippet}
-```
+For detailed information about each component's capabilities and options, refer to their respective API documentation pages.
diff --git a/docs/src/content/index.md b/docs/src/content/index.md
index 36df5fc..5b8dbd9 100644
--- a/docs/src/content/index.md
+++ b/docs/src/content/index.md
@@ -1,26 +1,57 @@
---
title: Introduction
-description: What exactly is Svecodocs?
+description: What is Barqode?
section: Overview
---
-
+Barqode provides a set of Svelte components for detecting and decoding QR codes and various other barcode formats right in the browser. It's a comprehensive solution that supports both camera streams and static image processing.
-After spending countless hours building documentation sites for various projects, we decided to build a docs package/starter template that we can use for future projects. This project is a result of that effort.
+Barqode started out as a port of [`vue-qrcode-reader`](https://github.com/gruhn/vue-qrcode-reader).
-Svecodocs is a starting point/utility library for building documentation sites under the [Svecosystem](https://github.com/svecosystem) umbrella. The code is open source, but it's built and maintained for our own specific needs, so we won't be accepting any public feature requests.
+## Components
-You are more than welcome to fork the project and customize it to your own needs.
+- **`BarqodeStream`** - continuously scans frames from a camera stream.
+- **`BarqodeDropzone`** - drag & drop, click to upload or capture images from camera.
## Features
-- **Markdown-based docs**. Write docs using Markdown and Svelte components
-- **Light and dark mode**. Toggle between light and dark mode
-- **Syntax highlighting**. Code blocks are automatically highlighted
-- **SEO-friendly**. Meta tags and Open Graph support out of the box
-- **Pre-built components**. Tabs, callouts, and more to use within the documentation
-- **Custom unified plugins**. Custom remark and rehype plugins to give more flexibility over the rendered HTML
-- **shadcn-svelte**. Beautifully designed Svelte components
-- **Tailwind v4**. Tailwind CSS v4 is used for styling
+- **Real-time scanning**. Detect codes from live camera stream.
+- **Multiple formats**. Support for QR codes and various barcode standards.
+- **Visual feedback**. Customizable canvas based tracking of detected codes.
+- **Cross-browser**. Works across modern browsers with a [WebAssembly-based polyfill](https://github.com/Sec-ant/barcode-detector) if needed.
+- **Camera selection**. Choose between front/rear cameras.
+- **Torch control**. Control device flashlight where supported.
+- **Responsive**. Components adapt to fill available space.
+- **Error handling**. Comprehensive error handling for camera/detection issues.
+
+## Usage Example
+
+```svelte
+
+
+
+
+
+
+
+```
+
+## Browser Support
+
+The components rely primarily on the [Barcode Detection API](https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API) and [Media Capture and Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Media_Capture_and_Streams_API), with fallbacks where possible.
+
+While the core scanning functionality uses the native `BarcodeDetector` where available, it falls back to a [WebAssembly-based polyfill](https://github.com/Sec-ant/barcode-detector) to ensure consistent behavior across browsers.
+
+For a detailed compatibility overview, check each component's documentation.
diff --git a/docs/src/lib/components/demos/barqode-dropzone.svelte b/docs/src/lib/components/demos/barqode-dropzone.svelte
new file mode 100644
index 0000000..0089ee5
--- /dev/null
+++ b/docs/src/lib/components/demos/barqode-dropzone.svelte
@@ -0,0 +1,53 @@
+
+
+
+
+ {#each Object.keys(barcodeFormats) as option}
+ {@const barcodeOption = option as BarcodeFormat}
+
+
+
+
+ {/each}
+
+
+
+ {#if error}
+
{error}
+ {/if}
+
+
+
+
+
+
+ Last detected: {result}
+
+
+
+
diff --git a/docs/src/lib/navigation.ts b/docs/src/lib/navigation.ts
index 50f5986..8b63a54 100644
--- a/docs/src/lib/navigation.ts
+++ b/docs/src/lib/navigation.ts
@@ -1,4 +1,4 @@
-import { createNavigation } from "@svecodocs/kit";
+import { defineNavigation } from "@svecodocs/kit";
import ChalkboardTeacher from "phosphor-svelte/lib/ChalkboardTeacher";
import RocketLaunch from "phosphor-svelte/lib/RocketLaunch";
import Tag from "phosphor-svelte/lib/Tag";
@@ -13,14 +13,14 @@ const components = allDocs
href: `/docs/${doc.slug}`,
}));
-const configuration = allDocs
- .filter((doc) => doc.section === "Configuration")
+const demos = allDocs
+ .filter((doc) => doc.section === "Demos")
.map((doc) => ({
title: doc.title,
href: `/docs/${doc.slug}`,
}));
-export const navigation = createNavigation({
+export const navigation = defineNavigation({
anchors: [
{
title: "Introduction",
@@ -40,12 +40,16 @@ export const navigation = createNavigation({
],
sections: [
{
- title: "Configuration",
- items: configuration,
+ title: "Components",
+ items: components.sort((a, b) => {
+ if (a.title === "BarqodeStream") return -1;
+ if (b.title === "BarqodeStream") return 1;
+ return a.title.localeCompare(b.title);
+ }),
},
{
- title: "Components",
- items: components,
+ title: "Demos",
+ items: demos,
},
],
});
diff --git a/docs/src/lib/site-config.ts b/docs/src/lib/site-config.ts
index 899cea7..ac4610a 100644
--- a/docs/src/lib/site-config.ts
+++ b/docs/src/lib/site-config.ts
@@ -9,7 +9,7 @@ export const siteConfig = defineSiteConfig({
width: "1200",
},
description: "QR and barcode detection for Svelte.",
- author: "Ollema",
+ author: "ollema",
keywords: [
"svelte qr code reader",
"svelte barcodes",
diff --git a/docs/src/routes/api/search.json/search.json b/docs/src/routes/api/search.json/search.json
index 269e220..319052c 100644
--- a/docs/src/routes/api/search.json/search.json
+++ b/docs/src/routes/api/search.json/search.json
@@ -2,85 +2,31 @@
{
"title": "Getting Started",
"href": "/docs/getting-started",
- "description": "A quick guide to get started using Svecodocs",
- "content": " import { Callout } from \"@svecodocs/kit\"; The following guide will walk you through the process of getting a Svecodocs project up and running. Clone the starter template Clone the Svecodocs starter template: pnpx degit svecosystem/svecodocs/starter Navigation The starter template comes with a basic navigation structure to get your started. To customize the navigation, adjust the src/lib/navigation.ts file. import { createNavigation } from \"@svecodocs/kit\"; export const navigation = createNavigation({ // Customize the navigation here }); Site config The site config is used to configure site-wide settings, such as the title, description, keywords, ogImage, and other metadata. The config is located in the src/lib/site-config.ts file. import { defineSiteConfig } from \"@svecodocs/kit\"; export const siteConfig = defineSiteConfig({ title: \"Svecodocs\", description: \"A SvelteKit docs starter template\", keywords: \"sveltekit, docs, starter, template\", ogImage: { url: \"https://docs.svecosystem.com/og.png\", height: 630, width: 1200, }, }); Per-Route Site Config You can override any part of the site config on a per-route basis using the useSiteConfig hook. This feature is still being worked on. Theme The starter template comes with the default Svecodocs theme (orange). To customize the theme, adjust the import in the src/app.css file to reflect the color scheme you want to use for your project. Each theme has been designed to work well in both light and dark mode. /* @import \"@svecodocs/kit/themes/orange.css\"; */ @import \"@svecodocs/kit/themes/emerald.css\"; @import \"@svecodocs/kit/globals.css\"; Logo To customize the logo displayed in the sidebar header, head to the src/routes/(docs)/+layout.svelte file and adjust the contents of the logo snippet. If the logo has a light and dark version, ensure to handle those similarly to the default Svecosystem logo. {#snippet logo()} The project name here {/snippet} `"
+ "description": "A quick guide to get started using barqode",
+ "content": "The following guide will walk you through installing and setting up the barqode components in your Svelte project. Installation Install the package using your preferred package manager: npm install barqode Basic Usage The simplest way to use the library is with the BarqodeStream component for live scanning: import { BarqodeStream, type DetectedBarcode } from \"barqode\"; function onDetect(detectedCodes: DetectedBarcode[]) { console.log(detectedCodes.map((detectedCode) => detectedCode.rawValue)); } .barqode { width: 100%; max-width: 600px; aspect-ratio: 4/3; } For detailed information about each component's capabilities and options, refer to their respective API documentation pages."
},
{
"title": "Introduction",
"href": "/docs/index",
- "description": "What exactly is Svecodocs?",
- "content": " import { Callout } from '@svecodocs/kit' After spending countless hours building documentation sites for various projects, we decided to build a docs package/starter template that we can use for future projects. This project is a result of that effort. Svecodocs is a starting point/utility library for building documentation sites under the $2 umbrella. The code is open source, but it's built and maintained for our own specific needs, so we won't be accepting any public feature requests. You are more than welcome to fork the project and customize it to your own needs. Features Markdown-based docs**. Write docs using Markdown and Svelte components Light and dark mode**. Toggle between light and dark mode Syntax highlighting**. Code blocks are automatically highlighted SEO-friendly**. Meta tags and Open Graph support out of the box Pre-built components**. Tabs, callouts, and more to use within the documentation Custom unified plugins**. Custom remark and rehype plugins to give more flexibility over the rendered HTML shadcn-svelte**. Beautifully designed Svelte components Tailwind v4**. Tailwind CSS v4 is used for styling"
+ "description": "What is Barqode?",
+ "content": "Barqode provides a set of Svelte components for detecting and decoding QR codes and various other barcode formats right in the browser. It's a comprehensive solution that supports both camera streams and static image processing. Barqode started out as a port of $2. Components BarqodeStream** - continuously scans frames from a camera stream. BarqodeDropzone** - drag & drop, click to upload or capture images from camera. Features Real-time scanning**. Detect codes from live camera stream. Multiple formats**. Support for QR codes and various barcode standards. Visual feedback**. Customizable canvas based tracking of detected codes. Cross-browser**. Works across modern browsers with a $2 if needed. Camera selection**. Choose between front/rear cameras. Torch control**. Control device flashlight where supported. Responsive**. Components adapt to fill available space. Error handling**. Comprehensive error handling for camera/detection issues. Usage Example import { BarqodeStream, type DetectedBarcode } from \"barqode\"; function onDetect(detectedCodes: DetectedBarcode[]) { console.log(detectedCodes.map((detectedCode) => detectedCode.rawValue)); } .barqode { width: 100%; max-width: 600px; aspect-ratio: 4/3; } Browser Support The components rely primarily on the $2 and $2, with fallbacks where possible. While the core scanning functionality uses the native BarcodeDetector where available, it falls back to a $2 to ensure consistent behavior across browsers. For a detailed compatibility overview, check each component's documentation."
},
{
- "title": "Button",
- "href": "/docs/components/button",
- "description": "A button component to use in examples and documentation.",
- "content": " import { Button, DemoContainer } from \"@svecodocs/kit\"; Usage import { Button } from \"@svecodocs/kit\"; Default Brand Ghost Outline Subtle Link Example Default Size Default Brand Destructive Ghost Outline Subtle Link Small Size Default Brand Destructive Ghost Outline Subtle Link "
+ "title": "BarqodeDropzone",
+ "href": "/docs/components/barqode-dropzone",
+ "description": "Click to upload images, drag & drop or use your camera to scan.",
+ "content": " import Demo from '$lib/components/demos/barqode-dropzone.svelte'; This component functions as a file input with the capture attribute set to environment which allows users to take a picture with their camera. You can also drag-and-drop image files from your desktop or images embedded into other web pages anywhere in the area the component occupies. The images are directly scanned and positive results are indicated by the onDetect callback. Demo Usage import { BarqodeDropzone, type DetectedBarcode } from \"barqode\"; let result = $state(\"\"); let dragover = $state(false); function onDetect(detectedCodes: DetectedBarcode[]) { result = detectedCodes.map((detectedCode) => detectedCode.rawValue).join(\", \"); } function onDragover(isDraggingOver: boolean) { dragover = isDraggingOver; } Click to upload or drop an image here Last detected: {result} .dragover { border-color: white; } Props formats Type: [BarcodeFormat[]](https://github.com/Sec-ant/barcode-detector?tab=readme-ov-file#barcode-detector) Default: [\"qr_code\"] Configure the barcode formats to detect. By default, only QR codes are detected. If you want to detect multiple formats, pass an array of formats: Under the hood, the standard $2 is used. Support varies across devices, operating systems and browsers. All components will prefer to use the native implementation if available and otherwise falls back to a polyfill implementation. Note that even if the native implementation is available, the component still might use the polyfill. For example, if the native implementation only supports the format 'qr_code' but the you select the formats ['qr_code', 'aztec']. onDetect Type: (detectedCodes: DetectedBarcode[]) => void Callback function that is called when a barcode is detected. It receives an array of $2, one callback per image. If not barcode is detected, the array will be empty. onDragover Type: (isDraggingOver: boolean) => void Callback function that is called when a file is dragged over the drop zone. onError Type: (error: Error) => void Callback function that is called when an error occurs. TODO: insert link to errors. Other props The BarqodeDropzone component accepts all attributes that a standard input element accepts. By default, the following attributes are set: type=\"file\". This is required to make the input a file input. You should not change this. name=\"image\". This is the name of the file input. accept=\"image/*\". This restricts the file types that can be uploaded to images. capture=\"environment\". This tells the browser to open the camera when the input is clicked on mobile devices. You can choose between user and environment, which opens the front and back camera respectively. You can also disable this functionality by setting it to null. multiple. This allows the user to upload multiple files at once. You can disable this by settings this to false. Browser Support This component depends on the $2 which is widely supported in modern browsers."
},
{
- "title": "Callout",
- "href": "/docs/components/callout",
- "description": "A callout component to highlight important information.",
- "content": " import { Callout } from \"@svecodocs/kit\"; import Avocado from \"phosphor-svelte/lib/Avocado\"; Callouts (also known as admonitions) are used to highlight a block of text. There are five types of callouts available: 'note', 'warning', 'danger', 'tip', and 'success'. You can override the default icon for the callout by passing a component via the icon prop. Usage import { Callout } from \"$lib/components\"; This is a note, used to highlight important information or provide additional context. You can use markdown in here as well! Just ensure you include a space between the component and the content in your Markdown file. Examples Warning This is an example of a warning callout. Note This is an example of a note callout. Danger This is an example of a danger callout. Tip This is an example of a tip callout. Success This is an example of a success callout. Custom Icon This is an example of a note callout with a custom icon. Custom Title This is an example of a warning callout with a custom title. "
+ "title": "BarqodeStream",
+ "href": "/docs/components/barqode-stream",
+ "description": "Continuously scans frames from a camera stream.",
+ "content": " import Demo from '$lib/components/demos/barqode-stream.svelte'; import { Callout } from '@svecodocs/kit'; The BarqodeStream component continuously scans frames from a camera stream and detects barcodes in real-time. Demo Usage import { BarqodeStream, type DetectedBarcode } from \"barqode\"; let loading = $state(true); let result: string | null = $state(null); function onCameraOn() { loading = false; } function onDetect(detectedCodes: DetectedBarcode[]) { result = detectedCode.map((code) => detectedCode.rawValue).join(\", \"); } function track(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) { for (const detectedCode of detectedCodes) { const [firstPoint, ...otherPoints] = detectedCode.cornerPoints; ctx.strokeStyle = \"#2563eb\"; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(firstPoint.x, firstPoint.y); for (const { x, y } of otherPoints) { ctx.lineTo(x, y); } ctx.lineTo(firstPoint.x, firstPoint.y); ctx.closePath(); ctx.stroke(); } } {#if loading} Loading... {/if} Last detected: {result} Props constraints Type: $2 Default: { video: { facingMode: \"environment\" } } Configure the the various camera options, for example whether to use front or rear camera. The object must be of type MediaTrackConstraints. The object is passed as-is to getUserMedia, which is the API call for requesting a camera stream: navigator.mediaDevices.getUserMedia({ audio: false, video: the_constraint_object_you_provide, }); When constraints is updated, a new camera stream is requested which triggers the onCameraOn callback again. You can catch errors with the onError callback. An error can occur when you try to use the front camera on a device that doesn't have one for example. formats Type: [BarcodeFormat[]](https://github.com/Sec-ant/barcode-detector?tab=readme-ov-file#barcode-detector) Default: [\"qr_code\"] Configure the barcode formats to detect. By default, only QR codes are detected. If you want to detect multiple formats, pass an array of formats: Don't select more barcode formats than needed. Scanning becomes more expensive the more formats you select. Under the hood, the standard $2 is used. Support varies across devices, operating systems and browsers. All components will prefer to use the native implementation if available and otherwise falls back to a polyfill implementation. Note that even if the native implementation is available, the component still might use the polyfill. For example, if the native implementation only supports the format 'qr_code' but the you select the formats ['qr_code', 'aztec']. paused Type: boolean (bindable) Default: false Whether the camera stream is or should be paused. Bindable, which means that you can pause/unpause the stream from outside the component. Pausing the stream by setting paused to true is useful if you want to show some microinteraction after successful scans. When the you set it to false, the camera stream will be restarted and the onCameraOn callback function will be triggered again. torch Type: boolean Default: false Turn the camera flashlight on or off. This is not consistently supported by all devices and browsers. Support can even vary on the same device with the same browser. For example the rear camera often has a flashlight but the front camera does not. We can only tell if flashlight control is supported once the camera is loaded and the onCameraOn callback has been called. At the moment, enabling the torch may silently fail on unsupported devices, but in the onCameraOn callback payload you can access the MediaTrackCapabilities object, from which you can determine if the torch is supported. The camera stream must be reloaded when turning the torch on or off. That means the onCameraOn event will be emitted again. track Type: (detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) => void Callback function that can be used to visually highlight detected barcodes. A transparent canvas is overlaid on top of the camera stream. The track function is used to draw on this canvas. It receives an array of $2 and a $2 as the second argument. Note that when track is set the scanning frequency has to be increased. So if you want to go easy on your target device you might not want to enable tracking. The track function is called for every frame. It is important to keep the function as performant as possible. This can lead to performance issues on low-end devices and memory leaks if not handled correctly. onCameraOn Type: (capabilities: MediaTrackCapabilities) => void Callback function that is called when the camera stream is successfully loaded. It might take a while before the component is ready and the scanning process starts. The user has to be asked for camera access permission first and the camera stream has to be loaded. If you want to show a loading indicator, you can wait for this callback to be called. It is called as soon as the camera start streaming. The callback receives the a promise which resolves with the cameras $2 when everything is ready. onError Type: (error: Error) => void Callback function that is called when an error occurs. TODO: insert link to errors. onCameraOff Type: () => void Callback function that is called when the camera stream is stopped. This can happen when the camera constraints are modified, for example when switching between front and rear camera or when turning the torch on or off. onDetect Type: (detectedCodes: DetectedBarcode[]) => void Callback function that is called when a barcode is detected. It receives an array of $2. If you scan the same barcode multiple times in a row, onDetect is still only called once. When you hold a barcode in the camera, frames are actually decoded multiple times a second but you don't want to be flooded with callbacks that often. That's why the last decoded QR code is always cached and only new results are propagated. However, changing the value of paused resets this internal cache. Browser Support This component depends on the $2 which is widely supported in modern browsers."
},
{
- "title": "Card Grid",
- "href": "/docs/components/card-grid",
- "description": "Display a grid of cards.",
- "content": " import { CardGrid, Card } from \"@svecodocs/kit\"; import RocketLaunch from \"phosphor-svelte/lib/RocketLaunch\"; import Blueprint from \"phosphor-svelte/lib/Blueprint\"; import Binary from \"phosphor-svelte/lib/Binary\"; import CloudCheck from \"phosphor-svelte/lib/CloudCheck\"; Use the CardGrid component to display a grid of $2 components. Usage import { CardGrid, Card } from \"@svecodocs/ui\"; You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. Examples 2 Columns (default) You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. 3 Columns You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. "
- },
- {
- "title": "Card",
- "href": "/docs/components/card",
- "description": "Display a card with a title and optional icon.",
- "content": " import { Card } from \"@svecodocs/kit\"; import RocketLaunch from \"phosphor-svelte/lib/RocketLaunch\"; You can use the Card component to display a card with a title and optional icon. Usage With Icon Pass an icon component to the icon prop to display an icon in the card. import { Card } from \"@svecodocs/ui\"; import RocketLaunch from \"phosphor-svelte/lib/RocketLaunch\"; You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. Link Card Pass the href prop to convert the card into a link. import { Card } from \"@svecodocs/ui\"; import RocketLaunch from \"phosphor-svelte/lib/RocketLaunch\"; You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. Without Icon If you don't want to use an icon, just don't pass the icon prop. import { Card } from \"@svecodocs/ui\"; You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. Horizontal You can use the horizontal prop to display the card horizontally. import { Card } from \"@svecodocs/ui\"; import RocketLaunch from \"phosphor-svelte/lib/RocketLaunch\"; You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. You can use markdown in here, just ensure to include a space between the component and the content in your Markdown file. "
- },
- {
- "title": "Demo Container",
- "href": "/docs/components/demo-container",
- "description": "Display a container with a border and a background color for examples/demos.",
- "content": " import { DemoContainer, Button } from \"@svecodocs/kit\"; Often times you'll want to display some demo/example components in a container. The DemoContainer component is a great way to do this, as it aligns effortlessly with the rest of the docs theme. Usage import { DemoContainer, Button } from \"@svecodocs/ui\"; Default Brand Outline Ghost Subtle Link Example Default Brand Outline Ghost Subtle Link "
- },
- {
- "title": "Input",
- "href": "/docs/components/input",
- "description": "A form input component to use in examples and documentation.",
- "content": " import { Input, Label, DemoContainer, Button } from \"@svecodocs/kit\"; When building documentation, it's often necessary to provide users with a form input to showcase a specific feature. The Input component is a great way to do this, as it aligns effortlessly with the rest of the docs theme. The Label component is also provided to help with accessibility. Usage import { Input, Label } from \"@svecodocs/kit\"; Your name Example First name Last name Update Profile "
- },
- {
- "title": "Native Select",
- "href": "/docs/components/native-select",
- "description": "A styled native select component to use in examples and documentation.",
- "content": " import { NativeSelect, Label, DemoContainer } from \"@svecodocs/kit\"; The NativeSelect component is a styled native select component that you can use in your examples and documentation. Usage import { NativeSelect } from \"@svecodocs/kit\"; Option 1 Option 2 Option 3 Example Select an option Option 1 Option 2 Option 3 "
- },
- {
- "title": "Steps",
- "href": "/docs/components/steps",
- "description": "Display a series of series of steps.",
- "content": " import { Step, Steps, Callout } from \"@svecodocs/kit\"; The Steps and Step components are used to display a series of steps, breaking down a process into more manageable chunks. Usage import { Steps, Step } from \"$lib/components\"; Install the package You can install the project via npm or pnpm. Start your engines You can start the project by running npm run dev or pnpm run dev. Example Install the package You can install the project via npm or pnpm. npm install @svecodocs/ui Start your engines You can start the project by running npm run dev or pnpm dev. npm run dev If you plan to use markdown-specific syntax in your steps, ensure you include a space between the component and the content in your Markdown file. "
- },
- {
- "title": "Tabs",
- "href": "/docs/components/tabs",
- "description": "Break content into multiple panes to reduce cognitive load.",
- "content": " import { Tabs, TabItem, Callout } from \"@svecodocs/kit\"; You can use the Tabs and TabItem components to create tabbed interfaces. A label prop must be provided to each TabItem which will be used to display the label. Whichever tab should be active by default is specified by the value prop on the Tabs component. Usage import { Tabs, TabItem } from \"@svecodocs/kit\"; This is the first tab's content. This is the second tab's content. Examples Simple Text This is the first tab's content. This is the second tab's content. Markdown Syntax import { Button } from \"@svecodocs/kit\"; alert(\"Hello!\")}>Click me export async function load() { return { transactions: [], }; } If you plan to use markdown-specific syntax in your tabs, ensure you include a space between the component and the content in your Markdown file. "
- },
- {
- "title": "Textarea",
- "href": "/docs/components/textarea",
- "description": "A textarea component to use in examples and documentation.",
- "content": " import { Textarea, Label, DemoContainer, Button } from \"@svecodocs/kit\"; When building documentation, it's often necessary to provide users with a textarea to showcase a specific feature. The Textarea component is a great way to do this, as it aligns effortlessly with the rest of the docs theme. The Label component is also provided to help with accessibility. Usage import { Textarea, Label } from \"@svecodocs/kit\"; Your bio Example Bio Update Profile "
- },
- {
- "title": "Navigation",
- "href": "/docs/configuration/navigation",
- "description": "Learn how to customize the navigation in your Svecodocs project.",
- "content": "Navigation is a key component of every site, documenting the structure of your site and providing a clear path for users to navigate through your content. Svecodocs comes with a navigation structure that is designed to be flexible and customizable. Each page in your site should have a corresponding navigation item, and the navigation items should be nested according to their hierarchy. Navigation Structure Main You can think of the main navigation as the root navigation for your site. Links in the main navigation are used to navigation to different sections of your site, such as \"Documentation\", \"API Reference\", and \"Blog\". Anchors Anchors are links that are displayed at the top of the sidebar and typically used to either highlight important information or provide quick access to linked content. Sections Sections are used to group related navigation items together. They are typically used to organize content into different categories, such as \"Components\", \"Configuration\", and \"Utilities\"."
- },
- {
- "title": "Theme",
- "href": "/docs/configuration/theme",
- "description": "Learn how to customize the theme in your Svecodocs project.",
- "content": "The theme determines the branded color scheme for your site. A theme for each of the TailwindCSS colors is provided by the @svecodocs/kit package. Each theme has been designed to present well in both light and dark mode. Using a theme To use a theme, import the theme file into your src/app.css file before importing the @svecodocs/kit/globals.css file. /* @import \"@svecodocs/kit/theme-orange.css\"; */ @import \"@svecodocs/kit/theme-emerald.css\"; @import \"@svecodocs/kit/globals.css\"; It's not recommended to customize the theme to maintain consistency across the UI components that are provided by Svecodocs and align with the provided themes. Available themes | Theme name | Import path | | ---------- | ---------------------------------- | | orange | @svecodocs/kit/theme-orange.css | | green | @svecodocs/kit/theme-green.css | | blue | @svecodocs/kit/theme-blue.css | | purple | @svecodocs/kit/theme-purple.css | | pink | @svecodocs/kit/theme-pink.css | | lime | @svecodocs/kit/theme-lime.css | | yellow | @svecodocs/kit/theme-yellow.css | | cyan | @svecodocs/kit/theme-cyan.css | | teal | @svecodocs/kit/theme-teal.css | | violet | @svecodocs/kit/theme-violet.css | | amber | @svecodocs/kit/theme-amber.css | | red | @svecodocs/kit/theme-red.css | | sky | @svecodocs/kit/theme-sky.css | | emerald | @svecodocs/kit/theme-emerald.css | | fuchsia | @svecodocs/kit/theme-fuchsia.css | | rose | @svecodocs/kit/theme-rose.css | Tailwind Variables Svecodocs uses TailwindCSS to style the UI components and provides a set of Tailwind variables that can be used to style your examples/custom components. Gray We override the TailwindCSS gray color scale to provide our own grays. Brand You can use the brand color to use the brand color of your project."
+ "title": "Full demo",
+ "href": "/docs/demos/full-demo",
+ "description": "Kitchen sink demo of the BarqodeStream component.",
+ "content": " import Demo from '$lib/components/demos/full-demo.svelte'; Modern mobile phones often have a variety of different cameras installed (e.g. front, rear, wide-angle, infrared, desk-view). The one picked by default is sometimes not the best choice. For more fine-grained control, you can select a camera by device constraints or by the device ID. Detected codes are visually highlighted in real-time. In this demo you can use the track function dropdown to change the flavor. By default only QR-codes are detected but a variety of other barcode formats are also supported. You can select one or multiple but the more you select the more expensive scanning becomes. Demo Usage import { BarqodeStream, type BarcodeFormat, type DetectedBarcode } from \"barqode\"; let result = $state(\"\"); let error = $state(\"\"); let selectedConstraints = $state({ facingMode: \"environment\" }); let barcodeFormats: { [key in BarcodeFormat]: boolean; } = $state({ aztec: false, code_128: false, code_39: false, code_93: false, codabar: false, databar: false, databar_expanded: false, databar_limited: false, data_matrix: false, dx_film_edge: false, ean_13: false, ean_8: false, itf: false, maxi_code: false, micro_qr_code: false, pdf417: false, qr_code: true, rm_qr_code: false, upc_a: false, upc_e: false, linear_codes: false, matrix_codes: false, unknown: false, }); // computed value for selected formats let selectedBarcodeFormats: BarcodeFormat[] = $derived( Object.keys(barcodeFormats).filter( (format: string) => barcodeFormats[format] ) as BarcodeFormat[] ); // track function options const trackFunctionOptions = [ { text: \"nothing (default)\", value: undefined }, { text: \"outline\", value: paintOutline }, { text: \"centered text\", value: paintCenterText }, { text: \"bounding box\", value: paintBoundingBox }, ]; let trackFunctionSelected = $state(trackFunctionOptions[1]); // camera constraint options const defaultConstraintOptions: { label: string; constraints: MediaTrackConstraints }[] = [ { label: \"rear camera\", constraints: { facingMode: \"environment\" } }, { label: \"front camera\", constraints: { facingMode: \"user\" } }, ]; let constraintOptions = $state(defaultConstraintOptions); async function onCameraOn() { try { const devices = await navigator.mediaDevices.enumerateDevices(); const videoDevices = devices.filter(({ kind }) => kind === \"videoinput\"); constraintOptions = [ ...defaultConstraintOptions, ...videoDevices.map(({ deviceId, label }) => ({ label: ${label}, constraints: { deviceId }, })), ]; error = \"\"; } catch (e) { console.error(e); } } function onError(err: { name: string; message: string }) { error = [${err.name}]: ; if (err.name === \"NotAllowedError\") { error += \"you need to grant camera access permission\"; } else if (err.name === \"NotFoundError\") { error += \"no camera on this device\"; } else if (err.name === \"NotSupportedError\") { error += \"secure context required (HTTPS, localhost)\"; } else if (err.name === \"NotReadableError\") { error += \"is the camera already in use?\"; } else if (err.name === \"OverconstrainedError\") { error += \"installed cameras are not suitable\"; } else if (err.name === \"StreamApiNotSupportedError\") { error += \"Stream API is not supported in this browser\"; } else { error += err.message; } } function onDetect(detectedCodes: DetectedBarcode[]) { console.log(detectedCodes); result = JSON.stringify(detectedCodes.map((code) => code.rawValue)); } // track functions function paintOutline( detectedCodes: { cornerPoints: { x: number; y: number }[]; boundingBox: DOMRectReadOnly; rawValue: string; format: Exclude; }[], ctx: CanvasRenderingContext2D ) { for (const detectedCode of detectedCodes) { const [firstPoint, ...otherPoints] = detectedCode.cornerPoints; ctx.strokeStyle = \"red\"; ctx.beginPath(); ctx.moveTo(firstPoint.x, firstPoint.y); for (const { x, y } of otherPoints) { ctx.lineTo(x, y); } ctx.lineTo(firstPoint.x, firstPoint.y); ctx.closePath(); ctx.stroke(); } } function paintBoundingBox( detectedCodes: { cornerPoints: { x: number; y: number }[]; boundingBox: DOMRectReadOnly; rawValue: string; format: Exclude; }[], ctx: CanvasRenderingContext2D ) { for (const detectedCode of detectedCodes) { const { boundingBox: { x, y, width, height }, } = detectedCode; ctx.lineWidth = 2; ctx.strokeStyle = \"#007bff\"; ctx.strokeRect(x, y, width, height); } } function paintCenterText( detectedCodes: { cornerPoints: { x: number; y: number }[]; boundingBox: DOMRectReadOnly; rawValue: string; format: Exclude; }[], ctx: CanvasRenderingContext2D ) { for (const detectedCode of detectedCodes) { const { boundingBox, rawValue } = detectedCode; const centerX = boundingBox.x + boundingBox.width / 2; const centerY = boundingBox.y + boundingBox.height / 2; const fontSize = Math.max(12, (50 * boundingBox.width) / ctx.canvas.width); ctx.font = bold ${fontSize}px sans-serif; ctx.textAlign = \"center\"; ctx.lineWidth = 3; ctx.strokeStyle = \"#35495e\"; ctx.strokeText(detectedCode.rawValue, centerX, centerY); ctx.fillStyle = \"#5cb984\"; ctx.fillText(rawValue, centerX, centerY); } } Camera constraints: {#each constraintOptions as option} {option.label} {/each} Track function: {#each trackFunctionOptions as option} {option.text} {/each} Barcode formats: {#each Object.keys(barcodeFormats) as option} {@const barcodeOption = option as BarcodeFormat} {option} {/each} {#if error} {error} {/if} Last result:{result} `"
}
]
diff --git a/docs/velite.config.js b/docs/velite.config.js
index beaa180..613063b 100644
--- a/docs/velite.config.js
+++ b/docs/velite.config.js
@@ -8,7 +8,7 @@ const baseSchema = s.object({
navLabel: s.string().optional(),
raw: s.raw(),
toc: s.toc(),
- section: s.enum(["Overview", "Components", "Configuration", "Utilities"]),
+ section: s.enum(["Overview", "Components", "Demos"]),
});
const docSchema = baseSchema.transform((data) => {
diff --git a/package.json b/package.json
index 479d0b0..5f062f9 100644
--- a/package.json
+++ b/package.json
@@ -24,23 +24,23 @@
"packageManager": "pnpm@9.14.4",
"devDependencies": {
"@changesets/cli": "^2.27.10",
- "@eslint/js": "^9.12.0",
+ "@eslint/js": "^9.16.0",
"@svitejs/changesets-changelog-github-compact": "^1.2.0",
"@types/node": "^22.10.1",
- "@typescript-eslint/eslint-plugin": "^8.10.0",
- "@typescript-eslint/scope-manager": "^8.10.0",
- "@typescript-eslint/utils": "^8.10.0",
- "eslint": "^9.0.0",
+ "@typescript-eslint/eslint-plugin": "^8.17.0",
+ "@typescript-eslint/scope-manager": "^8.17.0",
+ "@typescript-eslint/utils": "^8.17.0",
+ "eslint": "^9.16.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.46.1",
- "globals": "^15.11.0",
- "prettier": "^3.3.3",
+ "globals": "^15.13.0",
+ "prettier": "^3.4.1",
"prettier-plugin-svelte": "^3.3.2",
- "prettier-plugin-tailwindcss": "^0.6.8",
- "svelte": "^5.2.11",
+ "prettier-plugin-tailwindcss": "^0.6.9",
+ "svelte": "^5.4.0",
"svelte-eslint-parser": "^0.43.0",
- "typescript": "^5.6.3",
- "typescript-eslint": "^8.10.0",
+ "typescript": "^5.7.2",
+ "typescript-eslint": "^8.17.0",
"wrangler": "^3.91.0"
}
}
diff --git a/packages/barqode/README.md b/packages/barqode/README.md
index 16c70df..b907ada 100644
--- a/packages/barqode/README.md
+++ b/packages/barqode/README.md
@@ -1,58 +1 @@
-# create-svelte
-
-Everything you need to build a Svelte library, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
-
-Read more about creating a library [in the docs](https://svelte.dev/docs/kit/packaging).
-
-## Creating a project
-
-If you're seeing this, you've probably already done this step. Congrats!
-
-```bash
-# create a new project in the current directory
-npx sv create
-
-# create a new project in my-app
-npx sv create my-app
-```
-
-## Developing
-
-Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
-
-```bash
-npm run dev
-
-# or start the server and open the app in a new browser tab
-npm run dev -- --open
-```
-
-Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
-
-## Building
-
-To build your library:
-
-```bash
-npm run package
-```
-
-To create a production version of your showcase app:
-
-```bash
-npm run build
-```
-
-You can preview the production build with `npm run preview`.
-
-> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
-
-## Publishing
-
-Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
-
-To publish your library to [npm](https://www.npmjs.com):
-
-```bash
-npm publish
-```
+# Barqode
diff --git a/packages/barqode/package.json b/packages/barqode/package.json
index 393e25e..30afe5c 100644
--- a/packages/barqode/package.json
+++ b/packages/barqode/package.json
@@ -38,25 +38,30 @@
"svelte": "^5.0.0"
},
"devDependencies": {
- "@sveltejs/adapter-auto": "^3.0.0",
- "@sveltejs/kit": "^2.0.0",
- "@sveltejs/package": "^2.0.0",
- "@sveltejs/vite-plugin-svelte": "^4.0.0",
- "publint": "^0.2.0",
- "svelte": "^5.0.0",
- "svelte-check": "^4.0.0",
- "typescript": "^5.0.0",
- "vite": "^5.0.11"
+ "@sveltejs/adapter-auto": "^3.3.1",
+ "@sveltejs/kit": "^2.9.0",
+ "@sveltejs/package": "^2.3.7",
+ "@sveltejs/vite-plugin-svelte": "^4.0.2",
+ "publint": "^0.2.12",
+ "svelte": "^5.4.0",
+ "svelte-check": "^4.1.0",
+ "typescript": "^5.7.2",
+ "vite": "^5.4.11"
},
"license": "MIT",
"contributors": [
{
- "name": "Ollie",
+ "name": "Olle",
"url": "https://github.com/ollema"
},
{
"name": "Hunter Johnston",
"url": "https://github.com/huntabyte"
}
- ]
+ ],
+ "dependencies": {
+ "barcode-detector": "^2.3.1",
+ "runed": "^0.16.1",
+ "webrtc-adapter": "^9.0.1"
+ }
}
diff --git a/packages/barqode/src/app.html b/packages/barqode/src/app.html
new file mode 100644
index 0000000..f22aeaa
--- /dev/null
+++ b/packages/barqode/src/app.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ %sveltekit.head%
+
+
+
+
+
diff --git a/packages/barqode/src/lib/components/types.ts b/packages/barqode/src/lib/components/types.ts
new file mode 100644
index 0000000..9e65526
--- /dev/null
+++ b/packages/barqode/src/lib/components/types.ts
@@ -0,0 +1,125 @@
+import type { BarcodeFormat, DetectedBarcode, Point2D } from "barcode-detector/pure";
+import type { Snippet } from "svelte";
+import type { HTMLInputAttributes } from "svelte/elements";
+
+export type { DetectedBarcode, BarcodeFormat, Point2D };
+
+export type DropzoneProps = {
+ /**
+ * The formats of the barcodes to detect.
+ *
+ * @default ['qr_code']
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API#supported_barcode_formats
+ */
+ formats?: BarcodeFormat[];
+
+ /**
+ * A callback function called when the user drops an image file.
+ *
+ * @param codes - The detected barcodes.
+ */
+ onDetect?: (detectedCodes: DetectedBarcode[]) => void;
+
+ /**
+ * A callback function called when the user drags an image file over the drop zone.
+ *
+ * @param isDraggingOver - Whether the user is dragging an image file over the drop zone.
+ */
+ onDragover?: (isDraggingOver: boolean) => void;
+
+ /**
+ * A callback function called when the user drags an image file out of the drop zone.
+ *
+ * @param error - The error that occurred.
+ */
+ onError?: (error: Error) => void;
+
+ /**
+ * Optional prop for content to be overlayed on top of the drop zone.
+ */
+ children?: Snippet;
+} & HTMLInputAttributes;
+
+export type StreamProps = {
+ /**
+ * The MediaTrackConstraints specifying the desired media types and their constraints.
+ *
+ * @default { video: { facingMode: 'environment' } }
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints
+ */
+ constraints?: MediaTrackConstraints;
+
+ /**
+ * The formats of the barcodes to detect.
+ *
+ * @default ['qr_code']
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API#supported_barcode_formats
+ */
+ formats?: BarcodeFormat[];
+
+ /**
+ * Whether the camera stream is paused. Bindable.
+ *
+ * @default false
+ *
+ * Can also be set to `true` to pause the camera stream, which is useful when you want to show
+ * some microinteraction after successful scans. When the you set it to `false`, the camera
+ * stream will be restarted and the `onCameraOn` callback function will be triggered again.
+ */
+ paused?: boolean;
+
+ /**
+ * Whether the torch is enabled.
+ *
+ * @default false
+ *
+ * Not consistently supported across devices.
+ */
+ torch?: boolean;
+
+ /**
+ * Function to visually highlight the detected barcodes.
+ *
+ * @param codes - The detected barcodes with their adjusted corner points.
+ * @param ctx - The canvas rendering context.
+ */
+ track?: (detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) => void;
+
+ /**
+ * A callback function called when the camera stream is turned on.
+ *
+ * @param capabilities - The MediaTrackCapabilities of the camera stream.
+ */
+ onCameraOn?: (capabilities: MediaTrackCapabilities) => void;
+
+ /**
+ * A callback function called when the camera stream encounters an error.
+ *
+ * @param error - The error that occurred.
+ */
+ onError?: (error: Error) => void;
+
+ /**
+ * A callback function called when the camera stream is turned off.
+ */
+ onCameraOff?: () => void;
+
+ /**
+ * A callback function called a detection is made.
+ *
+ * Note: if you scan the same barcode code multiple times in a row, onDetect is still called once.
+ * When you hold a barcode in the camera, frames are actually decoded multiple times a second
+ * but you don't want to be flooded with onDetect callbacks that often.
+ * That's why the last decoded barcode is always cached and only new results are propagated.
+ * However changing the value of `paused` resets this internal cache.
+ */
+ onDetect?: (detectedCodes: DetectedBarcode[]) => void;
+
+ /**
+ * Optional prop for content to be overlayed on top of the camera stream.
+ */
+ children?: Snippet;
+};
diff --git a/packages/barqode/src/lib/index.ts b/packages/barqode/src/lib/index.ts
index 47d3c46..cde52bc 100644
--- a/packages/barqode/src/lib/index.ts
+++ b/packages/barqode/src/lib/index.ts
@@ -1 +1,9 @@
-// Reexport your entry components here
+export { default as BarqodeDropzone } from "./components/barqode-dropzone.svelte";
+export { default as BarqodeStream } from "./components/barqode-stream.svelte";
+
+export type {
+ DetectedBarcode,
+ BarcodeFormat,
+ DropzoneProps,
+ StreamProps,
+} from "./components/types.js";
diff --git a/packages/barqode/src/lib/internal/callforth.ts b/packages/barqode/src/lib/internal/callforth.ts
new file mode 100644
index 0000000..79f9d23
--- /dev/null
+++ b/packages/barqode/src/lib/internal/callforth.ts
@@ -0,0 +1,49 @@
+// based on vue-qrcode-reader by Niklas Gruhn
+// see https://github.com/gruhn/vue-qrcode-reader
+
+/**
+ * Creates a Promise that resolves when a specified event is triggered on the given EventTarget.
+ *
+ * @param eventTarget - The target to listen for events on.
+ * @param successEvent - The name of the event that will resolve the Promise.
+ * @param errorEvent - The name of the event that will reject the Promise. Defaults to 'error'.
+ *
+ * @returns A Promise that resolves with the event object when the successEvent is
+ * triggered, or rejects with the event object when the errorEvent is triggered.
+ */
+export function eventOn(
+ eventTarget: EventTarget,
+ successEvent: string,
+ errorEvent = "error"
+): Promise {
+ let $resolve: (value: Event) => void;
+ let $reject: (reason?: Event) => void;
+
+ const promise = new Promise(
+ (resolve: (value: Event) => void, reject: (reason?: Event) => void) => {
+ $resolve = resolve;
+ $reject = reject;
+
+ eventTarget.addEventListener(successEvent, $resolve);
+ eventTarget.addEventListener(errorEvent, $reject);
+ }
+ );
+
+ promise.finally(() => {
+ eventTarget.removeEventListener(successEvent, $resolve);
+ eventTarget.removeEventListener(errorEvent, $reject);
+ });
+
+ return promise;
+}
+
+/**
+ * Creates a promise that resolves after a specified number of milliseconds.
+ *
+ * @param ms - The number of milliseconds to wait before the promise resolves.
+ *
+ * @returns A promise that resolves after the specified delay.
+ */
+export function sleep(ms: number) {
+ return new Promise((resolve: (value: unknown) => void) => setTimeout(resolve, ms));
+}
diff --git a/packages/barqode/src/lib/internal/camera.ts b/packages/barqode/src/lib/internal/camera.ts
new file mode 100644
index 0000000..953abd2
--- /dev/null
+++ b/packages/barqode/src/lib/internal/camera.ts
@@ -0,0 +1,295 @@
+// based on vue-qrcode-reader by Niklas Gruhn
+// see https://github.com/gruhn/vue-qrcode-reader
+
+import {
+ StreamApiNotSupportedError,
+ InsecureContextError,
+ StreamLoadTimeoutError,
+} from "./errors.js";
+import { eventOn, sleep } from "./callforth.js";
+import shimGetUserMedia from "./shim-get-user-media.js";
+import { assertNever } from "./util.js";
+
+type StartTaskResult = {
+ type: "start";
+ data: {
+ video: HTMLVideoElement;
+ stream: MediaStream;
+ capabilities: Partial;
+ constraints: MediaTrackConstraints;
+ isTorchOn: boolean;
+ };
+};
+
+type StopTaskResult = {
+ type: "stop";
+ data: null;
+};
+
+type FailedTask = {
+ type: "failed";
+ error: Error;
+};
+
+type TaskResult = StartTaskResult | StopTaskResult | FailedTask;
+
+let taskQueue: Promise = Promise.resolve({ type: "stop", data: null });
+
+type CreateObjectURLCompat = (obj: MediaSource | Blob | MediaStream) => string;
+
+/**
+ * Starts the camera with the given constraints and attaches the stream to the provided video element.
+ *
+ * @param video - The HTML video element to which the camera stream will be attached.
+ * @param constraints - The media track constraints to apply when starting the camera.
+ * @param torch - A boolean indicating whether the torch (flashlight) should be enabled if supported.
+ *
+ * @returns A promise that resolves to a `StartTaskResult` object containing details about the started camera stream.
+ *
+ * @throws InsecureContextError - If the page is not loaded in a secure context (HTTPS).
+ * @throws StreamApiNotSupportedError - If the Stream API is not supported by the browser.
+ * @throws StreamLoadTimeoutError - If the video element fails to load the camera stream within a 6-second timeout.
+ */
+async function runStartTask(
+ video: HTMLVideoElement,
+ constraints: MediaTrackConstraints,
+ torch: boolean
+): Promise {
+ console.debug("[barqode] starting camera with constraints: ", JSON.stringify(constraints));
+
+ // at least in Chrome `navigator.mediaDevices` is undefined when the page is
+ // loaded using HTTP rather than HTTPS. thus `STREAM_API_NOT_SUPPORTED` is
+ // initialized with `false` although the API might actually be supported.
+ // so although `getUserMedia` already should have a built-in mechanism to
+ // detect insecure context (by throwing `NotAllowedError`), we have to do a
+ // manual check before even calling `getUserMedia`.
+ if (window.isSecureContext !== true) {
+ throw new InsecureContextError();
+ }
+
+ if (navigator?.mediaDevices?.getUserMedia === undefined) {
+ throw new StreamApiNotSupportedError();
+ }
+
+ // this is a browser API only shim. tt patches the global window object which
+ // is not available during SSR. So we lazily apply this shim at runtime.
+ shimGetUserMedia();
+
+ console.debug("[barqode] calling getUserMedia");
+ const stream = await navigator.mediaDevices.getUserMedia({
+ audio: false,
+ video: constraints,
+ });
+
+ if (video.srcObject !== undefined) {
+ video.srcObject = stream;
+ } else if (video.mozSrcObject !== undefined) {
+ video.mozSrcObject = stream;
+ } else if (window.URL.createObjectURL) {
+ video.src = (window.URL.createObjectURL as CreateObjectURLCompat)(stream);
+ } else if (window.webkitURL) {
+ video.src = (window.webkitURL.createObjectURL as CreateObjectURLCompat)(stream);
+ } else {
+ video.src = stream.id;
+ }
+
+ // in the WeChat browser on iOS, 'loadeddata' event won't get fired unless video is explicitly triggered by play()
+ video.play();
+
+ console.debug("[barqode] waiting for video element to load");
+ await Promise.race([
+ eventOn(video, "loadeddata"),
+
+ // on iOS devices in PWA mode, BarqodeStream works initially, but after killing and restarting the PWA,
+ // all video elements fail to load camera streams and never emit the `loadeddata` event.
+ // looks like this is related to a WebKit issue (see #298). no workarounds at the moment.
+ // to at least detect this situation, we throw an error if the event has not been emitted after a 6 second timeout.
+ sleep(6_000).then(() => {
+ throw new StreamLoadTimeoutError();
+ }),
+ ]);
+ console.debug("[barqode] video element loaded");
+
+ // according to: https://oberhofer.co/mediastreamtrack-and-its-capabilities/#queryingcapabilities
+ // on some devices, getCapabilities only returns a non-empty object after some delay.
+ // there is no appropriate event so we have to add a constant timeout
+ await sleep(500);
+
+ const [track] = stream.getVideoTracks();
+
+ const capabilities: Partial = track?.getCapabilities?.() ?? {};
+
+ let isTorchOn = false;
+ // @ts-expect-error torch is not in the MediaTrackConstraints type but it should be?
+ if (torch && capabilities.torch) {
+ // @ts-expect-error torch is not in the MediaTrackConstraints type but it should be?
+ await track.applyConstraints({ advanced: [{ torch: true }] });
+ isTorchOn = true;
+ }
+
+ console.debug("[barqode] camera ready");
+
+ return {
+ type: "start",
+ data: {
+ video,
+ stream,
+ capabilities,
+ constraints,
+ isTorchOn,
+ },
+ };
+}
+
+/**
+ * Starts the camera with the given video element and settings.
+ *
+ * @param video - The HTML video element to which the camera stream will be attached.
+ * @param options.constraints - The media track constraints to apply when starting the camera.
+ * @param options.torch - A boolean indicating whether the torch (flashlight) should be enabled if supported.
+ * @param options.restart - A boolean indicating whether to restart the camera even if no settings changed. Defaults to false.
+ *
+ * @returns A promise that resolves to a `MediaTrackCapabilities` object containing the camera capabilities.
+ *
+ * @throws Error - If something goes wrong with the camera task queue.
+ */
+export async function start(
+ video: HTMLVideoElement,
+ {
+ constraints,
+ torch,
+ restart = false,
+ }: {
+ constraints: MediaTrackConstraints;
+ torch: boolean;
+ restart?: boolean;
+ }
+): Promise> {
+ // update the task queue synchronously
+ taskQueue = taskQueue
+ .then((prevTaskResult) => {
+ if (prevTaskResult.type === "start") {
+ // previous task is a start task
+ // we'll check if we can reuse the previous result
+ const {
+ data: {
+ video: prevVideo,
+ stream: prevStream,
+ constraints: prevConstraints,
+ isTorchOn: prevIsTorchOn,
+ },
+ } = prevTaskResult;
+ // TODO: should we keep this object comparison
+ // this code only checks object sameness not equality
+ // deep comparison requires snapshots and value by value check
+ // which seem too much
+ if (
+ !restart &&
+ video === prevVideo &&
+ constraints === prevConstraints &&
+ torch === prevIsTorchOn
+ ) {
+ // things didn't change, reuse the previous result
+ return prevTaskResult;
+ }
+ // something changed, restart (stop then start)
+ return runStopTask(prevVideo, prevStream, prevIsTorchOn).then(() =>
+ runStartTask(video, constraints, torch)
+ );
+ } else if (prevTaskResult.type === "stop" || prevTaskResult.type === "failed") {
+ // previous task is a stop/error task
+ // we can safely start
+ return runStartTask(video, constraints, torch);
+ }
+
+ assertNever(prevTaskResult);
+ })
+ .catch((error: Error) => {
+ console.debug(`[barqode] starting camera failed with "${error}"`);
+ return { type: "failed", error };
+ });
+
+ // await the task queue asynchronously
+ const taskResult = await taskQueue;
+
+ if (taskResult.type === "stop") {
+ // we just synchronously updated the task above
+ // to make the latest task a start task
+ // so this case shouldn't happen
+ throw new Error("Something went wrong with the camera task queue (start task).");
+ } else if (taskResult.type === "failed") {
+ throw taskResult.error;
+ } else if (taskResult.type === "start") {
+ // return the data we want
+ return taskResult.data.capabilities;
+ }
+
+ assertNever(taskResult);
+}
+
+/**
+ * Stops the camera stream and cleans up associated resources.
+ *
+ * @param video - The HTML video element displaying the camera stream.
+ * @param stream - The MediaStream object representing the active camera stream.
+ * @param isTorchOn - A boolean indicating whether the torch is currently enabled.
+ *
+ * @returns A promise that resolves to a `StopTaskResult` when the camera is fully stopped.
+ */
+async function runStopTask(
+ video: HTMLVideoElement,
+ stream: MediaStream,
+ isTorchOn: boolean
+): Promise {
+ console.debug("[barqode] stopping camera");
+
+ video.src = "";
+ video.srcObject = null;
+ video.load();
+
+ // wait for load() to emit error
+ // because src and srcObject are empty
+ await eventOn(video, "error");
+
+ for (const track of stream.getTracks()) {
+ // @ts-expect-error torch is not in the MediaTrackConstraints type but it should be?
+ isTorchOn ?? (await track.applyConstraints({ advanced: [{ torch: false }] }));
+ stream.removeTrack(track);
+ track.stop();
+ }
+
+ return {
+ type: "stop",
+ data: null,
+ };
+}
+
+/**
+ * Stops any active camera stream and ensures proper cleanup.
+ *
+ * @returns A promise that resolves when the camera is fully stopped.
+ *
+ * @throws Error - If something goes wrong with the camera task queue.
+ */
+export async function stop() {
+ // update the task queue synchronously
+ taskQueue = taskQueue.then((prevTaskResult) => {
+ if (prevTaskResult.type === "stop" || prevTaskResult.type === "failed") {
+ // previous task is a stop task
+ // no need to stop again
+ return prevTaskResult;
+ }
+ const {
+ data: { video, stream, isTorchOn },
+ } = prevTaskResult;
+ return runStopTask(video, stream, isTorchOn);
+ });
+ // await the task queue asynchronously
+ const taskResult = await taskQueue;
+ if (taskResult.type === "start") {
+ // we just synchronously updated the task above
+ // to make the latest task a stop task
+ // so this case shouldn't happen
+ throw new Error("Something went wrong with the camera task queue (stop task).");
+ }
+}
diff --git a/packages/barqode/src/lib/internal/errors.ts b/packages/barqode/src/lib/internal/errors.ts
new file mode 100644
index 0000000..e047254
--- /dev/null
+++ b/packages/barqode/src/lib/internal/errors.ts
@@ -0,0 +1,35 @@
+export class DropImageFetchError extends Error {
+ constructor() {
+ super("can't process cross-origin image");
+
+ this.name = "DropImageFetchError";
+ }
+}
+
+export class StreamApiNotSupportedError extends Error {
+ constructor() {
+ super("this browser has no Stream API support");
+
+ this.name = "StreamApiNotSupportedError";
+ }
+}
+
+export class InsecureContextError extends Error {
+ constructor() {
+ super(
+ "camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP."
+ );
+
+ this.name = "InsecureContextError";
+ }
+}
+
+export class StreamLoadTimeoutError extends Error {
+ constructor() {
+ super(
+ "loading camera stream timed out after 6 seconds. If you are on iOS in PWA mode, this is a known issue (see https://github.com/gruhn/vue-qrcode-reader/issues/298)"
+ );
+
+ this.name = "StreamLoadTimeoutError";
+ }
+}
diff --git a/packages/barqode/src/lib/internal/global.d.ts b/packages/barqode/src/lib/internal/global.d.ts
new file mode 100644
index 0000000..781f628
--- /dev/null
+++ b/packages/barqode/src/lib/internal/global.d.ts
@@ -0,0 +1,5 @@
+///
+
+interface HTMLVideoElement {
+ mozSrcObject?: HTMLVideoElement["srcObject"];
+}
diff --git a/packages/barqode/src/lib/internal/scanner.ts b/packages/barqode/src/lib/internal/scanner.ts
new file mode 100644
index 0000000..a4b5cec
--- /dev/null
+++ b/packages/barqode/src/lib/internal/scanner.ts
@@ -0,0 +1,223 @@
+// based on vue-qrcode-reader by Niklas Gruhn
+// see https://github.com/gruhn/vue-qrcode-reader
+
+import { type DetectedBarcode, type BarcodeFormat, BarcodeDetector } from "barcode-detector/pure";
+import { eventOn } from "./callforth.js";
+import { DropImageFetchError } from "./errors.js";
+
+declare global {
+ interface Window {
+ BarcodeDetector?: typeof BarcodeDetector;
+ }
+}
+
+/**
+ * Singleton `BarcodeDetector` instance used by `BarqodeStream`. This is firstly to avoid
+ * the overhead of creating a new instances for scanning each frame. And secondly, the
+ * instances can seamlessly be replaced in the middle of the scanning process, if the
+ * `formats` prop of `BarqodeStream` is changed.
+ *
+ * This instance is not used by `BarqodeCapture` and `BarqodeDropzone`, because it may not
+ * have the right `formats` configured. For these components we create one-off `BarcodeDetector`
+ * instances because it does not happen so frequently anyway (see: `processFile`/`processUrl`).
+ */
+let barcodeDetector: BarcodeDetector;
+
+/**
+ * Constructs a `BarcodeDetector` instance, given a list of targeted barcode formats.
+ * Preferably, we want to use the native `BarcodeDetector` implementation if supported.
+ * Otherwise, we fall back to the polyfill implementation.
+ *
+ * Note, that we can't just monkey patch the polyfill on load, i.e.
+ *
+ * window.BarcodeDetector ??= BarcodeDetector
+ *
+ * for two reasons. Firstly, this is not SSR compatible, because `window` is not available
+ * during SSR. Secondly, even if the native implementation is available, we still might
+ * want to use the polyfill. For example, if the native implementation only supports the
+ * format `"qr_code"` but the user wants to scan `["qr_code", "aztec"]` (see #450).
+ */
+async function createBarcodeDetector(formats: BarcodeFormat[]): Promise {
+ if (window.BarcodeDetector === undefined) {
+ console.debug("[barqode] native BarcodeDetector not supported. Will use polyfill.");
+ return new BarcodeDetector({ formats });
+ }
+
+ const allSupportedFormats = await window.BarcodeDetector.getSupportedFormats();
+ const unsupportedFormats = formats.filter((format) => !allSupportedFormats.includes(format));
+
+ if (unsupportedFormats.length > 0) {
+ console.debug(
+ `[barqode] native BarcodeDetector does not support formats ${JSON.stringify(unsupportedFormats)}. Will use polyfill.`
+ );
+ return new BarcodeDetector({ formats });
+ }
+
+ console.debug("[barqode] will use native BarcodeDetector.");
+ return new window.BarcodeDetector({ formats });
+}
+
+/**
+ * Update the set of targeted barcode formats. In particular, this function
+ * can be called during scanning and the camera stream doesn't have to be
+ * interrupted.
+ */
+export async function setScanningFormats(formats: BarcodeFormat[]) {
+ barcodeDetector = await createBarcodeDetector(formats);
+}
+
+type ScanHandler = (_: DetectedBarcode[]) => void;
+
+type KeepScanningOptions = {
+ detectHandler: ScanHandler;
+ locateHandler: ScanHandler;
+ minDelay: number;
+ formats: BarcodeFormat[];
+};
+
+/**
+ * Continuously extracts frames from camera stream and tries to read
+ * potentially pictured QR codes.
+ */
+export async function keepScanning(
+ videoElement: HTMLVideoElement,
+ { detectHandler, locateHandler, minDelay, formats }: KeepScanningOptions
+) {
+ console.debug("[barqode] start scanning");
+ await setScanningFormats(formats);
+
+ const processFrame =
+ (state: { lastScanned: number; contentBefore: string[]; lastScanHadContent: boolean }) =>
+ async (timeNow: number) => {
+ if (videoElement.readyState === 0) {
+ console.debug("[barqode] stop scanning: video element readyState is 0");
+ } else {
+ const { lastScanned, contentBefore, lastScanHadContent } = state;
+
+ // Scanning is expensive and we don't need to scan camera frames with
+ // the maximum possible frequency. In particular when visual tracking
+ // is disabled. So we skip scanning a frame if `minDelay` has not passed
+ // yet. Notice that this approach is different from doing a `setTimeout`
+ // after each scan. With `setTimeout`, delay and scanning are sequential:
+ //
+ // |-- scan --|---- minDelay ----|-- scan --|---- minDelay ----|
+ //
+ // Instead we do it concurrently:
+ //
+ // |---- minDelay ----|---- minDelay ----|---- minDelay ----|
+ // |-- scan --| |-- scan --| |-- scan --|
+ //
+ // Let's say `minDelay` is 40ms, then we scan every 40ms as long as
+ // scanning itself does not take more than 40ms. In particular when
+ // visual tracking is enabled, that means we can repaint the tracking
+ // canvas every 40ms. So we paint
+ //
+ // 1000ms / 40ms = 25fps (frames per second)
+ //
+ // 24fps is the minimum frame-rate that is perceived as a continuous
+ // animation. We target 25fps just because 24 doesn't divide 1000ms
+ // evenly.
+ if (timeNow - lastScanned < minDelay) {
+ window.requestAnimationFrame(processFrame(state));
+ } else {
+ const detectedCodes = await barcodeDetector.detect(videoElement);
+
+ // Only emit a detect event, if at least one of the detected codes has
+ // not been seen before. Otherwise we spam tons of detect events while
+ // a QR code is in view of the camera. To avoid that we store the previous
+ // detection in `contentBefore`.
+ //
+ // Implicitly we also don't emit a `detect` event if `detectedCodes` is an
+ // empty array.
+ const anyNewCodesDetected = detectedCodes.some((code) => {
+ return !contentBefore.includes(code.rawValue);
+ });
+
+ if (anyNewCodesDetected) {
+ detectHandler(detectedCodes);
+ }
+
+ const currentScanHasContent = detectedCodes.length > 0;
+
+ // In contrast to the QR code content, the location changes all the time.
+ // So we call the locate handler on every detection to repaint the tracking
+ // canvas.
+ if (currentScanHasContent) {
+ locateHandler(detectedCodes);
+ }
+
+ // Additionally, we need to clear the tracking canvas once when no QR code
+ // is in view of the camera anymore. Technically this can be merged with the
+ // previous if-statement but this way it's more explicit.
+ if (!currentScanHasContent && lastScanHadContent) {
+ locateHandler(detectedCodes);
+ }
+
+ const newState = {
+ lastScanned: timeNow,
+ lastScanHadContent: currentScanHasContent,
+
+ // It can happen that a QR code is constantly in view of the camera but
+ // maybe a scanned frame is a bit blurry and we detect nothing but in the
+ // next frame we detect the code again. We also want to avoid emitting
+ // a `detect` event in such a case. So we don't reset `contentBefore`,
+ // if we detect nothing, only if we detect something new.
+ contentBefore: anyNewCodesDetected
+ ? detectedCodes.map((code) => code.rawValue)
+ : contentBefore,
+ };
+
+ window.requestAnimationFrame(processFrame(newState));
+ }
+ }
+ };
+
+ processFrame({
+ lastScanned: performance.now(),
+ contentBefore: [],
+ lastScanHadContent: false,
+ })(performance.now());
+}
+
+async function imageElementFromUrl(url: string) {
+ if (url.startsWith("http") && url.includes(location.host) === false) {
+ throw new DropImageFetchError();
+ }
+
+ const image = document.createElement("img");
+ image.src = url;
+
+ await eventOn(image, "load");
+
+ return image;
+}
+
+export async function processFile(
+ file: File,
+ formats: BarcodeFormat[] = ["qr_code"]
+): Promise {
+ // To scan files/urls we use one-off `BarcodeDetector` instances,
+ // since we don't scan as often as camera frames. Note, that we
+ // always use the polyfill. This is because (at the time of writing)
+ // some browser/OS combinations don't support `Blob`/`File` inputs
+ // into the `detect` function.
+ const barcodeDetector = new BarcodeDetector({ formats });
+
+ return await barcodeDetector.detect(file);
+}
+
+export async function processUrl(
+ url: string,
+ formats: BarcodeFormat[] = ["qr_code"]
+): Promise {
+ // To scan files/urls we use one-off `BarcodeDetector` instances,
+ // since we don't scan as often as camera frames. Note, that we
+ // always use the polyfill. This is because (at the time of writing)
+ // some browser/OS combinations don't support `Blob`/`File` inputs
+ // into the `detect` function.
+ const barcodeDetector = new BarcodeDetector({ formats });
+
+ const image = await imageElementFromUrl(url);
+
+ return await barcodeDetector.detect(image);
+}
diff --git a/packages/barqode/src/lib/internal/shim-get-user-media.ts b/packages/barqode/src/lib/internal/shim-get-user-media.ts
new file mode 100644
index 0000000..cb6829d
--- /dev/null
+++ b/packages/barqode/src/lib/internal/shim-get-user-media.ts
@@ -0,0 +1,32 @@
+// based on vue-qrcode-reader by Niklas Gruhn
+// see https://github.com/gruhn/vue-qrcode-reader
+
+// @ts-expect-error no types available
+import { shimGetUserMedia as chromeShim } from "webrtc-adapter/dist/chrome/getusermedia";
+// @ts-expect-error no types available
+import { shimGetUserMedia as firefoxShim } from "webrtc-adapter/dist/firefox/getusermedia";
+// @ts-expect-error no types available
+import { shimGetUserMedia as safariShim } from "webrtc-adapter/dist/safari/safari_shim";
+// @ts-expect-error no types available
+import { detectBrowser } from "webrtc-adapter/dist/utils";
+
+import { StreamApiNotSupportedError } from "./errors.js";
+import { idempotent } from "./util.js";
+
+export default idempotent(() => {
+ const browserDetails = detectBrowser(window);
+
+ switch (browserDetails.browser) {
+ case "chrome":
+ chromeShim(window, browserDetails);
+ break;
+ case "firefox":
+ firefoxShim(window, browserDetails);
+ break;
+ case "safari":
+ safariShim(window, browserDetails);
+ break;
+ default:
+ throw new StreamApiNotSupportedError();
+ }
+});
diff --git a/packages/barqode/src/lib/internal/util.ts b/packages/barqode/src/lib/internal/util.ts
new file mode 100644
index 0000000..295790e
--- /dev/null
+++ b/packages/barqode/src/lib/internal/util.ts
@@ -0,0 +1,38 @@
+// based on vue-qrcode-reader by Niklas Gruhn
+// see https://github.com/gruhn/vue-qrcode-reader
+
+/**
+ * Creates a function that ensures the given action is only executed once.
+ * Subsequent calls to the returned function will return the result of the first call.
+ *
+ * @template T - The return type of the action.
+ * @template U - The type of the arguments passed to the action.
+ * @param {function(U[]): T} action - The action to be executed idempotently.
+ * @returns {function(...U[]): T | undefined} A function that, when called, will execute the action only once and return the result.
+ */
+export const idempotent = (action: (x: U[]) => T) => {
+ let called = false;
+ let result: T | undefined = undefined;
+
+ return (...args: U[]) => {
+ if (called) {
+ return result;
+ } else {
+ result = action(args);
+ called = true;
+
+ return result;
+ }
+ };
+};
+
+/**
+ * Asserts that a given value is of type `never`, indicating that this code path should be unreachable.
+ * Throws an error if called, signaling a logic error in the code.
+ *
+ * @param _witness - The value that should be of type `never`.
+ * @throws {Error} Always throws an error to indicate unreachable code.
+ */
+export function assertNever(_witness: never): never {
+ throw new Error("this code should be unreachable");
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5b9b482..d21c88b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12,7 +12,7 @@ importers:
specifier: ^2.27.10
version: 2.27.10
'@eslint/js':
- specifier: ^9.12.0
+ specifier: ^9.16.0
version: 9.16.0
'@svitejs/changesets-changelog-github-compact':
specifier: ^1.2.0
@@ -21,16 +21,16 @@ importers:
specifier: ^22.10.1
version: 22.10.1
'@typescript-eslint/eslint-plugin':
- specifier: ^8.10.0
+ specifier: ^8.17.0
version: 8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.7.2))(eslint@9.16.0(jiti@2.4.1))(typescript@5.7.2)
'@typescript-eslint/scope-manager':
- specifier: ^8.10.0
+ specifier: ^8.17.0
version: 8.17.0
'@typescript-eslint/utils':
- specifier: ^8.10.0
+ specifier: ^8.17.0
version: 8.17.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.7.2)
eslint:
- specifier: ^9.0.0
+ specifier: ^9.16.0
version: 9.16.0(jiti@2.4.1)
eslint-config-prettier:
specifier: ^9.1.0
@@ -39,50 +39,53 @@ importers:
specifier: ^2.46.1
version: 2.46.1(eslint@9.16.0(jiti@2.4.1))(svelte@5.4.0)
globals:
- specifier: ^15.11.0
+ specifier: ^15.13.0
version: 15.13.0
prettier:
- specifier: ^3.3.3
+ specifier: ^3.4.1
version: 3.4.1
prettier-plugin-svelte:
specifier: ^3.3.2
version: 3.3.2(prettier@3.4.1)(svelte@5.4.0)
prettier-plugin-tailwindcss:
- specifier: ^0.6.8
+ specifier: ^0.6.9
version: 0.6.9(prettier-plugin-svelte@3.3.2(prettier@3.4.1)(svelte@5.4.0))(prettier@3.4.1)
svelte:
- specifier: ^5.2.11
+ specifier: ^5.4.0
version: 5.4.0
svelte-eslint-parser:
specifier: ^0.43.0
version: 0.43.0(svelte@5.4.0)
typescript:
- specifier: ^5.6.3
+ specifier: ^5.7.2
version: 5.7.2
typescript-eslint:
- specifier: ^8.10.0
+ specifier: ^8.17.0
version: 8.17.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.7.2)
wrangler:
specifier: ^3.91.0
- version: 3.91.0(@cloudflare/workers-types@4.20241127.0)
+ version: 3.91.0(@cloudflare/workers-types@4.20241202.0)
docs:
devDependencies:
'@svecodocs/kit':
- specifier: ^0.0.5
- version: 0.0.5(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(bits-ui@1.0.0-next.65(svelte@5.4.0))(svelte@5.4.0)(tailwindcss@4.0.0-beta.4)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))
+ specifier: ^0.1.1
+ version: 0.1.1(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(bits-ui@1.0.0-next.65(svelte@5.4.0))(svelte@5.4.0)(tailwindcss@4.0.0-beta.4)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))
'@sveltejs/adapter-cloudflare':
specifier: ^4.8.0
- version: 4.8.0(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(wrangler@3.91.0(@cloudflare/workers-types@4.20241127.0))
+ version: 4.8.0(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(wrangler@3.91.0(@cloudflare/workers-types@4.20241202.0))
'@sveltejs/kit':
- specifier: ^2.0.0
+ specifier: ^2.9.0
version: 2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))
'@sveltejs/vite-plugin-svelte':
- specifier: ^4.0.0
+ specifier: ^4.0.2
version: 4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))
'@tailwindcss/vite':
specifier: 4.0.0-beta.4
version: 4.0.0-beta.4(postcss-load-config@3.1.4(postcss@8.4.49))(postcss@8.4.49)(svelte@5.4.0)(typescript@5.7.2)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))
+ barqode:
+ specifier: workspace:^
+ version: link:../packages/barqode
mdsx:
specifier: ^0.0.6
version: 0.0.6(svelte@5.4.0)
@@ -90,10 +93,10 @@ importers:
specifier: ^3.0.0
version: 3.0.0(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))
svelte:
- specifier: ^5.2.11
+ specifier: ^5.4.0
version: 5.4.0
svelte-check:
- specifier: ^4.0.0
+ specifier: ^4.1.0
version: 4.1.0(svelte@5.4.0)(typescript@5.7.2)
svelte-preprocess:
specifier: ^6.0.3
@@ -102,43 +105,53 @@ importers:
specifier: 4.0.0-beta.4
version: 4.0.0-beta.4
typescript:
- specifier: ^5.0.0
+ specifier: ^5.7.2
version: 5.7.2
velite:
specifier: ^0.2.1
version: 0.2.1(acorn@8.14.0)
vite:
- specifier: ^5.0.11
+ specifier: ^5.4.11
version: 5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)
packages/barqode:
+ dependencies:
+ barcode-detector:
+ specifier: ^2.3.1
+ version: 2.3.1
+ runed:
+ specifier: ^0.16.1
+ version: 0.16.1(svelte@5.4.0)
+ webrtc-adapter:
+ specifier: ^9.0.1
+ version: 9.0.1
devDependencies:
'@sveltejs/adapter-auto':
- specifier: ^3.0.0
+ specifier: ^3.3.1
version: 3.3.1(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))
'@sveltejs/kit':
- specifier: ^2.0.0
+ specifier: ^2.9.0
version: 2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))
'@sveltejs/package':
- specifier: ^2.0.0
+ specifier: ^2.3.7
version: 2.3.7(svelte@5.4.0)(typescript@5.7.2)
'@sveltejs/vite-plugin-svelte':
- specifier: ^4.0.0
+ specifier: ^4.0.2
version: 4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))
publint:
- specifier: ^0.2.0
+ specifier: ^0.2.12
version: 0.2.12
svelte:
- specifier: ^5.0.0
+ specifier: ^5.4.0
version: 5.4.0
svelte-check:
- specifier: ^4.0.0
+ specifier: ^4.1.0
version: 4.1.0(svelte@5.4.0)(typescript@5.7.2)
typescript:
- specifier: ^5.0.0
+ specifier: ^5.7.2
version: 5.7.2
vite:
- specifier: ^5.0.11
+ specifier: ^5.4.11
version: 5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)
packages:
@@ -247,8 +260,8 @@ packages:
resolution: {integrity: sha512-eP6Ir45uPbKnpADVzUCtkRUYxYxjB1Ew6n/whTJvHu8H4m93USHAceCMm736VBZdlxuhXXUjEP3fCUxKPn+cfw==}
engines: {node: '>=16.7.0'}
- '@cloudflare/workers-types@4.20241127.0':
- resolution: {integrity: sha512-UqlvtqV8eI0CdPR7nxlbVlE52+lcjHvGdbYXEPwisy23+39RsFV7OOy0da0moJAhqnL2OhDmWTOaKdsVcPHiJQ==}
+ '@cloudflare/workers-types@4.20241202.0':
+ resolution: {integrity: sha512-ts4JD6Wih62SDmlc+OcnN1Db/DgEBcl+BUpJr7ht7pgWP81PCLyPcomgDXIeAqt2NLiOIOMMkYQZ1ZtWDo3/8A==}
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
@@ -1009,8 +1022,8 @@ packages:
'@shikijs/vscode-textmate@9.3.0':
resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==}
- '@svecodocs/kit@0.0.5':
- resolution: {integrity: sha512-DrwREJDQBDZTm6UOHg27CqpObFMUBcgYH9P94PSVsdJtaYOAv5GF33cjxwxvwNgczKgcpeZs4xWu3/HdFI5JKA==}
+ '@svecodocs/kit@0.1.1':
+ resolution: {integrity: sha512-VOaN+twbkStWo67QAk7DO88+CxVeTNzC49E4g7Ru0aJnBxzyXChr2dXUCJNyyONQ2LrvMnnjEQxkh69RuScEzA==}
peerDependencies:
'@sveltejs/kit': ^2.0.0
bits-ui: 1.0.0-next.65
@@ -1152,6 +1165,12 @@ packages:
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
+ '@types/dom-webcodecs@0.1.11':
+ resolution: {integrity: sha512-yPEZ3z7EohrmOxbk/QTAa0yonMFkNkjnVXqbGb7D4rMr+F1dGQ8ZUFxXkyLLJuiICPejZ0AZE9Rrk9wUCczx4A==}
+
+ '@types/emscripten@1.39.13':
+ resolution: {integrity: sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==}
+
'@types/estree-jsx@1.0.5':
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
@@ -1318,6 +1337,9 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ barcode-detector@2.3.1:
+ resolution: {integrity: sha512-D9KEtrquS1tmBZduxBZl8qublIKnRrFqD8TAHDYcLCyrHQBo+vitIxmjMJ61LvXjXyAMalOlO7q0Oh/9Rl2PbQ==}
+
better-path-resolve@1.0.0:
resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==}
engines: {node: '>=4'}
@@ -1489,8 +1511,8 @@ packages:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
- dotenv@16.4.5:
- resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
+ dotenv@16.4.6:
+ resolution: {integrity: sha512-JhcR/+KIjkkjiU8yEpaB/USlzVi3i5whwOjpIRNGi9svKEXZSe+Qp6IWAjFjv+2GViAoDRCUv/QLNziQxsLqDg==}
engines: {node: '>=12'}
emoji-regex-xs@1.0.0:
@@ -2645,6 +2667,11 @@ packages:
peerDependencies:
svelte: ^5.0.0-next.1
+ runed@0.16.1:
+ resolution: {integrity: sha512-k9ylt7sfEQiqOo2FmuilkLSk92pDzMSeVHFb8aPJGFPQPG9ErUxhfcHQwnmUhT03F8oQeO6bKB/TDoPKhdXbTA==}
+ peerDependencies:
+ svelte: ^5.0.0-next.1
+
sade@1.8.1:
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
engines: {node: '>=6'}
@@ -2652,6 +2679,9 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ sdp@3.2.0:
+ resolution: {integrity: sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==}
+
selfsigned@2.4.1:
resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==}
engines: {node: '>=10'}
@@ -3013,6 +3043,10 @@ packages:
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+ webrtc-adapter@9.0.1:
+ resolution: {integrity: sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==}
+ engines: {node: '>=6.0.0', npm: '>=3.10.0'}
+
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
@@ -3087,6 +3121,9 @@ packages:
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+ zxing-wasm@1.3.4:
+ resolution: {integrity: sha512-9l0QymyATF19FmI92QHe7Dayb+BUN7P7zFAt5iDgTnUf0dFWokz6GVA/W9EepjW5q8s3e89fIE/7uxpX27yqEQ==}
+
snapshots:
'@ampproject/remapping@2.3.0':
@@ -3271,7 +3308,7 @@ snapshots:
mime: 3.0.0
zod: 3.23.8
- '@cloudflare/workers-types@4.20241127.0': {}
+ '@cloudflare/workers-types@4.20241202.0': {}
'@cspotcode/source-map-support@0.8.1':
dependencies:
@@ -3811,7 +3848,7 @@ snapshots:
'@shikijs/vscode-textmate@9.3.0': {}
- '@svecodocs/kit@0.0.5(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(bits-ui@1.0.0-next.65(svelte@5.4.0))(svelte@5.4.0)(tailwindcss@4.0.0-beta.4)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))':
+ '@svecodocs/kit@0.1.1(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(bits-ui@1.0.0-next.65(svelte@5.4.0))(svelte@5.4.0)(tailwindcss@4.0.0-beta.4)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))':
dependencies:
'@sveltejs/kit': 2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))
bits-ui: 1.0.0-next.65(svelte@5.4.0)
@@ -3841,13 +3878,13 @@ snapshots:
'@sveltejs/kit': 2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))
import-meta-resolve: 4.1.0
- '@sveltejs/adapter-cloudflare@4.8.0(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(wrangler@3.91.0(@cloudflare/workers-types@4.20241127.0))':
+ '@sveltejs/adapter-cloudflare@4.8.0(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(wrangler@3.91.0(@cloudflare/workers-types@4.20241202.0))':
dependencies:
- '@cloudflare/workers-types': 4.20241127.0
+ '@cloudflare/workers-types': 4.20241202.0
'@sveltejs/kit': 2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))
esbuild: 0.24.0
worktop: 0.8.0-next.18
- wrangler: 3.91.0(@cloudflare/workers-types@4.20241127.0)
+ wrangler: 3.91.0(@cloudflare/workers-types@4.20241202.0)
'@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0)))(svelte@5.4.0)(vite@5.4.11(@types/node@22.10.1)(lightningcss@1.28.2)(terser@5.36.0))':
dependencies:
@@ -3903,7 +3940,7 @@ snapshots:
'@svitejs/changesets-changelog-github-compact@1.2.0':
dependencies:
'@changesets/get-github-info': 0.6.0
- dotenv: 16.4.5
+ dotenv: 16.4.6
transitivePeerDependencies:
- encoding
@@ -3995,6 +4032,10 @@ snapshots:
dependencies:
'@types/ms': 0.7.34
+ '@types/dom-webcodecs@0.1.11': {}
+
+ '@types/emscripten@1.39.13': {}
+
'@types/estree-jsx@1.0.5':
dependencies:
'@types/estree': 1.0.6
@@ -4164,6 +4205,11 @@ snapshots:
balanced-match@1.0.2: {}
+ barcode-detector@2.3.1:
+ dependencies:
+ '@types/dom-webcodecs': 0.1.11
+ zxing-wasm: 1.3.4
+
better-path-resolve@1.0.0:
dependencies:
is-windows: 1.0.2
@@ -4305,7 +4351,7 @@ snapshots:
dependencies:
path-type: 4.0.0
- dotenv@16.4.5: {}
+ dotenv@16.4.6: {}
emoji-regex-xs@1.0.0: {}
@@ -5880,12 +5926,19 @@ snapshots:
esm-env: 1.2.1
svelte: 5.4.0
+ runed@0.16.1(svelte@5.4.0):
+ dependencies:
+ esm-env: 1.2.1
+ svelte: 5.4.0
+
sade@1.8.1:
dependencies:
mri: 1.2.0
safer-buffer@2.1.2: {}
+ sdp@3.2.0: {}
+
selfsigned@2.4.1:
dependencies:
'@types/node-forge': 1.3.11
@@ -6237,6 +6290,10 @@ snapshots:
webidl-conversions@3.0.1: {}
+ webrtc-adapter@9.0.1:
+ dependencies:
+ sdp: 3.2.0
+
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
@@ -6261,7 +6318,7 @@ snapshots:
mrmime: 2.0.0
regexparam: 3.0.0
- wrangler@3.91.0(@cloudflare/workers-types@4.20241127.0):
+ wrangler@3.91.0(@cloudflare/workers-types@4.20241202.0):
dependencies:
'@cloudflare/kv-asset-handler': 0.3.4
'@cloudflare/workers-shared': 0.9.0
@@ -6283,7 +6340,7 @@ snapshots:
workerd: 1.20241106.1
xxhash-wasm: 1.1.0
optionalDependencies:
- '@cloudflare/workers-types': 4.20241127.0
+ '@cloudflare/workers-types': 4.20241202.0
fsevents: 2.3.3
transitivePeerDependencies:
- bufferutil
@@ -6313,3 +6370,7 @@ snapshots:
zod@3.23.8: {}
zwitch@2.0.4: {}
+
+ zxing-wasm@1.3.4:
+ dependencies:
+ '@types/emscripten': 1.39.13