react-native-expo-cropper 1.2.51 → 1.2.53

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.
@@ -29,7 +29,9 @@ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
29
29
  var CAMERA_PREVIEW_MAX_WIDTH = 500;
30
30
  function CustomCamera(_ref) {
31
31
  var onPhotoCaptured = _ref.onPhotoCaptured,
32
- onFrameCalculated = _ref.onFrameCalculated;
32
+ onFrameCalculated = _ref.onFrameCalculated,
33
+ _ref$instructionText = _ref.instructionText,
34
+ instructionText = _ref$instructionText === void 0 ? 'Place the tray inside the green box' : _ref$instructionText;
33
35
  var _useWindowDimensions = (0, _reactNative.useWindowDimensions)(),
34
36
  windowWidth = _useWindowDimensions.width;
35
37
  var cameraPreviewWidth = Math.min(windowWidth, CAMERA_PREVIEW_MAX_WIDTH);
@@ -318,7 +320,9 @@ function CustomCamera(_ref) {
318
320
  });
319
321
  console.log("Camera wrapper layout updated:", layout);
320
322
  }
321
- }, /*#__PURE__*/_react["default"].createElement(_expoCamera.CameraView, {
323
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
324
+ style: styles.cameraInstruction
325
+ }, instructionText), /*#__PURE__*/_react["default"].createElement(_expoCamera.CameraView, {
322
326
  style: styles.camera,
323
327
  facing: "back",
324
328
  ref: cameraRef,
@@ -397,6 +401,25 @@ var styles = _reactNative.StyleSheet.create({
397
401
  justifyContent: 'center',
398
402
  position: 'relative'
399
403
  },
404
+ cameraInstruction: {
405
+ position: 'absolute',
406
+ top: 8,
407
+ left: 12,
408
+ right: 12,
409
+ textAlign: 'center',
410
+ color: GLOW_WHITE,
411
+ fontSize: 14,
412
+ fontWeight: '600',
413
+ zIndex: 10,
414
+ textShadowColor: 'rgba(0, 0, 0, 0.7)',
415
+ textShadowOffset: {
416
+ width: 0,
417
+ height: 1
418
+ },
419
+ textShadowRadius: 2,
420
+ // Allow camera/scan frame to still receive touches
421
+ pointerEvents: 'none'
422
+ },
400
423
  camera: _objectSpread({}, _reactNative.StyleSheet.absoluteFillObject),
401
424
  scanFrame: {
402
425
  position: 'absolute',
@@ -50,7 +50,9 @@ var ImageCropper = function ImageCropper(_ref) {
50
50
  _ref$confirmText = _ref.confirmText,
51
51
  confirmText = _ref$confirmText === void 0 ? 'Confirm' : _ref$confirmText,
52
52
  _ref$resetText = _ref.resetText,
53
- resetText = _ref$resetText === void 0 ? 'Reset' : _ref$resetText;
53
+ resetText = _ref$resetText === void 0 ? 'Reset' : _ref$resetText,
54
+ _ref$cameraInstructio = _ref.cameraInstructionText,
55
+ cameraInstructionText = _ref$cameraInstructio === void 0 ? 'Place the tray inside the green box' : _ref$cameraInstructio;
54
56
  var _useWindowDimensions = (0, _reactNative.useWindowDimensions)(),
55
57
  windowWidth = _useWindowDimensions.width,
56
58
  windowHeight = _useWindowDimensions.height;
@@ -911,8 +913,10 @@ var ImageCropper = function ImageCropper(_ref) {
911
913
  // ✅ STRICT BOUNDS: visible image area (wrapper-relative) for lastValidPosition
912
914
  var strictMinX = contentRect.x;
913
915
  var strictMaxX = contentRect.x + contentRect.width;
916
+ // Tiny safety margin at bottom (2px) so point can almost reach bottom edge
917
+ var bottomSafePadding = 2;
914
918
  var strictMinY = contentRect.y;
915
- var strictMaxY = contentRect.y + contentRect.height;
919
+ var strictMaxY = contentRect.y + contentRect.height - bottomSafePadding;
916
920
 
917
921
  // ✅ DRAG BOUNDS: keep points strictly on the visible picture (no going outside image)
918
922
  var overshootMinX = strictMinX;
@@ -924,7 +928,15 @@ var ImageCropper = function ImageCropper(_ref) {
924
928
  var dragX = Math.max(overshootMinX, Math.min(newX, overshootMaxX));
925
929
  var dragY = Math.max(overshootMinY, Math.min(newY, overshootMaxY));
926
930
 
927
- // ✅ UPDATE POINT: Use drag bounds (overshoot) - allows visual freedom
931
+ // ✅ Bottom-band "stickiness": if the point is already in the last few pixels
932
+ // near the bottom, ignore any move that would suddenly pull it back up.
933
+ var bottomBandThreshold = strictMaxY - 3; // last ~3px of allowed area
934
+ var prevPoint = points[selectedPointIndex.current];
935
+ if (prevPoint && prevPoint.y >= bottomBandThreshold && dragY < prevPoint.y) {
936
+ dragY = prevPoint.y;
937
+ }
938
+
939
+ // ✅ UPDATE POINT: Use (possibly adjusted) dragY
928
940
  var updatedPoint = {
929
941
  x: dragX,
930
942
  y: dragY
@@ -1228,6 +1240,7 @@ var ImageCropper = function ImageCropper(_ref) {
1228
1240
  return /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1229
1241
  style: _ImageCropperStyles["default"].container
1230
1242
  }, showCustomCamera ? /*#__PURE__*/_react["default"].createElement(_CustomCamera["default"], {
1243
+ instructionText: cameraInstructionText,
1231
1244
  onPhotoCaptured: function onPhotoCaptured(uri, frameData) {
1232
1245
  // ✅ CRITICAL FIX: Store green frame coordinates for coordinate conversion
1233
1246
  if (frameData && frameData.greenFrame) {
@@ -1440,8 +1453,11 @@ var ImageCropper = function ImageCropper(_ref) {
1440
1453
  onPress: handleReset
1441
1454
  }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
1442
1455
  style: [_ImageCropperStyles["default"].buttonText, {
1443
- fontSize: Math.max(14, Math.round(18 * responsiveScale))
1444
- }]
1456
+ // Smaller font for longer labels so they stay on one line
1457
+ fontSize: Math.max(12, Math.round((resetText && resetText.length > 10 ? 14 : 18) * responsiveScale))
1458
+ }],
1459
+ numberOfLines: 1,
1460
+ ellipsizeMode: "tail"
1445
1461
  }, resetText)), /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1446
1462
  onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
1447
1463
  var _cameraFrameData$curr4, actualImageWidth, actualImageHeight, isCoverMode, captured, layout, contentRect, displayedWidth, displayedHeight, scale, coverOffsetX, coverOffsetY, scaledWidth, scaledHeight, originalUri, cropMeta, imagePoints, minX, minY, maxX, maxY, cropX, cropY, cropEndX, cropEndY, cropWidth, cropHeight, bbox, polygon, ext, safeExt, name, _t2;
@@ -1616,8 +1632,11 @@ var ImageCropper = function ImageCropper(_ref) {
1616
1632
  }]
1617
1633
  }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
1618
1634
  style: [_ImageCropperStyles["default"].buttonText, {
1619
- fontSize: Math.max(14, Math.round(18 * responsiveScale))
1620
- }]
1635
+ // Smaller font for longer labels so they stay on one line
1636
+ fontSize: Math.max(12, Math.round((confirmText && confirmText.length > 10 ? 14 : 18) * responsiveScale))
1637
+ }],
1638
+ numberOfLines: 1,
1639
+ ellipsizeMode: "tail"
1621
1640
  }, confirmText))), !showResult && !image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1622
