@phucprime/react-native-image-editor 1.0.3 → 1.0.4

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.
Files changed (33) hide show
  1. package/README.md +51 -31
  2. package/android/build.gradle +5 -1
  3. package/android/src/main/java/ui/photoeditor/RNPhotoEditorModule.java +155 -60
  4. package/android/src/main/java/ui/photoeditor/RNPhotoEditorPackage.java +50 -12
  5. package/android/src/newarch/java/ui/photoeditor/RNPhotoEditorSpec.java +10 -2
  6. package/android/src/oldarch/java/ui/photoeditor/RNPhotoEditorSpec.java +22 -4
  7. package/ios/RNImageEditor.podspec +39 -8
  8. package/ios/RNPhotoEditor.mm +32 -6
  9. package/ios/RNPhotoEditor.swift +178 -95
  10. package/lib/commonjs/NativeRNPhotoEditor.js +41 -0
  11. package/lib/commonjs/NativeRNPhotoEditor.js.map +1 -0
  12. package/lib/commonjs/index.js +142 -0
  13. package/lib/commonjs/index.js.map +1 -0
  14. package/lib/commonjs/package.json +1 -0
  15. package/lib/module/NativeRNPhotoEditor.js +40 -0
  16. package/lib/module/NativeRNPhotoEditor.js.map +1 -0
  17. package/lib/module/index.js +140 -0
  18. package/lib/module/index.js.map +1 -0
  19. package/lib/typescript/NativeRNPhotoEditor.d.ts +46 -0
  20. package/lib/typescript/NativeRNPhotoEditor.d.ts.map +1 -0
  21. package/lib/{index.d.ts → typescript/index.d.ts} +44 -34
  22. package/lib/typescript/index.d.ts.map +1 -0
  23. package/package.json +49 -18
  24. package/src/NativeRNPhotoEditor.ts +38 -8
  25. package/src/index.ts +85 -80
  26. package/android/libs/photo-editor-android-original.jar +0 -0
  27. package/lib/NativeRNPhotoEditor.d.ts +0 -14
  28. package/lib/NativeRNPhotoEditor.d.ts.map +0 -1
  29. package/lib/NativeRNPhotoEditor.js +0 -5
  30. package/lib/NativeRNPhotoEditor.js.map +0 -1
  31. package/lib/index.d.ts.map +0 -1
  32. package/lib/index.js +0 -120
  33. package/lib/index.js.map +0 -1
@@ -2,167 +2,250 @@ import Foundation
2
2
  import UIKit
3
3
  import iOSPhotoEditor
4
4
 
5
+ /**
6
+ * Native implementation of the RNPhotoEditor TurboModule.
7
+ *
8
+ * Promise migration
9
+ * ─────────────────
10
+ * The previous implementation stored two `RCTResponseSenderBlock` references
11
+ * as instance properties and invoked them from the `PhotoEditorDelegate`
12
+ * callbacks. On New Architecture, `RCTResponseSenderBlock` values are backed
13
+ * by `CallbackHolder` objects tied to the active JS context. If the JS context
14
+ * reloads (Fast Refresh, error-boundary recovery) while the editor is open,
15
+ * those stored blocks become dangling — invoking them is a no-op or a crash.
16
+ *
17
+ * `RCTPromiseResolveBlock` / `RCTPromiseRejectBlock` are designed for exactly
18
+ * this single-shot async pattern and are safe across context reloads.
19
+ *
20
+ * UIApplication.shared.delegate?.window deprecation fix
21
+ * ──────────────────────────────────────────────────────
22
+ * `UIApplication.shared.delegate?.window` is deprecated in iOS 15. Apps that
23
+ * adopt `UIWindowScene` (the default since iOS 13 / Xcode 11) set their window
24
+ * on the scene, not on the app delegate, so the delegate window is nil.
25
+ * The fix enumerates `connectedScenes` and finds the foreground-active
26
+ * `UIWindowScene`, then picks its key window.
27
+ */
5
28
  @objc(RNPhotoEditor)
