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.
- package/dist/CustomCamera.js +25 -2
- package/dist/ImageCropper.js +26 -7
- package/package.json +1 -1
- package/src/CustomCamera.js +24 -1
- package/src/ImageCropper.js +39 -5
package/dist/CustomCamera.js
CHANGED
|
@@ -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(
|
|
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',
|
package/dist/ImageCropper.js
CHANGED
|
@@ -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
|
-
// ✅
|
|
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
|
-
|
|
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
|
-
|
|
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
package/src/CustomCamera.js
CHANGED
|
@@ -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({
|
|
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
|
},
|
package/src/ImageCropper.js
CHANGED
|
@@ -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
|
-
|
|
789
|
+
let dragY = Math.max(overshootMinY, Math.min(newY, overshootMaxY));
|
|
787
790
|
|
|
788
|
-
// ✅
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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>
|