@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
package/README.md CHANGED
@@ -12,6 +12,8 @@ High-performance VIN detection for React Native powered by Google ML Kit barcode
12
12
 
13
13
  - `react-native-vision-camera` >= 3.9.0
14
14
  - `react-native-worklets-core` >= 0.4.0
15
+ - `react-native-gesture-handler` >= 2.0.0 (for tap-to-focus)
16
+ - `react-native-reanimated` >= 3.0.0 (for tap-to-focus)
15
17
  - iOS 13+ / Android 21+
16
18
 
17
19
  ## Installation
@@ -73,11 +75,45 @@ export function VinScannerExample(): JSX.Element {
73
75
 
74
76
  Every frame, the camera runs ML Kit barcode + text recognition, extracts 17-character VIN candidates, validates them (checksum included), and routes a payload to `callback`.
75
77
 
78
+ ## Camera Gestures
79
+
80
+ The VIN Scanner camera includes built-in support for intuitive camera controls:
81
+
82
+ ### Pinch to Zoom
83
+
84
+ Pinch-to-zoom is **enabled by default**. Simply pinch on the camera view to zoom in and out. The zoom gesture is natively implemented by `react-native-vision-camera` for optimal performance.
85
+
86
+ ### Tap to Focus
87
+
88
+ Tap anywhere on the camera view to focus at that point. This feature requires `react-native-gesture-handler` and `react-native-reanimated`:
89
+
90
+ **Installation:**
91
+
92
+ ```sh
93
+ yarn add react-native-gesture-handler react-native-reanimated
94
+ # or
95
+ npm install react-native-gesture-handler react-native-reanimated
96
+
97
+ # iOS
98
+ cd ios && pod install
99
+ ```
100
+
101
+ **Note:** These dependencies are likely already installed if you're using React Navigation or other common React Native libraries.
102
+
103
+ The tap-to-focus functionality works automatically once these dependencies are installed. Simply tap on the camera view where you want to focus, and the camera will adjust both auto-focus (AF) and auto-exposure (AE) for that point.
104
+
105
+ **How it works:**
106
+ - Tap on a VIN to focus precisely on that area
107
+ - The camera adjusts focus and exposure automatically
108
+ - Works seamlessly with the pinch-to-zoom gesture
109
+ - No additional configuration required
110
+
111
+
76
112
  ## Advanced Features
77
113
 
78
114
  ### AR Overlay with Confidence Scoring
79
115
 
80
- The package includes an optional AR overlay component that renders real-time bounding boxes around detected VINs, color-coded by confidence score.
116
+ The package previously included an AR overlay component for bounding boxes; it has been removed for now while alignment issues are addressed. Default barcode formats are tuned for VIN labels (`code-39`, `code-128`, `pdf-417`) with an automatic fallback to all formats after sustained misses.
81
117
 
82
118
  **Installation:**
83
119
 
@@ -90,7 +126,7 @@ npm install @shopify/react-native-skia
90
126
  **Usage:**
91
127
 
92
128
  ```tsx
93
- import { VinScannerOverlay } from '@mleonard9/vin-scanner';
129
+ // Overlay component removed (was VinScannerOverlay)
94
130
 
95
131
  export function VinScannerWithOverlay() {
96
132
  const [candidates, setCandidates] = useState<VinCandidate[]>([]);
@@ -108,10 +144,7 @@ export function VinScannerWithOverlay() {
108
144
  frameProcessor={frameProcessor}
109
145
  style={StyleSheet.absoluteFill}
110
146
  />
111
- <VinScannerOverlay
112
- candidates={candidates}
113
- colors={{ high: '#00FF00', medium: '#FFFF00', low: '#FF0000' }}
114
- />
147
+ {/* Overlay removed; render your own UI if needed */}
115
148
  </View>
116
149
  );
117
150
  }
@@ -220,16 +253,101 @@ The `candidates` array contains every potential VIN found in the frame. `firstCa
220
253
  | `options.barcode.formats` | `BarcodeFormat[]` | Restrict ML Kit formats (`'code-39'`, `'code-128'`, `'pdf-417'`, etc.) | `['all']` |
221
254
  | `options.text.enabled` | boolean | Enable text recognition | `true` |
222
255
  | `options.text.language` | `'latin' \| 'chinese' \| 'devanagari' \| 'japanese' \| 'korean'` | ML Kit language pack | `'latin'` |
256
+ | `options.text.requireConfirmation` | boolean | When true, text VINs are held until you confirm; barcodes still emit immediately | `false` |
257
+ | `options.text.pendingTtlMs` | number | Auto-dismiss pending text VINs after this many ms (when `requireConfirmation` is true) | `5000` |
223
258
  | `options.detection.textScanInterval` | number | Run text recognition every Nth frame (1 = every frame) | `3` |
224
259
  | `options.detection.maxFrameRate` | number | Max FPS budget for frame processing (drops surplus frames to avoid blocking) | `30` |
225
260
  | `options.detection.forceOrientation` | `'portrait' \| 'portrait-upside-down' \| 'landscape-left' \| 'landscape-right'` | Forces ML Kit to interpret every frame using the given orientation (useful when the UI is locked to portrait but the sensor reports landscape) | `null` |
226
261
  | `options.detection.scanRegion` | `ScanRegion` | Restrict ML Kit processing to a specific region of the frame (normalized coordinates 0.0-1.0). Significantly improves performance by ignoring irrelevant areas. | `{ x: 0.15, y: 0.15, width: 0.7, height: 0.7 }` |
227
- | `options.detection.enableFrameQualityCheck` | boolean | Enable intelligent frame quality checks to skip blurry or dark frames, improving accuracy | `true` |
262
+ | `options.detection.enableFrameQualityCheck` | boolean | Deprecated; use `minLuma`/`minSharpness` instead | `true` |
263
+ | `options.detection.minLuma` | number | Minimum mean luma (0–255) required to process a frame; skips too-dark frames | `30` |
264
+ | `options.detection.minSharpness` | number | Minimum sharpness metric required; skips blurry frames | `12` |
265
+ | `options.detection.minConfidence` | number | Minimum candidate confidence required before emitting | `0.6` |
266
+ | `options.detection.barcodeFallbackAfter` | number | Frames without barcode hits before scanning all formats | `45` |
228
267
  | `options.duplicateDebounceMs` | number | Time in milliseconds to suppress duplicate VIN callbacks for the same value | `1500` |
229
- | `options.showOverlay` | boolean | Enable AR overlay (requires `@shopify/react-native-skia`) | `false` |
230
- | `options.overlayColors` | `OverlayColors` | Custom colors for AR overlay: `{ high, medium, low }` | `{ high: '#00FF00', medium: '#FFFF00', low: '#FF0000' }` |
231
- | `options.cameraSettings` | `CameraSettings` | Camera configuration: `{ fps, lowLightBoost, videoStabilizationMode }` | `{ fps: 30, lowLightBoost: true, videoStabilizationMode: 'off' }` |
268
+ | `options.showOverlay` | boolean | Deprecated; overlay component removed | `false` |
269
+ | `options.overlayColors` | `OverlayColors` | Deprecated; overlay component removed | `{ high: '#00FF00', medium: '#FFFF00', low: '#FF0000' }` |
270
+ | `options.cameraSettings` | `CameraSettings` | Camera configuration: `{ fps (clamped 24–30), lowLightBoost, videoStabilizationMode }` | `{ fps: 24, lowLightBoost: true, videoStabilizationMode: 'cinematic' }` |
232
271
  | `options.onResult` | `(candidates, event) => void` | Convenience callback when using `useVinScanner`; receives all candidates and the raw event | `undefined` |
272
+ | `options.onTextPending` | `(pending) => void` | Invoked when `text.requireConfirmation` is true and text VINs are detected | `undefined` |
273
+ | `options.haptics` | boolean | Enable built-in haptic cues (requires `react-native-haptic-feedback` installed) | `true` |
274
+
275
+ ### Behaviors & defaults
276
+ - Barcode-first: barcodes emit immediately; text VINs can require confirmation.
277
+ - Session dedupe: VINs are not re-emitted within a scan session (in addition to time-based debounce).
278
+ - Quality gate: frames below `minLuma` or `minSharpness` are skipped.
279
+ - Confidence gate: candidates below `minConfidence` are dropped.
280
+ - Barcode formats: defaults to `code-39`, `code-128`, `pdf-417` with automatic fallback to all formats after `barcodeFallbackAfter` empty frames.
281
+ - Camera hints: FPS clamped to 24–30 and `videoStabilizationMode` defaults to `cinematic` to keep headroom and reduce jitter.
282
+
283
+ ### Text confirmation UI (barcode = instant, text = tap-to-confirm)
284
+
285
+ ```tsx
286
+ import { useState } from 'react';
287
+ import { Camera, useCameraDevice } from 'react-native-vision-camera';
288
+ import { useVinScanner, TextVinPrompt, VinCandidate } from '@mleonard9/vin-scanner';
289
+
290
+ export function ConfirmingScanner() {
291
+ const device = useCameraDevice('back');
292
+ const [pending, setPending] = useState<VinCandidate[]>([]);
293
+
294
+ const { frameProcessor, pendingTextCandidates, confirmTextCandidate } = useVinScanner({
295
+ text: { requireConfirmation: true },
296
+ onTextPending: setPending,
297
+ onResult: (candidates) => {
298
+ // barcode VINs (or confirmed text VINs) arrive here
299
+ console.log('confirmed VINs', candidates.map((c) => c.value));
300
+ },
301
+ });
302
+
303
+ return (
304
+ <>
305
+ {device && (
306
+ <Camera style={{ flex: 1 }} device={device} frameProcessor={frameProcessor} isActive />
307
+ )}
308
+ <TextVinPrompt
309
+ visible={pendingTextCandidates.length > 0}
310
+ candidates={pendingTextCandidates}
311
+ buttonLabel=\"Book It\"
312
+ buttonColor=\"#0A84FF\"
313
+ onConfirm={(candidate) => confirmTextCandidate(candidate.value)}
314
+ onDismiss={() => setPending([])}
315
+ />
316
+ </>
317
+ );
318
+ }
319
+ ```
320
+
321
+ ### Manual VIN keypad with checksum guard
322
+
323
+ ```tsx
324
+ import { ManualVinInput } from '@mleonard9/vin-scanner';
325
+
326
+ export function ManualEntry({ onSubmit }: { onSubmit: (vin: string) => void }) {
327
+ return (
328
+ <ManualVinInput
329
+ buttonLabel=\"Book It\"
330
+ buttonColor=\"#0A84FF\"
331
+ onSubmit={onSubmit}
332
+ />
333
+ );
334
+ }
335
+ ```
336
+
337
+ ### Pending banner (alternative to modal)
338
+
339
+ ```tsx
340
+ import { PendingVinBanner } from '@mleonard9/vin-scanner';
341
+
342
+ <PendingVinBanner
343
+ visible={pendingTextCandidates.length > 0}
344
+ candidates={pendingTextCandidates}
345
+ buttonLabel=\"Book It\"
346
+ buttonColor=\"#0A84FF\"
347
+ onConfirm={(candidate) => confirmTextCandidate(candidate.value)}
348
+ onDismiss={() => setPending([])}
349
+ />;
350
+ ```
233
351
 
234
352
  ### Performance
235
353
 
@@ -242,7 +360,7 @@ Phase 1 optimizations dramatically improve scanning performance through native R
242
360
  | ROI + text interval (3 frames) | ~45ms | **75% faster** |
243
361
  | ROI + quality check + throttle | ~30ms | **83% faster** |
244
362
 
245
- **Default configuration** uses ROI scanning (`scanRegion: { x: 0.15, y: 0.15, width: 0.7, height: 0.7 }`), text scan interval of 3, and frame quality checks enabled. This provides excellent accuracy while maintaining real-time performance on mid-range devices.
363
+ **Default configuration** uses ROI scanning (`scanRegion: { x: 0.15, y: 0.15, width: 0.7, height: 0.7 }`) and a text scan interval of 3. This provides excellent accuracy while maintaining real-time performance on mid-range devices.
246
364
 
247
365
  **Tip:** For challenging lighting or distance scenarios, set `textScanInterval: 1` to scan every frame at the cost of higher CPU usage.
248
366
 
@@ -302,4 +420,3 @@ npm publish --access public
302
420
  ```
303
421
 
304
422
  Ensure the authenticated npm user has access to the `@mleonard9` scope.
305
-
@@ -48,24 +48,33 @@ class VisionCameraBarcodeScannerModule(
48
48
 
49
49
  private fun buildScannerOptions(effective: Map<String, Any>): BarcodeScannerOptions {
50
50
  val builder = BarcodeScannerOptions.Builder()
51
- when {
52
- effective["code-128"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_CODE_128)
53
- effective["code-39"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_CODE_39)
54
- effective["code-93"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_CODE_93)
55
- effective["codabar"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_CODABAR)
56
- effective["ean-13"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_EAN_13)
57
- effective["ean-8"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_EAN_8)
58
- effective["itf"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_ITF)
59
- effective["upc-e"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_UPC_E)
60
- effective["upc-a"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_UPC_A)
61
- effective["qr"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_QR_CODE)
62
- effective["pdf-417"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_PDF417)
63
- effective["aztec"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_AZTEC)
64
- effective["data-matrix"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_DATA_MATRIX)
65
- effective["all"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_ALL_FORMATS)
66
- else -> builder.setBarcodeFormats(FORMAT_ALL_FORMATS)
51
+
52
+ var mask = 0
53
+ fun maybeAdd(enabled: Boolean, format: Int) {
54
+ if (enabled) mask = mask or format
55
+ }
56
+
57
+ // Allow multiple formats at once; fall back to ALL when none specified.
58
+ maybeAdd(effective["all"].toString().toBoolean(), FORMAT_ALL_FORMATS)
59
+ maybeAdd(effective["code-128"].toString().toBoolean(), FORMAT_CODE_128)
60
+ maybeAdd(effective["code-39"].toString().toBoolean(), FORMAT_CODE_39)
61
+ maybeAdd(effective["code-93"].toString().toBoolean(), FORMAT_CODE_93)
62
+ maybeAdd(effective["codabar"].toString().toBoolean(), FORMAT_CODABAR)
63
+ maybeAdd(effective["ean-13"].toString().toBoolean(), FORMAT_EAN_13)
64
+ maybeAdd(effective["ean-8"].toString().toBoolean(), FORMAT_EAN_8)
65
+ maybeAdd(effective["itf"].toString().toBoolean(), FORMAT_ITF)
66
+ maybeAdd(effective["upc-e"].toString().toBoolean(), FORMAT_UPC_E)
67
+ maybeAdd(effective["upc-a"].toString().toBoolean(), FORMAT_UPC_A)
68
+ maybeAdd(effective["qr"].toString().toBoolean(), FORMAT_QR_CODE)
69
+ maybeAdd(effective["pdf-417"].toString().toBoolean(), FORMAT_PDF417)
70
+ maybeAdd(effective["aztec"].toString().toBoolean(), FORMAT_AZTEC)
71
+ maybeAdd(effective["data-matrix"].toString().toBoolean(), FORMAT_DATA_MATRIX)
72
+
73
+ if (mask == 0) {
74
+ mask = FORMAT_ALL_FORMATS
67
75
  }
68
- return builder.build()
76
+
77
+ return builder.setBarcodeFormats(mask).build()
69
78
  }
70
79
 
71
80
  private fun orientationToDegrees(orientation: String?): Int? {
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ManualVinInput = ManualVinInput;
7
+ exports.default = void 0;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _reactNative = require("react-native");
10
+ var _vinUtils = require("./vinUtils");
11
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
12
+ const VIN_CHARS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
13
+ const DIGITS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
14
+ function ManualVinInput({
15
+ initialValue = '',
16
+ onSubmit,
17
+ buttonLabel = 'Use VIN',
18
+ buttonColor = '#0A84FF',
19
+ placeholder = 'Enter VIN (17 chars)'
20
+ }) {
21
+ const [value, setValue] = (0, _react.useState)(initialValue.toUpperCase());
22
+ const isValid = (0, _react.useMemo)(() => (0, _vinUtils.isValidVin)(value), [value]);
23
+ const showError = value.length >= 17 && !isValid;
24
+ const handlePress = ch => {
25
+ setValue(prev => (prev + ch).slice(0, 17).toUpperCase());
26
+ };
27
+ const handleBackspace = () => {
28
+ setValue(prev => prev.slice(0, -1));
29
+ };
30
+ const handleSubmit = () => {
31
+ if (isValid && onSubmit) {
32
+ onSubmit(value);
33
+ }
34
+ };
35
+ return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
36
+ style: styles.container
37
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.TextInput, {
38
+ value: value,
39
+ onChangeText: txt => setValue(txt.replace(/[^A-HJ-NPR-Z0-9]/gi, '').slice(0, 17).toUpperCase()),
40
+ style: [styles.input, showError && styles.errorInput],
41
+ placeholder: placeholder,
42
+ autoCapitalize: "characters",
43
+ autoCorrect: false
44
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
45
+ style: styles.row
46
+ }, VIN_CHARS.map(c => /*#__PURE__*/_react.default.createElement(Key, {
47
+ key: c,
48
+ label: c,
49
+ onPress: () => handlePress(c)
50
+ }))), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
51
+ style: styles.row
52
+ }, DIGITS.map(c => /*#__PURE__*/_react.default.createElement(Key, {
53
+ key: c,
54
+ label: c,
55
+ onPress: () => handlePress(c)
56
+ })), /*#__PURE__*/_react.default.createElement(Key, {
57
+ label: "\u232B",
58
+ onPress: handleBackspace,
59
+ wide: true
60
+ })), /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
61
+ style: [styles.submit, {
62
+ backgroundColor: buttonColor,
63
+ opacity: isValid ? 1 : 0.5
64
+ }],
65
+ disabled: !isValid,
66
+ onPress: handleSubmit
67
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
68
+ style: styles.submitText
69
+ }, buttonLabel)), showError && /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
70
+ style: styles.errorText
71
+ }, "Checksum invalid"));
72
+ }
73
+ function Key({
74
+ label,
75
+ onPress,
76
+ wide
77
+ }) {
78
+ return /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
79
+ onPress: onPress,
80
+ style: [styles.key, wide && styles.keyWide]
81
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
82
+ style: styles.keyText
83
+ }, label));
84
+ }
85
+ const styles = _reactNative.StyleSheet.create({
86
+ container: {
87
+ width: '100%',
88
+ padding: 12,
89
+ backgroundColor: '#0D0D0D',
90
+ borderRadius: 12
91
+ },
92
+ input: {
93
+ backgroundColor: '#111827',
94
+ color: 'white',
95
+ paddingHorizontal: 12,
96
+ paddingVertical: 10,
97
+ borderRadius: 8,
98
+ fontSize: 16,
99
+ letterSpacing: 1,
100
+ borderWidth: 1,
101
+ borderColor: '#1F2937',
102
+ marginBottom: 10
103
+ },
104
+ errorInput: {
105
+ borderColor: '#EF4444'
106
+ },
107
+ row: {
108
+ flexDirection: 'row',
109
+ flexWrap: 'wrap',
110
+ gap: 6,
111
+ marginBottom: 8
112
+ },
113
+ key: {
114
+ paddingVertical: 10,
115
+ paddingHorizontal: 12,
116
+ backgroundColor: '#1F2937',
117
+ borderRadius: 8,
118
+ minWidth: 42,
119
+ alignItems: 'center'
120
+ },
121
+ keyWide: {
122
+ minWidth: 64
123
+ },
124
+ keyText: {
125
+ color: 'white',
126
+ fontSize: 14,
127
+ fontWeight: '700'
128
+ },
129
+ submit: {
130
+ marginTop: 8,
131
+ paddingVertical: 12,
132
+ borderRadius: 10,
133
+ alignItems: 'center'
134
+ },
135
+ submitText: {
136
+ color: 'white',
137
+ fontSize: 16,
138
+ fontWeight: '700'
139
+ },
140
+ errorText: {
141
+ color: '#F87171',
142
+ marginTop: 6,
143
+ fontSize: 13
144
+ }
145
+ });
146
+ var _default = exports.default = ManualVinInput;
147
+ //# sourceMappingURL=ManualVinInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_vinUtils","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","VIN_CHARS","DIGITS","ManualVinInput","initialValue","onSubmit","buttonLabel","buttonColor","placeholder","value","setValue","useState","toUpperCase","isValid","useMemo","isValidVin","showError","length","handlePress","ch","prev","slice","handleBackspace","handleSubmit","createElement","View","style","styles","container","TextInput","onChangeText","txt","replace","input","errorInput","autoCapitalize","autoCorrect","row","map","c","Key","key","label","onPress","wide","Pressable","submit","backgroundColor","opacity","disabled","Text","submitText","errorText","keyWide","keyText","StyleSheet","create","width","padding","borderRadius","color","paddingHorizontal","paddingVertical","fontSize","letterSpacing","borderWidth","borderColor","marginBottom","flexDirection","flexWrap","gap","minWidth","alignItems","fontWeight","marginTop","_default","exports"],"sourceRoot":"../../src","sources":["ManualVinInput.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.PendingVinBanner = PendingVinBanner;
7
+ exports.default = void 0;
8
+ var _react = _interopRequireDefault(require("react"));
9
+ var _reactNative = require("react-native");
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ /**
12
+ * Compact bottom banner for pending text VINs.
13
+ * Works well together with `text.requireConfirmation`.
14
+ */
15
+ function PendingVinBanner({
16
+ visible,
17
+ candidates,
18
+ onConfirm,
19
+ onDismiss,
20
+ buttonLabel = 'Use VIN',
21
+ buttonColor = '#0A84FF'
22
+ }) {
23
+ const anim = _react.default.useRef(new _reactNative.Animated.Value(0)).current;
24
+ _react.default.useEffect(() => {
25
+ _reactNative.Animated.timing(anim, {
26
+ toValue: visible ? 1 : 0,
27
+ duration: 220,
28
+ useNativeDriver: true
29
+ }).start();
30
+ }, [visible, anim]);
31
+ const translateY = anim.interpolate({
32
+ inputRange: [0, 1],
33
+ outputRange: [160, 0]
34
+ });
35
+ if (!candidates.length) return null;
36
+ return /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, {
37
+ pointerEvents: visible ? 'auto' : 'none',
38
+ style: [styles.container, {
39
+ transform: [{
40
+ translateY
41
+ }],
42
+ opacity: anim
43
+ }]
44
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
45
+ style: styles.header
46
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
47
+ style: styles.label
48
+ }, candidates.length > 1 ? `${candidates.length} VINs detected` : 'VIN detected'), onDismiss && /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
49
+ onPress: onDismiss,
50
+ hitSlop: 12,
51
+ style: styles.dismiss
52
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
53
+ style: styles.dismissText
54
+ }, "Dismiss"))), candidates.map(vin => /*#__PURE__*/_react.default.createElement(_reactNative.View, {
55
+ key: vin.value,
56
+ style: styles.row
57
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
58
+ style: styles.vin
59
+ }, vin.value), /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
60
+ onPress: () => onConfirm(vin),
61
+ style: [styles.button, {
62
+ backgroundColor: buttonColor
63
+ }]
64
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
65
+ style: styles.buttonText
66
+ }, buttonLabel)))));
67
+ }
68
+ const styles = _reactNative.StyleSheet.create({
69
+ container: {
70
+ position: 'absolute',
71
+ left: 16,
72
+ right: 16,
73
+ bottom: 40,
74
+ backgroundColor: 'rgba(0,0,0,0.9)',
75
+ borderRadius: 16,
76
+ padding: 14,
77
+ gap: 10
78
+ },
79
+ header: {
80
+ flexDirection: 'row',
81
+ alignItems: 'center',
82
+ justifyContent: 'space-between'
83
+ },
84
+ label: {
85
+ color: '#E5E7EB',
86
+ fontSize: 14,
87
+ fontWeight: '700'
88
+ },
89
+ dismiss: {
90
+ padding: 4
91
+ },
92
+ dismissText: {
93
+ color: '#9CA3AF',
94
+ fontSize: 12
95
+ },
96
+ row: {
97
+ flexDirection: 'row',
98
+ alignItems: 'center',
99
+ justifyContent: 'space-between',
100
+ gap: 10
101
+ },
102
+ vin: {
103
+ flex: 1,
104
+ color: 'white',
105
+ fontSize: 16,
106
+ letterSpacing: 1
107
+ },
108
+ button: {
109
+ paddingHorizontal: 14,
110
+ paddingVertical: 10,
111
+ borderRadius: 10
112
+ },
113
+ buttonText: {
114
+ color: 'white',
115
+ fontSize: 14,
116
+ fontWeight: '700'
117
+ }
118
+ });
119
+ var _default = exports.default = PendingVinBanner;
120
+ //# sourceMappingURL=PendingVinBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_react","_interopRequireDefault","require","_reactNative","e","__esModule","default","PendingVinBanner","visible","candidates","onConfirm","onDismiss","buttonLabel","buttonColor","anim","React","useRef","Animated","Value","current","useEffect","timing","toValue","duration","useNativeDriver","start","translateY","interpolate","inputRange","outputRange","length","createElement","View","pointerEvents","style","styles","container","transform","opacity","header","Text","label","Pressable","onPress","hitSlop","dismiss","dismissText","map","vin","key","value","row","button","backgroundColor","buttonText","StyleSheet","create","position","left","right","bottom","borderRadius","padding","gap","flexDirection","alignItems","justifyContent","color","fontSize","fontWeight","flex","letterSpacing","paddingHorizontal","paddingVertical","_default","exports"],"sourceRoot":"../../src","sources":["PendingVinBanner.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.TextVinPrompt = TextVinPrompt;
7
+ exports.default = void 0;
8
+ var _react = _interopRequireDefault(require("react"));
9
+ var _reactNative = require("react-native");
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ /**
12
+ * Lightweight confirmation UI for text VINs.
13
+ * - Shows detected VINs in a simple modal.
14
+ * - Barcodes should be handled upstream (they bypass this UI).
15
+ */
16
+ function TextVinPrompt({
17
+ visible,
18
+ candidates,
19
+ onConfirm,
20
+ onDismiss,
21
+ title = 'VIN detected',
22
+ subtitle,
23
+ buttonLabel = 'Use VIN',
24
+ buttonColor = '#0A84FF'
25
+ }) {
26
+ if (!visible) return null;
27
+ return /*#__PURE__*/_react.default.createElement(_reactNative.Modal, {
28
+ transparent: true,
29
+ animationType: "fade",
30
+ visible: visible,
31
+ onRequestClose: onDismiss
32
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
33
+ style: styles.backdrop
34
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
35
+ style: styles.sheet
36
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
37
+ style: styles.title
38
+ }, title), subtitle ? /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
39
+ style: styles.subtitle
40
+ }, subtitle) : null, /*#__PURE__*/_react.default.createElement(_reactNative.ScrollView, {
41
+ style: styles.list,
42
+ contentContainerStyle: styles.listContent
43
+ }, candidates.map(candidate => /*#__PURE__*/_react.default.createElement(_reactNative.View, {
44
+ key: candidate.value,
45
+ style: styles.row
46
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
47
+ style: styles.vin,
48
+ numberOfLines: 1
49
+ }, candidate.value), /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
50
+ onPress: () => onConfirm(candidate),
51
+ style: [styles.button, {
52
+ backgroundColor: buttonColor
53
+ }],
54
+ android_ripple: {
55
+ color: '#ffffff22'
56
+ }
57
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
58
+ style: styles.buttonText
59
+ }, buttonLabel))))), onDismiss && /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
60
+ onPress: onDismiss,
61
+ style: styles.dismiss
62
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
63
+ style: styles.dismissText
64
+ }, "Dismiss")))));
65
+ }
66
+ const styles = _reactNative.StyleSheet.create({
67
+ backdrop: {
68
+ flex: 1,
69
+ backgroundColor: 'rgba(0,0,0,0.55)',
70
+ alignItems: 'center',
71
+ justifyContent: 'center',
72
+ padding: 16
73
+ },
74
+ sheet: {
75
+ width: '100%',
76
+ maxWidth: 420,
77
+ backgroundColor: '#0D0D0D',
78
+ borderRadius: 16,
79
+ padding: 16
80
+ },
81
+ title: {
82
+ color: 'white',
83
+ fontSize: 18,
84
+ fontWeight: '700',
85
+ marginBottom: 12
86
+ },
87
+ subtitle: {
88
+ color: '#D1D5DB',
89
+ fontSize: 14,
90
+ marginBottom: 8
91
+ },
92
+ list: {
93
+ maxHeight: 220
94
+ },
95
+ listContent: {
96
+ gap: 12
97
+ },
98
+ row: {
99
+ flexDirection: 'row',
100
+ alignItems: 'center',
101
+ justifyContent: 'space-between',
102
+ gap: 12
103
+ },
104
+ vin: {
105
+ flex: 1,
106
+ color: 'white',
107
+ fontSize: 16,
108
+ letterSpacing: 1
109
+ },
110
+ button: {
111
+ paddingHorizontal: 14,
112
+ paddingVertical: 10,
113
+ borderRadius: 10
114
+ },
115
+ buttonText: {
116
+ color: 'white',
117
+ fontSize: 14,
118
+ fontWeight: '700'
119
+ },
120
+ dismiss: {
121
+ marginTop: 12,
122
+ alignSelf: 'flex-end',
123
+ paddingHorizontal: 8,
124
+ paddingVertical: 6
125
+ },
126
+ dismissText: {
127
+ color: '#9CA3AF',
128
+ fontSize: 14
129
+ }
130
+ });
131
+ var _default = exports.default = TextVinPrompt;
132
+ //# sourceMappingURL=TextVinPrompt.js.map