Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
132117a
feat: Add cancelable parameter on Android
OS-pedrogustavobilro Dec 17, 2024
aa33a07
feat: Add cancelable parameter on iOS
OS-pedrogustavobilro Dec 17, 2024
dd4bb5a
refactor: Return canceled boolean instead of error
OS-pedrogustavobilro Dec 17, 2024
1c81806
refactor: Add cancelable parameter on Web
OS-pedrogustavobilro Dec 17, 2024
433e38d
docs: Update README for action-sheet
OS-pedrogustavobilro Dec 17, 2024
e247aa7
chore: fix lint issues
OS-pedrogustavobilro Dec 18, 2024
977f3de
Merge branch 'main' into feat/RMET-3576/action-sheet-cancelable
OS-pedrogustavobilro Jan 13, 2025
99443ae
refactor(action-sheet): Explicitly get string and bool arguments
OS-pedrogustavobilro Jan 13, 2025
89f87b2
chore(action-sheet) update README.md
OS-pedrogustavobilro Jan 13, 2025
30dc0e1
Merge branch 'main' into feat/RMET-3576/action-sheet-cancelable
OS-pedrogustavobilro Feb 19, 2026
a9431d3
chore: run fmt
OS-pedrogustavobilro Feb 19, 2026
e9531a9
docs: Update new parameters documentation
OS-pedrogustavobilro Feb 19, 2026
2dc1aaa
fix: Remove cancelable logic from iOS
OS-pedrogustavobilro Feb 20, 2026
c3203e6
fix(ios): return canceled with .cancel button
OS-pedrogustavobilro Feb 20, 2026
81c80f9
chore: fix lint issues
OS-pedrogustavobilro Feb 20, 2026
1c1394e
docs: Update new action sheet parameters
OS-pedrogustavobilro Feb 20, 2026
9d2181f
fix(ios): Backwards compatible behavior with iOS 18 and below
OS-pedrogustavobilro Feb 20, 2026
4ab4c3f
Merge branch 'main' into feat/RMET-3576/action-sheet-cancelable
OS-pedrogustavobilro Feb 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions action-sheet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,20 @@ to select.

#### ShowActionsResult

| Prop | Type | Description | Since |
| ----------- | ------------------- | -------------------------------------------- | ----- |
| **`index`** | <code>number</code> | The index of the clicked option (Zero-based) | 1.0.0 |
| Prop | Type | Description | Since |
| -------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| **`index`** | <code>number</code> | The index of the clicked option (Zero-based), or -1 if the sheet was canceled. On iOS, if there is a button with <a href="#actionsheetbuttonstyle">ActionSheetButtonStyle.Cancel</a>, and user clicks outside the sheet, the index of the cancel option is returned | 1.0.0 |
| **`canceled`** | <code>boolean</code> | True if sheet was canceled by user; False otherwise On Web, requires having @ionic/pwa-elements version 3.4.0 or higher. | 8.1.0 |


#### ShowActionsOptions

| Prop | Type | Description | Since |
| ------------- | -------------------------------- | ------------------------------------------------------------------------ | ----- |
| **`title`** | <code>string</code> | The title of the Action Sheet. | 1.0.0 |
| **`message`** | <code>string</code> | A message to show under the title. This option is only supported on iOS. | 1.0.0 |
| **`options`** | <code>ActionSheetButton[]</code> | Options the user can choose from. | 1.0.0 |
| Prop | Type | Description | Since |
| ---------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| **`title`** | <code>string</code> | The title of the Action Sheet. | 1.0.0 |
| **`message`** | <code>string</code> | A message to show under the title. This option is only supported on iOS. | 1.0.0 |
| **`options`** | <code>ActionSheetButton[]</code> | Options the user can choose from. | 1.0.0 |
| **`cancelable`** | <code>boolean</code> | If true, sheet is canceled when clicked outside; If false, it is not. By default, false. Not available on iOS, sheet is always cancelable by clicking outside of it. On Web, requires having @ionic/pwa-elements version 3.4.0 or higher. | 8.1.0 |


#### ActionSheetButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ public interface OnCancelListener {
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
this.cancelListener.onCancel();
if (this.cancelListener != null) {
this.cancelListener.onCancel();
}
}

