react-native-expo-cropper 1.2.37 → 1.2.39

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.
@@ -9,10 +9,8 @@ var _ImageCropperStyles = _interopRequireDefault(require("./ImageCropperStyles")
9
9
  var _react = _interopRequireWildcard(require("react"));
10
10
  var _reactNative = require("react-native");
11
11
  var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
12
- var _reactNativeViewShot = require("react-native-view-shot");
13
12
  var _CustomCamera = _interopRequireDefault(require("./CustomCamera"));
14
13
  var ImageManipulator = _interopRequireWildcard(require("expo-image-manipulator"));
15
- var FileSystem = _interopRequireWildcard(require("expo-file-system"));
16
14
  var _vectorIcons = require("@expo/vector-icons");
17
15
  var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
18
16
  var _ImageMaskProcessor = require("./ImageMaskProcessor");
@@ -22,15 +20,15 @@ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present,
22
20
  function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
23
21
  function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
24
22
  function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
23
+ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
24
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
25
+ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
26
+ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
25
27
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
26
28
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
27
29
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
28
30
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
29
31
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
30
- function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
31
- function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
32
- function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
33
- function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
34
32
  function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
35
33
  function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
36
34
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
@@ -38,6 +36,7 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
38
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; } }
39
37
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
40
38
  var ImageCropper = function ImageCropper(_ref) {
39
+ var _cameraFrameData$curr3;
41
40
  var onConfirm = _ref.onConfirm,
42
41
  openCameraFirst = _ref.openCameraFirst,
43
42
  initialImage = _ref.initialImage,
@@ -63,6 +62,16 @@ var ImageCropper = function ImageCropper(_ref) {
63
62
  var sourceImageUri = (0, _react.useRef)(null); // keep original image URI (full-res) for upload
64
63
  var cameraFrameData = (0, _react.useRef)(null); // ✅ Store green frame coordinates from camera
65
64
 
65
+ // ✅ RÉFÉRENTIEL UNIQUE : Wrapper commun 9/16 (identique à CustomCamera)
66
+ // Ce wrapper est utilisé dans CustomCamera ET ImageCropper pour garantir pixel-perfect matching
67
+ var commonWrapperRef = (0, _react.useRef)(null);
68
+ var commonWrapperLayout = (0, _react.useRef)({
69
+ x: 0,
70
+ y: 0,
71
+ width: 0,
72
+ height: 0
73
+ });
74
+
66
75
  // ✅ REFACTORISATION : Séparation claire entre dimensions originales et affichage
67
76
  // Dimensions réelles de l'image originale (pixels)
68
77
  var originalImageDimensions = (0, _react.useRef)({
@@ -83,70 +92,121 @@ var ImageCropper = function ImageCropper(_ref) {
83
92
  width: 0,
84
93
  height: 0
85
94
  });
86
- // Rectangle réel de l'image affichée (quand resizeMode='contain') à l'intérieur du conteneur
87
- // Sert à rendre la conversion coordonnées écran -> pixels image réellement pixel-perfect.
88
- var displayedContentRect = (0, _react.useRef)({
95
+ // ✅ imageDisplayRect : Rectangle réel de l'image affichée (quand resizeMode='contain') à l'intérieur du wrapper commun
96
+ // C'est la zone l'image est réellement visible (avec letterboxing si nécessaire)
97
+ // Les points de crop DOIVENT rester dans cette zone pour éviter de cropper hors de l'image
98
+ var imageDisplayRect = (0, _react.useRef)({
89
99
  x: 0,
90
100
  y: 0,
91
101
  width: 0,
92
102
  height: 0
93
103
  });
94
- var updateDisplayedContentRect = function updateDisplayedContentRect(layoutWidth, layoutHeight) {
104
+
105
+ // ✅ COMPATIBILITÉ : displayedContentRect reste pour le code existant, mais pointe vers imageDisplayRect
106
+ var displayedContentRect = imageDisplayRect;
107
+
108
+ // ✅ RÉFÉRENTIEL UNIQUE : Calculer imageDisplayRect à l'intérieur du wrapper commun
109
+ // - CAMERA: use "cover" mode → image fills wrapper, imageDisplayRect = full wrapper (same as preview)
110
+ // - GALLERY: use "contain" mode → imageDisplayRect = letterboxed area
111
+ var updateImageDisplayRect = function updateImageDisplayRect(wrapperWidth, wrapperHeight) {
95
112
  var iw = originalImageDimensions.current.width;
96
113
  var ih = originalImageDimensions.current.height;
97
- console.log("🔄 updateDisplayedContentRect called:", {
114
+
115
+ // ✅ CAMERA IMAGE: Use full wrapper so green frame and white frame show same content
116
+ if (cameraFrameData.current && cameraFrameData.current.greenFrame && wrapperWidth > 0 && wrapperHeight > 0) {
117
+ imageDisplayRect.current = {
118
+ x: 0,
119
+ y: 0,
120
+ width: wrapperWidth,
121
+ height: wrapperHeight
122
+ };
123
+ console.log("✅ Image display rect (COVER mode for camera - full wrapper):", imageDisplayRect.current);
124
+ return;
125
+ }
126
+ console.log("🔄 updateImageDisplayRect called:", {
98
127
  originalDimensions: {
99
128
  width: iw,
100
129
  height: ih
101
130
  },
102
- layoutDimensions: {
103
- width: layoutWidth,
104
- height: layoutHeight
131
+ wrapperDimensions: {
132
+ width: wrapperWidth,
133
+ height: wrapperHeight
105
134
  }
106
135
  });
107
- if (iw > 0 && ih > 0 && layoutWidth > 0 && layoutHeight > 0) {
108
- var scale = Math.min(layoutWidth / iw, layoutHeight / ih);
109
- var contentW = iw * scale;
110
- var contentH = ih * scale;
111
- var offsetX = (layoutWidth - contentW) / 2;
112
- var offsetY = (layoutHeight - contentH) / 2;
113
- displayedContentRect.current = {
136
+ if (iw > 0 && ih > 0 && wrapperWidth > 0 && wrapperHeight > 0) {
137
+ // Calculer comment l'image s'affiche en "contain" dans le wrapper (gallery)
138
+ var scale = Math.min(wrapperWidth / iw, wrapperHeight / ih);
139
+ var imageDisplayWidth = iw * scale;
140
+ var imageDisplayHeight = ih * scale;
141
+ var offsetX = (wrapperWidth - imageDisplayWidth) / 2;
142
+ var offsetY = (wrapperHeight - imageDisplayHeight) / 2;
143
+ imageDisplayRect.current = {
114
144
  x: offsetX,
115
145
  y: offsetY,
116
- width: contentW,
117
- height: contentH
146
+ width: imageDisplayWidth,
147
+ height: imageDisplayHeight
118
148
  };
119
- console.log("✅ Displayed content rect (contain) calculated:", displayedContentRect.current);
149
+ console.log("✅ Image display rect (contain in wrapper) calculated:", {
150
+ wrapper: {
151
+ width: wrapperWidth,
152
+ height: wrapperHeight
153
+ },
154
+ imageDisplayRect: imageDisplayRect.current,
155
+ scale: scale.toFixed(4)
156
+ });
120
157
  return;
121
158
  }
122
-
123
- // FALLBACK: If original dimensions not available yet, use layout as temporary measure
124
- if (layoutWidth > 0 && layoutHeight > 0) {
125
- displayedContentRect.current = {
159
+ if (wrapperWidth > 0 && wrapperHeight > 0) {
160
+ imageDisplayRect.current = {
126
161
  x: 0,
127
162
  y: 0,
128
- width: layoutWidth,
129
- height: layoutHeight
163
+ width: wrapperWidth,
164
+ height: wrapperHeight
130
165
  };
131
- console.log("⚠️ Using layout dimensions as fallback (original dimensions not available yet):", displayedContentRect.current);
166
+ console.log("⚠️ Using wrapper dimensions as fallback (original dimensions not available yet):", imageDisplayRect.current);
132
167
  } else {
133
- displayedContentRect.current = {
168
+ imageDisplayRect.current = {
134
169
  x: 0,
135
170
  y: 0,
136
171
  width: 0,
137
172
  height: 0
138
173
  };
139
- console.warn("❌ Cannot calculate displayedContentRect: missing dimensions");
174
+ console.warn("❌ Cannot calculate imageDisplayRect: missing dimensions");
140
175
  }
141
176
  };
177
+
178
+ // ✅ COMPATIBILITÉ : Alias pour le code existant
179
+ var updateDisplayedContentRect = updateImageDisplayRect;
142
180
  var selectedPointIndex = (0, _react.useRef)(null);
143
181
  var lastTap = (0, _react.useRef)(null);
144
182
 
183
+ // ✅ CRITICAL: Guard to prevent double crop box initialization
184
+ // This ensures crop box is initialized only once, especially for camera images
185
+ var hasInitializedCropBox = (0, _react.useRef)(false);
186
+ var imageSource = (0, _react.useRef)(null); // 'camera' | 'gallery' | null
187
+
145
188
  // ✅ FREE DRAG: Store initial touch position and point position for delta-based movement
146
189
  var initialTouchPosition = (0, _react.useRef)(null); // { x, y } - initial touch position when drag starts
147
190
  var initialPointPosition = (0, _react.useRef)(null); // { x, y } - initial point position when drag starts
148
191
  var lastTouchPosition = (0, _react.useRef)(null); // { x, y } - last touch position for incremental delta calculation
149
192
 
193
+ // ✅ CRITICAL: dragBase stores the VISUAL position (can be overshoot) during drag
194
+ // This ensures smooth continuous drag without "dead zones" at boundaries
195
+ // dragBase is updated with applied position (overshoot) after each movement
196
+ // and is used as base for next delta calculation
197
+ var dragBase = (0, _react.useRef)(null); // { x, y } - visual position during drag (can be overshoot)
198
+
199
+ // ✅ NEW APPROACH: touchOffset stores the initial offset between point and touch
200
+ // This eliminates delta accumulation issues and "dead zones"
201
+ // Once set at drag start, it remains constant throughout the drag
202
+ var touchOffset = (0, _react.useRef)(null); // { x, y } - offset = pointPosition - touchPosition
203
+
204
+ // ✅ Track if point was clamped in previous frame (to detect transition)
205
+ var wasClampedLastFrame = (0, _react.useRef)({
206
+ x: false,
207
+ y: false
208
+ });
209
+
150
210
  // Angle de rotation accumulé (pour éviter les rotations multiples)
151
211
  var rotationAngle = (0, _react.useRef)(0);
152
212
 
@@ -186,7 +246,7 @@ var ImageCropper = function ImageCropper(_ref) {
186
246
  var insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
187
247
 
188
248
  // ✅ NEW ARCH: mobile does NOT export the final crop.
189
- // We only compute crop metadata (bbox + polygon) and upload the ORIGINAL image to backend Python.
249
+
190
250
  // No view-shot / captureRef / bitmap masking on device.
191
251
  var enableMask = false;
192
252
  var enableRotation = false; // rotation would require careful coord transforms; keep off for pixel-perfect pipeline.
@@ -203,27 +263,32 @@ var ImageCropper = function ImageCropper(_ref) {
203
263
  rotationAngle.current = 0;
204
264
  // Clear camera frame data for gallery images
205
265
  cameraFrameData.current = null;
266
+ // ✅ CRITICAL: Reset initialization guard for new image
267
+ hasInitializedCropBox.current = false;
268
+ imageSource.current = null;
206
269
  }
207
270
  }, [openCameraFirst, initialImage]);
208
271
 
209
272
  // ✅ REFACTORISATION : Stocker uniquement les dimensions originales (pas de calcul théorique)
210
- // ✅ CRITICAL FIX: Get dimensions and account for potential scale factor differences
211
- // On Android, Image.getSize() returns physical pixels, but the image sent to backend
212
- // might be at a different scale. We'll send original dimensions to backend for adjustment.
273
+ // ✅ CRITICAL FIX: Single source of truth for image dimensions
274
+
213
275
  (0, _react.useEffect)(function () {
214
276
  if (!image) {
215
277
  originalImageDimensions.current = {
216
278
  width: 0,
217
279
  height: 0
218
280
  };
281
+ hasInitializedCropBox.current = false;
282
+ imageSource.current = null;
219
283
  return;
220
284
  }
221
285
  if (!sourceImageUri.current) {
222
286
  sourceImageUri.current = image;
223
287
  }
224
288
 
225
- // ✅ CRITICAL FIX: If we have capturedImageSize from camera, use it as source of truth
289
+ // ✅ CRITICAL FIX #1: If we have capturedImageSize from camera, use it as SINGLE SOURCE OF TRUTH
226
290
  // takePictureAsync returns physical dimensions, while Image.getSize() may return EXIF-oriented dimensions
291
+ // DO NOT call Image.getSize() for camera images - it can return swapped dimensions on Android
227
292
  if (cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
228
293
  var _cameraFrameData$curr = cameraFrameData.current.capturedImageSize,
229
294
  capturedWidth = _cameraFrameData$curr.width,
@@ -232,31 +297,88 @@ var ImageCropper = function ImageCropper(_ref) {
232
297
  width: capturedWidth,
233
298
  height: capturedHeight
234
299
  };
235
- console.log("✅ Using captured image dimensions from takePictureAsync:", {
300
+ imageSource.current = 'camera';
301
+ hasInitializedCropBox.current = false; // Reset guard for new camera image
302
+
303
+ console.log("✅ Using captured image dimensions from takePictureAsync (SINGLE SOURCE OF TRUTH):", {
236
304
  width: capturedWidth,
237
305
  height: capturedHeight,
238
- source: 'takePictureAsync'
306
+ source: 'takePictureAsync',
307
+ note: 'Image.getSize() will NOT be called for camera images'
239
308
  });
240
- // ✅ CRITICAL: Use displayedImageLayout dimensions if available, otherwise wait for onImageLayout
241
- var lw = displayedImageLayout.current.width;
242
- var lh = displayedImageLayout.current.height;
243
- if (lw > 0 && lh > 0) {
244
- updateDisplayedContentRect(lw, lh);
245
- // ✅ CRITICAL: Initialize crop box when we have camera frame data and image dimensions
246
- if (points.length === 0) {
309
+
310
+ // CRITICAL: Recalculate imageDisplayRect immediately with camera dimensions
311
+ var wrapper = commonWrapperLayout.current;
312
+ if (wrapper.width > 0 && wrapper.height > 0) {
313
+ updateImageDisplayRect(wrapper.width, wrapper.height);
314
+
315
+ // CRITICAL FIX #2: Initialize crop box immediately when cameraFrameData is available
316
+ // This ensures camera images are initialized from greenFrame BEFORE any other initialization
317
+ if (cameraFrameData.current && cameraFrameData.current.greenFrame && !hasInitializedCropBox.current) {
318
+ console.log("✅ Initializing crop box from cameraFrameData (immediate in useEffect):", {
319
+ hasGreenFrame: !!cameraFrameData.current.greenFrame,
320
+ wrapper: wrapper,
321
+ originalDimensions: originalImageDimensions.current
322
+ });
247
323
  initializeCropBox();
248
324
  }
249
325
  }
326
+ return; // ✅ CRITICAL: Exit early - DO NOT call Image.getSize()
327
+ }
328
+
329
+ // ✅ FALLBACK: Use Image.getSize() ONLY for gallery images (no cameraFrameData)
330
+ // BUT: Check again right before calling to avoid race condition
331
+ if (cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
332
+ console.log("⚠️ cameraFrameData exists, skipping Image.getSize() call");
333
+ return;
334
+ }
335
+
336
+ // ✅ CRITICAL: Also check imageSource - if it's 'camera', don't call Image.getSize()
337
+ if (imageSource.current === 'camera') {
338
+ console.log("⚠️ imageSource is 'camera', skipping Image.getSize() call");
250
339
  return;
251
340
  }
252
341
 
253
- // ✅ FALLBACK: Use Image.getSize() if no captured dimensions available (e.g., from gallery)
342
+ // ✅ CRITICAL: Set imageSource to 'gallery' ONLY if we're sure it's not a camera image
343
+ // Don't set it yet - we'll set it in the callback after verifying
344
+ hasInitializedCropBox.current = false; // Reset guard for new image
345
+
254
346
  _reactNative.Image.getSize(image, function (imgWidth, imgHeight) {
347
+ // ✅ CRITICAL SAFETY #1: Check if cameraFrameData appeared while Image.getSize() was resolving
348
+ // This is the PRIMARY check - cameraFrameData takes precedence
349
+ if (cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
350
+ console.warn("⚠️ Image.getSize() resolved but cameraFrameData exists - IGNORING Image.getSize() result to prevent dimension swap");
351
+ console.warn("⚠️ Camera dimensions (correct):", cameraFrameData.current.capturedImageSize);
352
+ console.warn("⚠️ Image.getSize() dimensions (potentially swapped):", {
353
+ width: imgWidth,
354
+ height: imgHeight
355
+ });
356
+ return; // ✅ CRITICAL: Exit early - do NOT update dimensions or initialize crop box
357
+ }
358
+
359
+ // ✅ CRITICAL SAFETY #2: Check imageSource (should be 'camera' if cameraFrameData was set)
360
+ if (imageSource.current === 'camera') {
361
+ console.warn("⚠️ Image.getSize() resolved but imageSource is 'camera' - IGNORING Image.getSize() result");
362
+ console.warn("⚠️ Image.getSize() dimensions (potentially swapped):", {
363
+ width: imgWidth,
364
+ height: imgHeight
365
+ });
366
+ return; // ✅ CRITICAL: Exit early - do NOT update dimensions or initialize crop box
367
+ }
368
+
369
+ // ✅ CRITICAL SAFETY #3: Check if crop box was already initialized (from camera)
370
+ if (hasInitializedCropBox.current) {
371
+ console.warn("⚠️ Image.getSize() resolved but crop box already initialized - IGNORING result to prevent double initialization");
372
+ return;
373
+ }
374
+
375
+ // ✅ SAFE: This is a gallery image, proceed with Image.getSize() result
376
+ imageSource.current = 'gallery';
255
377
  originalImageDimensions.current = {
256
378
  width: imgWidth,
257
379
  height: imgHeight
258
380
  };
259
- console.log("✅ Image dimensions from Image.getSize():", {
381
+ console.log("✅ Image dimensions from Image.getSize() (gallery image):", {
260
382
  width: imgWidth,
261
383
  height: imgHeight,
262
384
  platform: _reactNative.Platform.OS,
@@ -265,17 +387,18 @@ var ImageCropper = function ImageCropper(_ref) {
265
387
  source: 'Image.getSize()'
266
388
  });
267
389
 
268
- // ✅ IMPORTANT: onImageLayout peut se déclencher avant Image.getSize (race condition).
269
- // Recalculer le contentRect dès qu'on connaît la taille originale, sinon les coords seront décalées.
270
- var lw = displayedImageLayout.current.width;
271
- var lh = displayedImageLayout.current.height;
272
- if (lw > 0 && lh > 0) {
273
- updateDisplayedContentRect(lw, lh);
274
- // CRITICAL: Initialize crop box when we have image dimensions
275
- // - If we have camera frame data, use it to match green frame exactly
276
- // - If no camera frame data (gallery image), initialize with 70% default box
277
- if (points.length === 0) {
390
+ // ✅ RÉFÉRENTIEL UNIQUE : Recalculer imageDisplayRect dans le wrapper commun
391
+ // dès qu'on connaît la taille originale de l'image
392
+ var wrapper = commonWrapperLayout.current;
393
+ if (wrapper.width > 0 && wrapper.height > 0) {
394
+ updateImageDisplayRect(wrapper.width, wrapper.height);
395
+ // ✅ IMPORTANT: pour les images de la galerie (pas de cameraFrameData),
396
+ // initialiser automatiquement le cadre blanc (70% du wrapper) une fois que
397
+ // nous connaissons à la fois le wrapper et les dimensions originales.
398
+ // CRITICAL: Guard against double initialization
399
+ if (!hasInitializedCropBox.current && points.length === 0 && imageSource.current === 'gallery') {
278
400
  initializeCropBox();
401
+ hasInitializedCropBox.current = true;
279
402
  }
280
403
  }
281
404
  }, function (error) {
@@ -283,514 +406,200 @@ var ImageCropper = function ImageCropper(_ref) {
283
406
  });
284
407
  }, [image]);
285
408
 
286
- // CRITICAL FIX: Convert green frame coordinates (camera preview) to captured image coordinates
287
- var convertGreenFrameToImageCoords = function convertGreenFrameToImageCoords(greenFrame, capturedImageSize, displayedImageRect) {
288
- if (!greenFrame || !capturedImageSize) {
289
- console.warn("Cannot convert green frame: missing data");
290
- return null;
291
- }
292
- var frameX = greenFrame.x,
293
- frameY = greenFrame.y,
294
- frameWidth = greenFrame.width,
295
- frameHeight = greenFrame.height,
296
- wrapperWidth = greenFrame.wrapperWidth,
297
- wrapperHeight = greenFrame.wrapperHeight;
298
- var imgWidth = capturedImageSize.width,
299
- imgHeight = capturedImageSize.height;
300
- var displayX = displayedImageRect.x,
301
- displayY = displayedImageRect.y,
302
- displayWidth = displayedImageRect.width,
303
- displayHeight = displayedImageRect.height;
304
- console.log("🔄 Converting green frame:", {
305
- greenFrame: {
306
- frameX: frameX,
307
- frameY: frameY,
308
- frameWidth: frameWidth,
309
- frameHeight: frameHeight,
310
- wrapperWidth: wrapperWidth,
311
- wrapperHeight: wrapperHeight
312
- },
313
- capturedImageSize: {
314
- imgWidth: imgWidth,
315
- imgHeight: imgHeight
316
- },
317
- displayedImageRect: {
318
- displayX: displayX,
319
- displayY: displayY,
320
- displayWidth: displayWidth,
321
- displayHeight: displayHeight
322
- }
323
- });
324
-
325
- // ✅ SIMPLIFIED APPROACH: Assume CameraView fills the wrapper completely (no letterboxing in preview)
326
- // The green frame is drawn as a percentage of the wrapper (95% width, 80% height)
327
- // We need to map this directly to the captured image, accounting for aspect ratio differences
328
-
329
- var previewAspect = wrapperWidth / wrapperHeight;
330
- var capturedAspect = imgWidth / imgHeight;
331
- console.log("📐 Aspect ratios:", {
332
- previewAspect: previewAspect.toFixed(3),
333
- capturedAspect: capturedAspect.toFixed(3),
334
- wrapperSize: {
335
- wrapperWidth: wrapperWidth,
336
- wrapperHeight: wrapperHeight
337
- },
338
- capturedSize: {
339
- imgWidth: imgWidth,
340
- imgHeight: imgHeight
341
- }
342
- });
343
-
344
- // ✅ KEY INSIGHT: The green frame is 95% of wrapper width and 80% of wrapper height
345
- // If the captured image has a different aspect ratio, we need to map proportionally
346
- // The green frame represents a region in the preview, which should map to the same region in the image
347
-
348
- // Calculate green frame as percentage of wrapper
349
- var greenFramePercentX = frameX / wrapperWidth;
350
- var greenFramePercentY = frameY / wrapperHeight;
351
- var greenFramePercentWidth = frameWidth / wrapperWidth;
352
- var greenFramePercentHeight = frameHeight / wrapperHeight;
353
- console.log("📊 Green frame as percentage of wrapper:", {
354
- x: (greenFramePercentX * 100).toFixed(2) + '%',
355
- y: (greenFramePercentY * 100).toFixed(2) + '%',
356
- width: (greenFramePercentWidth * 100).toFixed(2) + '%',
357
- height: (greenFramePercentHeight * 100).toFixed(2) + '%'
358
- });
359
-
360
- // ✅ DIRECT MAPPING: Map green frame percentage directly to captured image
361
- // The green frame covers a certain percentage of the preview, which should map to the same percentage of the image
362
- // However, we need to account for aspect ratio differences
363
-
364
- // If preview and captured have same aspect ratio, direct mapping works
365
- // If different, we need to account for letterboxing in the preview
366
-
367
- // Calculate how the captured image would be displayed in the preview (aspect-fit)
368
- var previewContentWidth, previewContentHeight, previewOffsetX, previewOffsetY;
369
- if (Math.abs(capturedAspect - previewAspect) < 0.01) {
370
- // Same aspect ratio → no letterboxing, direct mapping
371
- previewContentWidth = wrapperWidth;
372
- previewContentHeight = wrapperHeight;
373
- previewOffsetX = 0;
374
- previewOffsetY = 0;
375
- } else if (capturedAspect > previewAspect) {
376
- // Image is wider → fills width, letterboxing on top/bottom
377
- previewContentWidth = wrapperWidth;
378
- previewContentHeight = wrapperWidth / capturedAspect;
379
- previewOffsetX = 0;
380
- previewOffsetY = (wrapperHeight - previewContentHeight) / 2;
381
- } else {
382
- // Image is taller → fills height, letterboxing on left/right
383
- previewContentHeight = wrapperHeight;
384
- previewContentWidth = wrapperHeight * capturedAspect;
385
- previewOffsetX = (wrapperWidth - previewContentWidth) / 2;
386
- previewOffsetY = 0;
409
+ // Le cadre blanc doit être calculé sur le MÊME wrapper que le cadre vert (9/16)
410
+ // Ensuite, on restreint les points pour qu'ils restent dans imageDisplayRect (image visible)
411
+ var initializeCropBox = function initializeCropBox() {
412
+ // CRITICAL FIX #2: Guard against double initialization
413
+ if (hasInitializedCropBox.current) {
414
+ console.log("⚠️ Crop box already initialized, skipping duplicate initialization");
415
+ return;
387
416
  }
388
- console.log("📐 Preview content area (actual image area in preview):", {
389
- previewContentWidth: previewContentWidth.toFixed(2),
390
- previewContentHeight: previewContentHeight.toFixed(2),
391
- previewOffsetX: previewOffsetX.toFixed(2),
392
- previewOffsetY: previewOffsetY.toFixed(2)
393
- });
394
417
 
395
- // Step 3: Convert green frame coordinates from wrapper space to preview content space
396
- // ✅ CRITICAL FIX: The green frame is drawn on the wrapper, but we need to map it to the actual image area
397
- // If the green frame overlaps letterboxing areas, we need to clip it to the actual image content area
398
-
399
- // Calculate green frame bounds in wrapper coordinates
400
- var frameLeft = frameX;
401
- var frameTop = frameY;
402
- var frameRight = frameX + frameWidth;
403
- var frameBottom = frameY + frameHeight;
404
-
405
- // Calculate preview content bounds in wrapper coordinates
406
- var contentLeft = previewOffsetX;
407
- var contentTop = previewOffsetY;
408
- var contentRight = previewOffsetX + previewContentWidth;
409
- var contentBottom = previewOffsetY + previewContentHeight;
410
-
411
- // ✅ KEY INSIGHT: The green frame should map to the same percentage of the image content area
412
- // But we need to account for letterboxing - the green frame might extend into letterboxing areas
413
-
414
- // ✅ Clip green frame to preview content area (intersection)
415
- var clippedLeft = Math.max(frameLeft, contentLeft);
416
- var clippedTop = Math.max(frameTop, contentTop);
417
- var clippedRight = Math.min(frameRight, contentRight);
418
- var clippedBottom = Math.min(frameBottom, contentBottom);
419
-
420
- // Calculate clipped green frame dimensions
421
- var clippedWidth = Math.max(0, clippedRight - clippedLeft);
422
- var clippedHeight = Math.max(0, clippedBottom - clippedTop);
423
-
424
- // If green frame is completely outside content area, return null
425
- if (clippedWidth <= 0 || clippedHeight <= 0) {
426
- console.error("❌ Green frame is completely outside preview content area!");
427
- return null;
418
+ // CRITICAL: Ensure common wrapper layout is available
419
+ var wrapper = commonWrapperLayout.current;
420
+ if (wrapper.width === 0 || wrapper.height === 0) {
421
+ console.warn("Cannot initialize crop box: common wrapper layout not ready");
422
+ return;
428
423
  }
429
424
 
430
- // ✅ ALTERNATIVE APPROACH: Map green frame as percentage of image content area
431
- // The green frame covers a certain percentage of the wrapper, but we want it to cover
432
- // the same visual percentage of the image content area
433
-
434
- // Calculate green frame center and size as percentage of wrapper
435
- var greenFrameCenterX = (frameLeft + frameRight) / 2;
436
- var greenFrameCenterY = (frameTop + frameBottom) / 2;
437
- var greenFrameCenterPercentX = greenFrameCenterX / wrapperWidth;
438
- var greenFrameCenterPercentY = greenFrameCenterY / wrapperHeight;
439
-
440
- // Map center to image content area
441
- var imageContentCenterX = previewOffsetX + previewContentWidth * greenFrameCenterPercentX;
442
- var imageContentCenterY = previewOffsetY + previewContentHeight * greenFrameCenterPercentY;
443
-
444
- // Calculate green frame size as percentage of image content area (not wrapper)
445
- // The green frame should cover the same visual percentage of the image as it does of the wrapper
446
- var imageContentFrameWidth = previewContentWidth * greenFramePercentWidth;
447
- var imageContentFrameHeight = previewContentHeight * greenFramePercentHeight;
448
-
449
- // Calculate final green frame in image content coordinates
450
- var finalFrameX = imageContentCenterX - imageContentFrameWidth / 2;
451
- var finalFrameY = imageContentCenterY - imageContentFrameHeight / 2;
452
- var finalFrameWidth = imageContentFrameWidth;
453
- var finalFrameHeight = imageContentFrameHeight;
454
-
455
- // Clamp to image content bounds
456
- var clampedFinalX = Math.max(previewOffsetX, Math.min(finalFrameX, previewOffsetX + previewContentWidth - finalFrameWidth));
457
- var clampedFinalY = Math.max(previewOffsetY, Math.min(finalFrameY, previewOffsetY + previewContentHeight - finalFrameHeight));
458
- var clampedFinalWidth = Math.min(finalFrameWidth, previewOffsetX + previewContentWidth - clampedFinalX);
459
- var clampedFinalHeight = Math.min(finalFrameHeight, previewOffsetY + previewContentHeight - clampedFinalY);
460
-
461
- // Convert to relative coordinates within preview content area
462
- var relativeX = clampedFinalX - previewOffsetX;
463
- var relativeY = clampedFinalY - previewOffsetY;
464
-
465
- // Normalize to 0-1 range within the preview content area (actual image area)
466
- var normalizedX = relativeX / previewContentWidth;
467
- var normalizedY = relativeY / previewContentHeight;
468
- var normalizedWidth = clampedFinalWidth / previewContentWidth;
469
- var normalizedHeight = clampedFinalHeight / previewContentHeight;
470
- console.log("✂️ Green frame mapping (percentage-based):", {
471
- originalFrame: {
472
- frameX: frameX,
473
- frameY: frameY,
474
- frameWidth: frameWidth,
475
- frameHeight: frameHeight
476
- },
477
- greenFramePercentages: {
478
- centerX: (greenFrameCenterPercentX * 100).toFixed(2) + '%',
479
- centerY: (greenFrameCenterPercentY * 100).toFixed(2) + '%',
480
- width: (greenFramePercentWidth * 100).toFixed(2) + '%',
481
- height: (greenFramePercentHeight * 100).toFixed(2) + '%'
482
- },
483
- previewContent: {
484
- previewOffsetX: previewOffsetX,
485
- previewOffsetY: previewOffsetY,
486
- previewContentWidth: previewContentWidth,
487
- previewContentHeight: previewContentHeight
488
- },
489
- mappedFrame: {
490
- finalX: finalFrameX.toFixed(2),
491
- finalY: finalFrameY.toFixed(2),
492
- finalWidth: finalFrameWidth.toFixed(2),
493
- finalHeight: finalFrameHeight.toFixed(2)
494
- },
495
- clampedFrame: {
496
- x: clampedFinalX.toFixed(2),
497
- y: clampedFinalY.toFixed(2),
498
- width: clampedFinalWidth.toFixed(2),
499
- height: clampedFinalHeight.toFixed(2)
500
- },
501
- normalized: {
502
- normalizedX: normalizedX.toFixed(4),
503
- normalizedY: normalizedY.toFixed(4),
504
- normalizedWidth: normalizedWidth.toFixed(4),
505
- normalizedHeight: normalizedHeight.toFixed(4)
425
+ // ✅ CRITICAL: Ensure imageDisplayRect is available (zone réelle de l'image dans le wrapper)
426
+ var imageRect = imageDisplayRect.current;
427
+ if (imageRect.width === 0 || imageRect.height === 0) {
428
+ // Recalculer si nécessaire
429
+ if (originalImageDimensions.current.width > 0 && originalImageDimensions.current.height > 0) {
430
+ updateImageDisplayRect(wrapper.width, wrapper.height);
431
+ imageRect = imageDisplayRect.current;
432
+ } else {
433
+ console.warn("Cannot initialize crop box: imageDisplayRect not available (original dimensions missing)");
434
+ return;
506
435
  }
507
- });
508
- console.log("📊 Normalized coordinates:", {
509
- relativeX: relativeX,
510
- relativeY: relativeY,
511
- normalizedX: normalizedX,
512
- normalizedY: normalizedY,
513
- normalizedWidth: normalizedWidth,
514
- normalizedHeight: normalizedHeight
515
- });
516
-
517
- // Step 4: Convert normalized coordinates to captured image pixel coordinates
518
- var imageX = normalizedX * imgWidth;
519
- var imageY = normalizedY * imgHeight;
520
- var imageWidth = normalizedWidth * imgWidth;
521
- var imageHeight = normalizedHeight * imgHeight;
522
- console.log("🖼️ Image pixel coordinates:", {
523
- imageX: imageX,
524
- imageY: imageY,
525
- imageWidth: imageWidth,
526
- imageHeight: imageHeight,
527
- imgWidth: imgWidth,
528
- imgHeight: imgHeight
529
- });
530
-
531
- // Step 5: Convert to displayed image coordinates (for white bounding box)
532
- // The captured image is displayed with resizeMode='contain' in ImageCropper
533
- var displayAspect = displayWidth / displayHeight;
534
- var displayContentWidth, displayContentHeight, displayOffsetX, displayOffsetY;
535
- if (capturedAspect > displayAspect) {
536
- // Image is wider than display → letterboxing on top/bottom
537
- displayContentWidth = displayWidth;
538
- displayContentHeight = displayWidth / capturedAspect;
539
- displayOffsetX = displayX;
540
- displayOffsetY = displayY + (displayHeight - displayContentHeight) / 2;
541
- } else {
542
- // Image is taller than display → letterboxing on left/right
543
- displayContentHeight = displayHeight;
544
- displayContentWidth = displayHeight * capturedAspect;
545
- displayOffsetX = displayX + (displayWidth - displayContentWidth) / 2;
546
- displayOffsetY = displayY;
547
436
  }
548
437
 
549
- // Convert image pixel coordinates to display coordinates
550
- var scaleX = displayContentWidth / imgWidth;
551
- var scaleY = displayContentHeight / imgHeight;
552
- var displayBoxX = displayOffsetX + imageX * scaleX;
553
- var displayBoxY = displayOffsetY + imageY * scaleY;
554
- var displayBoxWidth = imageWidth * scaleX;
555
- var displayBoxHeight = imageHeight * scaleY;
556
-
557
- // Clamp to displayed image bounds
558
- var clampedX = Math.max(displayOffsetX, Math.min(displayBoxX, displayOffsetX + displayContentWidth - displayBoxWidth));
559
- var clampedY = Math.max(displayOffsetY, Math.min(displayBoxY, displayOffsetY + displayContentHeight - displayBoxHeight));
560
- var clampedWidth = Math.min(displayBoxWidth, displayOffsetX + displayContentWidth - clampedX);
561
- var clampedHeight = Math.min(displayBoxHeight, displayOffsetY + displayContentHeight - clampedY);
562
- var result = {
563
- // Display coordinates (for white bounding box)
564
- displayCoords: {
565
- x: clampedX,
566
- y: clampedY,
567
- width: clampedWidth,
568
- height: clampedHeight
569
- },
570
- // Image pixel coordinates (for backend crop)
571
- imageCoords: {
572
- x: Math.max(0, Math.min(imageX, imgWidth - imageWidth)),
573
- y: Math.max(0, Math.min(imageY, imgHeight - imageHeight)),
574
- width: Math.max(0, Math.min(imageWidth, imgWidth)),
575
- height: Math.max(0, Math.min(imageHeight, imgHeight))
576
- },
577
- debug: {
578
- previewAspect: previewAspect,
579
- capturedAspect: capturedAspect,
580
- displayAspect: displayAspect,
581
- previewContentWidth: previewContentWidth,
582
- previewContentHeight: previewContentHeight,
583
- previewOffsetX: previewOffsetX,
584
- previewOffsetY: previewOffsetY,
585
- displayContentWidth: displayContentWidth,
586
- displayContentHeight: displayContentHeight,
587
- displayOffsetX: displayOffsetX,
588
- displayOffsetY: displayOffsetY,
589
- scaleX: scaleX,
590
- scaleY: scaleY
591
- }
592
- };
593
- console.log("✅ Green frame converted to image coordinates:", JSON.stringify(result, null, 2));
438
+ // CRITICAL FIX: Calculate crop box as percentage of VISIBLE IMAGE AREA (imageDisplayRect)
439
+ // NOT the wrapper. This ensures the crop box is truly 80% of the image, not 80% of wrapper then clamped.
440
+ // Calculate absolute position of imageDisplayRect within wrapper
441
+ var imageRectX = wrapper.x + imageRect.x;
442
+ var imageRectY = wrapper.y + imageRect.y;
594
443
 
595
- // ✅ VALIDATION: Ensure the white bounding box matches the green frame visually
596
- // Calculate what percentage of the image the green frame covers
597
- var greenFramePercentOfImage = {
598
- width: imageWidth / imgWidth * 100,
599
- height: imageHeight / imgHeight * 100
600
- };
601
- var greenFramePercentOfPreview = {
602
- width: frameWidth / wrapperWidth * 100,
603
- height: frameHeight / wrapperHeight * 100
604
- };
605
- console.log("📏 Green frame coverage:", {
606
- percentOfImage: greenFramePercentOfImage,
607
- percentOfPreview: greenFramePercentOfPreview,
608
- shouldMatch: "Green frame should cover same % of image as it does of preview"
609
- });
610
- return result;
611
- };
444
+ // ✅ PRIORITY RULE #3: IF image comes from camera Use EXACT green frame coordinates
445
+ // Image is displayed in "cover" mode (full wrapper), so green frame coords = white frame coords
446
+ if (cameraFrameData.current && cameraFrameData.current.greenFrame && originalImageDimensions.current.width > 0) {
447
+ var greenFrame = cameraFrameData.current.greenFrame;
612
448
 
613
- // ✅ REFACTORISATION : Initialiser le crop box avec les dimensions d'affichage réelles
614
- // CRITICAL FIX: If camera frame data exists, use it to match green frame exactly
615
- var initializeCropBox = function initializeCropBox() {
616
- // CRITICAL: Ensure displayedContentRect is available
617
- var contentRect = displayedContentRect.current;
618
- var layout = displayedImageLayout.current;
449
+ // ✅ Image fills wrapper (cover mode), so use green frame position directly in wrapper space
450
+ var _boxX = wrapper.x + greenFrame.x;
451
+ var _boxY = wrapper.y + greenFrame.y;
452
+ var _boxWidth = greenFrame.width;
453
+ var _boxHeight = greenFrame.height;
619
454
 
620
- // Recalculate if not available
621
- if (contentRect.width === 0 || contentRect.height === 0) {
622
- if (layout.width > 0 && layout.height > 0) {
623
- updateDisplayedContentRect(layout.width, layout.height);
624
- contentRect = displayedContentRect.current;
455
+ // SAFETY: Validate calculated coordinates before creating points
456
+ var _isValidCoordinate = function _isValidCoordinate(val) {
457
+ return typeof val === 'number' && isFinite(val) && !isNaN(val);
458
+ };
459
+ if (!_isValidCoordinate(_boxX) || !_isValidCoordinate(_boxY) || !_isValidCoordinate(_boxWidth) || !_isValidCoordinate(_boxHeight)) {
460
+ console.warn("⚠️ Invalid coordinates calculated for crop box, skipping initialization");
461
+ return;
625
462
  }
626
- // If still not available, use layout as fallback
627
- if (contentRect.width === 0 || contentRect.height === 0) {
628
- if (layout.width > 0 && layout.height > 0) {
629
- contentRect = {
630
- x: layout.x,
631
- y: layout.y,
632
- width: layout.width,
633
- height: layout.height
634
- };
635
- } else {
636
- console.warn("Cannot initialize crop box: displayed dimensions are zero");
637
- return;
638
- }
463
+
464
+ // Create points using EXACT greenFrame coordinates (mapped to ImageCropper wrapper)
465
+ var _newPoints = [{
466
+ x: _boxX,
467
+ y: _boxY
468
+ },
469
+ // Top-left
470
+ {
471
+ x: _boxX + _boxWidth,
472
+ y: _boxY
473
+ },
474
+ // Top-right
475
+ {
476
+ x: _boxX + _boxWidth,
477
+ y: _boxY + _boxHeight
478
+ },
479
+ // Bottom-right
480
+ {
481
+ x: _boxX,
482
+ y: _boxY + _boxHeight
483
+ } // Bottom-left
484
+ ];
485
+
486
+ // ✅ SAFETY: Validate all points before setting
487
+ var _validPoints = _newPoints.filter(function (p) {
488
+ return _isValidCoordinate(p.x) && _isValidCoordinate(p.y);
489
+ });
490
+ if (_validPoints.length !== _newPoints.length) {
491
+ console.warn("⚠️ Some points have invalid coordinates, skipping initialization");
492
+ return;
639
493
  }
494
+ console.log("✅ Initializing crop box for camera image (COVER MODE - exact green frame):", {
495
+ greenFrame: {
496
+ x: greenFrame.x,
497
+ y: greenFrame.y,
498
+ width: greenFrame.width,
499
+ height: greenFrame.height
500
+ },
501
+ whiteFrame: {
502
+ x: _boxX.toFixed(2),
503
+ y: _boxY.toFixed(2),
504
+ width: _boxWidth.toFixed(2),
505
+ height: _boxHeight.toFixed(2)
506
+ },
507
+ note: "Image in cover mode - white frame = same position/size as green frame, same content"
508
+ });
509
+ setPoints(_newPoints);
510
+ hasInitializedCropBox.current = true; // ✅ CRITICAL: Mark as initialized
511
+ // ✅ CRITICAL: DO NOT nullify cameraFrameData here - keep it for Image.getSize() callback check
512
+ // It will be cleared when loading a new image
513
+ return;
640
514
  }
641
- var _contentRect = contentRect,
642
- x = _contentRect.x,
643
- y = _contentRect.y,
644
- width = _contentRect.width,
645
- height = _contentRect.height;
646
-
647
- // ✅ PRIORITY: If we have green frame data from camera, use it to match exactly
648
- if (cameraFrameData.current && cameraFrameData.current.greenFrame && originalImageDimensions.current.width > 0) {
649
- var converted = convertGreenFrameToImageCoords(cameraFrameData.current.greenFrame, cameraFrameData.current.capturedImageSize || originalImageDimensions.current, contentRect);
650
- if (converted && converted.displayCoords) {
651
- var _converted$displayCoo = converted.displayCoords,
652
- _boxX = _converted$displayCoo.x,
653
- _boxY = _converted$displayCoo.y,
654
- _boxWidth = _converted$displayCoo.width,
655
- _boxHeight = _converted$displayCoo.height;
656
- var _contentRect2 = contentRect,
657
- contentX = _contentRect2.x,
658
- contentY = _contentRect2.y,
659
- contentWidth = _contentRect2.width,
660
- contentHeight = _contentRect2.height;
661
-
662
- // ✅ CRITICAL: Clamp points to displayed image bounds (double-check)
663
- // Ensure the bounding box stays within contentRect, but preserve aspect ratio and percentage
664
- // First, clamp position
665
- var clampedBoxX = Math.max(contentX, Math.min(_boxX, contentX + contentWidth - _boxWidth));
666
- var clampedBoxY = Math.max(contentY, Math.min(_boxY, contentY + contentHeight - _boxHeight));
667
-
668
- // ✅ CRITICAL: Preserve the width and height from conversion (they should already be correct)
669
- // Only adjust if they would exceed bounds, but try to maintain the 80% coverage
670
- var clampedBoxWidth = _boxWidth;
671
- var clampedBoxHeight = _boxHeight;
672
-
673
- // Ensure the box fits within contentRect
674
- if (clampedBoxX + clampedBoxWidth > contentX + contentWidth) {
675
- clampedBoxWidth = contentX + contentWidth - clampedBoxX;
676
- }
677
- if (clampedBoxY + clampedBoxHeight > contentY + contentHeight) {
678
- clampedBoxHeight = contentY + contentHeight - clampedBoxY;
679
- }
680
515
 
681
- // ✅ CRITICAL: If clamping reduced dimensions, adjust position to center the box
682
- // This ensures the white bounding box maintains the same visual percentage as the green frame
683
- if (clampedBoxWidth < _boxWidth || clampedBoxHeight < _boxHeight) {
684
- // Re-center if possible
685
- var idealX = contentX + (contentWidth - clampedBoxWidth) / 2;
686
- var idealY = contentY + (contentHeight - clampedBoxHeight) / 2;
687
- clampedBoxX = Math.max(contentX, Math.min(idealX, contentX + contentWidth - clampedBoxWidth));
688
- clampedBoxY = Math.max(contentY, Math.min(idealY, contentY + contentHeight - clampedBoxHeight));
689
- }
516
+ // ✅ PRIORITY RULE #3: DEFAULT logic ONLY for gallery images (NOT camera)
517
+ // If we reach here and imageSource is 'camera', something went wrong
518
+ if (imageSource.current === 'camera') {
519
+ console.warn("⚠️ Camera image but no greenFrame found - this should not happen");
520
+ return;
521
+ }
690
522
 
691
- // ✅ CRITICAL: Ensure points are within contentRect bounds but not exactly at the edges
692
- // This allows free movement in all directions
693
- var minX = contentX;
694
- var maxX = contentX + contentWidth;
695
- var minY = contentY;
696
- var maxY = contentY + contentHeight;
697
-
698
- // Create points from the clamped green frame coordinates
699
- // Clamp each point individually to ensure they're within bounds
700
- var _newPoints = [{
701
- x: Math.max(minX, Math.min(clampedBoxX, maxX)),
702
- y: Math.max(minY, Math.min(clampedBoxY, maxY))
703
- }, {
704
- x: Math.max(minX, Math.min(clampedBoxX + clampedBoxWidth, maxX)),
705
- y: Math.max(minY, Math.min(clampedBoxY, maxY))
706
- }, {
707
- x: Math.max(minX, Math.min(clampedBoxX + clampedBoxWidth, maxX)),
708
- y: Math.max(minY, Math.min(clampedBoxY + clampedBoxHeight, maxY))
709
- }, {
710
- x: Math.max(minX, Math.min(clampedBoxX, maxX)),
711
- y: Math.max(minY, Math.min(clampedBoxY + clampedBoxHeight, maxY))
712
- }];
713
-
714
- // ✅ VALIDATION: Verify the white bounding box matches green frame percentage (80%)
715
- var whiteBoxPercentOfDisplay = {
716
- width: clampedBoxWidth / contentRect.width * 100,
717
- height: clampedBoxHeight / contentRect.height * 100
718
- };
719
- var greenFramePercentOfWrapper = {
720
- width: cameraFrameData.current.greenFrame.width / cameraFrameData.current.greenFrame.wrapperWidth * 100,
721
- height: cameraFrameData.current.greenFrame.height / cameraFrameData.current.greenFrame.wrapperHeight * 100
722
- };
523
+ // ✅ DEFAULT: Crop box (70% of VISIBLE IMAGE AREA - centered) - ONLY for gallery images
524
+ var boxWidth = imageRect.width * 0.70; // 70% of visible image width
525
+ var boxHeight = imageRect.height * 0.70; // 70% of visible image height
526
+ var boxX = imageRectX + (imageRect.width - boxWidth) / 2; // Centered in image area
527
+ var boxY = imageRectY + (imageRect.height - boxHeight) / 2; // Centered in image area
723
528
 
724
- // ✅ DEBUG: Log if percentages don't match (should both be ~80%)
725
- if (Math.abs(whiteBoxPercentOfDisplay.width - greenFramePercentOfWrapper.width) > 5 || Math.abs(whiteBoxPercentOfDisplay.height - greenFramePercentOfWrapper.height) > 5) {
726
- console.warn("⚠️ White box percentage doesn't match green frame:", {
727
- whiteBox: whiteBoxPercentOfDisplay,
728
- greenFrame: greenFramePercentOfWrapper,
729
- difference: {
730
- width: Math.abs(whiteBoxPercentOfDisplay.width - greenFramePercentOfWrapper.width),
731
- height: Math.abs(whiteBoxPercentOfDisplay.height - greenFramePercentOfWrapper.height)
732
- }
733
- });
734
- }
735
- console.log("✅ Initializing crop box from green frame (clamped):", {
736
- greenFrame: cameraFrameData.current.greenFrame,
737
- converted: converted,
738
- clamped: {
739
- x: clampedBoxX,
740
- y: clampedBoxY,
741
- width: clampedBoxWidth,
742
- height: clampedBoxHeight
743
- },
744
- contentRect: contentRect,
745
- points: _newPoints,
746
- validation: {
747
- whiteBoxPercentOfDisplay: whiteBoxPercentOfDisplay,
748
- greenFramePercentOfWrapper: greenFramePercentOfWrapper,
749
- shouldMatch: "White box % should match green frame % (80% - accounting for aspect ratio)"
750
- }
751
- });
752
- setPoints(_newPoints);
753
- // Clear camera frame data after use to avoid reusing it
754
- cameraFrameData.current = null;
755
- return;
756
- }
529
+ // ✅ SAFETY: Validate calculated coordinates before creating points
530
+ var isValidCoordinate = function isValidCoordinate(val) {
531
+ return typeof val === 'number' && isFinite(val) && !isNaN(val);
532
+ };
533
+ if (!isValidCoordinate(boxX) || !isValidCoordinate(boxY) || !isValidCoordinate(boxWidth) || !isValidCoordinate(boxHeight)) {
534
+ console.warn("⚠️ Invalid coordinates calculated for default crop box, skipping initialization");
535
+ return;
757
536
  }
758
-
759
- // ✅ CRITICAL: Default crop box (70% of displayed area - centered)
760
- var boxWidth = width * 0.70; // 70% width
761
- var boxHeight = height * 0.70; // 70% height
762
- var boxX = x + (width - boxWidth) / 2; // Centered horizontally
763
- var boxY = y + (height - boxHeight) / 2; // Centered vertically
764
537
  var newPoints = [{
765
538
  x: boxX,
766
539
  y: boxY
767
- },
768
- // Top-left
769
- {
540
+ }, {
770
541
  x: boxX + boxWidth,
771
542
  y: boxY
772
- },
773
- // Top-right
774
- {
543
+ }, {
775
544
  x: boxX + boxWidth,
776
545
  y: boxY + boxHeight
777
- },
778
- // Bottom-right
779
- {
546
+ }, {
780
547
  x: boxX,
781
548
  y: boxY + boxHeight
782
- } // Bottom-left
783
- ];
784
- console.log("Initializing crop box (default - 70% centered):", {
785
- displayedWidth: width,
786
- displayedHeight: height,
787
- boxWidth: boxWidth,
788
- boxHeight: boxHeight,
789
- boxX: boxX,
790
- boxY: boxY,
549
+ }];
550
+
551
+ // SAFETY: Validate all points before setting
552
+ var validPoints = newPoints.filter(function (p) {
553
+ return isValidCoordinate(p.x) && isValidCoordinate(p.y);
554
+ });
555
+ if (validPoints.length !== newPoints.length) {
556
+ console.warn("⚠️ Some points have invalid coordinates in default crop box, skipping initialization");
557
+ return;
558
+ }
559
+ console.log("✅ Initializing crop box (default - 70% of visible image area, gallery only):", {
560
+ wrapper: {
561
+ width: wrapper.width,
562
+ height: wrapper.height
563
+ },
564
+ imageDisplayRect: imageRect,
565
+ boxInImage: {
566
+ x: boxX,
567
+ y: boxY,
568
+ width: boxWidth,
569
+ height: boxHeight
570
+ },
791
571
  points: newPoints
792
572
  });
793
573
  setPoints(newPoints);
574
+ hasInitializedCropBox.current = true; // ✅ CRITICAL: Mark as initialized
575
+ };
576
+
577
+ // ✅ RÉFÉRENTIEL UNIQUE : Callback pour mettre à jour le layout du wrapper commun
578
+ // Ce wrapper a exactement les mêmes dimensions que le wrapper de CustomCamera (9/16, width = screenWidth)
579
+ var onCommonWrapperLayout = function onCommonWrapperLayout(e) {
580
+ var layout = e.nativeEvent.layout;
581
+ commonWrapperLayout.current = {
582
+ x: layout.x,
583
+ y: layout.y,
584
+ width: layout.width,
585
+ height: layout.height
586
+ };
587
+ console.log("✅ Common wrapper layout updated:", commonWrapperLayout.current);
588
+
589
+ // ✅ Recalculer imageDisplayRect dès que le wrapper est prêt
590
+ if (originalImageDimensions.current.width > 0 && originalImageDimensions.current.height > 0) {
591
+ updateImageDisplayRect(layout.width, layout.height);
592
+
593
+ // ✅ CRITICAL FIX #2: Initialize crop box ONLY if not already initialized
594
+ // For camera images: initialize ONLY from greenFrame (already done when cameraFrameData was set)
595
+ if (!hasInitializedCropBox.current && points.length === 0) {
596
+ // ✅ CRITICAL: Only initialize for gallery images here
597
+ // Camera images should be initialized when cameraFrameData is set, not here
598
+ if (imageSource.current !== 'camera') {
599
+ initializeCropBox();
600
+ }
601
+ }
602
+ }
794
603
  };
795
604
 
796
605
  // ✅ REFACTORISATION : Mettre à jour les dimensions d'affichage et les dimensions pour SVG
@@ -813,9 +622,28 @@ var ImageCropper = function ImageCropper(_ref) {
813
622
  height: layout.height
814
623
  };
815
624
 
816
- // ✅ CORRECTION: recalculer contentRect (contain). Si les dimensions originales ne sont pas encore connues,
817
- // updateDisplayedContentRect va fallback et sera recalculé quand Image.getSize arrivera.
818
- updateDisplayedContentRect(layout.width, layout.height);
625
+ // ✅ Si l'image vient de la caméra et que les dimensions originales ne sont pas encore définies,
626
+ // les initialiser à partir de cameraFrameData AVANT de calculer le contentRect.
627
+ if (originalImageDimensions.current.width === 0 && cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
628
+ var _cameraFrameData$curr2 = cameraFrameData.current.capturedImageSize,
629
+ width = _cameraFrameData$curr2.width,
630
+ height = _cameraFrameData$curr2.height;
631
+ originalImageDimensions.current = {
632
+ width: width,
633
+ height: height
634
+ };
635
+ console.log("✅ originalImageDimensions initialisées depuis cameraFrameData dans onImageLayout:", {
636
+ width: width,
637
+ height: height
638
+ });
639
+ }
640
+
641
+ // ✅ RÉFÉRENTIEL UNIQUE : Recalculer imageDisplayRect dans le wrapper commun
642
+ // Si le wrapper commun n'est pas encore prêt, on attendra onCommonWrapperLayout
643
+ var wrapper = commonWrapperLayout.current;
644
+ if (wrapper.width > 0 && wrapper.height > 0) {
645
+ updateImageDisplayRect(wrapper.width, wrapper.height);
646
+ }
819
647
  console.log("Displayed image layout updated:", {
820
648
  width: layout.width,
821
649
  height: layout.height,
@@ -823,27 +651,16 @@ var ImageCropper = function ImageCropper(_ref) {
823
651
  y: layout.y
824
652
  });
825
653
 
826
- // ✅ CRITICAL FIX: Only initialize crop box if:
827
- // 1. Layout dimensions are available
828
- // 2. We have no points yet (first initialization)
829
- // 3. We have original image dimensions (either from camera or Image.getSize)
830
- // This prevents initializing with wrong dimensions for subsequent images
831
- if (layout.width > 0 && layout.height > 0 && points.length === 0) {
832
- // CRITICAL: Wait for original dimensions before initializing
833
- // If dimensions not available yet, initializeCropBox will be called from useEffect when Image.getSize completes
834
- if (originalImageDimensions.current.width > 0 && originalImageDimensions.current.height > 0) {
654
+ // ✅ CRITICAL FIX #2: Do NOT initialize crop box in onImageLayout for camera images
655
+ // Camera images should be initialized ONLY when cameraFrameData is set (in useEffect)
656
+ // Gallery images can be initialized here if not already done
657
+ if (wrapper.width > 0 && wrapper.height > 0 && layout.width > 0 && layout.height > 0 && !hasInitializedCropBox.current && points.length === 0 && originalImageDimensions.current.width > 0 && originalImageDimensions.current.height > 0) {
658
+ // CRITICAL: Only initialize for gallery images here
659
+ // Camera images should be initialized when cameraFrameData is set, not here
660
+ if (imageSource.current !== 'camera') {
835
661
  initializeCropBox();
836
- } else if (cameraFrameData.current && cameraFrameData.current.capturedImageSize) {
837
- // ✅ If we have camera dimensions, use them immediately
838
- originalImageDimensions.current = {
839
- width: cameraFrameData.current.capturedImageSize.width,
840
- height: cameraFrameData.current.capturedImageSize.height
841
- };
842
- initializeCropBox();
843
- } else {
844
- // ✅ For gallery images, we can initialize with layout dimensions as fallback
845
- // The crop box will be recalculated when Image.getSize completes
846
- // But we initialize now so the user sees a border immediately
662
+ } else if (cameraFrameData.current && cameraFrameData.current.greenFrame) {
663
+ // ✅ For camera images, initialize ONLY if greenFrame is available
847
664
  initializeCropBox();
848
665
  }
849
666
  }
@@ -856,6 +673,66 @@ var ImageCropper = function ImageCropper(_ref) {
856
673
  });
857
674
  return path + 'Z';
858
675
  };
676
+
677
+ // ✅ Helper function: Find closest point on a line segment to a tap point
678
+ var findClosestPointOnLine = function findClosestPointOnLine(tapX, tapY, lineStartX, lineStartY, lineEndX, lineEndY) {
679
+ var dx = lineEndX - lineStartX;
680
+ var dy = lineEndY - lineStartY;
681
+ var lengthSquared = dx * dx + dy * dy;
682
+ if (lengthSquared === 0) {
683
+ // Line segment is a point
684
+ return {
685
+ x: lineStartX,
686
+ y: lineStartY,
687
+ distance: Math.sqrt(Math.pow(tapX - lineStartX, 2) + Math.pow(tapY - lineStartY, 2))
688
+ };
689
+ }
690
+
691
+ // Calculate projection parameter t (0 to 1)
692
+ var t = Math.max(0, Math.min(1, ((tapX - lineStartX) * dx + (tapY - lineStartY) * dy) / lengthSquared));
693
+
694
+ // Calculate closest point on line segment
695
+ var closestX = lineStartX + t * dx;
696
+ var closestY = lineStartY + t * dy;
697
+
698
+ // Calculate distance from tap to closest point
699
+ var distance = Math.sqrt(Math.pow(tapX - closestX, 2) + Math.pow(tapY - closestY, 2));
700
+ return {
701
+ x: closestX,
702
+ y: closestY,
703
+ distance: distance,
704
+ t: t
705
+ };
706
+ };
707
+
708
+ // ✅ Helper function: Check if tap is near any line segment and find closest point
709
+ var findClosestPointOnFrame = function findClosestPointOnFrame(tapX, tapY) {
710
+ var lineTolerance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 30;
711
+ if (points.length < 2) return null;
712
+ var closestPoint = null;
713
+ var minDistance = Infinity;
714
+ var insertIndex = -1;
715
+
716
+ // Check each line segment (closed polygon: last point connects to first)
717
+ for (var i = 0; i < points.length; i++) {
718
+ var start = points[i];
719
+ var end = points[(i + 1) % points.length];
720
+ var result = findClosestPointOnLine(tapX, tapY, start.x, start.y, end.x, end.y);
721
+ if (result.distance < minDistance && result.distance < lineTolerance) {
722
+ minDistance = result.distance;
723
+ closestPoint = {
724
+ x: result.x,
725
+ y: result.y
726
+ };
727
+ // Insert after the start point of this segment
728
+ insertIndex = i + 1;
729
+ }
730
+ }
731
+ return closestPoint ? {
732
+ point: closestPoint,
733
+ insertIndex: insertIndex
734
+ } : null;
735
+ };
859
736
  var handleTap = function handleTap(e) {
860
737
  if (!image || showResult) return;
861
738
  var now = Date.now();
@@ -863,97 +740,144 @@ var ImageCropper = function ImageCropper(_ref) {
863
740
  tapX = _e$nativeEvent.locationX,
864
741
  tapY = _e$nativeEvent.locationY;
865
742
 
866
- // ✅ CRITICAL: Ensure displayedContentRect is available
867
- var contentRect = displayedContentRect.current;
868
- var layout = displayedImageLayout.current;
743
+ // ✅ RÉFÉRENTIEL UNIQUE : Utiliser imageDisplayRect (zone réelle de l'image dans le wrapper)
744
+ // Les coordonnées du tap sont relatives au wrapper commun
745
+ var imageRect = imageDisplayRect.current;
746
+ var wrapper = commonWrapperLayout.current;
869
747
 
870
748
  // Recalculate if not available
871
- if (contentRect.width === 0 || contentRect.height === 0) {
872
- if (layout.width > 0 && layout.height > 0) {
873
- updateDisplayedContentRect(layout.width, layout.height);
874
- contentRect = displayedContentRect.current;
749
+ if (imageRect.width === 0 || imageRect.height === 0) {
750
+ if (wrapper.width > 0 && wrapper.height > 0 && originalImageDimensions.current.width > 0) {
751
+ updateImageDisplayRect(wrapper.width, wrapper.height);
752
+ imageRect = imageDisplayRect.current;
875
753
  }
876
- // If still not available, use layout as fallback
877
- if (contentRect.width === 0 || contentRect.height === 0) {
878
- if (layout.width > 0 && layout.height > 0) {
879
- contentRect = {
880
- x: layout.x,
881
- y: layout.y,
882
- width: layout.width,
883
- height: layout.height
754
+ // If still not available, use wrapper as fallback
755
+ if (imageRect.width === 0 || imageRect.height === 0) {
756
+ if (wrapper.width > 0 && wrapper.height > 0) {
757
+ imageRect = {
758
+ x: wrapper.x,
759
+ y: wrapper.y,
760
+ width: wrapper.width,
761
+ height: wrapper.height
884
762
  };
885
763
  } else {
886
- console.warn("⚠️ Cannot handle tap: no layout dimensions available");
764
+ console.warn("⚠️ Cannot handle tap: wrapper or imageDisplayRect not available");
887
765
  return;
888
766
  }
889
767
  }
890
768
  }
891
769
 
892
- // ✅ Clamp to real displayed image content (avoid points outside image due to letterboxing)
893
- var _contentRect3 = contentRect,
894
- cx = _contentRect3.x,
895
- cy = _contentRect3.y,
896
- cw = _contentRect3.width,
897
- ch = _contentRect3.height;
898
- var boundedTapX = Math.max(cx, Math.min(tapX, cx + cw));
899
- var boundedTapY = Math.max(cy, Math.min(tapY, cy + ch));
900
- var selectRadius = 28; // easier to grab points
901
- if (lastTap.current && now - lastTap.current < 300) {
902
- var exists = points.some(function (p) {
903
- return Math.abs(p.x - boundedTapX) < selectRadius && Math.abs(p.y - boundedTapY) < selectRadius;
904
- });
905
- if (!exists) setPoints([].concat(_toConsumableArray(points), [{
906
- x: boundedTapX,
907
- y: boundedTapY
908
- }]));
909
- lastTap.current = null;
910
- } else {
911
- var index = points.findIndex(function (p) {
912
- return Math.abs(p.x - boundedTapX) < selectRadius && Math.abs(p.y - boundedTapY) < selectRadius;
770
+ // ✅ Clamp to real displayed image content (imageDisplayRect dans le wrapper)
771
+ // Les coordonnées tapX/tapY sont relatives au wrapper commun
772
+ var imageRectX = wrapper.x + imageRect.x;
773
+ var imageRectY = wrapper.y + imageRect.y;
774
+ var _x$y$width$height = {
775
+ x: imageRectX,
776
+ y: imageRectY,
777
+ width: imageRect.width,
778
+ height: imageRect.height
779
+ },
780
+ cx = _x$y$width$height.x,
781
+ cy = _x$y$width$height.y,
782
+ cw = _x$y$width$height.width,
783
+ ch = _x$y$width$height.height;
784
+ // ✅ Larger select radius for easier point selection (especially on touch screens)
785
+ var selectRadius = 50; // Increased from 28 to 50 for better UX
786
+
787
+ // CRITICAL: Check for existing point selection FIRST (using raw tap coordinates)
788
+ // Don't clamp tapX/Y for point selection - points can be anywhere in wrapper now
789
+ var index = points.findIndex(function (p) {
790
+ return Math.abs(p.x - tapX) < selectRadius && Math.abs(p.y - tapY) < selectRadius;
791
+ });
792
+ if (index !== -1) {
793
+ // ✅ Point found - select it for dragging
794
+ selectedPointIndex.current = index;
795
+
796
+ // Store initial positions
797
+ initialTouchPosition.current = {
798
+ x: tapX,
799
+ y: tapY
800
+ };
801
+ lastTouchPosition.current = {
802
+ x: tapX,
803
+ y: tapY
804
+ };
805
+ initialPointPosition.current = _objectSpread({}, points[index]);
806
+ lastValidPosition.current = _objectSpread({}, points[index]);
807
+
808
+ // Calculate offset between point and touch at drag start
809
+ touchOffset.current = {
810
+ x: points[index].x - tapX,
811
+ y: points[index].y - tapY
812
+ };
813
+ console.log("🎯 DRAG START - Offset calculated:", {
814
+ pointX: points[index].x.toFixed(2),
815
+ pointY: points[index].y.toFixed(2),
816
+ touchX: tapX.toFixed(2),
817
+ touchY: tapY.toFixed(2),
818
+ offsetX: touchOffset.current.x.toFixed(2),
819
+ offsetY: touchOffset.current.y.toFixed(2)
913
820
  });
914
- if (index !== -1) {
915
- selectedPointIndex.current = index;
916
- // ✅ FREE DRAG: Store initial positions for delta-based movement
917
- initialTouchPosition.current = {
918
- x: tapX,
919
- y: tapY
821
+
822
+ // Disable parent ScrollView scrolling when dragging
823
+ try {
824
+ var _findScrollView = function findScrollView(node) {
825
+ if (!node) return null;
826
+ if (node._component && node._component.setNativeProps) {
827
+ node._component.setNativeProps({
828
+ scrollEnabled: false
829
+ });
830
+ }
831
+ return _findScrollView(node._owner || node._parent);
920
832
  };
921
- lastTouchPosition.current = {
922
- x: tapX,
923
- y: tapY
924
- }; // Store last touch for incremental delta
925
- initialPointPosition.current = _objectSpread({}, points[index]);
926
- lastValidPosition.current = _objectSpread({}, points[index]); // store original position before move
927
-
928
- // CRITICAL: Disable parent ScrollView scrolling when dragging a point
929
- // This prevents ScrollView from intercepting vertical movement
930
- try {
931
- // Find and disable parent ScrollView if it exists
932
- var _findScrollView = function findScrollView(node) {
933
- if (!node) return null;
934
- if (node._component && node._component.setNativeProps) {
935
- // Try to disable scrolling
936
- node._component.setNativeProps({
937
- scrollEnabled: false
938
- });
939
- }
940
- return _findScrollView(node._owner || node._parent);
941
- };
942
- // Note: This is a workaround - ideally we'd pass a ref to disable scroll
943
- } catch (e) {
944
- // Ignore errors
833
+ } catch (e) {
834
+ // Ignore errors
835
+ }
836
+ } else {
837
+ // No point found - check if double-tap on a line to create new point
838
+ var isDoubleTap = lastTap.current && now - lastTap.current < 300;
839
+ if (isDoubleTap && points.length >= 2) {
840
+ // Find closest point on frame lines
841
+ var lineResult = findClosestPointOnFrame(tapX, tapY, 30); // 30px tolerance
842
+
843
+ if (lineResult) {
844
+ var point = lineResult.point,
845
+ insertIndex = lineResult.insertIndex;
846
+
847
+ // Check if a point already exists very close to this position
848
+ var exists = points.some(function (p) {
849
+ return Math.abs(p.x - point.x) < selectRadius && Math.abs(p.y - point.y) < selectRadius;
850
+ });
851
+ if (!exists) {
852
+ // Insert new point at the correct position in the polygon
853
+ var newPoints = _toConsumableArray(points);
854
+ newPoints.splice(insertIndex, 0, point);
855
+ setPoints(newPoints);
856
+ console.log("✅ New point created on frame line:", {
857
+ tap: {
858
+ x: tapX.toFixed(2),
859
+ y: tapY.toFixed(2)
860
+ },
861
+ newPoint: {
862
+ x: point.x.toFixed(2),
863
+ y: point.y.toFixed(2)
864
+ },
865
+ insertIndex: insertIndex,
866
+ totalPoints: newPoints.length
867
+ });
868
+ lastTap.current = null; // Reset to prevent triple-tap
869
+ return;
870
+ }
945
871
  }
946
872
  }
947
- lastTap.current = now;
948
873
  }
874
+ lastTap.current = now;
949
875
  };
950
876
  var handleMove = function handleMove(e) {
951
877
  if (showResult || selectedPointIndex.current === null) return;
952
878
 
953
879
  // ✅ FREE DRAG: Use delta-based movement for smooth, unconstrained dragging
954
- // ✅ CRITICAL FIX: Use incremental delta calculation for more reliable vertical movement
955
- // Instead of calculating delta from initial position, calculate from last position
956
- // This works better when ScrollView intercepts some events
880
+
957
881
  var nativeEvent = e.nativeEvent;
958
882
  var currentX = nativeEvent.locationX;
959
883
  var currentY = nativeEvent.locationY;
@@ -967,7 +891,6 @@ var ImageCropper = function ImageCropper(_ref) {
967
891
  return;
968
892
  }
969
893
 
970
- // ✅ CRITICAL: Use incremental delta (from last position) instead of absolute delta
971
894
  // This is more reliable when ScrollView affects coordinate updates
972
895
  var deltaX, deltaY;
973
896
  if (lastTouchPosition.current) {
@@ -983,157 +906,284 @@ var ImageCropper = function ImageCropper(_ref) {
983
906
  return;
984
907
  }
985
908
 
986
- // CRITICAL: Don't update lastTouchPosition here - update it AFTER clamping
987
- // This ensures that if the point was clamped, lastTouchPosition reflects the actual
988
- // touch position, allowing the next delta to be calculated correctly
989
-
990
- // ✅ CRITICAL: Ensure displayedContentRect is available
991
- var contentRect = displayedContentRect.current;
992
- var layout = displayedImageLayout.current;
909
+ // Les coordonnées de mouvement sont relatives au wrapper commun
910
+ var imageRect = imageDisplayRect.current;
911
+ var wrapper = commonWrapperLayout.current;
993
912
 
994
913
  // Recalculate if not available
995
- if (contentRect.width === 0 || contentRect.height === 0) {
996
- if (layout.width > 0 && layout.height > 0) {
997
- updateDisplayedContentRect(layout.width, layout.height);
998
- contentRect = displayedContentRect.current;
914
+ if (imageRect.width === 0 || imageRect.height === 0) {
915
+ if (wrapper.width > 0 && wrapper.height > 0 && originalImageDimensions.current.width > 0) {
916
+ updateImageDisplayRect(wrapper.width, wrapper.height);
917
+ imageRect = imageDisplayRect.current;
999
918
  }
1000
- // If still not available, use layout as fallback
1001
- if (contentRect.width === 0 || contentRect.height === 0) {
1002
- if (layout.width > 0 && layout.height > 0) {
1003
- contentRect = {
1004
- x: layout.x,
1005
- y: layout.y,
1006
- width: layout.width,
1007
- height: layout.height
919
+ // If still not available, use wrapper as fallback
920
+ if (imageRect.width === 0 || imageRect.height === 0) {
921
+ if (wrapper.width > 0 && wrapper.height > 0) {
922
+ imageRect = {
923
+ x: wrapper.x,
924
+ y: wrapper.y,
925
+ width: wrapper.width,
926
+ height: wrapper.height
1008
927
  };
1009
928
  } else {
1010
- console.warn("⚠️ Cannot move point: no layout dimensions available");
929
+ console.warn("⚠️ Cannot move point: wrapper or imageDisplayRect not available");
1011
930
  return;
1012
931
  }
1013
932
  }
1014
933
  }
1015
934
 
935
+ // ✅ CRITICAL: Calculate absolute bounds of imageDisplayRect within the wrapper
936
+ // imageRect is relative to wrapper, so we need to add wrapper offset
937
+ var imageRectX = wrapper.x + imageRect.x;
938
+ var imageRectY = wrapper.y + imageRect.y;
939
+ var contentRect = {
940
+ x: imageRectX,
941
+ y: imageRectY,
942
+ width: imageRect.width,
943
+ height: imageRect.height
944
+ };
945
+
1016
946
  // ✅ FREE DRAG: Ensure initial positions are set
1017
947
  if (!initialPointPosition.current) {
1018
948
  var currentPoint = points[selectedPointIndex.current];
1019
- if (currentPoint) {
949
+ if (currentPoint && typeof currentPoint.x === 'number' && typeof currentPoint.y === 'number') {
1020
950
  initialPointPosition.current = _objectSpread({}, currentPoint);
1021
951
  } else {
1022
- console.warn("⚠️ No point found for selected index");
952
+ console.warn("⚠️ No point found for selected index or invalid point data");
1023
953
  return;
1024
954
  }
1025
955
  }
1026
956
 
1027
- // ✅ CRITICAL: deltaX and deltaY are already calculated above using incremental approach
1028
- // This ensures smooth movement even when ScrollView affects coordinate updates
1029
-
1030
- // CRITICAL FIX: Use CURRENT point position (after previous clamping) instead of initial position
1031
- // This allows the point to move even if it was previously clamped to a limit
1032
- // If lastValidPosition exists (from previous clamping), use it; otherwise use initial position
1033
- var basePoint = lastValidPosition.current || initialPointPosition.current;
1034
-
1035
- // ✅ DEBUG: Log movement to identify vertical movement issues
1036
- if (Math.abs(deltaY) > 10) {
1037
- var _initialTouchPosition;
1038
- console.log("🔄 Movement detected:", {
1039
- deltaX: deltaX.toFixed(2),
1040
- deltaY: deltaY.toFixed(2),
1041
- currentY: currentY.toFixed(2),
1042
- initialY: (_initialTouchPosition = initialTouchPosition.current) === null || _initialTouchPosition === void 0 || (_initialTouchPosition = _initialTouchPosition.y) === null || _initialTouchPosition === void 0 ? void 0 : _initialTouchPosition.toFixed(2),
1043
- pointInitialY: initialPointPosition.current.y.toFixed(2),
1044
- basePointY: basePoint.y.toFixed(2),
1045
- usingLastValid: !!lastValidPosition.current
1046
- });
957
+ // ✅ NEW APPROACH: Use touchOffset to map touch position directly to point position
958
+ // This eliminates delta accumulation and "dead zone" issues completely
959
+ if (!touchOffset.current) {
960
+ console.warn("⚠️ touchOffset not initialized, cannot move point");
961
+ return;
1047
962
  }
1048
963
 
1049
- // ✅ FREE DRAG: Apply delta to CURRENT point position (not initial)
1050
- // - This allows movement even if point was previously clamped
1051
- // - No constraints on distance, angle, or edge length
1052
- // - No forced horizontal/vertical alignment
1053
- // - No angle locking
1054
- // - Direct mapping of gesture delta (dx, dy) - both X and Y treated equally
1055
- var newX = basePoint.x + deltaX;
1056
- var newY = basePoint.y + deltaY;
1057
-
1058
- // ONLY CONSTRAINT: Clamp to image bounds to keep points inside the image
1059
- // x ∈ [cx, cx + cw], y ∈ [cy, cy + ch]
1060
- // This is the ONLY constraint - no other geometry normalization
1061
- var _contentRect4 = contentRect,
1062
- cx = _contentRect4.x,
1063
- cy = _contentRect4.y,
1064
- cw = _contentRect4.width,
1065
- ch = _contentRect4.height;
1066
- // ✅ CRITICAL: Calculate bounds correctly - maxX = cx + cw, maxY = cy + ch
1067
- var maxX = cx + cw;
1068
- var maxY = cy + ch;
1069
- var boundedX = Math.max(cx, Math.min(newX, maxX));
1070
- var boundedY = Math.max(cy, Math.min(newY, maxY));
1071
-
1072
- // DEBUG: Log bounds calculation to verify clamping is correct
1073
- if (Math.abs(newY - boundedY) > 1) {
1074
- console.log("🔍 Y coordinate clamping:", {
1075
- newY: newY.toFixed(2),
1076
- boundedY: boundedY.toFixed(2),
1077
- cy: cy.toFixed(2),
1078
- maxY: maxY.toFixed(2),
1079
- ch: ch.toFixed(2),
1080
- deltaY: deltaY.toFixed(2),
1081
- isAtMaxLimit: Math.abs(boundedY - maxY) < 0.01,
1082
- isAtMinLimit: Math.abs(boundedY - cy) < 0.01
964
+ // ✅ DIRECT MAPPING: newPosition = touchPosition + offset
965
+ // No delta accumulation, no zone morte
966
+ var newX = currentX + touchOffset.current.x;
967
+ var newY = currentY + touchOffset.current.y;
968
+
969
+ // SEPARATE DRAG BOUNDS vs CROP BOUNDS
970
+ var cx = contentRect.x,
971
+ cy = contentRect.y,
972
+ cw = contentRect.width,
973
+ ch = contentRect.height;
974
+
975
+ // STRICT BOUNDS: For final crop safety (imageDisplayRect)
976
+ var strictMinX = cx;
977
+ var strictMaxX = cx + cw;
978
+ var strictMinY = cy;
979
+ var strictMaxY = cy + ch;
980
+
981
+ // ✅ DRAG BOUNDS: Allow movement ANYWHERE in wrapper during drag
982
+ // Points can move freely across the entire screen for maximum flexibility
983
+ // They will be clamped to imageDisplayRect only on release for safe cropping
984
+ var wrapperRect = wrapper;
985
+ var overshootMinX = wrapperRect.x;
986
+ var overshootMaxX = wrapperRect.x + wrapperRect.width;
987
+ var overshootMinY = wrapperRect.y;
988
+ var overshootMaxY = wrapperRect.y + wrapperRect.height;
989
+
990
+ // ✅ DRAG BOUNDS: Clamp ONLY to overshootBounds during drag (NOT strictBounds)
991
+ var dragX = Math.max(overshootMinX, Math.min(newX, overshootMaxX));
992
+ var dragY = Math.max(overshootMinY, Math.min(newY, overshootMaxY));
993
+
994
+ // ✅ UPDATE POINT: Use drag bounds (overshoot) - allows visual freedom
995
+ var updatedPoint = {
996
+ x: dragX,
997
+ y: dragY
998
+ };
999
+
1000
+ // ✅ CRITICAL: Detect if point is AT overshoot boundary (not just clamped)
1001
+ // Check if point is exactly at overshootMin/Max (within 1px tolerance)
1002
+ var isAtOvershootMinX = Math.abs(dragX - overshootMinX) < 1;
1003
+ var isAtOvershootMaxX = Math.abs(dragX - overshootMaxX) < 1;
1004
+ var isAtOvershootMinY = Math.abs(dragY - overshootMinY) < 1;
1005
+ var isAtOvershootMaxY = Math.abs(dragY - overshootMaxY) < 1;
1006
+ var isAtBoundaryX = isAtOvershootMinX || isAtOvershootMaxX;
1007
+ var isAtBoundaryY = isAtOvershootMinY || isAtOvershootMaxY;
1008
+
1009
+ // Only recalculate offset when FIRST hitting boundary (transition free → boundary)
1010
+ var justHitBoundaryX = isAtBoundaryX && !wasClampedLastFrame.current.x;
1011
+ var justHitBoundaryY = isAtBoundaryY && !wasClampedLastFrame.current.y;
1012
+ if (justHitBoundaryX || justHitBoundaryY) {
1013
+ // Point JUST hit overshoot boundary - recalculate offset once
1014
+ var newOffsetX = justHitBoundaryX ? dragX - currentX : touchOffset.current.x;
1015
+ var newOffsetY = justHitBoundaryY ? dragY - currentY : touchOffset.current.y;
1016
+ touchOffset.current = {
1017
+ x: newOffsetX,
1018
+ y: newOffsetY
1019
+ };
1020
+ console.log("✅ OFFSET RECALCULATED (hit boundary):", {
1021
+ axis: justHitBoundaryX ? 'X' : 'Y',
1022
+ touchY: currentY.toFixed(2),
1023
+ dragY: dragY.toFixed(2),
1024
+ newOffsetY: touchOffset.current.y.toFixed(2),
1025
+ note: "First contact with boundary - offset locked"
1083
1026
  });
1084
1027
  }
1085
1028
 
1086
- // DEBUG: Log if clamping is limiting movement
1087
- if (Math.abs(newY - boundedY) > 1 || Math.abs(newX - boundedX) > 1) {
1088
- console.log("⚠️ Movement clamped:", {
1089
- requested: {
1090
- x: newX.toFixed(2),
1091
- y: newY.toFixed(2)
1092
- },
1093
- clamped: {
1094
- x: boundedX.toFixed(2),
1095
- y: boundedY.toFixed(2)
1096
- },
1097
- contentRect: {
1098
- x: cx.toFixed(2),
1099
- y: cy.toFixed(2),
1100
- width: cw.toFixed(2),
1101
- height: ch.toFixed(2)
1102
- }
1029
+ // Update boundary state for next frame
1030
+ wasClampedLastFrame.current = {
1031
+ x: isAtBoundaryX,
1032
+ y: isAtBoundaryY
1033
+ };
1034
+
1035
+ // ✅ DEBUG: Log when in overshoot zone (only when not at boundary)
1036
+ var isInOvershootY = dragY < strictMinY || dragY > strictMaxY;
1037
+ if (isInOvershootY && !isAtBoundaryY) {
1038
+ console.log("🎯 IN OVERSHOOT ZONE:", {
1039
+ touchY: currentY.toFixed(2),
1040
+ appliedY: dragY.toFixed(2),
1041
+ overshootRange: "".concat(overshootMinY.toFixed(2), " - ").concat(overshootMaxY.toFixed(2)),
1042
+ strictRange: "".concat(strictMinY.toFixed(2), " - ").concat(strictMaxY.toFixed(2))
1103
1043
  });
1104
1044
  }
1105
1045
 
1106
- // ✅ FREE DRAG: Update point directly
1107
- // - No polygon simplification
1108
- // - No reordering of points
1109
- // - No geometry normalization during drag
1110
- var updatedPoint = {
1111
- x: boundedX,
1112
- y: boundedY
1113
- };
1114
- // ✅ CRITICAL: Always update lastValidPosition with the clamped position
1115
- // This ensures that the next movement calculation uses the current (clamped) position
1116
- // instead of the initial position, allowing movement even after clamping
1117
- lastValidPosition.current = updatedPoint;
1118
-
1119
- // ✅ CRITICAL: Update lastTouchPosition AFTER clamping
1120
- // This ensures that the next delta calculation is relative to the current touch position
1121
- // If the point was clamped, this allows movement in the opposite direction on the next move
1046
+ // ✅ Update lastValidPosition ONLY if point is within strictBounds
1047
+ var isStrictlyValid = dragX >= strictMinX && dragX <= strictMaxX && dragY >= strictMinY && dragY <= strictMaxY;
1048
+ if (isStrictlyValid) {
1049
+ lastValidPosition.current = updatedPoint;
1050
+ }
1051
+
1052
+ // ✅ Update lastTouchPosition for next frame (simple tracking)
1122
1053
  lastTouchPosition.current = {
1123
1054
  x: currentX,
1124
1055
  y: currentY
1125
1056
  };
1057
+
1058
+ // ✅ DEBUG: Log the point update before setPoints
1059
+ console.log("📍 UPDATING POINT:", {
1060
+ index: selectedPointIndex.current,
1061
+ newX: updatedPoint.x.toFixed(2),
1062
+ newY: updatedPoint.y.toFixed(2),
1063
+ touchX: currentX.toFixed(2),
1064
+ touchY: currentY.toFixed(2),
1065
+ offsetX: touchOffset.current.x.toFixed(2),
1066
+ offsetY: touchOffset.current.y.toFixed(2)
1067
+ });
1126
1068
  setPoints(function (prev) {
1127
- return prev.map(function (p, i) {
1128
- return i === selectedPointIndex.current ? updatedPoint : p;
1069
+ var _prev$pointIndex, _newPoints$pointIndex, _prev$pointIndex2, _newPoints$pointIndex2;
1070
+ // SAFETY: Ensure prev is a valid array
1071
+ if (!Array.isArray(prev) || prev.length === 0) {
1072
+ return prev;
1073
+ }
1074
+ var pointIndex = selectedPointIndex.current;
1075
+ // ✅ SAFETY: Validate pointIndex
1076
+ if (pointIndex === null || pointIndex === undefined || pointIndex < 0 || pointIndex >= prev.length) {
1077
+ return prev;
1078
+ }
1079
+
1080
+ // ✅ SAFETY: Filter out any invalid points and update the selected one
1081
+ var newPoints = prev.map(function (p, i) {
1082
+ if (i === pointIndex) {
1083
+ return updatedPoint;
1084
+ }
1085
+ // ✅ SAFETY: Ensure existing points are valid
1086
+ if (p && typeof p.x === 'number' && typeof p.y === 'number') {
1087
+ return p;
1088
+ }
1089
+ // If point is invalid, return a default point (shouldn't happen, but safety first)
1090
+ return {
1091
+ x: 0,
1092
+ y: 0
1093
+ };
1129
1094
  });
1095
+
1096
+ // ✅ DEBUG: Log the state update
1097
+ console.log("✅ STATE UPDATED:", {
1098
+ index: pointIndex,
1099
+ oldY: (_prev$pointIndex = prev[pointIndex]) === null || _prev$pointIndex === void 0 ? void 0 : _prev$pointIndex.y.toFixed(2),
1100
+ newY: (_newPoints$pointIndex = newPoints[pointIndex]) === null || _newPoints$pointIndex === void 0 ? void 0 : _newPoints$pointIndex.y.toFixed(2),
1101
+ changed: Math.abs(((_prev$pointIndex2 = prev[pointIndex]) === null || _prev$pointIndex2 === void 0 ? void 0 : _prev$pointIndex2.y) - ((_newPoints$pointIndex2 = newPoints[pointIndex]) === null || _newPoints$pointIndex2 === void 0 ? void 0 : _newPoints$pointIndex2.y)) > 0.01
1102
+ });
1103
+ return newPoints;
1130
1104
  });
1131
1105
  };
1132
1106
  var handleRelease = function handleRelease() {
1107
+ var wasDragging = selectedPointIndex.current !== null;
1108
+
1109
+ // ✅ CRITICAL: Reset drag state when drag ends
1110
+ touchOffset.current = null;
1111
+ wasClampedLastFrame.current = {
1112
+ x: false,
1113
+ y: false
1114
+ };
1115
+
1116
+ // ✅ VISUAL OVERSHOOT: Clamp points back to imageDisplayRect when drag ends
1117
+ // This ensures final crop is always within valid image bounds
1118
+ if (wasDragging && selectedPointIndex.current !== null) {
1119
+ var wrapper = commonWrapperLayout.current;
1120
+ var imageRect = imageDisplayRect.current;
1121
+
1122
+ // Recalculate imageDisplayRect if needed
1123
+ if (imageRect.width === 0 || imageRect.height === 0) {
1124
+ if (wrapper.width > 0 && wrapper.height > 0 && originalImageDimensions.current.width > 0) {
1125
+ updateImageDisplayRect(wrapper.width, wrapper.height);
1126
+ imageRect = imageDisplayRect.current;
1127
+ }
1128
+ }
1129
+ if (imageRect.width > 0 && imageRect.height > 0) {
1130
+ var imageRectX = wrapper.x + imageRect.x;
1131
+ var imageRectY = wrapper.y + imageRect.y;
1132
+ var imageRectMaxX = imageRectX + imageRect.width;
1133
+ var imageRectMaxY = imageRectY + imageRect.height;
1134
+
1135
+ // Clamp the dragged point back to strict image bounds
1136
+ setPoints(function (prev) {
1137
+ // ✅ SAFETY: Ensure prev is a valid array
1138
+ if (!Array.isArray(prev) || prev.length === 0) {
1139
+ return prev;
1140
+ }
1141
+ var pointIndex = selectedPointIndex.current;
1142
+ // ✅ SAFETY: Validate pointIndex and ensure point exists
1143
+ if (pointIndex === null || pointIndex === undefined || pointIndex < 0 || pointIndex >= prev.length) {
1144
+ return prev;
1145
+ }
1146
+ var point = prev[pointIndex];
1147
+ // ✅ SAFETY: Ensure point exists and has valid x/y properties
1148
+ if (!point || typeof point.x !== 'number' || typeof point.y !== 'number') {
1149
+ return prev;
1150
+ }
1151
+ var clampedPoint = {
1152
+ x: Math.max(imageRectX, Math.min(point.x, imageRectMaxX)),
1153
+ y: Math.max(imageRectY, Math.min(point.y, imageRectMaxY))
1154
+ };
1155
+
1156
+ // Only update if point was outside bounds
1157
+ if (point.x !== clampedPoint.x || point.y !== clampedPoint.y) {
1158
+ console.log("🔒 Clamping point back to image bounds on release:", {
1159
+ before: {
1160
+ x: point.x.toFixed(2),
1161
+ y: point.y.toFixed(2)
1162
+ },
1163
+ after: {
1164
+ x: clampedPoint.x.toFixed(2),
1165
+ y: clampedPoint.y.toFixed(2)
1166
+ },
1167
+ bounds: {
1168
+ minX: imageRectX.toFixed(2),
1169
+ maxX: imageRectMaxX.toFixed(2),
1170
+ minY: imageRectY.toFixed(2),
1171
+ maxY: imageRectMaxY.toFixed(2)
1172
+ }
1173
+ });
1174
+ return prev.map(function (p, i) {
1175
+ return i === pointIndex ? clampedPoint : p;
1176
+ });
1177
+ }
1178
+ return prev;
1179
+ });
1180
+ }
1181
+ }
1182
+
1133
1183
  // ✅ FREE DRAG: Clear initial positions when drag ends
1134
1184
  initialTouchPosition.current = null;
1135
1185
  initialPointPosition.current = null;
1136
- var wasDragging = selectedPointIndex.current !== null;
1186
+ lastValidPosition.current = null;
1137
1187
  selectedPointIndex.current = null;
1138
1188
 
1139
1189
  // ✅ CRITICAL: Re-enable parent ScrollView scrolling when drag ends
@@ -1150,6 +1200,7 @@ var ImageCropper = function ImageCropper(_ref) {
1150
1200
  };
1151
1201
  var handleReset = function handleReset() {
1152
1202
  // setPoints([]);
1203
+ hasInitializedCropBox.current = false; // ✅ CRITICAL: Reset guard to allow reinitialization
1153
1204
  initializeCropBox();
1154
1205
  };
1155
1206
 
@@ -1232,6 +1283,9 @@ var ImageCropper = function ImageCropper(_ref) {
1232
1283
  greenFrame: frameData.greenFrame,
1233
1284
  capturedImageSize: frameData.capturedImageSize
1234
1285
  };
1286
+ // ✅ CRITICAL: Set imageSource to 'camera' IMMEDIATELY to prevent Image.getSize() from being called
1287
+ imageSource.current = 'camera';
1288
+ hasInitializedCropBox.current = false; // Reset guard for new camera image
1235
1289
  console.log("✅ Camera frame data received:", cameraFrameData.current);
1236
1290
  }
1237
1291
  setImage(uri);
@@ -1245,10 +1299,124 @@ var ImageCropper = function ImageCropper(_ref) {
1245
1299
  onCancel: function onCancel() {
1246
1300
  return setShowCustomCamera(false);
1247
1301
  }
1248
- }) : /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, !showResult && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1249
- style: image ? _ImageCropperStyles["default"].buttonContainer : _ImageCropperStyles["default"].centerButtonsContainer
1250
- }, image && _reactNative.Platform.OS === 'android' && /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1251
- style: _ImageCropperStyles["default"].iconButton,
1302
+ }) : /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1303
+ style: {
1304
+ width: _reactNative.Dimensions.get('window').width,
1305
+ aspectRatio: 9 / 16,
1306
+ borderRadius: 30,
1307
+ overflow: 'hidden',
1308
+ alignItems: 'center',
1309
+ justifyContent: 'center',
1310
+ position: 'relative',
1311
+ backgroundColor: 'black',
1312
+ marginBottom: 0 // ✅ Les boutons sont maintenant en position absolue en bas
1313
+ },
1314
+ ref: commonWrapperRef,
1315
+ onLayout: onCommonWrapperLayout
1316
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1317
+ ref: viewRef,
1318
+ collapsable: false,
1319
+ style: _reactNative.StyleSheet.absoluteFill,
1320
+ onStartShouldSetResponder: function onStartShouldSetResponder() {
1321
+ return true;
1322
+ },
1323
+ onMoveShouldSetResponder: function onMoveShouldSetResponder(evt, gestureState) {
1324
+ // ✅ CRITICAL: Always capture movement when a point is selected
1325
+ // This ensures vertical movement is captured correctly
1326
+ if (selectedPointIndex.current !== null) {
1327
+ return true;
1328
+ }
1329
+ // ✅ CRITICAL: Capture ANY movement immediately (even 0px) to prevent ScrollView interception
1330
+ // This is especially important for vertical movement which ScrollView tries to intercept
1331
+ // We return true for ANY movement to ensure we capture it before ScrollView
1332
+ var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
1333
+ if (hasMovement && Math.abs(gestureState.dy) > 5) {
1334
+ console.log("🔄 Vertical movement detected in responder:", {
1335
+ dx: gestureState.dx.toFixed(2),
1336
+ dy: gestureState.dy.toFixed(2),
1337
+ selectedPoint: selectedPointIndex.current
1338
+ });
1339
+ }
1340
+ return true;
1341
+ },
1342
+ onResponderGrant: function onResponderGrant(e) {
1343
+ // ✅ CRITICAL: Grant responder immediately to prevent ScrollView from intercepting
1344
+ // This ensures we capture all movement, especially vertical
1345
+ // Handle tap to select point if needed
1346
+ if (selectedPointIndex.current === null) {
1347
+ handleTap(e);
1348
+ }
1349
+ },
1350
+ onResponderStart: handleTap,
1351
+ onResponderMove: function onResponderMove(e) {
1352
+ // ✅ CRITICAL: Always handle move events to ensure smooth movement in all directions
1353
+ // This is called for every move event, ensuring vertical movement is captured
1354
+ // handleMove now uses incremental delta calculation which is more reliable
1355
+ handleMove(e);
1356
+ },
1357
+ onResponderRelease: handleRelease,
1358
+ onResponderTerminationRequest: function onResponderTerminationRequest() {
1359
+ // ✅ CRITICAL: Never allow termination when dragging a point
1360
+ // This prevents ScrollView from stealing the responder during vertical movement
1361
+ return selectedPointIndex.current === null;
1362
+ }
1363
+ // ✅ CRITICAL: Prevent parent ScrollView from intercepting touches
1364
+ // Capture responder BEFORE parent ScrollView can intercept
1365
+ ,
1366
+ onStartShouldSetResponderCapture: function onStartShouldSetResponderCapture() {
1367
+ // Always capture start events
1368
+ return true;
1369
+ },
1370
+ onMoveShouldSetResponderCapture: function onMoveShouldSetResponderCapture(evt, gestureState) {
1371
+ // ✅ CRITICAL: Always capture movement events before parent ScrollView
1372
+ // This is essential for vertical movement which ScrollView tries to intercept
1373
+ // Especially important when a point is selected or when there's any movement
1374
+ if (selectedPointIndex.current !== null) {
1375
+ return true;
1376
+ }
1377
+ // ✅ CRITICAL: Capture movement BEFORE ScrollView can intercept
1378
+ // This ensures we get vertical movement even if ScrollView tries to steal it
1379
+ var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
1380
+ return hasMovement;
1381
+ }
1382
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
1383
+ source: {
1384
+ uri: image
1385
+ },
1386
+ style: _ImageCropperStyles["default"].image,
1387
+ resizeMode: (_cameraFrameData$curr3 = cameraFrameData.current) !== null && _cameraFrameData$curr3 !== void 0 && _cameraFrameData$curr3.greenFrame ? 'cover' : 'contain',
1388
+ onLayout: onImageLayout
1389
+ }), /*#__PURE__*/_react["default"].createElement(_reactNativeSvg["default"], {
1390
+ style: _ImageCropperStyles["default"].overlay,
1391
+ pointerEvents: "none"
1392
+ }, function () {
1393
+ // ✅ Use wrapper dimensions for SVG path (wrapper coordinates)
1394
+ var wrapperWidth = commonWrapperLayout.current.width || _reactNative.Dimensions.get('window').width;
1395
+ var wrapperHeight = commonWrapperLayout.current.height || _reactNative.Dimensions.get('window').width * 16 / 9;
1396
+ return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
1397
+ d: "M 0 0 H ".concat(wrapperWidth, " V ").concat(wrapperHeight, " H 0 Z ").concat(createPath()),
1398
+ fill: showResult ? 'white' : 'rgba(0, 0, 0, 0.8)',
1399
+ fillRule: "evenodd"
1400
+ }), !showResult && points.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
1401
+ d: createPath(),
1402
+ fill: "transparent",
1403
+ stroke: "white",
1404
+ strokeWidth: 2
1405
+ }), !showResult && points.map(function (point, index) {
1406
+ return /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Circle, {
1407
+ key: index,
1408
+ cx: point.x,
1409
+ cy: point.y,
1410
+ r: 10,
1411
+ fill: "white"
1412
+ });
1413
+ }));
1414
+ }()))), !showResult && image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1415
+ style: [_ImageCropperStyles["default"].buttonContainerBelow, {
1416
+ paddingBottom: Math.max(insets.bottom, 16)
1417
+ }]
1418
+ }, _reactNative.Platform.OS === 'android' && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1419
+ style: _ImageCropperStyles["default"].rotationButton,
1252
1420
  onPress: function onPress() {
1253
1421
  return enableRotation && rotatePreviewImage(90);
1254
1422
  },
@@ -1257,50 +1425,36 @@ var ImageCropper = function ImageCropper(_ref) {
1257
1425
  name: "sync",
1258
1426
  size: 24,
1259
1427
  color: "white"
1260
- }))), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1428
+ })), /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1261
1429
  style: _ImageCropperStyles["default"].button,
1262
1430
  onPress: handleReset
1263
1431
  }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
1264
1432
  style: _ImageCropperStyles["default"].buttonText
1265
- }, "Reset")), image && /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1433
+ }, "Reset")), /*#__PURE__*/_react["default"].createElement(_reactNative.TouchableOpacity, {
1266
1434
  style: _ImageCropperStyles["default"].button,
1267
1435
  onPress: /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
1268
- var actualImageWidth, actualImageHeight, layout, contentRect, displayedWidth, displayedHeight, scaleX, scaleY, scale, originalUri, cropMeta, imagePoints, minX, minY, maxX, maxY, cropX, cropY, cropEndX, cropEndY, cropWidth, cropHeight, bbox, polygon, name, _t2;
1436
+ var actualImageWidth, actualImageHeight, layout, contentRect, displayedWidth, displayedHeight, isCoverMode, scale, coverOffsetX, coverOffsetY, scaledWidth, scaledHeight, originalUri, cropMeta, imagePoints, minX, minY, maxX, maxY, cropX, cropY, cropEndX, cropEndY, cropWidth, cropHeight, bbox, polygon, name, _t2;
1269
1437
  return _regenerator().w(function (_context2) {
1270
1438
  while (1) switch (_context2.p = _context2.n) {
1271
1439
  case 0:
1272
1440
  setIsLoading(true);
1273
1441
  _context2.p = 1;
1274
1442
  console.log("=== Starting pixel-perfect metadata export (no bitmap crop on mobile) ===");
1275
-
1276
- // ✅ REFACTORISATION : Utiliser les dimensions stockées (plus efficace)
1277
1443
  actualImageWidth = originalImageDimensions.current.width;
1278
- actualImageHeight = originalImageDimensions.current.height; // Vérifier que les dimensions sont valides
1444
+ actualImageHeight = originalImageDimensions.current.height;
1279
1445
  if (!(actualImageWidth === 0 || actualImageHeight === 0)) {
1280
1446
  _context2.n = 2;
1281
1447
  break;
1282
1448
  }
1283
1449
  throw new Error("Original image dimensions not available. Please wait for image to load.");
1284
1450
  case 2:
1285
- console.log("Original image dimensions:", {
1286
- width: actualImageWidth,
1287
- height: actualImageHeight
1288
- });
1289
-
1290
- // ✅ CORRECTION : Utiliser le rectangle de contenu réel (contain)
1291
- // ✅ CRITICAL: Recalculate displayedContentRect if not available
1292
1451
  layout = displayedImageLayout.current;
1293
- console.log("🔍 Checking displayedContentRect before crop:", {
1294
- displayedContentRect: displayedContentRect.current,
1295
- displayedImageLayout: layout,
1296
- originalImageDimensions: originalImageDimensions.current
1297
- });
1298
1452
  if (layout.width > 0 && layout.height > 0) {
1299
1453
  updateDisplayedContentRect(layout.width, layout.height);
1300
1454
  }
1301
1455
  contentRect = displayedContentRect.current;
1302
1456
  displayedWidth = contentRect.width;
1303
- displayedHeight = contentRect.height; // Vérifier que les dimensions d'affichage sont valides
1457
+ displayedHeight = contentRect.height;
1304
1458
  if (!(displayedWidth === 0 || displayedHeight === 0)) {
1305
1459
  _context2.n = 4;
1306
1460
  break;
@@ -1309,8 +1463,6 @@ var ImageCropper = function ImageCropper(_ref) {
1309
1463
  _context2.n = 3;
1310
1464
  break;
1311
1465
  }
1312
- console.warn("⚠️ displayedContentRect not available, using displayedImageLayout as fallback");
1313
- // Use layout dimensions as fallback (assuming no letterboxing)
1314
1466
  contentRect = {
1315
1467
  x: layout.x,
1316
1468
  y: layout.y,
@@ -1319,69 +1471,40 @@ var ImageCropper = function ImageCropper(_ref) {
1319
1471
  };
1320
1472
  displayedWidth = contentRect.width;
1321
1473
  displayedHeight = contentRect.height;
1322
- // Update the ref for consistency
1323
1474
  displayedContentRect.current = contentRect;
1324
1475
  _context2.n = 4;
1325
1476
  break;
1326
1477
  case 3:
1327
- throw new Error("Displayed image dimensions not available. Image may not be laid out yet. Please wait a moment and try again.");
1478
+ throw new Error("Displayed image dimensions not available.");
1328
1479
  case 4:
1329
- console.log("✅ Using contentRect for crop:", contentRect);
1330
- console.log("Displayed image dimensions:", {
1331
- width: displayedWidth,
1332
- height: displayedHeight
1333
- });
1334
-
1335
- // ✅ CORRECTION : avec resizeMode='contain', l'échelle est UNIFORME + offsets.
1336
- // Vérifier que le scale est cohérent (même ratio pour width et height)
1337
- scaleX = actualImageWidth / displayedWidth;
1338
- scaleY = actualImageHeight / displayedHeight; // Pour resizeMode='contain', scaleX et scaleY doivent être égaux
1339
- // Si ce n'est pas le cas, il y a un problème de calcul
1340
- if (Math.abs(scaleX - scaleY) > 0.01) {
1341
- console.warn("Scale mismatch detected! This may cause incorrect crop coordinates.", {
1342
- scaleX: scaleX,
1343
- scaleY: scaleY,
1344
- actualImageWidth: actualImageWidth,
1345
- actualImageHeight: actualImageHeight,
1346
- displayedWidth: displayedWidth,
1347
- displayedHeight: displayedHeight
1348
- });
1480
+ isCoverMode = !!(cameraFrameData.current && cameraFrameData.current.greenFrame);
1481
+ coverOffsetX = 0, coverOffsetY = 0;
1482
+ if (isCoverMode) {
1483
+ scale = Math.max(displayedWidth / actualImageWidth, displayedHeight / actualImageHeight);
1484
+ scaledWidth = actualImageWidth * scale;
1485
+ scaledHeight = actualImageHeight * scale;
1486
+ coverOffsetX = (scaledWidth - displayedWidth) / 2;
1487
+ coverOffsetY = (scaledHeight - displayedHeight) / 2;
1488
+ } else {
1489
+ scale = actualImageWidth / displayedWidth;
1349
1490
  }
1350
- scale = scaleX; // Utiliser scaleX (ou scaleY, ils devraient être égaux)
1351
- console.log("Scale factor (contain, uniform):", {
1352
- scale: scale,
1353
- scaleX: scaleX,
1354
- scaleY: scaleY,
1355
- contentRect: contentRect,
1356
- actualImageSize: {
1357
- width: actualImageWidth,
1358
- height: actualImageHeight
1359
- },
1360
- displayedSize: {
1361
- width: displayedWidth,
1362
- height: displayedHeight
1363
- }
1364
- });
1365
1491
  originalUri = sourceImageUri.current || image;
1366
1492
  cropMeta = null;
1367
1493
  if (points.length > 0) {
1368
1494
  try {
1369
- console.log("Calculating crop boundaries from points...");
1370
- console.log("Points (display coordinates):", points);
1371
- console.log("Content rect (offsets):", contentRect);
1372
-
1373
- // ✅ CORRECTION : conversion display -> image px (contain) avec offsets
1374
- // S'assurer que les points sont bien dans les limites de l'image affichée
1375
1495
  imagePoints = points.map(function (point) {
1376
- // Clamp les coordonnées d'affichage à la zone réelle de l'image
1377
- var clampedX = Math.max(contentRect.x, Math.min(point.x, contentRect.x + contentRect.width));
1378
- var clampedY = Math.max(contentRect.y, Math.min(point.y, contentRect.y + contentRect.height));
1379
-
1380
- // Convertir en coordonnées de l'image originale
1381
- var origX = (clampedX - contentRect.x) * scale;
1382
- var origY = (clampedY - contentRect.y) * scale;
1383
-
1384
- // Clamp aux dimensions de l'image originale
1496
+ var clampedX, clampedY, origX, origY;
1497
+ if (isCoverMode) {
1498
+ clampedX = Math.max(0, Math.min(point.x, contentRect.width));
1499
+ clampedY = Math.max(0, Math.min(point.y, contentRect.height));
1500
+ origX = (clampedX + coverOffsetX) / scale;
1501
+ origY = (clampedY + coverOffsetY) / scale;
1502
+ } else {
1503
+ clampedX = Math.max(contentRect.x, Math.min(point.x, contentRect.x + contentRect.width));
1504
+ clampedY = Math.max(contentRect.y, Math.min(point.y, contentRect.y + contentRect.height));
1505
+ origX = (clampedX - contentRect.x) * scale;
1506
+ origY = (clampedY - contentRect.y) * scale;
1507
+ }
1385
1508
  var finalX = Math.max(0, Math.min(origX, actualImageWidth));
1386
1509
  var finalY = Math.max(0, Math.min(origY, actualImageHeight));
1387
1510
  return {
@@ -1389,9 +1512,6 @@ var ImageCropper = function ImageCropper(_ref) {
1389
1512
  y: finalY
1390
1513
  };
1391
1514
  });
1392
- console.log("Converted image points (original coordinates):", imagePoints);
1393
-
1394
- // Calculer la bounding box : min X, min Y, max X, max Y
1395
1515
  minX = Math.min.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
1396
1516
  return p.x;
1397
1517
  })));
@@ -1403,36 +1523,20 @@ var ImageCropper = function ImageCropper(_ref) {
1403
1523
  })));
1404
1524
  maxY = Math.max.apply(Math, _toConsumableArray(imagePoints.map(function (p) {
1405
1525
  return p.y;
1406
- }))); // ✅ CORRECTION : arrondi "conservateur" (floor origin + ceil end)
1407
- // évite de rogner des pixels et réduit le risque de crop plus petit (perte de détails).
1526
+ })));
1408
1527
  cropX = Math.max(0, Math.floor(minX));
1409
1528
  cropY = Math.max(0, Math.floor(minY));
1410
1529
  cropEndX = Math.min(actualImageWidth, Math.ceil(maxX));
1411
1530
  cropEndY = Math.min(actualImageHeight, Math.ceil(maxY));
1412
1531
  cropWidth = Math.max(0, cropEndX - cropX);
1413
1532
  cropHeight = Math.max(0, cropEndY - cropY);
1414
- console.log("Crop parameters (pixel-perfect):", {
1415
- x: cropX,
1416
- y: cropY,
1417
- width: cropWidth,
1418
- height: cropHeight,
1419
- imageWidth: actualImageWidth,
1420
- imageHeight: actualImageHeight,
1421
- boundingBox: {
1422
- minX: minX,
1423
- minY: minY,
1424
- maxX: maxX,
1425
- maxY: maxY
1426
- }
1427
- });
1428
1533
  if (cropWidth > 0 && cropHeight > 0) {
1429
- // 1) bbox in ORIGINAL image pixel coords
1430
1534
  bbox = {
1431
1535
  x: cropX,
1432
1536
  y: cropY,
1433
1537
  width: cropWidth,
1434
1538
  height: cropHeight
1435
- }; // 2) polygon points relative to bbox (still in ORIGINAL pixel grid)
1539
+ };
1436
1540
  polygon = imagePoints.map(function (point) {
1437
1541
  return {
1438
1542
  x: point.x - cropX,
@@ -1448,19 +1552,13 @@ var ImageCropper = function ImageCropper(_ref) {
1448
1552
  height: actualImageHeight
1449
1553
  }
1450
1554
  };
1451
- console.log("Crop meta ready:", cropMeta);
1452
- } else {
1453
- console.warn("Invalid crop dimensions, cannot export crop meta");
1454
1555
  }
1455
1556
  } catch (cropError) {
1456
1557
  console.error("Error computing crop meta:", cropError);
1457
1558
  }
1458
- } else {
1459
- console.log("No crop points defined, using original image");
1460
1559
  }
1461
1560
  name = "IMAGE XTK".concat(Date.now());
1462
1561
  if (onConfirm) {
1463
- console.log("Calling onConfirm with:", originalUri, name, cropMeta);
1464
1562
  onConfirm(originalUri, name, cropMeta);
1465
1563
  }
1466
1564
  _context2.n = 6;
@@ -1483,120 +1581,20 @@ var ImageCropper = function ImageCropper(_ref) {
1483
1581
  }))
1484
1582
  }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
1485
1583
  style: _ImageCropperStyles["default"].buttonText
1486
- }, "Confirm"))), image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1487
- ref: viewRef,
1488
- collapsable: false,
1489
- style: showFullScreenCapture ? _ImageCropperStyles["default"].fullscreenImageContainer : _ImageCropperStyles["default"].imageContainer,
1490
- onStartShouldSetResponder: function onStartShouldSetResponder() {
1491
- return true;
1492
- },
1493
- onMoveShouldSetResponder: function onMoveShouldSetResponder(evt, gestureState) {
1494
- // ✅ CRITICAL: Always capture movement when a point is selected
1495
- // This ensures vertical movement is captured correctly
1496
- if (selectedPointIndex.current !== null) {
1497
- return true;
1498
- }
1499
- // ✅ CRITICAL: Capture ANY movement immediately (even 0px) to prevent ScrollView interception
1500
- // This is especially important for vertical movement which ScrollView tries to intercept
1501
- // We return true for ANY movement to ensure we capture it before ScrollView
1502
- var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
1503
- if (hasMovement && Math.abs(gestureState.dy) > 5) {
1504
- console.log("🔄 Vertical movement detected in responder:", {
1505
- dx: gestureState.dx.toFixed(2),
1506
- dy: gestureState.dy.toFixed(2),
1507
- selectedPoint: selectedPointIndex.current
1508
- });
1509
- }
1510
- return true;
1511
- },
1512
- onResponderGrant: function onResponderGrant(e) {
1513
- // ✅ CRITICAL: Grant responder immediately to prevent ScrollView from intercepting
1514
- // This ensures we capture all movement, especially vertical
1515
- // Handle tap to select point if needed
1516
- if (selectedPointIndex.current === null) {
1517
- handleTap(e);
1518
- }
1519
- },
1520
- onResponderStart: handleTap,
1521
- onResponderMove: function onResponderMove(e) {
1522
- // ✅ CRITICAL: Always handle move events to ensure smooth movement in all directions
1523
- // This is called for every move event, ensuring vertical movement is captured
1524
- // handleMove now uses incremental delta calculation which is more reliable
1525
- handleMove(e);
1526
- },
1527
- onResponderRelease: handleRelease,
1528
- onResponderTerminationRequest: function onResponderTerminationRequest() {
1529
- // ✅ CRITICAL: Never allow termination when dragging a point
1530
- // This prevents ScrollView from stealing the responder during vertical movement
1531
- return selectedPointIndex.current === null;
1532
- }
1533
- // ✅ CRITICAL: Prevent parent ScrollView from intercepting touches
1534
- // Capture responder BEFORE parent ScrollView can intercept
1535
- ,
1536
- onStartShouldSetResponderCapture: function onStartShouldSetResponderCapture() {
1537
- // Always capture start events
1538
- return true;
1539
- },
1540
- onMoveShouldSetResponderCapture: function onMoveShouldSetResponderCapture(evt, gestureState) {
1541
- // ✅ CRITICAL: Always capture movement events before parent ScrollView
1542
- // This is essential for vertical movement which ScrollView tries to intercept
1543
- // Especially important when a point is selected or when there's any movement
1544
- if (selectedPointIndex.current !== null) {
1545
- return true;
1546
- }
1547
- // Also capture if there's any movement to prevent ScrollView from intercepting
1548
- var hasMovement = Math.abs(gestureState.dx) > 0 || Math.abs(gestureState.dy) > 0;
1549
- if (hasMovement && Math.abs(gestureState.dy) > 2) {
1550
- console.log("🔄 Capturing vertical movement before ScrollView:", {
1551
- dy: gestureState.dy.toFixed(2)
1552
- });
1553
- }
1554
- return hasMovement;
1555
- }
1556
- // ✅ CRITICAL: Prevent ScrollView from scrolling by stopping propagation
1557
- ,
1558
- onResponderReject: function onResponderReject() {
1559
- console.warn("⚠️ Responder rejected - ScrollView may intercept");
1560
- }
1561
- }, /*#__PURE__*/_react["default"].createElement(_reactNative.Image, {
1562
- source: {
1563
- uri: image
1564
- },
1565
- style: _ImageCropperStyles["default"].image,
1566
- onLayout: onImageLayout
1567
- }), /*#__PURE__*/_react["default"].createElement(_reactNativeSvg["default"], {
1568
- style: _ImageCropperStyles["default"].overlay,
1569
- pointerEvents: "none"
1570
- }, /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
1571
- d: "M 0 0 H ".concat(imageMeasure.current.width, " V ").concat(imageMeasure.current.height, " H 0 Z ").concat(createPath()),
1572
- fill: showResult ? 'white' : 'rgba(0, 0, 0, 0.8)',
1573
- fillRule: "evenodd"
1574
- }), !showResult && points.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Path, {
1575
- d: createPath(),
1576
- fill: "transparent",
1577
- stroke: "white",
1578
- strokeWidth: 2
1579
- }), !showResult && points.map(function (point, index) {
1580
- return /*#__PURE__*/_react["default"].createElement(_reactNativeSvg.Circle, {
1581
- key: index,
1582
- cx: point.x,
1583
- cy: point.y,
1584
- r: 10,
1585
- fill: "white"
1586
- });
1587
- })))), showMaskView && maskImageUri && maskPoints.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1584
+ }, "Confirm"))), !showResult && !image && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1585
+ style: _ImageCropperStyles["default"].centerButtonsContainer
1586
+ }, /*#__PURE__*/_react["default"].createElement(_reactNative.Text, {
1587
+ style: _ImageCropperStyles["default"].welcomeText
1588
+ }, "S\xE9lectionnez une image"))), showMaskView && maskImageUri && maskPoints.length > 0 && /*#__PURE__*/_react["default"].createElement(_reactNative.View, {
1588
1589
  style: {
1589
1590
  position: 'absolute',
1590
1591
  left: -maskDimensions.width - 100,
1591
- // Hors écran mais pas trop loin
1592
1592
  top: -maskDimensions.height - 100,
1593
1593
  width: maskDimensions.width,
1594
1594
  height: maskDimensions.height,
1595
1595
  opacity: 1,
1596
- // Opacité normale pour la capture
1597
1596
  pointerEvents: 'none',
1598
1597
  zIndex: 9999,
1599
- // Z-index élevé pour s'assurer qu'elle est au-dessus
1600
1598
  overflow: 'hidden'
1601
1599
  },
1602
1600
  collapsable: false