@vielzeug/sigil 3.0.0 → 26.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_format.cjs +1 -1
- package/dist/_format.cjs.map +1 -1
- package/dist/_format.js +1 -36
- package/dist/_format.js.map +1 -1
- package/dist/_galois.cjs +1 -1
- package/dist/_galois.cjs.map +1 -1
- package/dist/_galois.js +1 -21
- package/dist/_galois.js.map +1 -1
- package/dist/_mask.cjs +1 -1
- package/dist/_mask.cjs.map +1 -1
- package/dist/_mask.js +1 -80
- package/dist/_mask.js.map +1 -1
- package/dist/_matrix.cjs +1 -1
- package/dist/_matrix.cjs.map +1 -1
- package/dist/_matrix.js +1 -64
- package/dist/_matrix.js.map +1 -1
- package/dist/_reed-solomon.cjs +1 -1
- package/dist/_reed-solomon.cjs.map +1 -1
- package/dist/_reed-solomon.js +1 -21
- package/dist/_reed-solomon.js.map +1 -1
- package/dist/_segments.cjs +1 -1
- package/dist/_segments.cjs.map +1 -1
- package/dist/_segments.d.ts +22 -11
- package/dist/_segments.d.ts.map +1 -1
- package/dist/_segments.js +1 -69
- package/dist/_segments.js.map +1 -1
- package/dist/_tables.cjs +1 -1
- package/dist/_tables.cjs.map +1 -1
- package/dist/_tables.js +1 -408
- package/dist/_tables.js.map +1 -1
- package/dist/canvas.cjs +1 -1
- package/dist/canvas.cjs.map +1 -1
- package/dist/canvas.d.ts.map +1 -1
- package/dist/canvas.js +1 -13
- package/dist/canvas.js.map +1 -1
- package/dist/encode.cjs +1 -1
- package/dist/encode.cjs.map +1 -1
- package/dist/encode.d.ts +3 -1
- package/dist/encode.d.ts.map +1 -1
- package/dist/encode.js +1 -111
- package/dist/encode.js.map +1 -1
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js +1 -16
- package/dist/errors.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -6
- package/dist/scan.cjs +1 -1
- package/dist/scan.cjs.map +1 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +1 -140
- package/dist/scan.js.map +1 -1
- package/dist/sigil.cjs +1 -1
- package/dist/sigil.cjs.map +1 -1
- package/dist/sigil.iife.js +1 -1
- package/dist/sigil.iife.js.map +1 -1
- package/dist/sigil.js +1 -1
- package/dist/sigil.js.map +1 -1
- package/dist/svg.cjs +1 -1
- package/dist/svg.cjs.map +1 -1
- package/dist/svg.js +1 -25
- package/dist/svg.js.map +1 -1
- package/dist/types.d.ts +4 -6
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/dist/_dev.cjs +0 -2
- package/dist/_dev.cjs.map +0 -1
- package/dist/_dev.d.ts +0 -2
- package/dist/_dev.d.ts.map +0 -1
- package/dist/_dev.js +0 -6
- package/dist/_dev.js.map +0 -1
package/dist/encode.js
CHANGED
|
@@ -1,112 +1,2 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import { ALPHANUMERIC_CHARSET as e, CHAR_COUNT_BITS as t, dataCodewords as n, ecCodewordsPerBlock as r, matrixSize as i, numEcBlocks as a, totalCodewords as o, versionGroup as s } from "./_tables.js";
|
|
3
|
-
import { drawFormatInfo as c, drawVersionInfo as l } from "./_format.js";
|
|
4
|
-
import { applyMask as u, penaltyScore as d } from "./_mask.js";
|
|
5
|
-
import { createGrid as f, drawFunctionPatterns as p, placeData as m } from "./_matrix.js";
|
|
6
|
-
import { ecCodewords as h } from "./_reed-solomon.js";
|
|
7
|
-
import { buildDataCodewords as g, detectMode as _, segmentBits as v } from "./_segments.js";
|
|
8
|
-
import { SigilCapacityError as y, SigilOptionError as b } from "./errors.js";
|
|
9
|
-
//#region src/encode.ts
|
|
10
|
-
var x = 40;
|
|
11
|
-
function S(e) {
|
|
12
|
-
let { version: t, minVersion: n, mask: r, errorCorrection: i } = e, a = (e) => Number.isInteger(e) && e >= 1 && e <= x;
|
|
13
|
-
if (t !== void 0 && !a(t)) throw new b(`version must be an integer 1–${x}, got ${t}`);
|
|
14
|
-
if (n !== void 0 && !a(n)) throw new b(`minVersion must be an integer 1–${x}, got ${n}`);
|
|
15
|
-
if (t !== void 0 && n !== void 0 && n > t) throw new b(`minVersion (${n}) exceeds version (${t})`);
|
|
16
|
-
if (r !== void 0 && (!Number.isInteger(r) || r < 0 || r > 7)) throw new b(`mask must be an integer 0–7, got ${r}`);
|
|
17
|
-
if (i !== void 0 && !"LMQH".includes(i)) throw new b("errorCorrection must be 'L', 'M', 'Q', or 'H'");
|
|
18
|
-
}
|
|
19
|
-
function C(e, r, i) {
|
|
20
|
-
let a = t[i][s(e)], o = n(e, r) * 8 - 4 - a;
|
|
21
|
-
if (o <= 0) return 0;
|
|
22
|
-
switch (i) {
|
|
23
|
-
case "numeric": return Math.floor(o / 10) * 3 + (o % 10 >= 7 ? 2 : +(o % 10 >= 4));
|
|
24
|
-
case "alphanumeric": return Math.floor(o / 11) * 2 + +(o % 11 >= 6);
|
|
25
|
-
case "byte": return Math.floor(o / 8);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function w(e, t, r, i) {
|
|
29
|
-
for (let a = i; a <= x; a++) if (v(e, t, a) <= n(a, r) * 8) return a;
|
|
30
|
-
return -1;
|
|
31
|
-
}
|
|
32
|
-
function T(e, t, n) {
|
|
33
|
-
let i = a(t, n), s = r(t, n), c = o(t), l = Math.floor(c / i) - s, u = c % i, d = [], f = 0;
|
|
34
|
-
for (let t = 0; t < i; t++) {
|
|
35
|
-
let n = t >= i - u, r = e.slice(f, f + l + +!!n);
|
|
36
|
-
f += r.length;
|
|
37
|
-
let a = h(r, s);
|
|
38
|
-
d.push(n ? [...r, ...a] : [
|
|
39
|
-
...r,
|
|
40
|
-
0,
|
|
41
|
-
...a
|
|
42
|
-
]);
|
|
43
|
-
}
|
|
44
|
-
let p = [];
|
|
45
|
-
for (let e = 0; e < d[0].length; e++) for (let t = 0; t < i; t++) (e !== l || t >= i - u) && p.push(d[t][e]);
|
|
46
|
-
return p;
|
|
47
|
-
}
|
|
48
|
-
function E(e, t) {
|
|
49
|
-
let n = e.map((e) => Object.freeze([...e]));
|
|
50
|
-
return Object.freeze(n), Object.freeze({
|
|
51
|
-
...t,
|
|
52
|
-
get: (e, r) => e >= 0 && r >= 0 && e < t.size && r < t.size && n[r][e],
|
|
53
|
-
modules: n
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
function D(e, t = {}) {
|
|
57
|
-
S(t);
|
|
58
|
-
let r = typeof e == "string" ? e : new TextDecoder().decode(e);
|
|
59
|
-
if (r.length === 0) throw new b("Cannot encode an empty input");
|
|
60
|
-
let a = t.errorCorrection ?? "M", o = t.mode ?? _(r);
|
|
61
|
-
if (o !== "byte" && _(r) !== o && !O(r, o)) throw new b(`Input cannot be encoded in ${o} mode`);
|
|
62
|
-
let s = t.version ?? w(r, o, a, t.minVersion ?? 1);
|
|
63
|
-
if (s === -1 || v(r, o, s) > n(s, a) * 8) {
|
|
64
|
-
let e = new TextEncoder().encode(r).length, n = C(t.version ?? x, a, o);
|
|
65
|
-
throw new y(`Input of ${e} bytes exceeds QR capacity (${n} ${o === "byte" ? "bytes" : "chars"} at version ${t.version ?? x}-${a})`, e, n, t.version ?? x);
|
|
66
|
-
}
|
|
67
|
-
let h = v(r, o, s), D = n(s, a) * 8;
|
|
68
|
-
h / D > .9 && t.version === void 0 && `${Math.round(h / D * 100)}${s}${a}`;
|
|
69
|
-
let A = T(g(r, o, s, a), s, a), j = f(i(s));
|
|
70
|
-
p(j, s), m(j, A);
|
|
71
|
-
let M = t.mask === void 0 ? [
|
|
72
|
-
0,
|
|
73
|
-
1,
|
|
74
|
-
2,
|
|
75
|
-
3,
|
|
76
|
-
4,
|
|
77
|
-
5,
|
|
78
|
-
6,
|
|
79
|
-
7
|
|
80
|
-
] : [t.mask], N = M[0], P = Infinity, F = null;
|
|
81
|
-
for (let e of M) {
|
|
82
|
-
u(j, e), c(j, a, e);
|
|
83
|
-
let t = d(j);
|
|
84
|
-
t < P && (P = t, N = e, F = j.modules.map((e) => [...e])), u(j, e), e !== M[M.length - 1] && k(j);
|
|
85
|
-
}
|
|
86
|
-
return j.modules = F, l(j, s), E(j.modules, {
|
|
87
|
-
errorCorrection: a,
|
|
88
|
-
mask: N,
|
|
89
|
-
mode: o,
|
|
90
|
-
size: j.size,
|
|
91
|
-
version: s
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
function O(t, n) {
|
|
95
|
-
return n === "numeric" ? /^\d+$/.test(t) : n !== "alphanumeric" || [...t].every((t) => e.includes(t));
|
|
96
|
-
}
|
|
97
|
-
function k(e) {
|
|
98
|
-
let t = e.size, n = [];
|
|
99
|
-
for (let e = 0; e <= 8; e++) e !== 6 && n.push([8, e], [e, 8]);
|
|
100
|
-
for (let e = 0; e < 8; e++) n.push([t - 1 - e, 8]);
|
|
101
|
-
for (let e = 0; e < 7; e++) n.push([8, t - 1 - e]);
|
|
102
|
-
n.push([8, t - 8]);
|
|
103
|
-
for (let [t, r] of n) e.modules[r][t] = !1;
|
|
104
|
-
}
|
|
105
|
-
function A(e, t, n) {
|
|
106
|
-
if (!Number.isInteger(e) || e < 1 || e > x) throw new b(`version must be an integer 1–${x}, got ${e}`);
|
|
107
|
-
return C(e, t, n);
|
|
108
|
-
}
|
|
109
|
-
//#endregion
|
|
110
|
-
export { D as encodeQr, A as qrCapacity };
|
|
111
|
-
|
|
1
|
+
import{SigilCapacityError as e,SigilOptionError as t}from"./errors.js";import{ALPHANUMERIC_CHARSET as n,CHAR_COUNT_BITS as r,dataCodewords as i,ecCodewordsPerBlock as a,matrixSize as o,numEcBlocks as s,totalCodewords as c,versionGroup as l}from"./_tables.js";import{drawFormatInfo as u,drawVersionInfo as d}from"./_format.js";import{applyMask as f,penaltyScore as p}from"./_mask.js";import{createGrid as m,drawFunctionPatterns as h,placeData as g}from"./_matrix.js";import{ecCodewords as _}from"./_reed-solomon.js";import{buildDataCodewords as v,detectMode as y,payloadFromBytes as b,payloadFromText as x,payloadLength as S,segmentBits as C}from"./_segments.js";var w=40;function checkOptions(e){let{version:n,minVersion:r,mask:i,errorCorrection:a}=e,inRange=e=>Number.isInteger(e)&&e>=1&&e<=w;if(n!==void 0&&!inRange(n))throw new t(`version must be an integer 1–${w}, got ${n}`);if(r!==void 0&&!inRange(r))throw new t(`minVersion must be an integer 1–${w}, got ${r}`);if(n!==void 0&&r!==void 0&&r>n)throw new t(`minVersion (${r}) exceeds version (${n})`);if(i!==void 0&&(!Number.isInteger(i)||i<0||i>7))throw new t(`mask must be an integer 0–7, got ${i}`);if(a!==void 0&&!`LMQH`.includes(a))throw new t(`errorCorrection must be 'L', 'M', 'Q', or 'H'`)}function capacityFor(e,t,n){let a=r[n][l(e)],o=i(e,t)*8-4-a;if(o<=0)return 0;switch(n){case`numeric`:return Math.floor(o/10)*3+(o%10>=7?2:+(o%10>=4));case`alphanumeric`:return Math.floor(o/11)*2+ +(o%11>=6);case`byte`:return Math.floor(o/8)}}function pickVersion(e,t,n){for(let r=n;r<=w;r++)if(C(e,r)<=i(r,t)*8)return r;return null}function capacityError(t,n,r){let i=S(t),a=capacityFor(r,n,t.mode),o=t.mode===`byte`?`bytes`:`chars`;return new e(`Input of ${i} ${o} exceeds QR capacity (${a} ${o} at version ${r}-${n})`,i,a,r)}function interleaveBlocks(e,t,n){let r=s(t,n),i=a(t,n),o=c(t),l=Math.floor(o/r)-i,u=o%r,d=[],f=0;for(let t=0;t<r;t++){let n=t>=r-u,a=e.slice(f,f+l+ +!!n);f+=a.length;let o=_(a,i);d.push(n?[...a,...o]:[...a,0,...o])}let p=[];for(let e=0;e<d[0].length;e++)for(let t=0;t<r;t++)(e!==l||t>=r-u)&&p.push(d[t][e]);return p}function freezeMatrix(e,t){let n=e.map(e=>Object.freeze([...e]));return Object.freeze(n),Object.freeze({...t,get:(e,r)=>e>=0&&r>=0&&e<t.size&&r<t.size&&n[r][e]})}function encodeQr(e,n={}){if(checkOptions(n),e.length===0)throw new t(`Cannot encode an empty input`);let r=n.errorCorrection??`M`;if(typeof e!=`string`&&n.mode!==void 0&&n.mode!==`byte`)throw new t(`Uint8Array input can only be encoded in byte mode`);let a=n.mode??(typeof e==`string`?y(e):`byte`);if(typeof e==`string`&&!isModeCompatible(e,a))throw new t(`Input cannot be encoded in ${a} mode`);let s=typeof e==`string`?x(e,a):b(e),c=n.version??pickVersion(s,r,n.minVersion??1);if(c===null||C(s,c)>i(c,r)*8)throw capacityError(s,r,n.version??w);let l=interleaveBlocks(v(s,c,r),c,r),_=m(o(c));h(_,c),g(_,l);let S=n.mask===void 0?[0,1,2,3,4,5,6,7]:[n.mask],T=S[0],E=1/0;for(let e of S){f(_,e),u(_,r,e);let t=p(_);t<E&&(E=t,T=e),f(_,e)}return f(_,T),u(_,r,T),d(_,c),freezeMatrix(_.modules,{errorCorrection:r,mask:T,mode:a,size:_.size,version:c})}function isModeCompatible(e,t){return t===`numeric`?/^\d+$/.test(e):t!==`alphanumeric`||[...e].every(e=>n.includes(e))}function qrCapacity(e,n,r){if(!Number.isInteger(e)||e<1||e>w)throw new t(`version must be an integer 1–${w}, got ${e}`);return capacityFor(e,n,r)}export{encodeQr,qrCapacity};
|
|
112
2
|
//# sourceMappingURL=encode.js.map
|
package/dist/encode.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encode.js","names":[],"sources":["../src/encode.ts"],"sourcesContent":["import { warn } from './_dev';\nimport { drawFormatInfo, drawVersionInfo } from './_format';\nimport { applyMask, penaltyScore } from './_mask';\nimport { createGrid, drawFunctionPatterns, placeData } from './_matrix';\nimport { ecCodewords } from './_reed-solomon';\nimport { buildDataCodewords, detectMode, segmentBits } from './_segments';\nimport {\n ALPHANUMERIC_CHARSET,\n CHAR_COUNT_BITS,\n dataCodewords,\n ecCodewordsPerBlock,\n matrixSize,\n numEcBlocks,\n totalCodewords,\n versionGroup,\n} from './_tables';\nimport { SigilCapacityError, SigilOptionError } from './errors';\nimport type { QrEncodeOptions, QrErrorCorrection, QrMatrix, QrMode } from './types';\n\nconst MAX_VERSION = 40;\n\nfunction checkOptions(options: QrEncodeOptions): void {\n const { version, minVersion, mask, errorCorrection } = options;\n const inRange = (v: number) => Number.isInteger(v) && v >= 1 && v <= MAX_VERSION;\n if (version !== undefined && !inRange(version))\n throw new SigilOptionError(`version must be an integer 1–${MAX_VERSION}, got ${version}`);\n if (minVersion !== undefined && !inRange(minVersion))\n throw new SigilOptionError(`minVersion must be an integer 1–${MAX_VERSION}, got ${minVersion}`);\n if (version !== undefined && minVersion !== undefined && minVersion > version)\n throw new SigilOptionError(`minVersion (${minVersion}) exceeds version (${version})`);\n if (mask !== undefined && (!Number.isInteger(mask) || mask < 0 || mask > 7))\n throw new SigilOptionError(`mask must be an integer 0–7, got ${mask}`);\n if (errorCorrection !== undefined && !'LMQH'.includes(errorCorrection))\n throw new SigilOptionError(`errorCorrection must be 'L', 'M', 'Q', or 'H'`);\n}\n\n/** Character capacity of (version, level, mode) — the `qrCapacity` core. */\nfunction capacityFor(version: number, level: QrErrorCorrection, mode: QrMode): number {\n const countBits = CHAR_COUNT_BITS[mode][versionGroup(version)];\n const usable = dataCodewords(version, level) * 8 - 4 - countBits;\n if (usable <= 0) return 0;\n switch (mode) {\n case 'numeric':\n return Math.floor(usable / 10) * 3 + (usable % 10 >= 7 ? 2 : usable % 10 >= 4 ? 1 : 0);\n case 'alphanumeric':\n return Math.floor(usable / 11) * 2 + (usable % 11 >= 6 ? 1 : 0);\n case 'byte':\n return Math.floor(usable / 8);\n }\n}\n\n/** Smallest version ≥ minVersion whose data area fits the segment. */\nfunction pickVersion(data: string, mode: QrMode, level: QrErrorCorrection, minVersion: number): number {\n for (let v = minVersion; v <= MAX_VERSION; v++)\n if (segmentBits(data, mode, v) <= dataCodewords(v, level) * 8) return v;\n // Nothing fits — the caller reports against v40.\n return -1;\n}\n\n/** Split data codewords into EC blocks and interleave data + EC (§7.6). */\nfunction interleaveBlocks(data: readonly number[], version: number, level: QrErrorCorrection): number[] {\n const numBlocks = numEcBlocks(version, level);\n const ecLen = ecCodewordsPerBlock(version, level);\n const rawLen = totalCodewords(version);\n const shortLen = Math.floor(rawLen / numBlocks) - ecLen; // data codewords in a short block\n const numLong = rawLen % numBlocks; // long blocks carry one extra data codeword\n\n const blocks: number[][] = [];\n let offset = 0;\n for (let b = 0; b < numBlocks; b++) {\n const isLong = b >= numBlocks - numLong;\n const dat = data.slice(offset, offset + shortLen + (isLong ? 1 : 0));\n offset += dat.length;\n const ecc = ecCodewords(dat, ecLen);\n // Short blocks get a pad slot so every block array shares one length;\n // the slot is skipped during interleave below.\n blocks.push(isLong ? [...dat, ...ecc] : [...dat, 0, ...ecc]);\n }\n\n const out: number[] = [];\n for (let i = 0; i < blocks[0].length; i++)\n for (let b = 0; b < numBlocks; b++) {\n // Skip the pad slot on short blocks (index = shortLen).\n if (i !== shortLen || b >= numBlocks - numLong) out.push(blocks[b][i]);\n }\n return out;\n}\n\nfunction freezeMatrix(grid: readonly (readonly boolean[])[], meta: Omit<QrMatrix, 'modules' | 'get'>): QrMatrix {\n const modules = grid.map((row) => Object.freeze([...row]));\n Object.freeze(modules);\n return Object.freeze({\n ...meta,\n get: (x: number, y: number) => (x >= 0 && y >= 0 && x < meta.size && y < meta.size ? modules[y][x] : false),\n modules,\n });\n}\n\n/**\n * Encode `data` into a QR Model 2 symbol. Pure and synchronous.\n * Byte mode is UTF-8 without an ECI header — the industry-standard assumption.\n */\nexport function encodeQr(data: string | Uint8Array, options: QrEncodeOptions = {}): QrMatrix {\n checkOptions(options);\n const text = typeof data === 'string' ? data : new TextDecoder().decode(data);\n if (text.length === 0) throw new SigilOptionError('Cannot encode an empty input');\n\n const level = options.errorCorrection ?? 'M';\n const mode = options.mode ?? detectMode(text);\n if (mode !== 'byte' && detectMode(text) !== mode && !isModeCompatible(text, mode))\n throw new SigilOptionError(`Input cannot be encoded in ${mode} mode`);\n\n const version = options.version ?? pickVersion(text, mode, level, options.minVersion ?? 1);\n if (version === -1 || segmentBits(text, mode, version) > dataCodewords(version, level) * 8) {\n const bytes = new TextEncoder().encode(text).length;\n const max = capacityFor(options.version ?? MAX_VERSION, level, mode);\n throw new SigilCapacityError(\n `Input of ${bytes} bytes exceeds QR capacity (${max} ${mode === 'byte' ? 'bytes' : 'chars'} ` +\n `at version ${options.version ?? MAX_VERSION}-${level})`,\n bytes,\n max,\n options.version ?? MAX_VERSION,\n );\n }\n\n const used = segmentBits(text, mode, version);\n const capacity = dataCodewords(version, level) * 8;\n if (used / capacity > 0.9 && options.version === undefined)\n warn(\n `Payload uses ${Math.round((used / capacity) * 100)}% of version ${version}-${level} ` +\n `capacity; a slightly longer input will bump the QR size`,\n );\n\n const codewords = interleaveBlocks(buildDataCodewords(text, mode, version, level), version, level);\n const grid = createGrid(matrixSize(version));\n drawFunctionPatterns(grid, version);\n placeData(grid, codewords);\n\n // Try every mask (or the forced one), keep the lowest penalty.\n const candidates = options.mask !== undefined ? [options.mask] : [0, 1, 2, 3, 4, 5, 6, 7];\n let bestMask = candidates[0];\n let bestScore = Number.POSITIVE_INFINITY;\n let bestModules: boolean[][] | null = null;\n for (const m of candidates) {\n applyMask(grid, m);\n drawFormatInfo(grid, level, m);\n const score = penaltyScore(grid);\n if (score < bestScore) {\n bestScore = score;\n bestMask = m;\n bestModules = grid.modules.map((row) => [...row]);\n }\n applyMask(grid, m); // undo\n // Clear format info for the next candidate (function flags stay set).\n if (m !== candidates[candidates.length - 1]) clearFormat(grid);\n }\n\n grid.modules = bestModules as boolean[][];\n drawVersionInfo(grid, version);\n\n return freezeMatrix(grid.modules, {\n errorCorrection: level,\n mask: bestMask,\n mode,\n size: grid.size,\n version,\n });\n}\n\n/** Whether `text` is representable in `mode` (used when the mode is forced). */\nfunction isModeCompatible(text: string, mode: QrMode): boolean {\n if (mode === 'numeric') return /^\\d+$/.test(text);\n if (mode === 'alphanumeric') return [...text].every((ch) => ALPHANUMERIC_CHARSET.includes(ch));\n return true;\n}\n\n/** Un-mark and un-draw format modules so the next mask candidate starts clean. */\nfunction clearFormat(grid: ReturnType<typeof createGrid>): void {\n const size = grid.size;\n const cells: Array<readonly [number, number]> = [];\n for (let i = 0; i <= 8; i++) {\n if (i !== 6) {\n cells.push([8, i], [i, 8]);\n }\n }\n for (let i = 0; i < 8; i++) cells.push([size - 1 - i, 8]);\n for (let i = 0; i < 7; i++) cells.push([8, size - 1 - i]);\n cells.push([8, size - 8]);\n for (const [x, y] of cells) grid.modules[y][x] = false;\n}\n\n/** Max characters/bytes encodable at (version, level, mode). */\nexport function qrCapacity(version: number, errorCorrection: QrErrorCorrection, mode: QrMode): number {\n if (!Number.isInteger(version) || version < 1 || version > MAX_VERSION)\n throw new SigilOptionError(`version must be an integer 1–${MAX_VERSION}, got ${version}`);\n return capacityFor(version, errorCorrection, mode);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"encode.js","names":[],"sources":["../src/encode.ts"],"sourcesContent":["import { drawFormatInfo, drawVersionInfo } from './_format';\nimport { applyMask, penaltyScore } from './_mask';\nimport { createGrid, drawFunctionPatterns, placeData } from './_matrix';\nimport { ecCodewords } from './_reed-solomon';\nimport {\n buildDataCodewords,\n detectMode,\n payloadFromBytes,\n payloadFromText,\n payloadLength,\n type QrPayload,\n segmentBits,\n} from './_segments';\nimport {\n ALPHANUMERIC_CHARSET,\n CHAR_COUNT_BITS,\n dataCodewords,\n ecCodewordsPerBlock,\n matrixSize,\n numEcBlocks,\n totalCodewords,\n versionGroup,\n} from './_tables';\nimport { SigilCapacityError, SigilOptionError } from './errors';\nimport type { QrEncodeOptions, QrErrorCorrection, QrMatrix, QrMode } from './types';\n\nconst MAX_VERSION = 40;\n\nfunction checkOptions(options: QrEncodeOptions): void {\n const { version, minVersion, mask, errorCorrection } = options;\n const inRange = (v: number) => Number.isInteger(v) && v >= 1 && v <= MAX_VERSION;\n if (version !== undefined && !inRange(version))\n throw new SigilOptionError(`version must be an integer 1–${MAX_VERSION}, got ${version}`);\n if (minVersion !== undefined && !inRange(minVersion))\n throw new SigilOptionError(`minVersion must be an integer 1–${MAX_VERSION}, got ${minVersion}`);\n if (version !== undefined && minVersion !== undefined && minVersion > version)\n throw new SigilOptionError(`minVersion (${minVersion}) exceeds version (${version})`);\n if (mask !== undefined && (!Number.isInteger(mask) || mask < 0 || mask > 7))\n throw new SigilOptionError(`mask must be an integer 0–7, got ${mask}`);\n if (errorCorrection !== undefined && !'LMQH'.includes(errorCorrection))\n throw new SigilOptionError(`errorCorrection must be 'L', 'M', 'Q', or 'H'`);\n}\n\n/** Character capacity of (version, level, mode) — the `qrCapacity` core. */\nfunction capacityFor(version: number, level: QrErrorCorrection, mode: QrMode): number {\n const countBits = CHAR_COUNT_BITS[mode][versionGroup(version)];\n const usable = dataCodewords(version, level) * 8 - 4 - countBits;\n if (usable <= 0) return 0;\n switch (mode) {\n case 'numeric':\n return Math.floor(usable / 10) * 3 + (usable % 10 >= 7 ? 2 : usable % 10 >= 4 ? 1 : 0);\n case 'alphanumeric':\n return Math.floor(usable / 11) * 2 + (usable % 11 >= 6 ? 1 : 0);\n case 'byte':\n return Math.floor(usable / 8);\n }\n}\n\n/** Smallest version ≥ minVersion whose data area fits the segment, or `null`. */\nfunction pickVersion(payload: QrPayload, level: QrErrorCorrection, minVersion: number): number | null {\n for (let v = minVersion; v <= MAX_VERSION; v++) if (segmentBits(payload, v) <= dataCodewords(v, level) * 8) return v;\n return null;\n}\n\n/** The capacity error for a payload that does not fit at `version`. */\nfunction capacityError(payload: QrPayload, level: QrErrorCorrection, version: number): SigilCapacityError {\n const length = payloadLength(payload);\n const max = capacityFor(version, level, payload.mode);\n const unit = payload.mode === 'byte' ? 'bytes' : 'chars';\n return new SigilCapacityError(\n `Input of ${length} ${unit} exceeds QR capacity (${max} ${unit} at version ${version}-${level})`,\n length,\n max,\n version,\n );\n}\n\n/** Split data codewords into EC blocks and interleave data + EC (§7.6). */\nfunction interleaveBlocks(data: readonly number[], version: number, level: QrErrorCorrection): number[] {\n const numBlocks = numEcBlocks(version, level);\n const ecLen = ecCodewordsPerBlock(version, level);\n const rawLen = totalCodewords(version);\n const shortLen = Math.floor(rawLen / numBlocks) - ecLen; // data codewords in a short block\n const numLong = rawLen % numBlocks; // long blocks carry one extra data codeword\n\n const blocks: number[][] = [];\n let offset = 0;\n for (let b = 0; b < numBlocks; b++) {\n const isLong = b >= numBlocks - numLong;\n const dat = data.slice(offset, offset + shortLen + (isLong ? 1 : 0));\n offset += dat.length;\n const ecc = ecCodewords(dat, ecLen);\n // Short blocks get a pad slot so every block array shares one length;\n // the slot is skipped during interleave below.\n blocks.push(isLong ? [...dat, ...ecc] : [...dat, 0, ...ecc]);\n }\n\n const out: number[] = [];\n for (let i = 0; i < blocks[0].length; i++)\n for (let b = 0; b < numBlocks; b++) {\n // Skip the pad slot on short blocks (index = shortLen).\n if (i !== shortLen || b >= numBlocks - numLong) out.push(blocks[b][i]);\n }\n return out;\n}\n\nfunction freezeMatrix(grid: readonly (readonly boolean[])[], meta: Omit<QrMatrix, 'get'>): QrMatrix {\n const modules = grid.map((row) => Object.freeze([...row]));\n Object.freeze(modules);\n return Object.freeze({\n ...meta,\n get: (x: number, y: number) => (x >= 0 && y >= 0 && x < meta.size && y < meta.size ? modules[y][x] : false),\n });\n}\n\n/**\n * Encode `data` into a QR Model 2 symbol. Pure and synchronous.\n * Strings pick the most compact mode; `Uint8Array` is raw binary and always\n * byte mode, encoded as-is — no UTF-8 round-trip. Byte mode is UTF-8 without\n * an ECI header for string input — the industry-standard assumption.\n */\nexport function encodeQr(data: string | Uint8Array, options: QrEncodeOptions = {}): QrMatrix {\n checkOptions(options);\n if (data.length === 0) throw new SigilOptionError('Cannot encode an empty input');\n\n const level = options.errorCorrection ?? 'M';\n if (typeof data !== 'string' && options.mode !== undefined && options.mode !== 'byte')\n throw new SigilOptionError('Uint8Array input can only be encoded in byte mode');\n const mode = options.mode ?? (typeof data === 'string' ? detectMode(data) : 'byte');\n if (typeof data === 'string' && !isModeCompatible(data, mode))\n throw new SigilOptionError(`Input cannot be encoded in ${mode} mode`);\n const payload = typeof data === 'string' ? payloadFromText(data, mode) : payloadFromBytes(data);\n\n const version = options.version ?? pickVersion(payload, level, options.minVersion ?? 1);\n if (version === null || segmentBits(payload, version) > dataCodewords(version, level) * 8)\n throw capacityError(payload, level, options.version ?? MAX_VERSION);\n\n const codewords = interleaveBlocks(buildDataCodewords(payload, version, level), version, level);\n const grid = createGrid(matrixSize(version));\n drawFunctionPatterns(grid, version);\n placeData(grid, codewords);\n\n // Try every mask (or the forced one), keep the lowest penalty. Each candidate\n // is undone by the idempotent XOR, and the final drawFormatInfo below rewrites\n // every format module, so candidates never leak into each other.\n const candidates = options.mask !== undefined ? [options.mask] : [0, 1, 2, 3, 4, 5, 6, 7];\n let bestMask = candidates[0];\n let bestScore = Number.POSITIVE_INFINITY;\n for (const m of candidates) {\n applyMask(grid, m);\n drawFormatInfo(grid, level, m);\n const score = penaltyScore(grid);\n if (score < bestScore) {\n bestScore = score;\n bestMask = m;\n }\n applyMask(grid, m); // undo\n }\n\n // The grid is unmasked again — draw the winning candidate once, finally.\n applyMask(grid, bestMask);\n drawFormatInfo(grid, level, bestMask);\n drawVersionInfo(grid, version);\n\n return freezeMatrix(grid.modules, {\n errorCorrection: level,\n mask: bestMask,\n mode,\n size: grid.size,\n version,\n });\n}\n\n/** Whether `text` is representable in `mode` (used when the mode is forced). */\nfunction isModeCompatible(text: string, mode: QrMode): boolean {\n if (mode === 'numeric') return /^\\d+$/.test(text);\n if (mode === 'alphanumeric') return [...text].every((ch) => ALPHANUMERIC_CHARSET.includes(ch));\n return true;\n}\n\n/** Max characters/bytes encodable at (version, level, mode). */\nexport function qrCapacity(version: number, errorCorrection: QrErrorCorrection, mode: QrMode): number {\n if (!Number.isInteger(version) || version < 1 || version > MAX_VERSION)\n throw new SigilOptionError(`version must be an integer 1–${MAX_VERSION}, got ${version}`);\n return capacityFor(version, errorCorrection, mode);\n}\n"],"mappings":"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"}
|
package/dist/errors.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
var SigilError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},SigilCapacityError=class extends SigilError{bytes;maxBytes;version;constructor(e,t,n,r,i){super(e,i),this.bytes=t,this.maxBytes=n,this.version=r}},SigilOptionError=class extends SigilError{},SigilUnsupportedError=class extends SigilError{},SigilPermissionError=class extends SigilError{},SigilDisposedError=class extends SigilError{};exports.SigilCapacityError=SigilCapacityError,exports.SigilDisposedError=SigilDisposedError,exports.SigilError=SigilError,exports.SigilOptionError=SigilOptionError,exports.SigilPermissionError=SigilPermissionError,exports.SigilUnsupportedError=SigilUnsupportedError;
|
|
2
2
|
//# sourceMappingURL=errors.cjs.map
|
package/dist/errors.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base class for all sigil errors.\n * Use `instanceof SigilError` to catch any sigil-originated error in one branch.\n */\nexport class SigilError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when input data does not fit in the requested version/level. */\nexport class SigilCapacityError extends SigilError {\n readonly bytes: number;\n readonly maxBytes: number;\n readonly version: number;\n\n constructor(message: string, bytes: number, maxBytes: number, version: number, opts?: ErrorOptions) {\n super(message, opts);\n this.bytes = bytes;\n this.maxBytes = maxBytes;\n this.version = version;\n }\n}\n\n/** Thrown for an invalid option: version/minVersion out of range, mask outside 0–7. */\nexport class SigilOptionError extends SigilError {}\n\n/**\n * Thrown when the environment lacks `BarcodeDetector`, `getUserMedia`, or QR\n * format support. Feature-detect with `isQrScanSupported()`/`qrScanSupport()`.\n */\nexport class SigilUnsupportedError extends SigilError {}\n\n/** Thrown when camera access is denied (wraps `NotAllowedError`). */\nexport class SigilPermissionError extends SigilError {}\n\n/** Thrown when a disposed scanner is used. */\nexport class SigilDisposedError extends SigilError {}\n"],"mappings":"AAIA,IAAa,
|
|
1
|
+
{"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base class for all sigil errors.\n * Use `instanceof SigilError` to catch any sigil-originated error in one branch.\n */\nexport class SigilError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when input data does not fit in the requested version/level. */\nexport class SigilCapacityError extends SigilError {\n readonly bytes: number;\n readonly maxBytes: number;\n readonly version: number;\n\n constructor(message: string, bytes: number, maxBytes: number, version: number, opts?: ErrorOptions) {\n super(message, opts);\n this.bytes = bytes;\n this.maxBytes = maxBytes;\n this.version = version;\n }\n}\n\n/** Thrown for an invalid option: version/minVersion out of range, mask outside 0–7. */\nexport class SigilOptionError extends SigilError {}\n\n/**\n * Thrown when the environment lacks `BarcodeDetector`, `getUserMedia`, or QR\n * format support. Feature-detect with `isQrScanSupported()`/`qrScanSupport()`.\n */\nexport class SigilUnsupportedError extends SigilError {}\n\n/** Thrown when camera access is denied (wraps `NotAllowedError`). */\nexport class SigilPermissionError extends SigilError {}\n\n/** Thrown when a disposed scanner is used. */\nexport class SigilDisposedError extends SigilError {}\n"],"mappings":"AAIA,IAAa,WAAb,cAAgC,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,mBAAb,cAAwC,UAAW,CACjD,MACA,SACA,QAEA,YAAY,EAAiB,EAAe,EAAkB,EAAiB,EAAqB,CAClG,MAAM,EAAS,CAAI,EACnB,KAAK,MAAQ,EACb,KAAK,SAAW,EAChB,KAAK,QAAU,CACjB,CACF,EAGa,iBAAb,cAAsC,UAAW,CAAC,EAMrC,sBAAb,cAA2C,UAAW,CAAC,EAG1C,qBAAb,cAA0C,UAAW,CAAC,EAGzC,mBAAb,cAAwC,UAAW,CAAC"}
|
package/dist/errors.js
CHANGED
|
@@ -1,17 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
var e = class extends Error {
|
|
3
|
-
constructor(e, t) {
|
|
4
|
-
super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
|
|
5
|
-
}
|
|
6
|
-
}, t = class extends e {
|
|
7
|
-
bytes;
|
|
8
|
-
maxBytes;
|
|
9
|
-
version;
|
|
10
|
-
constructor(e, t, n, r, i) {
|
|
11
|
-
super(e, i), this.bytes = t, this.maxBytes = n, this.version = r;
|
|
12
|
-
}
|
|
13
|
-
}, n = class extends e {}, r = class extends e {}, i = class extends e {}, a = class extends e {};
|
|
14
|
-
//#endregion
|
|
15
|
-
export { t as SigilCapacityError, a as SigilDisposedError, e as SigilError, n as SigilOptionError, i as SigilPermissionError, r as SigilUnsupportedError };
|
|
16
|
-
|
|
1
|
+
var SigilError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},SigilCapacityError=class extends SigilError{bytes;maxBytes;version;constructor(e,t,n,r,i){super(e,i),this.bytes=t,this.maxBytes=n,this.version=r}},SigilOptionError=class extends SigilError{},SigilUnsupportedError=class extends SigilError{},SigilPermissionError=class extends SigilError{},SigilDisposedError=class extends SigilError{};export{SigilCapacityError,SigilDisposedError,SigilError,SigilOptionError,SigilPermissionError,SigilUnsupportedError};
|
|
17
2
|
//# sourceMappingURL=errors.js.map
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base class for all sigil errors.\n * Use `instanceof SigilError` to catch any sigil-originated error in one branch.\n */\nexport class SigilError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when input data does not fit in the requested version/level. */\nexport class SigilCapacityError extends SigilError {\n readonly bytes: number;\n readonly maxBytes: number;\n readonly version: number;\n\n constructor(message: string, bytes: number, maxBytes: number, version: number, opts?: ErrorOptions) {\n super(message, opts);\n this.bytes = bytes;\n this.maxBytes = maxBytes;\n this.version = version;\n }\n}\n\n/** Thrown for an invalid option: version/minVersion out of range, mask outside 0–7. */\nexport class SigilOptionError extends SigilError {}\n\n/**\n * Thrown when the environment lacks `BarcodeDetector`, `getUserMedia`, or QR\n * format support. Feature-detect with `isQrScanSupported()`/`qrScanSupport()`.\n */\nexport class SigilUnsupportedError extends SigilError {}\n\n/** Thrown when camera access is denied (wraps `NotAllowedError`). */\nexport class SigilPermissionError extends SigilError {}\n\n/** Thrown when a disposed scanner is used. */\nexport class SigilDisposedError extends SigilError {}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base class for all sigil errors.\n * Use `instanceof SigilError` to catch any sigil-originated error in one branch.\n */\nexport class SigilError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when input data does not fit in the requested version/level. */\nexport class SigilCapacityError extends SigilError {\n readonly bytes: number;\n readonly maxBytes: number;\n readonly version: number;\n\n constructor(message: string, bytes: number, maxBytes: number, version: number, opts?: ErrorOptions) {\n super(message, opts);\n this.bytes = bytes;\n this.maxBytes = maxBytes;\n this.version = version;\n }\n}\n\n/** Thrown for an invalid option: version/minVersion out of range, mask outside 0–7. */\nexport class SigilOptionError extends SigilError {}\n\n/**\n * Thrown when the environment lacks `BarcodeDetector`, `getUserMedia`, or QR\n * format support. Feature-detect with `isQrScanSupported()`/`qrScanSupport()`.\n */\nexport class SigilUnsupportedError extends SigilError {}\n\n/** Thrown when camera access is denied (wraps `NotAllowedError`). */\nexport class SigilPermissionError extends SigilError {}\n\n/** Thrown when a disposed scanner is used. */\nexport class SigilDisposedError extends SigilError {}\n"],"mappings":"AAIA,IAAa,WAAb,cAAgC,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,mBAAb,cAAwC,UAAW,CACjD,MACA,SACA,QAEA,YAAY,EAAiB,EAAe,EAAkB,EAAiB,EAAqB,CAClG,MAAM,EAAS,CAAI,EACnB,KAAK,MAAQ,EACb,KAAK,SAAW,EAChB,KAAK,QAAU,CACjB,CACF,EAGa,iBAAb,cAAsC,UAAW,CAAC,EAMrC,sBAAb,cAA2C,UAAW,CAAC,EAG1C,qBAAb,cAA0C,UAAW,CAAC,EAGzC,mBAAb,cAAwC,UAAW,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./canvas.cjs"),n=require("./encode.cjs"),r=require("./scan.cjs"),i=require("./svg.cjs");exports.SigilCapacityError=e.SigilCapacityError,exports.SigilDisposedError=e.SigilDisposedError,exports.SigilError=e.SigilError,exports.SigilOptionError=e.SigilOptionError,exports.SigilPermissionError=e.SigilPermissionError,exports.SigilUnsupportedError=e.SigilUnsupportedError,exports.createQrScanner=r.createQrScanner,exports.detectQr=r.detectQr,exports.drawToCanvas=t.drawToCanvas,exports.encodeQr=n.encodeQr,exports.isQrScanSupported=r.isQrScanSupported,exports.qrCapacity=n.qrCapacity,exports.qrScanSupport=r.qrScanSupport,exports.toSvg=i.toSvg;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { SigilCapacityError as t, SigilDisposedError as n, SigilError as r, SigilOptionError as i, SigilPermissionError as a, SigilUnsupportedError as o } from "./errors.js";
|
|
3
|
-
import { encodeQr as s, qrCapacity as c } from "./encode.js";
|
|
4
|
-
import { createQrScanner as l, detectQr as u, isQrScanSupported as d, qrScanSupport as f } from "./scan.js";
|
|
5
|
-
import { toSvg as p } from "./svg.js";
|
|
6
|
-
export { t as SigilCapacityError, n as SigilDisposedError, r as SigilError, i as SigilOptionError, a as SigilPermissionError, o as SigilUnsupportedError, l as createQrScanner, u as detectQr, e as drawToCanvas, s as encodeQr, d as isQrScanSupported, c as qrCapacity, f as qrScanSupport, p as toSvg };
|
|
1
|
+
import{SigilCapacityError as e,SigilDisposedError as t,SigilError as n,SigilOptionError as r,SigilPermissionError as i,SigilUnsupportedError as a}from"./errors.js";import{drawToCanvas as o}from"./canvas.js";import{encodeQr as s,qrCapacity as c}from"./encode.js";import{createQrScanner as l,detectQr as u,isQrScanSupported as d,qrScanSupport as f}from"./scan.js";import{toSvg as p}from"./svg.js";export{e as SigilCapacityError,t as SigilDisposedError,n as SigilError,r as SigilOptionError,i as SigilPermissionError,a as SigilUnsupportedError,l as createQrScanner,u as detectQr,o as drawToCanvas,s as encodeQr,d as isQrScanSupported,c as qrCapacity,f as qrScanSupport,p as toSvg};
|
package/dist/scan.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs");var t=globalThis,
|
|
1
|
+
const e=require("./errors.cjs");var t=globalThis,barcodeDetector=()=>t.BarcodeDetector;function isQrScanSupported(){return typeof barcodeDetector()==`function`}async function qrScanSupport(){let e=barcodeDetector();if(typeof e!=`function`)return!1;if(typeof e.getSupportedFormats!=`function`)return!0;try{return(await e.getSupportedFormats()).includes(`qr_code`)}catch{return!1}}async function detectQr(t,n={}){n.signal?.throwIfAborted();let r=barcodeDetector(),i=n.detector??(r?new r({formats:[`qr_code`]}):void 0);if(!i)throw new e.SigilUnsupportedError(`BarcodeDetector is not available in this environment`);let a=await i.detect(t);n.signal?.throwIfAborted();let o=a[0];return o?{cornerPoints:o.cornerPoints,value:o.rawValue}:null}function createQrScanner(t){let n=t.mediaDevices??globalThis.navigator?.mediaDevices,r=t.once??!0,i=t.intervalMs??200,a=new AbortController,o=new Set,s=new Set,c=`idle`,l=null,u=null,d=!1,f,emit=e=>{if(o.size!==0)for(let t of[...o])try{t(e)}catch{}},setStatus=e=>{c!==e&&(c=e,emit({status:e,type:`status-change`}))},isStarting=()=>c===`starting`,stopTracks=()=>{if(u!==null&&(clearInterval(u),u=null),l){for(let e of l.getTracks())e.stop();l=null}t.video.srcObject=null},fail=e=>{emit({error:e,type:`error`}),stopTracks(),setStatus(`idle`)},getDetector=()=>(f??=(async()=>{if(t.detector)return t.detector;let n=barcodeDetector();if(typeof n!=`function`)throw new e.SigilUnsupportedError(`BarcodeDetector is not available in this environment`);return new n({formats:[`qr_code`]})})(),f),tick=async()=>{if(c!==`scanning`||l===null)return;let n=t.video;if(n.readyState<2){emit({reason:`not-ready`,type:`frame-skipped`});return}if(d){emit({reason:`busy`,type:`frame-skipped`});return}d=!0;let i=Date.now();try{let e=(await(await getDetector()).detect(n))[0];if(e){let t={cornerPoints:e.cornerPoints,value:e.rawValue};emit({elapsedMs:Date.now()-i,type:`detect`,value:t.value});for(let e of[...s])e(t);r&&(stopTracks(),setStatus(`idle`))}}catch(t){fail(t instanceof e.SigilError?t:new e.SigilError(`QR detection failed`,{cause:t}))}finally{d=!1}},p={get disposalSignal(){return a.signal},dispose(){c!==`disposed`&&(emit({type:`dispose`}),stopTracks(),s.clear(),o.clear(),setStatus(`disposed`),a.abort())},get disposed(){return c===`disposed`},onResult(e){return c===`disposed`?()=>{}:(s.add(e),()=>s.delete(e))},async start(){if(c===`disposed`)throw new e.SigilDisposedError(`Scanner is disposed`);if(c===`starting`||c===`scanning`)return;if(!n||typeof n.getUserMedia!=`function`)throw new e.SigilUnsupportedError(`getUserMedia is not available in this environment`);await getDetector(),t.signal?.throwIfAborted(),setStatus(`starting`);let r;try{r=await n.getUserMedia({video:t.constraints??{facingMode:`environment`}})}catch(t){throw setStatus(`idle`),t instanceof DOMException&&t.name===`NotAllowedError`?new e.SigilPermissionError(`Camera permission denied`,{cause:t}):t instanceof Error?t:new e.SigilError(`Camera request failed`,{cause:t})}if(!isStarting()){for(let e of r.getTracks())e.stop();return}if(l=r,t.video.srcObject=l,await t.video.play().catch(()=>{}),!isStarting()){stopTracks();return}setStatus(`scanning`),u=setInterval(()=>void tick(),i),tick()},get status(){return c},stop(){c!==`disposed`&&(stopTracks(),setStatus(`idle`))},tap(e,t){if(c===`disposed`)return()=>{};o.add(e);let off=()=>o.delete(e);return t?.signal?.addEventListener(`abort`,off,{once:!0}),off},[Symbol.dispose](){p.dispose()}};if(t.signal){let onAbort=()=>p.stop();t.signal.addEventListener(`abort`,onAbort,{once:!0}),a.signal.addEventListener(`abort`,()=>t.signal?.removeEventListener(`abort`,onAbort),{once:!0})}return p}exports.createQrScanner=createQrScanner,exports.detectQr=detectQr,exports.isQrScanSupported=isQrScanSupported,exports.qrScanSupport=qrScanSupport;
|
|
2
2
|
//# sourceMappingURL=scan.cjs.map
|
package/dist/scan.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.cjs","names":[],"sources":["../src/scan.ts"],"sourcesContent":["import { SigilDisposedError, SigilError, SigilPermissionError, SigilUnsupportedError } from './errors';\nimport type { QrDetector, QrScanner, QrScannerOptions, QrScannerStatus, QrScanResult, SigilEvent } from './types';\n\n/**\n * Scanning via the native `BarcodeDetector` API — a thin lifecycle wrapper.\n * Feature detection is lazy so importing the package is always safe (SSR).\n * No from-scratch image decoder: unsupported environments get\n * `SigilUnsupportedError` at `start()`/`detectQr()`, never at import.\n */\n\ninterface BarcodeDetectorLike {\n detect(\n source: ImageBitmapSource,\n ): Promise<ReadonlyArray<{ rawValue: string; cornerPoints?: ReadonlyArray<{ x: number; y: number }> }>>;\n}\n\ninterface BarcodeDetectorCtor {\n getSupportedFormats?(): Promise<readonly string[]>;\n new (options?: { formats?: string[] }): BarcodeDetectorLike;\n}\n\nconst globalScope = globalThis as { BarcodeDetector?: BarcodeDetectorCtor };\n\nconst barcodeDetector = (): BarcodeDetectorCtor | undefined => globalScope.BarcodeDetector;\n\n/** Sync check: `BarcodeDetector` exists. Format support needs the async variant. */\nexport function isQrScanSupported(): boolean {\n return typeof barcodeDetector() === 'function';\n}\n\n/** Full check: `BarcodeDetector` exists and reports `qr_code` support. */\nexport async function qrScanSupport(): Promise<boolean> {\n const ctor = barcodeDetector();\n if (typeof ctor !== 'function') return false;\n if (typeof ctor.getSupportedFormats !== 'function') return true;\n try {\n return (await ctor.getSupportedFormats()).includes('qr_code');\n } catch {\n return false;\n }\n}\n\n/** One-shot detection on an image source; `null` when no code is found. */\nexport async function detectQr(\n source: ImageBitmapSource,\n options: { signal?: AbortSignal; detector?: QrDetector } = {},\n): Promise<QrScanResult | null> {\n options.signal?.throwIfAborted();\n const ctor = barcodeDetector();\n const detector = options.detector ?? (ctor ? new ctor({ formats: ['qr_code'] }) : undefined);\n if (!detector) throw new SigilUnsupportedError('BarcodeDetector is not available in this environment');\n const results = await detector.detect(source);\n options.signal?.throwIfAborted();\n const first = results[0];\n return first ? { cornerPoints: first.cornerPoints, value: first.rawValue } : null;\n}\n\nexport function createQrScanner(options: QrScannerOptions): QrScanner {\n const media = options.mediaDevices ?? globalThis.navigator?.mediaDevices;\n const once = options.once ?? true;\n const intervalMs = options.intervalMs ?? 200;\n const disposal = new AbortController();\n const tappers = new Set<(event: SigilEvent) => void>();\n const handlers = new Set<(result: QrScanResult) => void>();\n\n let status: QrScannerStatus = 'idle';\n let stream: MediaStream | null = null;\n let timer: ReturnType<typeof setInterval> | null = null;\n let busy = false;\n\n const emit = (event: SigilEvent): void => {\n if (tappers.size === 0) return;\n for (const handler of [...tappers]) {\n try {\n handler(event);\n } catch {\n // Observability must never affect behavior.\n }\n }\n };\n\n const setStatus = (next: QrScannerStatus): void => {\n if (status === next) return;\n status = next;\n emit({ status: next, type: 'status-change' });\n };\n\n const stopTracks = (): void => {\n if (timer !== null) {\n clearInterval(timer);\n timer = null;\n }\n if (stream) {\n for (const track of stream.getTracks()) track.stop();\n stream = null;\n }\n options.video.srcObject = null;\n };\n\n const fail = (error: SigilError): void => {\n emit({ error, type: 'error' });\n stopTracks();\n setStatus('stopped');\n };\n\n const tick = async (): Promise<void> => {\n if (status !== 'scanning' || stream === null) return;\n const video = options.video;\n if (video.readyState < 2 /* HAVE_CURRENT_DATA */) {\n emit({ reason: 'not-ready', type: 'frame-skipped' });\n return;\n }\n if (busy) {\n emit({ reason: 'busy', type: 'frame-skipped' });\n return;\n }\n busy = true;\n const started = Date.now();\n try {\n const detector = await detectorPromise;\n const results = await detector.detect(video);\n const first = results[0];\n if (first) {\n const result: QrScanResult = { cornerPoints: first.cornerPoints, value: first.rawValue };\n emit({ elapsedMs: Date.now() - started, type: 'detect', value: result.value });\n for (const handler of [...handlers]) handler(result);\n if (once) {\n stopTracks();\n setStatus('stopped');\n }\n }\n } catch (error) {\n fail(error instanceof SigilError ? error : new SigilError('QR detection failed', { cause: error }));\n } finally {\n busy = false;\n }\n };\n\n const detectorPromise: Promise<QrDetector> = Promise.resolve().then(() => {\n if (options.detector) return options.detector;\n const ctor = barcodeDetector();\n if (typeof ctor !== 'function')\n throw new SigilUnsupportedError('BarcodeDetector is not available in this environment');\n return new ctor({ formats: ['qr_code'] });\n });\n\n const api: QrScanner = {\n get disposalSignal() {\n return disposal.signal;\n },\n dispose() {\n if (status === 'disposed') return;\n emit({ type: 'dispose' });\n stopTracks();\n handlers.clear();\n tappers.clear();\n setStatus('disposed');\n disposal.abort();\n },\n get disposed() {\n return status === 'disposed';\n },\n onResult(handler) {\n if (status === 'disposed') return () => {};\n handlers.add(handler);\n return () => handlers.delete(handler);\n },\n async start() {\n if (status === 'disposed') throw new SigilDisposedError('Scanner is disposed');\n if (status === 'starting' || status === 'scanning') return;\n if (!media || typeof media.getUserMedia !== 'function')\n throw new SigilUnsupportedError('getUserMedia is not available in this environment');\n // Surface a missing detector at start() (not construction) per contract.\n await detectorPromise;\n options.signal?.throwIfAborted();\n setStatus('starting');\n try {\n stream = await media.getUserMedia({ video: options.constraints ?? { facingMode: 'environment' } });\n } catch (error) {\n setStatus('idle');\n if (error instanceof DOMException && error.name === 'NotAllowedError')\n throw new SigilPermissionError('Camera permission denied', { cause: error });\n throw error instanceof Error ? error : new SigilError('Camera request failed', { cause: error });\n }\n options.signal?.throwIfAborted();\n options.video.srcObject = stream;\n await options.video.play().catch(() => {});\n setStatus('scanning');\n timer = setInterval(() => void tick(), intervalMs);\n void tick();\n },\n get status() {\n return status;\n },\n stop() {\n if (status === 'disposed') return;\n stopTracks();\n setStatus(status === 'idle' ? 'idle' : 'stopped');\n },\n tap(handler, tapOptions) {\n if (status === 'disposed') return () => {};\n tappers.add(handler);\n const off = () => tappers.delete(handler);\n tapOptions?.signal?.addEventListener('abort', off, { once: true });\n return off;\n },\n [Symbol.dispose]() {\n api.dispose();\n },\n };\n\n options.signal?.addEventListener('abort', () => api.stop(), { once: true });\n return api;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scan.cjs","names":[],"sources":["../src/scan.ts"],"sourcesContent":["import { SigilDisposedError, SigilError, SigilPermissionError, SigilUnsupportedError } from './errors';\nimport type { QrDetector, QrScanner, QrScannerOptions, QrScannerStatus, QrScanResult, SigilEvent } from './types';\n\n/**\n * Scanning via the native `BarcodeDetector` API — a thin lifecycle wrapper.\n * Feature detection is lazy so importing the package is always safe (SSR).\n * No from-scratch image decoder: unsupported environments get\n * `SigilUnsupportedError` at `start()`/`detectQr()`, never at import.\n */\n\ninterface BarcodeDetectorLike {\n detect(\n source: ImageBitmapSource,\n ): Promise<ReadonlyArray<{ rawValue: string; cornerPoints?: ReadonlyArray<{ x: number; y: number }> }>>;\n}\n\ninterface BarcodeDetectorCtor {\n getSupportedFormats?(): Promise<readonly string[]>;\n new (options?: { formats?: string[] }): BarcodeDetectorLike;\n}\n\nconst globalScope = globalThis as { BarcodeDetector?: BarcodeDetectorCtor };\n\nconst barcodeDetector = (): BarcodeDetectorCtor | undefined => globalScope.BarcodeDetector;\n\n/** Sync check: `BarcodeDetector` exists. Format support needs the async variant. */\nexport function isQrScanSupported(): boolean {\n return typeof barcodeDetector() === 'function';\n}\n\n/** Full check: `BarcodeDetector` exists and reports `qr_code` support. */\nexport async function qrScanSupport(): Promise<boolean> {\n const ctor = barcodeDetector();\n if (typeof ctor !== 'function') return false;\n if (typeof ctor.getSupportedFormats !== 'function') return true;\n try {\n return (await ctor.getSupportedFormats()).includes('qr_code');\n } catch {\n return false;\n }\n}\n\n/** One-shot detection on an image source; `null` when no code is found. */\nexport async function detectQr(\n source: ImageBitmapSource,\n options: { signal?: AbortSignal; detector?: QrDetector } = {},\n): Promise<QrScanResult | null> {\n options.signal?.throwIfAborted();\n const ctor = barcodeDetector();\n const detector = options.detector ?? (ctor ? new ctor({ formats: ['qr_code'] }) : undefined);\n if (!detector) throw new SigilUnsupportedError('BarcodeDetector is not available in this environment');\n const results = await detector.detect(source);\n options.signal?.throwIfAborted();\n const first = results[0];\n return first ? { cornerPoints: first.cornerPoints, value: first.rawValue } : null;\n}\n\nexport function createQrScanner(options: QrScannerOptions): QrScanner {\n const media = options.mediaDevices ?? globalThis.navigator?.mediaDevices;\n const once = options.once ?? true;\n const intervalMs = options.intervalMs ?? 200;\n const disposal = new AbortController();\n const tappers = new Set<(event: SigilEvent) => void>();\n const handlers = new Set<(result: QrScanResult) => void>();\n\n let status: QrScannerStatus = 'idle';\n let stream: MediaStream | null = null;\n let timer: ReturnType<typeof setInterval> | null = null;\n let busy = false;\n let detector: Promise<QrDetector> | undefined;\n\n const emit = (event: SigilEvent): void => {\n if (tappers.size === 0) return;\n for (const handler of [...tappers]) {\n try {\n handler(event);\n } catch {\n // Observability must never affect behavior.\n }\n }\n };\n\n const setStatus = (next: QrScannerStatus): void => {\n if (status === next) return;\n status = next;\n emit({ status: next, type: 'status-change' });\n };\n\n // Read through a function: status changes across awaits via closures, and a\n // direct read would be narrowed to the pre-await value by the compiler.\n const isStarting = (): boolean => status === 'starting';\n\n const stopTracks = (): void => {\n if (timer !== null) {\n clearInterval(timer);\n timer = null;\n }\n if (stream) {\n for (const track of stream.getTracks()) track.stop();\n stream = null;\n }\n options.video.srcObject = null;\n };\n\n const fail = (error: SigilError): void => {\n emit({ error, type: 'error' });\n stopTracks();\n setStatus('idle');\n };\n\n // Created on first start() so a missing BarcodeDetector never rejects a\n // promise nobody awaits (construction must stay side-effect-free).\n const getDetector = (): Promise<QrDetector> => {\n detector ??= (async () => {\n if (options.detector) return options.detector;\n const ctor = barcodeDetector();\n if (typeof ctor !== 'function')\n throw new SigilUnsupportedError('BarcodeDetector is not available in this environment');\n return new ctor({ formats: ['qr_code'] });\n })();\n return detector;\n };\n\n const tick = async (): Promise<void> => {\n if (status !== 'scanning' || stream === null) return;\n const video = options.video;\n if (video.readyState < 2 /* HAVE_CURRENT_DATA */) {\n emit({ reason: 'not-ready', type: 'frame-skipped' });\n return;\n }\n if (busy) {\n emit({ reason: 'busy', type: 'frame-skipped' });\n return;\n }\n busy = true;\n const started = Date.now();\n try {\n const qrDetector = await getDetector();\n const results = await qrDetector.detect(video);\n const first = results[0];\n if (first) {\n const result: QrScanResult = { cornerPoints: first.cornerPoints, value: first.rawValue };\n emit({ elapsedMs: Date.now() - started, type: 'detect', value: result.value });\n for (const handler of [...handlers]) handler(result);\n if (once) {\n stopTracks();\n setStatus('idle');\n }\n }\n } catch (error) {\n fail(error instanceof SigilError ? error : new SigilError('QR detection failed', { cause: error }));\n } finally {\n busy = false;\n }\n };\n\n const api: QrScanner = {\n get disposalSignal() {\n return disposal.signal;\n },\n dispose() {\n if (status === 'disposed') return;\n emit({ type: 'dispose' });\n stopTracks();\n handlers.clear();\n tappers.clear();\n setStatus('disposed');\n disposal.abort();\n },\n get disposed() {\n return status === 'disposed';\n },\n onResult(handler) {\n if (status === 'disposed') return () => {};\n handlers.add(handler);\n return () => handlers.delete(handler);\n },\n async start() {\n if (status === 'disposed') throw new SigilDisposedError('Scanner is disposed');\n if (status === 'starting' || status === 'scanning') return;\n if (!media || typeof media.getUserMedia !== 'function')\n throw new SigilUnsupportedError('getUserMedia is not available in this environment');\n // Surface a missing detector at start() (not construction) per contract.\n await getDetector();\n options.signal?.throwIfAborted();\n setStatus('starting');\n let newStream: MediaStream;\n try {\n newStream = await media.getUserMedia({ video: options.constraints ?? { facingMode: 'environment' } });\n } catch (error) {\n setStatus('idle');\n if (error instanceof DOMException && error.name === 'NotAllowedError')\n throw new SigilPermissionError('Camera permission denied', { cause: error });\n throw error instanceof Error ? error : new SigilError('Camera request failed', { cause: error });\n }\n // stop()/dispose() during the permission prompt must not resurrect the scanner.\n if (!isStarting()) {\n for (const track of newStream.getTracks()) track.stop();\n return;\n }\n stream = newStream;\n options.video.srcObject = stream;\n await options.video.play().catch(() => {});\n if (!isStarting()) {\n stopTracks();\n return;\n }\n setStatus('scanning');\n timer = setInterval(() => void tick(), intervalMs);\n void tick();\n },\n get status() {\n return status;\n },\n stop() {\n if (status === 'disposed') return;\n stopTracks();\n setStatus('idle');\n },\n tap(handler, tapOptions) {\n if (status === 'disposed') return () => {};\n tappers.add(handler);\n const off = () => tappers.delete(handler);\n tapOptions?.signal?.addEventListener('abort', off, { once: true });\n return off;\n },\n [Symbol.dispose]() {\n api.dispose();\n },\n };\n\n if (options.signal) {\n const onAbort = (): void => api.stop();\n options.signal.addEventListener('abort', onAbort, { once: true });\n // A long-lived signal must not retain the scanner past dispose().\n disposal.signal.addEventListener('abort', () => options.signal?.removeEventListener('abort', onAbort), {\n once: true,\n });\n }\n return api;\n}\n"],"mappings":"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"}
|
package/dist/scan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,gBAAgB,EAAmB,YAAY,EAAc,MAAM,SAAS,CAAC;AAwBlH,oFAAoF;AACpF,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,0EAA0E;AAC1E,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAStD;AAED,2EAA2E;AAC3E,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,UAAU,CAAA;CAAO,GAC5D,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAS9B;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,gBAAgB,EAAmB,YAAY,EAAc,MAAM,SAAS,CAAC;AAwBlH,oFAAoF;AACpF,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,0EAA0E;AAC1E,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAStD;AAED,2EAA2E;AAC3E,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,UAAU,CAAA;CAAO,GAC5D,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAS9B;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAuLpE"}
|
package/dist/scan.js
CHANGED
|
@@ -1,141 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
//#region src/scan.ts
|
|
3
|
-
var i = globalThis, a = () => i.BarcodeDetector;
|
|
4
|
-
function o() {
|
|
5
|
-
return typeof a() == "function";
|
|
6
|
-
}
|
|
7
|
-
async function s() {
|
|
8
|
-
let e = a();
|
|
9
|
-
if (typeof e != "function") return !1;
|
|
10
|
-
if (typeof e.getSupportedFormats != "function") return !0;
|
|
11
|
-
try {
|
|
12
|
-
return (await e.getSupportedFormats()).includes("qr_code");
|
|
13
|
-
} catch {
|
|
14
|
-
return !1;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
async function c(e, t = {}) {
|
|
18
|
-
t.signal?.throwIfAborted();
|
|
19
|
-
let n = a(), i = t.detector ?? (n ? new n({ formats: ["qr_code"] }) : void 0);
|
|
20
|
-
if (!i) throw new r("BarcodeDetector is not available in this environment");
|
|
21
|
-
let o = await i.detect(e);
|
|
22
|
-
t.signal?.throwIfAborted();
|
|
23
|
-
let s = o[0];
|
|
24
|
-
return s ? {
|
|
25
|
-
cornerPoints: s.cornerPoints,
|
|
26
|
-
value: s.rawValue
|
|
27
|
-
} : null;
|
|
28
|
-
}
|
|
29
|
-
function l(i) {
|
|
30
|
-
let o = i.mediaDevices ?? globalThis.navigator?.mediaDevices, s = i.once ?? !0, c = i.intervalMs ?? 200, l = new AbortController(), u = /* @__PURE__ */ new Set(), d = /* @__PURE__ */ new Set(), f = "idle", p = null, m = null, h = !1, g = (e) => {
|
|
31
|
-
if (u.size !== 0) for (let t of [...u]) try {
|
|
32
|
-
t(e);
|
|
33
|
-
} catch {}
|
|
34
|
-
}, _ = (e) => {
|
|
35
|
-
f !== e && (f = e, g({
|
|
36
|
-
status: e,
|
|
37
|
-
type: "status-change"
|
|
38
|
-
}));
|
|
39
|
-
}, v = () => {
|
|
40
|
-
if (m !== null && (clearInterval(m), m = null), p) {
|
|
41
|
-
for (let e of p.getTracks()) e.stop();
|
|
42
|
-
p = null;
|
|
43
|
-
}
|
|
44
|
-
i.video.srcObject = null;
|
|
45
|
-
}, y = (e) => {
|
|
46
|
-
g({
|
|
47
|
-
error: e,
|
|
48
|
-
type: "error"
|
|
49
|
-
}), v(), _("stopped");
|
|
50
|
-
}, b = async () => {
|
|
51
|
-
if (f !== "scanning" || p === null) return;
|
|
52
|
-
let e = i.video;
|
|
53
|
-
if (e.readyState < 2) {
|
|
54
|
-
g({
|
|
55
|
-
reason: "not-ready",
|
|
56
|
-
type: "frame-skipped"
|
|
57
|
-
});
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (h) {
|
|
61
|
-
g({
|
|
62
|
-
reason: "busy",
|
|
63
|
-
type: "frame-skipped"
|
|
64
|
-
});
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
h = !0;
|
|
68
|
-
let n = Date.now();
|
|
69
|
-
try {
|
|
70
|
-
let t = (await (await x).detect(e))[0];
|
|
71
|
-
if (t) {
|
|
72
|
-
let e = {
|
|
73
|
-
cornerPoints: t.cornerPoints,
|
|
74
|
-
value: t.rawValue
|
|
75
|
-
};
|
|
76
|
-
g({
|
|
77
|
-
elapsedMs: Date.now() - n,
|
|
78
|
-
type: "detect",
|
|
79
|
-
value: e.value
|
|
80
|
-
});
|
|
81
|
-
for (let t of [...d]) t(e);
|
|
82
|
-
s && (v(), _("stopped"));
|
|
83
|
-
}
|
|
84
|
-
} catch (e) {
|
|
85
|
-
y(e instanceof t ? e : new t("QR detection failed", { cause: e }));
|
|
86
|
-
} finally {
|
|
87
|
-
h = !1;
|
|
88
|
-
}
|
|
89
|
-
}, x = Promise.resolve().then(() => {
|
|
90
|
-
if (i.detector) return i.detector;
|
|
91
|
-
let e = a();
|
|
92
|
-
if (typeof e != "function") throw new r("BarcodeDetector is not available in this environment");
|
|
93
|
-
return new e({ formats: ["qr_code"] });
|
|
94
|
-
}), S = {
|
|
95
|
-
get disposalSignal() {
|
|
96
|
-
return l.signal;
|
|
97
|
-
},
|
|
98
|
-
dispose() {
|
|
99
|
-
f !== "disposed" && (g({ type: "dispose" }), v(), d.clear(), u.clear(), _("disposed"), l.abort());
|
|
100
|
-
},
|
|
101
|
-
get disposed() {
|
|
102
|
-
return f === "disposed";
|
|
103
|
-
},
|
|
104
|
-
onResult(e) {
|
|
105
|
-
return f === "disposed" ? () => {} : (d.add(e), () => d.delete(e));
|
|
106
|
-
},
|
|
107
|
-
async start() {
|
|
108
|
-
if (f === "disposed") throw new e("Scanner is disposed");
|
|
109
|
-
if (f !== "starting" && f !== "scanning") {
|
|
110
|
-
if (!o || typeof o.getUserMedia != "function") throw new r("getUserMedia is not available in this environment");
|
|
111
|
-
await x, i.signal?.throwIfAborted(), _("starting");
|
|
112
|
-
try {
|
|
113
|
-
p = await o.getUserMedia({ video: i.constraints ?? { facingMode: "environment" } });
|
|
114
|
-
} catch (e) {
|
|
115
|
-
throw _("idle"), e instanceof DOMException && e.name === "NotAllowedError" ? new n("Camera permission denied", { cause: e }) : e instanceof Error ? e : new t("Camera request failed", { cause: e });
|
|
116
|
-
}
|
|
117
|
-
i.signal?.throwIfAborted(), i.video.srcObject = p, await i.video.play().catch(() => {}), _("scanning"), m = setInterval(() => void b(), c), b();
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
get status() {
|
|
121
|
-
return f;
|
|
122
|
-
},
|
|
123
|
-
stop() {
|
|
124
|
-
f !== "disposed" && (v(), _(f === "idle" ? "idle" : "stopped"));
|
|
125
|
-
},
|
|
126
|
-
tap(e, t) {
|
|
127
|
-
if (f === "disposed") return () => {};
|
|
128
|
-
u.add(e);
|
|
129
|
-
let n = () => u.delete(e);
|
|
130
|
-
return t?.signal?.addEventListener("abort", n, { once: !0 }), n;
|
|
131
|
-
},
|
|
132
|
-
[Symbol.dispose]() {
|
|
133
|
-
S.dispose();
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
return i.signal?.addEventListener("abort", () => S.stop(), { once: !0 }), S;
|
|
137
|
-
}
|
|
138
|
-
//#endregion
|
|
139
|
-
export { l as createQrScanner, c as detectQr, o as isQrScanSupported, s as qrScanSupport };
|
|
140
|
-
|
|
1
|
+
import{SigilDisposedError as e,SigilError as t,SigilPermissionError as n,SigilUnsupportedError as r}from"./errors.js";var i=globalThis,barcodeDetector=()=>i.BarcodeDetector;function isQrScanSupported(){return typeof barcodeDetector()==`function`}async function qrScanSupport(){let e=barcodeDetector();if(typeof e!=`function`)return!1;if(typeof e.getSupportedFormats!=`function`)return!0;try{return(await e.getSupportedFormats()).includes(`qr_code`)}catch{return!1}}async function detectQr(e,t={}){t.signal?.throwIfAborted();let n=barcodeDetector(),i=t.detector??(n?new n({formats:[`qr_code`]}):void 0);if(!i)throw new r(`BarcodeDetector is not available in this environment`);let a=await i.detect(e);t.signal?.throwIfAborted();let o=a[0];return o?{cornerPoints:o.cornerPoints,value:o.rawValue}:null}function createQrScanner(i){let a=i.mediaDevices??globalThis.navigator?.mediaDevices,o=i.once??!0,s=i.intervalMs??200,c=new AbortController,l=new Set,u=new Set,d=`idle`,f=null,p=null,m=!1,h,emit=e=>{if(l.size!==0)for(let t of[...l])try{t(e)}catch{}},setStatus=e=>{d!==e&&(d=e,emit({status:e,type:`status-change`}))},isStarting=()=>d===`starting`,stopTracks=()=>{if(p!==null&&(clearInterval(p),p=null),f){for(let e of f.getTracks())e.stop();f=null}i.video.srcObject=null},fail=e=>{emit({error:e,type:`error`}),stopTracks(),setStatus(`idle`)},getDetector=()=>(h??=(async()=>{if(i.detector)return i.detector;let e=barcodeDetector();if(typeof e!=`function`)throw new r(`BarcodeDetector is not available in this environment`);return new e({formats:[`qr_code`]})})(),h),tick=async()=>{if(d!==`scanning`||f===null)return;let e=i.video;if(e.readyState<2){emit({reason:`not-ready`,type:`frame-skipped`});return}if(m){emit({reason:`busy`,type:`frame-skipped`});return}m=!0;let n=Date.now();try{let t=(await(await getDetector()).detect(e))[0];if(t){let e={cornerPoints:t.cornerPoints,value:t.rawValue};emit({elapsedMs:Date.now()-n,type:`detect`,value:e.value});for(let t of[...u])t(e);o&&(stopTracks(),setStatus(`idle`))}}catch(e){fail(e instanceof t?e:new t(`QR detection failed`,{cause:e}))}finally{m=!1}},g={get disposalSignal(){return c.signal},dispose(){d!==`disposed`&&(emit({type:`dispose`}),stopTracks(),u.clear(),l.clear(),setStatus(`disposed`),c.abort())},get disposed(){return d===`disposed`},onResult(e){return d===`disposed`?()=>{}:(u.add(e),()=>u.delete(e))},async start(){if(d===`disposed`)throw new e(`Scanner is disposed`);if(d===`starting`||d===`scanning`)return;if(!a||typeof a.getUserMedia!=`function`)throw new r(`getUserMedia is not available in this environment`);await getDetector(),i.signal?.throwIfAborted(),setStatus(`starting`);let o;try{o=await a.getUserMedia({video:i.constraints??{facingMode:`environment`}})}catch(e){throw setStatus(`idle`),e instanceof DOMException&&e.name===`NotAllowedError`?new n(`Camera permission denied`,{cause:e}):e instanceof Error?e:new t(`Camera request failed`,{cause:e})}if(!isStarting()){for(let e of o.getTracks())e.stop();return}if(f=o,i.video.srcObject=f,await i.video.play().catch(()=>{}),!isStarting()){stopTracks();return}setStatus(`scanning`),p=setInterval(()=>void tick(),s),tick()},get status(){return d},stop(){d!==`disposed`&&(stopTracks(),setStatus(`idle`))},tap(e,t){if(d===`disposed`)return()=>{};l.add(e);let off=()=>l.delete(e);return t?.signal?.addEventListener(`abort`,off,{once:!0}),off},[Symbol.dispose](){g.dispose()}};if(i.signal){let onAbort=()=>g.stop();i.signal.addEventListener(`abort`,onAbort,{once:!0}),c.signal.addEventListener(`abort`,()=>i.signal?.removeEventListener(`abort`,onAbort),{once:!0})}return g}export{createQrScanner,detectQr,isQrScanSupported,qrScanSupport};
|
|
141
2
|
//# sourceMappingURL=scan.js.map
|
package/dist/scan.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.js","names":[],"sources":["../src/scan.ts"],"sourcesContent":["import { SigilDisposedError, SigilError, SigilPermissionError, SigilUnsupportedError } from './errors';\nimport type { QrDetector, QrScanner, QrScannerOptions, QrScannerStatus, QrScanResult, SigilEvent } from './types';\n\n/**\n * Scanning via the native `BarcodeDetector` API — a thin lifecycle wrapper.\n * Feature detection is lazy so importing the package is always safe (SSR).\n * No from-scratch image decoder: unsupported environments get\n * `SigilUnsupportedError` at `start()`/`detectQr()`, never at import.\n */\n\ninterface BarcodeDetectorLike {\n detect(\n source: ImageBitmapSource,\n ): Promise<ReadonlyArray<{ rawValue: string; cornerPoints?: ReadonlyArray<{ x: number; y: number }> }>>;\n}\n\ninterface BarcodeDetectorCtor {\n getSupportedFormats?(): Promise<readonly string[]>;\n new (options?: { formats?: string[] }): BarcodeDetectorLike;\n}\n\nconst globalScope = globalThis as { BarcodeDetector?: BarcodeDetectorCtor };\n\nconst barcodeDetector = (): BarcodeDetectorCtor | undefined => globalScope.BarcodeDetector;\n\n/** Sync check: `BarcodeDetector` exists. Format support needs the async variant. */\nexport function isQrScanSupported(): boolean {\n return typeof barcodeDetector() === 'function';\n}\n\n/** Full check: `BarcodeDetector` exists and reports `qr_code` support. */\nexport async function qrScanSupport(): Promise<boolean> {\n const ctor = barcodeDetector();\n if (typeof ctor !== 'function') return false;\n if (typeof ctor.getSupportedFormats !== 'function') return true;\n try {\n return (await ctor.getSupportedFormats()).includes('qr_code');\n } catch {\n return false;\n }\n}\n\n/** One-shot detection on an image source; `null` when no code is found. */\nexport async function detectQr(\n source: ImageBitmapSource,\n options: { signal?: AbortSignal; detector?: QrDetector } = {},\n): Promise<QrScanResult | null> {\n options.signal?.throwIfAborted();\n const ctor = barcodeDetector();\n const detector = options.detector ?? (ctor ? new ctor({ formats: ['qr_code'] }) : undefined);\n if (!detector) throw new SigilUnsupportedError('BarcodeDetector is not available in this environment');\n const results = await detector.detect(source);\n options.signal?.throwIfAborted();\n const first = results[0];\n return first ? { cornerPoints: first.cornerPoints, value: first.rawValue } : null;\n}\n\nexport function createQrScanner(options: QrScannerOptions): QrScanner {\n const media = options.mediaDevices ?? globalThis.navigator?.mediaDevices;\n const once = options.once ?? true;\n const intervalMs = options.intervalMs ?? 200;\n const disposal = new AbortController();\n const tappers = new Set<(event: SigilEvent) => void>();\n const handlers = new Set<(result: QrScanResult) => void>();\n\n let status: QrScannerStatus = 'idle';\n let stream: MediaStream | null = null;\n let timer: ReturnType<typeof setInterval> | null = null;\n let busy = false;\n\n const emit = (event: SigilEvent): void => {\n if (tappers.size === 0) return;\n for (const handler of [...tappers]) {\n try {\n handler(event);\n } catch {\n // Observability must never affect behavior.\n }\n }\n };\n\n const setStatus = (next: QrScannerStatus): void => {\n if (status === next) return;\n status = next;\n emit({ status: next, type: 'status-change' });\n };\n\n const stopTracks = (): void => {\n if (timer !== null) {\n clearInterval(timer);\n timer = null;\n }\n if (stream) {\n for (const track of stream.getTracks()) track.stop();\n stream = null;\n }\n options.video.srcObject = null;\n };\n\n const fail = (error: SigilError): void => {\n emit({ error, type: 'error' });\n stopTracks();\n setStatus('stopped');\n };\n\n const tick = async (): Promise<void> => {\n if (status !== 'scanning' || stream === null) return;\n const video = options.video;\n if (video.readyState < 2 /* HAVE_CURRENT_DATA */) {\n emit({ reason: 'not-ready', type: 'frame-skipped' });\n return;\n }\n if (busy) {\n emit({ reason: 'busy', type: 'frame-skipped' });\n return;\n }\n busy = true;\n const started = Date.now();\n try {\n const detector = await detectorPromise;\n const results = await detector.detect(video);\n const first = results[0];\n if (first) {\n const result: QrScanResult = { cornerPoints: first.cornerPoints, value: first.rawValue };\n emit({ elapsedMs: Date.now() - started, type: 'detect', value: result.value });\n for (const handler of [...handlers]) handler(result);\n if (once) {\n stopTracks();\n setStatus('stopped');\n }\n }\n } catch (error) {\n fail(error instanceof SigilError ? error : new SigilError('QR detection failed', { cause: error }));\n } finally {\n busy = false;\n }\n };\n\n const detectorPromise: Promise<QrDetector> = Promise.resolve().then(() => {\n if (options.detector) return options.detector;\n const ctor = barcodeDetector();\n if (typeof ctor !== 'function')\n throw new SigilUnsupportedError('BarcodeDetector is not available in this environment');\n return new ctor({ formats: ['qr_code'] });\n });\n\n const api: QrScanner = {\n get disposalSignal() {\n return disposal.signal;\n },\n dispose() {\n if (status === 'disposed') return;\n emit({ type: 'dispose' });\n stopTracks();\n handlers.clear();\n tappers.clear();\n setStatus('disposed');\n disposal.abort();\n },\n get disposed() {\n return status === 'disposed';\n },\n onResult(handler) {\n if (status === 'disposed') return () => {};\n handlers.add(handler);\n return () => handlers.delete(handler);\n },\n async start() {\n if (status === 'disposed') throw new SigilDisposedError('Scanner is disposed');\n if (status === 'starting' || status === 'scanning') return;\n if (!media || typeof media.getUserMedia !== 'function')\n throw new SigilUnsupportedError('getUserMedia is not available in this environment');\n // Surface a missing detector at start() (not construction) per contract.\n await detectorPromise;\n options.signal?.throwIfAborted();\n setStatus('starting');\n try {\n stream = await media.getUserMedia({ video: options.constraints ?? { facingMode: 'environment' } });\n } catch (error) {\n setStatus('idle');\n if (error instanceof DOMException && error.name === 'NotAllowedError')\n throw new SigilPermissionError('Camera permission denied', { cause: error });\n throw error instanceof Error ? error : new SigilError('Camera request failed', { cause: error });\n }\n options.signal?.throwIfAborted();\n options.video.srcObject = stream;\n await options.video.play().catch(() => {});\n setStatus('scanning');\n timer = setInterval(() => void tick(), intervalMs);\n void tick();\n },\n get status() {\n return status;\n },\n stop() {\n if (status === 'disposed') return;\n stopTracks();\n setStatus(status === 'idle' ? 'idle' : 'stopped');\n },\n tap(handler, tapOptions) {\n if (status === 'disposed') return () => {};\n tappers.add(handler);\n const off = () => tappers.delete(handler);\n tapOptions?.signal?.addEventListener('abort', off, { once: true });\n return off;\n },\n [Symbol.dispose]() {\n api.dispose();\n },\n };\n\n options.signal?.addEventListener('abort', () => api.stop(), { once: true });\n return api;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scan.js","names":[],"sources":["../src/scan.ts"],"sourcesContent":["import { SigilDisposedError, SigilError, SigilPermissionError, SigilUnsupportedError } from './errors';\nimport type { QrDetector, QrScanner, QrScannerOptions, QrScannerStatus, QrScanResult, SigilEvent } from './types';\n\n/**\n * Scanning via the native `BarcodeDetector` API — a thin lifecycle wrapper.\n * Feature detection is lazy so importing the package is always safe (SSR).\n * No from-scratch image decoder: unsupported environments get\n * `SigilUnsupportedError` at `start()`/`detectQr()`, never at import.\n */\n\ninterface BarcodeDetectorLike {\n detect(\n source: ImageBitmapSource,\n ): Promise<ReadonlyArray<{ rawValue: string; cornerPoints?: ReadonlyArray<{ x: number; y: number }> }>>;\n}\n\ninterface BarcodeDetectorCtor {\n getSupportedFormats?(): Promise<readonly string[]>;\n new (options?: { formats?: string[] }): BarcodeDetectorLike;\n}\n\nconst globalScope = globalThis as { BarcodeDetector?: BarcodeDetectorCtor };\n\nconst barcodeDetector = (): BarcodeDetectorCtor | undefined => globalScope.BarcodeDetector;\n\n/** Sync check: `BarcodeDetector` exists. Format support needs the async variant. */\nexport function isQrScanSupported(): boolean {\n return typeof barcodeDetector() === 'function';\n}\n\n/** Full check: `BarcodeDetector` exists and reports `qr_code` support. */\nexport async function qrScanSupport(): Promise<boolean> {\n const ctor = barcodeDetector();\n if (typeof ctor !== 'function') return false;\n if (typeof ctor.getSupportedFormats !== 'function') return true;\n try {\n return (await ctor.getSupportedFormats()).includes('qr_code');\n } catch {\n return false;\n }\n}\n\n/** One-shot detection on an image source; `null` when no code is found. */\nexport async function detectQr(\n source: ImageBitmapSource,\n options: { signal?: AbortSignal; detector?: QrDetector } = {},\n): Promise<QrScanResult | null> {\n options.signal?.throwIfAborted();\n const ctor = barcodeDetector();\n const detector = options.detector ?? (ctor ? new ctor({ formats: ['qr_code'] }) : undefined);\n if (!detector) throw new SigilUnsupportedError('BarcodeDetector is not available in this environment');\n const results = await detector.detect(source);\n options.signal?.throwIfAborted();\n const first = results[0];\n return first ? { cornerPoints: first.cornerPoints, value: first.rawValue } : null;\n}\n\nexport function createQrScanner(options: QrScannerOptions): QrScanner {\n const media = options.mediaDevices ?? globalThis.navigator?.mediaDevices;\n const once = options.once ?? true;\n const intervalMs = options.intervalMs ?? 200;\n const disposal = new AbortController();\n const tappers = new Set<(event: SigilEvent) => void>();\n const handlers = new Set<(result: QrScanResult) => void>();\n\n let status: QrScannerStatus = 'idle';\n let stream: MediaStream | null = null;\n let timer: ReturnType<typeof setInterval> | null = null;\n let busy = false;\n let detector: Promise<QrDetector> | undefined;\n\n const emit = (event: SigilEvent): void => {\n if (tappers.size === 0) return;\n for (const handler of [...tappers]) {\n try {\n handler(event);\n } catch {\n // Observability must never affect behavior.\n }\n }\n };\n\n const setStatus = (next: QrScannerStatus): void => {\n if (status === next) return;\n status = next;\n emit({ status: next, type: 'status-change' });\n };\n\n // Read through a function: status changes across awaits via closures, and a\n // direct read would be narrowed to the pre-await value by the compiler.\n const isStarting = (): boolean => status === 'starting';\n\n const stopTracks = (): void => {\n if (timer !== null) {\n clearInterval(timer);\n timer = null;\n }\n if (stream) {\n for (const track of stream.getTracks()) track.stop();\n stream = null;\n }\n options.video.srcObject = null;\n };\n\n const fail = (error: SigilError): void => {\n emit({ error, type: 'error' });\n stopTracks();\n setStatus('idle');\n };\n\n // Created on first start() so a missing BarcodeDetector never rejects a\n // promise nobody awaits (construction must stay side-effect-free).\n const getDetector = (): Promise<QrDetector> => {\n detector ??= (async () => {\n if (options.detector) return options.detector;\n const ctor = barcodeDetector();\n if (typeof ctor !== 'function')\n throw new SigilUnsupportedError('BarcodeDetector is not available in this environment');\n return new ctor({ formats: ['qr_code'] });\n })();\n return detector;\n };\n\n const tick = async (): Promise<void> => {\n if (status !== 'scanning' || stream === null) return;\n const video = options.video;\n if (video.readyState < 2 /* HAVE_CURRENT_DATA */) {\n emit({ reason: 'not-ready', type: 'frame-skipped' });\n return;\n }\n if (busy) {\n emit({ reason: 'busy', type: 'frame-skipped' });\n return;\n }\n busy = true;\n const started = Date.now();\n try {\n const qrDetector = await getDetector();\n const results = await qrDetector.detect(video);\n const first = results[0];\n if (first) {\n const result: QrScanResult = { cornerPoints: first.cornerPoints, value: first.rawValue };\n emit({ elapsedMs: Date.now() - started, type: 'detect', value: result.value });\n for (const handler of [...handlers]) handler(result);\n if (once) {\n stopTracks();\n setStatus('idle');\n }\n }\n } catch (error) {\n fail(error instanceof SigilError ? error : new SigilError('QR detection failed', { cause: error }));\n } finally {\n busy = false;\n }\n };\n\n const api: QrScanner = {\n get disposalSignal() {\n return disposal.signal;\n },\n dispose() {\n if (status === 'disposed') return;\n emit({ type: 'dispose' });\n stopTracks();\n handlers.clear();\n tappers.clear();\n setStatus('disposed');\n disposal.abort();\n },\n get disposed() {\n return status === 'disposed';\n },\n onResult(handler) {\n if (status === 'disposed') return () => {};\n handlers.add(handler);\n return () => handlers.delete(handler);\n },\n async start() {\n if (status === 'disposed') throw new SigilDisposedError('Scanner is disposed');\n if (status === 'starting' || status === 'scanning') return;\n if (!media || typeof media.getUserMedia !== 'function')\n throw new SigilUnsupportedError('getUserMedia is not available in this environment');\n // Surface a missing detector at start() (not construction) per contract.\n await getDetector();\n options.signal?.throwIfAborted();\n setStatus('starting');\n let newStream: MediaStream;\n try {\n newStream = await media.getUserMedia({ video: options.constraints ?? { facingMode: 'environment' } });\n } catch (error) {\n setStatus('idle');\n if (error instanceof DOMException && error.name === 'NotAllowedError')\n throw new SigilPermissionError('Camera permission denied', { cause: error });\n throw error instanceof Error ? error : new SigilError('Camera request failed', { cause: error });\n }\n // stop()/dispose() during the permission prompt must not resurrect the scanner.\n if (!isStarting()) {\n for (const track of newStream.getTracks()) track.stop();\n return;\n }\n stream = newStream;\n options.video.srcObject = stream;\n await options.video.play().catch(() => {});\n if (!isStarting()) {\n stopTracks();\n return;\n }\n setStatus('scanning');\n timer = setInterval(() => void tick(), intervalMs);\n void tick();\n },\n get status() {\n return status;\n },\n stop() {\n if (status === 'disposed') return;\n stopTracks();\n setStatus('idle');\n },\n tap(handler, tapOptions) {\n if (status === 'disposed') return () => {};\n tappers.add(handler);\n const off = () => tappers.delete(handler);\n tapOptions?.signal?.addEventListener('abort', off, { once: true });\n return off;\n },\n [Symbol.dispose]() {\n api.dispose();\n },\n };\n\n if (options.signal) {\n const onAbort = (): void => api.stop();\n options.signal.addEventListener('abort', onAbort, { once: true });\n // A long-lived signal must not retain the scanner past dispose().\n disposal.signal.addEventListener('abort', () => options.signal?.removeEventListener('abort', onAbort), {\n once: true,\n });\n }\n return api;\n}\n"],"mappings":"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"}
|