@mleonard9/vin-scanner 1.3.0 → 1.4.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.
Files changed (62) hide show
  1. package/README.md +129 -12
  2. package/android/src/main/java/com/visioncamerabarcodescanner/VisionCameraBarcodeScannerModule.kt +26 -17
  3. package/lib/commonjs/ManualVinInput.js +147 -0
  4. package/lib/commonjs/ManualVinInput.js.map +1 -0
  5. package/lib/commonjs/PendingVinBanner.js +120 -0
  6. package/lib/commonjs/PendingVinBanner.js.map +1 -0
  7. package/lib/commonjs/TextVinPrompt.js +132 -0
  8. package/lib/commonjs/TextVinPrompt.js.map +1 -0
  9. package/lib/commonjs/haptics.js +36 -0
  10. package/lib/commonjs/haptics.js.map +1 -0
  11. package/lib/commonjs/index.js +184 -13
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/commonjs/useVinScanner.js +164 -6
  14. package/lib/commonjs/useVinScanner.js.map +1 -1
  15. package/lib/commonjs/vinUtils.js +23 -9
  16. package/lib/commonjs/vinUtils.js.map +1 -1
  17. package/lib/module/ManualVinInput.js +139 -0
  18. package/lib/module/ManualVinInput.js.map +1 -0
  19. package/lib/module/PendingVinBanner.js +112 -0
  20. package/lib/module/PendingVinBanner.js.map +1 -0
  21. package/lib/module/TextVinPrompt.js +124 -0
  22. package/lib/module/TextVinPrompt.js.map +1 -0
  23. package/lib/module/haptics.js +27 -0
  24. package/lib/module/haptics.js.map +1 -0
  25. package/lib/module/index.js +171 -12
  26. package/lib/module/index.js.map +1 -1
  27. package/lib/module/useVinScanner.js +165 -7
  28. package/lib/module/useVinScanner.js.map +1 -1
  29. package/lib/module/vinUtils.js +23 -9
  30. package/lib/module/vinUtils.js.map +1 -1
  31. package/lib/typescript/src/ManualVinInput.d.ts +11 -0
  32. package/lib/typescript/src/ManualVinInput.d.ts.map +1 -0
  33. package/lib/typescript/src/PendingVinBanner.d.ts +17 -0
  34. package/lib/typescript/src/PendingVinBanner.d.ts.map +1 -0
  35. package/lib/typescript/src/TextVinPrompt.d.ts +20 -0
  36. package/lib/typescript/src/TextVinPrompt.d.ts.map +1 -0
  37. package/lib/typescript/src/haptics.d.ts +4 -0
  38. package/lib/typescript/src/haptics.d.ts.map +1 -0
  39. package/lib/typescript/src/index.d.ts +3 -1
  40. package/lib/typescript/src/index.d.ts.map +1 -1
  41. package/lib/typescript/src/types.d.ts +46 -7
  42. package/lib/typescript/src/types.d.ts.map +1 -1
  43. package/lib/typescript/src/useVinScanner.d.ts +3 -1
  44. package/lib/typescript/src/useVinScanner.d.ts.map +1 -1
  45. package/lib/typescript/src/vinUtils.d.ts +7 -2
  46. package/lib/typescript/src/vinUtils.d.ts.map +1 -1
  47. package/package.json +5 -1
  48. package/src/ManualVinInput.tsx +145 -0
  49. package/src/PendingVinBanner.tsx +128 -0
  50. package/src/TextVinPrompt.tsx +139 -0
  51. package/src/haptics.ts +32 -0
  52. package/src/index.tsx +195 -22
  53. package/src/types.ts +46 -7
  54. package/src/useVinScanner.ts +188 -8
  55. package/src/vinUtils.ts +34 -17
  56. package/lib/commonjs/VinScannerOverlay.js +0 -60
  57. package/lib/commonjs/VinScannerOverlay.js.map +0 -1
  58. package/lib/module/VinScannerOverlay.js +0 -53
  59. package/lib/module/VinScannerOverlay.js.map +0 -1
  60. package/lib/typescript/src/VinScannerOverlay.d.ts +0 -14
  61. package/lib/typescript/src/VinScannerOverlay.d.ts.map +0 -1
  62. package/src/VinScannerOverlay.tsx +0 -55
