react-native-rectangle-doc-scanner 3.85.0 → 3.87.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.
@@ -109,6 +109,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
109
109
  const [isGalleryOpen, setIsGalleryOpen] = (0, react_1.useState)(false);
110
110
  const [rectangleDetected, setRectangleDetected] = (0, react_1.useState)(false);
111
111
  const [rectangleHint, setRectangleHint] = (0, react_1.useState)(false);
112
+ const [flashEnabled, setFlashEnabled] = (0, react_1.useState)(false);
112
113
  const resolvedGridColor = gridColor ?? overlayColor;
113
114
  const docScannerRef = (0, react_1.useRef)(null);
114
115
  const captureModeRef = (0, react_1.useRef)(null);
@@ -171,6 +172,9 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
171
172
  catch (error) {
172
173
  console.error('[FullDocScanner] openCropper error:', error);
173
174
  setProcessing(false);
175
+ // Reset capture state when cropper fails or is cancelled
176
+ captureInProgressRef.current = false;
177
+ captureModeRef.current = null;
174
178
  const errorCode = error?.code;
175
179
  const errorMessage = error?.message || String(error);
176
180
  if (errorCode === CROPPER_TIMEOUT_CODE || errorMessage === CROPPER_TIMEOUT_CODE) {
@@ -315,6 +319,9 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
315
319
  const handleClose = (0, react_1.useCallback)(() => {
316
320
  onClose?.();
317
321
  }, [onClose]);
322
+ const handleFlashToggle = (0, react_1.useCallback)(() => {
323
+ setFlashEnabled(prev => !prev);
324
+ }, []);
318
325
  const handleConfirm = (0, react_1.useCallback)(() => {
319
326
  if (croppedImageData) {
320
327
  onResult({
@@ -414,7 +421,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
414
421
  react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.retake)),
415
422
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleConfirm, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button" },
416
423
  react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.confirm))))) : (react_1.default.createElement(react_native_1.View, { style: styles.flex },
417
- 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 },
424
+ 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, enableTorch: flashEnabled },
418
425
  react_1.default.createElement(react_native_1.View, { style: styles.overlayTop, pointerEvents: "box-none" },
419
426
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.closeButton, onPress: handleClose, accessibilityLabel: mergedStrings.cancel, accessibilityRole: "button" },
420
427
  react_1.default.createElement(react_native_1.Text, { style: styles.closeButtonLabel }, "\u00D7"))),
@@ -423,13 +430,17 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
423
430
  mergedStrings.captureHint && (react_1.default.createElement(react_native_1.Text, { style: styles.captureText }, mergedStrings.captureHint)),
424
431
  mergedStrings.manualHint && (react_1.default.createElement(react_native_1.Text, { style: styles.captureText }, mergedStrings.manualHint))))),
425
432
  react_1.default.createElement(react_native_1.View, { style: styles.shutterContainer, pointerEvents: "box-none" },
426
- enableGallery && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.galleryButton, processing && styles.buttonDisabled], onPress: handleGalleryPick, disabled: processing, accessibilityLabel: mergedStrings.galleryButton, accessibilityRole: "button" },
427
- react_1.default.createElement(react_native_1.Text, { style: styles.galleryButtonText }, "\uD83D\uDCC1"))),
433
+ react_1.default.createElement(react_native_1.View, { style: styles.leftButtonsContainer },
434
+ enableGallery && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.galleryButton, processing && styles.buttonDisabled], onPress: handleGalleryPick, disabled: processing, accessibilityLabel: mergedStrings.galleryButton, accessibilityRole: "button" },
435
+ react_1.default.createElement(react_native_1.Text, { style: styles.galleryButtonText }, "\uD83D\uDCC1"))),
436
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.flashButton, processing && styles.buttonDisabled], onPress: handleFlashToggle, disabled: processing, accessibilityLabel: "Toggle flash", accessibilityRole: "button" },
437
+ react_1.default.createElement(react_native_1.Text, { style: styles.flashButtonText }, flashEnabled ? '⚡' : '⚡️'))),
428
438
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.shutterButton, processing && styles.buttonDisabled], onPress: triggerManualCapture, disabled: processing, accessibilityLabel: mergedStrings.manualHint, accessibilityRole: "button" },
429
439
  react_1.default.createElement(react_native_1.View, { style: [
430
440
  styles.shutterInner,
431
441
  rectangleHint && { backgroundColor: overlayColor }
432
- ] })))))),
442
+ ] })),
443
+ react_1.default.createElement(react_native_1.View, { style: styles.rightButtonsPlaceholder }))))),
433
444
  processing && (react_1.default.createElement(react_native_1.View, { style: styles.processingOverlay },
434
445
  react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: overlayColor }),
