react-native-rectangle-doc-scanner 3.118.0 → 3.120.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.
@@ -7,6 +7,7 @@ type PictureEvent = {
7
7
  width?: number;
8
8
  height?: number;
9
9
  rectangleCoordinates?: NativeRectangle | null;
10
+ rectangleOnScreen?: NativeRectangle | null;
10
11
  };
11
12
  export type RectangleDetectEvent = Omit<RectangleEventPayload, 'rectangleCoordinates' | 'rectangleOnScreen'> & {
12
13
  rectangleCoordinates?: Rectangle | null;
@@ -118,7 +118,9 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
118
118
  height: event.height,
119
119
  });
120
120
  setIsAutoCapturing(false);
121
- const normalizedRectangle = normalizeRectangle(event.rectangleCoordinates ?? null) ?? lastRectangleRef.current;
121
+ const normalizedRectangle = normalizeRectangle(event.rectangleCoordinates ?? null) ??
122
+ normalizeRectangle(event.rectangleOnScreen ?? null) ??
123
+ lastRectangleRef.current;
122
124
  const quad = normalizedRectangle ? (0, coordinate_1.rectangleToQuad)(normalizedRectangle) : null;
123
125
  const origin = captureOriginRef.current;
124
126
  captureOriginRef.current = 'auto';
@@ -286,8 +288,8 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
286
288
  setIsAutoCapturing(false);
287
289
  }
288
290
  }
289
- if (payload.rectangleCoordinates) {
290
- lastRectangleRef.current = payload.rectangleCoordinates;
291
+ if (payload.rectangleCoordinates || payload.rectangleOnScreen) {
292
+ lastRectangleRef.current = payload.rectangleCoordinates ?? payload.rectangleOnScreen ?? null;
291
293
  }
292
294
  const isGoodRectangle = payload.lastDetectionType === 0;
293
295
  const hasValidRectangle = isGoodRectangle && rectangleOnScreen;
@@ -321,6 +323,13 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
321
323
  captureResolvers.current.reject(new Error('reset'));
322
324
  captureResolvers.current = null;
323
325
  }
326
+ if (rectangleClearTimeoutRef.current) {
327
+ clearTimeout(rectangleClearTimeoutRef.current);
328
+ rectangleClearTimeoutRef.current = null;
329
+ }
330
+ lastRectangleRef.current = null;
331
+ setDetectedRectangle(null);
332
+ setIsAutoCapturing(false);
324
333
  captureOriginRef.current = 'auto';
325
334
  },
326
335
  }), [capture]);
@@ -207,6 +207,10 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
207
207
  errorMessage.includes('cancelled') ||
208
208
  errorMessage.includes('cancel')) {
209
209
  console.log('[FullDocScanner] User cancelled cropper');
210
+ // DocScanner 상태를 리셋하여 카메라가 다시 작동하도록 함
211
+ if (docScannerRef.current?.reset) {
212
+ docScannerRef.current.reset();
213
+ }
210
214
  }
