@mleonard9/vin-scanner 1.5.1 → 1.5.2
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/README.md +19 -2
- package/ios/VisionCameraBarcodeScanner.m +2 -23
- package/ios/VisionCameraTextRecognition.m +2 -21
- package/lib/commonjs/ManualVinInput.js +25 -15
- package/lib/commonjs/ManualVinInput.js.map +1 -1
- package/lib/commonjs/PendingVinBanner.js +44 -21
- package/lib/commonjs/PendingVinBanner.js.map +1 -1
- package/lib/commonjs/ScannerChromeOverlay.js +185 -0
- package/lib/commonjs/ScannerChromeOverlay.js.map +1 -0
- package/lib/commonjs/TextVinPrompt.js +33 -18
- package/lib/commonjs/TextVinPrompt.js.map +1 -1
- package/lib/commonjs/index.js +11 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/scanBarcodes.js +6 -2
- package/lib/commonjs/scanBarcodes.js.map +1 -1
- package/lib/commonjs/scanText.js +6 -2
- package/lib/commonjs/scanText.js.map +1 -1
- package/lib/commonjs/useVinScanner.js +15 -4
- package/lib/commonjs/useVinScanner.js.map +1 -1
- package/lib/commonjs/vinUtils.js +6 -4
- package/lib/commonjs/vinUtils.js.map +1 -1
- package/lib/module/ManualVinInput.js +25 -15
- package/lib/module/ManualVinInput.js.map +1 -1
- package/lib/module/PendingVinBanner.js +45 -22
- package/lib/module/PendingVinBanner.js.map +1 -1
- package/lib/module/ScannerChromeOverlay.js +177 -0
- package/lib/module/ScannerChromeOverlay.js.map +1 -0
- package/lib/module/TextVinPrompt.js +33 -18
- package/lib/module/TextVinPrompt.js.map +1 -1
- package/lib/module/index.js +5 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/scanBarcodes.js +6 -2
- package/lib/module/scanBarcodes.js.map +1 -1
- package/lib/module/scanText.js +6 -2
- package/lib/module/scanText.js.map +1 -1
- package/lib/module/useVinScanner.js +15 -4
- package/lib/module/useVinScanner.js.map +1 -1
- package/lib/module/vinUtils.js +6 -4
- package/lib/module/vinUtils.js.map +1 -1
- package/lib/typescript/src/ManualVinInput.d.ts.map +1 -1
- package/lib/typescript/src/PendingVinBanner.d.ts +2 -1
- package/lib/typescript/src/PendingVinBanner.d.ts.map +1 -1
- package/lib/typescript/src/ScannerChromeOverlay.d.ts +19 -0
- package/lib/typescript/src/ScannerChromeOverlay.d.ts.map +1 -0
- package/lib/typescript/src/TextVinPrompt.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/scanBarcodes.d.ts.map +1 -1
- package/lib/typescript/src/scanText.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +8 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useVinScanner.d.ts.map +1 -1
- package/lib/typescript/src/vinUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ManualVinInput.tsx +22 -15
- package/src/PendingVinBanner.tsx +66 -27
- package/src/ScannerChromeOverlay.tsx +214 -0
- package/src/TextVinPrompt.tsx +31 -16
- package/src/index.tsx +6 -2
- package/src/scanBarcodes.ts +9 -5
- package/src/scanText.ts +9 -5
- package/src/types.ts +8 -2
- package/src/useVinScanner.ts +20 -4
- package/src/vinUtils.ts +5 -1
package/src/TextVinPrompt.tsx
CHANGED
|
@@ -32,7 +32,7 @@ export function TextVinPrompt({
|
|
|
32
32
|
onDismiss,
|
|
33
33
|
title = 'VIN detected',
|
|
34
34
|
subtitle,
|
|
35
|
-
buttonLabel = '
|
|
35
|
+
buttonLabel = 'Book It',
|
|
36
36
|
buttonColor = '#0A84FF',
|
|
37
37
|
}: TextVinPromptProps) {
|
|
38
38
|
if (!visible) return null;
|
|
@@ -52,8 +52,11 @@ export function TextVinPrompt({
|
|
|
52
52
|
style={styles.list}
|
|
53
53
|
contentContainerStyle={styles.listContent}
|
|
54
54
|
>
|
|
55
|
-
{candidates.map((candidate) => (
|
|
56
|
-
<View
|
|
55
|
+
{candidates.map((candidate, index) => (
|
|
56
|
+
<View
|
|
57
|
+
key={`${candidate.value}-${index}`}
|
|
58
|
+
style={[styles.row, index > 0 && styles.rowBorder]}
|
|
59
|
+
>
|
|
57
60
|
<Text style={styles.vin} numberOfLines={1}>
|
|
58
61
|
{candidate.value}
|
|
59
62
|
</Text>
|
|
@@ -81,7 +84,7 @@ export function TextVinPrompt({
|
|
|
81
84
|
const styles = StyleSheet.create({
|
|
82
85
|
backdrop: {
|
|
83
86
|
flex: 1,
|
|
84
|
-
backgroundColor: 'rgba(0,0,0,0.
|
|
87
|
+
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
85
88
|
alignItems: 'center',
|
|
86
89
|
justifyContent: 'center',
|
|
87
90
|
padding: 16,
|
|
@@ -89,38 +92,50 @@ const styles = StyleSheet.create({
|
|
|
89
92
|
sheet: {
|
|
90
93
|
width: '100%',
|
|
91
94
|
maxWidth: 420,
|
|
92
|
-
backgroundColor: '
|
|
93
|
-
borderRadius:
|
|
94
|
-
|
|
95
|
+
backgroundColor: 'white',
|
|
96
|
+
borderRadius: 20,
|
|
97
|
+
paddingVertical: 24,
|
|
98
|
+
paddingHorizontal: 16,
|
|
99
|
+
shadowColor: '#000',
|
|
100
|
+
shadowOffset: { width: 0, height: 2 },
|
|
101
|
+
shadowOpacity: 0.25,
|
|
102
|
+
shadowRadius: 4,
|
|
103
|
+
elevation: 5,
|
|
95
104
|
},
|
|
96
105
|
title: {
|
|
97
|
-
color: '
|
|
106
|
+
color: '#111827',
|
|
98
107
|
fontSize: 18,
|
|
99
108
|
fontWeight: '700',
|
|
100
|
-
marginBottom:
|
|
109
|
+
marginBottom: 8,
|
|
101
110
|
},
|
|
102
111
|
subtitle: {
|
|
103
|
-
color: '#
|
|
112
|
+
color: '#4B5563',
|
|
104
113
|
fontSize: 14,
|
|
105
|
-
marginBottom:
|
|
114
|
+
marginBottom: 12,
|
|
106
115
|
},
|
|
107
116
|
list: {
|
|
108
117
|
maxHeight: 220,
|
|
109
118
|
},
|
|
110
119
|
listContent: {
|
|
111
|
-
|
|
120
|
+
paddingBottom: 4,
|
|
112
121
|
},
|
|
113
122
|
row: {
|
|
114
123
|
flexDirection: 'row',
|
|
115
124
|
alignItems: 'center',
|
|
116
125
|
justifyContent: 'space-between',
|
|
117
|
-
|
|
126
|
+
paddingVertical: 10,
|
|
127
|
+
},
|
|
128
|
+
rowBorder: {
|
|
129
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
130
|
+
borderTopColor: '#E5E7EB',
|
|
118
131
|
},
|
|
119
132
|
vin: {
|
|
120
133
|
flex: 1,
|
|
121
|
-
color: '
|
|
134
|
+
color: '#111827',
|
|
122
135
|
fontSize: 16,
|
|
136
|
+
fontWeight: '700',
|
|
123
137
|
letterSpacing: 1,
|
|
138
|
+
marginRight: 12,
|
|
124
139
|
},
|
|
125
140
|
button: {
|
|
126
141
|
paddingHorizontal: 14,
|
|
@@ -133,13 +148,13 @@ const styles = StyleSheet.create({
|
|
|
133
148
|
fontWeight: '700',
|
|
134
149
|
},
|
|
135
150
|
dismiss: {
|
|
136
|
-
marginTop:
|
|
151
|
+
marginTop: 8,
|
|
137
152
|
alignSelf: 'flex-end',
|
|
138
153
|
paddingHorizontal: 8,
|
|
139
154
|
paddingVertical: 6,
|
|
140
155
|
},
|
|
141
156
|
dismissText: {
|
|
142
|
-
color: '#
|
|
157
|
+
color: '#6B7280',
|
|
143
158
|
fontSize: 14,
|
|
144
159
|
},
|
|
145
160
|
});
|
package/src/index.tsx
CHANGED
|
@@ -45,7 +45,9 @@ export const Camera = forwardRef(function Camera(
|
|
|
45
45
|
|
|
46
46
|
const handleDetections = useRunOnJS(
|
|
47
47
|
(payload: WorkletPayload) => {
|
|
48
|
-
const candidates = buildVinCandidates(payload, resolvedOptions)
|
|
48
|
+
const candidates = buildVinCandidates(payload, resolvedOptions).filter(
|
|
49
|
+
(c) => c.confidence >= resolvedOptions.detection.minConfidence
|
|
50
|
+
);
|
|
49
51
|
const barcodeCandidates = candidates.filter(
|
|
50
52
|
(c) => c.source === 'barcode'
|
|
51
53
|
);
|
|
@@ -306,8 +308,9 @@ function useTextScanner(
|
|
|
306
308
|
}
|
|
307
309
|
return createTextRecognitionPlugin({
|
|
308
310
|
language: options.language,
|
|
311
|
+
validationPattern: options.validationPattern,
|
|
309
312
|
});
|
|
310
|
-
}, [options.enabled, options.language]);
|
|
313
|
+
}, [options.enabled, options.language, options.validationPattern]);
|
|
311
314
|
}
|
|
312
315
|
|
|
313
316
|
export type {
|
|
@@ -334,3 +337,4 @@ export { isValidVin } from './vinUtils';
|
|
|
334
337
|
export { TextVinPrompt } from './TextVinPrompt';
|
|
335
338
|
export { PendingVinBanner } from './PendingVinBanner';
|
|
336
339
|
export { ManualVinInput } from './ManualVinInput';
|
|
340
|
+
export { ScannerChromeOverlay } from './ScannerChromeOverlay';
|
package/src/scanBarcodes.ts
CHANGED
|
@@ -216,11 +216,15 @@ export function createBarcodeScannerPlugin(
|
|
|
216
216
|
overrides || orientation
|
|
217
217
|
? mergeArgs(baseOptions, overrides, orientation ?? null)
|
|
218
218
|
: baseOptions;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
219
|
+
try {
|
|
220
|
+
const result = plugin.call(
|
|
221
|
+
unwrapPluginFrame(frame),
|
|
222
|
+
toSerializableArgs(args)
|
|
223
|
+
);
|
|
224
|
+
return normalizeBarcodeDetections(result);
|
|
225
|
+
} catch {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
224
228
|
},
|
|
225
229
|
};
|
|
226
230
|
}
|
package/src/scanText.ts
CHANGED
|
@@ -179,11 +179,15 @@ export function createTextRecognitionPlugin(
|
|
|
179
179
|
overrides || orientation
|
|
180
180
|
? mergeTextArgs(options, overrides, orientation ?? null)
|
|
181
181
|
: options;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
try {
|
|
183
|
+
const result = plugin.call(
|
|
184
|
+
unwrapPluginFrame(frame),
|
|
185
|
+
toSerializableArgs(args)
|
|
186
|
+
);
|
|
187
|
+
return normalizeTextDetections(result);
|
|
188
|
+
} catch {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
187
191
|
},
|
|
188
192
|
};
|
|
189
193
|
}
|
package/src/types.ts
CHANGED
|
@@ -152,6 +152,12 @@ export type BarcodeScannerOptions = {
|
|
|
152
152
|
export type TextScannerOptions = {
|
|
153
153
|
enabled?: boolean;
|
|
154
154
|
language?: TextRecognitionLanguage;
|
|
155
|
+
/**
|
|
156
|
+
* Optional regex pattern forwarded to the native text recognizer to reduce
|
|
157
|
+
* bridge traffic before VIN validation runs in JS.
|
|
158
|
+
* Default: `[A-Z0-9]{10,}`
|
|
159
|
+
*/
|
|
160
|
+
validationPattern?: string;
|
|
155
161
|
/**
|
|
156
162
|
* When true, text VINs are surfaced via `onTextPending` and must be
|
|
157
163
|
* confirmed manually; barcode VINs still emit immediately.
|
|
@@ -212,7 +218,7 @@ export type DetectionOptions = {
|
|
|
212
218
|
/**
|
|
213
219
|
* Enable intelligent frame quality checks to skip blurry or dark frames.
|
|
214
220
|
* This improves accuracy by only processing high-quality frames.
|
|
215
|
-
*
|
|
221
|
+
* Set to `false` to disable the luma/sharpness gates entirely.
|
|
216
222
|
*/
|
|
217
223
|
enableFrameQualityCheck?: boolean;
|
|
218
224
|
/**
|
|
@@ -227,7 +233,7 @@ export type DetectionOptions = {
|
|
|
227
233
|
minSharpness?: number;
|
|
228
234
|
/**
|
|
229
235
|
* Minimum candidate confidence required before emitting.
|
|
230
|
-
* Default: 0.
|
|
236
|
+
* Default: 0 (checksum validity is the primary acceptance gate).
|
|
231
237
|
*/
|
|
232
238
|
minConfidence?: number;
|
|
233
239
|
/**
|
package/src/useVinScanner.ts
CHANGED
|
@@ -23,6 +23,7 @@ export function useVinScanner(options?: VinScannerOptions) {
|
|
|
23
23
|
>([]);
|
|
24
24
|
const pendingTextTimestampRef = useRef<number | null>(null);
|
|
25
25
|
const pendingTextRef = useRef<VinCandidate[]>([]);
|
|
26
|
+
const pendingTextKeyRef = useRef('');
|
|
26
27
|
const pendingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
27
28
|
const hapticsEnabled = options?.haptics ?? true;
|
|
28
29
|
const sessionSeen = useRef<Set<string>>(new Set());
|
|
@@ -42,7 +43,7 @@ export function useVinScanner(options?: VinScannerOptions) {
|
|
|
42
43
|
}
|
|
43
44
|
return createTextRecognitionPlugin({
|
|
44
45
|
language: resolvedOptions.text.language,
|
|
45
|
-
validationPattern:
|
|
46
|
+
validationPattern: resolvedOptions.text.validationPattern,
|
|
46
47
|
});
|
|
47
48
|
}, [resolvedOptions.text]);
|
|
48
49
|
|
|
@@ -57,6 +58,16 @@ export function useVinScanner(options?: VinScannerOptions) {
|
|
|
57
58
|
|
|
58
59
|
const emitTextPending = useRunOnJS(
|
|
59
60
|
(pending: VinCandidate[], ts: number) => {
|
|
61
|
+
const pendingKey = pending
|
|
62
|
+
.map((candidate) => candidate.value)
|
|
63
|
+
.sort()
|
|
64
|
+
.join('|');
|
|
65
|
+
|
|
66
|
+
if (pendingKey.length === 0 || pendingKey === pendingTextKeyRef.current) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pendingTextKeyRef.current = pendingKey;
|
|
60
71
|
pendingTextTimestampRef.current = ts;
|
|
61
72
|
pendingTextRef.current = pending;
|
|
62
73
|
setPendingTextCandidates(pending);
|
|
@@ -66,6 +77,7 @@ export function useVinScanner(options?: VinScannerOptions) {
|
|
|
66
77
|
const ttl = resolvedOptions.text.pendingTtlMs;
|
|
67
78
|
if (ttl > 0) {
|
|
68
79
|
pendingTimerRef.current = setTimeout(() => {
|
|
80
|
+
pendingTextKeyRef.current = '';
|
|
69
81
|
pendingTextTimestampRef.current = null;
|
|
70
82
|
pendingTextRef.current = [];
|
|
71
83
|
setPendingTextCandidates([]);
|
|
@@ -227,7 +239,7 @@ export function useVinScanner(options?: VinScannerOptions) {
|
|
|
227
239
|
noBarcodeFrameCount.current = 0;
|
|
228
240
|
useBarcodeFallback.current = false;
|
|
229
241
|
}
|
|
230
|
-
const firstCandidate = pickFirstCandidate(
|
|
242
|
+
const firstCandidate = pickFirstCandidate(filtered, resolvedOptions);
|
|
231
243
|
|
|
232
244
|
const t3 = Date.now();
|
|
233
245
|
|
|
@@ -236,7 +248,7 @@ export function useVinScanner(options?: VinScannerOptions) {
|
|
|
236
248
|
const event: VinScannerEvent = {
|
|
237
249
|
timestamp: payload.timestamp,
|
|
238
250
|
duration,
|
|
239
|
-
candidates,
|
|
251
|
+
candidates: filtered,
|
|
240
252
|
firstCandidate,
|
|
241
253
|
raw: {
|
|
242
254
|
barcodes: payload.barcodes,
|
|
@@ -300,7 +312,7 @@ export function useVinScanner(options?: VinScannerOptions) {
|
|
|
300
312
|
lastEmittedVin.current = firstCandidate.value;
|
|
301
313
|
lastEmitTimestamp.current = now;
|
|
302
314
|
sessionSeen.current.add(firstCandidate.value);
|
|
303
|
-
emitResult(
|
|
315
|
+
emitResult(filtered, event);
|
|
304
316
|
}
|
|
305
317
|
}
|
|
306
318
|
},
|
|
@@ -321,6 +333,7 @@ export function useVinScanner(options?: VinScannerOptions) {
|
|
|
321
333
|
clearTimeout(pendingTimerRef.current);
|
|
322
334
|
pendingTimerRef.current = null;
|
|
323
335
|
}
|
|
336
|
+
pendingTextKeyRef.current = '';
|
|
324
337
|
pendingTextTimestampRef.current = null;
|
|
325
338
|
pendingTextRef.current = [];
|
|
326
339
|
setPendingTextCandidates([]);
|
|
@@ -334,6 +347,9 @@ export function useVinScanner(options?: VinScannerOptions) {
|
|
|
334
347
|
};
|
|
335
348
|
|
|
336
349
|
emitResult(pending, event);
|
|
350
|
+
if (selected?.value) {
|
|
351
|
+
sessionSeen.current.add(selected.value);
|
|
352
|
+
}
|
|
337
353
|
lastEmittedVin.current = selected?.value ?? null;
|
|
338
354
|
lastEmitTimestamp.current = Date.now();
|
|
339
355
|
},
|
package/src/vinUtils.ts
CHANGED
|
@@ -66,6 +66,7 @@ const DEFAULT_RESOLVED_OPTIONS: ResolvedVinScannerOptions = {
|
|
|
66
66
|
text: {
|
|
67
67
|
enabled: true,
|
|
68
68
|
language: 'latin',
|
|
69
|
+
validationPattern: '[A-Z0-9]{10,}',
|
|
69
70
|
requireConfirmation: false,
|
|
70
71
|
pendingTtlMs: 5000,
|
|
71
72
|
},
|
|
@@ -76,7 +77,7 @@ const DEFAULT_RESOLVED_OPTIONS: ResolvedVinScannerOptions = {
|
|
|
76
77
|
scanRegion: { x: 0.15, y: 0.15, width: 0.7, height: 0.7 },
|
|
77
78
|
minLuma: 30,
|
|
78
79
|
minSharpness: 12,
|
|
79
|
-
minConfidence: 0
|
|
80
|
+
minConfidence: 0,
|
|
80
81
|
barcodeFallbackAfter: 45,
|
|
81
82
|
},
|
|
82
83
|
showOverlay: false,
|
|
@@ -111,6 +112,9 @@ export const resolveOptions = (
|
|
|
111
112
|
enabled: options?.text?.enabled ?? DEFAULT_RESOLVED_OPTIONS.text.enabled,
|
|
112
113
|
language:
|
|
113
114
|
options?.text?.language ?? DEFAULT_RESOLVED_OPTIONS.text.language,
|
|
115
|
+
validationPattern:
|
|
116
|
+
options?.text?.validationPattern ??
|
|
117
|
+
DEFAULT_RESOLVED_OPTIONS.text.validationPattern,
|
|
114
118
|
requireConfirmation:
|
|
115
119
|
options?.text?.requireConfirmation ??
|
|
116
120
|
DEFAULT_RESOLVED_OPTIONS.text.requireConfirmation,
|