react-native-rectangle-doc-scanner 3.69.0 → 3.71.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.
@@ -58,11 +58,13 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
58
58
  const [croppedImageData, setCroppedImageData] = (0, react_1.useState)(null);
59
59
  const [isGalleryOpen, setIsGalleryOpen] = (0, react_1.useState)(false);
60
60
  const [rectangleDetected, setRectangleDetected] = (0, react_1.useState)(false);
61
+ const [rectangleHint, setRectangleHint] = (0, react_1.useState)(false);
61
62
  const resolvedGridColor = gridColor ?? overlayColor;
62
63
  const docScannerRef = (0, react_1.useRef)(null);
63
64
  const captureModeRef = (0, react_1.useRef)(null);
64
65
  const captureInProgressRef = (0, react_1.useRef)(false);
65
- const rectangleTimeoutRef = (0, react_1.useRef)(null);
66
+ const rectangleCaptureTimeoutRef = (0, react_1.useRef)(null);
67
+ const rectangleHintTimeoutRef = (0, react_1.useRef)(null);
66
68
  const mergedStrings = (0, react_1.useMemo)(() => ({
67
69
  captureHint: strings?.captureHint,
68
70
  manualHint: strings?.manualHint,
@@ -166,6 +168,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
166
168
  processing,
167
169
  hasRef: hasScanner,
168
170
  rectangleDetected,
171
+ rectangleHint,
169
172
  currentCaptureMode: captureModeRef.current,
170
173
  captureInProgress: captureInProgressRef.current,
171
174
  });
@@ -214,7 +217,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
214
217
  emitError(error, 'Failed to capture image. Please try again.');
215
218
  }
216
219
  });