private String title;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class ActionSheetPlugin extends Plugin {
@PluginMethod
public void showActions(final PluginCall call) {
String title = call.getString("title");
boolean cancelable = Boolean.TRUE.equals(call.getBoolean("cancelable", false));
JSArray options = call.getArray("options");
if (options == null) {
call.reject("Must supply options");
Expand All @@ -39,7 +40,10 @@ public void showActions(final PluginCall call) {
}
implementation.setTitle(title);
implementation.setOptions(actionOptions);
implementation.setCancelable(false);
implementation.setCancelable(cancelable);
if (cancelable) {
implementation.setOnCancelListener(() -> resolve(call, -1));
}
implementation.setOnSelectedListener((index) -> {
JSObject ret = new JSObject();
ret.put("index", index);
Expand All @@ -52,4 +56,12 @@ public void showActions(final PluginCall call) {
call.reject("JSON error processing an option for showActions", ex);
}
}

private void resolve(final PluginCall call, int selectedIndex) {
JSObject ret = new JSObject();
ret.put("index", selectedIndex);
ret.put("canceled", selectedIndex < 0);
call.resolve(ret);
implementation.dismiss();
}
}
72 changes: 66 additions & 6 deletions action-sheet/ios/Sources/ActionSheetPlugin/ActionSheetPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,103 @@ import Capacitor
* here: https://capacitorjs.com/docs/plugins/ios
*/
@objc(ActionSheetPlugin)
public class ActionSheetPlugin: CAPPlugin, CAPBridgedPlugin {
public class ActionSheetPlugin: CAPPlugin, CAPBridgedPlugin, UIAdaptivePresentationControllerDelegate {
public let identifier = "ActionSheetPlugin"
public let jsName = "ActionSheet"
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "showActions", returnType: CAPPluginReturnPromise)
]
private let implementation = ActionSheet()
private var currentCall: CAPPluginCall?

@objc func showActions(_ call: CAPPluginCall) {
let title = call.options["title"] as? String
let message = call.options["message"] as? String

let options = call.getArray("options", JSObject.self) ?? []
var alertActions = [UIAlertAction]()
var hasCancellableButton = false
for (index, option) in options.enumerated() {
let style = option["style"] as? String ?? "DEFAULT"
let title = option["title"] as? String ?? ""
var buttonStyle: UIAlertAction.Style = .default
if style == "DESTRUCTIVE" {
buttonStyle = .destructive
} else if style == "CANCEL" {
hasCancellableButton = true
buttonStyle = .cancel
}
let action = UIAlertAction(title: title, style: buttonStyle, handler: { (_) in
call.resolve([
"index": index
])
let action = UIAlertAction(title: title, style: buttonStyle, handler: { [weak self] (_) in
if buttonStyle == .cancel {
call.actionSheetCanceled()
} else {
call.resolve([
"index": index,
"canceled": false
])
}
self?.currentCall = nil
})
alertActions.append(action)
}

DispatchQueue.main.async { [weak self] in
if let alertController = self?.implementation.buildActionSheet(title: title, message: message, actions: alertActions) {
self?.setCenteredPopover(alertController)
self?.bridge?.viewController?.present(alertController, animated: true, completion: nil)
self?.bridge?.viewController?.present(alertController, animated: true) {
if !hasCancellableButton {
self?.setupCancelationListerners(alertController, call)
}
}
}
}
}

private func setupCancelationListerners(_ alertController: UIAlertController, _ call: CAPPluginCall) {
if #available(iOS 26, *) {
self.currentCall = call
alertController.presentationController?.delegate = self
} else {
// For iOS versions below 26, setting the presentation controller delegate would result in a crash
// "Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The presentation controller of an alert controller presenting as an alert must not have its delegate modified"
// Hence, the alternative by adding a gesture recognizer (which only works for iOS versions below 26)
let gestureRecognizer = TapGestureRecognizerWithClosure {
alertController.dismiss(animated: true, completion: nil)
call.actionSheetCanceled()
}
let backroundView = alertController.view.superview?.subviews[0]
backroundView?.addGestureRecognizer(gestureRecognizer)
}
}

// MARK: - UIAdaptivePresentationControllerDelegate

public func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
self.currentCall?.actionSheetCanceled()
self.currentCall = nil
}
}

// MARK: - TapGestureRecognizerWithClosure
private final class TapGestureRecognizerWithClosure: UITapGestureRecognizer {
private let onTap: () -> Void

init(onTap: @escaping () -> Void) {
self.onTap = onTap
super.init(target: nil, action: nil)
self.addTarget(self, action: #selector(action))
}

@objc private func action() {
onTap()
}
}

private extension CAPPluginCall {
func actionSheetCanceled() {
resolve([
"index": -1,
"canceled": true
])
}
}
23 changes: 22 additions & 1 deletion action-sheet/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ export interface ShowActionsOptions {
* @since 1.0.0
*/
options: ActionSheetButton[];

/**
* If true, sheet is canceled when clicked outside; If false, it is not. By default, false.
*
* Not available on iOS, sheet is always cancelable by clicking outside of it.
*
* On Web, requires having @ionic/pwa-elements version 3.4.0 or higher.
*
* @since 8.1.0
*/
cancelable?: boolean;
}

export enum ActionSheetButtonStyle {
Expand Down Expand Up @@ -76,11 +87,21 @@ export interface ActionSheetButton {

export interface ShowActionsResult {
/**
* The index of the clicked option (Zero-based)
* The index of the clicked option (Zero-based), or -1 if the sheet was canceled.
*
* On iOS, if there is a button with ActionSheetButtonStyle.Cancel, and user clicks outside the sheet, the index of the cancel option is returned
*
* @since 1.0.0
*/
index: number;
/**
* True if sheet was canceled by user; False otherwise
*
* On Web, requires having @ionic/pwa-elements version 3.4.0 or higher.
*
* @since 8.1.0
*/
canceled: boolean;
}

export interface ActionSheetPlugin {
Expand Down
11 changes: 10 additions & 1 deletion action-sheet/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,30 @@

export class ActionSheetWeb extends WebPlugin implements ActionSheetPlugin {
async showActions(options: ShowActionsOptions): Promise<ShowActionsResult> {
return new Promise<ShowActionsResult>((resolve, _reject) => {

Check warning on line 7 in action-sheet/src/web.ts

View workflow job for this annotation

GitHub Actions / lint (action-sheet)

'_reject' is defined but never used
let actionSheet: any = document.querySelector('pwa-action-sheet');
if (!actionSheet) {
actionSheet = document.createElement('pwa-action-sheet');
document.body.appendChild(actionSheet);
}
actionSheet.header = options.title;
actionSheet.cancelable = false;
actionSheet.cancelable = options.cancelable;
actionSheet.options = options.options;
actionSheet.addEventListener('onSelection', async (e: any) => {
const selection = e.detail;
resolve({
index: selection,
canceled: false,
});
});
if (options.cancelable) {
actionSheet.addEventListener('onCanceled', async () => {
resolve({
index: -1,
canceled: true,
});
});
}
});
}
}