react-native-unified-action-sheet 0.1.1 → 0.3.0

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 (61) hide show
  1. package/CHANGELOG.md +66 -1
  2. package/README.md +38 -105
  3. package/android/build.gradle +28 -1
  4. package/android/src/main/java/com/unifiedactionsheet/ActionSheetOptions.kt +6 -2
  5. package/android/src/main/java/com/unifiedactionsheet/PromptOptions.kt +132 -0
  6. package/android/src/main/java/com/unifiedactionsheet/SheetContentBuilder.kt +31 -0
  7. package/android/src/main/java/com/unifiedactionsheet/SheetPresenters.kt +54 -0
  8. package/android/src/main/java/com/unifiedactionsheet/UnifiedActionSheetModule.kt +61 -0
  9. package/android/src/material/java/com/unifiedactionsheet/BottomSheetPresenterFactory.kt +10 -0
  10. package/android/src/material/java/com/unifiedactionsheet/MaterialBottomSheetPresenter.kt +90 -0
  11. package/android/src/material/res/values/unified_action_sheet_bottom_sheet.xml +22 -0
  12. package/android/src/noMaterial/java/com/unifiedactionsheet/BottomSheetPresenterFactory.kt +10 -0
  13. package/app.plugin.js +1 -0
  14. package/ios/BottomSheetViewController.swift +208 -0
  15. package/ios/UnifiedActionSheet.mm +73 -1
  16. package/ios/UnifiedActionSheetImpl.swift +248 -11
  17. package/jest/index.d.ts +15 -1
  18. package/jest/index.js +28 -0
  19. package/lib/commonjs/NativeUnifiedActionSheet.js.map +1 -1
  20. package/lib/commonjs/action-sheet-options.interface.js +4 -0
  21. package/lib/commonjs/common-options.interface.js +2 -0
  22. package/lib/commonjs/common-options.interface.js.map +1 -0
  23. package/lib/commonjs/index.js +57 -5
  24. package/lib/commonjs/index.js.map +1 -1
  25. package/lib/commonjs/prompt-options.interface.js +6 -0
  26. package/lib/commonjs/prompt-options.interface.js.map +1 -0
  27. package/lib/module/NativeUnifiedActionSheet.js.map +1 -1
  28. package/lib/module/action-sheet-options.interface.js +2 -0
  29. package/lib/module/common-options.interface.js +2 -0
  30. package/lib/module/common-options.interface.js.map +1 -0
  31. package/lib/module/index.js +55 -4
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/module/prompt-options.interface.js +4 -0
  34. package/lib/module/prompt-options.interface.js.map +1 -0
  35. package/lib/typescript/commonjs/src/NativeUnifiedActionSheet.d.ts +23 -0
  36. package/lib/typescript/commonjs/src/NativeUnifiedActionSheet.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/src/action-sheet-options.interface.d.ts +5 -14
  38. package/lib/typescript/commonjs/src/action-sheet-options.interface.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/src/common-options.interface.d.ts +17 -0
  40. package/lib/typescript/commonjs/src/common-options.interface.d.ts.map +1 -0
  41. package/lib/typescript/commonjs/src/index.d.ts +4 -0
  42. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/src/prompt-options.interface.d.ts +19 -0
  44. package/lib/typescript/commonjs/src/prompt-options.interface.d.ts.map +1 -0
  45. package/lib/typescript/module/src/NativeUnifiedActionSheet.d.ts +23 -0
  46. package/lib/typescript/module/src/NativeUnifiedActionSheet.d.ts.map +1 -1
  47. package/lib/typescript/module/src/action-sheet-options.interface.d.ts +5 -14
  48. package/lib/typescript/module/src/action-sheet-options.interface.d.ts.map +1 -1
  49. package/lib/typescript/module/src/common-options.interface.d.ts +17 -0
  50. package/lib/typescript/module/src/common-options.interface.d.ts.map +1 -0
  51. package/lib/typescript/module/src/index.d.ts +4 -0
  52. package/lib/typescript/module/src/index.d.ts.map +1 -1
  53. package/lib/typescript/module/src/prompt-options.interface.d.ts +19 -0
  54. package/lib/typescript/module/src/prompt-options.interface.d.ts.map +1 -0
  55. package/package.json +12 -2
  56. package/plugin/withUnifiedActionSheet.js +58 -0
  57. package/src/NativeUnifiedActionSheet.ts +22 -0
  58. package/src/action-sheet-options.interface.ts +10 -20
  59. package/src/common-options.interface.ts +18 -0
  60. package/src/index.tsx +100 -6
  61. package/src/prompt-options.interface.ts +27 -0
