react-native-rectangle-doc-scanner 3.26.0 → 3.27.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,7 +58,6 @@ const calculateMetrics = (polygon) => {
58
58
  maxY,
59
59
  width: maxX - minX,
60
60
  height: maxY - minY,
61
- centerX: minX + (maxX - minX) / 2,
62
61
  };
63
62
  };
64
63
  const createPointsString = (polygon) => [
@@ -84,25 +83,24 @@ const createGridLines = (polygon) => GRID_STEPS.flatMap((step) => {
84
83
  const ScannerOverlay = ({ active, color = '#0b7ef4', lineWidth = react_native_1.StyleSheet.hairlineWidth, polygon, }) => {
85
84
  const scanProgress = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
86
85
  const fallbackBase = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
86
+ const [scanY, setScanY] = (0, react_1.useState)(null);
87
87
  const metrics = (0, react_1.useMemo)(() => (polygon ? calculateMetrics(polygon) : null), [polygon]);
88
88
  const scanBarHeight = (0, react_1.useMemo)(() => {
89
89
  if (!metrics)
90
90
  return 0;
91
91
  return Math.max(metrics.height * 0.2, 16);
92
92
  }, [metrics]);
93
- const scanTranslate = (0, react_1.useMemo)(() => {
94
- if (!metrics || scanBarHeight === 0) {
95
- return null;
93
+ const travelDistance = (0, react_1.useMemo)(() => {
94
+ if (!metrics) {
95
+ return 0;
96
96
  }
97
- return scanProgress.interpolate({
98
- inputRange: [0, 1],
99
- outputRange: [metrics.minY, Math.max(metrics.minY, metrics.maxY - scanBarHeight)],
100
- });
101
- }, [metrics, scanBarHeight, scanProgress]);
97
+ return Math.max(metrics.height - scanBarHeight, 0);
98
+ }, [metrics, scanBarHeight]);
102
99
  (0, react_1.useEffect)(() => {
103
- if (!active || !metrics || metrics.height <= 1) {
104
- scanProgress.stopAnimation();
105
- scanProgress.setValue(0);
100
+ scanProgress.stopAnimation();
101
+ scanProgress.setValue(0);
102
+ setScanY(null);
103
+ if (!active || !metrics || travelDistance <= 0) {
106
104
  return undefined;
107
105
  }
108
106
  const loop = react_native_1.Animated.loop(react_native_1.Animated.sequence([
@@ -122,16 +120,32 @@ const ScannerOverlay = ({ active, color = '#0b7ef4', lineWidth = react_native_1.
122
120
  loop.start();
123
121
  return () => {
124
122
  loop.stop();
123
+ scanProgress.stopAnimation();
124
+ };
125
+ }, [active, metrics, scanProgress, travelDistance]);
126
+ (0, react_1.useEffect)(() => {
127
+ if (!metrics || travelDistance <= 0) {
128
+ setScanY(null);
129
+ return undefined;
130
+ }
131
+ const listenerId = scanProgress.addListener(({ value }) => {
132
+ const nextValue = metrics.minY + travelDistance * value;
133
+ if (Number.isFinite(nextValue)) {
134
+ setScanY(nextValue);
135
+ }
136
+ });
137
+ return () => {
138
+ scanProgress.removeListener(listenerId);
125
139
  };
126
- }, [active, metrics, scanProgress]);
140
+ }, [metrics, scanProgress, travelDistance]);
127
141
  if (!polygon || !metrics || metrics.width <= 0 || metrics.height <= 0) {
128
142
  return null;
129
143
  }
130
144
  if (SvgModule) {
131
145
  const { default: Svg, Polygon, Line, Defs, LinearGradient, Stop, Rect } = SvgModule;
132
- const AnimatedRect = react_native_1.Animated.createAnimatedComponent(Rect);
133
146
  const gridLines = createGridLines(polygon);
134
147
  const points = createPointsString(polygon);
148
+ const scanRectY = scanY ?? metrics.minY;
135
149
  return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: react_native_1.StyleSheet.absoluteFill },
136
150
  react_1.default.createElement(Svg, { style: react_native_1.StyleSheet.absoluteFill },
137
151
  react_1.default.createElement(Polygon, { points: points, fill: color, opacity: 0.15 }),
@@ -142,10 +156,11 @@ const ScannerOverlay = ({ active, color = '#0b7ef4', lineWidth = react_native_1.
142
156
  react_1.default.createElement(Stop, { offset: "0%", stopColor: "rgba(255,255,255,0)" }),
143
157
  react_1.default.createElement(Stop, { offset: "50%", stopColor: color, stopOpacity: 0.8 }),
144
158
  react_1.default.createElement(Stop, { offset: "100%", stopColor: "rgba(255,255,255,0)" }))),
145
- active && scanTranslate && (react_1.default.createElement(AnimatedRect, { x: metrics.minX, width: metrics.width, height: scanBarHeight, fill: "url(#scanGradient)", y: scanTranslate })))));
159
+ active && travelDistance > 0 && Number.isFinite(scanRectY) && (react_1.default.createElement(Rect, { x: metrics.minX, width: metrics.width, height: scanBarHeight, fill: "url(#scanGradient)", y: scanRectY })))));
146
160
  }
147
- // Fallback rendering without react-native-svg
148
- const relativeTranslate = scanTranslate != null ? react_native_1.Animated.subtract(scanTranslate, metrics.minY) : fallbackBase;
161
+ const relativeTranslate = metrics && travelDistance > 0
162
+ ? react_native_1.Animated.multiply(scanProgress, travelDistance)
163
+ : fallbackBase;
149
164
  return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: react_native_1.StyleSheet.absoluteFill },
150
165
  react_1.default.createElement(react_native_1.View, { style: [
151
166
  styles.fallbackBox,
@@ -157,7 +172,7 @@ const ScannerOverlay = ({ active, color = '#0b7ef4', lineWidth = react_native_1.
157
172
  borderColor: color,
158
173
  borderWidth: lineWidth,
159
174
  },
160
- ] }, active && (react_1.default.createElement(react_native_1.Animated.View, { style: [
175
+ ] }, active && travelDistance > 0 && (react_1.default.createElement(react_native_1.Animated.View, { style: [
161
176
  styles.fallbackScanBar,
162
177
  {
163
178
  backgroundColor: color,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.26.0",
3
+ "version": "3.27.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useMemo, useRef } from 'react';
1
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { Animated, Easing, StyleSheet, View } from 'react-native';
3
3
  import type { Rectangle } from '../types';
4
4
 
@@ -21,7 +21,6 @@ type PolygonMetrics = {
21
21
  maxY: number;
22
22
  width: number;
23
23
  height: number;
24
- centerX: number;
25
24
  };
26
25
 
27
26
  const calculateMetrics = (polygon: Rectangle): PolygonMetrics => {
@@ -57,7 +56,6 @@ const calculateMetrics = (polygon: Rectangle): PolygonMetrics => {
57
56
  maxY,
58
57
  width: maxX - minX,
59
58
  height: maxY - minY,
60
- centerX: minX + (maxX - minX) / 2,
61
59
  };
62
60
  };
63
61
 
@@ -102,6 +100,7 @@ export const ScannerOverlay: React.FC<ScannerOverlayProps> = ({
102
100
  }) => {
103
101
  const scanProgress = useRef(new Animated.Value(0)).current;
104
102
  const fallbackBase = useRef(new Animated.Value(0)).current;
103
+ const [scanY, setScanY] = useState<number | null>(null);
105
104
 
106
105
  const metrics = useMemo(() => (polygon ? calculateMetrics(polygon) : null), [polygon]);
107
106
 
@@ -110,21 +109,19 @@ export const ScannerOverlay: React.FC<ScannerOverlayProps> = ({
110
109
  return Math.max(metrics.height * 0.2, 16);
111
110
  }, [metrics]);
112
111
 
113
- const scanTranslate = useMemo(() => {
114
- if (!metrics || scanBarHeight === 0) {
115
- return null;
112
+ const travelDistance = useMemo(() => {
113
+ if (!metrics) {
114
+ return 0;
116
115
  }
117
-
118
- return scanProgress.interpolate({
119
- inputRange: [0, 1],
120
- outputRange: [metrics.minY, Math.max(metrics.minY, metrics.maxY - scanBarHeight)],
121
- });
122
- }, [metrics, scanBarHeight, scanProgress]);
116
+ return Math.max(metrics.height - scanBarHeight, 0);
117
+ }, [metrics, scanBarHeight]);
123
118
 
124
119
  useEffect(() => {
125
- if (!active || !metrics || metrics.height <= 1) {
126
- scanProgress.stopAnimation();
127
- scanProgress.setValue(0);
120
+ scanProgress.stopAnimation();
121
+ scanProgress.setValue(0);
122
+ setScanY(null);
123
+
124
+ if (!active || !metrics || travelDistance <= 0) {
128
125
  return undefined;
129
126
  }
130
127
 
@@ -148,8 +145,27 @@ export const ScannerOverlay: React.FC<ScannerOverlayProps> = ({
148
145
  loop.start();
149
146
  return () => {
150
147
  loop.stop();
148
+ scanProgress.stopAnimation();
151
149
  };
152
- }, [active, metrics, scanProgress]);
150
+ }, [active, metrics, scanProgress, travelDistance]);
151
+
152
+ useEffect(() => {
153
+ if (!metrics || travelDistance <= 0) {
154
+ setScanY(null);
155
+ return undefined;
156
+ }
157
+
158
+ const listenerId = scanProgress.addListener(({ value }) => {
159
+ const nextValue = metrics.minY + travelDistance * value;
160
+ if (Number.isFinite(nextValue)) {
161
+ setScanY(nextValue);
162
+ }
163
+ });
164
+
165
+ return () => {
166
+ scanProgress.removeListener(listenerId);
167
+ };
168
+ }, [metrics, scanProgress, travelDistance]);
153
169
 
154
170
  if (!polygon || !metrics || metrics.width <= 0 || metrics.height <= 0) {
155
171
  return null;
@@ -157,10 +173,9 @@ export const ScannerOverlay: React.FC<ScannerOverlayProps> = ({
157
173
 
158
174
  if (SvgModule) {
159
175
  const { default: Svg, Polygon, Line, Defs, LinearGradient, Stop, Rect } = SvgModule;
160
- const AnimatedRect = Animated.createAnimatedComponent(Rect);
161
-
162
176
  const gridLines = createGridLines(polygon);
163
177
  const points = createPointsString(polygon);
178
+ const scanRectY = scanY ?? metrics.minY;
164
179
 
165
180
  return (
166
181
  <View pointerEvents="none" style={StyleSheet.absoluteFill}>
@@ -186,13 +201,13 @@ export const ScannerOverlay: React.FC<ScannerOverlayProps> = ({
186
201
  <Stop offset="100%" stopColor="rgba(255,255,255,0)" />
187
202
  </LinearGradient>
188
203
  </Defs>
189
- {active && scanTranslate && (
190
- <AnimatedRect
204
+ {active && travelDistance > 0 && Number.isFinite(scanRectY) && (
205
+ <Rect
191
206
  x={metrics.minX}
192
207
  width={metrics.width}
193
208
  height={scanBarHeight}
194
209
  fill="url(#scanGradient)"
195
- y={scanTranslate}
210
+ y={scanRectY}
196
211
  />
197
212
  )}
198
213
  </Svg>
@@ -200,9 +215,10 @@ export const ScannerOverlay: React.FC<ScannerOverlayProps> = ({
200
215
  );
201
216
  }
202
217
 
203
- // Fallback rendering without react-native-svg
204
218
  const relativeTranslate =
205
- scanTranslate != null ? Animated.subtract(scanTranslate, metrics.minY) : fallbackBase;
219
+ metrics && travelDistance > 0
220
+ ? Animated.multiply(scanProgress, travelDistance)
221
+ : fallbackBase;
206
222
 
207
223
  return (
208
224
  <View pointerEvents="none" style={StyleSheet.absoluteFill}>
@@ -219,7 +235,7 @@ export const ScannerOverlay: React.FC<ScannerOverlayProps> = ({
219
235
  },
220
236
  ]}
221
237
  >
222
- {active && (
238
+ {active && travelDistance > 0 && (
223
239
  <Animated.View
224
240
  style={[
225
241
  styles.fallbackScanBar,