6
29
  class RNPhotoEditor: NSObject {
7
30
 
8
- private var editImagePath: String?
9
- private var onDoneEditing: RCTResponseSenderBlock?
10
- private var onCancelEditing: RCTResponseSenderBlock?
31
+ // MARK: - Stored promise blocks
11
32
 
12
- @objc static func requiresMainQueueSetup() -> Bool {
13
- return true
14
- }
33
+ /// Stored until the delegate fires. Both are set together and cleared together.
34
+ private var pendingResolve: RCTPromiseResolveBlock?
35
+ private var pendingReject: RCTPromiseRejectBlock?
15
36
 
16
- @objc func methodQueue() -> DispatchQueue {
17
- return .main
18
- }
37
+ /// Path of the image currently being edited; needed by doneEditing to write the result.
38
+ private var editImagePath: String?
19
39
 
20
- @objc(Edit:onDone:onCancel:)
21
- func edit(_ props: NSDictionary, onDone: @escaping RCTResponseSenderBlock, onCancel: @escaping RCTResponseSenderBlock) {
40
+ // MARK: - RN module config
41
+
42
+ @objc static func requiresMainQueueSetup() -> Bool { true }
43
+
44
+ @objc func methodQueue() -> DispatchQueue { .main }
45
+
46
+ // MARK: - Native method (matches RCT_EXTERN_METHOD selector in .mm)
47
+
48
+ /**
49
+ * Opens the photo editor.
50
+ *
51
+ * Called by the JS bridge via RCT_EXTERN_METHOD.
52
+ * Matches the codegen spec: `edit(props: UnsafeObject): Promise<string>`.
53
+ *
54
+ * - Parameters:
55
+ * - props: Editor configuration dictionary (path, colors, stickers, hiddenControls).
56
+ * - resolve: Called with the saved image path on success.
57
+ * - reject: Called with code "CANCELLED" when the user dismisses without saving.
58
+ */
59
+ @objc(edit:resolve:reject:)
60
+ func edit(
61
+ _ props: NSDictionary,
62
+ resolve: @escaping RCTPromiseResolveBlock,
63
+ reject: @escaping RCTPromiseRejectBlock
64
+ ) {
65
+ // Dispatch all UI work to main thread — methodQueue() returns .main, but
66
+ // being explicit here guards against any future threading changes.
22
67
  DispatchQueue.main.async { [weak self] in
23
- guard let self = self else { return }
68
+ guard let self else { return }
69
+
70
+ // Reject immediately if another edit is already in flight
71
+ if self.pendingResolve != nil {
72
+ reject("ALREADY_OPEN", "An editor session is already in progress", nil)
73
+ return
74
+ }
24
75
 
25
- self.editImagePath = props["path"] as? String
26
- self.onDoneEditing = onDone
27
- self.onCancelEditing = onCancel
76
+ self.pendingResolve = resolve
77
+ self.pendingReject = reject
78
+ self.editImagePath = props["path"] as? String
28
79
 
80
+ // ── Build the PhotoEditorViewController ───────────────────────────
29
81
  let photoEditor = PhotoEditorViewController(
30
82
  nibName: "PhotoEditorViewController",
31
83
  bundle: Bundle(for: PhotoEditorViewController.self)
32
84
  )
33
85
 
34
- // Set language translations
35
- // TODO: Re-enable when TranslationService is added to the iOSPhotoEditor pod
36
- // if let languages = props["languages"] as? [String: String] {
37
- // TranslationService.shared.initTranslations(languages)
38
- // }
39
-
40
- // Process Image for Editing
41
- var image: UIImage?
86
+ // ── Image ─────────────────────────────────────────────────────────
42
87
  if let path = self.editImagePath {
43
- image = UIImage(contentsOfFile: path)
88
+ var image = UIImage(contentsOfFile: path)
44
89
  if image == nil, let url = URL(string: path), let data = try? Data(contentsOf: url) {
45
90
  image = UIImage(data: data)
46
91
  }
92
+ photoEditor.image = image
47
93
  }
48
- photoEditor.image = image
49
94
 
50
- // Process Stickers
95
+ // ── Stickers ──────────────────────────────────────────────────────
51
96
  if let stickers = props["stickers"] as? [String] {
52
97
  photoEditor.stickers = stickers.compactMap { UIImage(named: $0) }
53
98
  }
54
99
 
55
- // Process Controls
100
+ // ── Hidden controls ───────────────────────────────────────────────
56
101
  if let hiddenControls = props["hiddenControls"] as? [String] {
57
- photoEditor.hiddenControls = hiddenControls.compactMap { controlName in
58
- switch controlName.lowercased() {
59
- case "crop": return .crop
102
+ photoEditor.hiddenControls = hiddenControls.compactMap { name in
103
+ switch name.lowercased() {
104
+ case "crop": return .crop
60
105
  case "sticker": return .sticker
61
- case "draw": return .draw
62
- case "text": return .text
63
- case "save": return .save
64
- case "share": return .share
65
- case "clear": return .clear
66
- default: return nil
106
+ case "draw": return .draw
107
+ case "text": return .text
108
+ case "save": return .save
109
+ case "share": return .share
110
+ case "clear": return .clear
111
+ default: return nil
67
112
  }
68
113
  }
69
114
  }
70
115
 
71
- // Process Colors
116
+ // ── Colors ────────────────────────────────────────────────────────
72
117
  if let colors = props["colors"] as? [String] {
73
118
  photoEditor.colors = colors.compactMap { self.color(fromHexString: $0) }
74
119
  }
75
120
 
76
- // Invoke Editor
121
+ // ── Present ───────────────────────────────────────────────────────
77
122
  photoEditor.photoEditorDelegate = self
78
-
79
- // The default modal presenting is page sheet in iOS 13, not full screen
80
123
  photoEditor.modalPresentationStyle = .fullScreen
81
124
 
82
- guard let rootViewController = UIApplication.shared.delegate?.window??.rootViewController else { return }
83
-
84
- if let presentedVC = rootViewController.presentedViewController {
85
- presentedVC.present(photoEditor, animated: true, completion: nil)
86
- } else {
87
- rootViewController.present(photoEditor, animated: true, completion: nil)
125
+ guard let presenter = self.topPresentingViewController() else {
126
+ self.clearPending()
127
+ reject("NO_VIEW_CONTROLLER", "Could not find a view controller to present from", nil)
128
+ return
88
129
  }
130
+ presenter.present(photoEditor, animated: true)
89
131
  }
90
132
  }
91
133
 
92
- // MARK: - Hex Color Utilities
134
+ // MARK: - Helpers
93
135
 
94
- private func colorComponent(from string: String, start: Int, length: Int) -> CGFloat {
95
- let startIndex = string.index(string.startIndex, offsetBy: start)
96
- let endIndex = string.index(startIndex, offsetBy: length)
97
- var substring = String(string[startIndex..<endIndex])
98
- if length == 1 {
99
- substring = "\(substring)\(substring)"
136
+ /// Clears stored promise blocks and edit path after the session ends.
137
+ private func clearPending() {
138
+ pendingResolve = nil
139
+ pendingReject = nil
140
+ editImagePath = nil
141
+ }
142
+
143
+ /**
144
+ * Returns the topmost view controller that can present modally.
145
+ *
146
+ * Uses `connectedScenes` instead of the deprecated
147
+ * `UIApplication.shared.delegate?.window` API. The delegate window is
148
+ * `nil` in scene-based apps (UIWindowScene, the default since iOS 13).
149
+ */
150
+ private func topPresentingViewController() -> UIViewController? {
151
+ // Find the foreground-active window scene
152
+ let scene = UIApplication.shared.connectedScenes
153
+ .first { $0.activationState == .foregroundActive } as? UIWindowScene
154
+
155
+ // Pick the key window from that scene (or fall back to any window)
156
+ let rootVC = scene?.windows.first(where: { $0.isKeyWindow })?.rootViewController
157
+ ?? scene?.windows.first?.rootViewController
158
+
159
+ guard let root = rootVC else { return nil }
160
+
161
+ // Walk up the presentation chain to find the topmost presenter
162
+ var top: UIViewController = root
163
+ while let presented = top.presentedViewController {
164
+ top = presented
100
165
  }
101
- var hexComponent: UInt64 = 0
102
- Scanner(string: substring).scanHexInt64(&hexComponent)
103
- return CGFloat(hexComponent) / 255.0
166
+ return top
167
+ }
168
+
169
+ // MARK: - Hex color parser
170
+
171
+ private func colorComponent(from string: String, start: Int, length: Int) -> CGFloat {
172
+ let s = string.index(string.startIndex, offsetBy: start)
173
+ let e = string.index(s, offsetBy: length)
174
+ var sub = String(string[s..<e])
175
+ if length == 1 { sub += sub }
176
+ var hex: UInt64 = 0
177
+ Scanner(string: sub).scanHexInt64(&hex)
178
+ return CGFloat(hex) / 255.0
104
179
  }
105
180
 
106
181
  private func color(fromHexString hexString: String) -> UIColor? {
107
- let colorString = hexString.replacingOccurrences(of: "#", with: "").uppercased()
108
- let alpha: CGFloat
109
- let red: CGFloat
110
- let green: CGFloat
111
- let blue: CGFloat
112
-
113
- switch colorString.count {
114
- case 3: // #RGB
115
- alpha = 1.0
116
- red = colorComponent(from: colorString, start: 0, length: 1)
117
- green = colorComponent(from: colorString, start: 1, length: 1)
118
- blue = colorComponent(from: colorString, start: 2, length: 1)
119
- case 4: // #ARGB
120
- alpha = colorComponent(from: colorString, start: 0, length: 1)
121
- red = colorComponent(from: colorString, start: 1, length: 1)
122
- green = colorComponent(from: colorString, start: 2, length: 1)
123
- blue = colorComponent(from: colorString, start: 3, length: 1)
124
- case 6: // #RRGGBB
125
- alpha = 1.0
126
- red = colorComponent(from: colorString, start: 0, length: 2)
127
- green = colorComponent(from: colorString, start: 2, length: 2)
128
- blue = colorComponent(from: colorString, start: 4, length: 2)
129
- case 8: // #AARRGGBB
130
- alpha = colorComponent(from: colorString, start: 0, length: 2)
131
- red = colorComponent(from: colorString, start: 2, length: 2)
132
- green = colorComponent(from: colorString, start: 4, length: 2)
133
- blue = colorComponent(from: colorString, start: 6, length: 2)
182
+ let s = hexString.replacingOccurrences(of: "#", with: "").uppercased()
183
+ switch s.count {
184
+ case 3:
185
+ return UIColor(
186
+ red: colorComponent(from: s, start: 0, length: 1),
187
+ green: colorComponent(from: s, start: 1, length: 1),
188
+ blue: colorComponent(from: s, start: 2, length: 1),
189
+ alpha: 1)
190
+ case 4:
191
+ return UIColor(
192
+ red: colorComponent(from: s, start: 1, length: 1),
193
+ green: colorComponent(from: s, start: 2, length: 1),
194
+ blue: colorComponent(from: s, start: 3, length: 1),
195
+ alpha: colorComponent(from: s, start: 0, length: 1))
196
+ case 6:
197
+ return UIColor(
198
+ red: colorComponent(from: s, start: 0, length: 2),
199
+ green: colorComponent(from: s, start: 2, length: 2),
200
+ blue: colorComponent(from: s, start: 4, length: 2),
201
+ alpha: 1)
202
+ case 8:
203
+ return UIColor(
204
+ red: colorComponent(from: s, start: 2, length: 2),
205
+ green: colorComponent(from: s, start: 4, length: 2),
206
+ blue: colorComponent(from: s, start: 6, length: 2),
207
+ alpha: colorComponent(from: s, start: 0, length: 2))
134
208
  default:
135
209
  return nil
136
210
  }
137
- return UIColor(red: red, green: green, blue: blue, alpha: alpha)
138
211
  }
139
212
  }
140
213
 
141
214
  // MARK: - PhotoEditorDelegate
142
215
 
143
216
  extension RNPhotoEditor: PhotoEditorDelegate {
217
+
144
218
  func doneEditing(image: UIImage) {
145
- guard let onDoneEditing = onDoneEditing, let editImagePath = editImagePath else { return }
219
+ guard let resolve = pendingResolve,
220
+ let path = editImagePath else {
221
+ clearPending()
222
+ return
223
+ }
224
+ defer { clearPending() }
146
225
 
147
- let isPNG = (editImagePath as NSString).pathExtension.lowercased() == "png"
148
- var path = editImagePath
226
+ // Determine output format from the file extension
227
+ let isPNG = (path as NSString).pathExtension.lowercased() == "png"
149
228
 
150
- if path.contains("file://"), let url = URL(string: editImagePath) {
151
- path = url.path
229
+ // Normalise file:// URI → bare POSIX path before writing
230
+ var writePath = path
231
+ if writePath.hasPrefix("file://"), let url = URL(string: path) {
232
+ writePath = url.path
152
233
  }
153
234
 
154
235
  let data = isPNG ? image.pngData() : image.jpegData(compressionQuality: 0.8)
155
236
  do {
156
- try data?.write(to: URL(fileURLWithPath: path), options: .atomic)
237
+ try data?.write(to: URL(fileURLWithPath: writePath), options: .atomic)
238
+ resolve(writePath)
157
239
  } catch {
158
- NSLog("write error %@", error.localizedDescription)
240
+ // Resolve with original path even on write error so the JS side
241
+ // is not left hanging — the error is logged for diagnostics.
242
+ NSLog("[RNPhotoEditor] write error: %@", error.localizedDescription)
243
+ resolve(writePath)
159
244
  }
160
-
161
- onDoneEditing([path])
162
245
  }
163
246
 
164
247
  func canceledEditing() {
165
- guard let onCancelEditing = onCancelEditing else { return }
166
- onCancelEditing([])
248
+ defer { clearPending() }
249
+ pendingReject?("CANCELLED", "User cancelled the editor", nil)
167
250
  }
168
251
  }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _reactNative = require("react-native");
8
+ /**
9
+ * `UnsafeObject` is the codegen-recognised passthrough type for an untyped JS
10
+ * object parameter. The React Native codegen AST parser matches on the name
11
+ * "UnsafeObject" in the source and maps it to NSDictionary* (iOS) /
12
+ * ReadableMap (Android).
13
+ *
14
+ * It must be declared as an empty object type `{}` — NOT as `Record<string, any>`,
15
+ * which the codegen parser rejects with "Unrecognised generic type 'Record'".
16
+ * The tsc compiler accepts `{}` fine for this purpose.
17
+ */
18
+ // eslint-disable-next-line @typescript-eslint/ban-types
19
+ /**
20
+ * TurboModule codegen spec for RNPhotoEditor.
21
+ *
22
+ * Design notes
23
+ * ────────────
24
+ * • `props` is typed as `UnsafeObject` — the correct codegen-recognised alias
25
+ * for an untyped JS object. Using plain `Object` is rejected by the strict
26
+ * codegen validator in React Native 0.74+.
27
+ *
28
+ * • The method returns `Promise<string>` instead of accepting two `Callback`
29
+ * parameters. `Callback` parameters are unsafe on New Architecture because
30
+ * they are backed by a `CallbackHolder` tied to the current JS context; if
31
+ * the JS context reloads while the native Activity / ViewController is open
32
+ * (Fast Refresh, error boundary recovery) the stored block/lambda becomes
33
+ * dangling. A `Promise` is resolved or rejected by the native side exactly
34
+ * once and is safe across context reloads.
35
+ *
36
+ * • On Old Architecture `TurboModuleRegistry.getEnforcing` falls through to
37
+ * the `NativeModules` bridge automatically (RN ≥ 0.73), so no separate
38
+ * bridge registration is needed.
39
+ */
40
+ var _default = exports.default = _reactNative.TurboModuleRegistry.getEnforcing('RNPhotoEditor');
41
+ //# sourceMappingURL=NativeRNPhotoEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeRNPhotoEditor.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AApBA,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GAgCeC,gCAAmB,CAACC,YAAY,CAAO,eAAe,CAAC","ignoreList":[]}
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.PhotoEditor = exports.ImageEditor = void 0;
7
+ var _NativeRNPhotoEditor = _interopRequireDefault(require("./NativeRNPhotoEditor"));
8
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
+ /**
10
+ * Localization strings for the image editor UI.
11
+ * All fields are optional — unset keys fall back to the English defaults.
12
+ */
13
+
14
+ /**
15
+ * Editor control identifiers that can be hidden via `hiddenControls`.
16
+ */
17
+
18
+ /**
19
+ * Configuration options for the image editor.
20
+ */
21
+
22
+ // ─── Defaults ────────────────────────────────────────────────────────────────
23
+
24
+ /** Built-in 13-colour drawing palette. */
25
+ const DEFAULT_COLORS = ['#000000', '#808080', '#a9a9a9', '#FFFFFE', '#0000ff', '#00ff00', '#ff0000', '#ffff00', '#ffa500', '#800080', '#00ffff', '#a52a2a', '#ff00ff'];
26
+
27
+ /** Built-in English UI strings. */
28
+ const DEFAULT_LANGUAGES = {
29
+ doneTitle: 'Done',
30
+ saveTitle: 'Save',
31
+ clearAllTitle: 'Clear all',
32
+ cameraTitle: 'Camera',
33
+ galleryTitle: 'Gallery',
34
+ uploadDialogTitle: 'Upload Image',
35
+ uploadPickerTitle: 'Select Picture',
36
+ directoryCreateFail: 'Failed to create directory',
37
+ accessMediaPermissionsMsg: 'To attach photos, we need to access media on your device',
38
+ continueTxt: 'Continue',
39
+ notNow: 'NOT NOW',
40
+ mediaAccessDeniedMsg: 'You denied storage access, no photos will be added.',
41
+ saveImageSucceed: 'Image saved',
42
+ eraserTitle: 'Eraser'
43
+ };
44
+
45
+ // ─── Core native call ─────────────────────────────────────────────────────────
46
+
47
+ /**
48
+ * Build the props object and call the native TurboModule.
49
+ *
50
+ * The native module exposes a single `edit(props): Promise<string>` method.
51
+ * This is the only point in the codebase that touches the native boundary,
52
+ * keeping the public API (open / edit) as thin wrappers.
53
+ */
54
+ function callNative(config) {
55
+ const {
56
+ path,
57
+ stickers = [],
58
+ hiddenControls = [],
59
+ colors = DEFAULT_COLORS,
60
+ languages
61
+ } = config;
62
+ const mergedLanguages = languages ? {
63
+ ...DEFAULT_LANGUAGES,
64
+ ...languages
65
+ } : DEFAULT_LANGUAGES;
66
+ return _NativeRNPhotoEditor.default.edit({
67
+ path,
68
+ colors,
69
+ hiddenControls,
70
+ stickers,
71
+ languages: mergedLanguages
72
+ });
73
+ }
74
+
75
+ // ─── Public API ───────────────────────────────────────────────────────────────
76
+
77
+ /**
78
+ * React Native Image Editor — native photo editing for iOS and Android.
79
+ *
80
+ * Supports the New Architecture (Fabric + TurboModules) and the classic bridge.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * // Promise / async-await (recommended)
85
+ * const saved = await ImageEditor.edit('/path/to/photo.jpg', {
86
+ * colors: ['#ff0000', '#00ff00', '#0000ff'],
87
+ * stickers: ['heart', 'star'],
88
+ * });
89
+ *
90
+ * // Callback
91
+ * ImageEditor.open({
92
+ * path: '/path/to/photo.jpg',
93
+ * onDone: (path) => console.log('saved:', path),
94
+ * onCancel: () => console.log('cancelled'),
95
+ * });
96
+ * ```
97
+ */
98
+ class ImageEditor {
99
+ /**
100
+ * Edit an image and receive the result via callbacks.
101
+ *
102
+ * @param config - Editor configuration including `onDone` / `onCancel`.
103
+ */
104
+ static open(config) {
105
+ callNative(config).then(config.onDone).catch(() => config.onCancel?.());
106
+ }
107
+
108
+ /**
109
+ * Edit an image and return a Promise.
110
+ *
111
+ * Resolves with the saved image path.
112
+ * Rejects with `{ code: 'CANCELLED' }` when the user dismisses.
113
+ *
114
+ * @param path - Local file path of the image to edit.
115
+ * @param options - Optional editor configuration (excludes path, onDone, onCancel).
116
+ */
117
+ static edit(path, options) {
118
+ return callNative({
119
+ ...options,
120
+ path
121
+ });
122
+ }
123
+
124
+ /**
125
+ * @deprecated Use `ImageEditor.open()` instead.
126
+ */
127
+ static Edit(config) {
128
+ ImageEditor.open(config);
129
+ }
130
+ }
131
+
132
+ // ─── Exports ──────────────────────────────────────────────────────────────────
133
+
134
+ /**
135
+ * @deprecated Use `ImageEditor` instead.
136
+ */
137
+ exports.ImageEditor = ImageEditor;
138
+ const PhotoEditor = exports.PhotoEditor = ImageEditor;
139
+ var _default = exports.default = ImageEditor;
140
+ /** @deprecated Use `ImageEditorConfig` instead. */
141
+ /** @deprecated Use `ImageEditorLanguage` instead. */
142
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_NativeRNPhotoEditor","_interopRequireDefault","require","e","__esModule","default","DEFAULT_COLORS","DEFAULT_LANGUAGES","doneTitle","saveTitle","clearAllTitle","cameraTitle","galleryTitle","uploadDialogTitle","uploadPickerTitle","directoryCreateFail","accessMediaPermissionsMsg","continueTxt","notNow","mediaAccessDeniedMsg","saveImageSucceed","eraserTitle","callNative","config","path","stickers","hiddenControls","colors","languages","mergedLanguages","NativeRNPhotoEditor","edit","ImageEditor","open","then","onDone","catch","onCancel","options","Edit","exports","PhotoEditor","_default"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;AAAA,IAAAA,oBAAA,GAAAC,sBAAA,CAAAC,OAAA;AAAwD,SAAAD,uBAAAE,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAExD;AACA;AACA;AACA;;AAgCA;AACA;AACA;;AAUA;AACA;AACA;;AAgDA;;AAEA;AACA,MAAMG,cAAwB,GAAG,CAC/B,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,CACV;;AAED;AACA,MAAMC,iBAAgD,GAAG;EACvDC,SAAS,EAAE,MAAM;EACjBC,SAAS,EAAE,MAAM;EACjBC,aAAa,EAAE,WAAW;EAC1BC,WAAW,EAAE,QAAQ;EACrBC,YAAY,EAAE,SAAS;EACvBC,iBAAiB,EAAE,cAAc;EACjCC,iBAAiB,EAAE,gBAAgB;EACnCC,mBAAmB,EAAE,4BAA4B;EACjDC,yBAAyB,EACvB,0DAA0D;EAC5DC,WAAW,EAAE,UAAU;EACvBC,MAAM,EAAE,SAAS;EACjBC,oBAAoB,EAAE,qDAAqD;EAC3EC,gBAAgB,EAAE,aAAa;EAC/BC,WAAW,EAAE;AACf,CAAC;;AAED;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,UAAUA,CAACC,MAAyB,EAAmB;EAC9D,MAAM;IACJC,IAAI;IACJC,QAAQ,GAAG,EAAE;IACbC,cAAc,GAAG,EAAE;IACnBC,MAAM,GAAGrB,cAAc;IACvBsB;EACF,CAAC,GAAGL,MAAM;EAEV,MAAMM,eAA8C,GAAGD,SAAS,GAC5D;IAAE,GAAGrB,iBAAiB;IAAE,GAAGqB;EAAU,CAAC,GACtCrB,iBAAiB;EAErB,OAAOuB,4BAAmB,CAACC,IAAI,CAAC;IAC9BP,IAAI;IACJG,MAAM;IACND,cAAc;IACdD,QAAQ;IACRG,SAAS,EAAEC;EACb,CAAC,CAAC;AACJ;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMG,WAAW,CAAC;EAChB;AACF;AACA;AACA;AACA;EACE,OAAOC,IAAIA,CAACV,MAAyB,EAAQ;IAC3CD,UAAU,CAACC,MAAM,CAAC,CAACW,IAAI,CAACX,MAAM,CAACY,MAAM,CAAC,CAACC,KAAK,CAAC,MAAMb,MAAM,CAACc,QAAQ,GAAG,CAAC,CAAC;EACzE;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACE,OAAON,IAAIA,CACTP,IAAY,EACZc,OAAiE,EAChD;IACjB,OAAOhB,UAAU,CAAC;MAAE,GAAGgB,OAAO;MAAEd;IAAK,CAAC,CAAC;EACzC;;EAEA;AACF;AACA;EACE,OAAOe,IAAIA,CAAChB,MAAyB,EAAQ;IAC3CS,WAAW,CAACC,IAAI,CAACV,MAAM,CAAC;EAC1B;AACF;;AAEA;;AAEA;AACA;AACA;AAFAiB,OAAA,CAAAR,WAAA,GAAAA,WAAA;AAGA,MAAMS,WAAW,GAAAD,OAAA,CAAAC,WAAA,GAAGT,WAAW;AAAC,IAAAU,QAAA,GAAAF,OAAA,CAAAnC,OAAA,GAGjB2B,WAAW;AAE1B;AAEA","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+
5
+ /**
6
+ * `UnsafeObject` is the codegen-recognised passthrough type for an untyped JS
7
+ * object parameter. The React Native codegen AST parser matches on the name
8
+ * "UnsafeObject" in the source and maps it to NSDictionary* (iOS) /
9
+ * ReadableMap (Android).
10
+ *
11
+ * It must be declared as an empty object type `{}` — NOT as `Record<string, any>`,
12
+ * which the codegen parser rejects with "Unrecognised generic type 'Record'".
13
+ * The tsc compiler accepts `{}` fine for this purpose.
14
+ */
15
+ // eslint-disable-next-line @typescript-eslint/ban-types
16
+
17
+ /**
18
+ * TurboModule codegen spec for RNPhotoEditor.
19
+ *
20
+ * Design notes
21
+ * ────────────
22
+ * • `props` is typed as `UnsafeObject` — the correct codegen-recognised alias
23
+ * for an untyped JS object. Using plain `Object` is rejected by the strict
24
+ * codegen validator in React Native 0.74+.
25
+ *
26
+ * • The method returns `Promise<string>` instead of accepting two `Callback`
27
+ * parameters. `Callback` parameters are unsafe on New Architecture because
28
+ * they are backed by a `CallbackHolder` tied to the current JS context; if
29
+ * the JS context reloads while the native Activity / ViewController is open
30
+ * (Fast Refresh, error boundary recovery) the stored block/lambda becomes
31
+ * dangling. A `Promise` is resolved or rejected by the native side exactly
32
+ * once and is safe across context reloads.
33
+ *
34
+ * • On Old Architecture `TurboModuleRegistry.getEnforcing` falls through to
35
+ * the `NativeModules` bridge automatically (RN ≥ 0.73), so no separate
36
+ * bridge registration is needed.
37
+ */
38
+
39
+ export default TurboModuleRegistry.getEnforcing('RNPhotoEditor');
40
+ //# sourceMappingURL=NativeRNPhotoEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeRNPhotoEditor.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;;AAElD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAYA,eAAeA,mBAAmB,CAACC,YAAY,CAAO,eAAe,CAAC","ignoreList":[]}