react-native-rectangle-doc-scanner 3.59.0 → 3.61.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.
@@ -167,7 +167,8 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
167
167
  captureOriginRef.current = 'manual';
168
168
  capture().catch((error) => {
169
169
  captureOriginRef.current = 'auto';
170
- console.warn('[DocScanner] manual capture failed', error);
170
+ console.error('[DocScanner] manual capture failed', error);
171
+ throw error;
171
172
  });
172
173
  }, [capture]);
173
174
  const handleRectangleDetect = (0, react_1.useCallback)((event) => {
@@ -83,8 +83,15 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
83
83
  try {
84
84
  console.log('[FullDocScanner] openCropper called with path:', imagePath);
85
85
  setProcessing(true);
86
+ // Clean path - remove file:// prefix for react-native-image-crop-picker
87
+ // The library handles the prefix internally and double prefixing causes issues
88
+ let cleanPath = imagePath;
89
+ if (cleanPath.startsWith('file://')) {
90
+ cleanPath = cleanPath.replace('file://', '');
91
+ }
92
+ console.log('[FullDocScanner] Clean path for cropper:', cleanPath);
86
93
  const croppedImage = await react_native_image_crop_picker_1.default.openCropper({
87
- path: imagePath,
94
+ path: cleanPath,
88
95
  mediaType: 'photo',
89
96
  width: cropWidth,
90
97
  height: cropHeight,
@@ -99,7 +106,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
99
106
  hasBase64: !!croppedImage.data,
100
107
  });
101
108
  setProcessing(false);
102
- // Show check_DP confirmation screen
109
+ // Show confirmation screen
103
110
  setCroppedImageData({
104
111
  path: croppedImage.path,
105
112
  base64: croppedImage.data ?? undefined,
@@ -108,11 +115,14 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
108
115
  catch (error) {
109
116
  console.error('[FullDocScanner] openCropper error:', error);
110
117
  setProcessing(false);
111
- if (error?.message !== 'User cancelled image selection') {
112
- emitError(error instanceof Error ? error : new Error(String(error)), 'Failed to crop image.');
118
+ const errorMessage = error?.message || String(error);
119
+ if (errorMessage === 'User cancelled image selection' ||
120
+ errorMessage.includes('cancelled') ||
121
+ errorMessage.includes('cancel')) {
122
+ console.log('[FullDocScanner] User cancelled cropper');
113
123
  }
114
124
  else {
115
- console.log('[FullDocScanner] User cancelled cropper');
125
+ emitError(error instanceof Error ? error : new Error(errorMessage), 'Failed to crop image. Please try again.');
116
126
  }
117
127
  }
118
128
  }, [cropWidth, cropHeight, emitError]);
@@ -123,19 +133,16 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
123
133
  croppedPath: document.croppedPath,
124
134
  initialPath: document.initialPath,
125
135
  captureMode: captureModeRef.current,
136
+ captureInProgress: captureInProgressRef.current,
126
137
  });
127
- captureInProgressRef.current = false;
128
- if (document.origin === 'auto') {
129
- console.log('[FullDocScanner] Ignoring auto capture result');
130
- return;
131
- }
132
138
  const captureMode = captureModeRef.current;
139
+ // Reset capture state
140
+ captureInProgressRef.current = false;
141
+ captureModeRef.current = null;
133
142
  if (!captureMode) {
134
- console.warn('[FullDocScanner] Missing capture mode for manual capture result');
135
- captureModeRef.current = null;
143
+ console.warn('[FullDocScanner] Missing capture mode for capture result, ignoring');
136
144
  return;
137
145
  }
138
- captureModeRef.current = null;
139
146
  const normalizedDoc = normalizeCapturedDocument(document);
140
147
  if (captureMode === 'no-grid') {
141
148
  console.log('[FullDocScanner] No grid at capture button press: opening cropper for manual selection');
@@ -178,35 +185,58 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
178
185
  const captureMode = rectangleDetected ? 'grid' : 'no-grid';
179
186
  captureModeRef.current = captureMode;
180
187
  captureInProgressRef.current = true;
188
+ // Add timeout to reset state if capture hangs
189
+ const captureTimeout = setTimeout(() => {
190
+ if (captureInProgressRef.current) {
191
+ console.error('[FullDocScanner] Capture timeout - resetting state');
192
+ captureModeRef.current = null;
193
+ captureInProgressRef.current = false;
194
+ emitError(new Error('Capture timeout'), 'Capture timed out. Please try again.');
195
+ }
196
+ }, 10000);
181
197
  scannerInstance.capture()
182
198
  .then((result) => {
183
- console.log('[FullDocScanner] Manual capture success:', result);
184
- captureInProgressRef.current = false;
199
+ clearTimeout(captureTimeout);
200
+ console.log('[FullDocScanner] Manual capture promise resolved:', {
201
+ hasResult: !!result,
202
+ croppedImage: result?.croppedImage,
203
+ initialImage: result?.initialImage,
204
+ });
205
+ // Note: captureInProgressRef is reset in handleCapture
185
206
  })
186
207
  .catch((error) => {
208
+ clearTimeout(captureTimeout);
209
+ const errorMessage = error instanceof Error ? error.message : String(error);
210
+ console.error('[FullDocScanner] Manual capture failed:', errorMessage, error);
187
211
  captureModeRef.current = null;
188
212
  captureInProgressRef.current = false;
189
- console.error('[FullDocScanner] Manual capture failed:', error);
190
213
  if (error instanceof Error && error.message !== 'capture_in_progress') {
191
- emitError(error, 'Failed to capture image.');
214
+ emitError(error, 'Failed to capture image. Please try again.');
192
215
  }
193
216
  });
194
217
  }, [processing, rectangleDetected, emitError]);
195
218
  const handleGalleryPick = (0, react_1.useCallback)(async () => {
196
219
  console.log('[FullDocScanner] handleGalleryPick called');
197
220
  if (processing || isGalleryOpen) {
221
+ console.log('[FullDocScanner] Skipping gallery pick - already processing:', { processing, isGalleryOpen });
198
222
  return;
199
223
  }
200
224
  try {
201
225
  setIsGalleryOpen(true);
226
+ console.log('[FullDocScanner] Launching image library...');
202
227
  const result = await (0, react_native_image_picker_1.launchImageLibrary)({
203
228
  mediaType: 'photo',
204
229
  quality: 1,
205
230
  selectionLimit: 1,
206
231
  });
232
+ console.log('[FullDocScanner] Image library result:', {
233
+ didCancel: result.didCancel,
234
+ hasAssets: !!result.assets,
235
+ assetsLength: result.assets?.length,
236
+ });
207
237
  setIsGalleryOpen(false);
208
238
  if (result.didCancel || !result.assets?.[0]?.uri) {
209
- console.log('[FullDocScanner] User cancelled gallery picker');
239
+ console.log('[FullDocScanner] User cancelled gallery picker or no image selected');
210
240
  return;
211
241
  }
212
242
  const imageUri = result.assets[0].uri;
@@ -215,6 +245,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
215
245
  await openCropper(imageUri);
216
246
  }
217
247
  catch (error) {
248
+ console.error('[FullDocScanner] Gallery pick error:', error);
218
249
  setIsGalleryOpen(false);
219
250
  emitError(error instanceof Error ? error : new Error(String(error)), 'Failed to pick image from gallery.');
220
251
  }
@@ -250,29 +281,33 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
250
281
  const stableCounter = event.stableCounter ?? 0;
251
282
  const hasRectangle = Boolean(event.rectangleOnScreen ?? event.rectangleCoordinates);
252
283
  const isGoodRectangle = hasRectangle && event.lastDetectionType === 0;
253
- if (hasRectangle) {
284
+ // Clear timeout immediately when rectangle is lost
285
+ if (!hasRectangle || !isGoodRectangle) {
286
+ if (rectangleTimeoutRef.current) {
287
+ clearTimeout(rectangleTimeoutRef.current);
288
+ rectangleTimeoutRef.current = null;
289
+ }
290
+ setRectangleDetected(false);
291
+ }
292
+ else {
293
+ // Rectangle detected - clear any existing timeout
254
294
  if (rectangleTimeoutRef.current) {
255
295
  clearTimeout(rectangleTimeoutRef.current);
256
296
  }
297
+ setRectangleDetected(true);
298
+ // Set timeout to clear rectangle after brief period of no updates
257
299
  rectangleTimeoutRef.current = setTimeout(() => {
258
300
  rectangleTimeoutRef.current = null;
259
301
  setRectangleDetected(false);
302
+ console.log('[FullDocScanner] Rectangle timeout - clearing detection');
260
303
  }, 300);
261
304
  }
262
- else if (rectangleTimeoutRef.current) {
263
- clearTimeout(rectangleTimeoutRef.current);
264
- rectangleTimeoutRef.current = null;
265
- }
266
- setRectangleDetected((prev) => {
267
- if (prev !== isGoodRectangle) {
268
- console.log('[FullDocScanner] Rectangle detection update', {
269
- lastDetectionType: event.lastDetectionType,
270
- stableCounter,
271
- hasRectangle,
272
- isGoodRectangle,
273
- });
274
- }
275
- return isGoodRectangle;
305
+ console.log('[FullDocScanner] Rectangle detection update', {
306
+ lastDetectionType: event.lastDetectionType,
307
+ stableCounter,
308
+ hasRectangle,
309
+ isGoodRectangle,
310
+ rectangleDetected: isGoodRectangle,
276
311
  });
277
312
  }, []);
278
313
  (0, react_1.useEffect)(() => () => {
@@ -290,7 +325,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
290
325
  react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.retake)),
291
326
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleConfirm, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button" },
292
327
  react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.confirm))))) : (react_1.default.createElement(react_native_1.View, { style: styles.flex },
293
- react_1.default.createElement(DocScanner_1.DocScanner, { ref: docScannerRef, autoCapture: true, overlayColor: overlayColor, showGrid: showGrid, gridColor: resolvedGridColor, gridLineWidth: gridLineWidth, minStableFrames: minStableFrames ?? 6, detectionConfig: detectionConfig, onCapture: handleCapture, onRectangleDetect: handleRectangleDetect, showManualCaptureButton: false },
328
+ react_1.default.createElement(DocScanner_1.DocScanner, { ref: docScannerRef, autoCapture: false, overlayColor: overlayColor, showGrid: showGrid, gridColor: resolvedGridColor, gridLineWidth: gridLineWidth, minStableFrames: minStableFrames ?? 6, detectionConfig: detectionConfig, onCapture: handleCapture, onRectangleDetect: handleRectangleDetect, showManualCaptureButton: false },
294
329
  react_1.default.createElement(react_native_1.View, { style: styles.overlayTop, pointerEvents: "box-none" },
295
330
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.closeButton, onPress: handleClose, accessibilityLabel: mergedStrings.cancel, accessibilityRole: "button" },
296
331
  react_1.default.createElement(react_native_1.Text, { style: styles.closeButtonLabel }, "\u00D7"))),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.59.0",
3
+ "version": "3.61.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -241,7 +241,8 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
241
241
  captureOriginRef.current = 'manual';
242
242
  capture().catch((error) => {
243
243
  captureOriginRef.current = 'auto';
244
- console.warn('[DocScanner] manual capture failed', error);
244
+ console.error('[DocScanner] manual capture failed', error);
245
+ throw error;
245
246
  });
246
247
  }, [capture]);
