react-native-unified-action-sheet 0.2.0 → 0.4.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.
- package/CHANGELOG.md +87 -1
- package/README.md +44 -112
- package/android/build.gradle +28 -1
- package/android/src/main/java/com/unifiedactionsheet/ActionSheetOptions.kt +41 -2
- package/android/src/main/java/com/unifiedactionsheet/PromptOptions.kt +27 -5
- package/android/src/main/java/com/unifiedactionsheet/SheetContentBuilder.kt +75 -9
- package/android/src/main/java/com/unifiedactionsheet/SheetPresenters.kt +61 -10
- package/android/src/main/java/com/unifiedactionsheet/UnifiedActionSheetModule.kt +14 -7
- package/android/src/material/java/com/unifiedactionsheet/BottomSheetPresenterFactory.kt +10 -0
- package/android/src/material/java/com/unifiedactionsheet/MaterialBottomSheetPresenter.kt +153 -0
- package/android/src/material/res/values/unified_action_sheet_bottom_sheet.xml +22 -0
- package/android/src/noMaterial/java/com/unifiedactionsheet/BottomSheetPresenterFactory.kt +10 -0
- package/app.plugin.js +1 -0
- package/ios/BottomSheetViewController.swift +210 -0
- package/ios/UnifiedActionSheet.mm +51 -5
- package/ios/UnifiedActionSheetImpl.swift +259 -22
- package/jest/index.d.ts +12 -9
- package/jest/index.js +37 -18
- package/lib/commonjs/NativeUnifiedActionSheet.js.map +1 -1
- package/lib/commonjs/index.js +102 -18
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeUnifiedActionSheet.js.map +1 -1
- package/lib/module/index.js +102 -18
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/NativeUnifiedActionSheet.d.ts +10 -1
- package/lib/typescript/commonjs/src/NativeUnifiedActionSheet.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/action-sheet-options.interface.d.ts +5 -2
- package/lib/typescript/commonjs/src/action-sheet-options.interface.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/common-options.interface.d.ts +9 -0
- package/lib/typescript/commonjs/src/common-options.interface.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +6 -6
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/prompt-options.interface.d.ts +10 -6
- package/lib/typescript/commonjs/src/prompt-options.interface.d.ts.map +1 -1
- package/lib/typescript/module/src/NativeUnifiedActionSheet.d.ts +10 -1
- package/lib/typescript/module/src/NativeUnifiedActionSheet.d.ts.map +1 -1
- package/lib/typescript/module/src/action-sheet-options.interface.d.ts +5 -2
- package/lib/typescript/module/src/action-sheet-options.interface.d.ts.map +1 -1
- package/lib/typescript/module/src/common-options.interface.d.ts +9 -0
- package/lib/typescript/module/src/common-options.interface.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +6 -6
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/prompt-options.interface.d.ts +10 -6
- package/lib/typescript/module/src/prompt-options.interface.d.ts.map +1 -1
- package/package.json +12 -2
- package/plugin/withUnifiedActionSheet.js +58 -0
- package/src/NativeUnifiedActionSheet.ts +12 -2
- package/src/action-sheet-options.interface.ts +12 -1
- package/src/common-options.interface.ts +18 -0
- package/src/index.tsx +147 -42
- package/src/prompt-options.interface.ts +21 -5
|
@@ -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
|
|
@@ -24,6 +26,7 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
24
26
|
let tintColor = Self.color(options["tintColor"])
|
|
25
27
|
let cancelButtonTintColor = Self.color(options["cancelButtonTintColor"])
|
|
26
28
|
let destructiveColor = Self.color(options["destructiveColor"])
|
|
29
|
+
let preferredIndex = (options["preferredButtonIndex"] as? NSNumber)?.intValue
|
|
27
30
|
|
|
28
31
|
guard let parent = Self.presentedViewController() else {
|
|
29
32
|
completion(cancelButtonIndex)
|
|
@@ -31,6 +34,29 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
31
34
|
return
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
// 'bottom' is a real sheet on iPhone. iPad keeps the action sheet below,
|
|
38
|
+
// which presents as a popover there: a sheet on iPad is a centered form
|
|
39
|
+
// sheet, not attached to the bottom at all.
|
|
40
|
+
if options["presentationStyle"] as? String == "bottom",
|
|
41
|
+
UIDevice.current.userInterfaceIdiom != .pad
|
|
42
|
+
{
|
|
43
|
+
presentBottomSheet(
|
|
44
|
+
on: parent,
|
|
45
|
+
options: options,
|
|
46
|
+
labels: labels,
|
|
47
|
+
cancelButtonIndex: cancelButtonIndex,
|
|
48
|
+
destructiveIndices: destructiveIndices,
|
|
49
|
+
disabledIndices: disabledIndices,
|
|
50
|
+
preferredIndex: preferredIndex,
|
|
51
|
+
tintColor: tintColor,
|
|
52
|
+
cancelButtonTintColor: cancelButtonTintColor,
|
|
53
|
+
destructiveColor: destructiveColor,
|
|
54
|
+
completion: completion
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
34
60
|
// 'centered' maps to UIKit's centered alert style; anything else (including
|
|
35
61
|
// absent, and 'anchored') stays .actionSheet, the iOS default — presented
|
|
36
62
|
// from the bottom on iPhone and as a popover on iPad.
|
|
@@ -45,7 +71,7 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
45
71
|
let presentation = Presentation(
|
|
46
72
|
controller: alert,
|
|
47
73
|
cancelButtonIndex: cancelButtonIndex,
|
|
48
|
-
completion: { index, _ in completion(index) }
|
|
74
|
+
completion: { index, _, _ in completion(index) }
|
|
49
75
|
)
|
|
50
76
|
|
|
51
77
|
for (index, label) in labels.enumerated() {
|
|
@@ -53,7 +79,10 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
53
79
|
let style: UIAlertAction.Style =
|
|
54
80
|
destructiveIndices.contains(index) ? .destructive : (isCancel ? .cancel : .default)
|
|
55
81
|
|
|
56
|
-
|
|
82
|
+
// Weak: the presentation holds the alert, which holds this handler.
|
|
83
|
+
let action = UIAlertAction(title: label, style: style) { [weak self, weak presentation] _ in
|
|
84
|
+
guard let presentation else { return }
|
|
85
|
+
|
|
57
86
|
self?.finish(presentation, index: index)
|
|
58
87
|
}
|
|
59
88
|
|
|
@@ -74,6 +103,12 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
74
103
|
alert.addAction(action)
|
|
75
104
|
}
|
|
76
105
|
|
|
106
|
+
// UIKit only honors a preferred action in the alert style; the action
|
|
107
|
+
// sheet already bolds its cancel button.
|
|
108
|
+
if isCentered, let preferredIndex, alert.actions.indices.contains(preferredIndex) {
|
|
109
|
+
alert.preferredAction = alert.actions[preferredIndex]
|
|
110
|
+
}
|
|
111
|
+
|
|
77
112
|
alert.view.tintColor = tintColor
|
|
78
113
|
|
|
79
114
|
switch options["userInterfaceStyle"] as? String {
|
|
@@ -116,7 +151,7 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
116
151
|
/// sheet, and a prompt is inherently a centered, modal question.
|
|
117
152
|
@objc public func showPrompt(
|
|
118
153
|
options: NSDictionary,
|
|
119
|
-
completion: @escaping (Int, String) -> Void
|
|
154
|
+
completion: @escaping (Int, String, String) -> Void
|
|
120
155
|
) {
|
|
121
156
|
let labels = options["options"] as? [String] ?? []
|
|
122
157
|
let cancelButtonIndex = (options["cancelButtonIndex"] as? NSNumber)?.intValue ?? -1
|
|
@@ -129,9 +164,14 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
129
164
|
let tintColor = Self.color(options["tintColor"])
|
|
130
165
|
let cancelButtonTintColor = Self.color(options["cancelButtonTintColor"])
|
|
131
166
|
let destructiveColor = Self.color(options["destructiveColor"])
|
|
167
|
+
let preferredIndex = (options["preferredButtonIndex"] as? NSNumber)?.intValue
|
|
168
|
+
let textRequiredIndices = Set(
|
|
169
|
+
(options["textRequiredButtonIndices"] as? [NSNumber])?.map { $0.intValue } ?? []
|
|
170
|
+
)
|
|
171
|
+
let type = options["type"] as? String ?? "plain-text"
|
|
132
172
|
|
|
133
173
|
guard let parent = Self.presentedViewController() else {
|
|
134
|
-
completion(cancelButtonIndex, "")
|
|
174
|
+
completion(cancelButtonIndex, "", "")
|
|
135
175
|
|
|
136
176
|
return
|
|
137
177
|
}
|
|
@@ -145,15 +185,35 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
145
185
|
alert.addTextField { field in
|
|
146
186
|
field.placeholder = Self.text(options["placeholder"])
|
|
147
187
|
field.text = Self.text(options["defaultValue"])
|
|
148
|
-
field.isSecureTextEntry =
|
|
188
|
+
field.isSecureTextEntry = type == "secure-text"
|
|
149
189
|
field.keyboardType = Self.keyboardType(options["keyboardType"])
|
|
190
|
+
if type == "login-password" {
|
|
191
|
+
field.textContentType = .username
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// As in React Native's Alert.prompt: a secure field below the login one.
|
|
196
|
+
// The content types let Password AutoFill offer saved credentials.
|
|
197
|
+
if type == "login-password" {
|
|
198
|
+
alert.addTextField { field in
|
|
199
|
+
field.placeholder = Self.text(options["passwordPlaceholder"])
|
|
200
|
+
field.isSecureTextEntry = true
|
|
201
|
+
field.textContentType = .password
|
|
202
|
+
}
|
|
150
203
|
}
|
|
151
204
|
|
|
152
205
|
let presentation = Presentation(
|
|
153
206
|
controller: alert,
|
|
154
207
|
cancelButtonIndex: cancelButtonIndex,
|
|
155
208
|
// Weak, or the presentation would retain the controller it is stored on.
|
|
156
|
-
|
|
209
|
+
currentValues: { [weak alert] in
|
|
210
|
+
let fields = alert?.textFields ?? []
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
fields.first?.text ?? "",
|
|
214
|
+
fields.count > 1 ? (fields[1].text ?? "") : ""
|
|
215
|
+
)
|
|
216
|
+
},
|
|
157
217
|
completion: completion
|
|
158
218
|
)
|
|
159
219
|
|
|
@@ -162,7 +222,10 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
162
222
|
let style: UIAlertAction.Style =
|
|
163
223
|
destructiveIndices.contains(index) ? .destructive : (isCancel ? .cancel : .default)
|
|
164
224
|
|
|
165
|
-
|
|
225
|
+
// Weak: the presentation holds the alert, which holds this handler.
|
|
226
|
+
let action = UIAlertAction(title: label, style: style) { [weak self, weak presentation] _ in
|
|
227
|
+
guard let presentation else { return }
|
|
228
|
+
|
|
166
229
|
self?.finish(presentation, index: index)
|
|
167
230
|
}
|
|
168
231
|
|
|
@@ -179,6 +242,29 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
179
242
|
alert.addAction(action)
|
|
180
243
|
}
|
|
181
244
|
|
|
245
|
+
// Bold, and triggered by the keyboard's return key.
|
|
246
|
+
if let preferredIndex, alert.actions.indices.contains(preferredIndex) {
|
|
247
|
+
alert.preferredAction = alert.actions[preferredIndex]
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// requiresText: those buttons stay disabled while any field is empty.
|
|
251
|
+
if !textRequiredIndices.isEmpty {
|
|
252
|
+
let update: () -> Void = { [weak alert] in
|
|
253
|
+
guard let alert else { return }
|
|
254
|
+
|
|
255
|
+
let filled = (alert.textFields ?? []).allSatisfy { !($0.text ?? "").isEmpty }
|
|
256
|
+
for index in textRequiredIndices where alert.actions.indices.contains(index) {
|
|
257
|
+
alert.actions[index].isEnabled = filled && !disabledIndices.contains(index)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
alert.textFields?.forEach { field in
|
|
262
|
+
field.addAction(UIAction { _ in update() }, for: .editingChanged)
|
|
263
|
+
}
|
|
264
|
+
// A defaultValue may already satisfy it.
|
|
265
|
+
update()
|
|
266
|
+
}
|
|
267
|
+
|
|
182
268
|
alert.view.tintColor = tintColor
|
|
183
269
|
|
|
184
270
|
switch options["userInterfaceStyle"] as? String {
|
|
@@ -191,6 +277,116 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
191
277
|
parent.present(alert, animated: true)
|
|
192
278
|
}
|
|
193
279
|
|
|
280
|
+
private func presentBottomSheet(
|
|
281
|
+
on parent: UIViewController,
|
|
282
|
+
options: NSDictionary,
|
|
283
|
+
labels: [String],
|
|
284
|
+
cancelButtonIndex: Int,
|
|
285
|
+
destructiveIndices: Set<Int>,
|
|
286
|
+
disabledIndices: Set<Int>,
|
|
287
|
+
preferredIndex: Int?,
|
|
288
|
+
tintColor: UIColor?,
|
|
289
|
+
cancelButtonTintColor: UIColor?,
|
|
290
|
+
destructiveColor: UIColor?,
|
|
291
|
+
completion: @escaping (Int) -> Void
|
|
292
|
+
) {
|
|
293
|
+
let rows = labels.enumerated().map { index, label in
|
|
294
|
+
BottomSheetViewController.Row(
|
|
295
|
+
index: index,
|
|
296
|
+
label: label,
|
|
297
|
+
isDestructive: destructiveIndices.contains(index),
|
|
298
|
+
isEnabled: !disabledIndices.contains(index),
|
|
299
|
+
isPreferred: index == preferredIndex
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let sheet = BottomSheetViewController(
|
|
304
|
+
title: Self.text(options["title"]),
|
|
305
|
+
message: Self.text(options["message"]),
|
|
306
|
+
rows: rows.filter { $0.index != cancelButtonIndex },
|
|
307
|
+
cancelRow: rows.first { $0.index == cancelButtonIndex },
|
|
308
|
+
tintColor: tintColor,
|
|
309
|
+
cancelButtonTintColor: cancelButtonTintColor,
|
|
310
|
+
destructiveColor: destructiveColor
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
let presentation = Presentation(
|
|
314
|
+
controller: sheet,
|
|
315
|
+
cancelButtonIndex: cancelButtonIndex,
|
|
316
|
+
completion: { index, _, _ in completion(index) }
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
// Weak: the presentation holds the sheet, which holds this closure.
|
|
320
|
+
sheet.onSelect = { [weak self, weak presentation] index in
|
|
321
|
+
guard let presentation else { return }
|
|
322
|
+
|
|
323
|
+
// Unlike an alert action, a row does not dismiss the sheet itself.
|
|
324
|
+
// Resolve once it is gone, as an alert does.
|
|
325
|
+
presentation.controller.dismiss(animated: true) {
|
|
326
|
+
self?.finish(presentation, index: index)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
switch options["userInterfaceStyle"] as? String {
|
|
331
|
+
case "dark": sheet.overrideUserInterfaceStyle = .dark
|
|
332
|
+
case "light": sheet.overrideUserInterfaceStyle = .light
|
|
333
|
+
default: sheet.overrideUserInterfaceStyle = .unspecified
|
|
334
|
+
}
|
|
335
|
+
sheet.view.tintColor = tintColor
|
|
336
|
+
|
|
337
|
+
if let controller = sheet.sheetPresentationController {
|
|
338
|
+
controller.prefersGrabberVisible = true
|
|
339
|
+
controller.prefersEdgeAttachedInCompactHeight = true
|
|
340
|
+
// Swipe down resolves through presentationControllerDidDismiss.
|
|
341
|
+
controller.delegate = self
|
|
342
|
+
|
|
343
|
+
let bounds = parent.view.window?.bounds ?? parent.view.bounds
|
|
344
|
+
let fitting = sheet.fittingHeight(width: bounds.width)
|
|
345
|
+
let requested = options["detents"] as? [String] ?? []
|
|
346
|
+
|
|
347
|
+
if !requested.isEmpty {
|
|
348
|
+
// The caller's heights; it opens at the first. Duplicates collapse,
|
|
349
|
+
// e.g. 'auto' and 'medium' on iOS 15, where 'auto' is half height.
|
|
350
|
+
var detents: [UISheetPresentationController.Detent] = []
|
|
351
|
+
var identifiers: [UISheetPresentationController.Detent.Identifier] = []
|
|
352
|
+
for name in requested {
|
|
353
|
+
let (detent, identifier) = Self.detent(named: name, fitting: fitting)
|
|
354
|
+
guard !identifiers.contains(identifier) else { continue }
|
|
355
|
+
|
|
356
|
+
detents.append(detent)
|
|
357
|
+
identifiers.append(identifier)
|
|
358
|
+
}
|
|
359
|
+
controller.detents = detents
|
|
360
|
+
controller.selectedDetentIdentifier = identifiers.first
|
|
361
|
+
} else if #available(iOS 16.0, *), fitting <= bounds.height / 2 {
|
|
362
|
+
// A short list opens at its own height; a long one opens at half
|
|
363
|
+
// height and drags up to expand, like the Android sheet. Fitting the
|
|
364
|
+
// content needs a custom detent, iOS 16+; iOS 15 opens at half height.
|
|
365
|
+
controller.detents = [Self.detent(named: "auto", fitting: fitting).0]
|
|
366
|
+
} else {
|
|
367
|
+
controller.detents = [.medium(), .large()]
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
presentations.append(presentation)
|
|
372
|
+
parent.present(sheet, animated: true)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/// Interactive dismissal of a sheet: a swipe down or a tap on the dimmed
|
|
376
|
+
/// area. It resolves the cancel index, or -1 without one, like Android. A
|
|
377
|
+
/// programmatic dismiss() does not call this.
|
|
378
|
+
public func presentationControllerDidDismiss(
|
|
379
|
+
_ presentationController: UIPresentationController
|
|
380
|
+
) {
|
|
381
|
+
guard
|
|
382
|
+
let presentation = presentations.first(where: {
|
|
383
|
+
$0.controller === presentationController.presentedViewController
|
|
384
|
+
})
|
|
385
|
+
else { return }
|
|
386
|
+
|
|
387
|
+
finish(presentation, index: presentation.cancelButtonIndex)
|
|
388
|
+
}
|
|
389
|
+
|
|
194
390
|
@objc public func dismissAll() {
|
|
195
391
|
guard let bottom = presentations.first else { return }
|
|
196
392
|
|
|
@@ -211,6 +407,21 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
211
407
|
}
|
|
212
408
|
}
|
|
213
409
|
|
|
410
|
+
/// The JS runtime is going away: close every sheet, prompt and alert, but
|
|
411
|
+
/// resolve none of them. Their promises belong to the runtime being torn
|
|
412
|
+
/// down, and the list is shared, so stale entries must not outlive it.
|
|
413
|
+
@objc public func invalidate() {
|
|
414
|
+
let all = presentations
|
|
415
|
+
presentations.removeAll()
|
|
416
|
+
all.forEach { $0.discard() }
|
|
417
|
+
|
|
418
|
+
// As in dismissAll(): the lowest sheet's presenter dismisses it and
|
|
419
|
+
// everything stacked above it.
|
|
420
|
+
all.first { $0.controller.presentingViewController != nil }?
|
|
421
|
+
.controller.presentingViewController?
|
|
422
|
+
.dismiss(animated: false)
|
|
423
|
+
}
|
|
424
|
+
|
|
214
425
|
@objc public func dismiss() {
|
|
215
426
|
guard let presentation = presentations.last else { return }
|
|
216
427
|
|
|
@@ -224,10 +435,8 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
224
435
|
public func popoverPresentationControllerDidDismissPopover(
|
|
225
436
|
_ popoverPresentationController: UIPopoverPresentationController
|
|
226
437
|
) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
as? UIAlertController,
|
|
230
|
-
let presentation = presentations.first(where: { $0.controller === alert })
|
|
438
|
+
let alert = popoverPresentationController.presentedViewController
|
|
439
|
+
guard let presentation = presentations.first(where: { $0.controller === alert })
|
|
231
440
|
else { return }
|
|
232
441
|
|
|
233
442
|
finish(presentation, index: presentation.cancelButtonIndex)
|
|
@@ -240,31 +449,59 @@ public class UnifiedActionSheetImpl: NSObject, UIPopoverPresentationControllerDe
|
|
|
240
449
|
presentation.resolve(index)
|
|
241
450
|
}
|
|
242
451
|
|
|
452
|
+
/// 'auto' fits the content (iOS 16+; half height on iOS 15), 'medium' is
|
|
453
|
+
/// half height, 'large' is full height.
|
|
454
|
+
private static func detent(
|
|
455
|
+
named name: String,
|
|
456
|
+
fitting: CGFloat
|
|
457
|
+
) -> (UISheetPresentationController.Detent, UISheetPresentationController.Detent.Identifier) {
|
|
458
|
+
switch name {
|
|
459
|
+
case "large":
|
|
460
|
+
return (.large(), .large)
|
|
461
|
+
case "auto":
|
|
462
|
+
if #available(iOS 16.0, *) {
|
|
463
|
+
let identifier = UISheetPresentationController.Detent.Identifier("unifiedActionSheet.content")
|
|
464
|
+
|
|
465
|
+
return (.custom(identifier: identifier) { min(fitting, $0.maximumDetentValue) }, identifier)
|
|
466
|
+
}
|
|
467
|
+
return (.medium(), .medium)
|
|
468
|
+
default:
|
|
469
|
+
return (.medium(), .medium)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
243
473
|
private final class Presentation {
|
|
244
|
-
|
|
474
|
+
/// A UIAlertController, or the bottom sheet on iPhone.
|
|
475
|
+
let controller: UIViewController
|
|
245
476
|
let cancelButtonIndex: Int
|
|
246
|
-
/// Read at resolve time, not at creation: the
|
|
247
|
-
/// whatever is in the
|
|
248
|
-
private let
|
|
249
|
-
private var completion: ((Int, String) -> Void)?
|
|
477
|
+
/// Read at resolve time, not at creation: the values that matter are
|
|
478
|
+
/// whatever is in the fields when the prompt closes. Sheets pass constants.
|
|
479
|
+
private let currentValues: () -> (String, String)
|
|
480
|
+
private var completion: ((Int, String, String) -> Void)?
|
|
250
481
|
|
|
251
482
|
init(
|
|
252
|
-
controller:
|
|
483
|
+
controller: UIViewController,
|
|
253
484
|
cancelButtonIndex: Int,
|
|
254
|
-
|
|
255
|
-
completion: @escaping (Int, String) -> Void
|
|
485
|
+
currentValues: @escaping () -> (String, String) = { ("", "") },
|
|
486
|
+
completion: @escaping (Int, String, String) -> Void
|
|
256
487
|
) {
|
|
257
488
|
self.controller = controller
|
|
258
489
|
self.cancelButtonIndex = cancelButtonIndex
|
|
259
|
-
self.
|
|
490
|
+
self.currentValues = currentValues
|
|
260
491
|
self.completion = completion
|
|
261
492
|
}
|
|
262
493
|
|
|
494
|
+
/// Drops the completion without calling it.
|
|
495
|
+
func discard() {
|
|
496
|
+
completion = nil
|
|
497
|
+
}
|
|
498
|
+
|
|
263
499
|
func resolve(_ index: Int) {
|
|
264
500
|
guard let completion else { return }
|
|
265
501
|
|
|
266
502
|
self.completion = nil
|
|
267
|
-
|
|
503
|
+
let (text, password) = currentValues()
|
|
504
|
+
completion(index, text, password)
|
|
268
505
|
}
|
|
269
506
|
}
|
|
270
507
|
|
package/jest/index.d.ts
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ActionSheetOptionsInterface,
|
|
3
|
+
ActionSheetResultInterface,
|
|
3
4
|
PromptOptionsInterface,
|
|
4
5
|
PromptResultInterface,
|
|
5
6
|
} from 'react-native-unified-action-sheet';
|
|
6
7
|
|
|
7
|
-
/// Queues the index the next sheet resolves with
|
|
8
|
-
/// resolving
|
|
8
|
+
/// Queues the index the next sheet resolves with; the reason is derived as the
|
|
9
|
+
/// real module would. Pass nothing to go back to resolving as dismissed.
|
|
9
10
|
export declare function setNextButtonIndex(index?: number): void;
|
|
10
11
|
|
|
11
12
|
export declare const showActionSheetWithOptions: jest.Mock<
|
|
12
|
-
Promise<
|
|
13
|
+
Promise<ActionSheetResultInterface>,
|
|
13
14
|
[ActionSheetOptionsInterface]
|
|
14
15
|
>;
|
|
15
|
-
/// Queues what the next prompt resolves with
|
|
16
|
-
/// resolving
|
|
17
|
-
export declare function setNextPromptResult(
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
/// Queues what the next prompt resolves with; the reason is derived as the
|
|
17
|
+
/// real module would. Pass nothing to go back to resolving as dismissed.
|
|
18
|
+
export declare function setNextPromptResult(result?: {
|
|
19
|
+
buttonIndex: number;
|
|
20
|
+
text: string;
|
|
21
|
+
password?: string;
|
|
22
|
+
}): void;
|
|
20
23
|
|
|
21
24
|
export declare const showPromptWithOptions: jest.Mock<
|
|
22
|
-
Promise<PromptResultInterface
|
|
25
|
+
Promise<PromptResultInterface>,
|
|
23
26
|
[PromptOptionsInterface]
|
|
24
27
|
>;
|
|
25
28
|
export declare const dismissActionSheet: jest.Mock<void, []>;
|
package/jest/index.js
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* require('react-native-unified-action-sheet/jest')
|
|
8
8
|
* );
|
|
9
9
|
*
|
|
10
|
-
* By default every sheet resolves
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* By default every sheet resolves as dismissed: { reason: 'dismissed',
|
|
11
|
+
* buttonIndex: undefined }. To simulate a tap, queue the index the next sheet
|
|
12
|
+
* should resolve with; it resolves { reason, buttonIndex } with the reason the
|
|
13
|
+
* real module would give ('cancelled' for the cancel button or -1, otherwise
|
|
14
|
+
* 'selected'), and the pressed button's onPress runs as it would for real:
|
|
13
15
|
*
|
|
14
16
|
* setNextButtonIndex(0);
|
|
15
17
|
* await openTheSheet();
|
|
@@ -17,11 +19,26 @@
|
|
|
17
19
|
* Prefer that over mockResolvedValueOnce, which replaces the implementation and
|
|
18
20
|
* so skips onPress.
|
|
19
21
|
*
|
|
20
|
-
* A prompt is queued the same way, with
|
|
22
|
+
* A prompt is queued the same way, with what the field(s) should hold (password
|
|
23
|
+
* only for a 'login-password' prompt); onPress receives { text, password }:
|
|
21
24
|
*
|
|
22
25
|
* setNextPromptResult({ buttonIndex: 0, text: 'typed' });
|
|
23
26
|
* await openThePrompt();
|
|
24
27
|
*/
|
|
28
|
+
const buttonsOf = (options) => (options && options.options) || [];
|
|
29
|
+
|
|
30
|
+
/// The same rule as the real module: the first button styled 'cancel', or -1,
|
|
31
|
+
/// is a cancellation; any other index is a selection.
|
|
32
|
+
const closeResult = (buttons, buttonIndex) => {
|
|
33
|
+
if (buttonIndex == null)
|
|
34
|
+
return { reason: 'dismissed', buttonIndex: undefined };
|
|
35
|
+
|
|
36
|
+
const cancelIndex = buttons.findIndex((button) => button.style === 'cancel');
|
|
37
|
+
const cancelled = buttonIndex < 0 || buttonIndex === cancelIndex;
|
|
38
|
+
|
|
39
|
+
return { reason: cancelled ? 'cancelled' : 'selected', buttonIndex };
|
|
40
|
+
};
|
|
41
|
+
|
|
25
42
|
let nextButtonIndex;
|
|
26
43
|
|
|
27
44
|
const setNextButtonIndex = (index) => {
|
|
@@ -29,15 +46,14 @@ const setNextButtonIndex = (index) => {
|
|
|
29
46
|
};
|
|
30
47
|
|
|
31
48
|
const showActionSheetWithOptions = jest.fn((options) => {
|
|
32
|
-
const
|
|
49
|
+
const buttons = buttonsOf(options);
|
|
50
|
+
const result = closeResult(buttons, nextButtonIndex);
|
|
33
51
|
nextButtonIndex = undefined;
|
|
34
52
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (button && typeof button.onPress === 'function') button.onPress();
|
|
38
|
-
}
|
|
53
|
+
const button = result.buttonIndex != null && buttons[result.buttonIndex];
|
|
54
|
+
if (button && typeof button.onPress === 'function') button.onPress();
|
|
39
55
|
|
|
40
|
-
return Promise.resolve(
|
|
56
|
+
return Promise.resolve(result);
|
|
41
57
|
});
|
|
42
58
|
|
|
43
59
|
let nextPromptResult;
|
|
@@ -47,16 +63,19 @@ const setNextPromptResult = (result) => {
|
|
|
47
63
|
};
|
|
48
64
|
|
|
49
65
|
const showPromptWithOptions = jest.fn((options) => {
|
|
50
|
-
const
|
|
66
|
+
const buttons = buttonsOf(options);
|
|
67
|
+
const queued = nextPromptResult;
|
|
51
68
|
nextPromptResult = undefined;
|
|
52
69
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
const values = { text: (queued && queued.text) || '' };
|
|
71
|
+
if (queued && queued.password != null) values.password = queued.password;
|
|
72
|
+
const result = {
|
|
73
|
+
...closeResult(buttons, queued ? queued.buttonIndex : undefined),
|
|
74
|
+
...values,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const button = result.buttonIndex != null && buttons[result.buttonIndex];
|
|
78
|
+
if (button && typeof button.onPress === 'function') button.onPress(values);
|
|
60
79
|
|
|
61
80
|
return Promise.resolve(result);
|
|
62
81
|
});
|
|
@@ -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,
|
|
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,GAuDtDC,gCAAmB,CAACC,YAAY,CAAO,oBAAoB,CAAC","ignoreList":[]}
|