react-native-rectangle-doc-scanner 3.115.0 → 3.117.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.
@@ -125,19 +125,14 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
125
125
  const [rotationDegrees, setRotationDegrees] = (0, react_1.useState)(0);
126
126
  const [capturedPhotos, setCapturedPhotos] = (0, react_1.useState)([]);
127
127
  const [currentPhotoIndex, setCurrentPhotoIndex] = (0, react_1.useState)(0);
128
- const [previewPhotoIndex, setPreviewPhotoIndex] = (0, react_1.useState)(0);
129
128
  const resolvedGridColor = gridColor ?? overlayColor;
130
129
  const docScannerRef = (0, react_1.useRef)(null);
131
130
  const captureModeRef = (0, react_1.useRef)(null);
132
131
  const captureInProgressRef = (0, react_1.useRef)(false);
133
132
  const rectangleCaptureTimeoutRef = (0, react_1.useRef)(null);
134
133
  const rectangleHintTimeoutRef = (0, react_1.useRef)(null);
135
- const currentPhotoIndexRef = (0, react_1.useRef)(currentPhotoIndex);
136
134
  const isBusinessMode = type === 'business';
137
135
  const maxPhotos = isBusinessMode ? 2 : 1;
138
- (0, react_1.useEffect)(() => {
139
- currentPhotoIndexRef.current = currentPhotoIndex;
140
- }, [currentPhotoIndex]);
141
136
  const mergedStrings = (0, react_1.useMemo)(() => ({
142
137
  captureHint: strings?.captureHint,
143
138
  manualHint: strings?.manualHint,
@@ -190,7 +185,6 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
190
185
  hasBase64: !!croppedImage.data,
191
186
  });
192
187
  setProcessing(false);
193
- setPreviewPhotoIndex(currentPhotoIndexRef.current);
194
188
  // Show confirmation screen
195
189
  setCroppedImageData({
196
190
  path: croppedImage.path,
@@ -237,8 +231,6 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
237
231
  console.warn('[FullDocScanner] Missing capture mode for capture result, ignoring');
238
232
  return;
239
233
  }
240
- const previewIndex = currentPhotoIndexRef.current;
241
- setPreviewPhotoIndex(previewIndex);
242
234
  const normalizedDoc = normalizeCapturedDocument(document);
243
235
  if (captureMode === 'no-grid') {
244
236
  console.log('[FullDocScanner] No grid at capture button press: opening cropper for manual selection');
@@ -401,20 +393,24 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
401
393
  };
402
394
  const updatedPhotos = [...capturedPhotos, currentPhoto];
403
395
  console.log('[FullDocScanner] Photos captured:', updatedPhotos.length, 'of', maxPhotos);
404
- // Business 모드이고 아직 첫 번째 사진만 찍은 경우
405
- if (isBusinessMode && updatedPhotos.length === 1) {
406
- // 두 번째 사진 촬영 여부를 물어봄 (UI에서 버튼으로 표시)
407
- setCapturedPhotos(updatedPhotos);
408
- setCurrentPhotoIndex(1);
409
- // 확인 화면을 유지하고 "뒷면 촬영" 버튼을 표시
410
- return;
411
- }
412
396
  // 모든 사진 촬영 완료 - 결과 반환
413
397
  console.log('[FullDocScanner] All photos captured, returning results');
414
398
  onResult(updatedPhotos);
415
- }, [croppedImageData, rotationDegrees, capturedPhotos, isBusinessMode, maxPhotos, onResult]);
399
+ }, [croppedImageData, rotationDegrees, capturedPhotos, onResult]);
416
400
  const handleCaptureSecondPhoto = (0, react_1.useCallback)(() => {
417
401
  console.log('[FullDocScanner] Capturing second photo');
402
+ if (!croppedImageData) {
403
+ return;
404
+ }
405
+ // 현재 사진(앞면)을 먼저 저장
406
+ const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
407
+ const currentPhoto = {
408
+ path: croppedImageData.path,
409
+ base64: croppedImageData.base64,
410
+ rotationDegrees: rotationNormalized,
411
+ };
412
+ setCapturedPhotos([currentPhoto]);
413
+ setCurrentPhotoIndex(1);
418
414
  // 확인 화면을 닫고 카메라로 돌아감
419
415
  setCroppedImageData(null);
420
416
  setRotationDegrees(0);
@@ -434,27 +430,33 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
434
430
  if (docScannerRef.current?.reset) {
435
431
  docScannerRef.current.reset();
436
432
  }
437
- }, []);
433
+ }, [croppedImageData, rotationDegrees]);
438
434
  const handleSkipSecondPhoto = (0, react_1.useCallback)(() => {
439
- console.log('[FullDocScanner] Skipping second photo');
440
- // 번째 사진만 반환
441
- onResult(capturedPhotos);
442
- }, [capturedPhotos, onResult]);
435
+ console.log('[FullDocScanner] Skipping second photo - saving current photo only');
436
+ if (!croppedImageData) {
437
+ return;
438
+ }
439
+ // 현재 사진만 저장하고 완료
440
+ const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
441
+ const currentPhoto = {
442
+ path: croppedImageData.path,
443
+ base64: croppedImageData.base64,
444
+ rotationDegrees: rotationNormalized,
445
+ };
446
+ onResult([currentPhoto]);
447
+ }, [croppedImageData, rotationDegrees, onResult]);
443
448
  const handleRetake = (0, react_1.useCallback)(() => {
444
449
  console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
445
- if (isBusinessMode) {
446
- if (currentPhotoIndex === 1 && previewPhotoIndex === 0 && capturedPhotos.length === 1) {
447
- console.log('[FullDocScanner] Retake detected on front preview after confirmation - reverting to front capture');
448
- setCapturedPhotos([]);
449
- setCurrentPhotoIndex(0);
450
- setPreviewPhotoIndex(0);
451
- }
452
- else if (previewPhotoIndex === 1 && capturedPhotos.length === 2) {
453
- console.log('[FullDocScanner] Retake detected on back preview after final confirmation - removing back photo');
454
- setCapturedPhotos(capturedPhotos.slice(0, 1));
455
- setCurrentPhotoIndex(1);
456
- setPreviewPhotoIndex(1);
457
- }
450
+ // Business 모드에서 두 번째 사진을 다시 찍는 경우, 첫 번째 사진 유지
451
+ if (isBusinessMode && capturedPhotos.length === 1) {
452
+ console.log('[FullDocScanner] Retake detected on back photo - keeping front photo');
453
+ setCurrentPhotoIndex(1);
454
+ }
455
+ else {
456
+ // 첫 번째 사진 또는 일반 모드: 모든 상태 초기화
457
+ console.log('[FullDocScanner] Retake detected - resetting all photos');
458
+ setCapturedPhotos([]);
459
+ setCurrentPhotoIndex(0);
458
460
  }
459
461
  setCroppedImageData(null);
460
462
  setRotationDegrees(0);
@@ -475,7 +477,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
475
477
  if (docScannerRef.current?.reset) {
476
478
  docScannerRef.current.reset();
477
479
  }
478
- }, [capturedPhotos, currentPhotoIndex, isBusinessMode, previewPhotoIndex]);
480
+ }, [capturedPhotos.length, isBusinessMode]);
479
481
  const handleRectangleDetect = (0, react_1.useCallback)((event) => {
480
482
  const stableCounter = event.stableCounter ?? 0;
481
483
  const rectangleCoordinates = event.rectangleOnScreen ?? event.rectangleCoordinates;
@@ -529,7 +531,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
529
531
  // check_DP: Show confirmation screen
530
532
  react_1.default.createElement(react_native_1.View, { style: styles.confirmationContainer },
531
533
  isBusinessMode && (react_1.default.createElement(react_native_1.View, { style: styles.photoHeader },
532
- react_1.default.createElement(react_native_1.Text, { style: styles.photoHeaderText }, previewPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second))),
534
+ react_1.default.createElement(react_native_1.Text, { style: styles.photoHeaderText }, currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second))),
533
535
  isImageRotationSupported() ? (react_1.default.createElement(react_native_1.View, { style: styles.rotateButtonsCenter },
534
536
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.rotateButtonTop, onPress: () => handleRotateImage(-90), accessibilityLabel: "\uC67C\uCABD\uC73C\uB85C 90\uB3C4 \uD68C\uC804", accessibilityRole: "button" },
535
537
  react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BA"),
@@ -541,18 +543,10 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
541
543
  styles.previewImage,
542
544
  { transform: [{ rotate: `${rotationDegrees}deg` }] }
543
545
  ], resizeMode: "contain" }),