@@ -2,7 +2,9 @@ import Foundation
2
2
  import UIKit
3
3
 
4
4
  @objc(UnifiedActionSheetImpl)
5
- public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDelegate {
5
+ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDelegate,
6
+ UISheetPresentationControllerDelegate
7
+ {
6
8
  @objc public static let shared = UnifiedActionSheetImpl()
7
9
 
8
10
  private static let dismissedByApi = -2
@@ -31,6 +33,28 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
31
33
  return
32
34
  }
33
35
 
36
+ // 'bottom' is a real sheet on iPhone. iPad keeps the action sheet below,
37
+ // which presents as a popover there: a sheet on iPad is a centered form
38
+ // sheet, not attached to the bottom at all.
39
+ if options["presentationStyle"] as? String == "bottom",
40
+ UIDevice.current.userInterfaceIdiom != .pad
41
+ {
42
+ presentBottomSheet(
43
+ on: parent,
44
+ options: options,
45
+ labels: labels,
46
+ cancelButtonIndex: cancelButtonIndex,
47
+ destructiveIndices: destructiveIndices,
48
+ disabledIndices: disabledIndices,
49
+ tintColor: tintColor,
50
+ cancelButtonTintColor: cancelButtonTintColor,
51
+ destructiveColor: destructiveColor,
52
+ completion: completion
53
+ )
54
+
55
+ return
56
+ }
57
+
34
58
  // 'centered' maps to UIKit's centered alert style; anything else (including
35
59
  // absent, and 'anchored') stays .actionSheet, the iOS default — presented
36
60
  // from the bottom on iPhone and as a popover on iPad.
@@ -45,7 +69,7 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
45
69
  let presentation = Presentation(
46
70
  controller: alert,
47
71
  cancelButtonIndex: cancelButtonIndex,
48
- completion: completion
72
+ completion: { index, _ in completion(index) }
49
73
  )
50
74
 
51
75
  for (index, label) in labels.enumerated() {
@@ -111,6 +135,183 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
111
135
  parent.present(alert, animated: true)
112
136
  }
113
137
 
138
+ /// React Native's Alert.prompt is iOS-only; this is the shared half of the
139
+ /// unified prompt. Always .alert -- UIKit has no text field in an action
140
+ /// sheet, and a prompt is inherently a centered, modal question.
141
+ @objc public func showPrompt(
142
+ options: NSDictionary,
143
+ completion: @escaping (Int, String) -> Void
144
+ ) {
145
+ let labels = options["options"] as? [String] ?? []
146
+ let cancelButtonIndex = (options["cancelButtonIndex"] as? NSNumber)?.intValue ?? -1
147
+ let destructiveIndices = Set(
148
+ (options["destructiveButtonIndices"] as? [NSNumber])?.map { $0.intValue } ?? []
149
+ )
150
+ let disabledIndices = Set(
151
+ (options["disabledButtonIndices"] as? [NSNumber])?.map { $0.intValue } ?? []
152
+ )
153
+ let tintColor = Self.color(options["tintColor"])
154
+ let cancelButtonTintColor = Self.color(options["cancelButtonTintColor"])
155
+ let destructiveColor = Self.color(options["destructiveColor"])
156
+
157
+ guard let parent = Self.presentedViewController() else {
158
+ completion(cancelButtonIndex, "")
159
+
160
+ return
161
+ }
162
+
163
+ let alert = UIAlertController(
164
+ title: Self.text(options["title"]),
165
+ message: Self.text(options["message"]),
166
+ preferredStyle: .alert
167
+ )
168
+
169
+ alert.addTextField { field in
170
+ field.placeholder = Self.text(options["placeholder"])
171
+ field.text = Self.text(options["defaultValue"])
172
+ field.isSecureTextEntry = (options["secureTextEntry"] as? NSNumber)?.boolValue ?? false
173
+ field.keyboardType = Self.keyboardType(options["keyboardType"])
174
+ }
175
+
176
+ let presentation = Presentation(
177
+ controller: alert,
178
+ cancelButtonIndex: cancelButtonIndex,
179
+ // Weak, or the presentation would retain the controller it is stored on.
180
+ currentText: { [weak alert] in alert?.textFields?.first?.text ?? "" },
181
+ completion: completion
182
+ )
183
+
184
+ for (index, label) in labels.enumerated() {
185
+ let isCancel = index == cancelButtonIndex
186
+ let style: UIAlertAction.Style =
187
+ destructiveIndices.contains(index) ? .destructive : (isCancel ? .cancel : .default)
188
+
189
+ let action = UIAlertAction(title: label, style: style) { [weak self] _ in
190
+ self?.finish(presentation, index: index)
191
+ }
192
+
193
+ let color: UIColor? =
194
+ style == .destructive
195
+ ? destructiveColor
196
+ : (isCancel ? (cancelButtonTintColor ?? tintColor) : tintColor)
197
+
198
+ action.isEnabled = !disabledIndices.contains(index)
199
+
200
+ if let color {
201
+ action.setValue(color, forKey: "titleTextColor")
202
+ }
203
+ alert.addAction(action)
204
+ }
205
+
206
+ alert.view.tintColor = tintColor
207
+
208
+ switch options["userInterfaceStyle"] as? String {
209
+ case "dark": alert.overrideUserInterfaceStyle = .dark
210
+ case "light": alert.overrideUserInterfaceStyle = .light
211
+ default: alert.overrideUserInterfaceStyle = .unspecified
212
+ }
213
+
214
+ presentations.append(presentation)
215
+ parent.present(alert, animated: true)
216
+ }
217
+
218
+ private func presentBottomSheet(
219
+ on parent: UIViewController,
220
+ options: NSDictionary,
221
+ labels: [String],
222
+ cancelButtonIndex: Int,
223
+ destructiveIndices: Set<Int>,
224
+ disabledIndices: Set<Int>,
225
+ tintColor: UIColor?,
226
+ cancelButtonTintColor: UIColor?,
227
+ destructiveColor: UIColor?,
228
+ completion: @escaping (Int) -> Void
229
+ ) {
230
+ let rows = labels.enumerated().map { index, label in
231
+ BottomSheetViewController.Row(
232
+ index: index,
233
+ label: label,
234
+ isDestructive: destructiveIndices.contains(index),
235
+ isEnabled: !disabledIndices.contains(index)
236
+ )
237
+ }
238
+
239
+ let sheet = BottomSheetViewController(
240
+ title: Self.text(options["title"]),
241
+ message: Self.text(options["message"]),
242
+ rows: rows.filter { $0.index != cancelButtonIndex },
243
+ cancelRow: rows.first { $0.index == cancelButtonIndex },
244
+ tintColor: tintColor,
245
+ cancelButtonTintColor: cancelButtonTintColor,
246
+ destructiveColor: destructiveColor
247
+ )
248
+
249
+ let presentation = Presentation(
250
+ controller: sheet,
251
+ cancelButtonIndex: cancelButtonIndex,
252
+ completion: { index, _ in completion(index) }
253
+ )
254
+
255
+ // Weak: the presentation holds the sheet, which holds this closure.
256
+ sheet.onSelect = { [weak self, weak presentation] index in
257
+ guard let presentation else { return }
258
+
259
+ // Unlike an alert action, a row does not dismiss the sheet itself.
260
+ // Resolve once it is gone, as an alert does.
261
+ presentation.controller.dismiss(animated: true) {
262
+ self?.finish(presentation, index: index)
263
+ }
264
+ }
265
+
266
+ switch options["userInterfaceStyle"] as? String {
267
+ case "dark": sheet.overrideUserInterfaceStyle = .dark
268
+ case "light": sheet.overrideUserInterfaceStyle = .light
269
+ default: sheet.overrideUserInterfaceStyle = .unspecified
270
+ }
271
+ sheet.view.tintColor = tintColor
272
+
273
+ if let controller = sheet.sheetPresentationController {
274
+ controller.prefersGrabberVisible = true
275
+ controller.prefersEdgeAttachedInCompactHeight = true
276
+ // Swipe down resolves through presentationControllerDidDismiss.
277
+ controller.delegate = self
278
+
279
+ let bounds = parent.view.window?.bounds ?? parent.view.bounds
280
+ let fitting = sheet.fittingHeight(width: bounds.width)
281
+
282
+ // A short list opens at its own height; a long one opens at half height
283
+ // and drags up to expand, like the Android sheet. Fitting the content
284
+ // needs a custom detent, iOS 16+; iOS 15 opens at half height.
285
+ if #available(iOS 16.0, *), fitting <= bounds.height / 2 {
286
+ controller.detents = [
287
+ .custom(identifier: .init("unifiedActionSheet.content")) { context in
288
+ min(fitting, context.maximumDetentValue)
289
+ },
290
+ ]
291
+ } else {
292
+ controller.detents = [.medium(), .large()]
293
+ }
294
+ }
295
+
296
+ presentations.append(presentation)
297
+ parent.present(sheet, animated: true)
298
+ }
299
+
300
+ /// Interactive dismissal of a sheet: a swipe down or a tap on the dimmed
301
+ /// area. It resolves the cancel index, or -1 without one, like Android. A
302
+ /// programmatic dismiss() does not call this.
303
+ public func presentationControllerDidDismiss(
304
+ _ presentationController: UIPresentationController
305
+ ) {
306
+ guard
307
+ let presentation = presentations.first(where: {
308
+ $0.controller === presentationController.presentedViewController
309
+ })
310
+ else { return }
311
+
312
+ finish(presentation, index: presentation.cancelButtonIndex)
313
+ }
314
+
114
315
  @objc public func dismissAll() {
115
316
  guard let bottom = presentations.first else { return }
116
317
 
@@ -131,6 +332,21 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
131
332
  }
