react-native-expo-cropper 1.2.45 → 1.2.47

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.
@@ -25,11 +25,14 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
25
25
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
26
26
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
27
27
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
28
- var _Dimensions$get = _reactNative.Dimensions.get('window'),
29
- width = _Dimensions$get.width;
28
+ // Max width for camera preview on large screens (tablets) so it doesn't stretch full width
29
+ var CAMERA_PREVIEW_MAX_WIDTH = 500;
30
30
  function CustomCamera(_ref) {
31
31
  var onPhotoCaptured = _ref.onPhotoCaptured,
32
32
  onFrameCalculated = _ref.onFrameCalculated;
33
+ var _useWindowDimensions = (0, _reactNative.useWindowDimensions)(),
34
+ windowWidth = _useWindowDimensions.width;
35
+ var cameraPreviewWidth = Math.min(windowWidth, CAMERA_PREVIEW_MAX_WIDTH);
33
36
  var _useState = (0, _react.useState)(false),
34
37
  _useState2 = _slicedToArray(_useState, 2),
35
38
  isReady = _useState2[0],
@@ -42,22 +45,28 @@ function CustomCamera(_ref) {
42
45
  _useState6 = _slicedToArray(_useState5, 2),
43
46
  hasPermission = _useState6[0],
44
47
  setHasPermission = _useState6[1];
48
+ // Force a fresh native CameraView instance per "open" to avoid stale/cached preview.
49
+ var _useState7 = (0, _react.useState)(function () {
50
+ return "".concat(Date.now(), "-").concat(Math.random().toString(16).slice(2));
51
+ }),
52
+ _useState8 = _slicedToArray(_useState7, 1),
53
+ cameraViewMountKey = _useState8[0];
45
54
  var cameraRef = (0, _react.useRef)(null);
46
55
  var cameraWrapperRef = (0, _react.useRef)(null);
47
56
  var insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
48
- var _useState7 = (0, _react.useState)({
57
+ var _useState9 = (0, _react.useState)({
49
58
  width: 0,
50
59
  height: 0,
51
60
  x: 0,
52
61
  y: 0
53
62
  }),
54
- _useState8 = _slicedToArray(_useState7, 2),
55
- cameraWrapperLayout = _useState8[0],
56
- setCameraWrapperLayout = _useState8[1];
57
- var _useState9 = (0, _react.useState)(null),
58
63
  _useState0 = _slicedToArray(_useState9, 2),
59
- greenFrame = _useState0[0],
60
- setGreenFrame = _useState0[1];
64
+ cameraWrapperLayout = _useState0[0],
65
+ setCameraWrapperLayout = _useState0[1];
66
+ var _useState1 = (0, _react.useState)(null),
67
+ _useState10 = _slicedToArray(_useState1, 2),
68
+ greenFrame = _useState10[0],
69
+ setGreenFrame = _useState10[1];
61
70
  (0, _react.useEffect)(function () {
62
71
  _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
63
72
  var _yield$Camera$request, status;
@@ -249,7 +258,9 @@ function CustomCamera(_ref) {
249
258
  return /*#__PURE__*/_react["default"].createElement(_reactNative.SafeAreaView, {
250
259
  style: styles.outerContainer
251
260
  }, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
252
- style: styles.cameraWrapper,
261
+ style: [styles.cameraWrapper, {
262
+ width: cameraPreviewWidth
263
+ }],
253
264
  ref: cameraWrapperRef,
254
265
  onLayout: function onLayout(e) {
255
266
  var layout = e.nativeEvent.layout;
@@ -262,6 +273,7 @@ function CustomCamera(_ref) {
262
273
  console.log("Camera wrapper layout updated:", layout);
263
274
  }
264
275
  }, /*#__PURE__*/_react["default"].createElement(_expoCamera.CameraView, {
276
+ key: cameraViewMountKey,
265
277
  style: styles.camera,
266
278
  facing: "back",
267
279
  ref: cameraRef,
@@ -310,7 +322,6 @@ var styles = _reactNative.StyleSheet.create({
310
322
  alignItems: 'center'
311
323
  },
312
324
  cameraWrapper: {
313
- width: width,
314
325
  aspectRatio: 9 / 16,
315
326
  borderRadius: 30,
316
327
  overflow: 'hidden',
@@ -36,6 +36,9 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
36
36
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
37
37
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
38
38
  var PRIMARY_GREEN = '#198754';
39
+
40
+ // Max width for crop preview on large screens (tablets) - must match CustomCamera.js
41
+ var CAMERA_PREVIEW_MAX_WIDTH = 500;
39
42
  var ImageCropper = function ImageCropper(_ref) {
40
43
  var _cameraFrameData$curr3;
41
44
  var onConfirm = _ref.onConfirm,
@@ -43,6 +46,9 @@ var ImageCropper = function ImageCropper(_ref) {
43
46
  initialImage = _ref.initialImage,
44
47
  addheight = _ref.addheight,
45
48
  rotationLabel = _ref.rotationLabel;
49
+ var _useWindowDimensions = (0, _reactNative.useWindowDimensions)(),
50
+ windowWidth = _useWindowDimensions.width;
51
+ var cameraPreviewWidth = Math.min(windowWidth, CAMERA_PREVIEW_MAX_WIDTH);
46
52
  var _useState = (0, _react.useState)(null),
47
53
  _useState2 = _slicedToArray(_useState, 2),
48
54
  image = _useState2[0],
@@ -247,6 +253,7 @@ var ImageCropper = function ImageCropper(_ref) {
247
253
  var rotationInProgressRef = (0, _react.useRef)(false); // block duplicate taps immediately
248
254
  var lastValidPosition = (0, _react.useRef)(null);
249
255
  var insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
256
+ var openCameraTimeoutRef = (0, _react.useRef)(null);
250
257
 
251
258
  // ✅ NEW ARCH: mobile does NOT export the final crop.
252
259
 
@@ -255,8 +262,17 @@ var ImageCropper = function ImageCropper(_ref) {
255
262
  var enableRotation = true; // rotation resets crop box so it is re-initialized on rotated image.
256
263
 
257
264
  (0, _react.useEffect)(function () {
265
+ // If a previous open was scheduled, cancel it.
266
+ if (openCameraTimeoutRef.current) {
267
+ clearTimeout(openCameraTimeoutRef.current);
268
+ openCameraTimeoutRef.current = null;
269
+ }
258
270
  if (openCameraFirst) {
259
- setShowCustomCamera(true);
271
+ // Delay opening the camera so the prior session can fully release.
272
+ openCameraTimeoutRef.current = setTimeout(function () {
273
+ setShowCustomCamera(true);
274
+ openCameraTimeoutRef.current = null;
275
+ }, 800);
260
276
  } else if (initialImage) {
261
277
  setImage(initialImage);
262
278
  sourceImageUri.current = initialImage;
@@ -270,6 +286,12 @@ var ImageCropper = function ImageCropper(_ref) {
270
286
  hasInitializedCropBox.current = false;
271
287
  imageSource.current = null;
272
288
  }
289
+ return function () {
290
+ if (openCameraTimeoutRef.current) {
291
+ clearTimeout(openCameraTimeoutRef.current);
292
+ openCameraTimeoutRef.current = null;
293
+ }
294
+ };
273
295
  }, [openCameraFirst, initialImage]);
274
296
 
275
297
  // ✅ REFACTORISATION : Stocker uniquement les dimensions originales (pas de calcul théorique)
@@ -1322,17 +1344,9 @@ var ImageCropper = function ImageCropper(_ref) {
1322
1344
  return setShowCustomCamera(false);
1323
1345
  }
1324
1346
  }) : /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1325
- style: {
1326
- width: _reactNative.Dimensions.get('window').width,
1327
- aspectRatio: 9 / 16,
1328
- borderRadius: 30,
1329
- overflow: 'hidden',
1330
- alignItems: 'center',
1331
- justifyContent: 'center',
1332
- position: 'relative',
1333
- backgroundColor: 'black',
1334
- marginBottom: 0 // ✅ Les boutons sont maintenant en position absolue en bas
1335
- },
1347
+ style: [_ImageCropperStyles["default"].commonWrapper, {
1348
+ width: cameraPreviewWidth
1349
+ }],
1336
1350
  ref: commonWrapperRef,
1337
1351
  onLayout: onCommonWrapperLayout
1338
1352
  }, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
@@ -1413,8 +1427,8 @@ var ImageCropper = function ImageCropper(_ref) {
1413
1427
  pointerEvents: "none"
1414
1428
  }, function () {
1415
1429
  // ✅ Use wrapper dimensions for SVG path (wrapper coordinates)
1416
- var wrapperWidth = commonWrapperLayout.current.width || _reactNative.Dimensions.get('window').width;
1417
- var wrapperHeight = commonWrapperLayout.current.height || _reactNative.Dimensions.get('window').width * 16 / 9;
1430
+ var wrapperWidth = commonWrapperLayout.current.width || cameraPreviewWidth;
1431
+ var wrapperHeight = commonWrapperLayout.current.height || cameraPreviewWidth * 16 / 9;
1418
1432
  return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
1419
1433
  d: "M 0 0 H ".concat(wrapperWidth, " V ").concat(wrapperHeight, " H 0 Z ").concat(createPath()),
1420
1434
  fill: showResult ? 'white' : 'rgba(0, 0, 0, 0.8)',
@@ -21,6 +21,17 @@ var styles = _reactNative.StyleSheet.create({
21
21
  // ✅ Start from top, allow content to flow down
22
22
  backgroundColor: DEEP_BLACK
23
23
  },
24
+ // Wrapper for crop preview (9/16, same as CustomCamera); width set dynamically for tablet
25
+ commonWrapper: {
26
+ aspectRatio: 9 / 16,
27
+ borderRadius: 30,
28
+ overflow: 'hidden',
29
+ alignItems: 'center',
30
+ justifyContent: 'center',
31
+ position: 'relative',
32
+ backgroundColor: 'black',
33
+ marginBottom: 0
34
+ },
24
35
  buttonContainer: {
25
36
  position: 'absolute',
26
37
  bottom: 50,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-expo-cropper",
3
- "version": "1.2.45",
3
+ "version": "1.2.47",
4
4
  "description": "Recadrage polygonal d'images.",
5
5
  "main": "index.js",
6
6
  "author": "PCS AGRI",
@@ -22,6 +22,8 @@ export default function CustomCamera({ onPhotoCaptured, onFrameCalculated }) {
22
22
  const [isReady, setIsReady] = useState(false);
23
23
  const [loadingBeforeCapture, setLoadingBeforeCapture] = useState(false);
24
24
  const [hasPermission, setHasPermission] = useState(null);
25
+ // Force a fresh native CameraView instance per "open" to avoid stale/cached preview.
26
+ const [cameraViewMountKey] = useState(() => `${Date.now()}-${Math.random().toString(16).slice(2)}`);
25
27
  const cameraRef = useRef(null);
26
28
  const cameraWrapperRef = useRef(null);
27
29
  const insets = useSafeAreaInsets();
@@ -213,6 +215,7 @@ export default function CustomCamera({ onPhotoCaptured, onFrameCalculated }) {
213
215
  }}
214
216
  >
215
217
  <CameraView
218
+ key={cameraViewMountKey}
216
219
  style={styles.camera}
217
220
  facing="back"
218
221
  ref={cameraRef}
@@ -141,6 +141,7 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage, addheight, rot
141
141
  const rotationInProgressRef = useRef(false); // block duplicate taps immediately
142
142
  const lastValidPosition = useRef(null);
143
143
  const insets = useSafeAreaInsets();
144
+ const openCameraTimeoutRef = useRef(null);
144
145
 
145
146
  // ✅ NEW ARCH: mobile does NOT export the final crop.
146
147
 
@@ -153,22 +154,39 @@ const ImageCropper = ({ onConfirm, openCameraFirst, initialImage, addheight, rot
153
154
 
154
155
 
155
156
  useEffect(() => {
156
- if (openCameraFirst) {
157
- setShowCustomCamera(true);
158
- } else if (initialImage) {
159
- setImage(initialImage);
160
- sourceImageUri.current = initialImage;
161
- // ✅ CRITICAL: Reset points when loading a new image from gallery
162
- // This ensures the crop box will be automatically initialized
163
- setPoints([]);
164
- rotationAngle.current = 0;
165
- // Clear camera frame data for gallery images
166
- cameraFrameData.current = null;
167
- // ✅ CRITICAL: Reset initialization guard for new image
168
- hasInitializedCropBox.current = false;
169
- imageSource.current = null;
170
- }
171
- }, [openCameraFirst, initialImage]);
157
+ // If a previous open was scheduled, cancel it.
158
+ if (openCameraTimeoutRef.current) {
159
+ clearTimeout(openCameraTimeoutRef.current);
160
+ openCameraTimeoutRef.current = null;
161
+ }
162
+
163
+ if (openCameraFirst) {
164
+ // Delay opening the camera so the prior session can fully release.
165
+ openCameraTimeoutRef.current = setTimeout(() => {
166
+ setShowCustomCamera(true);
167
+ openCameraTimeoutRef.current = null;
168
+ }, 800);
169
+ } else if (initialImage) {
170
+ setImage(initialImage);
171
+ sourceImageUri.current = initialImage;
172
+ // CRITICAL: Reset points when loading a new image from gallery
173
+ // This ensures the crop box will be automatically initialized
174
+ setPoints([]);
175
+ rotationAngle.current = 0;
176
+ // Clear camera frame data for gallery images
177
+ cameraFrameData.current = null;
178
+ // ✅ CRITICAL: Reset initialization guard for new image
179
+ hasInitializedCropBox.current = false;
180
+ imageSource.current = null;
181
+ }
182
+
183
+ return () => {
184
+ if (openCameraTimeoutRef.current) {
185
+ clearTimeout(openCameraTimeoutRef.current);
186
+ openCameraTimeoutRef.current = null;
187
+ }
188
+ };
189
+ }, [openCameraFirst, initialImage]);
172
190
 
173
191
 
174
192
  // ✅ REFACTORISATION : Stocker uniquement les dimensions originales (pas de calcul théorique)