544
- isBusinessMode &&
545
- capturedPhotos.length === 1 &&
546
- currentPhotoIndex === 1 &&
547
- previewPhotoIndex === 0 &&
548
- mergedStrings.secondPrompt ? (react_1.default.createElement(react_native_1.Text, { style: styles.confirmationPromptText }, mergedStrings.secondPrompt)) : null,
549
546
  react_1.default.createElement(react_native_1.View, { style: styles.confirmationButtons },
550
547
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.retakeButton], onPress: handleRetake, accessibilityLabel: mergedStrings.retake, accessibilityRole: "button" },
551
548
  react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.retake)),
552
- isBusinessMode &&
553
- capturedPhotos.length === 1 &&
554
- currentPhotoIndex === 1 &&
555
- previewPhotoIndex === 0 ? (react_1.default.createElement(react_1.default.Fragment, null,
549
+ isBusinessMode && capturedPhotos.length === 0 ? (react_1.default.createElement(react_1.default.Fragment, null,
556
550
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.confirmButtonPrimary], onPress: handleCaptureSecondPhoto, accessibilityLabel: mergedStrings.secondBtn, accessibilityRole: "button" },
557
551
  react_1.default.createElement(react_native_1.Text, { style: styles.confirmButtonText }, mergedStrings.secondBtn)),
558
552
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.confirmButton, styles.skipButton], onPress: handleSkipSecondPhoto, accessibilityLabel: mergedStrings.confirm, accessibilityRole: "button" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.115.0",
3
+ "version": "3.117.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -183,22 +183,16 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
183
183
  const [rotationDegrees, setRotationDegrees] = useState(0);
