@lmb-it/kitsconcerto-native 0.1.285 → 0.1.286
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/mobile/src/ui/accordion/index.js +2 -2
- package/components/ImageCropper/compression.cjs +63 -0
- package/components/ImageCropper/compression.cjs.map +1 -0
- package/components/ImageCropper/compression.js +58 -0
- package/components/ImageCropper/compression.js.map +1 -0
- package/components/ImageCropper/constants.cjs +35 -0
- package/components/ImageCropper/constants.cjs.map +1 -0
- package/components/ImageCropper/constants.js +23 -0
- package/components/ImageCropper/constants.js.map +1 -0
- package/components/ImageCropper/cropperState.cjs +119 -0
- package/components/ImageCropper/cropperState.cjs.map +1 -0
- package/components/ImageCropper/cropperState.js +114 -0
- package/components/ImageCropper/cropperState.js.map +1 -0
- package/components/ImageCropper/encode.cjs +45 -0
- package/components/ImageCropper/encode.cjs.map +1 -0
- package/components/ImageCropper/encode.js +42 -0
- package/components/ImageCropper/encode.js.map +1 -0
- package/components/ImageCropper/encoderRegistry.cjs +13 -0
- package/components/ImageCropper/encoderRegistry.cjs.map +1 -0
- package/components/ImageCropper/encoderRegistry.js +9 -0
- package/components/ImageCropper/encoderRegistry.js.map +1 -0
- package/components/ImageCropper/encoders/rnImageEncoder.cjs +54 -0
- package/components/ImageCropper/encoders/rnImageEncoder.cjs.map +1 -0
- package/components/ImageCropper/encoders/rnImageEncoder.js +51 -0
- package/components/ImageCropper/encoders/rnImageEncoder.js.map +1 -0
- package/components/ImageCropper/geometry.cjs +208 -0
- package/components/ImageCropper/geometry.cjs.map +1 -0
- package/components/ImageCropper/geometry.js +186 -0
- package/components/ImageCropper/geometry.js.map +1 -0
- package/components/ImageCropper/index.cjs +436 -0
- package/components/ImageCropper/index.cjs.map +1 -0
- package/components/ImageCropper/index.js +434 -0
- package/components/ImageCropper/index.js.map +1 -0
- package/components/ImageCropper/pinchZoom.cjs +8 -0
- package/components/ImageCropper/pinchZoom.cjs.map +1 -0
- package/components/ImageCropper/pinchZoom.js +5 -0
- package/components/ImageCropper/pinchZoom.js.map +1 -0
- package/components/ImageCropper/useImageSource.cjs +87 -0
- package/components/ImageCropper/useImageSource.cjs.map +1 -0
- package/components/ImageCropper/useImageSource.js +84 -0
- package/components/ImageCropper/useImageSource.js.map +1 -0
- package/hooks/useComponentDefaults.cjs +12 -0
- package/hooks/useComponentDefaults.cjs.map +1 -1
- package/hooks/useComponentDefaults.js +12 -0
- package/hooks/useComponentDefaults.js.map +1 -1
- package/index.cjs +120 -115
- package/index.cjs.map +1 -1
- package/index.d.ts +394 -3
- package/index.js +2 -0
- package/index.js.map +1 -1
- package/package.json +13 -1
- package/packages/types/src/Theme/defaultTheme.cjs +111 -0
- package/packages/types/src/Theme/defaultTheme.cjs.map +1 -1
- package/packages/types/src/Theme/defaultTheme.js +111 -0
- package/packages/types/src/Theme/defaultTheme.js.map +1 -1
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var reactNative = require('react-native');
|
|
6
|
+
var Slider = require('@react-native-community/slider');
|
|
7
|
+
var index_native = require('../../primitives/Icon/index.cjs');
|
|
8
|
+
require('../../contexts/DialogContext.cjs');
|
|
9
|
+
require('../../contexts/PermissionsContext.cjs');
|
|
10
|
+
require('../../contexts/BreadcrumbContext.cjs');
|
|
11
|
+
var useComponentDefaults = require('../../hooks/useComponentDefaults.cjs');
|
|
12
|
+
var useResolvedElementStyles = require('../../hooks/useResolvedElementStyles.cjs');
|
|
13
|
+
var cropperState = require('./cropperState.cjs');
|
|
14
|
+
var geometry = require('./geometry.cjs');
|
|
15
|
+
var pinchZoom = require('./pinchZoom.cjs');
|
|
16
|
+
var constants = require('./constants.cjs');
|
|
17
|
+
var useImageSource_native = require('./useImageSource.cjs');
|
|
18
|
+
var encode_native = require('./encode.cjs');
|
|
19
|
+
var encoderRegistry = require('./encoderRegistry.cjs');
|
|
20
|
+
|
|
21
|
+
const gestureHandler = (() => {
|
|
22
|
+
try {
|
|
23
|
+
return require("react-native-gesture-handler");
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
})();
|
|
28
|
+
const StageFrame = ({ gesture, children }) => {
|
|
29
|
+
if (!gestureHandler || !gesture) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
30
|
+
const { GestureDetector, GestureHandlerRootView } = gestureHandler;
|
|
31
|
+
return /* @__PURE__ */ jsxRuntime.jsx(GestureHandlerRootView, { style: { width: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(GestureDetector, { gesture, children }) });
|
|
32
|
+
};
|
|
33
|
+
const touchDistance = (event) => {
|
|
34
|
+
const touches = event.nativeEvent.touches;
|
|
35
|
+
if (touches.length < 2) return 0;
|
|
36
|
+
const [a, b] = touches;
|
|
37
|
+
return Math.hypot((b.pageX ?? 0) - (a.pageX ?? 0), (b.pageY ?? 0) - (a.pageY ?? 0));
|
|
38
|
+
};
|
|
39
|
+
const touchMidpoint = (event, origin) => {
|
|
40
|
+
const touches = event.nativeEvent.touches;
|
|
41
|
+
if (touches.length < 2) return { x: 0, y: 0 };
|
|
42
|
+
const [a, b] = touches;
|
|
43
|
+
return {
|
|
44
|
+
x: ((a.pageX ?? 0) + (b.pageX ?? 0)) / 2 - origin.x,
|
|
45
|
+
y: ((a.pageY ?? 0) + (b.pageY ?? 0)) / 2 - origin.y
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
function ImageCropper(rawProps) {
|
|
49
|
+
const { mergedProps: props, elementStyles } = useComponentDefaults.default("ImageCropper", rawProps);
|
|
50
|
+
const el = useResolvedElementStyles.default(elementStyles);
|
|
51
|
+
const {
|
|
52
|
+
source,
|
|
53
|
+
aspect = constants.DEFAULT_ASPECT,
|
|
54
|
+
maskShape = "rectangle",
|
|
55
|
+
output,
|
|
56
|
+
onCrop,
|
|
57
|
+
onCancel,
|
|
58
|
+
onError,
|
|
59
|
+
onInteractionChange,
|
|
60
|
+
title,
|
|
61
|
+
hidePreview,
|
|
62
|
+
hideGrid,
|
|
63
|
+
maxZoom = constants.DEFAULT_MAX_ZOOM,
|
|
64
|
+
labels,
|
|
65
|
+
testID
|
|
66
|
+
} = props;
|
|
67
|
+
const text = { ...constants.DEFAULT_LABELS, ...labels ?? {} };
|
|
68
|
+
const ratio = geometry.aspectOf(aspect);
|
|
69
|
+
const isCircle = maskShape === "circle";
|
|
70
|
+
const { image, loading, error } = useImageSource_native.useImageSource(source, onError);
|
|
71
|
+
const [viewport, setViewport] = React.useState({ width: 0, height: 0 });
|
|
72
|
+
const originRef = React.useRef({ x: 0, y: 0 });
|
|
73
|
+
const stageRef = React.useRef(null);
|
|
74
|
+
const onStageLayout = React.useCallback((e) => {
|
|
75
|
+
const { width, height } = e.nativeEvent.layout;
|
|
76
|
+
setViewport({ width: Math.round(width), height: Math.round(height) });
|
|
77
|
+
stageRef.current?.measureInWindow((x, y) => {
|
|
78
|
+
originRef.current = { x, y };
|
|
79
|
+
});
|
|
80
|
+
}, []);
|
|
81
|
+
const config = React.useMemo(
|
|
82
|
+
() => ({
|
|
83
|
+
source: image ? { width: image.width, height: image.height } : { width: 1, height: 1 },
|
|
84
|
+
viewport: viewport.width > 0 ? viewport : { width: 1, height: 1 },
|
|
85
|
+
ratio,
|
|
86
|
+
maxZoom,
|
|
87
|
+
// ⚠️ ALWAYS FALSE ON NATIVE — see the note at the top of the file.
|
|
88
|
+
resizable: false
|
|
89
|
+
}),
|
|
90
|
+
[image, viewport, ratio, maxZoom]
|
|
91
|
+
);
|
|
92
|
+
const [state, dispatch] = React.useReducer(cropperState.cropperReducer, config, cropperState.initialCropperState);
|
|
93
|
+
React.useEffect(() => {
|
|
94
|
+
dispatch({ type: "configure", config });
|
|
95
|
+
}, [config]);
|
|
96
|
+
const ready = !!image && viewport.width > 1 && viewport.height > 1;
|
|
97
|
+
const pinchRef = React.useRef(null);
|
|
98
|
+
const lastPanRef = React.useRef({ x: 0, y: 0 });
|
|
99
|
+
const scaleRef = React.useRef(state.transform.scale);
|
|
100
|
+
scaleRef.current = state.transform.scale;
|
|
101
|
+
const resetPan = React.useCallback(() => {
|
|
102
|
+
lastPanRef.current = { x: 0, y: 0 };
|
|
103
|
+
}, []);
|
|
104
|
+
const beginGesture = React.useCallback(() => {
|
|
105
|
+
pinchRef.current = null;
|
|
106
|
+
lastPanRef.current = { x: 0, y: 0 };
|
|
107
|
+
}, []);
|
|
108
|
+
const applyPan = React.useCallback((dx, dy) => {
|
|
109
|
+
dispatch({
|
|
110
|
+
type: "pan",
|
|
111
|
+
dx: dx - lastPanRef.current.x,
|
|
112
|
+
dy: dy - lastPanRef.current.y
|
|
113
|
+
});
|
|
114
|
+
lastPanRef.current = { x: dx, y: dy };
|
|
115
|
+
}, []);
|
|
116
|
+
const applyPinch = React.useCallback((reading, anchor) => {
|
|
117
|
+
if (!pinchZoom.isUsablePinchReading(reading)) return;
|
|
118
|
+
const pinch = pinchRef.current;
|
|
119
|
+
if (!pinch) {
|
|
120
|
+
pinchRef.current = { reading, scale: scaleRef.current };
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
dispatch({ type: "zoomAt", anchor, scale: pinchZoom.pinchScale(pinch, reading) });
|
|
124
|
+
}, []);
|
|
125
|
+
const interactingRef = React.useRef(false);
|
|
126
|
+
const setInteracting = React.useCallback(
|
|
127
|
+
(active) => {
|
|
128
|
+
if (interactingRef.current === active) return;
|
|
129
|
+
interactingRef.current = active;
|
|
130
|
+
onInteractionChange?.(active);
|
|
131
|
+
},
|
|
132
|
+
[onInteractionChange]
|
|
133
|
+
);
|
|
134
|
+
const detector = React.useMemo(() => {
|
|
135
|
+
if (!gestureHandler) return null;
|
|
136
|
+
const { Gesture } = gestureHandler;
|
|
137
|
+
const pan = Gesture.Pan().runOnJS(true).onBegin(() => {
|
|
138
|
+
setInteracting(true);
|
|
139
|
+
resetPan();
|
|
140
|
+
}).onUpdate((e) => {
|
|
141
|
+
if (e.numberOfPointers > 1 || pinchRef.current) {
|
|
142
|
+
lastPanRef.current = { x: e.translationX, y: e.translationY };
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
applyPan(e.translationX, e.translationY);
|
|
146
|
+
}).onFinalize(() => {
|
|
147
|
+
setInteracting(false);
|
|
148
|
+
resetPan();
|
|
149
|
+
});
|
|
150
|
+
const pinch = Gesture.Pinch().runOnJS(true).onBegin(() => {
|
|
151
|
+
setInteracting(true);
|
|
152
|
+
pinchRef.current = { reading: 1, scale: scaleRef.current };
|
|
153
|
+
}).onUpdate((e) => {
|
|
154
|
+
applyPinch(e.scale, { x: e.focalX, y: e.focalY });
|
|
155
|
+
}).onFinalize(() => {
|
|
156
|
+
setInteracting(false);
|
|
157
|
+
pinchRef.current = null;
|
|
158
|
+
});
|
|
159
|
+
return Gesture.Simultaneous(pan, pinch);
|
|
160
|
+
}, [applyPan, applyPinch, resetPan, setInteracting]);
|
|
161
|
+
const scrollBlocker = React.useMemo(
|
|
162
|
+
() => reactNative.PanResponder.create({
|
|
163
|
+
onStartShouldSetPanResponder: () => true,
|
|
164
|
+
onMoveShouldSetPanResponder: () => true,
|
|
165
|
+
onPanResponderTerminationRequest: () => false,
|
|
166
|
+
onShouldBlockNativeResponder: () => true,
|
|
167
|
+
onPanResponderGrant: () => setInteracting(true),
|
|
168
|
+
onPanResponderRelease: () => setInteracting(false),
|
|
169
|
+
onPanResponderTerminate: () => setInteracting(false)
|
|
170
|
+
}),
|
|
171
|
+
[setInteracting]
|
|
172
|
+
);
|
|
173
|
+
const responder = React.useMemo(
|
|
174
|
+
() => reactNative.PanResponder.create({
|
|
175
|
+
onStartShouldSetPanResponder: () => true,
|
|
176
|
+
onMoveShouldSetPanResponder: () => true,
|
|
177
|
+
// ⚠️ Refuses a JS-level handover. It does NOT save a vertical
|
|
178
|
+
// drag inside a ScrollView — that cancellation is native and
|
|
179
|
+
// never asks — but it does hold the gesture against JS
|
|
180
|
+
// responders nearer the touch.
|
|
181
|
+
onPanResponderTerminationRequest: () => false,
|
|
182
|
+
onPanResponderGrant: () => {
|
|
183
|
+
setInteracting(true);
|
|
184
|
+
beginGesture();
|
|
185
|
+
},
|
|
186
|
+
onPanResponderMove: (event, gesture) => {
|
|
187
|
+
const touches = event.nativeEvent.touches.length;
|
|
188
|
+
if (touches >= 2) {
|
|
189
|
+
applyPinch(
|
|
190
|
+
touchDistance(event),
|
|
191
|
+
touchMidpoint(event, originRef.current)
|
|
192
|
+
);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (pinchRef.current) {
|
|
196
|
+
pinchRef.current = null;
|
|
197
|
+
lastPanRef.current = { x: gesture.dx, y: gesture.dy };
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
applyPan(gesture.dx, gesture.dy);
|
|
201
|
+
},
|
|
202
|
+
onPanResponderRelease: () => {
|
|
203
|
+
setInteracting(false);
|
|
204
|
+
beginGesture();
|
|
205
|
+
},
|
|
206
|
+
onPanResponderTerminate: () => {
|
|
207
|
+
setInteracting(false);
|
|
208
|
+
beginGesture();
|
|
209
|
+
}
|
|
210
|
+
}),
|
|
211
|
+
[applyPan, applyPinch, beginGesture, setInteracting]
|
|
212
|
+
);
|
|
213
|
+
const [busy, setBusy] = React.useState(false);
|
|
214
|
+
const canSave = ready && !busy && encoderRegistry.hasCropEncoder();
|
|
215
|
+
const save = React.useCallback(async () => {
|
|
216
|
+
if (!ready || !image || busy) return;
|
|
217
|
+
setBusy(true);
|
|
218
|
+
try {
|
|
219
|
+
const rect = geometry.cropToSourceRect(state.crop, state.transform, config.source, state.rotation);
|
|
220
|
+
const format = output?.format ?? constants.DEFAULT_FORMAT;
|
|
221
|
+
const result = await encode_native.encodeCropNative(
|
|
222
|
+
image.uri,
|
|
223
|
+
rect,
|
|
224
|
+
config.source,
|
|
225
|
+
state.rotation,
|
|
226
|
+
{
|
|
227
|
+
width: output?.maxWidth,
|
|
228
|
+
height: output?.maxHeight,
|
|
229
|
+
bytes: output?.maxBytes ?? constants.DEFAULT_MAX_BYTES
|
|
230
|
+
},
|
|
231
|
+
format
|
|
232
|
+
);
|
|
233
|
+
if (!result.withinBudget) {
|
|
234
|
+
onError?.({
|
|
235
|
+
reason: "too-large",
|
|
236
|
+
message: `The smallest version this could produce is ${Math.round(result.size / 1024)} KB.`
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const { withinBudget: _ignored, ...payload } = result;
|
|
240
|
+
onCrop?.(payload);
|
|
241
|
+
} catch (cause) {
|
|
242
|
+
onError?.({
|
|
243
|
+
reason: "encode-failed",
|
|
244
|
+
message: cause instanceof Error ? cause.message : "The image could not be saved."
|
|
245
|
+
});
|
|
246
|
+
} finally {
|
|
247
|
+
setBusy(false);
|
|
248
|
+
}
|
|
249
|
+
}, [ready, image, busy, state, config.source, output, onCrop, onError]);
|
|
250
|
+
const { crop, transform, rotation } = state;
|
|
251
|
+
const oriented = image ? geometry.orientedSize({ width: image.width, height: image.height }, rotation) : null;
|
|
252
|
+
const band = cropperState.scaleRange(state);
|
|
253
|
+
const zoomLabel = Number.isFinite(band.min) && band.min > 0 ? `${(transform.scale / band.min).toFixed(1)}\xD7` : "1.0\xD7";
|
|
254
|
+
const windowRadius = isCircle ? Math.max(crop.width, crop.height) / 2 : Number(el.mask?.borderRadius ?? 0);
|
|
255
|
+
const SCRIM = Math.ceil(Math.max(viewport.width, viewport.height)) || 1;
|
|
256
|
+
const toolButton = (icon, label, onPress, disabled) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
257
|
+
reactNative.Pressable,
|
|
258
|
+
{
|
|
259
|
+
onPress,
|
|
260
|
+
disabled,
|
|
261
|
+
accessibilityRole: "button",
|
|
262
|
+
accessibilityLabel: label,
|
|
263
|
+
accessibilityState: { disabled: !!disabled },
|
|
264
|
+
style: {
|
|
265
|
+
flexDirection: "row",
|
|
266
|
+
alignItems: "center",
|
|
267
|
+
gap: 6,
|
|
268
|
+
paddingHorizontal: 8,
|
|
269
|
+
paddingVertical: 8,
|
|
270
|
+
opacity: disabled ? 0.5 : 1
|
|
271
|
+
},
|
|
272
|
+
children: [
|
|
273
|
+
/* @__PURE__ */ jsxRuntime.jsx(index_native.Icon, { name: icon, size: "sm", color: el.icon?.color }),
|
|
274
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: el.text, children: label })
|
|
275
|
+
]
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { testID, style: [{ width: "100%" }, el.root], children: [
|
|
279
|
+
title != null && /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [{ flexDirection: "row", alignItems: "center" }, el.header], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: el.title, children: title }) }),
|
|
280
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [el.content], children: !source || error ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [{ alignItems: "center" }, el.dropZone], children: [
|
|
281
|
+
/* @__PURE__ */ jsxRuntime.jsx(index_native.Icon, { name: "image", size: "lg", color: el.icon?.color }),
|
|
282
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: el.text, children: error ? error.message : text.dropZone })
|
|
283
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
284
|
+
/* @__PURE__ */ jsxRuntime.jsx(StageFrame, { gesture: detector, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
285
|
+
reactNative.View,
|
|
286
|
+
{
|
|
287
|
+
ref: stageRef,
|
|
288
|
+
onLayout: onStageLayout,
|
|
289
|
+
...detector ? scrollBlocker.panHandlers : responder.panHandlers,
|
|
290
|
+
style: [{ width: "100%", aspectRatio: 4 / 3, overflow: "hidden" }, el.container],
|
|
291
|
+
children: [
|
|
292
|
+
ready && image && oriented && /* @__PURE__ */ jsxRuntime.jsx(
|
|
293
|
+
reactNative.Image,
|
|
294
|
+
{
|
|
295
|
+
source: { uri: image.uri },
|
|
296
|
+
resizeMode: "stretch",
|
|
297
|
+
style: {
|
|
298
|
+
position: "absolute",
|
|
299
|
+
left: transform.x,
|
|
300
|
+
top: transform.y,
|
|
301
|
+
width: oriented.width * transform.scale,
|
|
302
|
+
height: oriented.height * transform.scale,
|
|
303
|
+
transform: [{ rotate: `${rotation}deg` }]
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
),
|
|
307
|
+
ready && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
308
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
309
|
+
reactNative.View,
|
|
310
|
+
{
|
|
311
|
+
pointerEvents: "none",
|
|
312
|
+
style: [
|
|
313
|
+
{
|
|
314
|
+
position: "absolute",
|
|
315
|
+
left: crop.x - SCRIM,
|
|
316
|
+
top: crop.y - SCRIM,
|
|
317
|
+
width: crop.width + SCRIM * 2,
|
|
318
|
+
height: crop.height + SCRIM * 2,
|
|
319
|
+
borderWidth: SCRIM,
|
|
320
|
+
borderColor: el.shade?.backgroundColor ?? "rgba(0,0,0,0.6)",
|
|
321
|
+
borderRadius: windowRadius + SCRIM
|
|
322
|
+
}
|
|
323
|
+
]
|
|
324
|
+
}
|
|
325
|
+
),
|
|
326
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
327
|
+
reactNative.View,
|
|
328
|
+
{
|
|
329
|
+
pointerEvents: "none",
|
|
330
|
+
style: [
|
|
331
|
+
{
|
|
332
|
+
position: "absolute",
|
|
333
|
+
left: crop.x,
|
|
334
|
+
top: crop.y,
|
|
335
|
+
width: crop.width,
|
|
336
|
+
height: crop.height,
|
|
337
|
+
borderStyle: "solid"
|
|
338
|
+
},
|
|
339
|
+
el.cropWindow,
|
|
340
|
+
{ borderRadius: windowRadius }
|
|
341
|
+
],
|
|
342
|
+
children: !hideGrid && [1, 2].map((n) => /* @__PURE__ */ jsxRuntime.jsxs(React.Fragment, { children: [
|
|
343
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
344
|
+
{
|
|
345
|
+
position: "absolute",
|
|
346
|
+
top: 0,
|
|
347
|
+
bottom: 0,
|
|
348
|
+
width: 1,
|
|
349
|
+
left: crop.width * n / 3
|
|
350
|
+
},
|
|
351
|
+
el.gridLine
|
|
352
|
+
] }),
|
|
353
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
354
|
+
{
|
|
355
|
+
position: "absolute",
|
|
356
|
+
left: 0,
|
|
357
|
+
right: 0,
|
|
358
|
+
height: 1,
|
|
359
|
+
top: crop.height * n / 3
|
|
360
|
+
},
|
|
361
|
+
el.gridLine
|
|
362
|
+
] })
|
|
363
|
+
] }, n))
|
|
364
|
+
}
|
|
365
|
+
),
|
|
366
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
367
|
+
reactNative.View,
|
|
368
|
+
{
|
|
369
|
+
pointerEvents: "none",
|
|
370
|
+
style: [{ position: "absolute", right: 8, bottom: 8 }, el.zoomChip],
|
|
371
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: el.zoomChip?.color }, children: zoomLabel })
|
|
372
|
+
}
|
|
373
|
+
)
|
|
374
|
+
] }),
|
|
375
|
+
(loading || busy) && /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [{ position: "absolute", left: 0, right: 0, bottom: 0 }, el.progressFill] })
|
|
376
|
+
]
|
|
377
|
+
}
|
|
378
|
+
) }),
|
|
379
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [
|
|
380
|
+
/* @__PURE__ */ jsxRuntime.jsx(index_native.Icon, { name: "zoom-in", size: "sm", color: el.icon?.color }),
|
|
381
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
382
|
+
Slider,
|
|
383
|
+
{
|
|
384
|
+
style: { flex: 1 },
|
|
385
|
+
minimumValue: 0,
|
|
386
|
+
maximumValue: 1,
|
|
387
|
+
value: cropperState.zoomFraction(state),
|
|
388
|
+
disabled: !ready,
|
|
389
|
+
minimumTrackTintColor: el.thumb?.backgroundColor,
|
|
390
|
+
maximumTrackTintColor: el.track?.backgroundColor,
|
|
391
|
+
thumbTintColor: el.thumb?.backgroundColor,
|
|
392
|
+
onValueChange: (value) => dispatch({ type: "setZoom", value })
|
|
393
|
+
}
|
|
394
|
+
),
|
|
395
|
+
!hidePreview && ready && image && oriented && /* The preview is the same image, clipped and offset so the
|
|
396
|
+
crop fills the frame — no pixel work, and therefore
|
|
397
|
+
nothing that has to wait for an encoder. */
|
|
398
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
399
|
+
reactNative.View,
|
|
400
|
+
{
|
|
401
|
+
style: [
|
|
402
|
+
{ width: 64, height: 64, overflow: "hidden" },
|
|
403
|
+
el.previewFrame,
|
|
404
|
+
isCircle ? { borderRadius: 32 } : null
|
|
405
|
+
],
|
|
406
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
407
|
+
reactNative.Image,
|
|
408
|
+
{
|
|
409
|
+
source: { uri: image.uri },
|
|
410
|
+
resizeMode: "stretch",
|
|
411
|
+
style: {
|
|
412
|
+
position: "absolute",
|
|
413
|
+
width: oriented.width * transform.scale * (64 / crop.width),
|
|
414
|
+
height: oriented.height * transform.scale * (64 / crop.width),
|
|
415
|
+
left: (transform.x - crop.x) * (64 / crop.width),
|
|
416
|
+
top: (transform.y - crop.y) * (64 / crop.width),
|
|
417
|
+
transform: [{ rotate: `${rotation}deg` }]
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
)
|
|
423
|
+
] })
|
|
424
|
+
] }) }),
|
|
425
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [{ flexDirection: "row", alignItems: "center" }, el.footer], children: [
|
|
426
|
+
toolButton("rotate-cw", text.rotate, () => dispatch({ type: "rotate" }), !ready),
|
|
427
|
+
toolButton("rotate-ccw", text.reset, () => dispatch({ type: "reset" }), !ready),
|
|
428
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flex: 1 } }),
|
|
429
|
+
toolButton("x", text.cancel, () => onCancel?.()),
|
|
430
|
+
toolButton("check", busy ? text.processing : text.save, () => void save(), !canSave)
|
|
431
|
+
] })
|
|
432
|
+
] });
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
exports.ImageCropper = ImageCropper;
|
|
436
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../../../src/components/ImageCropper/index.native.tsx"],"sourcesContent":["import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from \"react\";\nimport {\n Image,\n PanResponder,\n Pressable,\n Text as RNText,\n View,\n type LayoutChangeEvent,\n type PanResponderGestureState,\n type GestureResponderEvent,\n} from \"react-native\";\nimport Slider from \"@react-native-community/slider\";\nimport type {ICropError, ICropResult, IImageCropperProps} from \"@lmb-it/kitsconcerto-types\";\nimport {Icon} from \"../../primitives\";\nimport {useComponentDefaults, useResolvedElementStyles} from \"../../hooks\";\nimport {\n cropperReducer,\n initialCropperState,\n scaleRange,\n zoomFraction,\n type ICropperConfig,\n} from \"./cropperState\";\nimport {aspectOf, cropToSourceRect, orientedSize} from \"./geometry\";\nimport {isUsablePinchReading, pinchScale, type IPinchBaseline} from \"./pinchZoom\";\nimport {\n DEFAULT_ASPECT,\n DEFAULT_FORMAT,\n DEFAULT_LABELS,\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_ZOOM,\n} from \"./constants\";\n/**\n * Gesture-handler, if the consumer installed it.\n *\n * ⛔ `require` IN A TRY, NOT AN `import`. A static import is evaluated when this\n * module loads, so an app without the optional peer would fail to render the\n * whole library rather than fall back. `uiPkg.devDependencies` is already in\n * the native rollup's externals list, so this is never bundled either way.\n */\nconst gestureHandler: typeof import(\"react-native-gesture-handler\") | null = (() => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require\n return require(\"react-native-gesture-handler\");\n } catch {\n return null;\n }\n})();\n\nimport {useImageSource} from \"./useImageSource.native\";\nimport {encodeCropNative, hasCropEncoder} from \"./encode.native\";\n\n/**\n * ImageCropper (native).\n *\n * ⛔ IT RUNS THE SAME THREE MODULES AS THE WEB FILE. `cropperState`, `geometry`\n * and `compression` are imported unchanged; this file contributes gestures, a\n * slider, views, and the one platform call the web file makes with a canvas.\n * That is the whole reason those modules have no React in them — a cropper\n * whose platforms each own their own arithmetic produces a photo that is right\n * on the phone and off-centre on the dashboard from one upload.\n *\n * ⛔ GESTURE-HANDLER WHEN IT IS INSTALLED, `PanResponder` WHEN IT IS NOT — AND\n * THE REASON IS A BUG A SIMULATOR CANNOT SHOW YOU. `PanResponder` alone loses\n * every VERTICAL drag to an enclosing `ScrollView`, which is what a cropper\n * sits in inside any real form. A horizontal drag works, so the gesture looks\n * fine until someone drags the way people actually drag.\n *\n * It is not a responder negotiation that can be won from here.\n * `RCTScrollViewComponentView.touchesShouldCancelInContentView:` answers\n * `![self _shouldDisableScrollInteraction]`, and that helper walks the\n * SUPERVIEWS of the scroll view looking for a JS responder — upward, never\n * into its own content. A `PanResponder` in a DESCENDANT is therefore never\n * consulted, and UIScrollView cancels its touches natively. ⛔ So returning\n * `false` from `onPanResponderTerminationRequest`, or claiming the touch in\n * the capture phase, cannot help: there is no JS termination request to\n * refuse. Gesture-handler wins because it competes as a native gesture\n * recogniser, at the only layer where this contest is actually held.\n *\n * ⚠️ THE IMPORT IS GUARDED, NOT STATIC. Gesture-handler is an OPTIONAL peer —\n * the same contract this package already uses for the cropper's two encoder\n * libraries — so a consumer that never installed it must still get a working\n * component rather than a module-load crash. Without it the `PanResponder`\n * path below is used, and it is honestly degraded: fine standalone, beaten by\n * a vertical parent scroll.\n *\n * ⚠️ THE MATH IS NOT DUPLICATED. Both paths call the same `beginGesture` /\n * `applyPan` / `applyPinch` callbacks; only the event source differs. Two\n * copies of the pinch arithmetic would drift, and the fallback is the copy\n * nobody would notice drifting.\n *\n * ⚠️ RESIZING GRIPS ARE NOT OFFERED HERE, as the props type says. A finger has\n * no hover and a grip small enough to look right is too small to hit reliably;\n * native frames by pinch and pan against a fixed window instead.\n */\n\n/**\n * Puts the stage under gesture-handler when it is installed, and out of the way\n * when it is not.\n *\n * ⚠️ DECLARED AT MODULE SCOPE ON PURPOSE. A component defined inside the render\n * is a new type on every pass, so React would unmount and remount the stage —\n * which loses the measured viewport and resets the user's framing mid-drag.\n *\n * ⚠️ IT MOUNTS ITS OWN `GestureHandlerRootView`. Gesture-handler needs one\n * above the detector, and a library cannot assume the host app has mounted it;\n * nesting a second one inside an app that already has one is supported and is\n * what the library itself does for modals.\n */\nconst StageFrame = ({gesture, children}: {gesture: unknown; children: React.ReactNode}) => {\n if (!gestureHandler || !gesture) return <>{children}</>;\n const {GestureDetector, GestureHandlerRootView} = gestureHandler;\n return (\n <GestureHandlerRootView style={{width: \"100%\"}}>\n <GestureDetector gesture={gesture as never}>{children as never}</GestureDetector>\n </GestureHandlerRootView>\n );\n};\n\n/** Distance between the first two touches, for pinch. */\nconst touchDistance = (event: GestureResponderEvent): number => {\n const touches = event.nativeEvent.touches;\n if (touches.length < 2) return 0;\n const [a, b] = touches;\n return Math.hypot((b!.pageX ?? 0) - (a!.pageX ?? 0), (b!.pageY ?? 0) - (a!.pageY ?? 0));\n};\n\n/** Midpoint of the first two touches, in stage coordinates. */\nconst touchMidpoint = (\n event: GestureResponderEvent,\n origin: {x: number; y: number},\n): {x: number; y: number} => {\n const touches = event.nativeEvent.touches;\n if (touches.length < 2) return {x: 0, y: 0};\n const [a, b] = touches;\n return {\n x: ((a!.pageX ?? 0) + (b!.pageX ?? 0)) / 2 - origin.x,\n y: ((a!.pageY ?? 0) + (b!.pageY ?? 0)) / 2 - origin.y,\n };\n};\n\nexport function ImageCropper(rawProps: IImageCropperProps) {\n const {mergedProps: props, elementStyles} =\n useComponentDefaults<IImageCropperProps>(\"ImageCropper\", rawProps);\n const el = useResolvedElementStyles(elementStyles);\n\n const {\n source,\n aspect = DEFAULT_ASPECT,\n maskShape = \"rectangle\",\n output,\n onCrop,\n onCancel,\n onError,\n onInteractionChange,\n title,\n hidePreview,\n hideGrid,\n maxZoom = DEFAULT_MAX_ZOOM,\n labels,\n testID,\n } = props;\n\n const text = {...DEFAULT_LABELS, ...(labels ?? {})};\n const ratio = aspectOf(aspect);\n const isCircle = maskShape === \"circle\";\n\n const {image, loading, error} = useImageSource(source, onError);\n\n /* ─── The stage measures itself ──────────────────────────────────────── */\n const [viewport, setViewport] = useState({width: 0, height: 0});\n const originRef = useRef({x: 0, y: 0});\n const stageRef = useRef<View | null>(null);\n\n const onStageLayout = useCallback((e: LayoutChangeEvent) => {\n const {width, height} = e.nativeEvent.layout;\n setViewport({width: Math.round(width), height: Math.round(height)});\n // ⚠️ THE PAGE ORIGIN IS MEASURED SEPARATELY. `PanResponder` reports\n // touches in PAGE coordinates, and the geometry is written against the\n // STAGE's own space; without this offset every pinch anchors to a point\n // the user is not touching.\n stageRef.current?.measureInWindow((x, y) => {\n originRef.current = {x, y};\n });\n }, []);\n\n const config: ICropperConfig = useMemo(\n () => ({\n source: image ? {width: image.width, height: image.height} : {width: 1, height: 1},\n viewport: viewport.width > 0 ? viewport : {width: 1, height: 1},\n ratio,\n maxZoom,\n // ⚠️ ALWAYS FALSE ON NATIVE — see the note at the top of the file.\n resizable: false,\n }),\n [image, viewport, ratio, maxZoom],\n );\n\n const [state, dispatch] = useReducer(cropperReducer, config, initialCropperState);\n useEffect(() => {\n dispatch({type: \"configure\", config});\n }, [config]);\n\n const ready = !!image && viewport.width > 1 && viewport.height > 1;\n\n /* ─── Gestures ───────────────────────────────────────────────────────── */\n const pinchRef = useRef<IPinchBaseline | null>(null);\n const lastPanRef = useRef({x: 0, y: 0});\n const scaleRef = useRef(state.transform.scale);\n scaleRef.current = state.transform.scale;\n\n /**\n * The three things a gesture can say, shared by both input paths.\n *\n * ⚠️ THEY TAKE CUMULATIVE TRAVEL, NOT PER-FRAME DELTAS, because that is what\n * both sources report: `PanResponderGestureState.dx` and gesture-handler's\n * `translationX` are both measured from where the gesture began. The\n * differencing happens here, once.\n */\n /** Re-bases the pan so the next frame is measured from here, not from zero. */\n const resetPan = useCallback(() => {\n lastPanRef.current = {x: 0, y: 0};\n }, []);\n\n const beginGesture = useCallback(() => {\n pinchRef.current = null;\n lastPanRef.current = {x: 0, y: 0};\n }, []);\n\n const applyPan = useCallback((dx: number, dy: number) => {\n dispatch({\n type: \"pan\",\n dx: dx - lastPanRef.current.x,\n dy: dy - lastPanRef.current.y,\n });\n lastPanRef.current = {x: dx, y: dy};\n }, []);\n\n /**\n * One frame of a pinch.\n *\n * ⚠️ `reading` IS WHATEVER THE PLATFORM MEASURES A PINCH WITH, not a ratio.\n * Gesture-handler reports a cumulative `scale` starting at 1; `PanResponder`\n * gives nothing but the raw pixel distance between two fingers. The first\n * reading of a gesture becomes the baseline and every later one is divided\n * by it, so both mean the same thing by the time they arrive here and\n * neither call site has to do arithmetic of its own.\n *\n * ⛔ IT INITIALISES ITSELF, AND THAT IS NOT BELT-AND-BRACES. Requiring\n * `onBegin` to have run first makes the whole pinch depend on one callback\n * firing in one order; if it ever does not, every frame is dropped and\n * pinch is silently dead with nothing in the logs. Seeding from the first\n * reading instead means pinch works whether or not `onBegin` ran.\n *\n * ⚠️ THE SCALE IS READ FROM WHERE THE PINCH STARTED, never from the current\n * one. Multiplying the live scale by a factor that is itself cumulative\n * compounds it every frame, and the image runs away.\n */\n const applyPinch = useCallback((reading: number, anchor: {x: number; y: number}) => {\n if (!isUsablePinchReading(reading)) return;\n const pinch = pinchRef.current;\n if (!pinch) {\n pinchRef.current = {reading, scale: scaleRef.current};\n return;\n }\n dispatch({type: \"zoomAt\", anchor, scale: pinchScale(pinch, reading)});\n }, []);\n\n /**\n * Tells the host whether a finger is on the stage, so it can stand its\n * `ScrollView` down for the duration.\n *\n * ⚠️ IT DE-DUPLICATES. Both gesture paths can report a start more than once\n * for one touch — a pan and a pinch beginning together, or a pinch handing\n * back to a pan — and a host that re-renders on every one of those would\n * re-render mid-drag, repeatedly, for no change in state.\n */\n const interactingRef = useRef(false);\n const setInteracting = useCallback(\n (active: boolean) => {\n if (interactingRef.current === active) return;\n interactingRef.current = active;\n onInteractionChange?.(active);\n },\n [onInteractionChange],\n );\n\n /* ── Path A: gesture-handler, when the consumer installed it ──────────── */\n const detector = useMemo(() => {\n if (!gestureHandler) return null;\n const {Gesture} = gestureHandler;\n\n const pan = Gesture.Pan()\n // ⛔ `runOnJS` BECAUSE THE STATE IS A REDUCER, NOT A WORKLET. Without\n // it these callbacks are compiled as worklets and `dispatch` is not\n // one, which fails at the first finger movement.\n .runOnJS(true)\n .onBegin(() => { setInteracting(true); resetPan(); })\n .onUpdate((e) => {\n // ⛔ `numberOfPointers`, NOT JUST `pinchRef`. A pan tracks the\n // CENTROID of its touches, so the instant a second finger lands\n // the translation jumps by half the gap between the fingers —\n // often a hundred points. Waiting for the pinch gesture to\n // begin before suppressing leaves a window of one or more\n // frames where that jump is applied as a real pan, and the\n // image lurches at the start of every zoom. The pointer count\n // is known on the very first frame and does not depend on which\n // gesture's callback ran first.\n //\n // ⛔ THE BASELINE IS KEPT CURRENT RATHER THAN FROZEN, so a pinch\n // that ends does not become a fling: `translationX` goes on\n // accumulating through the two-finger phase even though the\n // zoom is handling it, and the frame after the fingers lift\n // would otherwise apply the whole pinch travel at once.\n if (e.numberOfPointers > 1 || pinchRef.current) {\n lastPanRef.current = {x: e.translationX, y: e.translationY};\n return;\n }\n applyPan(e.translationX, e.translationY);\n })\n .onFinalize(() => { setInteracting(false); resetPan(); });\n\n const pinch = Gesture.Pinch()\n .runOnJS(true)\n .onBegin(() => {\n setInteracting(true);\n pinchRef.current = {reading: 1, scale: scaleRef.current};\n })\n .onUpdate((e) => {\n applyPinch(e.scale, {x: e.focalX, y: e.focalY});\n })\n // ⚠️ CLEARS ONLY THE PINCH. The pan may still be running — these two\n // are simultaneous — and resetting its baseline here would make the\n // next frame replay the whole gesture as a jump.\n .onFinalize(() => {\n setInteracting(false);\n pinchRef.current = null;\n });\n\n // ⚠️ SIMULTANEOUS, so a pinch that drifts does not have to choose between\n // zooming and moving — which is how a two-finger adjustment actually\n // feels on a phone.\n return Gesture.Simultaneous(pan, pinch);\n }, [applyPan, applyPinch, resetPan, setInteracting]);\n\n /**\n * A `PanResponder` that claims the touch and then does nothing with it.\n *\n * ⛔ THIS IS WHAT STOPS AN ENCLOSING ScrollView EATING A VERTICAL DRAG, AND\n * GESTURE-HANDLER ALONE DOES NOT. A horizontal drag never competes with a\n * vertical scroll, so pan and pinch both looked fixed; the moment a finger\n * moved DOWN the scroll view took it. The chain that fixes it runs through\n * both libraries and no single file shows it:\n *\n * 1. gesture-handler replaces `UIManager.setJSResponder`\n * (`createHandler.tsx`), so every JS responder change is forwarded to\n * its native manager.\n * 2. `PanResponder` reports `blockNativeResponder` — and DEFAULTS IT TO\n * TRUE when `onShouldBlockNativeResponder` is absent\n * (`PanResponder.js`). The docs call it Android-only, and on iOS it\n * does nothing at all until step 1 is in place.\n * 3. gesture-handler's manager answers by calling `blockOtherRecognizers`\n * on its root recognisers, setting the root recogniser to `Began`.\n * 4. `RNRootViewGestureRecognizer.canPreventGestureRecognizer:` then\n * returns YES for every recogniser except RN's own touch handler —\n * UIScrollView's pan among them. The scroll view cannot recognise, so\n * it cannot scroll.\n *\n * ⛔ IT MUST NOT MOVE ANYTHING. The detector is already handling this\n * gesture; a second responder acting on the same fingers would apply every\n * delta twice. It claims the touch, refuses to hand it back, and leaves the\n * arithmetic alone.\n *\n * ⚠️ `onShouldBlockNativeResponder` IS SPELLED OUT rather than left to its\n * default, because that default IS the mechanism, and a later reader\n * deleting an \"unused\" line would quietly return vertical drags to the\n * scroll view.\n *\n * ⚠️ IT CANNOT HELP WHEN THE TOUCH NEVER ARRIVES. `UIScrollView` delays\n * content touches and, once it decides a gesture is a scroll, stops\n * delivering them at all — there is nothing left to claim. That case needs\n * the host to stand its scroll view down, which is what\n * `onInteractionChange` is for.\n */\n const scrollBlocker = useMemo(\n () =>\n PanResponder.create({\n onStartShouldSetPanResponder: () => true,\n onMoveShouldSetPanResponder: () => true,\n onPanResponderTerminationRequest: () => false,\n onShouldBlockNativeResponder: () => true,\n onPanResponderGrant: () => setInteracting(true),\n onPanResponderRelease: () => setInteracting(false),\n onPanResponderTerminate: () => setInteracting(false),\n }),\n [setInteracting],\n );\n\n /* ── Path B: PanResponder, the no-dependency fallback ─────────────────── */\n const responder = useMemo(\n () =>\n PanResponder.create({\n onStartShouldSetPanResponder: () => true,\n onMoveShouldSetPanResponder: () => true,\n // ⚠️ Refuses a JS-level handover. It does NOT save a vertical\n // drag inside a ScrollView — that cancellation is native and\n // never asks — but it does hold the gesture against JS\n // responders nearer the touch.\n onPanResponderTerminationRequest: () => false,\n onPanResponderGrant: () => { setInteracting(true); beginGesture(); },\n onPanResponderMove: (event: GestureResponderEvent, gesture: PanResponderGestureState) => {\n const touches = event.nativeEvent.touches.length;\n\n if (touches >= 2) {\n applyPinch(\n touchDistance(event),\n touchMidpoint(event, originRef.current),\n );\n return;\n }\n\n // ⛔ A PINCH THAT DROPS TO ONE FINGER MUST NOT BECOME A FLING.\n // `gesture.dx` is measured from where the whole gesture began,\n // so the frame after a finger lifts carries the entire pinch\n // travel as a pan. Clearing the pinch and re-basing the pan on\n // the next frame is what stops the image leaping.\n if (pinchRef.current) {\n pinchRef.current = null;\n lastPanRef.current = {x: gesture.dx, y: gesture.dy};\n return;\n }\n\n applyPan(gesture.dx, gesture.dy);\n },\n onPanResponderRelease: () => { setInteracting(false); beginGesture(); },\n onPanResponderTerminate: () => { setInteracting(false); beginGesture(); },\n }),\n [applyPan, applyPinch, beginGesture, setInteracting],\n );\n\n /* ─── Save ───────────────────────────────────────────────────────────── */\n const [busy, setBusy] = useState(false);\n const canSave = ready && !busy && hasCropEncoder();\n\n const save = useCallback(async () => {\n if (!ready || !image || busy) return;\n setBusy(true);\n try {\n const rect = cropToSourceRect(state.crop, state.transform, config.source, state.rotation);\n const format = output?.format ?? DEFAULT_FORMAT;\n const result = await encodeCropNative(\n image.uri,\n rect,\n config.source,\n state.rotation,\n {\n width: output?.maxWidth,\n height: output?.maxHeight,\n bytes: output?.maxBytes ?? DEFAULT_MAX_BYTES,\n },\n format,\n );\n if (!result.withinBudget) {\n onError?.({\n reason: \"too-large\",\n message: `The smallest version this could produce is ${Math.round(result.size / 1024)} KB.`,\n });\n }\n const {withinBudget: _ignored, ...payload} = result;\n onCrop?.(payload as ICropResult);\n } catch (cause) {\n onError?.({\n reason: \"encode-failed\",\n message: cause instanceof Error ? cause.message : \"The image could not be saved.\",\n });\n } finally {\n setBusy(false);\n }\n // ⚠️ THE ENCODER RUNS ON SAVE AND NOWHERE ELSE, which is what \"compute\n // the file size when the gesture settles, not during\" asks for — here it\n // holds by construction rather than by a debounce that could drift.\n }, [ready, image, busy, state, config.source, output, onCrop, onError]);\n\n /* ─── Render ─────────────────────────────────────────────────────────── */\n const {crop, transform, rotation} = state;\n const oriented = image ? orientedSize({width: image.width, height: image.height}, rotation) : null;\n const band = scaleRange(state);\n const zoomLabel = Number.isFinite(band.min) && band.min > 0\n ? `${(transform.scale / band.min).toFixed(1)}×`\n : \"1.0×\";\n\n /**\n * ⚠️ HALF THE CROP, NOT THE WHOLE OF IT. `borderRadius` is a RADIUS, so a\n * circular window is half the longer side. Passing the full side used to\n * look correct only because iOS silently clamps a radius larger than half\n * the box — which also meant the scrim's radius was being clamped to a\n * DIFFERENT value than the window's, and the two shapes stopped agreeing\n * the moment the crop was not square.\n */\n const windowRadius = isCircle\n ? Math.max(crop.width, crop.height) / 2\n : Number(el.mask?.borderRadius ?? 0);\n\n /**\n * ⛔ THE HOLE IS PUNCHED WITH A BORDER, NOT A BOX-SHADOW. React Native has\n * no 9999px shadow spread to lean on the way the web file does, and four\n * scrim rectangles around the window can only ever cut a RECTANGLE — the\n * avatar case would need a second code path. A view the size of the crop\n * with an enormous border draws the scrim on all four sides at once and\n * follows `borderRadius`, so the circle falls out of the same code.\n *\n * ⛔ IT IS MEASURED FROM THE STAGE, AND A FIXED 2000 DID NOT RENDER AT ALL.\n * The crop always sits inside the stage, so reaching one stage-length out\n * from each edge is already more than enough to cover it — and the stage\n * clips anything beyond. The border then stays the same order of magnitude\n * as the view it is drawn on, instead of a 4000pt border on a 300pt stage\n * that iOS declined to paint, leaving the image completely undimmed.\n *\n * ⚠️ KEEP `windowRadius + SCRIM` EQUAL TO HALF THE OUTER BOX. That is what\n * makes the hole exactly `windowRadius` and keeps it off the clamp.\n */\n const SCRIM = Math.ceil(Math.max(viewport.width, viewport.height)) || 1;\n\n const toolButton = (icon: string, label: string, onPress: () => void, disabled?: boolean) => (\n <Pressable\n onPress={onPress}\n disabled={disabled}\n accessibilityRole=\"button\"\n accessibilityLabel={label}\n accessibilityState={{disabled: !!disabled}}\n style={{\n flexDirection: \"row\", alignItems: \"center\", gap: 6,\n paddingHorizontal: 8, paddingVertical: 8,\n opacity: disabled ? 0.5 : 1,\n }}\n >\n <Icon name={icon as never} size=\"sm\" color={el.icon?.color as string | undefined} />\n <RNText style={el.text}>{label}</RNText>\n </Pressable>\n );\n\n return (\n <View testID={testID} style={[{width: \"100%\"}, el.root]}>\n {title != null && (\n <View style={[{flexDirection: \"row\", alignItems: \"center\"}, el.header]}>\n <RNText style={el.title}>{title}</RNText>\n </View>\n )}\n\n <View style={[el.content]}>\n {!source || error ? (\n <View style={[{alignItems: \"center\"}, el.dropZone]}>\n <Icon name=\"image\" size=\"lg\" color={el.icon?.color as string | undefined} />\n <RNText style={el.text}>{error ? error.message : text.dropZone}</RNText>\n </View>\n ) : (\n <>\n <StageFrame gesture={detector}>\n <View\n ref={stageRef}\n onLayout={onStageLayout}\n // ⚠️ ONLY ONE PATH MAY MOVE THE IMAGE. With the\n // detector active the responder's job is reduced to\n // claiming the touch so the scroll view is blocked;\n // letting the full one through as well would apply\n // every delta of the same drag twice.\n {...(detector ? scrollBlocker.panHandlers : responder.panHandlers)}\n style={[{width: \"100%\", aspectRatio: 4 / 3, overflow: \"hidden\"}, el.container]}\n >\n {ready && image && oriented && (\n <Image\n source={{uri: image.uri}}\n // ⚠️ `resizeMode: 'stretch'` BECAUSE THE BOX IS ALREADY\n // THE RIGHT SHAPE. The geometry decides the exact\n // rectangle the image occupies; letting RN re-fit it\n // inside that rectangle would apply the scaling twice.\n resizeMode=\"stretch\"\n style={{\n position: \"absolute\",\n left: transform.x,\n top: transform.y,\n width: oriented.width * transform.scale,\n height: oriented.height * transform.scale,\n transform: [{rotate: `${rotation}deg`}],\n }}\n />\n )}\n\n {ready && (\n <>\n <View\n pointerEvents=\"none\"\n style={[\n {\n position: \"absolute\",\n left: crop.x - SCRIM,\n top: crop.y - SCRIM,\n width: crop.width + SCRIM * 2,\n height: crop.height + SCRIM * 2,\n borderWidth: SCRIM,\n borderColor: (el.shade?.backgroundColor as string) ?? \"rgba(0,0,0,0.6)\",\n borderRadius: windowRadius + SCRIM,\n },\n ]}\n />\n <View\n pointerEvents=\"none\"\n style={[\n {\n position: \"absolute\",\n left: crop.x, top: crop.y,\n width: crop.width, height: crop.height,\n borderStyle: \"solid\",\n },\n el.cropWindow,\n {borderRadius: windowRadius},\n ]}\n >\n {!hideGrid && [1, 2].map((n) => (\n <React.Fragment key={n}>\n <View style={[\n {position: \"absolute\", top: 0, bottom: 0, width: 1,\n left: (crop.width * n) / 3},\n el.gridLine,\n ]} />\n <View style={[\n {position: \"absolute\", left: 0, right: 0, height: 1,\n top: (crop.height * n) / 3},\n el.gridLine,\n ]} />\n </React.Fragment>\n ))}\n </View>\n\n <View\n pointerEvents=\"none\"\n style={[{position: \"absolute\", right: 8, bottom: 8}, el.zoomChip]}\n >\n <RNText style={{color: el.zoomChip?.color as string | undefined}}>\n {zoomLabel}\n </RNText>\n </View>\n </>\n )}\n\n {(loading || busy) && (\n <View style={[{position: \"absolute\", left: 0, right: 0, bottom: 0}, el.progressFill]} />\n )}\n </View>\n </StageFrame>\n\n <View style={{flexDirection: \"row\", alignItems: \"center\", gap: 12}}>\n <Icon name=\"zoom-in\" size=\"sm\" color={el.icon?.color as string | undefined} />\n <Slider\n style={{flex: 1}}\n minimumValue={0}\n maximumValue={1}\n value={zoomFraction(state)}\n disabled={!ready}\n minimumTrackTintColor={el.thumb?.backgroundColor as string | undefined}\n maximumTrackTintColor={el.track?.backgroundColor as string | undefined}\n thumbTintColor={el.thumb?.backgroundColor as string | undefined}\n onValueChange={(value) => dispatch({type: \"setZoom\", value})}\n />\n {!hidePreview && ready && image && oriented && (\n /* The preview is the same image, clipped and offset so the\n crop fills the frame — no pixel work, and therefore\n nothing that has to wait for an encoder. */\n <View\n style={[\n {width: 64, height: 64, overflow: \"hidden\"},\n el.previewFrame,\n isCircle ? {borderRadius: 32} : null,\n ]}\n >\n <Image\n source={{uri: image.uri}}\n resizeMode=\"stretch\"\n style={{\n position: \"absolute\",\n width: oriented.width * transform.scale * (64 / crop.width),\n height: oriented.height * transform.scale * (64 / crop.width),\n left: (transform.x - crop.x) * (64 / crop.width),\n top: (transform.y - crop.y) * (64 / crop.width),\n transform: [{rotate: `${rotation}deg`}],\n }}\n />\n </View>\n )}\n </View>\n </>\n )}\n </View>\n\n <View style={[{flexDirection: \"row\", alignItems: \"center\"}, el.footer]}>\n {toolButton(\"rotate-cw\", text.rotate, () => dispatch({type: \"rotate\"}), !ready)}\n {toolButton(\"rotate-ccw\", text.reset, () => dispatch({type: \"reset\"}), !ready)}\n <View style={{flex: 1}} />\n {toolButton(\"x\", text.cancel, () => onCancel?.())}\n {toolButton(\"check\", busy ? text.processing : text.save, () => void save(), !canSave)}\n </View>\n </View>\n );\n}\n\nexport default ImageCropper;\n"],"names":["jsx","useComponentDefaults","useResolvedElementStyles","DEFAULT_ASPECT","DEFAULT_MAX_ZOOM","DEFAULT_LABELS","aspectOf","useImageSource","useState","useRef","useCallback","useMemo","useReducer","cropperReducer","initialCropperState","useEffect","isUsablePinchReading","pinchScale","PanResponder","hasCropEncoder","cropToSourceRect","DEFAULT_FORMAT","encodeCropNative","DEFAULT_MAX_BYTES","orientedSize","scaleRange","jsxs","Pressable","Icon","RNText","View","Fragment","Image","zoomFraction"],"mappings":"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"}
|