@@ -1,4 +1,4 @@
1
- import { useMemo, useRef } from 'react';
1
+ import { useMemo, useRef, useState, useCallback } from 'react';
2
2
  import { useFrameProcessor } from 'react-native-vision-camera';
3
3
  import { useRunOnJS } from 'react-native-worklets-core';
4
4
  import type { Frame } from 'react-native-vision-camera';
@@ -10,6 +10,10 @@ import {
10
10
  pickFirstCandidate,
11
11
  resolveOptions,
12
12
  } from './vinUtils';
13
+ import {
14
+ triggerSuccessHaptic,
15
+ triggerSoftHaptic,
16
+ } from './haptics';
13
17
 
14
18
  export function useVinScanner(options?: VinScannerOptions) {
15
19
  const resolvedOptions = useMemo(() => resolveOptions(options), [options]);
@@ -17,6 +21,14 @@ export function useVinScanner(options?: VinScannerOptions) {
17
21
  const frameCounterRef = useRef(0);
18
22
  const lastEmittedVin = useRef<string | null>(null);
19
23
  const lastEmitTimestamp = useRef(0);
24
+ const [pendingTextCandidates, setPendingTextCandidates] = useState<VinCandidate[]>([]);
25
+ const pendingTextTimestampRef = useRef<number | null>(null);
26
+ const pendingTextRef = useRef<VinCandidate[]>([]);
27
+ const pendingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
28
+ const hapticsEnabled = options?.haptics ?? true;
29
+ const sessionSeen = useRef<Set<string>>(new Set());
30
+ const noBarcodeFrameCount = useRef(0);
31
+ const useBarcodeFallback = useRef(false);
20
32
 
21
33
  const barcodeScanner = useMemo(() => {
22
34
  if (!resolvedOptions.barcode.enabled) {
@@ -44,6 +56,32 @@ export function useVinScanner(options?: VinScannerOptions) {
44
56
  [options?.onResult]
45
57
  );
46
58
 
59
+ const emitTextPending = useRunOnJS(
60
+ (pending: VinCandidate[], ts: number) => {
61
+ pendingTextTimestampRef.current = ts;
62
+ pendingTextRef.current = pending;
63
+ setPendingTextCandidates(pending);
64
+ if (pendingTimerRef.current) {
65
+ clearTimeout(pendingTimerRef.current);
66
+ }
67
+ const ttl = resolvedOptions.text.pendingTtlMs;
68
+ if (ttl > 0) {
69
+ pendingTimerRef.current = setTimeout(() => {
70
+ pendingTextTimestampRef.current = null;
71
+ pendingTextRef.current = [];
72
+ setPendingTextCandidates([]);
73
+ }, ttl);
74
+ }
75
+ if (typeof options?.onTextPending === 'function') {
76
+ options.onTextPending(pending);
77
+ }
78
+ if (hapticsEnabled) {
79
+ triggerSoftHaptic();
80
+ }
81
+ },
82
+ [options?.onTextPending, resolvedOptions.text.pendingTtlMs, hapticsEnabled]
83
+ );
84
+
47
85
  const frameProcessor = useFrameProcessor(
48
86
  (frame: Frame) => {
49
87
  'worklet';
@@ -65,6 +103,52 @@ export function useVinScanner(options?: VinScannerOptions) {
65
103
  resolvedOptions.detection.forceOrientation ?? null;
66
104
  const regionOverride = resolvedOptions.detection.scanRegion ?? null;
67
105
 
106
+ // Frame quality gate (luma + sharpness)
107
+ const minLuma = resolvedOptions.detection.minLuma;
108
+ const minSharpness = resolvedOptions.detection.minSharpness;
109
+ if (minLuma > 0 || minSharpness > 0) {
110
+ const planes = (frame as any)?.planes;
111
+ if (planes?.length > 0) {
112
+ const yPlane = planes[0];
113
+ const bytes: Uint8Array | undefined = yPlane?.bytes;
114
+ const width: number | undefined = yPlane?.width;
115
+ const height: number | undefined = yPlane?.height;
116
+ const stride: number | undefined = yPlane?.bytesPerRow;
117
+ if (bytes && width && height && stride) {
118
+ let lumaSum = 0;
119
+ const sampleStep = 8;
120
+ const sampleCount = Math.floor(bytes.length / sampleStep);
121
+ for (let i = 0; i < bytes.length; i += sampleStep) {
122
+ lumaSum += bytes[i]!;
123
+ }
124
+ const meanLuma = lumaSum / Math.max(1, sampleCount);
125
+ if (minLuma > 0 && meanLuma < minLuma) {
126
+ return;
127
+ }
128
+
129
+ if (minSharpness > 0) {
130
+ const step = Math.max(2, Math.floor(width / 96));
131
+ let sharpAccum = 0;
132
+ let sharpCount = 0;
133
+ for (let y = step; y < height - step; y += step) {
134
+ const row = y * stride;
135
+ for (let x = step; x < width - step; x += step) {
136
+ const idx = row + x;
137
+ const gx = bytes[idx + 1]! - bytes[idx - 1]!;
138
+ const gy = bytes[idx + stride]! - bytes[idx - stride]!;
139
+ sharpAccum += Math.abs(gx) + Math.abs(gy);
140
+ sharpCount += 1;
141
+ }
142
+ }
143
+ const sharpness = sharpAccum / Math.max(1, sharpCount);
144
+ if (sharpness < minSharpness) {
145
+ return;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+
68
152
  const barcodeArgs = orientationOverride || regionOverride
69
153
  ? {
70
154
  ...(orientationOverride && { orientation: orientationOverride }),
@@ -76,7 +160,12 @@ export function useVinScanner(options?: VinScannerOptions) {
76
160
  const t0 = Date.now();
77
161
 
78
162
  const barcodes = barcodeScanner
79
- ? barcodeScanner.scanBarcodes(frame, barcodeArgs)
163
+ ? barcodeScanner.scanBarcodes(
164
+ frame,
165
+ useBarcodeFallback.current
166
+ ? { all: true }
167
+ : barcodeArgs
168
+ ) ?? []
80
169
  : [];
81
170
 
82
171
  const t1 = Date.now();
@@ -96,11 +185,21 @@ export function useVinScanner(options?: VinScannerOptions) {
96
185
  textScanner &&
97
186
  (textScanInterval <= 1 || frameIndex % textScanInterval === 0);
98
187
 
99
- const textBlocks =
188
+ let textBlocks =
100
189
  shouldRunText && textScanner
101
- ? textScanner.scanText(frame, textArgs)
190
+ ? textScanner.scanText(frame, textArgs) ?? []
102
191
  : [];
103
192
 
193
+ // Two-pass OCR: if we skipped this frame or got nothing and barcodes are empty, run one immediate OCR pass.
194
+ if (
195
+ textScanner &&
196
+ textBlocks.length === 0 &&
197
+ barcodes.length === 0 &&
198
+ !shouldRunText
199
+ ) {
200
+ textBlocks = textScanner.scanText(frame, textArgs) ?? [];
201
+ }
202
+
104
203
  const t2 = Date.now();
105
204
 
106
205
  const payload = {
@@ -110,6 +209,20 @@ export function useVinScanner(options?: VinScannerOptions) {
110
209
  };
111
210
 
112
211
  const candidates = buildVinCandidates(payload, resolvedOptions);
212
+ const filtered = candidates.filter((c) => c.confidence >= resolvedOptions.detection.minConfidence);
213
+ const barcodeCandidates = filtered.filter((c) => c.source === 'barcode');
214
+ const textCandidates = filtered.filter((c) => c.source === 'text');
215
+
216
+ // Adaptive barcode fallback: if no barcode hits for N frames, scan all formats.
217
+ if (barcodeCandidates.length === 0) {
218
+ noBarcodeFrameCount.current += 1;
219
+ if (noBarcodeFrameCount.current >= resolvedOptions.detection.barcodeFallbackAfter) {
220
+ useBarcodeFallback.current = true;
221
+ }
222
+ } else {
223
+ noBarcodeFrameCount.current = 0;
224
+ useBarcodeFallback.current = false;
225
+ }
113
226
  const firstCandidate = pickFirstCandidate(candidates, resolvedOptions);
114
227
 
115
228
  const t3 = Date.now();
@@ -133,22 +246,89 @@ export function useVinScanner(options?: VinScannerOptions) {
133
246
  },
134
247
  };
135
248
 
136
- // Smart duplicate filtering with time-based debouncing
249
+ // If a barcode is present, emit immediately (highest confidence path)
250
+ if (barcodeCandidates.length > 0) {
251
+ const barcodeFirst = pickFirstCandidate(barcodeCandidates, resolvedOptions);
252
+ if (barcodeFirst) {
253
+ const debounceMs = resolvedOptions.duplicateDebounceMs ?? 1500;
254
+ const isDuplicate =
255
+ barcodeFirst.value === lastEmittedVin.current &&
256
+ now - lastEmitTimestamp.current < debounceMs;
257
+
258
+ if (!isDuplicate) {
259
+ lastEmittedVin.current = barcodeFirst.value;
260
+ lastEmitTimestamp.current = now;
261
+ sessionSeen.current.add(barcodeFirst.value);
262
+ emitResult(barcodeCandidates, {
263
+ ...event,
264
+ candidates: barcodeCandidates,
265
+ firstCandidate: barcodeFirst,
266
+ });
267
+ if (hapticsEnabled) {
268
+ triggerSuccessHaptic();
269
+ }
270
+ }
271
+ }
272
+ return;
273
+ }
274
+
275
+ // For text-only detections, optionally require user confirmation
276
+ if (resolvedOptions.text.requireConfirmation && textCandidates.length > 0) {
277
+ emitTextPending(textCandidates, payload.timestamp);
278
+ return;
279
+ }
280
+
281
+ // Default behavior: emit first candidate (text)
137
282
  if (firstCandidate) {
138
283
  const debounceMs = resolvedOptions.duplicateDebounceMs ?? 1500;
139
284
  const isDuplicate =
140
285
  firstCandidate.value === lastEmittedVin.current &&
141
286
  now - lastEmitTimestamp.current < debounceMs;
287
+ const seen = sessionSeen.current.has(firstCandidate.value);
142
288
 
143
- if (!isDuplicate) {
289
+ if (!isDuplicate && !seen) {
144
290
  lastEmittedVin.current = firstCandidate.value;
145
291
  lastEmitTimestamp.current = now;
292
+ sessionSeen.current.add(firstCandidate.value);
146
293
  emitResult(candidates, event);
147
294
  }
148
295
  }
149
296
  },
150
- [barcodeScanner, textScanner, emitResult, resolvedOptions]
297
+ [barcodeScanner, textScanner, emitResult, emitTextPending, resolvedOptions]
298
+ );
299
+
300
+ const confirmTextCandidate = useCallback(
301
+ (vinValue?: string) => {
302
+ const pending = pendingTextRef.current;
303
+ if (!pending.length) return;
304
+ const selected =
305
+ (vinValue && pending.find((c) => c.value === vinValue)) || pending[0];
306
+
307
+ const ts = pendingTextTimestampRef.current ?? Date.now();
308
+
309
+ // Clear pending state
310
+ if (pendingTimerRef.current) {
311
+ clearTimeout(pendingTimerRef.current);
312
+ pendingTimerRef.current = null;
313
+ }
314
+ pendingTextTimestampRef.current = null;
315
+ pendingTextRef.current = [];
316
+ setPendingTextCandidates([]);
317
+
318
+ const event: VinScannerEvent = {
319
+ timestamp: ts,
320
+ duration: 0,
321
+ candidates: pending,
322
+ firstCandidate: selected,
323
+ raw: { barcodes: [], textBlocks: [] },
324
+ };
325
+
326
+ emitResult(pending, event);
327
+ lastEmittedVin.current = selected?.value ?? null;
328
+ lastEmitTimestamp.current = Date.now();
329
+ },
330
+ [emitResult]
151
331
  );
152
332
 
153
- return { frameProcessor };
333
+ return { frameProcessor, pendingTextCandidates, confirmTextCandidate };
154
334
  }
package/src/vinUtils.ts CHANGED
@@ -40,13 +40,18 @@ export type ResolvedVinScannerOptions = {
40
40
  enabled: boolean;
41
41
  language: TextRecognitionLanguage;
42
42
  validationPattern?: string;
43
+ requireConfirmation: boolean;
44
+ pendingTtlMs: number;
43
45
  };
44
- detection: Required<DetectionOptions> & {
46
+ detection: Omit<Required<DetectionOptions>, 'enableFrameQualityCheck'> & {
45
47
  textScanInterval: number;
46
48
  maxFrameRate: number;
47
49
  forceOrientation: FrameOrientation | null;
48
50
  scanRegion: ScanRegion | null;
49
- enableFrameQualityCheck: boolean;
51
+ minLuma: number;
52
+ minSharpness: number;
53
+ minConfidence: number;
54
+ barcodeFallbackAfter: number;
50
55
  };
51
56
  showOverlay: boolean;
52
57
  overlayColors: OverlayColors;
@@ -56,18 +61,23 @@ export type ResolvedVinScannerOptions = {
56
61
  const DEFAULT_RESOLVED_OPTIONS: ResolvedVinScannerOptions = {
57
62
  barcode: {
58
63
  enabled: true,
59
- formats: ['all'],
64
+ formats: ['code-39', 'code-128', 'pdf-417'],
60
65
  },
61
66
  text: {
62
67
  enabled: true,
63
68
  language: 'latin',
69
+ requireConfirmation: false,
70
+ pendingTtlMs: 5000,
64
71
  },
65
72
  detection: {
66
73
  textScanInterval: 3,
67
74
  maxFrameRate: 30,
68
75
  forceOrientation: null,
69
76
  scanRegion: { x: 0.15, y: 0.15, width: 0.7, height: 0.7 },
70
- enableFrameQualityCheck: true,
77
+ minLuma: 30,
78
+ minSharpness: 12,
79
+ minConfidence: 0.6,
80
+ barcodeFallbackAfter: 45,
71
81
  },
72
82
  showOverlay: false,
73
83
  overlayColors: {
@@ -101,6 +111,11 @@ export const resolveOptions = (
101
111
  enabled: options?.text?.enabled ?? DEFAULT_RESOLVED_OPTIONS.text.enabled,
102
112
  language:
103
113
  options?.text?.language ?? DEFAULT_RESOLVED_OPTIONS.text.language,
114
+ requireConfirmation:
115
+ options?.text?.requireConfirmation ??
116
+ DEFAULT_RESOLVED_OPTIONS.text.requireConfirmation,
117
+ pendingTtlMs:
118
+ options?.text?.pendingTtlMs ?? DEFAULT_RESOLVED_OPTIONS.text.pendingTtlMs,
104
119
  },
105
120
  detection: {
106
121
  textScanInterval,
@@ -111,8 +126,14 @@ export const resolveOptions = (
111
126
  options?.detection?.forceOrientation ??
112
127
  DEFAULT_RESOLVED_OPTIONS.detection.forceOrientation,
113
128
  scanRegion: options?.detection?.scanRegion ?? DEFAULT_RESOLVED_OPTIONS.detection.scanRegion,
114
- enableFrameQualityCheck:
115
- options?.detection?.enableFrameQualityCheck ?? true,
129
+ minLuma:
130
+ options?.detection?.minLuma ?? DEFAULT_RESOLVED_OPTIONS.detection.minLuma,
131
+ minSharpness:
132
+ options?.detection?.minSharpness ?? DEFAULT_RESOLVED_OPTIONS.detection.minSharpness,
133
+ minConfidence:
134
+ options?.detection?.minConfidence ?? DEFAULT_RESOLVED_OPTIONS.detection.minConfidence,
135
+ barcodeFallbackAfter:
136
+ options?.detection?.barcodeFallbackAfter ?? DEFAULT_RESOLVED_OPTIONS.detection.barcodeFallbackAfter,
116
137
  },
117
138
  showOverlay: options?.showOverlay ?? false,
118
139
  overlayColors: {
@@ -431,17 +452,13 @@ const isValidWmi = (value: string): boolean => {
431
452
  return false;
432
453
  }
433
454
  const firstChar = value[0];
434
- // North America: 1, 2 (Canada), 3, 4, 5 (United States)
435
- // Japan : 'J' Germany : 'W'
436
- return (
437
- firstChar === '1' ||
438
- firstChar === '2' ||
439
- firstChar === '3' ||
440
- firstChar === '4' ||
441
- firstChar === '5' ||
442
- firstChar === 'J' ||
443
- firstChar === 'W'
444
- );
455
+ if (!firstChar) {
456
+ return false;
457
+ }
458
+
459
+ // Allow any WMI that starts with a VIN-valid character (A–Z minus I/O/Q, or 1–9).
460
+ // This keeps validation global while still rejecting impossible leading chars.
461
+ return /^[A-HJ-NPR-Z1-9]$/.test(firstChar);
445
462
  };
446
463
 
447
464
  /**
@@ -1,60 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.VinScannerOverlay = VinScannerOverlay;
7
- var _react = _interopRequireDefault(require("react"));
8
- var _reactNative = require("react-native");
9
- var _reactNativeSkia = require("@shopify/react-native-skia");
10
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
- const DEFAULT_COLORS = {
12
- high: '#00FF00',
13
- medium: '#FFFF00',
14
- low: '#FF0000'
15
- };
16
-
17
- /**
18
- * AR overlay component that renders bounding boxes around detected VINs.
19
- * Color-coded by confidence score: green (>0.8), yellow (0.5-0.8), red (<0.5).
20
- *
21
- * Requires @shopify/react-native-skia peer dependency.
22
- */
23
- function VinScannerOverlay({
24
- candidates,
25
- colors
26
- }) {
27
- const resolvedColors = {
28
- ...DEFAULT_COLORS,
29
- ...colors
30
- };
31
- const getColor = confidence => {
32
- if (confidence > 0.8) return resolvedColors.high;
33
- if (confidence >= 0.5) return resolvedColors.medium;
34
- return resolvedColors.low;
35
- };
36
- return /*#__PURE__*/_react.default.createElement(_reactNativeSkia.Canvas, {
37
- style: _reactNative.StyleSheet.absoluteFill,
38
- pointerEvents: "none"
39
- }, candidates.map((candidate, index) => {
40
- if (!candidate.boundingBox) return null;
41
- const {
42
- left,
43
- top,
44
- width,
45
- height
46
- } = candidate.boundingBox;
47
- const color = getColor(candidate.confidence);
48
- return /*#__PURE__*/_react.default.createElement(_reactNativeSkia.Rect, {
49
- key: `${candidate.value}-${index}`,
50
- x: left,
51
- y: top,
52
- width: width || 0,
53
- height: height || 0,
54
- color: color,
55
- style: "stroke",
56
- strokeWidth: 3
57
- });
58
- }));
59
- }
60
- //# sourceMappingURL=VinScannerOverlay.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["_react","_interopRequireDefault","require","_reactNative","_reactNativeSkia","e","__esModule","default","DEFAULT_COLORS","high","medium","low","VinScannerOverlay","candidates","colors","resolvedColors","getColor","confidence","createElement","Canvas","style","StyleSheet","absoluteFill","pointerEvents","map","candidate","index","boundingBox","left","top","width","height","color","Rect","key","value","x","y","strokeWidth"],"sourceRoot":"../../src","sources":["VinScannerOverlay.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,sBAAA,CAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AACA,IAAAE,gBAAA,GAAAF,OAAA;AAA0D,SAAAD,uBAAAI,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAQ1D,MAAMG,cAA6B,GAAG;EAClCC,IAAI,EAAE,SAAS;EACfC,MAAM,EAAE,SAAS;EACjBC,GAAG,EAAE;AACT,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,iBAAiBA,CAAC;EAAEC,UAAU;EAAEC;AAA+B,CAAC,EAAE;EAC9E,MAAMC,cAAc,GAAG;IAAE,GAAGP,cAAc;IAAE,GAAGM;EAAO,CAAC;EAEvD,MAAME,QAAQ,GAAIC,UAAkB,IAAa;IAC7C,IAAIA,UAAU,GAAG,GAAG,EAAE,OAAOF,cAAc,CAACN,IAAI;IAChD,IAAIQ,UAAU,IAAI,GAAG,EAAE,OAAOF,cAAc,CAACL,MAAM;IACnD,OAAOK,cAAc,CAACJ,GAAG;EAC7B,CAAC;EAED,oBACIX,MAAA,CAAAO,OAAA,CAAAW,aAAA,CAACd,gBAAA,CAAAe,MAAM;IAACC,KAAK,EAAEC,uBAAU,CAACC,YAAa;IAACC,aAAa,EAAC;EAAM,GACvDV,UAAU,CAACW,GAAG,CAAC,CAACC,SAAS,EAAEC,KAAK,KAAK;IAClC,IAAI,CAACD,SAAS,CAACE,WAAW,EAAE,OAAO,IAAI;IAEvC,MAAM;MAAEC,IAAI;MAAEC,GAAG;MAAEC,KAAK;MAAEC;IAAO,CAAC,GAAGN,SAAS,CAACE,WAAW;IAC1D,MAAMK,KAAK,GAAGhB,QAAQ,CAACS,SAAS,CAACR,UAAU,CAAC;IAE5C,oBACIjB,MAAA,CAAAO,OAAA,CAAAW,aAAA,CAACd,gBAAA,CAAA6B,IAAI;MACDC,GAAG,EAAE,GAAGT,SAAS,CAACU,KAAK,IAAIT,KAAK,EAAG;MACnCU,CAAC,EAAER,IAAK;MACRS,CAAC,EAAER,GAAI;MACPC,KAAK,EAAEA,KAAK,IAAI,CAAE;MAClBC,MAAM,EAAEA,MAAM,IAAI,CAAE;MACpBC,KAAK,EAAEA,KAAM;MACbZ,KAAK,EAAC,QAAQ;MACdkB,WAAW,EAAE;IAAE,CAClB,CAAC;EAEV,CAAC,CACG,CAAC;AAEjB","ignoreList":[]}
@@ -1,53 +0,0 @@
1
- import React from 'react';
2
- import { StyleSheet } from 'react-native';
3
- import { Canvas, Rect } from '@shopify/react-native-skia';
4
- const DEFAULT_COLORS = {
5
- high: '#00FF00',
6
- medium: '#FFFF00',
7
- low: '#FF0000'
8
- };
9
-
10
- /**
11
- * AR overlay component that renders bounding boxes around detected VINs.
12
- * Color-coded by confidence score: green (>0.8), yellow (0.5-0.8), red (<0.5).
13
- *
14
- * Requires @shopify/react-native-skia peer dependency.
15
- */
16
- export function VinScannerOverlay({
17
- candidates,
18
- colors
19
- }) {
20
- const resolvedColors = {
21
- ...DEFAULT_COLORS,
22
- ...colors
23
- };
24
- const getColor = confidence => {
25
- if (confidence > 0.8) return resolvedColors.high;
26
- if (confidence >= 0.5) return resolvedColors.medium;
27
- return resolvedColors.low;
28
- };
29
- return /*#__PURE__*/React.createElement(Canvas, {
30
- style: StyleSheet.absoluteFill,
31
- pointerEvents: "none"
32
- }, candidates.map((candidate, index) => {
33
- if (!candidate.boundingBox) return null;
34
- const {
35
- left,
36
- top,
37
- width,
38
- height
39
- } = candidate.boundingBox;
40
- const color = getColor(candidate.confidence);
41
- return /*#__PURE__*/React.createElement(Rect, {
42
- key: `${candidate.value}-${index}`,
43
- x: left,
44
- y: top,
45
- width: width || 0,
46
- height: height || 0,
47
- color: color,
48
- style: "stroke",
49
- strokeWidth: 3
50
- });
51
- }));
52
- }
53
- //# sourceMappingURL=VinScannerOverlay.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["React","StyleSheet","Canvas","Rect","DEFAULT_COLORS","high","medium","low","VinScannerOverlay","candidates","colors","resolvedColors","getColor","confidence","createElement","style","absoluteFill","pointerEvents","map","candidate","index","boundingBox","left","top","width","height","color","key","value","x","y","strokeWidth"],"sourceRoot":"../../src","sources":["VinScannerOverlay.tsx"],"mappings":"AAAA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,UAAU,QAAQ,cAAc;AACzC,SAASC,MAAM,EAAEC,IAAI,QAAQ,4BAA4B;AAQzD,MAAMC,cAA6B,GAAG;EAClCC,IAAI,EAAE,SAAS;EACfC,MAAM,EAAE,SAAS;EACjBC,GAAG,EAAE;AACT,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,iBAAiBA,CAAC;EAAEC,UAAU;EAAEC;AAA+B,CAAC,EAAE;EAC9E,MAAMC,cAAc,GAAG;IAAE,GAAGP,cAAc;IAAE,GAAGM;EAAO,CAAC;EAEvD,MAAME,QAAQ,GAAIC,UAAkB,IAAa;IAC7C,IAAIA,UAAU,GAAG,GAAG,EAAE,OAAOF,cAAc,CAACN,IAAI;IAChD,IAAIQ,UAAU,IAAI,GAAG,EAAE,OAAOF,cAAc,CAACL,MAAM;IACnD,OAAOK,cAAc,CAACJ,GAAG;EAC7B,CAAC;EAED,oBACIP,KAAA,CAAAc,aAAA,CAACZ,MAAM;IAACa,KAAK,EAAEd,UAAU,CAACe,YAAa;IAACC,aAAa,EAAC;EAAM,GACvDR,UAAU,CAACS,GAAG,CAAC,CAACC,SAAS,EAAEC,KAAK,KAAK;IAClC,IAAI,CAACD,SAAS,CAACE,WAAW,EAAE,OAAO,IAAI;IAEvC,MAAM;MAAEC,IAAI;MAAEC,GAAG;MAAEC,KAAK;MAAEC;IAAO,CAAC,GAAGN,SAAS,CAACE,WAAW;IAC1D,MAAMK,KAAK,GAAGd,QAAQ,CAACO,SAAS,CAACN,UAAU,CAAC;IAE5C,oBACIb,KAAA,CAAAc,aAAA,CAACX,IAAI;MACDwB,GAAG,EAAE,GAAGR,SAAS,CAACS,KAAK,IAAIR,KAAK,EAAG;MACnCS,CAAC,EAAEP,IAAK;MACRQ,CAAC,EAAEP,GAAI;MACPC,KAAK,EAAEA,KAAK,IAAI,CAAE;MAClBC,MAAM,EAAEA,MAAM,IAAI,CAAE;MACpBC,KAAK,EAAEA,KAAM;MACbX,KAAK,EAAC,QAAQ;MACdgB,WAAW,EAAE;IAAE,CAClB,CAAC;EAEV,CAAC,CACG,CAAC;AAEjB","ignoreList":[]}
@@ -1,14 +0,0 @@
1
- import React from 'react';
2
- import type { VinCandidate, OverlayColors } from './types';
3
- export type VinScannerOverlayProps = {
4
- candidates: VinCandidate[];
5
- colors?: Partial<OverlayColors>;
6
- };
7
- /**
8
- * AR overlay component that renders bounding boxes around detected VINs.
9
- * Color-coded by confidence score: green (>0.8), yellow (0.5-0.8), red (<0.5).
10
- *
11
- * Requires @shopify/react-native-skia peer dependency.
12
- */
13
- export declare function VinScannerOverlay({ candidates, colors }: VinScannerOverlayProps): React.JSX.Element;
14
- //# sourceMappingURL=VinScannerOverlay.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"VinScannerOverlay.d.ts","sourceRoot":"","sources":["../../../src/VinScannerOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,MAAM,sBAAsB,GAAG;IACjC,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CACnC,CAAC;AAQF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,sBAAsB,qBAgC/E"}
@@ -1,55 +0,0 @@
1
- import React from 'react';
2
- import { StyleSheet } from 'react-native';
3
- import { Canvas, Rect } from '@shopify/react-native-skia';
4
- import type { VinCandidate, OverlayColors } from './types';
5
-
6
- export type VinScannerOverlayProps = {
7
- candidates: VinCandidate[];
8
- colors?: Partial<OverlayColors>;
9
- };
10
-
11
- const DEFAULT_COLORS: OverlayColors = {
12
- high: '#00FF00',
13
- medium: '#FFFF00',
14
- low: '#FF0000',
15
- };
16
-
17
- /**
18
- * AR overlay component that renders bounding boxes around detected VINs.
19
- * Color-coded by confidence score: green (>0.8), yellow (0.5-0.8), red (<0.5).
20
- *
21
- * Requires @shopify/react-native-skia peer dependency.
22
- */
23
- export function VinScannerOverlay({ candidates, colors }: VinScannerOverlayProps) {
24
- const resolvedColors = { ...DEFAULT_COLORS, ...colors };
25
-
26
- const getColor = (confidence: number): string => {
27
- if (confidence > 0.8) return resolvedColors.high;
28
- if (confidence >= 0.5) return resolvedColors.medium;
29
- return resolvedColors.low;
30
- };
31
-
32
- return (
33
- <Canvas style={StyleSheet.absoluteFill} pointerEvents="none">
34
- {candidates.map((candidate, index) => {
35
- if (!candidate.boundingBox) return null;
36
-
37
- const { left, top, width, height } = candidate.boundingBox;
38
- const color = getColor(candidate.confidence);
39
-
40
- return (
41
- <Rect
42
- key={`${candidate.value}-${index}`}
43
- x={left}
44
- y={top}
45
- width={width || 0}
46
- height={height || 0}
47
- color={color}
48
- style="stroke"
49
- strokeWidth={3}
50
- />
51
- );
52
- })}
53
- </Canvas>
54
- );
55
- }