217
- }, [processing, rectangleDetected, emitError]);
220
+ }, [processing, rectangleDetected, rectangleHint, emitError]);
218
221
  const handleGalleryPick = (0, react_1.useCallback)(async () => {
219
222
  console.log('[FullDocScanner] handleGalleryPick called');
220
223
  if (processing || isGalleryOpen) {
@@ -266,11 +269,16 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
266
269
  setCroppedImageData(null);
267
270
  setProcessing(false);
268
271
  setRectangleDetected(false);
272
+ setRectangleHint(false);
269
273
  captureModeRef.current = null;
270
274
  captureInProgressRef.current = false;
271
- if (rectangleTimeoutRef.current) {
272
- clearTimeout(rectangleTimeoutRef.current);
273
- rectangleTimeoutRef.current = null;
275
+ if (rectangleCaptureTimeoutRef.current) {
276
+ clearTimeout(rectangleCaptureTimeoutRef.current);
277
+ rectangleCaptureTimeoutRef.current = null;
278
+ }
279
+ if (rectangleHintTimeoutRef.current) {
280
+ clearTimeout(rectangleHintTimeoutRef.current);
281
+ rectangleHintTimeoutRef.current = null;
274
282
  }
275
283
  // Reset DocScanner state
276
284
  if (docScannerRef.current?.reset) {
@@ -281,47 +289,60 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
281
289
  const stableCounter = event.stableCounter ?? 0;
282
290
  const rectangleCoordinates = event.rectangleOnScreen ?? event.rectangleCoordinates;
283
291
  const hasRectangle = Boolean(rectangleCoordinates);
284
- const isConfidentRectangle = hasRectangle && (event.lastDetectionType === 0 || stableCounter > 0);
285
- const scheduleDetectionClear = () => {
286
- if (rectangleTimeoutRef.current) {
287
- clearTimeout(rectangleTimeoutRef.current);
292
+ const captureReady = hasRectangle && event.lastDetectionType === 0 && stableCounter >= 1;
293
+ const scheduleClear = (ref, clearFn) => {
294
+ if (ref.current) {
295
+ clearTimeout(ref.current);
288
296
  }
289
- rectangleTimeoutRef.current = setTimeout(() => {
290
- rectangleTimeoutRef.current = null;
291
- setRectangleDetected((prev) => {
292
- if (!prev) {
293
- return prev;
294
- }
295
- console.log('[FullDocScanner] Rectangle timeout - clearing detection');
296
- return false;
297
- });
297
+ ref.current = setTimeout(() => {
298
+ ref.current = null;
299
+ clearFn();
298
300
  }, 350);
299
301
  };
300
- if (isConfidentRectangle) {
301
- scheduleDetectionClear();
302
- setRectangleDetected((prev) => (prev ? prev : true));
302
+ if (hasRectangle) {
303
+ scheduleClear(rectangleHintTimeoutRef, () => setRectangleHint(false));
304
+ setRectangleHint(true);
305
+ }
306
+ else {
307
+ if (rectangleHintTimeoutRef.current) {
308
+ clearTimeout(rectangleHintTimeoutRef.current);
309
+ rectangleHintTimeoutRef.current = null;
310
+ }
311
+ setRectangleHint(false);
312
+ }
313
+ if (captureReady) {
314
+ scheduleClear(rectangleCaptureTimeoutRef, () => {
315
+ console.log('[FullDocScanner] Rectangle timeout - clearing detection');
316
+ setRectangleDetected(false);
317
+ });
318
+ setRectangleDetected(true);
303
319
  }
304
320
  else if (!hasRectangle) {
305
- if (rectangleTimeoutRef.current) {
306
- clearTimeout(rectangleTimeoutRef.current);
307
- rectangleTimeoutRef.current = null;
321
+ if (rectangleCaptureTimeoutRef.current) {
322
+ clearTimeout(rectangleCaptureTimeoutRef.current);
323
+ rectangleCaptureTimeoutRef.current = null;
308
324
  }
309
325
  setRectangleDetected(false);
310
326
  }
311
- else {
312
- // Rectangle is present but confidence is low – keep current state but schedule a clear
313
- scheduleDetectionClear();
327
+ else if (rectangleDetected) {
328
+ scheduleClear(rectangleCaptureTimeoutRef, () => {
329
+ console.log('[FullDocScanner] Rectangle timeout - clearing detection');
330
+ setRectangleDetected(false);
331
+ });
314
332
  }
315
333
  console.log('[FullDocScanner] Rectangle detection update', {
316
334
  lastDetectionType: event.lastDetectionType,
317
335
  stableCounter,
318
336
  hasRectangle,
319
- isConfidentRectangle,
337
+ captureReady,
320
338
  });
321
- }, []);
339
+ }, [rectangleDetected]);
322
340
  (0, react_1.useEffect)(() => () => {
323
- if (rectangleTimeoutRef.current) {
324
- clearTimeout(rectangleTimeoutRef.current);
341
+ if (rectangleCaptureTimeoutRef.current) {
342
+ clearTimeout(rectangleCaptureTimeoutRef.current);
343
+ }
344
+ if (rectangleHintTimeoutRef.current) {
345
+ clearTimeout(rectangleHintTimeoutRef.current);
325
346
  }
326
347
  }, []);
327
348
  return (react_1.default.createElement(react_native_1.View, { style: styles.container },
@@ -348,7 +369,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
348
369
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.shutterButton, processing && styles.buttonDisabled], onPress: triggerManualCapture, disabled: processing, accessibilityLabel: mergedStrings.manualHint, accessibilityRole: "button" },
349
370
  react_1.default.createElement(react_native_1.View, { style: [
350
371
  styles.shutterInner,
351
- rectangleDetected && { backgroundColor: overlayColor }
372
+ rectangleHint && { backgroundColor: overlayColor }
352
373
  ] })))))),
353
374
  processing && (react_1.default.createElement(react_native_1.View, { style: styles.processingOverlay },
354
375
  react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: overlayColor }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.69.0",
3
+ "version": "3.71.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -86,11 +86,13 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
86
86
  const [croppedImageData, setCroppedImageData] = useState<{path: string; base64?: string} | null>(null);
87
87
  const [isGalleryOpen, setIsGalleryOpen] = useState(false);
88
88
  const [rectangleDetected, setRectangleDetected] = useState(false);
89
+ const [rectangleHint, setRectangleHint] = useState(false);
89
90
  const resolvedGridColor = gridColor ?? overlayColor;
90
91
  const docScannerRef = useRef<DocScannerHandle | null>(null);
91
92
  const captureModeRef = useRef<'grid' | 'no-grid' | null>(null);
92
93
  const captureInProgressRef = useRef(false);
93
- const rectangleTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
94
+ const rectangleCaptureTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
95
+ const rectangleHintTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
94
96
 
95
97
  const mergedStrings = useMemo(
96
98
  () => ({
@@ -226,6 +228,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
226
228
  processing,
227
229
  hasRef: hasScanner,
228
230
  rectangleDetected,
231
+ rectangleHint,
229
232
  currentCaptureMode: captureModeRef.current,
230
233
  captureInProgress: captureInProgressRef.current,
231
234
  });
@@ -288,7 +291,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
288
291
  );
289
292
  }
290
293
  });
291
- }, [processing, rectangleDetected, emitError]);
294
+ }, [processing, rectangleDetected, rectangleHint, emitError]);
292
295
 