247
248
 
@@ -121,8 +121,17 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
121
121
  try {
122
122
  console.log('[FullDocScanner] openCropper called with path:', imagePath);
123
123
  setProcessing(true);
124
+
125
+ // Clean path - remove file:// prefix for react-native-image-crop-picker
126
+ // The library handles the prefix internally and double prefixing causes issues
127
+ let cleanPath = imagePath;
128
+ if (cleanPath.startsWith('file://')) {
129
+ cleanPath = cleanPath.replace('file://', '');
130
+ }
131
+ console.log('[FullDocScanner] Clean path for cropper:', cleanPath);
132
+
124
133
  const croppedImage = await ImageCropPicker.openCropper({
125
- path: imagePath,
134
+ path: cleanPath,
126
135
  mediaType: 'photo',
127
136
  width: cropWidth,
128
137
  height: cropHeight,
@@ -140,7 +149,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
140
149
 
141
150
  setProcessing(false);
142
151
 
143
- // Show check_DP confirmation screen
152
+ // Show confirmation screen
144
153
  setCroppedImageData({
145
154
  path: croppedImage.path,
146
155
  base64: croppedImage.data ?? undefined,
@@ -148,13 +157,18 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
148
157
  } catch (error) {
149
158
  console.error('[FullDocScanner] openCropper error:', error);
150
159
  setProcessing(false);
151
- if ((error as any)?.message !== 'User cancelled image selection') {
160
+
161
+ const errorMessage = (error as any)?.message || String(error);
162
+
163
+ if (errorMessage === 'User cancelled image selection' ||
164
+ errorMessage.includes('cancelled') ||
165
+ errorMessage.includes('cancel')) {
166
+ console.log('[FullDocScanner] User cancelled cropper');
167
+ } else {
152
168
  emitError(
153
- error instanceof Error ? error : new Error(String(error)),
154
- 'Failed to crop image.',
169
+ error instanceof Error ? error : new Error(errorMessage),
170
+ 'Failed to crop image. Please try again.',
155
171
  );
156
- } else {
157
- console.log('[FullDocScanner] User cancelled cropper');
158
172
  }
159
173
  }
160
174
  },
@@ -169,25 +183,20 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
169
183
  croppedPath: document.croppedPath,
170
184
  initialPath: document.initialPath,
171
185
  captureMode: captureModeRef.current,
186
+ captureInProgress: captureInProgressRef.current,
172
187
  });
173
188
 
174
- captureInProgressRef.current = false;
175
-
176
- if (document.origin === 'auto') {
177
- console.log('[FullDocScanner] Ignoring auto capture result');
178
- return;
179
- }
180
-
181
189
  const captureMode = captureModeRef.current;
182
190
 
191
+ // Reset capture state
192
+ captureInProgressRef.current = false;
193
+ captureModeRef.current = null;
194
+
183
195
  if (!captureMode) {
184
- console.warn('[FullDocScanner] Missing capture mode for manual capture result');
185
- captureModeRef.current = null;
196
+ console.warn('[FullDocScanner] Missing capture mode for capture result, ignoring');
186
197
  return;
187
198
  }
188
199
 
189
- captureModeRef.current = null;
190
-
191
200
  const normalizedDoc = normalizeCapturedDocument(document);
192
201
 
193
202
  if (captureMode === 'no-grid') {
@@ -242,19 +251,40 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
242
251
  captureModeRef.current = captureMode;
243
252
  captureInProgressRef.current = true;
244
253
 
254
+ // Add timeout to reset state if capture hangs
255
+ const captureTimeout = setTimeout(() => {
256
+ if (captureInProgressRef.current) {
257
+ console.error('[FullDocScanner] Capture timeout - resetting state');
258
+ captureModeRef.current = null;
259
+ captureInProgressRef.current = false;
260
+ emitError(
261
+ new Error('Capture timeout'),
262
+ 'Capture timed out. Please try again.',
263
+ );
264
+ }
265
+ }, 10000);
266
+
245
267
  scannerInstance.capture()
246
268
  .then((result) => {
247
- console.log('[FullDocScanner] Manual capture success:', result);
248
- captureInProgressRef.current = false;
269
+ clearTimeout(captureTimeout);
270
+ console.log('[FullDocScanner] Manual capture promise resolved:', {
271
+ hasResult: !!result,
272
+ croppedImage: result?.croppedImage,
273
+ initialImage: result?.initialImage,
274
+ });
275
+ // Note: captureInProgressRef is reset in handleCapture
249
276
  })
250
277
  .catch((error: unknown) => {
278
+ clearTimeout(captureTimeout);
279
+ const errorMessage = error instanceof Error ? error.message : String(error);
280
+ console.error('[FullDocScanner] Manual capture failed:', errorMessage, error);
251
281
  captureModeRef.current = null;
252
282
  captureInProgressRef.current = false;
253
- console.error('[FullDocScanner] Manual capture failed:', error);
283
+
254
284
  if (error instanceof Error && error.message !== 'capture_in_progress') {
255
285
  emitError(
256
286
  error,
257
- 'Failed to capture image.',
287
+ 'Failed to capture image. Please try again.',
258
288
  );
259
289
  }
260
290
  });
@@ -263,21 +293,30 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
263
293
  const handleGalleryPick = useCallback(async () => {
264
294
  console.log('[FullDocScanner] handleGalleryPick called');
265
295
  if (processing || isGalleryOpen) {
296
+ console.log('[FullDocScanner] Skipping gallery pick - already processing:', { processing, isGalleryOpen });
266
297
  return;
267
298
  }
268
299
 
269
300
  try {
270
301
  setIsGalleryOpen(true);
302
+ console.log('[FullDocScanner] Launching image library...');
303
+
271
304
  const result = await launchImageLibrary({
272
305
  mediaType: 'photo',
273
306
  quality: 1,
274
307
  selectionLimit: 1,
275
308
  });
276
309
 
310
+ console.log('[FullDocScanner] Image library result:', {
311
+ didCancel: result.didCancel,
312
+ hasAssets: !!result.assets,
313
+ assetsLength: result.assets?.length,
314
+ });
315
+
277
316
  setIsGalleryOpen(false);
278
317
 
279
318
  if (result.didCancel || !result.assets?.[0]?.uri) {
280
- console.log('[FullDocScanner] User cancelled gallery picker');
319
+ console.log('[FullDocScanner] User cancelled gallery picker or no image selected');
281
320
  return;
282
321
  }
283
322
 
@@ -287,6 +326,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
287
326
  // Open cropper with the selected image
288
327
  await openCropper(imageUri);
289
328
  } catch (error) {
329
+ console.error('[FullDocScanner] Gallery pick error:', error);
290
330
  setIsGalleryOpen(false);
291
331
  emitError(
292
332
  error instanceof Error ? error : new Error(String(error)),
@@ -330,29 +370,34 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
330
370
  const hasRectangle = Boolean(event.rectangleOnScreen ?? event.rectangleCoordinates);
331
371
  const isGoodRectangle = hasRectangle && event.lastDetectionType === 0;
332
372
 
333
- if (hasRectangle) {
373
+ // Clear timeout immediately when rectangle is lost
374
+ if (!hasRectangle || !isGoodRectangle) {
334
375
  if (rectangleTimeoutRef.current) {
335
376
  clearTimeout(rectangleTimeoutRef.current);
377
+ rectangleTimeoutRef.current = null;
336
378
  }
379
+ setRectangleDetected(false);
380
+ } else {
381
+ // Rectangle detected - clear any existing timeout
382
+ if (rectangleTimeoutRef.current) {
383
+ clearTimeout(rectangleTimeoutRef.current);
384
+ }
385
+ setRectangleDetected(true);
386
+
387
+ // Set timeout to clear rectangle after brief period of no updates
337
388
  rectangleTimeoutRef.current = setTimeout(() => {
338
389
  rectangleTimeoutRef.current = null;
339
390
  setRectangleDetected(false);
391
+ console.log('[FullDocScanner] Rectangle timeout - clearing detection');
340
392
  }, 300);
341
- } else if (rectangleTimeoutRef.current) {
342
- clearTimeout(rectangleTimeoutRef.current);
343
- rectangleTimeoutRef.current = null;
344
393
  }
345
394
 
346
- setRectangleDetected((prev) => {
347
- if (prev !== isGoodRectangle) {
348
- console.log('[FullDocScanner] Rectangle detection update', {
349
- lastDetectionType: event.lastDetectionType,
350
- stableCounter,
351
- hasRectangle,
352
- isGoodRectangle,
353
- });
354
- }
355
- return isGoodRectangle;
395
+ console.log('[FullDocScanner] Rectangle detection update', {
396
+ lastDetectionType: event.lastDetectionType,
397
+ stableCounter,
398
+ hasRectangle,
399
+ isGoodRectangle,
400
+ rectangleDetected: isGoodRectangle,
356
401
  });
357
402
  }, []);
358
403
 
@@ -395,7 +440,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
395
440
  <View style={styles.flex}>
396
441
  <DocScanner
397
442
  ref={docScannerRef}
398
- autoCapture={true}
443
+ autoCapture={false}
399
444
  overlayColor={overlayColor}
400
445
  showGrid={showGrid}
401
446
  gridColor={resolvedGridColor}