132
333
  }
133
334
 
335
+ /// The JS runtime is going away: close every sheet, prompt and alert, but
336
+ /// resolve none of them. Their promises belong to the runtime being torn
337
+ /// down, and the list is shared, so stale entries must not outlive it.
338
+ @objc public func invalidate() {
339
+ let all = presentations
340
+ presentations.removeAll()
341
+ all.forEach { $0.discard() }
342
+
343
+ // As in dismissAll(): the lowest sheet's presenter dismisses it and
344
+ // everything stacked above it.
345
+ all.first { $0.controller.presentingViewController != nil }?
346
+ .controller.presentingViewController?
347
+ .dismiss(animated: false)
348
+ }
349
+
134
350
  @objc public func dismiss() {
135
351
  guard let presentation = presentations.last else { return }
136
352
 
@@ -144,10 +360,8 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
144
360
  public func popoverPresentationControllerDidDismissPopover(
145
361
  _ popoverPresentationController: UIPopoverPresentationController
146
362
  ) {
147
- guard
148
- let alert = popoverPresentationController.presentedViewController
149
- as? UIAlertController,
150
- let presentation = presentations.first(where: { $0.controller === alert })
363
+ let alert = popoverPresentationController.presentedViewController
364
+ guard let presentation = presentations.first(where: { $0.controller === alert })
151
365
  else { return }
152
366
 
153
367
  finish(presentation, index: presentation.cancelButtonIndex)
@@ -161,25 +375,36 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
161
375
  }
162
376
 
163
377
  private final class Presentation {
164
- let controller: UIAlertController
378
+ /// A UIAlertController, or the bottom sheet on iPhone.
379
+ let controller: UIViewController
165
380
  let cancelButtonIndex: Int
166
- private var completion: ((Int) -> Void)?
381
+ /// Read at resolve time, not at creation: the value that matters is
382
+ /// whatever is in the field when the prompt closes. Sheets pass a constant.
383
+ private let currentText: () -> String
384
+ private var completion: ((Int, String) -> Void)?
167
385
 
168
386
  init(
169
- controller: UIAlertController,
387
+ controller: UIViewController,
170
388
  cancelButtonIndex: Int,
171
- completion: @escaping (Int) -> Void
389
+ currentText: @escaping () -> String = { "" },
390
+ completion: @escaping (Int, String) -> Void
172
391
  ) {
173
392
  self.controller = controller
174
393
  self.cancelButtonIndex = cancelButtonIndex
394
+ self.currentText = currentText
175
395
  self.completion = completion
176
396
  }
177
397
 
398
+ /// Drops the completion without calling it.
399
+ func discard() {
400
+ completion = nil
401
+ }
402
+
178
403
  func resolve(_ index: Int) {
179
404
  guard let completion else { return }
180
405
 
181
406
  self.completion = nil
182
- completion(index)
407
+ completion(index, currentText())
183
408
  }
184
409
  }
185
410
 
@@ -208,6 +433,18 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
208
433
  return CGRect(x: x, y: y, width: width, height: height)
209
434
  }