435
446
  mergedStrings.processing && (react_1.default.createElement(react_native_1.Text, { style: styles.processingText }, mergedStrings.processing))))));
@@ -463,11 +474,20 @@ const styles = react_native_1.StyleSheet.create({
463
474
  left: 0,
464
475
  right: 0,
465
476
  flexDirection: 'row',
466
- justifyContent: 'center',
477
+ justifyContent: 'space-between',
467
478
  alignItems: 'center',
468
- gap: 24,
479
+ paddingHorizontal: 40,
469
480
  zIndex: 10,
470
481
  },
482
+ leftButtonsContainer: {
483
+ flexDirection: 'row',
484
+ gap: 12,
485
+ alignItems: 'center',
486
+ flex: 1,
487
+ },
488
+ rightButtonsPlaceholder: {
489
+ flex: 1,
490
+ },
471
491
  closeButton: {
472
492
  width: 40,
473
493
  height: 40,
@@ -494,9 +514,9 @@ const styles = react_native_1.StyleSheet.create({
494
514
  textAlign: 'center',
495
515
  },
496
516
  galleryButton: {
497
- width: 60,
498
- height: 60,
499
- borderRadius: 30,
517
+ width: 56,
518
+ height: 56,
519
+ borderRadius: 28,
500
520
  borderWidth: 3,
501
521
  borderColor: '#fff',
502
522
  justifyContent: 'center',
@@ -504,7 +524,20 @@ const styles = react_native_1.StyleSheet.create({
504
524
  backgroundColor: 'rgba(255,255,255,0.1)',
505
525
  },
506
526
  galleryButtonText: {
507
- fontSize: 28,
527
+ fontSize: 24,
528
+ },
529
+ flashButton: {
530
+ width: 56,
531
+ height: 56,
532
+ borderRadius: 28,
533
+ borderWidth: 3,
534
+ borderColor: '#fff',
535
+ justifyContent: 'center',
536
+ alignItems: 'center',
537
+ backgroundColor: 'rgba(255,255,255,0.1)',
538
+ },
539
+ flashButtonText: {
540
+ fontSize: 24,
508
541
  },
509
542
  shutterButton: {
510
543
  width: 80,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.85.0",
3
+ "version": "3.87.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -150,6 +150,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
150
150
  const [isGalleryOpen, setIsGalleryOpen] = useState(false);
151
151
  const [rectangleDetected, setRectangleDetected] = useState(false);
152
152
  const [rectangleHint, setRectangleHint] = useState(false);
153
+ const [flashEnabled, setFlashEnabled] = useState(false);
153
154
  const resolvedGridColor = gridColor ?? overlayColor;
154
155
  const docScannerRef = useRef<DocScannerHandle | null>(null);
155
156
  const captureModeRef = useRef<'grid' | 'no-grid' | null>(null);
@@ -231,6 +232,10 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
231
232
  console.error('[FullDocScanner] openCropper error:', error);
232
233
  setProcessing(false);
233
234
 
235
+ // Reset capture state when cropper fails or is cancelled
236
+ captureInProgressRef.current = false;
237
+ captureModeRef.current = null;
238
+
234
239
  const errorCode = (error as any)?.code;
235
240
  const errorMessage = (error as any)?.message || String(error);
236
241
 
@@ -422,6 +427,10 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
422
427
  onClose?.();
423
428
  }, [onClose]);
424
429
 
430
+ const handleFlashToggle = useCallback(() => {
431
+ setFlashEnabled(prev => !prev);
432
+ }, []);
433
+
425
434
  const handleConfirm = useCallback(() => {
426
435
  if (croppedImageData) {
427
436
  onResult({
@@ -565,6 +574,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
565
574
  onCapture={handleCapture}
566
575
  onRectangleDetect={handleRectangleDetect}
567
576
  showManualCaptureButton={false}
577
+ enableTorch={flashEnabled}
568
578
  >
569
579
  <View style={styles.overlayTop} pointerEvents="box-none">
570
580
  <TouchableOpacity
@@ -589,17 +599,28 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
589
599
  </View>
590
600
  )}
591
601
  <View style={styles.shutterContainer} pointerEvents="box-none">
592
- {enableGallery && (
602
+ <View style={styles.leftButtonsContainer}>
603
+ {enableGallery && (
604
+ <TouchableOpacity
605
+ style={[styles.galleryButton, processing && styles.buttonDisabled]}
606
+ onPress={handleGalleryPick}
607
+ disabled={processing}
608
+ accessibilityLabel={mergedStrings.galleryButton}
609
+ accessibilityRole="button"
610
+ >
611
+ <Text style={styles.galleryButtonText}>📁</Text>
612
+ </TouchableOpacity>
613
+ )}
593
614
  <TouchableOpacity
594
- style={[styles.galleryButton, processing && styles.buttonDisabled]}
595
- onPress={handleGalleryPick}
615
+ style={[styles.flashButton, processing && styles.buttonDisabled]}
616
+ onPress={handleFlashToggle}
596
617
  disabled={processing}
597
- accessibilityLabel={mergedStrings.galleryButton}
618
+ accessibilityLabel="Toggle flash"
598
619
  accessibilityRole="button"
599
620
  >
600
- <Text style={styles.galleryButtonText}>📁</Text>
621
+ <Text style={styles.flashButtonText}>{flashEnabled ? '⚡' : '⚡️'}</Text>
601
622
  </TouchableOpacity>
602
- )}
623
+ </View>
603
624
  <TouchableOpacity
604
625
  style={[styles.shutterButton, processing && styles.buttonDisabled]}
605
626
  onPress={triggerManualCapture}
@@ -612,6 +633,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
612
633
  rectangleHint && { backgroundColor: overlayColor }
613
634
  ]} />
614
635
  </TouchableOpacity>
636
+ <View style={styles.rightButtonsPlaceholder} />
615
637
  </View>
616
638
  </DocScanner>
617
639
  </View>
@@ -657,11 +679,20 @@ const styles = StyleSheet.create({
657
679
  left: 0,
658
680
  right: 0,
659
681
  flexDirection: 'row',
660
- justifyContent: 'center',
682
+ justifyContent: 'space-between',
661
683
  alignItems: 'center',
662
- gap: 24,
684
+ paddingHorizontal: 40,
663
685
  zIndex: 10,
664
686
  },
687
+ leftButtonsContainer: {
688
+ flexDirection: 'row',
689
+ gap: 12,
690
+ alignItems: 'center',
691
+ flex: 1,
692
+ },
693
+ rightButtonsPlaceholder: {
694
+ flex: 1,
695
+ },
665
696
  closeButton: {
666
697
  width: 40,
667
698
  height: 40,
@@ -688,9 +719,9 @@ const styles = StyleSheet.create({
688
719
  textAlign: 'center',
689
720
  },
690
721
  galleryButton: {
691
- width: 60,
692
- height: 60,
693
- borderRadius: 30,
722
+ width: 56,
723
+ height: 56,
724
+ borderRadius: 28,
694
725
  borderWidth: 3,
695
726
  borderColor: '#fff',
696
727
  justifyContent: 'center',
@@ -698,7 +729,20 @@ const styles = StyleSheet.create({
698
729
  backgroundColor: 'rgba(255,255,255,0.1)',
699
730
  },
700
731
  galleryButtonText: {
701
- fontSize: 28,
732
+ fontSize: 24,
733
+ },
734
+ flashButton: {
735
+ width: 56,
736
+ height: 56,
737
+ borderRadius: 28,
738
+ borderWidth: 3,
739
+ borderColor: '#fff',
740
+ justifyContent: 'center',
741
+ alignItems: 'center',
742
+ backgroundColor: 'rgba(255,255,255,0.1)',
743
+ },
744
+ flashButtonText: {
745
+ fontSize: 24,
702
746
  },
703
747
  shutterButton: {
704
748
  width: 80,
@@ -28,13 +28,28 @@
28
28
  [self setupCameraView];
29
29
  [self start];
30
30
  _hasSetupCamera = YES;
31
+ } else if (_hasSetupCamera && self.window && !CGRectIsEmpty(self.bounds)) {
32
+ // Check if camera session is running, restart if needed
33
+ if (self.captureSession && !self.captureSession.isRunning) {
34
+ NSLog(@"[DocumentScanner] Camera session not running, restarting...");
35
+ [self start];
36
+ }
31
37
  }
32
38
  }
33
39
 
34
40
  - (void)didMoveToWindow {
35
41
  [super didMoveToWindow];
36
- if (!self.window && _hasSetupCamera) {
42
+ if (self.window && _hasSetupCamera) {
43
+ // Restart camera when view is added back to window
44
+ if (self.captureSession && !self.captureSession.isRunning) {
45
+ NSLog(@"[DocumentScanner] View added to window, restarting camera...");
46
+ dispatch_async(dispatch_get_main_queue(), ^{
47
+ [self start];
48
+ });
49
+ }
50
+ } else if (!self.window && _hasSetupCamera) {
37
51
  // Stop camera when view is removed from window
52
+ NSLog(@"[DocumentScanner] View removed from window, stopping camera");
38
53
  [self stop];
39
54
  }
40
55
  }