react-native-rectangle-doc-scanner 3.53.0 → 3.55.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.
@@ -205,8 +205,9 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
205
205
  }), [capture]);
206
206
  const overlayPolygon = detectedRectangle?.rectangleOnScreen ?? detectedRectangle?.rectangleCoordinates ?? null;
207
207
  const overlayIsActive = autoCapture ? isAutoCapturing : (detectedRectangle?.stableCounter ?? 0) > 0;
208
+ const detectionThreshold = autoCapture ? minStableFrames : 9999;
208
209
  return (react_1.default.createElement(react_native_1.View, { style: styles.container },
209
- react_1.default.createElement(react_native_document_scanner_1.default, { ref: scannerRef, style: styles.scanner, detectionCountBeforeCapture: minStableFrames, overlayColor: overlayColor, enableTorch: enableTorch, quality: normalizedQuality, useBase64: useBase64, manualOnly: !autoCapture, detectionConfig: detectionConfig, onPictureTaken: handlePictureTaken, onError: handleError, onRectangleDetect: handleRectangleDetect }),
210
+ react_1.default.createElement(react_native_document_scanner_1.default, { ref: scannerRef, style: styles.scanner, detectionCountBeforeCapture: detectionThreshold, overlayColor: overlayColor, enableTorch: enableTorch, quality: normalizedQuality, useBase64: useBase64, manualOnly: !autoCapture, detectionConfig: detectionConfig, onPictureTaken: handlePictureTaken, onError: handleError, onRectangleDetect: handleRectangleDetect }),
210
211
  showGrid && overlayPolygon && (react_1.default.createElement(overlay_1.ScannerOverlay, { active: overlayIsActive, color: gridColor ?? overlayColor, lineWidth: gridLineWidth, polygon: overlayPolygon })),
211
212
  showManualCaptureButton && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.button, onPress: handleManualCapture })),
212
213
  children));
@@ -61,6 +61,8 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
61
61
  const resolvedGridColor = gridColor ?? overlayColor;
62
62
  const docScannerRef = (0, react_1.useRef)(null);
63
63
  const captureModeRef = (0, react_1.useRef)(null);
64
+ const captureInProgressRef = (0, react_1.useRef)(false);
65
+ const rectangleTimeoutRef = (0, react_1.useRef)(null);
64
66
  const mergedStrings = (0, react_1.useMemo)(() => ({
65
67
  captureHint: strings?.captureHint,
66
68
  manualHint: strings?.manualHint,
@@ -122,6 +124,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
122
124
  initialPath: document.initialPath,
123
125
  captureMode: captureModeRef.current,
124
126
  });
127
+ captureInProgressRef.current = false;
125
128
  if (document.origin === 'auto') {
126
129
  console.log('[FullDocScanner] Ignoring auto capture result');
127
130
  return;
@@ -150,33 +153,39 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
150
153
  await openCropper(normalizedDoc.path);
151
154
  }, [openCropper]);