211
215
  else {
212
216
  emitError(error instanceof Error ? error : new Error(errorMessage), 'Failed to crop image. Please try again.');
@@ -515,9 +519,14 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
515
519
  croppedImageData ? (
516
520
  // check_DP: Show confirmation screen
517
521
  react_1.default.createElement(react_native_1.View, { style: styles.confirmationContainer },
518
- isBusinessMode && (react_1.default.createElement(react_native_1.View, { style: styles.photoHeader },
519
- react_1.default.createElement(react_native_1.Text, { style: styles.photoHeaderText }, currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second))),
520
- isImageRotationSupported() ? (react_1.default.createElement(react_native_1.View, { style: styles.rotateButtonsCenter },
522
+ isBusinessMode && isImageRotationSupported() ? (react_1.default.createElement(react_native_1.View, { style: styles.businessHeaderRow },
523
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.rotateButtonLeft, onPress: () => handleRotateImage(-90), accessibilityLabel: "\uC67C\uCABD\uC73C\uB85C 90\uB3C4 \uD68C\uC804", accessibilityRole: "button" },
524
+ react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BA")),
525
+ react_1.default.createElement(react_native_1.View, { style: styles.photoHeaderCenter },
526
+ react_1.default.createElement(react_native_1.Text, { style: styles.photoHeaderText }, currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second)),
527
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.rotateButtonRight, onPress: () => handleRotateImage(90), accessibilityLabel: "\uC624\uB978\uCABD\uC73C\uB85C 90\uB3C4 \uD68C\uC804", accessibilityRole: "button" },
528
+ react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BB")))) : isBusinessMode ? (react_1.default.createElement(react_native_1.View, { style: styles.photoHeader },
529
+ react_1.default.createElement(react_native_1.Text, { style: styles.photoHeaderText }, currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second))) : isImageRotationSupported() ? (react_1.default.createElement(react_native_1.View, { style: styles.rotateButtonsCenter },
521
530
  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" },
522
531
  react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BA"),
523
532
  react_1.default.createElement(react_native_1.Text, { style: styles.rotateButtonLabel }, "\uC88C\uB85C 90\u00B0")),
@@ -817,4 +826,38 @@ const styles = react_native_1.StyleSheet.create({
817
826
  borderColor: '#fff',
818
827
  overflow: 'hidden',
819
828
  },
829
+ businessHeaderRow: {
830
+ position: 'absolute',
831
+ top: 80,
832
+ left: 0,
833
+ right: 0,
834
+ flexDirection: 'row',
835
+ justifyContent: 'space-between',
836
+ alignItems: 'center',
837
+ paddingHorizontal: 20,
838
+ zIndex: 10,
839
+ },
840
+ rotateButtonLeft: {
841
+ width: 48,
842
+ height: 48,
843
+ borderRadius: 24,
844
+ backgroundColor: 'rgba(50,50,50,0.8)',
845
+ justifyContent: 'center',
846
+ alignItems: 'center',
847
+ borderWidth: 1,
848
+ borderColor: 'rgba(255,255,255,0.3)',
849
+ },
850
+ rotateButtonRight: {
851
+ width: 48,
852
+ height: 48,
853
+ borderRadius: 24,
854
+ backgroundColor: 'rgba(50,50,50,0.8)',
855
+ justifyContent: 'center',
856
+ alignItems: 'center',
857
+ borderWidth: 1,
858
+ borderColor: 'rgba(255,255,255,0.3)',
859
+ },
860
+ photoHeaderCenter: {
861
+ alignItems: 'center',
862
+ },
820
863
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.118.0",
3
+ "version": "3.120.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -28,6 +28,7 @@ type PictureEvent = {
28
28
  width?: number;
29
29
  height?: number;
30
30
  rectangleCoordinates?: NativeRectangle | null;
31
+ rectangleOnScreen?: NativeRectangle | null;
31
32
  };
32
33
 
33
34
  export type RectangleDetectEvent = Omit<RectangleEventPayload, 'rectangleCoordinates' | 'rectangleOnScreen'> & {
@@ -195,7 +196,9 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
195
196
  setIsAutoCapturing(false);
196
197
 
197
198
  const normalizedRectangle =
198
- normalizeRectangle(event.rectangleCoordinates ?? null) ?? lastRectangleRef.current;
199
+ normalizeRectangle(event.rectangleCoordinates ?? null) ??
200
+ normalizeRectangle(event.rectangleOnScreen ?? null) ??
201
+ lastRectangleRef.current;
199
202
  const quad = normalizedRectangle ? rectangleToQuad(normalizedRectangle) : null;
200
203
  const origin = captureOriginRef.current;
201
204
  captureOriginRef.current = 'auto';
@@ -390,8 +393,8 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
390
393
  }
391
394
  }
392
395
 
393
- if (payload.rectangleCoordinates) {
394
- lastRectangleRef.current = payload.rectangleCoordinates;
396
+ if (payload.rectangleCoordinates || payload.rectangleOnScreen) {
397
+ lastRectangleRef.current = payload.rectangleCoordinates ?? payload.rectangleOnScreen ?? null;
395
398
  }
396
399
 
397
400
  const isGoodRectangle = payload.lastDetectionType === 0;
@@ -432,6 +435,15 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
432
435
  captureResolvers.current.reject(new Error('reset'));
433
436
  captureResolvers.current = null;
434
437
  }
438
+
439
+ if (rectangleClearTimeoutRef.current) {
440
+ clearTimeout(rectangleClearTimeoutRef.current);
441
+ rectangleClearTimeoutRef.current = null;
442
+ }
443
+
444
+ lastRectangleRef.current = null;
445
+ setDetectedRectangle(null);
446
+ setIsAutoCapturing(false);
435
447
  captureOriginRef.current = 'auto';
436
448
  },
437
449
  }),
