diff --git a/docs/.well-known/agent-skills/fliplet-js-api/SKILL.md b/docs/.well-known/agent-skills/fliplet-js-api/SKILL.md index 10920288..9b3967e2 100644 --- a/docs/.well-known/agent-skills/fliplet-js-api/SKILL.md +++ b/docs/.well-known/agent-skills/fliplet-js-api/SKILL.md @@ -55,7 +55,7 @@ The Fliplet client-side JavaScript API: every Fliplet.X namespace (Storage, User - [Fliplet.App.Submissions](https://developers.fliplet.com/API/fliplet-app-submissions.md): Read App Store and Google Play submission metadata for the current Fliplet app — version, status, build numbers — via the fliplet-app-submissions package. - [Fliplet.Media.Audio.Player](https://developers.fliplet.com/API/fliplet-audio-player.md): Embed an audio player UI in a screen by tagging HTML elements with audio URLs; the framework auto-initializes players on screen load. - [Fliplet.Media.Audio](https://developers.fliplet.com/API/fliplet-audio.md): Play, pause, stop, and seek audio files on device or from a URL in Fliplet apps via the Audio namespace. -- [Fliplet.Barcode](https://developers.fliplet.com/API/fliplet-barcode.md): Generate QR codes and 1D/2D barcodes on screen, and scan them from the device camera, via the fliplet-barcode package. +- [Fliplet.Barcode](https://developers.fliplet.com/API/fliplet-barcode.md): Scan QR codes and 1D/2D barcodes from the camera on web and native (attachScanner / scan) and generate barcode images, via the fliplet-barcode package. - [Fliplet.Chat](https://developers.fliplet.com/API/fliplet-chat.md): Build one-to-one, group, and public-channel chat features. Fliplet.Chat owns the conversations and messages data sources internally — supply only the contacts list (who can chat with whom). - [Fliplet.Communicate](https://developers.fliplet.com/API/fliplet-communicate.md): Send email, SMS, push notifications, and share URLs from a Fliplet app using a single Communicate namespace. - [Fliplet.Content()](https://developers.fliplet.com/API/fliplet-content.md): Create, query, update, and delete shared content records (bookmarks, likes, saved searches) backed by a data source via Fliplet.Content. @@ -103,6 +103,7 @@ The Fliplet client-side JavaScript API: every Fliplet.X namespace (Storage, User - [V3 app bootstrap constraints](https://developers.fliplet.com/API/v3/app-bootstrap.md): The four constraints every V3 boot HTML must satisfy. Covers Fliplet.require.lazy for dependencies, Fliplet.Media.getContents for source files, the Fliplet().then(...) init sequence, and the locked v… - [V3 App Settings Convention](https://developers.fliplet.com/API/v3/app-settings.md): V3 app settings convention for storing public and private configuration. Covers the underscore prefix convention for editor-private settings. - [V3 Authentication Patterns](https://developers.fliplet.com/API/v3/auth.md): V3 authentication patterns for email/password login, session management, logout, and protected routes. Use these patterns when building authentication flows in V3 apps. +- [V3 barcodes](https://developers.fliplet.com/API/v3/barcode.md): Scan and generate QR codes and barcodes in V3 apps with Fliplet.Barcode — attachScanner() to scan (web + native) and encode() to render barcode images. - [V3 Alpine.js apps](https://developers.fliplet.com/API/v3/frameworks/alpine.md): Constraints for building V3 apps in Alpine.js. Alpine is attribute-driven HTML with no build step, so it maps cleanly to the V3 runtime. Covers x-init timing relative to Fliplet().then and platform-c… - [V3 framework guide — picking and setup](https://developers.fliplet.com/API/v3/frameworks/overview.md): Picking a frontend framework for a V3 app. Lists the runtime constraints every framework must cope with (no build step, no bundler, no transpile) and compares Vue, React, Alpine, and vanilla JS again… - [V3 React apps](https://developers.fliplet.com/API/v3/frameworks/react.md): Constraints for building V3 apps in React. Covers the JSX transpilation problem (the #1 cause of first-deploy failures) with three alternatives, React Router 6 createHashRouter wiring (no basename —… diff --git a/docs/.well-known/agent-skills/index.json b/docs/.well-known/agent-skills/index.json index 0c483af1..584b31d9 100644 --- a/docs/.well-known/agent-skills/index.json +++ b/docs/.well-known/agent-skills/index.json @@ -130,7 +130,7 @@ "tags": [ "js-api" ], - "sha256": "c07cf31ad3a44743fc7a6e656685bd8dd20f07bc92fc7a76d6899a77d0092ea5" + "sha256": "7d2ee408458c7970f50f21310b0be09aba835bb86c8eeca41a6227bbc4c89530" }, { "name": "fliplet-docs-index", diff --git a/docs/.well-known/llms-full.txt b/docs/.well-known/llms-full.txt index 65e9d5e3..52bccc83 100644 --- a/docs/.well-known/llms-full.txt +++ b/docs/.well-known/llms-full.txt @@ -12596,19 +12596,58 @@ URL: https://developers.fliplet.com/API/fliplet-barcode.md # `Fliplet.Barcode` -Scan QR codes and other 1D/2D barcodes from the device camera, and generate barcode images on screen, via the `fliplet-barcode` package. Barcode scanning is only supported in native apps. +Scan QR codes and other 1D/2D barcodes from the device camera, and generate barcode images on screen, via the `fliplet-barcode` package. + +The package offers two ways to scan: `attachScanner()` embeds a scanner in a container in your own UI and works on web and native, while `scan()` opens a ready-made full-screen scanner on native. Use `show()` or `encode()` to generate barcode images. Building a scanning screen in a V3 app? See the [V3 barcode scanning guide](./v3/barcode.md). ## Install Add the `fliplet-barcode` dependency to your screen or app resources. +## Fliplet.Barcode.attachScanner() + +(Returns a controller object) + +`attachScanner()` is a low-level scanner with **no built-in UI**: you provide a container element (placed and styled inside your own screen or modal) and `attachScanner()` drives the camera and decoder into it. It works the same on **web and native**. + +### Usage + +```js +// 1. Put a container in your own UI:
+var scanner = Fliplet.Barcode.attachScanner(document.getElementById('reader'), { + onScan: function (result) { + // result.text — the decoded value + // result.format — e.g. 'QR_CODE', 'CODE_128' + scanner.stop(); // one-shot: stop after the first scan (or keep scanning) + }, + onError: function (error) { + // camera permission denied, no camera, or library failed to load + } +}); + +// when the user leaves the screen or closes your scanner UI: +scanner.stop(); +``` + +A typical "tap a button to scan" screen shows your own button, reveals the `#reader` container when tapped, calls `attachScanner()`, and displays `result.text` on screen from inside `onScan`. + +* **element** (HTMLElement | String) The container to render the camera into (an element or its `id`). You own its placement and styling — `attachScanner()` neither creates nor removes it. +* **options** (Object) + * **onScan** (Function) Called on every successful decode with `{ text, format }`. Call `scanner.stop()` inside it for one-shot scanning. + * **onError** (Function) Called on a fatal start error (camera permission denied, no camera, or library load failure). + * **fps** (Number) Decode attempts per second. Default `10`. + * **qrbox** (Object | Function) Scan-box size (html5-qrcode `qrbox`). Defaults to a centred square at 70% of the smaller edge. +* **Returns** a controller `{ stop() }`. `stop()` ends the camera, tears down the decoder (your element is left in the DOM), and returns a `Promise`. + +**Permissions**: on web the browser prompts for camera access when scanning starts and requires a secure context (HTTPS). On native the OS prompts on first use. + ## Fliplet.Barcode.scan() (Returns `Promise`) -Scan a QR code or barcode. +Open a ready-made full-screen scanner and resolve with the result. This is a convenience shortcut for a quick, standalone scan where you do not need the scanner embedded in your own screen. -**Note**: Barcode scanning is only supported in native apps. +**Note**: `scan()` is only supported in native apps. For scanning that also works on the web, use [`attachScanner()`](#fliplet-barcode-attachscanner). ### Usage @@ -27847,6 +27886,195 @@ var session = await Fliplet.User.getCachedSession(); --- +# V3 barcodes +URL: https://developers.fliplet.com/API/v3/barcode.md + +# V3 barcodes + +The `fliplet-barcode` package does two things in a V3 app, both the same on **web and native**: it **scans** QR codes and barcodes with `Fliplet.Barcode.attachScanner()`, and it **generates** QR code and barcode images with `Fliplet.Barcode.encode()`. + +Scanning uses `attachScanner()` — a UI-less scanner you drive into a container element inside your own screen. It runs everywhere a V3 app runs (slug-hosted web, the Studio preview, and the native shell). You build the scanning UI yourself, the same way you build a login screen on top of `Fliplet.Session`; the API owns only the camera and the decoder. + +This guide covers the recommended embedded-scanner pattern, a full worked example, the controller and options, camera permissions, and generating barcode images. + +## Prerequisites + +Add the `fliplet-barcode` package to the screen, then load it before use: + +```js +await Fliplet.require.lazy.chain('fliplet-barcode'); +``` + +Once the package is loaded, `Fliplet.Barcode` exposes two methods, both of which work the same on **web and native**: `attachScanner()` (scan, documented below) and `encode()` (generate a QR code or barcode image, documented under [Generating barcodes](#generating-barcodes)). + +## The recommended pattern: attachScanner() + +`attachScanner()` has **no built-in UI**. You place and style a container element; `attachScanner()` runs the camera and decoder into it and calls you back on each successful decode. Because there is no platform-specific overlay, the same code works on web and native. + +```js +// Your screen owns this element and its styling: +//
+ +const scanner = Fliplet.Barcode.attachScanner(document.getElementById('reader'), { + onScan(result) { + // result.text — the decoded value, e.g. "https://example.com" or "5012345678900" + // result.format — the symbology, e.g. "QR_CODE", "CODE_128", "EAN_13" + scanner.stop(); // one-shot: stop after the first hit (omit to keep scanning) + showResult(result.text); + }, + onError(error) { + // Fatal start error: camera permission denied, no camera, or library load failure. + showMessage('Could not start the scanner. Check camera permissions and try again.'); + } +}); + +// Stop the camera when the user leaves the screen or closes your scanner UI: +scanner.stop(); +``` + +### Worked example: tap a button to scan + +A typical scanning screen keeps the viewfinder hidden until the user asks for it, scans once, then shows the result. This is the whole flow in vanilla JS: + +```html + + +

+``` + +```js +await Fliplet.require.lazy.chain('fliplet-barcode'); + +const btn = document.getElementById('scan-btn'); +const reader = document.getElementById('reader'); +const resultEl = document.getElementById('result'); +let scanner = null; + +btn.addEventListener('click', function () { + reader.hidden = false; + scanner = Fliplet.Barcode.attachScanner(reader, { + onScan(result) { + resultEl.textContent = `${result.format}: ${result.text}`; + scanner.stop(); + reader.hidden = true; + }, + onError() { + resultEl.textContent = 'Camera unavailable — check permissions.'; + reader.hidden = true; + } + }); +}); +``` + +### Vue component + +In a V3 Vue screen, start the scanner from a button handler and always stop it on unmount so the camera is released: + +```js +// Scanner.vue (runtime template — V3 single-file components run without a build step) +export default { + data() { + return { value: '', scanner: null }; + }, + methods: { + async start() { + await Fliplet.require.lazy.chain('fliplet-barcode'); + this.scanner = Fliplet.Barcode.attachScanner(this.$refs.reader, { + onScan: (result) => { + this.value = result.text; + this.scanner.stop(); + }, + onError: () => { this.value = 'Camera unavailable.'; } + }); + } + }, + beforeUnmount() { + if (this.scanner) { + this.scanner.stop(); + } + }, + template: ` +
+ +
+

{{ value }}

+
+ ` +}; +``` + +## Controller and options + +`attachScanner(element, options)` returns a controller and accepts: + +* **element** (HTMLElement | String) — the container to render the camera into (an element or its `id`). You own its placement and styling; `attachScanner()` neither creates nor removes it. +* **options.onScan** (Function) — called on every successful decode with `{ text, format }`. Call `controller.stop()` inside it for one-shot scanning. +* **options.onError** (Function) — called on a fatal start error (permission denied, no camera, or library load failure). Per-frame decode misses are *not* errors and are ignored. +* **options.fps** (Number) — decode attempts per second. Default `10`. +* **options.qrbox** (Object | Function) — scan-box size. Defaults to a centered square at 70% of the smaller edge. + +The controller is `{ stop() }`. `stop()` ends the camera, tears down the decoder (your element stays in the DOM), and returns a `Promise`. + +## Camera permissions + +* **Web** — the browser prompts for camera access when scanning starts, and `getUserMedia` requires a secure context (HTTPS). In the Studio preview the app runs in an iframe that already delegates `camera`; a published slug is served over HTTPS. +* **Native** — the OS prompts on first use. + +Handle a denied permission in `onError` — show the user how to re-enable the camera rather than leaving an empty viewfinder. + +## Generating barcodes + +To render a QR code or barcode image, use `Fliplet.Barcode.encode()`. It works on web and native, returns the image as a Base64 string, and defaults to a QR code unless you pass a different `format`. You place the returned image in your own UI — the same "you own the UI" model as `attachScanner()` — for example into an `` `src`, or save it with `Fliplet.Media`. + +### Fliplet.Barcode.encode() + +Encode text into a QR code or barcode and resolve with the image as a Base64 string. + +```js +await Fliplet.require.lazy.chain('fliplet-barcode'); + +// QR code (default) +Fliplet.Barcode.encode('https://example.com').then(function (data) { + document.getElementById('qr').src = data; // +}); + +// Barcode with options +Fliplet.Barcode.encode('5012345678900', { format: 'ean13' }).then(function (data) { + document.getElementById('code').src = data; +}); +``` + +* **text** (String) — the value to encode. +* **options** (Object) + * **format** (String) — `qr` (**default**), or `barcode` (encodes as `code128`), or a specific symbology: `code39`, `code128`, `code128A`, `code128B`, `code128C`, `ean13`, `ean8`, `ean5`, `ean2`, `upc`, `upce`, `itf14`, `itf`, `msi`, `msi10`, `msi11`, `msi1010`, `msi1110`, `pharmacode`, `codabar`, `genericbarcode`. + * **color** (String) — foreground color, keyword (`green`) or hex (`#00ff00`). **Default** `#000000`. + * **background** (String) — background color, keyword or hex. **Default** `#ffffff`. + * **width** (Number) — **QR code only.** Width of the QR image. **Default** `600`. (Barcode width is driven by the text length and `lineWidth`.) + * **height** (Number) — height of the image. **Default** `600` for QR, `150` for barcode. + * **lineWidth** (Number) — **barcode only.** Width of a single bar; larger values produce a wider image. **Default** `2`. + +> **Tip:** QR codes are always 1:1, so they scale cleanly. Prefer a QR code unless a linear barcode is specifically required; for barcodes, tune `height` and `lineWidth` to the length of the encoded text. `encode()` returns the raw image and renders no UI of its own — display it however your screen needs. + +## Patterns — DO and DON'T + +**DO** + +- Build your own scanning UI (button, viewfinder container, result area) and drive `attachScanner()` into it. +- Call `controller.stop()` after the first scan for one-shot flows, and on screen teardown/unmount to release the camera. +- Handle `onError` with a user-facing message about camera permissions. +- Give the container an explicit size — the camera fills it. + +**DON'T** + +- Don't leave the scanner running after navigation — a live camera drains battery and blocks other capture. +- Don't add a separate "scanner" package or a custom camera/getUserMedia stack — `attachScanner()` is the supported cross-platform scanner. + +## Related + +- [V3 routing](./routing.md) — base-path and navigation patterns for the screen that hosts your scanner. + +--- + # V3 Alpine.js apps URL: https://developers.fliplet.com/API/v3/frameworks/alpine.md @@ -40418,7 +40646,7 @@ Every Fliplet JS API available to V3 apps, grouped by capability category. Each ## Media -- [`Fliplet.Barcode`](https://developers.fliplet.com/API/fliplet-barcode.html) — Generate QR codes and 1D/2D barcodes on screen, and scan them from the device camera, via the fliplet-barcode package. +- [`Fliplet.Barcode`](https://developers.fliplet.com/API/v3/barcode.html) — Scan and generate QR codes and barcodes in V3 apps with Fliplet.Barcode — attachScanner() to scan (web + native) and show()/encode() to render barcode images. - [`Fliplet.Media`](https://developers.fliplet.com/API/fliplet-media.html) — Browse folders, upload and manage files, and download media to devices via the Fliplet Media namespace. - [`Fliplet.Media.Audio`](https://developers.fliplet.com/API/fliplet-audio.html) — Play, pause, stop, and seek audio files on device or from a URL in Fliplet apps via the Audio namespace. diff --git a/docs/.well-known/llms-v3-libraries.json b/docs/.well-known/llms-v3-libraries.json index 732d20f2..c7fda002 100644 --- a/docs/.well-known/llms-v3-libraries.json +++ b/docs/.well-known/llms-v3-libraries.json @@ -1,6 +1,6 @@ { "version": 1, - "generatedAt": "2026-06-10T17:50:12.068Z", + "generatedAt": "2026-06-17T10:20:55.308Z", "libraries": [ { "package": "fliplet-analytics-spa", @@ -61,9 +61,9 @@ { "package": "fliplet-barcode", "namespace": "Fliplet.Barcode", - "title": "Fliplet.Barcode", - "description": "Generate QR codes and 1D/2D barcodes on screen, and scan them from the device camera, via the fliplet-barcode package.", - "docUrl": "https://developers.fliplet.com/API/fliplet-barcode.md", + "title": "V3 barcodes", + "description": "Scan and generate QR codes and barcodes in V3 apps with Fliplet.Barcode — attachScanner() to scan (web + native) and encode() to render barcode images.", + "docUrl": "https://developers.fliplet.com/API/v3/barcode.md", "preloaded": false, "capabilities": [ "barcode", diff --git a/docs/.well-known/llms.txt b/docs/.well-known/llms.txt index cbd7ba9b..9fb325d3 100644 --- a/docs/.well-known/llms.txt +++ b/docs/.well-known/llms.txt @@ -103,7 +103,7 @@ - [Fliplet.App.Submissions](https://developers.fliplet.com/API/fliplet-app-submissions.md): Read App Store and Google Play submission metadata for the current Fliplet app — version, status, build numbers — via the fliplet-app-submissions package. - [Fliplet.Media.Audio.Player](https://developers.fliplet.com/API/fliplet-audio-player.md): Embed an audio player UI in a screen by tagging HTML elements with audio URLs; the framework auto-initializes players on screen load. - [Fliplet.Media.Audio](https://developers.fliplet.com/API/fliplet-audio.md): Play, pause, stop, and seek audio files on device or from a URL in Fliplet apps via the Audio namespace. -- [Fliplet.Barcode](https://developers.fliplet.com/API/fliplet-barcode.md): Generate QR codes and 1D/2D barcodes on screen, and scan them from the device camera, via the fliplet-barcode package. +- [Fliplet.Barcode](https://developers.fliplet.com/API/fliplet-barcode.md): Scan QR codes and 1D/2D barcodes from the camera on web and native (attachScanner / scan) and generate barcode images, via the fliplet-barcode package. - [Fliplet.Chat](https://developers.fliplet.com/API/fliplet-chat.md): Build one-to-one, group, and public-channel chat features. Fliplet.Chat owns the conversations and messages data sources internally — supply only the contacts list (who can chat with whom). - [Fliplet.Communicate](https://developers.fliplet.com/API/fliplet-communicate.md): Send email, SMS, push notifications, and share URLs from a Fliplet app using a single Communicate namespace. - [Fliplet.Content()](https://developers.fliplet.com/API/fliplet-content.md): Create, query, update, and delete shared content records (bookmarks, likes, saved searches) backed by a data source via Fliplet.Content. @@ -149,6 +149,7 @@ - [V3 app bootstrap constraints](https://developers.fliplet.com/API/v3/app-bootstrap.md): The four constraints every V3 boot HTML must satisfy. Covers Fliplet.require.lazy for dependencies, Fliplet.Media.getContents for source files, the Fliplet().then(...) init sequence, and the locked v… - [V3 App Settings Convention](https://developers.fliplet.com/API/v3/app-settings.md): V3 app settings convention for storing public and private configuration. Covers the underscore prefix convention for editor-private settings. - [V3 Authentication Patterns](https://developers.fliplet.com/API/v3/auth.md): V3 authentication patterns for email/password login, session management, logout, and protected routes. Use these patterns when building authentication flows in V3 apps. +- [V3 barcodes](https://developers.fliplet.com/API/v3/barcode.md): Scan and generate QR codes and barcodes in V3 apps with Fliplet.Barcode — attachScanner() to scan (web + native) and encode() to render barcode images. - [V3 Alpine.js apps](https://developers.fliplet.com/API/v3/frameworks/alpine.md): Constraints for building V3 apps in Alpine.js. Alpine is attribute-driven HTML with no build step, so it maps cleanly to the V3 runtime. Covers x-init timing relative to Fliplet().then and platform-c… - [V3 framework guide — picking and setup](https://developers.fliplet.com/API/v3/frameworks/overview.md): Picking a frontend framework for a V3 app. Lists the runtime constraints every framework must cope with (no build step, no bundler, no transpile) and compares Vue, React, Alpine, and vanilla JS again… - [V3 React apps](https://developers.fliplet.com/API/v3/frameworks/react.md): Constraints for building V3 apps in React. Covers the JSX transpilation problem (the #1 cause of first-deploy failures) with three alternatives, React Router 6 createHashRouter wiring (no basename —… diff --git a/docs/API/fliplet-barcode.md b/docs/API/fliplet-barcode.md index a85e3a0f..b1443993 100644 --- a/docs/API/fliplet-barcode.md +++ b/docs/API/fliplet-barcode.md @@ -1,28 +1,66 @@ --- title: Fliplet.Barcode -description: Generate QR codes and 1D/2D barcodes on screen, and scan them from the device camera, via the fliplet-barcode package. +description: Scan QR codes and 1D/2D barcodes from the camera on web and native (attachScanner / scan) and generate barcode images, via the fliplet-barcode package. type: api-reference tags: [js-api, barcode] -v3_relevant: true +v3_relevant: false deprecated: false -category: media -capabilities: [barcode, qr code, qrcode, scan, scanner, camera scan, generate barcode, ean, upc, 1d barcode, 2d barcode] +exclude_from_v3_catalog: true --- # `Fliplet.Barcode` -Scan QR codes and other 1D/2D barcodes from the device camera, and generate barcode images on screen, via the `fliplet-barcode` package. Barcode scanning is only supported in native apps. +Scan QR codes and other 1D/2D barcodes from the device camera, and generate barcode images on screen, via the `fliplet-barcode` package. + +The package offers two ways to scan: `attachScanner()` embeds a scanner in a container in your own UI and works on web and native, while `scan()` opens a ready-made full-screen scanner on native. Use `show()` or `encode()` to generate barcode images. Building a scanning screen in a V3 app? See the [V3 barcode scanning guide](./v3/barcode.md). ## Install Add the `fliplet-barcode` dependency to your screen or app resources. +## Fliplet.Barcode.attachScanner() + +(Returns a controller object) + +`attachScanner()` is a low-level scanner with **no built-in UI**: you provide a container element (placed and styled inside your own screen or modal) and `attachScanner()` drives the camera and decoder into it. It works the same on **web and native**. + +### Usage + +```js +// 1. Put a container in your own UI:
+var scanner = Fliplet.Barcode.attachScanner(document.getElementById('reader'), { + onScan: function (result) { + // result.text — the decoded value + // result.format — e.g. 'QR_CODE', 'CODE_128' + scanner.stop(); // one-shot: stop after the first scan (or keep scanning) + }, + onError: function (error) { + // camera permission denied, no camera, or library failed to load + } +}); + +// when the user leaves the screen or closes your scanner UI: +scanner.stop(); +``` + +A typical "tap a button to scan" screen shows your own button, reveals the `#reader` container when tapped, calls `attachScanner()`, and displays `result.text` on screen from inside `onScan`. + +* **element** (HTMLElement | String) The container to render the camera into (an element or its `id`). You own its placement and styling — `attachScanner()` neither creates nor removes it. +* **options** (Object) + * **onScan** (Function) Called on every successful decode with `{ text, format }`. Call `scanner.stop()` inside it for one-shot scanning. + * **onError** (Function) Called on a fatal start error (camera permission denied, no camera, or library load failure). + * **fps** (Number) Decode attempts per second. Default `10`. + * **qrbox** (Object | Function) Scan-box size (html5-qrcode `qrbox`). Defaults to a centred square at 70% of the smaller edge. +* **Returns** a controller `{ stop() }`. `stop()` ends the camera, tears down the decoder (your element is left in the DOM), and returns a `Promise`. + +**Permissions**: on web the browser prompts for camera access when scanning starts and requires a secure context (HTTPS). On native the OS prompts on first use. + ## Fliplet.Barcode.scan() (Returns `Promise`) -Scan a QR code or barcode. +Open a ready-made full-screen scanner and resolve with the result. This is a convenience shortcut for a quick, standalone scan where you do not need the scanner embedded in your own screen. -**Note**: Barcode scanning is only supported in native apps. +**Note**: `scan()` is only supported in native apps. For scanning that also works on the web, use [`attachScanner()`](#fliplet-barcode-attachscanner). ### Usage diff --git a/docs/API/v3/barcode.md b/docs/API/v3/barcode.md new file mode 100644 index 00000000..c1ee8b8c --- /dev/null +++ b/docs/API/v3/barcode.md @@ -0,0 +1,196 @@ +--- +title: "V3 barcodes" +description: Scan and generate QR codes and barcodes in V3 apps with Fliplet.Barcode — attachScanner() to scan (web + native) and encode() to render barcode images. +type: guide +tags: [js-api, v3, barcode] +v3_relevant: true +deprecated: false +package: fliplet-barcode +namespace: Fliplet.Barcode +category: media +capabilities: [barcode, qr code, qrcode, scan, scanner, camera scan, generate barcode, ean, upc, 1d barcode, 2d barcode] +--- + +# V3 barcodes + +The `fliplet-barcode` package does two things in a V3 app, both the same on **web and native**: it **scans** QR codes and barcodes with `Fliplet.Barcode.attachScanner()`, and it **generates** QR code and barcode images with `Fliplet.Barcode.encode()`. + +Scanning uses `attachScanner()` — a UI-less scanner you drive into a container element inside your own screen. It runs everywhere a V3 app runs (slug-hosted web, the Studio preview, and the native shell). You build the scanning UI yourself, the same way you build a login screen on top of `Fliplet.Session`; the API owns only the camera and the decoder. + +This guide covers the recommended embedded-scanner pattern, a full worked example, the controller and options, camera permissions, and generating barcode images. + +## Prerequisites + +Add the `fliplet-barcode` package to the screen, then load it before use: + +```js +await Fliplet.require.lazy.chain('fliplet-barcode'); +``` + +Once the package is loaded, `Fliplet.Barcode` exposes two methods, both of which work the same on **web and native**: `attachScanner()` (scan, documented below) and `encode()` (generate a QR code or barcode image, documented under [Generating barcodes](#generating-barcodes)). + +## The recommended pattern: attachScanner() + +`attachScanner()` has **no built-in UI**. You place and style a container element; `attachScanner()` runs the camera and decoder into it and calls you back on each successful decode. Because there is no platform-specific overlay, the same code works on web and native. + +```js +// Your screen owns this element and its styling: +//
+ +const scanner = Fliplet.Barcode.attachScanner(document.getElementById('reader'), { + onScan(result) { + // result.text — the decoded value, e.g. "https://example.com" or "5012345678900" + // result.format — the symbology, e.g. "QR_CODE", "CODE_128", "EAN_13" + scanner.stop(); // one-shot: stop after the first hit (omit to keep scanning) + showResult(result.text); + }, + onError(error) { + // Fatal start error: camera permission denied, no camera, or library load failure. + showMessage('Could not start the scanner. Check camera permissions and try again.'); + } +}); + +// Stop the camera when the user leaves the screen or closes your scanner UI: +scanner.stop(); +``` + +### Worked example: tap a button to scan + +A typical scanning screen keeps the viewfinder hidden until the user asks for it, scans once, then shows the result. This is the whole flow in vanilla JS: + +```html + + +

+``` + +```js +await Fliplet.require.lazy.chain('fliplet-barcode'); + +const btn = document.getElementById('scan-btn'); +const reader = document.getElementById('reader'); +const resultEl = document.getElementById('result'); +let scanner = null; + +btn.addEventListener('click', function () { + reader.hidden = false; + scanner = Fliplet.Barcode.attachScanner(reader, { + onScan(result) { + resultEl.textContent = `${result.format}: ${result.text}`; + scanner.stop(); + reader.hidden = true; + }, + onError() { + resultEl.textContent = 'Camera unavailable — check permissions.'; + reader.hidden = true; + } + }); +}); +``` + +### Vue component + +In a V3 Vue screen, start the scanner from a button handler and always stop it on unmount so the camera is released: + +```js +// Scanner.vue (runtime template — V3 single-file components run without a build step) +export default { + data() { + return { value: '', scanner: null }; + }, + methods: { + async start() { + await Fliplet.require.lazy.chain('fliplet-barcode'); + this.scanner = Fliplet.Barcode.attachScanner(this.$refs.reader, { + onScan: (result) => { + this.value = result.text; + this.scanner.stop(); + }, + onError: () => { this.value = 'Camera unavailable.'; } + }); + } + }, + beforeUnmount() { + if (this.scanner) { + this.scanner.stop(); + } + }, + template: ` +
+ +
+

{{ value }}

+
+ ` +}; +``` + +## Controller and options + +`attachScanner(element, options)` returns a controller and accepts: + +* **element** (HTMLElement | String) — the container to render the camera into (an element or its `id`). You own its placement and styling; `attachScanner()` neither creates nor removes it. +* **options.onScan** (Function) — called on every successful decode with `{ text, format }`. Call `controller.stop()` inside it for one-shot scanning. +* **options.onError** (Function) — called on a fatal start error (permission denied, no camera, or library load failure). Per-frame decode misses are *not* errors and are ignored. +* **options.fps** (Number) — decode attempts per second. Default `10`. +* **options.qrbox** (Object | Function) — scan-box size. Defaults to a centered square at 70% of the smaller edge. + +The controller is `{ stop() }`. `stop()` ends the camera, tears down the decoder (your element stays in the DOM), and returns a `Promise`. + +## Camera permissions + +* **Web** — the browser prompts for camera access when scanning starts, and `getUserMedia` requires a secure context (HTTPS). In the Studio preview the app runs in an iframe that already delegates `camera`; a published slug is served over HTTPS. +* **Native** — the OS prompts on first use. + +Handle a denied permission in `onError` — show the user how to re-enable the camera rather than leaving an empty viewfinder. + +## Generating barcodes + +To render a QR code or barcode image, use `Fliplet.Barcode.encode()`. It works on web and native, returns the image as a Base64 string, and defaults to a QR code unless you pass a different `format`. You place the returned image in your own UI — the same "you own the UI" model as `attachScanner()` — for example into an `` `src`, or save it with `Fliplet.Media`. + +### Fliplet.Barcode.encode() + +Encode text into a QR code or barcode and resolve with the image as a Base64 string. + +```js +await Fliplet.require.lazy.chain('fliplet-barcode'); + +// QR code (default) +Fliplet.Barcode.encode('https://example.com').then(function (data) { + document.getElementById('qr').src = data; // +}); + +// Barcode with options +Fliplet.Barcode.encode('5012345678900', { format: 'ean13' }).then(function (data) { + document.getElementById('code').src = data; +}); +``` + +* **text** (String) — the value to encode. +* **options** (Object) + * **format** (String) — `qr` (**default**), or `barcode` (encodes as `code128`), or a specific symbology: `code39`, `code128`, `code128A`, `code128B`, `code128C`, `ean13`, `ean8`, `ean5`, `ean2`, `upc`, `upce`, `itf14`, `itf`, `msi`, `msi10`, `msi11`, `msi1010`, `msi1110`, `pharmacode`, `codabar`, `genericbarcode`. + * **color** (String) — foreground color, keyword (`green`) or hex (`#00ff00`). **Default** `#000000`. + * **background** (String) — background color, keyword or hex. **Default** `#ffffff`. + * **width** (Number) — **QR code only.** Width of the QR image. **Default** `600`. (Barcode width is driven by the text length and `lineWidth`.) + * **height** (Number) — height of the image. **Default** `600` for QR, `150` for barcode. + * **lineWidth** (Number) — **barcode only.** Width of a single bar; larger values produce a wider image. **Default** `2`. + +> **Tip:** QR codes are always 1:1, so they scale cleanly. Prefer a QR code unless a linear barcode is specifically required; for barcodes, tune `height` and `lineWidth` to the length of the encoded text. `encode()` returns the raw image and renders no UI of its own — display it however your screen needs. + +## Patterns — DO and DON'T + +**DO** + +- Build your own scanning UI (button, viewfinder container, result area) and drive `attachScanner()` into it. +- Call `controller.stop()` after the first scan for one-shot flows, and on screen teardown/unmount to release the camera. +- Handle `onError` with a user-facing message about camera permissions. +- Give the container an explicit size — the camera fills it. + +**DON'T** + +- Don't leave the scanner running after navigation — a live camera drains battery and blocks other capture. +- Don't add a separate "scanner" package or a custom camera/getUserMedia stack — `attachScanner()` is the supported cross-platform scanner. + +## Related + +- [V3 routing](./routing.md) — base-path and navigation patterns for the screen that hosts your scanner. diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md index 8a9e09e7..e2399c0c 100644 --- a/docs/CLAUDE.md +++ b/docs/CLAUDE.md @@ -94,6 +94,34 @@ none. the V3 design system replaces, meta/overview docs, deprecated namespaces, or docs that aren't a Fliplet API per se. +### Dedicated V3 capability docs (the V3-version pattern) + +When an API behaves differently enough on V3 that the builder needs V3-specific +guidance, give the package its own doc at **`API/v3/.md`** and make *that* +the catalog entry — don't steer the shared reference. This is the pattern used by +`fliplet-barcode` (and `app-actions` before it): + +1. **The V3 doc** (`API/v3/.md`) declares the catalog fields — + `package:`, `category:`, `capabilities:`, and `namespace:` (the real JS + global, since the doc's title is a guide title like "V3 barcode scanning", + not `Fliplet.Barcode`). A doc under `API/v3/` becomes a catalog entry **only** + when it declares `package:`, so the general V3 guides (routing, auth, + frameworks) stay out. +2. **The shared reference** (`API/fliplet-.md`) is set + `v3_relevant: false` **and** `exclude_from_v3_catalog: true`, so the catalog, + capabilities index, and Studio registry all derive the V3 doc's URL — no + hand-set URLs, no Studio-side override. +3. **Content rule:** the V3 doc contains **only the recommended cross-platform + primitive**. Remove native-only or alternative methods entirely (don't just + de-emphasize them) — the builder reads this as its sole source for the + package and will pick whatever runnable method it sees, so an unwanted method + in the doc becomes unwanted code. Keep those methods in the shared reference. + +`build-agent-indexes.mjs` enforces **one catalog doc per package** +(`validateCatalogUniqueness`, `--strict`) — forgetting the +`exclude_from_v3_catalog` opt-out fails the build rather than silently shipping +two `docUrl`s for one package. + ## Exclusion list — do not index, do not polish These files are handled at the server or build layer and must never be diff --git a/docs/bin/__tests__/build-agent-indexes.test.mjs b/docs/bin/__tests__/build-agent-indexes.test.mjs index c84a3164..fd3d65d8 100644 --- a/docs/bin/__tests__/build-agent-indexes.test.mjs +++ b/docs/bin/__tests__/build-agent-indexes.test.mjs @@ -29,6 +29,7 @@ import { assignToCluster, validateFrontmatter, validateCapabilities, + validateCatalogUniqueness, ALLOWED_TYPES, CLUSTERS, collectDocs, @@ -740,6 +741,25 @@ describe('emitV3LibraryCatalog', () => { assert.equal(catalog.libraries[0].package, 'fliplet-barcode'); }); + it('includes an API/v3/*.md package doc and honours the namespace override', () => { + const docs = [ + makeDoc( + 'API/v3/barcode.md', + { package: 'fliplet-barcode', namespace: 'Fliplet.Barcode', category: 'media' }, + 'V3 barcode scanning', + 'Scan on web + native', + ), + ]; + const catalog = emitV3LibraryCatalog(docs); + assert.equal(catalog.libraries.length, 1); + assert.equal(catalog.libraries[0].package, 'fliplet-barcode'); + // namespace comes from frontmatter, NOT the guide-style title + assert.equal(catalog.libraries[0].namespace, 'Fliplet.Barcode'); + assert.equal(catalog.libraries[0].title, 'V3 barcode scanning'); + assert.equal(catalog.libraries[0].preloaded, false); + assert.equal(catalog.libraries[0].docUrl, 'https://developers.fliplet.com/API/v3/barcode.html'); + }); + it('excludes docs not matching the installable or ambient path patterns', () => { const docs = [ makeDoc('Building-themes.md', {}, 'Themes'), @@ -938,6 +958,47 @@ describe('isV3CatalogEntry', () => { true, ); }); + + it('includes API/v3/*.md only when it declares a package', () => { + assert.equal(isV3CatalogEntry(doc('API/v3/barcode.md', { package: 'fliplet-barcode' })), true); + // general V3 guides (no package) describe patterns, not a package — stay out + assert.equal(isV3CatalogEntry(doc('API/v3/routing.md')), false); + assert.equal(isV3CatalogEntry(doc('API/v3/auth.md', { package: '' })), false); + }); + + it('respects exclude_from_v3_catalog on an API/v3 package doc', () => { + assert.equal( + isV3CatalogEntry(doc('API/v3/barcode.md', { package: 'fliplet-barcode', exclude_from_v3_catalog: 'true' })), + false, + ); + }); + + it('ignores nested API/v3 subdirs (e.g. frameworks/) regardless of package', () => { + assert.equal(isV3CatalogEntry(doc('API/v3/frameworks/vue.md', { package: 'x' })), false); + }); +}); + +describe('validateCatalogUniqueness', () => { + function doc(relPath, fm = {}) { + return { relPath, fm }; + } + + it('flags a package that resolves to two catalog docs', () => { + const errors = validateCatalogUniqueness([ + doc('API/fliplet-barcode.md', { package: 'fliplet-barcode', category: 'media', capabilities: '[barcode]' }), + doc('API/v3/barcode.md', { package: 'fliplet-barcode', category: 'media', capabilities: '[barcode]' }), + ]); + assert.equal(errors.length, 2); + assert.match(errors[0].message, /resolves to 2 V3 catalog entries/); + }); + + it('passes when the shared reference opts out (one catalog doc per package)', () => { + const errors = validateCatalogUniqueness([ + doc('API/fliplet-barcode.md', { exclude_from_v3_catalog: 'true' }), + doc('API/v3/barcode.md', { package: 'fliplet-barcode', category: 'media', capabilities: '[barcode]' }), + ]); + assert.equal(errors.length, 0); + }); }); describe('validateCapabilities', () => { diff --git a/docs/bin/build-agent-indexes.mjs b/docs/bin/build-agent-indexes.mjs index 42137a99..b6eb5438 100644 --- a/docs/bin/build-agent-indexes.mjs +++ b/docs/bin/build-agent-indexes.mjs @@ -757,6 +757,41 @@ export function validateCapabilities(docs) { return errors; } +// One catalog entry per package. A package's dedicated V3 doc +// (API/v3/.md with `package:`) is meant to REPLACE its shared +// API/fliplet-.md reference as the catalog entry — and the shared doc +// must then opt out via `exclude_from_v3_catalog: true`. If both stay in the +// catalog, the V3 builder gets two conflicting `docUrl`s for one package and +// may read the wrong (e.g. native-only) version. This lint is the tripwire: +// it fails the build when any package resolves to >1 catalog doc, so a +// forgotten opt-out can't silently ship the wrong version. +export function validateCatalogUniqueness(docs) { + const errors = []; + const byPackage = new Map(); + for (const doc of docs) { + if (!isV3CatalogEntry(doc)) continue; + const fm = doc.fm || {}; + const pkg = (fm.package && String(fm.package).trim()) || deriveV3Package(doc.relPath); + if (!pkg) continue; + if (!byPackage.has(pkg)) byPackage.set(pkg, []); + byPackage.get(pkg).push(doc.relPath); + } + for (const [pkg, paths] of byPackage) { + if (paths.length > 1) { + for (const p of paths) { + errors.push({ + relPath: p, + field: 'package', + message: `package \`${pkg}\` resolves to ${paths.length} V3 catalog entries: ${paths.join(', ')}`, + hint: 'A package needs exactly one catalog doc. If a dedicated API/v3/ doc is the V3 entry, mark the shared API/fliplet-*.md with `exclude_from_v3_catalog: true`.', + docUrl: CONTRIBUTING_URL, + }); + } + } + } + return errors; +} + // --------------------------------------------------------------------------- // Cross-link validation (strict-mode link-rot detection). // @@ -1029,6 +1064,24 @@ function main() { } } + // Catalog uniqueness: exactly one V3 catalog doc per package. Same --strict + // gate. Guards the "shared ref opts out, V3 doc takes over" handoff so a + // forgotten `exclude_from_v3_catalog` can't ship two docUrls for one package. + const dupErrors = validateCatalogUniqueness(docs); + if (dupErrors.length > 0) { + const tag = strict ? '[error]' : '[warn]'; + for (const e of dupErrors) { + console.error(`${tag} ${e.relPath}: ${e.message}`); + if (e.hint) console.error(` hint: ${e.hint}`); + } + if (strict) { + console.error( + `\n${dupErrors.length} catalog-uniqueness error${dupErrors.length === 1 ? '' : 's'} — failing build (--strict).`, + ); + process.exit(1); + } + } + // Cross-link validation: catch internal links that don't resolve to an // existing doc file. Same --strict gate as the validators above. const linkErrors = validateCrossLinks(docs, docsRoot); diff --git a/docs/bin/emitters.mjs b/docs/bin/emitters.mjs index 611f4932..b4cf52da 100644 --- a/docs/bin/emitters.mjs +++ b/docs/bin/emitters.mjs @@ -9,12 +9,20 @@ // // Stdlib only — no npm deps. Imported by build-agent-indexes.mjs. -// V3 catalog membership is determined by two path patterns plus the +// V3 catalog membership is determined by the path patterns below plus the // `exclude_from_v3_catalog: true` frontmatter opt-out. Keep these regexes // private to this module — the predicate `isV3CatalogEntry` is the public // contract. const V3_INSTALLABLE_PATH_RE = /^API\/fliplet-[^/]+\.md$/; const V3_AMBIENT_PATH_RE = /^API\/core\/[^/]+\.md$/; +// A V3-specific capability doc under API/v3/ becomes the catalog entry for its +// package ONLY when it declares `package:` in frontmatter. This lets an +// installable package ship a dedicated V3 doc (e.g. API/v3/barcode.md leading +// with the cross-platform primitive) as the canonical catalog entry, while the +// shared API/fliplet-.md reference opts out via `exclude_from_v3_catalog`. +// The `package:` gate keeps the general V3 guides (routing, auth, frameworks/) +// out of the catalog — they describe patterns, not a single package. +const V3_GUIDE_PATH_RE = /^API\/v3\/[^/]+\.md$/; // Allowed values for the `category:` frontmatter field. Documented in // docs/CLAUDE.md as the canonical schema. Each catalog entry picks exactly @@ -52,22 +60,25 @@ export const ALLOWED_CATEGORIES_SET = new Set(ALLOWED_CATEGORIES); // must use this — drift between them is a real bug (page would show entries // the agent doesn't know about, or vice versa). // -// A doc is a V3 catalog entry when: -// - its path matches `API/fliplet-*.md` (installable) OR -// `API/core/*.md` (ambient, preloaded via fliplet-core), AND -// - its frontmatter does NOT set `exclude_from_v3_catalog: true`. +// A doc is a V3 catalog entry when its frontmatter does NOT set +// `exclude_from_v3_catalog: true`, AND its path matches one of: +// - `API/fliplet-*.md` (installable), OR +// - `API/core/*.md` (ambient, preloaded via fliplet-core), OR +// - `API/v3/*.md` that declares `package:` (a package's dedicated V3 doc; +// the `package:` gate keeps the pattern guides — routing/auth/frameworks — +// out, since they describe patterns rather than one package). // // Note: this is DIFFERENT from `shouldExclude(path)` in exclusions.mjs. // `shouldExclude` skips a path from indexing entirely (redirect stubs, // `_site/`, etc.); `isV3CatalogEntry` decides catalog membership for docs // that ARE indexed. Conflating them mis-fires lints across all docs. export function isV3CatalogEntry(doc) { - const isInstallable = V3_INSTALLABLE_PATH_RE.test(doc.relPath); - const isAmbient = V3_AMBIENT_PATH_RE.test(doc.relPath); - if (!isInstallable && !isAmbient) return false; const fm = doc.fm || {}; if (fm.exclude_from_v3_catalog === 'true') return false; - return true; + const isInstallable = V3_INSTALLABLE_PATH_RE.test(doc.relPath); + const isAmbient = V3_AMBIENT_PATH_RE.test(doc.relPath); + const isV3Guide = V3_GUIDE_PATH_RE.test(doc.relPath) && !!(fm.package && String(fm.package).trim()); + return isInstallable || isAmbient || isV3Guide; } export function deriveV3Package(relPath) { @@ -127,9 +138,15 @@ export function emitV3LibraryCatalog(docs) { pkg = 'fliplet-core'; } + // For most docs the title IS the JS namespace (e.g. "Fliplet.Barcode"). A + // dedicated V3 doc is titled as a guide ("V3 barcode scanning"), so it can + // declare the actual global via `namespace:` — the builder's registry shows + // `package → namespace`, so this must be the real global, not the doc title. + const namespace = (fm.namespace && fm.namespace.trim()) || doc.title; + const entry = { package: pkg, - namespace: doc.title, + namespace, title: doc.title, description: doc.description || '', // AI-consumption surface: emit the raw .md URL (Studio's V3 builder fetches @@ -184,7 +201,7 @@ export function emitCapabilitiesIndex(docs) { const category = fm.category && fm.category.trim(); const isAmbient = V3_AMBIENT_PATH_RE.test(doc.relPath); const item = { - namespace: doc.title, + namespace: (fm.namespace && fm.namespace.trim()) || doc.title, description: doc.description || '', url: doc.url, preloaded: isAmbient, diff --git a/docs/v3/capabilities.md b/docs/v3/capabilities.md index 16e5b555..0eeab48f 100644 --- a/docs/v3/capabilities.md +++ b/docs/v3/capabilities.md @@ -39,7 +39,7 @@ Every Fliplet JS API available to V3 apps, grouped by capability category. Each ## Media -- [`Fliplet.Barcode`](https://developers.fliplet.com/API/fliplet-barcode.html) — Generate QR codes and 1D/2D barcodes on screen, and scan them from the device camera, via the fliplet-barcode package. +- [`Fliplet.Barcode`](https://developers.fliplet.com/API/v3/barcode.html) — Scan and generate QR codes and barcodes in V3 apps with Fliplet.Barcode — attachScanner() to scan (web + native) and encode() to render barcode images. - [`Fliplet.Media`](https://developers.fliplet.com/API/fliplet-media.html) — Browse folders, upload and manage files, and download media to devices via the Fliplet Media namespace. - [`Fliplet.Media.Audio`](https://developers.fliplet.com/API/fliplet-audio.html) — Play, pause, stop, and seek audio files on device or from a URL in Fliplet apps via the Audio namespace.