152
155
  const triggerManualCapture = (0, react_1.useCallback)(() => {
156
+ const scannerInstance = docScannerRef.current;
157
+ const hasScanner = !!scannerInstance;
153
158
  console.log('[FullDocScanner] triggerManualCapture called', {
154
159
  processing,
155
- hasRef: !!docScannerRef.current,
160
+ hasRef: hasScanner,
156
161
  rectangleDetected,
157
162
  currentCaptureMode: captureModeRef.current,
163
+ captureInProgress: captureInProgressRef.current,
158
164
  });
159
165
  if (processing) {
160
166
  console.log('[FullDocScanner] Already processing, skipping manual capture');
161
167
  return;
162
168
  }
163
- // Check if capture is already in progress
164
- if (captureModeRef.current !== null) {
169
+ if (captureInProgressRef.current) {
165
170
  console.log('[FullDocScanner] Capture already in progress, skipping');
166
171
  return;
167
172
  }
168
- if (!docScannerRef.current) {
173
+ if (!hasScanner) {
169
174
  console.error('[FullDocScanner] DocScanner ref not available');
170
175
  return;
171
176
  }
172
177
  console.log('[FullDocScanner] Starting manual capture, grid detected:', rectangleDetected);
173
- captureModeRef.current = rectangleDetected ? 'grid' : 'no-grid';
174
- docScannerRef.current.capture()
178
+ const captureMode = rectangleDetected ? 'grid' : 'no-grid';
179
+ captureModeRef.current = captureMode;
180
+ captureInProgressRef.current = true;
181
+ scannerInstance.capture()
175
182
  .then((result) => {
176
183
  console.log('[FullDocScanner] Manual capture success:', result);
184
+ captureInProgressRef.current = false;
177
185
  })
178
186
  .catch((error) => {
179
187
  captureModeRef.current = null;
188
+ captureInProgressRef.current = false;
180
189
  console.error('[FullDocScanner] Manual capture failed:', error);
181
190
  if (error instanceof Error && error.message !== 'capture_in_progress') {
182
191
  emitError(error, 'Failed to capture image.');
@@ -227,6 +236,11 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
227
236
  setProcessing(false);
228
237
  setRectangleDetected(false);
229
238
  captureModeRef.current = null;
239
+ captureInProgressRef.current = false;
240
+ if (rectangleTimeoutRef.current) {
241
+ clearTimeout(rectangleTimeoutRef.current);
242
+ rectangleTimeoutRef.current = null;
243
+ }
230
244
  // Reset DocScanner state
231
245
  if (docScannerRef.current?.reset) {
232
246
  docScannerRef.current.reset();
@@ -235,7 +249,20 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
235
249
  const handleRectangleDetect = (0, react_1.useCallback)((event) => {
236
250
  const stableCounter = event.stableCounter ?? 0;
237
251
  const hasRectangle = Boolean(event.rectangleOnScreen ?? event.rectangleCoordinates);
238
- const isGoodRectangle = event.lastDetectionType === 0 && hasRectangle && stableCounter > 0;
252
+ const isGoodRectangle = hasRectangle && event.lastDetectionType === 0;
253
+ if (hasRectangle) {
254
+ if (rectangleTimeoutRef.current) {
255
+ clearTimeout(rectangleTimeoutRef.current);
256
+ }
257
+ rectangleTimeoutRef.current = setTimeout(() => {
258
+ rectangleTimeoutRef.current = null;
259
+ setRectangleDetected(false);
260
+ }, 300);
261
+ }
262
+ else if (rectangleTimeoutRef.current) {
263
+ clearTimeout(rectangleTimeoutRef.current);
264
+ rectangleTimeoutRef.current = null;
265
+ }
239
266
  setRectangleDetected((prev) => {
240
267
  if (prev !== isGoodRectangle) {
241
268
  console.log('[FullDocScanner] Rectangle detection update', {
@@ -248,6 +275,11 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
248
275
  return isGoodRectangle;
249
276
  });
250
277
  }, []);
278
+ (0, react_1.useEffect)(() => () => {
279
+ if (rectangleTimeoutRef.current) {
280
+ clearTimeout(rectangleTimeoutRef.current);
281
+ }
282
+ }, []);
251
283
  return (react_1.default.createElement(react_native_1.View, { style: styles.container },
252
284
  croppedImageData ? (
253
285
  // check_DP: Show confirmation screen
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.53.0",
3
+ "version": "3.55.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -293,12 +293,14 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
293
293
  const overlayPolygon = detectedRectangle?.rectangleOnScreen ?? detectedRectangle?.rectangleCoordinates ?? null;
294
294
  const overlayIsActive = autoCapture ? isAutoCapturing : (detectedRectangle?.stableCounter ?? 0) > 0;
295
295
 
296
- return (
296
+ const detectionThreshold = autoCapture ? minStableFrames : 9999;
297
+
298
+ return (
297
299
  <View style={styles.container}>
298
300
  <DocumentScanner
299
301
  ref={scannerRef}
300
302
  style={styles.scanner}
301
- detectionCountBeforeCapture={minStableFrames}
303
+ detectionCountBeforeCapture={detectionThreshold}
302
304
  overlayColor={overlayColor}
303
305
  enableTorch={enableTorch}
304
306
  quality={normalizedQuality}
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useMemo, useRef, useState } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import {
3
3
  ActivityIndicator,
4
4
  Alert,
@@ -89,6 +89,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
89
89
  const resolvedGridColor = gridColor ?? overlayColor;
90
90
  const docScannerRef = useRef<DocScannerHandle | null>(null);
91
91
  const captureModeRef = useRef<'grid' | 'no-grid' | null>(null);
92
+ const captureInProgressRef = useRef(false);
93
+ const rectangleTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
92
94
 
93
95
  const mergedStrings = useMemo(
94
96
  () => ({
@@ -169,6 +171,8 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
169
171
  captureMode: captureModeRef.current,
170
172
  });
171
173
 
174
+ captureInProgressRef.current = false;
175
+
172
176
  if (document.origin === 'auto') {
173
177
  console.log('[FullDocScanner] Ignoring auto capture result');
174
178
  return;
@@ -207,11 +211,14 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
207
211
  );
208
212
 
209
213
  const triggerManualCapture = useCallback(() => {
214
+ const scannerInstance = docScannerRef.current;
215
+ const hasScanner = !!scannerInstance;
210
216
  console.log('[FullDocScanner] triggerManualCapture called', {
211
217
  processing,
212
- hasRef: !!docScannerRef.current,
218
+ hasRef: hasScanner,
213
219
  rectangleDetected,
214
220
  currentCaptureMode: captureModeRef.current,
221
+ captureInProgress: captureInProgressRef.current,
215
222
  });
216
223
 
217
224
  if (processing) {
@@ -219,27 +226,30 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
219
226
  return;
220
227
  }
221
228
 
222
- // Check if capture is already in progress
223
- if (captureModeRef.current !== null) {
229
+ if (captureInProgressRef.current) {
224
230
  console.log('[FullDocScanner] Capture already in progress, skipping');
225
231
  return;
226
232
  }
227
233
 
228
- if (!docScannerRef.current) {
234
+ if (!hasScanner) {
229
235
  console.error('[FullDocScanner] DocScanner ref not available');
230
236
  return;
231
237
  }
232
238
 
233
239
  console.log('[FullDocScanner] Starting manual capture, grid detected:', rectangleDetected);
234
240
 
235
- captureModeRef.current = rectangleDetected ? 'grid' : 'no-grid';
241
+ const captureMode = rectangleDetected ? 'grid' : 'no-grid';
242
+ captureModeRef.current = captureMode;
243
+ captureInProgressRef.current = true;
236
244
 
237
- docScannerRef.current.capture()
245
+ scannerInstance.capture()
238
246
  .then((result) => {
239
247
  console.log('[FullDocScanner] Manual capture success:', result);
248
+ captureInProgressRef.current = false;
240
249
  })
241
250
  .catch((error: unknown) => {
242
251
  captureModeRef.current = null;
252
+ captureInProgressRef.current = false;
243
253
  console.error('[FullDocScanner] Manual capture failed:', error);
244
254
  if (error instanceof Error && error.message !== 'capture_in_progress') {
245
255
  emitError(
@@ -304,6 +314,11 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
304
314
  setProcessing(false);
305
315
  setRectangleDetected(false);
306
316
  captureModeRef.current = null;
317
+ captureInProgressRef.current = false;
318
+ if (rectangleTimeoutRef.current) {
319
+ clearTimeout(rectangleTimeoutRef.current);
320
+ rectangleTimeoutRef.current = null;
321
+ }
307
322
  // Reset DocScanner state
308
323
  if (docScannerRef.current?.reset) {
309
324
  docScannerRef.current.reset();
@@ -313,7 +328,20 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
313
328
  const handleRectangleDetect = useCallback((event: RectangleDetectEvent) => {
314
329
  const stableCounter = event.stableCounter ?? 0;
315
330
  const hasRectangle = Boolean(event.rectangleOnScreen ?? event.rectangleCoordinates);
316
- const isGoodRectangle = event.lastDetectionType === 0 && hasRectangle && stableCounter > 0;
331
+ const isGoodRectangle = hasRectangle && event.lastDetectionType === 0;
332
+
333
+ if (hasRectangle) {
334
+ if (rectangleTimeoutRef.current) {
335
+ clearTimeout(rectangleTimeoutRef.current);
336
+ }
337
+ rectangleTimeoutRef.current = setTimeout(() => {
338
+ rectangleTimeoutRef.current = null;
339
+ setRectangleDetected(false);
340
+ }, 300);
341
+ } else if (rectangleTimeoutRef.current) {
342
+ clearTimeout(rectangleTimeoutRef.current);
343
+ rectangleTimeoutRef.current = null;
344
+ }
317
345
 
318
346
  setRectangleDetected((prev) => {
319
347
  if (prev !== isGoodRectangle) {
@@ -324,11 +352,16 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
324
352
  isGoodRectangle,
325
353
  });
326
354
  }
327
-
328
355
  return isGoodRectangle;
329
356
  });
330
357
  }, []);
331
358
 
359
+ useEffect(() => () => {
360
+ if (rectangleTimeoutRef.current) {
361
+ clearTimeout(rectangleTimeoutRef.current);
362
+ }
363
+ }, []);
364
+
332
365
  return (
333
366
  <View style={styles.container}>
334
367
  {croppedImageData ? (