@@ -291,6 +291,10 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
291
291
  errorMessage.includes('cancel')
292
292
  ) {
293
293
  console.log('[FullDocScanner] User cancelled cropper');
294
+ // DocScanner 상태를 리셋하여 카메라가 다시 작동하도록 함
295
+ if (docScannerRef.current?.reset) {
296
+ docScannerRef.current.reset();
297
+ }
294
298
  } else {
295
299
  emitError(
296
300
  error instanceof Error ? error : new Error(errorMessage),
@@ -672,17 +676,40 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
672
676
  {croppedImageData ? (
673
677
  // check_DP: Show confirmation screen
674
678
  <View style={styles.confirmationContainer}>
675
- {/* 헤더 - 앞면/뒷면 표시 */}
676
- {isBusinessMode && (
679
+ {/* Business 모드: 회전 버튼(왼쪽/오른쪽) + 헤더(가운데) 배치 */}
680
+ {isBusinessMode && isImageRotationSupported() ? (
681
+ <View style={styles.businessHeaderRow}>
682
+ <TouchableOpacity
683
+ style={styles.rotateButtonLeft}
684
+ onPress={() => handleRotateImage(-90)}
685
+ accessibilityLabel="왼쪽으로 90도 회전"
686
+ accessibilityRole="button"
687
+ >
688
+ <Text style={styles.rotateIconText}>↺</Text>
689
+ </TouchableOpacity>
690
+
691
+ <View style={styles.photoHeaderCenter}>
692
+ <Text style={styles.photoHeaderText}>
693
+ {currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second}
694
+ </Text>
695
+ </View>
696
+
697
+ <TouchableOpacity
698
+ style={styles.rotateButtonRight}
699
+ onPress={() => handleRotateImage(90)}
700
+ accessibilityLabel="오른쪽으로 90도 회전"
701
+ accessibilityRole="button"
702
+ >
703
+ <Text style={styles.rotateIconText}>↻</Text>
704
+ </TouchableOpacity>
705
+ </View>
706
+ ) : isBusinessMode ? (
677
707
  <View style={styles.photoHeader}>
678
708
  <Text style={styles.photoHeaderText}>
679
709
  {currentPhotoIndex === 0 ? mergedStrings.first : mergedStrings.second}
680
710
  </Text>
681
711
  </View>
682
- )}
683
-
684
- {/* 회전 버튼들 - 가운데 정렬 */}
685
- {isImageRotationSupported() ? (
712
+ ) : isImageRotationSupported() ? (
686
713
  <View style={styles.rotateButtonsCenter}>
687
714
  <TouchableOpacity
688
715
  style={styles.rotateButtonTop}
@@ -1105,4 +1132,38 @@ const styles = StyleSheet.create({
1105
1132
  borderColor: '#fff',
1106
1133
  overflow: 'hidden',
1107
1134
  },
1135
+ businessHeaderRow: {
1136
+ position: 'absolute',
1137
+ top: 80,
1138
+ left: 0,
1139
+ right: 0,
1140
+ flexDirection: 'row',
1141
+ justifyContent: 'space-between',
1142
+ alignItems: 'center',
1143
+ paddingHorizontal: 20,
1144
+ zIndex: 10,
1145
+ },
1146
+ rotateButtonLeft: {
1147
+ width: 48,
1148
+ height: 48,
1149
+ borderRadius: 24,
1150
+ backgroundColor: 'rgba(50,50,50,0.8)',
1151
+ justifyContent: 'center',
1152
+ alignItems: 'center',
1153
+ borderWidth: 1,
1154
+ borderColor: 'rgba(255,255,255,0.3)',
1155
+ },
1156
+ rotateButtonRight: {
1157
+ width: 48,
1158
+ height: 48,
1159
+ borderRadius: 24,
1160
+ backgroundColor: 'rgba(50,50,50,0.8)',
1161
+ justifyContent: 'center',
1162
+ alignItems: 'center',
1163
+ borderWidth: 1,
1164
+ borderColor: 'rgba(255,255,255,0.3)',
1165
+ },
1166
+ photoHeaderCenter: {
1167
+ alignItems: 'center',
1168
+ },
1108
1169
  });