210
435
 
436
+ /// Mirrors the subset of React Native's keyboardType values that map cleanly
437
+ /// onto both platforms; anything else falls back to the default keyboard.
438
+ private static func keyboardType(_ value: Any?) -> UIKeyboardType {
439
+ switch value as? String {
440
+ case "email-address": return .emailAddress
441
+ case "numeric": return .numberPad
442
+ case "phone-pad": return .phonePad
443
+ case "url": return .URL
444
+ default: return .default
445
+ }
446
+ }
447
+
211
448
  private static func text(_ value: Any?) -> String? {
212
449
  guard let string = value as? String, !string.isEmpty else { return nil }
213
450
 
package/jest/index.d.ts CHANGED
@@ -1,4 +1,8 @@
1
- import type { ActionSheetOptionsInterface } from 'react-native-unified-action-sheet';
1
+ import type {
2
+ ActionSheetOptionsInterface,
3
+ PromptOptionsInterface,
4
+ PromptResultInterface,
5
+ } from 'react-native-unified-action-sheet';
2
6
 
3
7
  /// Queues the index the next sheet resolves with. Pass nothing to go back to
4
8
  /// resolving with no selection.
@@ -8,5 +12,15 @@ export declare const showActionSheetWithOptions: jest.Mock<
8
12
  Promise<number | undefined>,
9
13
  [ActionSheetOptionsInterface]
10
14
  >;
15
+ /// Queues what the next prompt resolves with. Pass nothing to go back to
16
+ /// resolving with no selection.
17
+ export declare function setNextPromptResult(
18
+ result?: PromptResultInterface
19
+ ): void;
20
+
21
+ export declare const showPromptWithOptions: jest.Mock<
22
+ Promise<PromptResultInterface | undefined>,
23
+ [PromptOptionsInterface]
24
+ >;
11
25
  export declare const dismissActionSheet: jest.Mock<void, []>;
12
26
  export declare const dismissAllActionSheets: jest.Mock<void, []>;
package/jest/index.js CHANGED
@@ -16,6 +16,11 @@
16
16
  *
17
17
  * Prefer that over mockResolvedValueOnce, which replaces the implementation and
18
18
  * so skips onPress.
19
+ *
20
+ * A prompt is queued the same way, with the text the field should resolve with:
21
+ *
22
+ * setNextPromptResult({ buttonIndex: 0, text: 'typed' });
23
+ * await openThePrompt();
19
24
  */
20
25
  let nextButtonIndex;
21
26
 
@@ -35,12 +40,35 @@ const showActionSheetWithOptions = jest.fn((options) => {
35
40
  return Promise.resolve(buttonIndex);
36
41
  });
37
42
 
43
+ let nextPromptResult;
44
+
45
+ const setNextPromptResult = (result) => {
46
+ nextPromptResult = result;
47
+ };
48
+
49
+ const showPromptWithOptions = jest.fn((options) => {
50
+ const result = nextPromptResult;
51
+ nextPromptResult = undefined;
52
+
53
+ if (result) {
54
+ const button =
55
+ options && options.options && options.options[result.buttonIndex];
56
+ if (button && typeof button.onPress === 'function') {
57
+ button.onPress(result.text);
58
+ }
59
+ }
60
+
61
+ return Promise.resolve(result);
62
+ });
63
+
38
64
  const dismissActionSheet = jest.fn();
39
65
  const dismissAllActionSheets = jest.fn();
40
66
 
41
67
  module.exports = {
42
68
  setNextButtonIndex,
69
+ setNextPromptResult,
43
70
  showActionSheetWithOptions,
71
+ showPromptWithOptions,
44
72
  dismissActionSheet,
45
73
  dismissAllActionSheets,
46
74
  };
@@ -1 +1 @@
1
- {"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeUnifiedActionSheet.ts"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAAqE,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GA4BtDC,gCAAmB,CAACC,YAAY,CAAO,oBAAoB,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeUnifiedActionSheet.ts"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAAqE,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GAkDtDC,gCAAmB,CAACC,YAAY,CAAO,oBAAoB,CAAC","ignoreList":[]}
@@ -1,2 +1,6 @@
1
1
  "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
2
6
  //# sourceMappingURL=action-sheet-options.interface.js.map
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=common-options.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["common-options.interface.ts"],"mappings":"","ignoreList":[]}
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.showActionSheetWithOptions = exports.dismissAllActionSheets = exports.dismissActionSheet = void 0;
6
+ exports.showPromptWithOptions = exports.showActionSheetWithOptions = exports.dismissAllActionSheets = exports.dismissActionSheet = void 0;
7
7
  var _reactNative = require("react-native");
