@barcodesdk/react 0.1.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/LICENSE +9 -0
- package/dist/barcode-scanner-view.d.ts +25 -0
- package/dist/barcode-scanner-view.d.ts.map +1 -0
- package/dist/context.d.ts +4 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +391 -0
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +32 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/store.d.ts +55 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/use-barcode-scanner.d.ts +21 -0
- package/dist/use-barcode-scanner.d.ts.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Copyright (c) 2026 Vijay Reddy
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is proprietary beta software. Unauthorized copying,
|
|
6
|
+
modification, distribution, or use is prohibited except under a written
|
|
7
|
+
license agreement with the copyright holder.
|
|
8
|
+
|
|
9
|
+
Third-party notices for bundled dependencies are in NOTICES.md.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode, type Ref } from "react";
|
|
2
|
+
export interface BarcodeScannerViewHandle {
|
|
3
|
+
video: HTMLVideoElement | null;
|
|
4
|
+
start: () => Promise<void>;
|
|
5
|
+
stop: () => Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export interface BarcodeScannerViewProps {
|
|
8
|
+
className?: string;
|
|
9
|
+
style?: CSSProperties;
|
|
10
|
+
videoClassName?: string;
|
|
11
|
+
videoStyle?: CSSProperties;
|
|
12
|
+
/** Render overlay content inside the video frame (absolute positioned). */
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
/** Auto attach + start when the video element mounts and scanner is ready. */
|
|
15
|
+
autoStart?: boolean;
|
|
16
|
+
/** Forwarded ref with video + start/stop helpers. */
|
|
17
|
+
viewRef?: Ref<BarcodeScannerViewHandle>;
|
|
18
|
+
onStartError?: (error: unknown) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Video preview bound to the Provider scanner.
|
|
22
|
+
* Overlay children should be absolutely positioned over the video.
|
|
23
|
+
*/
|
|
24
|
+
export declare function BarcodeScannerView({ className, style, videoClassName, videoStyle, children, autoStart, viewRef, onStartError, }: BarcodeScannerViewProps): import("react").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=barcode-scanner-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"barcode-scanner-view.d.ts","sourceRoot":"","sources":["../src/barcode-scanner-view.tsx"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,GAAG,EACT,MAAM,OAAO,CAAC;AAGf,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qDAAqD;IACrD,OAAO,CAAC,EAAE,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACxC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,KAAK,EACL,cAAc,EACd,UAAU,EACV,QAAQ,EACR,SAAiB,EACjB,OAAO,EACP,YAAY,GACb,EAAE,uBAAuB,+BAmEzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,eAAO,MAAM,mBAAmB,8CAA2C,CAAC;AAE5E,wBAAgB,sBAAsB,IAAI,YAAY,CAQrD"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createScannerStore, ScannerStore } from "./store.js";
|
|
2
|
+
export type { ScannerStoreOptions, ScannerStoreSnapshot, } from "./store.js";
|
|
3
|
+
export { BarcodeScannerProvider } from "./provider.js";
|
|
4
|
+
export type { BarcodeScannerProviderProps } from "./provider.js";
|
|
5
|
+
export { useBarcodeScanner } from "./use-barcode-scanner.js";
|
|
6
|
+
export type { UseBarcodeScannerResult } from "./use-barcode-scanner.js";
|
|
7
|
+
export { BarcodeScannerView } from "./barcode-scanner-view.js";
|
|
8
|
+
export type { BarcodeScannerViewHandle, BarcodeScannerViewProps, } from "./barcode-scanner-view.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC9D,YAAY,EACV,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACvD,YAAY,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EACV,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,2BAA2B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
|
+
import { createBarcodeScanner } from "@barcodesdk/core";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { createContext, useContext, useState, useEffect, useSyncExternalStore, useCallback, useMemo, useRef, useImperativeHandle } from "react";
|
|
8
|
+
const EMPTY_SNAPSHOT = {
|
|
9
|
+
state: "idle",
|
|
10
|
+
results: [],
|
|
11
|
+
accepted: [],
|
|
12
|
+
acceptedHistory: [],
|
|
13
|
+
lastError: null,
|
|
14
|
+
metrics: null,
|
|
15
|
+
capabilities: null
|
|
16
|
+
};
|
|
17
|
+
function mergeAcceptedHistory(previous, accepted, limit) {
|
|
18
|
+
if (accepted.length === 0) {
|
|
19
|
+
return previous;
|
|
20
|
+
}
|
|
21
|
+
const next = [...previous];
|
|
22
|
+
for (const item of accepted) {
|
|
23
|
+
if (!next.some(
|
|
24
|
+
(existing) => existing.format === item.format && existing.text === item.text
|
|
25
|
+
)) {
|
|
26
|
+
next.unshift(item);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return next.slice(0, limit);
|
|
30
|
+
}
|
|
31
|
+
class ScannerStore {
|
|
32
|
+
constructor(options) {
|
|
33
|
+
__publicField(this, "scanner");
|
|
34
|
+
__publicField(this, "ownsScanner");
|
|
35
|
+
__publicField(this, "snapshot");
|
|
36
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
37
|
+
__publicField(this, "unsubscribers", []);
|
|
38
|
+
__publicField(this, "acceptedHistoryLimit");
|
|
39
|
+
__publicField(this, "destroyed", false);
|
|
40
|
+
__publicField(this, "subscribe", (listener) => {
|
|
41
|
+
this.listeners.add(listener);
|
|
42
|
+
return () => {
|
|
43
|
+
this.listeners.delete(listener);
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
__publicField(this, "getSnapshot", () => this.snapshot);
|
|
47
|
+
__publicField(this, "getServerSnapshot", () => EMPTY_SNAPSHOT);
|
|
48
|
+
if (!options.config && !options.scanner) {
|
|
49
|
+
throw new Error("ScannerStore requires either config or scanner.");
|
|
50
|
+
}
|
|
51
|
+
if (options.config && options.scanner) {
|
|
52
|
+
throw new Error("Pass either config or scanner, not both.");
|
|
53
|
+
}
|
|
54
|
+
this.ownsScanner = Boolean(options.config);
|
|
55
|
+
this.scanner = options.scanner ?? createBarcodeScanner(options.config);
|
|
56
|
+
this.acceptedHistoryLimit = options.acceptedHistoryLimit ?? 12;
|
|
57
|
+
this.snapshot = {
|
|
58
|
+
...EMPTY_SNAPSHOT,
|
|
59
|
+
state: this.scanner.getState(),
|
|
60
|
+
capabilities: this.scanner.getCapabilities()
|
|
61
|
+
};
|
|
62
|
+
this.unsubscribers.push(
|
|
63
|
+
this.scanner.on("state", ({ state }) => {
|
|
64
|
+
this.update({ state, lastError: state === "error" ? this.snapshot.lastError : this.snapshot.lastError });
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
this.unsubscribers.push(
|
|
68
|
+
this.scanner.on("result", ({ results, accepted, metrics }) => {
|
|
69
|
+
this.update({
|
|
70
|
+
results,
|
|
71
|
+
accepted: accepted ?? [],
|
|
72
|
+
acceptedHistory: mergeAcceptedHistory(
|
|
73
|
+
this.snapshot.acceptedHistory,
|
|
74
|
+
accepted ?? [],
|
|
75
|
+
this.acceptedHistoryLimit
|
|
76
|
+
),
|
|
77
|
+
metrics: metrics ?? this.snapshot.metrics
|
|
78
|
+
});
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
this.unsubscribers.push(
|
|
82
|
+
this.scanner.on("error", (info) => {
|
|
83
|
+
this.update({ lastError: info });
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
if (options.autoInitialize !== false) {
|
|
87
|
+
void this.initialize();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** True after `destroy()`; used to recreate the store on Strict Mode remount. */
|
|
91
|
+
get isDestroyed() {
|
|
92
|
+
return this.destroyed;
|
|
93
|
+
}
|
|
94
|
+
async initialize() {
|
|
95
|
+
this.assertAlive();
|
|
96
|
+
await this.scanner.initialize();
|
|
97
|
+
if (this.destroyed) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.update({
|
|
101
|
+
state: this.scanner.getState(),
|
|
102
|
+
capabilities: this.scanner.getCapabilities()
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async attach(target) {
|
|
106
|
+
this.assertAlive();
|
|
107
|
+
await this.scanner.attach(target);
|
|
108
|
+
}
|
|
109
|
+
async start() {
|
|
110
|
+
this.assertAlive();
|
|
111
|
+
this.update({
|
|
112
|
+
results: [],
|
|
113
|
+
accepted: [],
|
|
114
|
+
lastError: null
|
|
115
|
+
});
|
|
116
|
+
await this.scanner.start();
|
|
117
|
+
}
|
|
118
|
+
async stop() {
|
|
119
|
+
this.assertAlive();
|
|
120
|
+
await this.scanner.stop();
|
|
121
|
+
if (this.destroyed) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.update({ results: [], accepted: [] });
|
|
125
|
+
}
|
|
126
|
+
async pause() {
|
|
127
|
+
this.assertAlive();
|
|
128
|
+
await this.scanner.pause();
|
|
129
|
+
}
|
|
130
|
+
async resume() {
|
|
131
|
+
this.assertAlive();
|
|
132
|
+
await this.scanner.resume();
|
|
133
|
+
}
|
|
134
|
+
async scanImage(source) {
|
|
135
|
+
this.assertAlive();
|
|
136
|
+
const results = await this.scanner.scanImage(source);
|
|
137
|
+
if (this.destroyed) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
return results;
|
|
141
|
+
}
|
|
142
|
+
clearError() {
|
|
143
|
+
this.update({ lastError: null });
|
|
144
|
+
}
|
|
145
|
+
clearAcceptedHistory() {
|
|
146
|
+
this.update({ acceptedHistory: [] });
|
|
147
|
+
}
|
|
148
|
+
async destroy() {
|
|
149
|
+
if (this.destroyed) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
this.destroyed = true;
|
|
153
|
+
for (const unsubscribe of this.unsubscribers) {
|
|
154
|
+
unsubscribe();
|
|
155
|
+
}
|
|
156
|
+
this.unsubscribers.length = 0;
|
|
157
|
+
this.listeners.clear();
|
|
158
|
+
if (this.ownsScanner) {
|
|
159
|
+
await this.scanner.destroy();
|
|
160
|
+
}
|
|
161
|
+
this.snapshot = {
|
|
162
|
+
...EMPTY_SNAPSHOT,
|
|
163
|
+
state: this.ownsScanner ? "destroyed" : this.scanner.getState()
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
update(patch) {
|
|
167
|
+
this.snapshot = { ...this.snapshot, ...patch };
|
|
168
|
+
for (const listener of [...this.listeners]) {
|
|
169
|
+
listener();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
assertAlive() {
|
|
173
|
+
if (this.destroyed) {
|
|
174
|
+
throw new Error("ScannerStore has been destroyed.");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function createScannerStore(options) {
|
|
179
|
+
return new ScannerStore(options);
|
|
180
|
+
}
|
|
181
|
+
const ScannerStoreContext = createContext(null);
|
|
182
|
+
function useScannerStoreContext() {
|
|
183
|
+
const store = useContext(ScannerStoreContext);
|
|
184
|
+
if (!store) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
"useBarcodeScanner must be used within a BarcodeScannerProvider."
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return store;
|
|
190
|
+
}
|
|
191
|
+
function createOwnedStore({
|
|
192
|
+
config,
|
|
193
|
+
scanner,
|
|
194
|
+
acceptedHistoryLimit,
|
|
195
|
+
autoInitialize
|
|
196
|
+
}) {
|
|
197
|
+
return createScannerStore({
|
|
198
|
+
config,
|
|
199
|
+
scanner,
|
|
200
|
+
acceptedHistoryLimit,
|
|
201
|
+
autoInitialize
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function BarcodeScannerProvider({
|
|
205
|
+
children,
|
|
206
|
+
config,
|
|
207
|
+
scanner,
|
|
208
|
+
acceptedHistoryLimit,
|
|
209
|
+
autoInitialize = true,
|
|
210
|
+
store: externalStore
|
|
211
|
+
}) {
|
|
212
|
+
const [ownedStore, setOwnedStore] = useState(
|
|
213
|
+
() => createOwnedStore({
|
|
214
|
+
config,
|
|
215
|
+
scanner,
|
|
216
|
+
acceptedHistoryLimit,
|
|
217
|
+
autoInitialize
|
|
218
|
+
})
|
|
219
|
+
);
|
|
220
|
+
if (!externalStore && ownedStore.isDestroyed) {
|
|
221
|
+
setOwnedStore(
|
|
222
|
+
createOwnedStore({
|
|
223
|
+
config,
|
|
224
|
+
scanner,
|
|
225
|
+
acceptedHistoryLimit,
|
|
226
|
+
autoInitialize
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
const store = externalStore ?? ownedStore;
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
if (externalStore) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (store.isDestroyed) {
|
|
236
|
+
setOwnedStore(
|
|
237
|
+
createOwnedStore({
|
|
238
|
+
config,
|
|
239
|
+
scanner,
|
|
240
|
+
acceptedHistoryLimit,
|
|
241
|
+
autoInitialize
|
|
242
|
+
})
|
|
243
|
+
);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const active = store;
|
|
247
|
+
return () => {
|
|
248
|
+
void active.destroy();
|
|
249
|
+
};
|
|
250
|
+
}, [store, externalStore]);
|
|
251
|
+
return /* @__PURE__ */ jsx(ScannerStoreContext.Provider, { value: store, children });
|
|
252
|
+
}
|
|
253
|
+
function useBarcodeScanner() {
|
|
254
|
+
const store = useScannerStoreContext();
|
|
255
|
+
const snapshot = useSyncExternalStore(
|
|
256
|
+
store.subscribe,
|
|
257
|
+
store.getSnapshot,
|
|
258
|
+
store.getServerSnapshot
|
|
259
|
+
);
|
|
260
|
+
const initialize = useCallback(() => store.initialize(), [store]);
|
|
261
|
+
const attach = useCallback(
|
|
262
|
+
(target) => store.attach(target),
|
|
263
|
+
[store]
|
|
264
|
+
);
|
|
265
|
+
const start = useCallback(() => store.start(), [store]);
|
|
266
|
+
const stop = useCallback(() => store.stop(), [store]);
|
|
267
|
+
const pause = useCallback(() => store.pause(), [store]);
|
|
268
|
+
const resume = useCallback(() => store.resume(), [store]);
|
|
269
|
+
const scanImage = useCallback(
|
|
270
|
+
(source) => store.scanImage(source),
|
|
271
|
+
[store]
|
|
272
|
+
);
|
|
273
|
+
const clearError = useCallback(() => store.clearError(), [store]);
|
|
274
|
+
const clearAcceptedHistory = useCallback(
|
|
275
|
+
() => store.clearAcceptedHistory(),
|
|
276
|
+
[store]
|
|
277
|
+
);
|
|
278
|
+
return useMemo(
|
|
279
|
+
() => ({
|
|
280
|
+
...snapshot,
|
|
281
|
+
scanner: store.scanner,
|
|
282
|
+
initialize,
|
|
283
|
+
attach,
|
|
284
|
+
start,
|
|
285
|
+
stop,
|
|
286
|
+
pause,
|
|
287
|
+
resume,
|
|
288
|
+
scanImage,
|
|
289
|
+
clearError,
|
|
290
|
+
clearAcceptedHistory
|
|
291
|
+
}),
|
|
292
|
+
[
|
|
293
|
+
snapshot,
|
|
294
|
+
store.scanner,
|
|
295
|
+
initialize,
|
|
296
|
+
attach,
|
|
297
|
+
start,
|
|
298
|
+
stop,
|
|
299
|
+
pause,
|
|
300
|
+
resume,
|
|
301
|
+
scanImage,
|
|
302
|
+
clearError,
|
|
303
|
+
clearAcceptedHistory
|
|
304
|
+
]
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
function BarcodeScannerView({
|
|
308
|
+
className,
|
|
309
|
+
style,
|
|
310
|
+
videoClassName,
|
|
311
|
+
videoStyle,
|
|
312
|
+
children,
|
|
313
|
+
autoStart = false,
|
|
314
|
+
viewRef,
|
|
315
|
+
onStartError
|
|
316
|
+
}) {
|
|
317
|
+
const videoRef = useRef(null);
|
|
318
|
+
const { state, attach, start, stop } = useBarcodeScanner();
|
|
319
|
+
const startedRef = useRef(false);
|
|
320
|
+
const runStart = useCallback(async () => {
|
|
321
|
+
const video = videoRef.current;
|
|
322
|
+
if (!video) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
await attach({ video });
|
|
326
|
+
await start();
|
|
327
|
+
startedRef.current = true;
|
|
328
|
+
}, [attach, start]);
|
|
329
|
+
const runStop = useCallback(async () => {
|
|
330
|
+
await stop();
|
|
331
|
+
startedRef.current = false;
|
|
332
|
+
}, [stop]);
|
|
333
|
+
useImperativeHandle(
|
|
334
|
+
viewRef,
|
|
335
|
+
() => ({
|
|
336
|
+
video: videoRef.current,
|
|
337
|
+
start: runStart,
|
|
338
|
+
stop: runStop
|
|
339
|
+
}),
|
|
340
|
+
[runStart, runStop]
|
|
341
|
+
);
|
|
342
|
+
useEffect(() => {
|
|
343
|
+
if (!autoStart) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (state !== "ready" && state !== "paused") {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (startedRef.current) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
void runStart().catch((error) => {
|
|
353
|
+
onStartError == null ? void 0 : onStartError(error);
|
|
354
|
+
});
|
|
355
|
+
}, [autoStart, state, runStart, onStartError]);
|
|
356
|
+
return /* @__PURE__ */ jsxs(
|
|
357
|
+
"div",
|
|
358
|
+
{
|
|
359
|
+
className,
|
|
360
|
+
style: { position: "relative", overflow: "hidden", ...style },
|
|
361
|
+
children: [
|
|
362
|
+
/* @__PURE__ */ jsx(
|
|
363
|
+
"video",
|
|
364
|
+
{
|
|
365
|
+
ref: videoRef,
|
|
366
|
+
className: videoClassName,
|
|
367
|
+
"aria-label": "Barcode camera preview",
|
|
368
|
+
style: {
|
|
369
|
+
display: "block",
|
|
370
|
+
width: "100%",
|
|
371
|
+
height: "100%",
|
|
372
|
+
objectFit: "contain",
|
|
373
|
+
...videoStyle
|
|
374
|
+
},
|
|
375
|
+
muted: true,
|
|
376
|
+
playsInline: true
|
|
377
|
+
}
|
|
378
|
+
),
|
|
379
|
+
children
|
|
380
|
+
]
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
export {
|
|
385
|
+
BarcodeScannerProvider,
|
|
386
|
+
BarcodeScannerView,
|
|
387
|
+
ScannerStore,
|
|
388
|
+
createScannerStore,
|
|
389
|
+
useBarcodeScanner
|
|
390
|
+
};
|
|
391
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/store.ts","../src/context.tsx","../src/provider.tsx","../src/use-barcode-scanner.ts","../src/barcode-scanner-view.tsx"],"sourcesContent":["import {\n createBarcodeScanner,\n type AttachTarget,\n type BarcodeResult,\n type BarcodeScanner,\n type ImageScanSource,\n type ScannerCapabilities,\n type ScannerConfig,\n type ScannerErrorInfo,\n type ScannerMetrics,\n type ScannerState,\n} from \"@barcodesdk/core\";\n\nexport interface ScannerStoreSnapshot {\n state: ScannerState;\n results: BarcodeResult[];\n /** Most recent accepted batch from the tracker. */\n accepted: BarcodeResult[];\n /** Deduped accepted history for UI lists (ring buffer). */\n acceptedHistory: BarcodeResult[];\n lastError: ScannerErrorInfo | null;\n metrics: ScannerMetrics | null;\n capabilities: ScannerCapabilities | null;\n}\n\nexport interface ScannerStoreOptions {\n /** Create and own a scanner from config. Mutually exclusive with `scanner`. */\n config?: ScannerConfig;\n /** Share an existing scanner instance (Provider will not destroy it). */\n scanner?: BarcodeScanner;\n /** Max unique accepted codes retained in history (default 12). */\n acceptedHistoryLimit?: number;\n /** Auto-call initialize() after store creation (default true). */\n autoInitialize?: boolean;\n}\n\nconst EMPTY_SNAPSHOT: ScannerStoreSnapshot = {\n state: \"idle\",\n results: [],\n accepted: [],\n acceptedHistory: [],\n lastError: null,\n metrics: null,\n capabilities: null,\n};\n\nfunction mergeAcceptedHistory(\n previous: BarcodeResult[],\n accepted: BarcodeResult[],\n limit: number,\n): BarcodeResult[] {\n if (accepted.length === 0) {\n return previous;\n }\n const next = [...previous];\n for (const item of accepted) {\n if (\n !next.some(\n (existing) =>\n existing.format === item.format && existing.text === item.text,\n )\n ) {\n next.unshift(item);\n }\n }\n return next.slice(0, limit);\n}\n\n/**\n * External store wrapping `@barcodesdk/core`. Designed for\n * `useSyncExternalStore` — React never duplicates scanner state in useState.\n */\nexport class ScannerStore {\n readonly scanner: BarcodeScanner;\n readonly ownsScanner: boolean;\n private snapshot: ScannerStoreSnapshot;\n private readonly listeners = new Set<() => void>();\n private readonly unsubscribers: Array<() => void> = [];\n private readonly acceptedHistoryLimit: number;\n private destroyed = false;\n\n /** True after `destroy()`; used to recreate the store on Strict Mode remount. */\n get isDestroyed(): boolean {\n return this.destroyed;\n }\n\n constructor(options: ScannerStoreOptions) {\n if (!options.config && !options.scanner) {\n throw new Error(\"ScannerStore requires either config or scanner.\");\n }\n if (options.config && options.scanner) {\n throw new Error(\"Pass either config or scanner, not both.\");\n }\n\n this.ownsScanner = Boolean(options.config);\n this.scanner = options.scanner ?? createBarcodeScanner(options.config!);\n this.acceptedHistoryLimit = options.acceptedHistoryLimit ?? 12;\n this.snapshot = {\n ...EMPTY_SNAPSHOT,\n state: this.scanner.getState(),\n capabilities: this.scanner.getCapabilities(),\n };\n\n this.unsubscribers.push(\n this.scanner.on(\"state\", ({ state }) => {\n this.update({ state, lastError: state === \"error\" ? this.snapshot.lastError : this.snapshot.lastError });\n }),\n );\n this.unsubscribers.push(\n this.scanner.on(\"result\", ({ results, accepted, metrics }) => {\n this.update({\n results,\n accepted: accepted ?? [],\n acceptedHistory: mergeAcceptedHistory(\n this.snapshot.acceptedHistory,\n accepted ?? [],\n this.acceptedHistoryLimit,\n ),\n metrics: metrics ?? this.snapshot.metrics,\n });\n }),\n );\n this.unsubscribers.push(\n this.scanner.on(\"error\", (info) => {\n this.update({ lastError: info });\n }),\n );\n\n if (options.autoInitialize !== false) {\n void this.initialize();\n }\n }\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n getSnapshot = (): ScannerStoreSnapshot => this.snapshot;\n\n getServerSnapshot = (): ScannerStoreSnapshot => EMPTY_SNAPSHOT;\n\n async initialize(): Promise<void> {\n this.assertAlive();\n await this.scanner.initialize();\n if (this.destroyed) {\n return;\n }\n this.update({\n state: this.scanner.getState(),\n capabilities: this.scanner.getCapabilities(),\n });\n }\n\n async attach(target: AttachTarget): Promise<void> {\n this.assertAlive();\n await this.scanner.attach(target);\n }\n\n async start(): Promise<void> {\n this.assertAlive();\n this.update({\n results: [],\n accepted: [],\n lastError: null,\n });\n await this.scanner.start();\n }\n\n async stop(): Promise<void> {\n this.assertAlive();\n await this.scanner.stop();\n if (this.destroyed) {\n return;\n }\n this.update({ results: [], accepted: [] });\n }\n\n async pause(): Promise<void> {\n this.assertAlive();\n await this.scanner.pause();\n }\n\n async resume(): Promise<void> {\n this.assertAlive();\n await this.scanner.resume();\n }\n\n async scanImage(source: ImageScanSource): Promise<BarcodeResult[]> {\n this.assertAlive();\n const results = await this.scanner.scanImage(source);\n if (this.destroyed) {\n return [];\n }\n return results;\n }\n\n clearError(): void {\n this.update({ lastError: null });\n }\n\n clearAcceptedHistory(): void {\n this.update({ acceptedHistory: [] });\n }\n\n async destroy(): Promise<void> {\n if (this.destroyed) {\n return;\n }\n this.destroyed = true;\n for (const unsubscribe of this.unsubscribers) {\n unsubscribe();\n }\n this.unsubscribers.length = 0;\n this.listeners.clear();\n if (this.ownsScanner) {\n await this.scanner.destroy();\n }\n this.snapshot = {\n ...EMPTY_SNAPSHOT,\n state: this.ownsScanner ? \"destroyed\" : this.scanner.getState(),\n };\n }\n\n private update(patch: Partial<ScannerStoreSnapshot>): void {\n this.snapshot = { ...this.snapshot, ...patch };\n for (const listener of [...this.listeners]) {\n listener();\n }\n }\n\n private assertAlive(): void {\n if (this.destroyed) {\n throw new Error(\"ScannerStore has been destroyed.\");\n }\n }\n}\n\nexport function createScannerStore(options: ScannerStoreOptions): ScannerStore {\n return new ScannerStore(options);\n}\n","\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport type { ScannerStore } from \"./store.js\";\n\nexport const ScannerStoreContext = createContext<ScannerStore | null>(null);\n\nexport function useScannerStoreContext(): ScannerStore {\n const store = useContext(ScannerStoreContext);\n if (!store) {\n throw new Error(\n \"useBarcodeScanner must be used within a BarcodeScannerProvider.\",\n );\n }\n return store;\n}\n","\"use client\";\n\nimport { useEffect, useState, type ReactNode } from \"react\";\nimport type { BarcodeScanner, ScannerConfig } from \"@barcodesdk/core\";\nimport { ScannerStoreContext } from \"./context.js\";\nimport { createScannerStore, type ScannerStore } from \"./store.js\";\n\nexport type BarcodeScannerProviderProps = {\n children: ReactNode;\n /**\n * Create a Provider-owned scanner from config.\n * The store destroys the scanner on unmount.\n */\n config?: ScannerConfig;\n /**\n * Share an existing scanner. Provider will not destroy it on unmount.\n * Mutually exclusive with `config`.\n */\n scanner?: BarcodeScanner;\n /** Max unique accepted codes kept in history (default 12). */\n acceptedHistoryLimit?: number;\n /** Auto initialize after mount (default true). */\n autoInitialize?: boolean;\n /** Optional external store (advanced); when set, config/scanner are ignored. */\n store?: ScannerStore;\n};\n\nfunction createOwnedStore({\n config,\n scanner,\n acceptedHistoryLimit,\n autoInitialize,\n}: Pick<\n BarcodeScannerProviderProps,\n \"config\" | \"scanner\" | \"acceptedHistoryLimit\" | \"autoInitialize\"\n>): ScannerStore {\n return createScannerStore({\n config,\n scanner,\n acceptedHistoryLimit,\n autoInitialize,\n });\n}\n\n/**\n * Owns or shares a ScannerStore for the subtree.\n *\n * Ownership rules:\n * - `config` → Provider creates + destroys the core scanner\n * - `scanner` → shared instance; caller owns destroy\n * - `store` → advanced: inject a prebuilt store\n */\nexport function BarcodeScannerProvider({\n children,\n config,\n scanner,\n acceptedHistoryLimit,\n autoInitialize = true,\n store: externalStore,\n}: BarcodeScannerProviderProps) {\n const [ownedStore, setOwnedStore] = useState(() =>\n createOwnedStore({\n config,\n scanner,\n acceptedHistoryLimit,\n autoInitialize,\n }),\n );\n\n if (!externalStore && ownedStore.isDestroyed) {\n setOwnedStore(\n createOwnedStore({\n config,\n scanner,\n acceptedHistoryLimit,\n autoInitialize,\n }),\n );\n }\n\n const store = externalStore ?? ownedStore;\n\n useEffect(() => {\n if (externalStore) {\n return;\n }\n\n if (store.isDestroyed) {\n setOwnedStore(\n createOwnedStore({\n config,\n scanner,\n acceptedHistoryLimit,\n autoInitialize,\n }),\n );\n return;\n }\n\n const active = store;\n return () => {\n void active.destroy();\n };\n }, [store, externalStore]);\n\n return (\n <ScannerStoreContext.Provider value={store}>\n {children}\n </ScannerStoreContext.Provider>\n );\n}\n","\"use client\";\n\nimport { useCallback, useMemo, useSyncExternalStore } from \"react\";\nimport type {\n AttachTarget,\n BarcodeResult,\n ImageScanSource,\n} from \"@barcodesdk/core\";\nimport { useScannerStoreContext } from \"./context.js\";\nimport type { ScannerStoreSnapshot } from \"./store.js\";\n\nexport interface UseBarcodeScannerResult extends ScannerStoreSnapshot {\n scanner: ReturnType<typeof useScannerStoreContext>[\"scanner\"];\n initialize: () => Promise<void>;\n attach: (target: AttachTarget) => Promise<void>;\n start: () => Promise<void>;\n stop: () => Promise<void>;\n pause: () => Promise<void>;\n resume: () => Promise<void>;\n scanImage: (source: ImageScanSource) => Promise<BarcodeResult[]>;\n clearError: () => void;\n clearAcceptedHistory: () => void;\n}\n\n/**\n * Subscribe to scanner state via `useSyncExternalStore`.\n * Core owns state; this hook only mirrors the store snapshot.\n */\nexport function useBarcodeScanner(): UseBarcodeScannerResult {\n const store = useScannerStoreContext();\n const snapshot = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n );\n\n const initialize = useCallback(() => store.initialize(), [store]);\n const attach = useCallback(\n (target: AttachTarget) => store.attach(target),\n [store],\n );\n const start = useCallback(() => store.start(), [store]);\n const stop = useCallback(() => store.stop(), [store]);\n const pause = useCallback(() => store.pause(), [store]);\n const resume = useCallback(() => store.resume(), [store]);\n const scanImage = useCallback(\n (source: ImageScanSource) => store.scanImage(source),\n [store],\n );\n const clearError = useCallback(() => store.clearError(), [store]);\n const clearAcceptedHistory = useCallback(\n () => store.clearAcceptedHistory(),\n [store],\n );\n\n return useMemo(\n () => ({\n ...snapshot,\n scanner: store.scanner,\n initialize,\n attach,\n start,\n stop,\n pause,\n resume,\n scanImage,\n clearError,\n clearAcceptedHistory,\n }),\n [\n snapshot,\n store.scanner,\n initialize,\n attach,\n start,\n stop,\n pause,\n resume,\n scanImage,\n clearError,\n clearAcceptedHistory,\n ],\n );\n}\n","\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n type CSSProperties,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport { useBarcodeScanner } from \"./use-barcode-scanner.js\";\n\nexport interface BarcodeScannerViewHandle {\n video: HTMLVideoElement | null;\n start: () => Promise<void>;\n stop: () => Promise<void>;\n}\n\nexport interface BarcodeScannerViewProps {\n className?: string;\n style?: CSSProperties;\n videoClassName?: string;\n videoStyle?: CSSProperties;\n /** Render overlay content inside the video frame (absolute positioned). */\n children?: ReactNode;\n /** Auto attach + start when the video element mounts and scanner is ready. */\n autoStart?: boolean;\n /** Forwarded ref with video + start/stop helpers. */\n viewRef?: Ref<BarcodeScannerViewHandle>;\n onStartError?: (error: unknown) => void;\n}\n\n/**\n * Video preview bound to the Provider scanner.\n * Overlay children should be absolutely positioned over the video.\n */\nexport function BarcodeScannerView({\n className,\n style,\n videoClassName,\n videoStyle,\n children,\n autoStart = false,\n viewRef,\n onStartError,\n}: BarcodeScannerViewProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n const { state, attach, start, stop } = useBarcodeScanner();\n const startedRef = useRef(false);\n\n const runStart = useCallback(async () => {\n const video = videoRef.current;\n if (!video) {\n return;\n }\n await attach({ video });\n await start();\n startedRef.current = true;\n }, [attach, start]);\n\n const runStop = useCallback(async () => {\n await stop();\n startedRef.current = false;\n }, [stop]);\n\n useImperativeHandle(\n viewRef,\n () => ({\n video: videoRef.current,\n start: runStart,\n stop: runStop,\n }),\n [runStart, runStop],\n );\n\n useEffect(() => {\n if (!autoStart) {\n return;\n }\n if (state !== \"ready\" && state !== \"paused\") {\n return;\n }\n if (startedRef.current) {\n return;\n }\n void runStart().catch((error) => {\n onStartError?.(error);\n });\n }, [autoStart, state, runStart, onStartError]);\n\n return (\n <div\n className={className}\n style={{ position: \"relative\", overflow: \"hidden\", ...style }}\n >\n <video\n ref={videoRef}\n className={videoClassName}\n aria-label=\"Barcode camera preview\"\n style={{\n display: \"block\",\n width: \"100%\",\n height: \"100%\",\n objectFit: \"contain\",\n ...videoStyle,\n }}\n muted\n playsInline\n />\n {children}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { BarcodeScanner, ScannerConfig } from "@barcodesdk/core";
|
|
3
|
+
import { type ScannerStore } from "./store.js";
|
|
4
|
+
export type BarcodeScannerProviderProps = {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* Create a Provider-owned scanner from config.
|
|
8
|
+
* The store destroys the scanner on unmount.
|
|
9
|
+
*/
|
|
10
|
+
config?: ScannerConfig;
|
|
11
|
+
/**
|
|
12
|
+
* Share an existing scanner. Provider will not destroy it on unmount.
|
|
13
|
+
* Mutually exclusive with `config`.
|
|
14
|
+
*/
|
|
15
|
+
scanner?: BarcodeScanner;
|
|
16
|
+
/** Max unique accepted codes kept in history (default 12). */
|
|
17
|
+
acceptedHistoryLimit?: number;
|
|
18
|
+
/** Auto initialize after mount (default true). */
|
|
19
|
+
autoInitialize?: boolean;
|
|
20
|
+
/** Optional external store (advanced); when set, config/scanner are ignored. */
|
|
21
|
+
store?: ScannerStore;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Owns or shares a ScannerStore for the subtree.
|
|
25
|
+
*
|
|
26
|
+
* Ownership rules:
|
|
27
|
+
* - `config` → Provider creates + destroys the core scanner
|
|
28
|
+
* - `scanner` → shared instance; caller owns destroy
|
|
29
|
+
* - `store` → advanced: inject a prebuilt store
|
|
30
|
+
*/
|
|
31
|
+
export declare function BarcodeScannerProvider({ children, config, scanner, acceptedHistoryLimit, autoInitialize, store: externalStore, }: BarcodeScannerProviderProps): import("react").JSX.Element;
|
|
32
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEnE,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;;;OAGG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,8DAA8D;IAC9D,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kDAAkD;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gFAAgF;IAChF,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB,CAAC;AAmBF;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,MAAM,EACN,OAAO,EACP,oBAAoB,EACpB,cAAqB,EACrB,KAAK,EAAE,aAAa,GACrB,EAAE,2BAA2B,+BAmD7B"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type AttachTarget, type BarcodeResult, type BarcodeScanner, type ImageScanSource, type ScannerCapabilities, type ScannerConfig, type ScannerErrorInfo, type ScannerMetrics, type ScannerState } from "@barcodesdk/core";
|
|
2
|
+
export interface ScannerStoreSnapshot {
|
|
3
|
+
state: ScannerState;
|
|
4
|
+
results: BarcodeResult[];
|
|
5
|
+
/** Most recent accepted batch from the tracker. */
|
|
6
|
+
accepted: BarcodeResult[];
|
|
7
|
+
/** Deduped accepted history for UI lists (ring buffer). */
|
|
8
|
+
acceptedHistory: BarcodeResult[];
|
|
9
|
+
lastError: ScannerErrorInfo | null;
|
|
10
|
+
metrics: ScannerMetrics | null;
|
|
11
|
+
capabilities: ScannerCapabilities | null;
|
|
12
|
+
}
|
|
13
|
+
export interface ScannerStoreOptions {
|
|
14
|
+
/** Create and own a scanner from config. Mutually exclusive with `scanner`. */
|
|
15
|
+
config?: ScannerConfig;
|
|
16
|
+
/** Share an existing scanner instance (Provider will not destroy it). */
|
|
17
|
+
scanner?: BarcodeScanner;
|
|
18
|
+
/** Max unique accepted codes retained in history (default 12). */
|
|
19
|
+
acceptedHistoryLimit?: number;
|
|
20
|
+
/** Auto-call initialize() after store creation (default true). */
|
|
21
|
+
autoInitialize?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* External store wrapping `@barcodesdk/core`. Designed for
|
|
25
|
+
* `useSyncExternalStore` — React never duplicates scanner state in useState.
|
|
26
|
+
*/
|
|
27
|
+
export declare class ScannerStore {
|
|
28
|
+
readonly scanner: BarcodeScanner;
|
|
29
|
+
readonly ownsScanner: boolean;
|
|
30
|
+
private snapshot;
|
|
31
|
+
private readonly listeners;
|
|
32
|
+
private readonly unsubscribers;
|
|
33
|
+
private readonly acceptedHistoryLimit;
|
|
34
|
+
private destroyed;
|
|
35
|
+
/** True after `destroy()`; used to recreate the store on Strict Mode remount. */
|
|
36
|
+
get isDestroyed(): boolean;
|
|
37
|
+
constructor(options: ScannerStoreOptions);
|
|
38
|
+
subscribe: (listener: () => void) => (() => void);
|
|
39
|
+
getSnapshot: () => ScannerStoreSnapshot;
|
|
40
|
+
getServerSnapshot: () => ScannerStoreSnapshot;
|
|
41
|
+
initialize(): Promise<void>;
|
|
42
|
+
attach(target: AttachTarget): Promise<void>;
|
|
43
|
+
start(): Promise<void>;
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
pause(): Promise<void>;
|
|
46
|
+
resume(): Promise<void>;
|
|
47
|
+
scanImage(source: ImageScanSource): Promise<BarcodeResult[]>;
|
|
48
|
+
clearError(): void;
|
|
49
|
+
clearAcceptedHistory(): void;
|
|
50
|
+
destroy(): Promise<void>;
|
|
51
|
+
private update;
|
|
52
|
+
private assertAlive;
|
|
53
|
+
}
|
|
54
|
+
export declare function createScannerStore(options: ScannerStoreOptions): ScannerStore;
|
|
55
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,YAAY,EAClB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,mDAAmD;IACnD,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,2DAA2D;IAC3D,eAAe,EAAE,aAAa,EAAE,CAAC;IACjC,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACnC,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,mBAAmB;IAClC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,yEAAyE;IACzE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,kEAAkE;IAClE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kEAAkE;IAClE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAkCD;;;GAGG;AACH,qBAAa,YAAY;IACvB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyB;IACvD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,SAAS,CAAS;IAE1B,iFAAiF;IACjF,IAAI,WAAW,IAAI,OAAO,CAEzB;gBAEW,OAAO,EAAE,mBAAmB;IA+CxC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAK9C;IAEF,WAAW,QAAO,oBAAoB,CAAkB;IAExD,iBAAiB,QAAO,oBAAoB,CAAmB;IAEzD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAUtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IASrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKvB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IASlE,UAAU,IAAI,IAAI;IAIlB,oBAAoB,IAAI,IAAI;IAItB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB9B,OAAO,CAAC,MAAM;IAOd,OAAO,CAAC,WAAW;CAKpB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAE7E"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AttachTarget, BarcodeResult, ImageScanSource } from "@barcodesdk/core";
|
|
2
|
+
import { useScannerStoreContext } from "./context.js";
|
|
3
|
+
import type { ScannerStoreSnapshot } from "./store.js";
|
|
4
|
+
export interface UseBarcodeScannerResult extends ScannerStoreSnapshot {
|
|
5
|
+
scanner: ReturnType<typeof useScannerStoreContext>["scanner"];
|
|
6
|
+
initialize: () => Promise<void>;
|
|
7
|
+
attach: (target: AttachTarget) => Promise<void>;
|
|
8
|
+
start: () => Promise<void>;
|
|
9
|
+
stop: () => Promise<void>;
|
|
10
|
+
pause: () => Promise<void>;
|
|
11
|
+
resume: () => Promise<void>;
|
|
12
|
+
scanImage: (source: ImageScanSource) => Promise<BarcodeResult[]>;
|
|
13
|
+
clearError: () => void;
|
|
14
|
+
clearAcceptedHistory: () => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Subscribe to scanner state via `useSyncExternalStore`.
|
|
18
|
+
* Core owns state; this hook only mirrors the store snapshot.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useBarcodeScanner(): UseBarcodeScannerResult;
|
|
21
|
+
//# sourceMappingURL=use-barcode-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-barcode-scanner.d.ts","sourceRoot":"","sources":["../src/use-barcode-scanner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,MAAM,WAAW,uBAAwB,SAAQ,oBAAoB;IACnE,OAAO,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,SAAS,CAAC,CAAC;IAC9D,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,SAAS,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IACjE,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,oBAAoB,EAAE,MAAM,IAAI,CAAC;CAClC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,uBAAuB,CAuD3D"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@barcodesdk/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React provider, hook, and scanner view for Barcode WebSDK",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"author": "Vijay Reddy",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/vjiayreddy/barcodesdk.git",
|
|
10
|
+
"directory": "packages/react"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public",
|
|
14
|
+
"tag": "beta"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"module": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
32
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@barcodesdk/core": "0.1.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@testing-library/react": "^16",
|
|
39
|
+
"@types/react": "^19",
|
|
40
|
+
"@types/react-dom": "^19",
|
|
41
|
+
"jsdom": "^26",
|
|
42
|
+
"react": "19.2.8",
|
|
43
|
+
"react-dom": "19.2.8",
|
|
44
|
+
"typescript": "^5",
|
|
45
|
+
"vite": "^6",
|
|
46
|
+
"vitest": "^3"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "vite build && tsc --emitDeclarationOnly -p tsconfig.build.json",
|
|
50
|
+
"dev": "vite build --watch",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:watch": "vitest"
|
|
54
|
+
}
|
|
55
|
+
}
|