184
184
  const [capturedPhotos, setCapturedPhotos] = useState<FullDocScannerResult[]>([]);
185
185
  const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
186
- const [previewPhotoIndex, setPreviewPhotoIndex] = useState(0);
187
186
  const resolvedGridColor = gridColor ?? overlayColor;
188
187
  const docScannerRef = useRef<DocScannerHandle | null>(null);
189
188
  const captureModeRef = useRef<'grid' | 'no-grid' | null>(null);
190
189
  const captureInProgressRef = useRef(false);
191
190
  const rectangleCaptureTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
192
191
  const rectangleHintTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
193
- const currentPhotoIndexRef = useRef(currentPhotoIndex);
194
192
 
195
193
  const isBusinessMode = type === 'business';
196
194
  const maxPhotos = isBusinessMode ? 2 : 1;
197
195
 
198
- useEffect(() => {
199
- currentPhotoIndexRef.current = currentPhotoIndex;
200
- }, [currentPhotoIndex]);
201
-
202
196
  const mergedStrings = useMemo(
203
197
  () => ({
204
198
  captureHint: strings?.captureHint,
@@ -269,8 +263,6 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
269
263
 
270
264
  setProcessing(false);
271
265
 
272
- setPreviewPhotoIndex(currentPhotoIndexRef.current);
273
-
274
266
  // Show confirmation screen
275
267
  setCroppedImageData({
276
268
  path: croppedImage.path,
@@ -333,9 +325,6 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
333
325
  return;
334
326
  }
335
327
 
336
- const previewIndex = currentPhotoIndexRef.current;
337
- setPreviewPhotoIndex(previewIndex);
338
-
339
328
  const normalizedDoc = normalizeCapturedDocument(document);
340
329
 
341
330
  if (captureMode === 'no-grid') {
@@ -542,22 +531,29 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
542
531
  const updatedPhotos = [...capturedPhotos, currentPhoto];
543
532
  console.log('[FullDocScanner] Photos captured:', updatedPhotos.length, 'of', maxPhotos);
544
533
 
545
- // Business 모드이고 아직 첫 번째 사진만 찍은 경우
546
- if (isBusinessMode && updatedPhotos.length === 1) {
547
- // 두 번째 사진 촬영 여부를 물어봄 (UI에서 버튼으로 표시)
548
- setCapturedPhotos(updatedPhotos);
549
- setCurrentPhotoIndex(1);
550
- // 확인 화면을 유지하고 "뒷면 촬영" 버튼을 표시
551
- return;
552
- }
553
-
554
534
  // 모든 사진 촬영 완료 - 결과 반환
555
535
  console.log('[FullDocScanner] All photos captured, returning results');
556
536
  onResult(updatedPhotos);
557
- }, [croppedImageData, rotationDegrees, capturedPhotos, isBusinessMode, maxPhotos, onResult]);
537
+ }, [croppedImageData, rotationDegrees, capturedPhotos, onResult]);
558
538
 
559
539
  const handleCaptureSecondPhoto = useCallback(() => {
560
540
  console.log('[FullDocScanner] Capturing second photo');
541
+
542
+ if (!croppedImageData) {
543
+ return;
544
+ }
545
+
546
+ // 현재 사진(앞면)을 먼저 저장
547
+ const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
548
+ const currentPhoto: FullDocScannerResult = {
549
+ path: croppedImageData.path,
550
+ base64: croppedImageData.base64,
551
+ rotationDegrees: rotationNormalized,
552
+ };
553
+
554
+ setCapturedPhotos([currentPhoto]);
555
+ setCurrentPhotoIndex(1);
556
+
561
557
  // 확인 화면을 닫고 카메라로 돌아감
562
558
  setCroppedImageData(null);
563
559
  setRotationDegrees(0);
@@ -577,29 +573,38 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
577
573
  if (docScannerRef.current?.reset) {
578
574
  docScannerRef.current.reset();
579
575
  }
580
- }, []);
576
+ }, [croppedImageData, rotationDegrees]);
581
577
 