8
8
  const DISMISSED_BY_API = -2;
9
9
 
@@ -20,12 +20,18 @@ const toWireOptions = ({
20
20
  // crosses the bridge. Omit<> would not strip it at runtime.
21
21
  anchor: _anchor,
22
22
  ...rest
23
- }) => {
23
+ }) => ({
24
+ ...rest,
25
+ ...toWireButtons(options)
26
+ });
27
+ /// Same flattening as the sheet: labels plus index sets. Kept generic over the
28
+ /// button shape so the two APIs cannot disagree about what 'cancel' means.
29
+ const toWireButtons = buttons => {
24
30
  const labels = [];
25
31
  const destructive = [];
26
32
  const disabled = [];
27
33
  let cancelButtonIndex;
28
- options.forEach((button, index) => {
34
+ buttons.forEach((button, index) => {
29
35
  labels.push(button.label);
30
36
  if (button.style === 'destructive') destructive.push(index);
31
37
  if (button.style === 'cancel' && cancelButtonIndex == null) {
@@ -34,7 +40,6 @@ const toWireOptions = ({
34
40
  if (button.disabled) disabled.push(index);
35
41
  });
36
42
  return {
37
- ...rest,
38
43
  options: labels,
39
44
  ...(cancelButtonIndex == null ? {} : {
40
45
  cancelButtonIndex
@@ -50,6 +55,18 @@ const toWireOptions = ({
50
55
  const showWithNativeModule = options => nativeModule().showActionSheetWithOptions(options)
51
56
  // A programmatic dismiss resolves with no index rather than a selection.
52
57
  .then(buttonIndex => buttonIndex === DISMISSED_BY_API ? undefined : buttonIndex).catch(() => options.cancelButtonIndex ?? -1);
58
+ let warnedMaterialDisabled = false;
59
+
60
+ /// 'bottom' needs Material on Android, which the app opts into at build time.
61
+ /// Without it the sheet falls back to a centered dialog; say so once, in
62
+ /// development, since the fallback alone looks like the option was ignored.
63
+ const warnIfBottomUnavailable = presentationStyle => {
64
+ if (!__DEV__ || warnedMaterialDisabled || _reactNative.Platform.OS !== 'android' || presentationStyle !== 'bottom' || nativeModule().getConstants().isMaterialEnabled) {
65
+ return;
66
+ }
67
+ warnedMaterialDisabled = true;
68
+ console.warn("react-native-unified-action-sheet: presentationStyle 'bottom' needs Material on Android, " + 'so this sheet falls back to a centered dialog. Set unifiedActionSheet.material=true in ' + "android/gradle.properties, or add ['react-native-unified-action-sheet', { material: true }] " + 'to your Expo plugins, then rebuild the app.');
69
+ };
53
70
 
54
71
  /// The anchor is measured in JS and sent across as a rect, so neither native
55
72
  /// module has to resolve a view: a ref's own measureInWindow is the supported
@@ -83,7 +100,12 @@ const showActionSheetWithOptions = options => {
83
100
  if (_reactNative.Platform.OS !== 'ios' && _reactNative.Platform.OS !== 'android') {
84
101
  return Promise.resolve(undefined);
85
102
  }
86
- return withAnchorRect(wire, options.anchor).then(showWithNativeModule).then(press);
103
+ warnIfBottomUnavailable(options.presentationStyle);
104
+
105
+ // A bottom sheet has no anchor, so don't measure one: on iPad a rect would
106
+ // turn the sheet into an arrowed popover pointing at it.
107
+ const anchor = options.presentationStyle === 'bottom' ? undefined : options.anchor;
108
+ return withAnchorRect(wire, anchor).then(showWithNativeModule).then(press);
87
109
  };
88
110
  exports.showActionSheetWithOptions = showActionSheetWithOptions;
89
111
  const dismissActionSheet = () => {
@@ -100,5 +122,35 @@ const dismissAllActionSheets = () => {
100
122
  nativeModule().dismissAllActionSheets();
101
123
  }
102
124
  };
125
+
126
+ /// A prompt is the sheet's centered dialog with a text field: iOS presents a
127
+ /// UIAlertController alert with addTextField, Android the same AppCompatDialog
128
+ /// the centered style uses. React Native's own Alert.prompt is iOS-only and a
129
+ /// silent no-op on Android, which is the gap this fills.
103
130
  exports.dismissAllActionSheets = dismissAllActionSheets;
131
+ const showPromptWithOptions = options => {
132
+ const {
133
+ options: buttons,
134
+ ...rest
135
+ } = options;
136
+ const wire = {
137
+ ...rest,
138
+ ...toWireButtons(buttons)
139
+ };
140
+ if (_reactNative.Platform.OS !== 'ios' && _reactNative.Platform.OS !== 'android') {
141
+ return Promise.resolve(undefined);
142
+ }
143
+ return nativeModule().showPromptWithOptions(wire).then(result =>
144
+ // A programmatic dismiss resolves with no selection, matching the sheet.
145
+ result.buttonIndex === DISMISSED_BY_API ? undefined : result).catch(() => ({
146
+ buttonIndex: wire.cancelButtonIndex ?? -1,
147
+ text: ''
148
+ })).then(result => {
149
+ // The text goes to the handler, so a caller using onPress alone never
150
+ // has to read the resolved value.
151
+ if (result) buttons[result.buttonIndex]?.onPress?.(result.text);
152
+ return result;
153
+ });
154
+ };
155
+ exports.showPromptWithOptions = showPromptWithOptions;
104
156
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_reactNative","require","DISMISSED_BY_API","nativeModule","default","toWireOptions","options","anchor","_anchor","rest","labels","destructive","disabled","cancelButtonIndex","forEach","button","index","push","label","style","length","destructiveButtonIndices","disabledButtonIndices","showWithNativeModule","showActionSheetWithOptions","then","buttonIndex","undefined","catch","withAnchorRect","wire","target","current","Promise","resolve","measureInWindow","x","y","width","height","measured","anchorRect","press","onPress","Platform","OS","exports","dismissActionSheet","dismissAllActionSheets"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_reactNative","require","DISMISSED_BY_API","nativeModule","default","toWireOptions","options","anchor","_anchor","rest","toWireButtons","buttons","labels","destructive","disabled","cancelButtonIndex","forEach","button","index","push","label","style","length","destructiveButtonIndices","disabledButtonIndices","showWithNativeModule","showActionSheetWithOptions","then","buttonIndex","undefined","catch","warnedMaterialDisabled","warnIfBottomUnavailable","presentationStyle","__DEV__","Platform","OS","getConstants","isMaterialEnabled","console","warn","withAnchorRect","wire","target","current","Promise","resolve","measureInWindow","x","y","width","height","measured","anchorRect","press","onPress","exports","dismissActionSheet","dismissAllActionSheets","showPromptWithOptions","result","text"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ //# sourceMappingURL=prompt-options.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["prompt-options.interface.ts"],"mappings":"","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeUnifiedActionSheet.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AA4BpE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,oBAAoB,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeUnifiedActionSheet.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AAkDpE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,oBAAoB,CAAC","ignoreList":[]}
@@ -1,2 +1,4 @@
1
1
  "use strict";
2
+
3
+ export {};
2
4
  //# sourceMappingURL=action-sheet-options.interface.js.map
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=common-options.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["common-options.interface.ts"],"mappings":"","ignoreList":[]}