1641
  style: _ImageCropperStyles["default"].centerButtonsContainer
1623
1642
  }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-expo-cropper",
3
- "version": "1.2.51",
3
+ "version": "1.2.53",
4
4
  "description": "Recadrage polygonal d'images.",
5
5
  "main": "index.js",
6
6
  "author": "PCS AGRI",
@@ -16,7 +16,11 @@ import { Camera, CameraView } from 'expo-camera';
16
16
  // Max width for camera preview on large screens (tablets) so it doesn't stretch full width
17
17
  const CAMERA_PREVIEW_MAX_WIDTH = 500;
18
18
 
19
- export default function CustomCamera({ onPhotoCaptured, onFrameCalculated }) {
19
+ export default function CustomCamera({
20
+ onPhotoCaptured,
21
+ onFrameCalculated,
22
+ instructionText = 'Place the tray inside the green box',
23
+ }) {
20
24
  const { width: windowWidth } = useWindowDimensions();
21
25
  const cameraPreviewWidth = Math.min(windowWidth, CAMERA_PREVIEW_MAX_WIDTH);
22
26
  const [isReady, setIsReady] = useState(false);
@@ -250,6 +254,9 @@ export default function CustomCamera({ onPhotoCaptured, onFrameCalculated }) {
250
254
  console.log("Camera wrapper layout updated:", layout);
251
255
  }}
252
256
  >
257
+ <Text style={styles.cameraInstruction}>
258
+ {instructionText}
259
+ </Text>
253
260
  <CameraView
254
261
  style={styles.camera}
255
262
  facing="back"
@@ -345,6 +352,22 @@ const styles = StyleSheet.create({
345
352
  justifyContent: 'center',
346
353
  position: 'relative',
347
354
  },
355
+ cameraInstruction: {
356
+ position: 'absolute',
357
+ top: 8,
358
+ left: 12,
359
+ right: 12,
360
+ textAlign: 'center',
361
+ color: GLOW_WHITE,
362
+ fontSize: 14,
363
+ fontWeight: '600',
364
+ zIndex: 10,
365
+ textShadowColor: 'rgba(0, 0, 0, 0.7)',
366
+ textShadowOffset: { width: 0, height: 1 },
367
+ textShadowRadius: 2,
368
+ // Allow camera/scan frame to still receive touches
369
+ pointerEvents: 'none',
370
+ },
348
371
  camera: {
349
372
  ...StyleSheet.absoluteFillObject,
350
373
  },
@@ -22,6 +22,7 @@ const ImageCropper = ({
22
22
  onCancel,
23
23
  confirmText = 'Confirm',
24
24
  resetText = 'Reset',
25
+ cameraInstructionText = 'Place the tray inside the green box',
25
26
  }) => {
26
27
  const { width: windowWidth, height: windowHeight } = useWindowDimensions();
27
28
 
@@ -772,8 +773,10 @@ const ImageCropper = ({
772
773
  // ✅ STRICT BOUNDS: visible image area (wrapper-relative) for lastValidPosition
773
774
  const strictMinX = contentRect.x;
774
775
  const strictMaxX = contentRect.x + contentRect.width;
776
+ // Tiny safety margin at bottom (2px) so point can almost reach bottom edge
777
+ const bottomSafePadding = 2;
775
778
  const strictMinY = contentRect.y;
776
- const strictMaxY = contentRect.y + contentRect.height;
779
+ const strictMaxY = contentRect.y + contentRect.height - bottomSafePadding;
777
780
 
778
781
  // ✅ DRAG BOUNDS: keep points strictly on the visible picture (no going outside image)
779
782
  const overshootMinX = strictMinX;
@@ -783,9 +786,17 @@ const ImageCropper = ({
783
786
 
784
787
  // ✅ Clamp to visible image so points always stay on the picture
785
788
  const dragX = Math.max(overshootMinX, Math.min(newX, overshootMaxX));
786
- const dragY = Math.max(overshootMinY, Math.min(newY, overshootMaxY));
789
+ let dragY = Math.max(overshootMinY, Math.min(newY, overshootMaxY));
787
790
 
788
- // ✅ UPDATE POINT: Use drag bounds (overshoot) - allows visual freedom
791
+ // ✅ Bottom-band "stickiness": if the point is already in the last few pixels
792
+ // near the bottom, ignore any move that would suddenly pull it back up.
793
+ const bottomBandThreshold = strictMaxY - 3; // last ~3px of allowed area
794
+ const prevPoint = points[selectedPointIndex.current];
795
+ if (prevPoint && prevPoint.y >= bottomBandThreshold && dragY < prevPoint.y) {
796
+ dragY = prevPoint.y;
797
+ }
798
+
799
+ // ✅ UPDATE POINT: Use (possibly adjusted) dragY
789
800
  const updatedPoint = { x: dragX, y: dragY };
790
801
 
791
802
  // ✅ CRITICAL: Detect if point is AT overshoot boundary (not just clamped)
@@ -1053,6 +1064,7 @@ const rotatePreviewImage = async (degrees) => {
1053
1064
 
1054
1065
  {showCustomCamera ? (
1055
1066
  <CustomCamera
1067
+ instructionText={cameraInstructionText}
1056
1068
  onPhotoCaptured={(uri, frameData) => {
1057
1069
  // ✅ CRITICAL FIX: Store green frame coordinates for coordinate conversion
1058
1070
  if (frameData && frameData.greenFrame) {
@@ -1271,8 +1283,19 @@ const rotatePreviewImage = async (degrees) => {
1271
1283
  <Text
1272
1284
  style={[
1273
1285
  styles.buttonText,
1274
- { fontSize: Math.max(14, Math.round(18 * responsiveScale)) },
1286
+ {
1287
+ // Smaller font for longer labels so they stay on one line
1288
+ fontSize: Math.max(
1289
+ 12,
1290
+ Math.round(
1291
+ (resetText && resetText.length > 10 ? 14 : 18) *
1292
+ responsiveScale
1293
+ )
1294
+ ),
1295
+ },
1275
1296
  ]}
1297
+ numberOfLines={1}
1298
+ ellipsizeMode="tail"
1276
1299
  >
1277
1300
  {resetText}
1278
1301
  </Text>
@@ -1421,8 +1444,19 @@ const rotatePreviewImage = async (degrees) => {
1421
1444
  <Text
1422
1445
  style={[
1423
1446
  styles.buttonText,
1424
- { fontSize: Math.max(14, Math.round(18 * responsiveScale)) },
1447
+ {
1448
+ // Smaller font for longer labels so they stay on one line
1449
+ fontSize: Math.max(
1450
+ 12,
1451
+ Math.round(
1452
+ (confirmText && confirmText.length > 10 ? 14 : 18) *
1453
+ responsiveScale
1454
+ )
1455
+ ),
1456
+ },
1425
1457
  ]}
1458
+ numberOfLines={1}
1459
+ ellipsizeMode="tail"
1426
1460
  >
1427
1461
  {confirmText}
1428
1462
  </Text>