582
578
  const handleSkipSecondPhoto = useCallback(() => {
583
- console.log('[FullDocScanner] Skipping second photo');
584
- // 첫 번째 사진만 반환
585
- onResult(capturedPhotos);
586
- }, [capturedPhotos, onResult]);
579
+ console.log('[FullDocScanner] Skipping second photo - saving current photo only');
580
+
581
+ if (!croppedImageData) {
582
+ return;
583
+ }
584
+
585
+ // 현재 사진만 저장하고 완료
586
+ const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
587
+ const currentPhoto: FullDocScannerResult = {
588
+ path: croppedImageData.path,
589
+ base64: croppedImageData.base64,
590
+ rotationDegrees: rotationNormalized,
591
+ };
592
+
593
+ onResult([currentPhoto]);
594
+ }, [croppedImageData, rotationDegrees, onResult]);
587
595
 
588
596
  const handleRetake = useCallback(() => {
589
597
  console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
590
598
 
591
- if (isBusinessMode) {
592
- if (currentPhotoIndex === 1 && previewPhotoIndex === 0 && capturedPhotos.length === 1) {
593
- console.log('[FullDocScanner] Retake detected on front preview after confirmation - reverting to front capture');
594
- setCapturedPhotos([]);
595
- setCurrentPhotoIndex(0);
596
- setPreviewPhotoIndex(0);
597
- } else if (previewPhotoIndex === 1 && capturedPhotos.length === 2) {
598
- console.log('[FullDocScanner] Retake detected on back preview after final confirmation - removing back photo');
599
- setCapturedPhotos(capturedPhotos.slice(0, 1));
600
- setCurrentPhotoIndex(1);
601
- setPreviewPhotoIndex(1);
602
- }
599
+ // Business 모드에서 두 번째 사진을 다시 찍는 경우, 첫 번째 사진 유지
600
+ if (isBusinessMode && capturedPhotos.length === 1) {
601
+ console.log('[FullDocScanner] Retake detected on back photo - keeping front photo');
602
+ setCurrentPhotoIndex(1);
603
+ } else {
604
+ // 첫 번째 사진 또는 일반 모드: 모든 상태 초기화
605
+ console.log('[FullDocScanner] Retake detected - resetting all photos');
606
+ setCapturedPhotos([]);
607
+ setCurrentPhotoIndex(0);
603
608
  }
604
609
 
605
610
  setCroppedImageData(null);
@@ -621,7 +626,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
621
626
  if (docScannerRef.current?.reset) {
622
627
  docScannerRef.current.reset();
623
628
  }
624
- }, [capturedPhotos, currentPhotoIndex, isBusinessMode, previewPhotoIndex]);
629
+ }, [capturedPhotos.length, isBusinessMode]);
625
630
 
626
631
  const handleRectangleDetect = useCallback((event: RectangleDetectEvent) => {
627
632
  const stableCounter = event.stableCounter ?? 0;
@@ -689,7 +694,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
689
694
  {isBusinessMode && (
690
695
  <View style={styles.photoHeader}>
691
696
  <Text style={styles.photoHeaderText}>
692
- {previewPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second}
697
+ {currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second}
693
698
  </Text>
694
699
  </View>
695
700
  )}
@@ -725,13 +730,6 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
725
730
  ]}
726
731
  resizeMode="contain"
727
732
  />
728
- {isBusinessMode &&
729
- capturedPhotos.length === 1 &&
730
- currentPhotoIndex === 1 &&
731
- previewPhotoIndex === 0 &&
732
- mergedStrings.secondPrompt ? (
733
- <Text style={styles.confirmationPromptText}>{mergedStrings.secondPrompt}</Text>
734
- ) : null}
735
733
  <View style={styles.confirmationButtons}>
736
734
  <TouchableOpacity
737
735
  style={[styles.confirmButton, styles.retakeButton]}
@@ -742,11 +740,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
742
740
  <Text style={styles.confirmButtonText}>{mergedStrings.retake}</Text>
743
741
  </TouchableOpacity>
744
742
 
745
- {/* Business 모드이고 첫 번째 사진을 찍은 후: 뒷면 촬영 버튼 또는 확인 버튼 */}
746
- {isBusinessMode &&
747
- capturedPhotos.length === 1 &&
748
- currentPhotoIndex === 1 &&
749
- previewPhotoIndex === 0 ? (
743
+ {/* Business 모드이고 첫 번째 사진일 때: 뒷면 촬영 버튼과 확인 버튼 동시 표시 */}
744
+ {isBusinessMode && capturedPhotos.length === 0 ? (
750
745
  <>
751
746
  <TouchableOpacity
752
747
  style={[styles.confirmButton, styles.confirmButtonPrimary]}