@gaozh1024/photo-picker 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,620 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ MEDIA_PICKER_ROUTES: () => MEDIA_PICKER_ROUTES,
34
+ PhotoAlbumScreen: () => PhotoAlbumScreen,
35
+ PhotoCropScreen: () => PhotoCropScreen,
36
+ clearPhotoAlbumCompleteCallback: () => clearPhotoAlbumCompleteCallback,
37
+ clearPickerCache: () => clearPickerCache,
38
+ createCroppedPhotoAlbumItem: () => createCroppedPhotoAlbumItem,
39
+ formatPhotoPickerText: () => formatPhotoPickerText,
40
+ getPhotoAlbumCompleteCallback: () => getPhotoAlbumCompleteCallback,
41
+ isPhotoPickerNativeError: () => isPhotoPickerNativeError,
42
+ mediaPickerColors: () => mediaPickerColors,
43
+ normalizeOpenOptions: () => normalizeOpenOptions,
44
+ normalizePickerMediaType: () => normalizePickerMediaType,
45
+ pickMedia: () => pickMedia,
46
+ registerPhotoAlbumCompleteCallback: () => registerPhotoAlbumCompleteCallback,
47
+ releaseMedia: () => releaseMedia,
48
+ resolvePhotoPickerUiConfig: () => resolvePhotoPickerUiConfig
49
+ });
50
+ module.exports = __toCommonJS(index_exports);
51
+
52
+ // src/constants.ts
53
+ var MEDIA_PICKER_ROUTES = {
54
+ PHOTO_ALBUM: "PhotoAlbum",
55
+ PHOTO_CROP: "PhotoCrop"
56
+ };
57
+ var mediaPickerColors = {
58
+ primary: { 500: "#3b82f6" },
59
+ slate: {
60
+ 50: "#f8fafc",
61
+ 100: "#f1f5f9",
62
+ 200: "#e2e8f0",
63
+ 700: "#334155",
64
+ 800: "#1e293b",
65
+ 900: "#0f172a",
66
+ 950: "#020617"
67
+ }
68
+ };
69
+
70
+ // src/screens/PhotoAlbumScreen.tsx
71
+ var import_react = __toESM(require("react"));
72
+ var import_react_native2 = require("react-native");
73
+
74
+ // src/internal/photoPickerCallbackRegistry.ts
75
+ var callbackRegistry = /* @__PURE__ */ new Map();
76
+ function registerPhotoAlbumCompleteCallback(callback) {
77
+ const callbackId = `photo-picker-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
78
+ callbackRegistry.set(callbackId, callback);
79
+ return callbackId;
80
+ }
81
+ function getPhotoAlbumCompleteCallback(callbackId) {
82
+ return callbackId ? callbackRegistry.get(callbackId) : void 0;
83
+ }
84
+ function clearPhotoAlbumCompleteCallback(callbackId) {
85
+ if (callbackId) callbackRegistry.delete(callbackId);
86
+ }
87
+
88
+ // src/native.ts
89
+ var import_react_native = require("react-native");
90
+ var import_expo_modules_core = require("expo-modules-core");
91
+ function getNativeModule() {
92
+ if (import_react_native.Platform.OS !== "android") {
93
+ throw new Error(
94
+ "@gaozh1024/photo-picker currently provides the native system picker on Android only."
95
+ );
96
+ }
97
+ try {
98
+ return (0, import_expo_modules_core.requireNativeModule)("PhotoPickerModule");
99
+ } catch {
100
+ throw new Error(
101
+ "PhotoPickerModule is unavailable. Rebuild the Android app after installing @gaozh1024/photo-picker."
102
+ );
103
+ }
104
+ }
105
+ function pickMedia(options = {}) {
106
+ return getNativeModule().pickMedia(options);
107
+ }
108
+ function isPhotoPickerNativeError(error, code) {
109
+ if (!error || typeof error !== "object" || !("code" in error)) return false;
110
+ const errorCode = error.code;
111
+ return (errorCode === "PICKER_BUSY" || errorCode === "PICKER_LAUNCH_FAILED" || errorCode === "PICKER_SELECTION_LIMIT_UNSUPPORTED") && (code === void 0 || errorCode === code);
112
+ }
113
+ function releaseMedia(uris) {
114
+ return getNativeModule().releaseMedia(uris);
115
+ }
116
+ function clearPickerCache() {
117
+ return getNativeModule().clearPickerCache();
118
+ }
119
+
120
+ // src/utils/photoPickerFlow.ts
121
+ var DEFAULT_TEXTS = {
122
+ albumTitle: "\u9009\u62E9\u5A92\u4F53",
123
+ cropTitle: "\u88C1\u526A\u56FE\u7247",
124
+ cropConfirmButton: "\u5B8C\u6210",
125
+ cropSavingButton: "\u4FDD\u5B58\u4E2D",
126
+ cropCircleHint: "\u62D6\u52A8\u548C\u7F29\u653E\u56FE\u7247\uFF0C\u4F7F\u4E3B\u4F53\u4F4D\u4E8E\u5706\u5F62\u533A\u57DF\u5185",
127
+ cropRectHint: "\u62D6\u52A8\u548C\u7F29\u653E\u56FE\u7247\uFF0C\u8C03\u6574\u88C1\u526A\u533A\u57DF",
128
+ cropMissingPhoto: "\u672A\u627E\u5230\u53EF\u88C1\u526A\u56FE\u7247",
129
+ durationLimitAlertTitle: "\u63D0\u793A",
130
+ durationLimitAlertMessage: "\u89C6\u9891\u65F6\u957F\u4E0D\u80FD\u8D85\u8FC7 {maxDuration} \u79D2",
131
+ openAlbumError: "\u6253\u5F00\u7CFB\u7EDF\u9009\u62E9\u5668\u5931\u8D25"
132
+ };
133
+ function normalizeOpenOptions(options) {
134
+ const requestedMaxSelection = options?.maxSelection ?? 9;
135
+ const maxSelection = Number.isFinite(requestedMaxSelection) ? Math.max(1, Math.floor(requestedMaxSelection)) : 1;
136
+ const crop = options?.crop;
137
+ const allowsMultipleSelection = crop ? false : options?.allowsMultipleSelection ?? maxSelection > 1;
138
+ return {
139
+ ...options,
140
+ crop,
141
+ maxSelection: crop ? 1 : allowsMultipleSelection ? maxSelection : 1,
142
+ mediaType: crop ? "photo" : options?.mediaType ?? "all",
143
+ allowsMultipleSelection
144
+ };
145
+ }
146
+ function resolvePhotoPickerUiConfig(uiConfig) {
147
+ return {
148
+ texts: {
149
+ ...DEFAULT_TEXTS,
150
+ ...uiConfig?.texts
151
+ }
152
+ };
153
+ }
154
+ function formatPhotoPickerText(template, variables) {
155
+ return template.replace(/\{(\w+)\}/g, (_, key) => String(variables[key] ?? ""));
156
+ }
157
+ function createCroppedPhotoAlbumItem(photo, manipulated, crop) {
158
+ const baseName = photo.filename ?? photo.fileName ?? "photo";
159
+ const croppedName = `${baseName.replace(/\.[^.]+$/, "")}-cropped.jpg`;
160
+ return {
161
+ ...photo,
162
+ id: `${photo.id}-crop-${Date.now()}`,
163
+ uri: manipulated.uri,
164
+ localUri: manipulated.uri,
165
+ filename: croppedName,
166
+ fileName: croppedName,
167
+ mimeType: "image/jpeg",
168
+ width: manipulated.width,
169
+ height: manipulated.height,
170
+ edited: true,
171
+ crop: {
172
+ ...crop,
173
+ width: manipulated.width,
174
+ height: manipulated.height
175
+ }
176
+ };
177
+ }
178
+ function normalizePickerMediaType(mediaType) {
179
+ return mediaType === "photo" || mediaType === "video" || mediaType === "all" ? mediaType : "all";
180
+ }
181
+
182
+ // src/screens/PhotoAlbumScreen.tsx
183
+ var import_jsx_runtime = require("react/jsx-runtime");
184
+ function PhotoAlbumScreen({ route, navigation }) {
185
+ const startedRef = import_react.default.useRef(false);
186
+ const [errorMessage, setErrorMessage] = import_react.default.useState();
187
+ const callbackId = route?.params?.callbackId;
188
+ const options = import_react.default.useMemo(
189
+ () => normalizeOpenOptions({
190
+ ...route?.params?.options,
191
+ maxSelection: route?.params?.options?.maxSelection ?? route?.params?.maxSelection,
192
+ allowsMultipleSelection: route?.params?.options?.allowsMultipleSelection ?? route?.params?.allowsMultipleSelection,
193
+ mediaType: route?.params?.options?.mediaType ?? (route?.params?.mediaTypes?.length === 1 ? route.params.mediaTypes[0] : "all")
194
+ }),
195
+ [
196
+ route?.params?.allowsMultipleSelection,
197
+ route?.params?.maxSelection,
198
+ route?.params?.mediaTypes,
199
+ route?.params?.options
200
+ ]
201
+ );
202
+ const uiConfig = import_react.default.useMemo(
203
+ () => resolvePhotoPickerUiConfig({
204
+ ...route?.params?.uiConfig,
205
+ ...options.uiConfig,
206
+ texts: {
207
+ ...route?.params?.uiConfig?.texts,
208
+ ...options.uiConfig?.texts
209
+ }
210
+ }),
211
+ [options.uiConfig, route?.params?.uiConfig]
212
+ );
213
+ const routeNames = import_react.default.useMemo(
214
+ () => ({
215
+ photoAlbum: route?.params?.routeNames?.photoAlbum ?? MEDIA_PICKER_ROUTES.PHOTO_ALBUM,
216
+ photoCrop: route?.params?.routeNames?.photoCrop ?? MEDIA_PICKER_ROUTES.PHOTO_CROP
217
+ }),
218
+ [route?.params?.routeNames?.photoAlbum, route?.params?.routeNames?.photoCrop]
219
+ );
220
+ const close = import_react.default.useCallback(() => {
221
+ clearPhotoAlbumCompleteCallback(callbackId);
222
+ navigation?.goBack();
223
+ }, [callbackId, navigation]);
224
+ const complete = import_react.default.useCallback(
225
+ (assets) => {
226
+ const overLimitVideo = options.maxVideoDuration == null ? void 0 : assets.find(
227
+ (asset) => asset.mediaType === "video" && (asset.duration ?? 0) > options.maxVideoDuration
228
+ );
229
+ if (overLimitVideo) {
230
+ import_react_native2.Alert.alert(
231
+ uiConfig.texts.durationLimitAlertTitle,
232
+ formatPhotoPickerText(uiConfig.texts.durationLimitAlertMessage, {
233
+ maxDuration: options.maxVideoDuration
234
+ })
235
+ );
236
+ return;
237
+ }
238
+ if (options.crop) {
239
+ const photo = assets[0];
240
+ if (!photo) return;
241
+ navigation?.navigate?.(routeNames.photoCrop, {
242
+ photo,
243
+ crop: options.crop,
244
+ quality: options.quality,
245
+ callbackId,
246
+ routeNames,
247
+ uiConfig: options.uiConfig
248
+ });
249
+ return;
250
+ }
251
+ getPhotoAlbumCompleteCallback(callbackId)?.(assets);
252
+ clearPhotoAlbumCompleteCallback(callbackId);
253
+ navigation?.goBack();
254
+ },
255
+ [
256
+ callbackId,
257
+ navigation,
258
+ options,
259
+ routeNames,
260
+ uiConfig.texts.durationLimitAlertMessage,
261
+ uiConfig.texts.durationLimitAlertTitle
262
+ ]
263
+ );
264
+ const openPicker = import_react.default.useCallback(async () => {
265
+ setErrorMessage(void 0);
266
+ try {
267
+ const result = await pickMedia({
268
+ mediaType: options.mediaType,
269
+ maxSelection: options.maxSelection,
270
+ allowsMultipleSelection: options.allowsMultipleSelection,
271
+ cacheMode: "copy"
272
+ });
273
+ if (result.cancelled) {
274
+ close();
275
+ return;
276
+ }
277
+ complete(result.assets);
278
+ } catch (error) {
279
+ setErrorMessage(error instanceof Error ? error.message : uiConfig.texts.openAlbumError);
280
+ }
281
+ }, [
282
+ close,
283
+ complete,
284
+ options.allowsMultipleSelection,
285
+ options.maxSelection,
286
+ options.mediaType,
287
+ uiConfig.texts.openAlbumError
288
+ ]);
289
+ import_react.default.useEffect(() => {
290
+ if (startedRef.current) return;
291
+ startedRef.current = true;
292
+ void openPicker();
293
+ }, [openPicker]);
294
+ import_react.default.useEffect(
295
+ () => () => {
296
+ if (!options.crop) {
297
+ clearPhotoAlbumCompleteCallback(callbackId);
298
+ }
299
+ },
300
+ [callbackId, options.crop]
301
+ );
302
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: styles.container, children: [
303
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.ActivityIndicator, { size: "large", color: "#3b82f6" }),
304
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: styles.title, children: errorMessage ? uiConfig.texts.openAlbumError : uiConfig.texts.albumTitle }),
305
+ errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: styles.message, children: errorMessage }) : null,
306
+ errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: styles.actions, children: [
307
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Pressable, { onPress: () => void openPicker(), style: styles.primaryButton, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: styles.primaryButtonText, children: "\u91CD\u8BD5" }) }),
308
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Pressable, { onPress: close, style: styles.secondaryButton, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: styles.secondaryButtonText, children: "\u53D6\u6D88" }) })
309
+ ] }) : null
310
+ ] });
311
+ }
312
+ var styles = import_react_native2.StyleSheet.create({
313
+ container: {
314
+ flex: 1,
315
+ alignItems: "center",
316
+ justifyContent: "center",
317
+ padding: 24,
318
+ backgroundColor: "#f8fafc"
319
+ },
320
+ title: { marginTop: 16, color: "#0f172a", fontSize: 16, fontWeight: "600" },
321
+ message: { marginTop: 8, color: "#64748b", fontSize: 13, lineHeight: 20, textAlign: "center" },
322
+ actions: { flexDirection: "row", gap: 12, marginTop: 20 },
323
+ primaryButton: {
324
+ borderRadius: 8,
325
+ backgroundColor: "#2563eb",
326
+ paddingHorizontal: 18,
327
+ paddingVertical: 10
328
+ },
329
+ primaryButtonText: { color: "#ffffff", fontSize: 14, fontWeight: "600" },
330
+ secondaryButton: {
331
+ borderRadius: 8,
332
+ borderWidth: 1,
333
+ borderColor: "#cbd5e1",
334
+ paddingHorizontal: 18,
335
+ paddingVertical: 10
336
+ },
337
+ secondaryButtonText: { color: "#334155", fontSize: 14, fontWeight: "600" }
338
+ });
339
+
340
+ // src/screens/PhotoCropScreen.tsx
341
+ var import_react2 = __toESM(require("react"));
342
+ var import_react_native3 = require("react-native");
343
+ var import_native2 = require("@react-navigation/native");
344
+ var import_react_native_zoom_toolkit = require("react-native-zoom-toolkit");
345
+ var import_expo_image = require("expo-image");
346
+ var import_expo_image_manipulator = require("expo-image-manipulator");
347
+ var import_react_native_safe_area_context = require("react-native-safe-area-context");
348
+ var import_jsx_runtime2 = require("react/jsx-runtime");
349
+ var { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = import_react_native3.Dimensions.get("window");
350
+ var HEADER_ROW_HEIGHT = 48;
351
+ var FOOTER_MIN_HEIGHT = 52;
352
+ var CONTENT_HORIZONTAL_PADDING = 16;
353
+ var CONTENT_VERTICAL_PADDING = 12;
354
+ function getCropBoxSize(aspect, maxWidth, maxHeight) {
355
+ const ratio = aspect[0] / aspect[1];
356
+ let width = maxWidth;
357
+ let height = width / ratio;
358
+ if (height > maxHeight) {
359
+ height = maxHeight;
360
+ width = height * ratio;
361
+ }
362
+ return { width: Math.floor(width), height: Math.floor(height) };
363
+ }
364
+ function getImageDisplaySize(imageWidth, imageHeight, cropWidth, cropHeight) {
365
+ const imageRatio = imageWidth / imageHeight;
366
+ const cropRatio = cropWidth / cropHeight;
367
+ return imageRatio > cropRatio ? { width: cropHeight * imageRatio, height: cropHeight } : { width: cropWidth, height: cropWidth / imageRatio };
368
+ }
369
+ function PhotoCropScreen({ route, navigation }) {
370
+ const insets = (0, import_react_native_safe_area_context.useSafeAreaInsets)();
371
+ const isFocused = (0, import_native2.useIsFocused)();
372
+ const cropRef = import_react2.default.useRef(null);
373
+ const [saving, setSaving] = import_react2.default.useState(false);
374
+ const [closing, setClosing] = import_react2.default.useState(false);
375
+ const [contentLayout, setContentLayout] = import_react2.default.useState({ width: 0, height: 0 });
376
+ const photo = route?.params?.photo;
377
+ const cropOptions = route?.params?.crop;
378
+ const callbackId = route?.params?.callbackId;
379
+ const uiConfig = import_react2.default.useMemo(
380
+ () => resolvePhotoPickerUiConfig(route?.params?.uiConfig),
381
+ [route?.params?.uiConfig]
382
+ );
383
+ const quality = route?.params?.quality ?? cropOptions?.quality ?? 1;
384
+ const aspect = cropOptions?.aspect ?? [1, 1];
385
+ const isCircleCrop = cropOptions?.shape === "circle";
386
+ const headerHeight = insets.top + HEADER_ROW_HEIGHT;
387
+ const footerHeight = Math.max(insets.bottom, 16) + FOOTER_MIN_HEIGHT;
388
+ const cropBox = import_react2.default.useMemo(
389
+ () => getCropBoxSize(
390
+ aspect,
391
+ SCREEN_WIDTH - CONTENT_HORIZONTAL_PADDING * 2,
392
+ SCREEN_HEIGHT - headerHeight - footerHeight - CONTENT_VERTICAL_PADDING * 2
393
+ ),
394
+ [aspect, footerHeight, headerHeight]
395
+ );
396
+ const cropFramePosition = import_react2.default.useMemo(
397
+ () => ({
398
+ left: Math.max(0, (contentLayout.width - cropBox.width) / 2),
399
+ top: Math.max(0, (contentLayout.height - cropBox.height) / 2)
400
+ }),
401
+ [contentLayout.height, contentLayout.width, cropBox.height, cropBox.width]
402
+ );
403
+ const imageSize = import_react2.default.useMemo(
404
+ () => !photo ? { width: cropBox.width, height: cropBox.height } : getImageDisplaySize(
405
+ Math.max(photo.width, 1),
406
+ Math.max(photo.height, 1),
407
+ cropBox.width,
408
+ cropBox.height
409
+ ),
410
+ [cropBox.height, cropBox.width, photo]
411
+ );
412
+ const closeFlow = import_react2.default.useCallback(
413
+ (count = 1) => {
414
+ if (closing) return;
415
+ setClosing(true);
416
+ requestAnimationFrame(() => {
417
+ if (count > 1 && navigation?.pop) navigation.pop(count);
418
+ else navigation?.goBack();
419
+ });
420
+ },
421
+ [closing, navigation]
422
+ );
423
+ const handleCancel = import_react2.default.useCallback(() => {
424
+ clearPhotoAlbumCompleteCallback(callbackId);
425
+ closeFlow();
426
+ }, [callbackId, closeFlow]);
427
+ const handleConfirm = import_react2.default.useCallback(async () => {
428
+ if (!photo || !cropRef.current || saving) return;
429
+ setSaving(true);
430
+ try {
431
+ const result = cropRef.current.crop();
432
+ const manipulated = await (0, import_expo_image_manipulator.manipulateAsync)(photo.uri, [{ crop: result.crop }], {
433
+ compress: quality,
434
+ format: import_expo_image_manipulator.SaveFormat.JPEG
435
+ });
436
+ const croppedPhoto = createCroppedPhotoAlbumItem(photo, manipulated, cropOptions);
437
+ getPhotoAlbumCompleteCallback(callbackId)?.([croppedPhoto]);
438
+ clearPhotoAlbumCompleteCallback(callbackId);
439
+ closeFlow(2);
440
+ } catch (error) {
441
+ console.error("[photo-picker] crop failed", error);
442
+ } finally {
443
+ setSaving(false);
444
+ }
445
+ }, [callbackId, closeFlow, cropOptions, photo, quality, saving]);
446
+ if (!photo) {
447
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: styles2.empty, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: styles2.emptyText, children: uiConfig.texts.cropMissingPhoto }) });
448
+ }
449
+ const footerHint = isCircleCrop ? uiConfig.texts.cropCircleHint : uiConfig.texts.cropRectHint;
450
+ const showContent = isFocused && !closing;
451
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.screen, children: [
452
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: [styles2.header, { height: headerHeight, paddingTop: insets.top }], children: [
453
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Pressable, { onPress: handleCancel, style: styles2.headerButton, accessibilityRole: "button", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: styles2.headerIcon, children: "\xD7" }) }),
454
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: styles2.headerTitle, children: uiConfig.texts.cropTitle }),
455
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
456
+ import_react_native3.Pressable,
457
+ {
458
+ onPress: handleConfirm,
459
+ style: styles2.headerButton,
460
+ disabled: saving,
461
+ accessibilityRole: "button",
462
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: styles2.headerAction, children: saving ? uiConfig.texts.cropSavingButton : uiConfig.texts.cropConfirmButton })
463
+ }
464
+ )
465
+ ] }),
466
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
467
+ import_react_native3.View,
468
+ {
469
+ style: styles2.content,
470
+ onLayout: ({ nativeEvent }) => setContentLayout(nativeEvent.layout),
471
+ children: [
472
+ showContent ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
473
+ import_react_native_zoom_toolkit.CropZoom,
474
+ {
475
+ ref: cropRef,
476
+ cropSize: cropBox,
477
+ resolution: { width: Math.max(photo.width, 1), height: Math.max(photo.height, 1) },
478
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
479
+ import_expo_image.Image,
480
+ {
481
+ source: { uri: photo.uri },
482
+ style: imageSize,
483
+ contentFit: "cover",
484
+ transition: 0,
485
+ cachePolicy: "memory-disk"
486
+ }
487
+ )
488
+ }
489
+ ) : null,
490
+ showContent && contentLayout.width > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { pointerEvents: "none", style: styles2.overlay, children: [
491
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
492
+ import_react_native3.View,
493
+ {
494
+ style: [styles2.mask, { left: 0, top: 0, right: 0, height: cropFramePosition.top }]
495
+ }
496
+ ),
497
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
498
+ import_react_native3.View,
499
+ {
500
+ style: [
501
+ styles2.mask,
502
+ {
503
+ left: 0,
504
+ top: cropFramePosition.top,
505
+ width: cropFramePosition.left,
506
+ height: cropBox.height
507
+ }
508
+ ]
509
+ }
510
+ ),
511
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
512
+ import_react_native3.View,
513
+ {
514
+ style: [
515
+ styles2.mask,
516
+ {
517
+ left: cropFramePosition.left + cropBox.width,
518
+ top: cropFramePosition.top,
519
+ right: 0,
520
+ height: cropBox.height
521
+ }
522
+ ]
523
+ }
524
+ ),
525
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
526
+ import_react_native3.View,
527
+ {
528
+ style: [
529
+ styles2.mask,
530
+ { left: 0, right: 0, top: cropFramePosition.top + cropBox.height, bottom: 0 }
531
+ ]
532
+ }
533
+ ),
534
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
535
+ import_react_native3.View,
536
+ {
537
+ style: [
538
+ styles2.cropFrame,
539
+ {
540
+ width: cropBox.width,
541
+ height: cropBox.height,
542
+ left: cropFramePosition.left,
543
+ top: cropFramePosition.top,
544
+ borderRadius: isCircleCrop ? cropBox.width / 2 : 16
545
+ }
546
+ ]
547
+ }
548
+ )
549
+ ] }) : null
550
+ ]
551
+ }
552
+ ),
553
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
554
+ import_react_native3.View,
555
+ {
556
+ style: [
557
+ styles2.footer,
558
+ { minHeight: footerHeight, paddingBottom: Math.max(insets.bottom, 16) }
559
+ ],
560
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Text, { style: styles2.hint, children: footerHint })
561
+ }
562
+ )
563
+ ] });
564
+ }
565
+ var styles2 = import_react_native3.StyleSheet.create({
566
+ screen: { flex: 1, backgroundColor: "#000000" },
567
+ empty: { flex: 1, justifyContent: "center", alignItems: "center" },
568
+ emptyText: { color: "#334155", fontSize: 16 },
569
+ header: {
570
+ flexDirection: "row",
571
+ alignItems: "center",
572
+ justifyContent: "space-between",
573
+ paddingHorizontal: 12,
574
+ backgroundColor: "#000000"
575
+ },
576
+ headerButton: { minWidth: 52, height: 40, justifyContent: "center", alignItems: "center" },
577
+ headerTitle: { color: "#ffffff", fontSize: 18, fontWeight: "600" },
578
+ headerIcon: { color: "#ffffff", fontSize: 30, lineHeight: 32 },
579
+ headerAction: { color: "#ffffff", fontSize: 15, fontWeight: "600" },
580
+ content: {
581
+ flex: 1,
582
+ justifyContent: "center",
583
+ alignItems: "center",
584
+ overflow: "hidden",
585
+ paddingHorizontal: CONTENT_HORIZONTAL_PADDING,
586
+ paddingVertical: CONTENT_VERTICAL_PADDING,
587
+ backgroundColor: "#000000"
588
+ },
589
+ overlay: { ...import_react_native3.StyleSheet.absoluteFillObject },
590
+ mask: { position: "absolute", backgroundColor: "rgba(0,0,0,0.58)" },
591
+ cropFrame: { position: "absolute", borderWidth: 2, borderColor: "#ffffff" },
592
+ footer: {
593
+ alignItems: "center",
594
+ justifyContent: "center",
595
+ paddingHorizontal: 16,
596
+ paddingTop: 12,
597
+ backgroundColor: "#000000"
598
+ },
599
+ hint: { color: "rgba(255,255,255,0.8)" }
600
+ });
601
+ // Annotate the CommonJS export names for ESM import in node:
602
+ 0 && (module.exports = {
603
+ MEDIA_PICKER_ROUTES,
604
+ PhotoAlbumScreen,
605
+ PhotoCropScreen,
606
+ clearPhotoAlbumCompleteCallback,
607
+ clearPickerCache,
608
+ createCroppedPhotoAlbumItem,
609
+ formatPhotoPickerText,
610
+ getPhotoAlbumCompleteCallback,
611
+ isPhotoPickerNativeError,
612
+ mediaPickerColors,
613
+ normalizeOpenOptions,
614
+ normalizePickerMediaType,
615
+ pickMedia,
616
+ registerPhotoAlbumCompleteCallback,
617
+ releaseMedia,
618
+ resolvePhotoPickerUiConfig
619
+ });
620
+ //# sourceMappingURL=index.js.map