293
296
  const handleGalleryPick = useCallback(async () => {
294
297
  console.log('[FullDocScanner] handleGalleryPick called');
@@ -353,11 +356,16 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
353
356
  setCroppedImageData(null);
354
357
  setProcessing(false);
355
358
  setRectangleDetected(false);
359
+ setRectangleHint(false);
356
360
  captureModeRef.current = null;
357
361
  captureInProgressRef.current = false;
358
- if (rectangleTimeoutRef.current) {
359
- clearTimeout(rectangleTimeoutRef.current);
360
- rectangleTimeoutRef.current = null;
362
+ if (rectangleCaptureTimeoutRef.current) {
363
+ clearTimeout(rectangleCaptureTimeoutRef.current);
364
+ rectangleCaptureTimeoutRef.current = null;
365
+ }
366
+ if (rectangleHintTimeoutRef.current) {
367
+ clearTimeout(rectangleHintTimeoutRef.current);
368
+ rectangleHintTimeoutRef.current = null;
361
369
  }
362
370
  // Reset DocScanner state
363
371
  if (docScannerRef.current?.reset) {
@@ -369,52 +377,70 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
369
377
  const stableCounter = event.stableCounter ?? 0;
370
378
  const rectangleCoordinates = event.rectangleOnScreen ?? event.rectangleCoordinates;
371
379
  const hasRectangle = Boolean(rectangleCoordinates);
372
- const isConfidentRectangle = hasRectangle && (event.lastDetectionType === 0 || stableCounter > 0);
373
-
374
- const scheduleDetectionClear = () => {
375
- if (rectangleTimeoutRef.current) {
376
- clearTimeout(rectangleTimeoutRef.current);
380
+ const captureReady = hasRectangle && event.lastDetectionType === 0 && stableCounter >= 1;
381
+
382
+ const scheduleClear = (
383
+ ref: React.MutableRefObject<ReturnType<typeof setTimeout> | null>,
384
+ clearFn: () => void,
385
+ ) => {
386
+ if (ref.current) {
387
+ clearTimeout(ref.current);
377
388
  }
378
-
379
- rectangleTimeoutRef.current = setTimeout(() => {
380
- rectangleTimeoutRef.current = null;
381
- setRectangleDetected((prev) => {
382
- if (!prev) {
383
- return prev;
384
- }
385
- console.log('[FullDocScanner] Rectangle timeout - clearing detection');
386
- return false;
387
- });
389
+ ref.current = setTimeout(() => {
390
+ ref.current = null;
391
+ clearFn();
388
392
  }, 350);
389
393
  };
390
394
 
391
- if (isConfidentRectangle) {
392
- scheduleDetectionClear();
393
- setRectangleDetected((prev) => (prev ? prev : true));
395
+ if (hasRectangle) {
396
+ scheduleClear(rectangleHintTimeoutRef, () => setRectangleHint(false));
397
+ setRectangleHint(true);
398
+ } else {
399
+ if (rectangleHintTimeoutRef.current) {
400
+ clearTimeout(rectangleHintTimeoutRef.current);
401
+ rectangleHintTimeoutRef.current = null;
402
+ }
403
+ setRectangleHint(false);
404
+ }
405
+
406
+ if (captureReady) {
407
+ scheduleClear(rectangleCaptureTimeoutRef, () => {
408
+ console.log('[FullDocScanner] Rectangle timeout - clearing detection');
409
+ setRectangleDetected(false);
410
+ });
411
+ setRectangleDetected(true);
394
412
  } else if (!hasRectangle) {
395
- if (rectangleTimeoutRef.current) {
396
- clearTimeout(rectangleTimeoutRef.current);
397
- rectangleTimeoutRef.current = null;
413
+ if (rectangleCaptureTimeoutRef.current) {
414
+ clearTimeout(rectangleCaptureTimeoutRef.current);
415
+ rectangleCaptureTimeoutRef.current = null;
398
416
  }
399
417
  setRectangleDetected(false);
400
- } else {
401
- // Rectangle is present but confidence is low – keep current state but schedule a clear
402
- scheduleDetectionClear();
418
+ } else if (rectangleDetected) {
419
+ scheduleClear(rectangleCaptureTimeoutRef, () => {
420
+ console.log('[FullDocScanner] Rectangle timeout - clearing detection');
421
+ setRectangleDetected(false);
422
+ });
403
423
  }
404
424
 
405
425
  console.log('[FullDocScanner] Rectangle detection update', {
406
426
  lastDetectionType: event.lastDetectionType,
407
427
  stableCounter,
408
428
  hasRectangle,
409
- isConfidentRectangle,
429
+ captureReady,
410
430
  });
411
- }, []);
431
+ }, [rectangleDetected]);
412
432
 
413
- useEffect(() => () => {
414
- if (rectangleTimeoutRef.current) {
415
- clearTimeout(rectangleTimeoutRef.current);
416
- }
417
- }, []);
433
+ useEffect(
434
+ () => () => {
435
+ if (rectangleCaptureTimeoutRef.current) {
436
+ clearTimeout(rectangleCaptureTimeoutRef.current);
437
+ }
438
+ if (rectangleHintTimeoutRef.current) {
439
+ clearTimeout(rectangleHintTimeoutRef.current);
440
+ }
441
+ },
442
+ [],
443
+ );
418
444
 
419
445
  return (
420
446
  <View style={styles.container}>
@@ -503,7 +529,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
503
529
  >
504
530
  <View style={[
505
531
  styles.shutterInner,
506
- rectangleDetected && { backgroundColor: overlayColor }
532
+ rectangleHint && { backgroundColor: overlayColor }
507
533
  ]} />
508
534
  </TouchableOpacity>
509
535
  </View>
@@ -34,39 +34,18 @@ RCT_EXPORT_VIEW_PROPERTY(quality, float)
34
34
  RCT_EXPORT_VIEW_PROPERTY(brightness, float)
35
35
  RCT_EXPORT_VIEW_PROPERTY(contrast, float)
36
36
 
37
- // Main capture method - returns a Promise
38
- RCT_EXPORT_METHOD(capture:(nullable id)reactTag
39
- resolver:(RCTPromiseResolveBlock)resolve
37
+ // Main capture method - returns a Promise and uses the last mounted scanner view
38
+ RCT_EXPORT_METHOD(capture:(RCTPromiseResolveBlock)resolve
40
39
  rejecter:(RCTPromiseRejectBlock)reject) {
41
- NSLog(@"[RNPdfScannerManager] capture called with reactTag: %@", reactTag);
40
+ NSLog(@"[RNPdfScannerManager] capture requested (no reactTag)");
42
41
  dispatch_async(dispatch_get_main_queue(), ^{
43
- DocumentScannerView *targetView = nil;
44
- NSNumber *resolvedTag = ([reactTag isKindOfClass:[NSNumber class]]) ? (NSNumber *)reactTag : nil;
45
-
46
- if (!resolvedTag && reactTag) {
47
- NSLog(@"[RNPdfScannerManager] Unexpected reactTag type %@ - falling back to last known view", NSStringFromClass([reactTag class]));
48
- }
49
-
50
- if (resolvedTag) {
51
- UIView *view = [self.bridge.uiManager viewForReactTag:resolvedTag];
52
- if ([view isKindOfClass:[DocumentScannerView class]]) {
53
- targetView = (DocumentScannerView *)view;
54
- self->_scannerView = targetView;
55
- } else if (view) {
56
- NSLog(@"[RNPdfScannerManager] View for tag %@ is not DocumentScannerView: %@", resolvedTag, NSStringFromClass(view.class));
57
- } else {
58
- NSLog(@"[RNPdfScannerManager] No view found for tag %@", resolvedTag);
59
- }
60
- }
61
-
62
- if (!targetView && self->_scannerView) {
63
- NSLog(@"[RNPdfScannerManager] Falling back to last known scanner view");
64
- targetView = self->_scannerView;
65
- }
42
+ DocumentScannerView *targetView = self->_scannerView;
66
43
 
67
44
  if (!targetView) {
68
- NSLog(@"[RNPdfScannerManager] ERROR: No scanner view available for capture");
69
- reject(@"NO_VIEW", @"No scanner view available for capture", nil);
45
+ NSLog(@"[RNPdfScannerManager] ERROR: Scanner view not yet ready for capture");
46
+ if (reject) {
47
+ reject(@"NO_VIEW", @"Document scanner view is not ready", nil);
48
+ }
70
49
  return;
71
50
  }
72
51
 
@@ -27,18 +27,15 @@ class PdfScanner extends React.Component {
27
27
  }
28
28
 
29
29
  capture() {
30
- console.log('[PdfScanner/ios.js] capture called, ref:', this.scannerRef.current);
31
- const handle = findNodeHandle(this.scannerRef.current);
32
- console.log('[PdfScanner/ios.js] node handle (reactTag):', handle);
30
+ console.log('[PdfScanner/ios.js] capture called');
33
31
 
34
- if (!handle) {
35
- console.error('[PdfScanner/ios.js] ERROR: No handle found for scanner ref');
36
- return Promise.reject(new Error('No handle found for scanner view'));
32
+ if (!this.scannerRef.current) {
33
+ const error = new Error('DocumentScanner native view is not ready');
34
+ console.error('[PdfScanner/ios.js] ERROR:', error.message);
35
+ return Promise.reject(error);
37
36
  }
38
37
 
39
- // Call native method with reactTag - now returns a Promise
40
- console.log('[PdfScanner/ios.js] Calling native capture with handle:', handle);
41
- return NativeModules.RNPdfScannerManager.capture(handle);
38
+ return NativeModules.RNPdfScannerManager.capture();
42
39
  }
43
40
 
44
41
  render() {