Skip to content

feat: support message dialogs with 3 buttons #2641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: v2
Choose a base branch
from
6 changes: 6 additions & 0 deletions .changes/dialog-3-buttons.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"dialog": "minor"
"dialog-js": "minor"
---

Add support for showing a message dialog with 3 buttons.
30 changes: 21 additions & 9 deletions examples/api/src/views/Dialog.svelte
Original file line number Diff line number Diff line change
@@ -44,6 +44,13 @@
await message("Tauri is awesome!");
}

async function msgCustom(result) {
const buttons = { yes: "awesome", no: "amazing", cancel: "stunning" };
await message(`Tauri is: `, { buttons })
.then((res) => onMessage(`Tauri is ${res}`))
.catch(onMessage);
}

function openDialog() {
open({
title: "My wonderful open dialog",
@@ -136,12 +143,17 @@
<label for="dialog-directory">Directory</label>
</div>
<br />
<button class="btn" id="open-dialog" on:click={openDialog}>Open dialog</button>
<button class="btn" id="save-dialog" on:click={saveDialog}
>Open save dialog</button
>
<button class="btn" id="prompt-dialog" on:click={prompt}>Prompt</button>
<button class="btn" id="custom-prompt-dialog" on:click={promptCustom}
>Prompt (custom)</button
>
<button class="btn" id="message-dialog" on:click={msg}>Message</button>

<div class="flex flex-wrap flex-col md:flex-row gap-2 children:flex-shrink-0">
<button class="btn" id="open-dialog" on:click={openDialog}>Open dialog</button>
<button class="btn" id="save-dialog" on:click={saveDialog}
>Open save dialog</button
>
<button class="btn" id="prompt-dialog" on:click={prompt}>Prompt</button>
<button class="btn" id="custom-prompt-dialog" on:click={promptCustom}
>Prompt (custom)</button
>
<button class="btn" id="message-dialog" on:click={msg}>Message</button>
<button class="btn" id="message-dialog" on:click={msgCustom}>Message (custom)</button>

</div>
26 changes: 18 additions & 8 deletions plugins/dialog/android/src/main/java/DialogPlugin.kt
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@ class MessageOptions {
var title: String? = null
lateinit var message: String
var okButtonLabel: String? = null
var noButtonLabel: String? = null
var cancelButtonLabel: String? = null
}

@@ -169,9 +170,8 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
return
}

val handler = { cancelled: Boolean, value: Boolean ->
val handler = { value: String ->
val ret = JSObject()
ret.put("cancelled", cancelled)
ret.put("value", value)
invoke.resolve(ret)
}
@@ -183,24 +183,34 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
if (args.title != null) {
builder.setTitle(args.title)
}

val okButtonLabel = args.okButtonLabel ?: "Ok"

builder
.setMessage(args.message)
.setPositiveButton(
args.okButtonLabel ?: "OK"
) { dialog, _ ->
.setPositiveButton(okButtonLabel) { dialog, _ ->
dialog.dismiss()
handler(false, true)
handler(okButtonLabel)
}
.setOnCancelListener { dialog ->
dialog.dismiss()
handler(true, false)
handler(args.cancelButtonLabel ?: "Cancel")
}

if (args.noButtonLabel != null) {
builder.setNeutralButton(args.noButtonLabel) { dialog, _ ->
dialog.dismiss()
handler(args.noButtonLabel!!)
}
}

if (args.cancelButtonLabel != null) {
builder.setNegativeButton( args.cancelButtonLabel) { dialog, _ ->
dialog.dismiss()
handler(false, false)
handler(args.cancelButtonLabel!!)
}
}

val dialog = builder.create()
dialog.show()
}
2 changes: 1 addition & 1 deletion plugins/dialog/api-iife.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

119 changes: 115 additions & 4 deletions plugins/dialog/guest-js/index.ts
Original file line number Diff line number Diff line change
@@ -77,6 +77,72 @@ interface SaveDialogOptions {
canCreateDirectories?: boolean
}

/**
* Default buttons for a message dialog.
*
* @since 2.3.0
*/
export type MessageDialogDefaultButtons =
| 'Ok'
| 'OkCancel'
| 'YesNo'
| 'YesNoCancel'

/**
* The Yes, No and Cancel buttons of a message dialog.
*
* @since 2.3.0
*/
export type MessageDialogButtonsYesNoCancel = {
/** The Yes button. */
yes: string
/** The No button. */
no: string
/** The Cancel button. */
cancel: string
}

/**
* The Ok and Cancel buttons of a message dialog.
*
* @since 2.3.0
*/
export type MessageDialogButtonsOkCancel = {
/** The Ok button. */
ok: string
/** The Cancel button. */
cancel: string
}

/**
* The Ok button of a message dialog.
*
* @since 2.3.0
*/
export type MessageDialogButtonsOk = {
/** The Ok button. */
ok: string
}
Comment on lines +96 to +125
Copy link
Contributor

@Legend-Master Legend-Master Apr 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small suggestion, maybe we can change these to something like this, so typescript doesn't hint ok and yes at the same type

Suggested change
export type MessageDialogButtonsYesNoCancel = {
/** The Yes button. */
yes: string
/** The No button. */
no: string
/** The Cancel button. */
cancel: string
}
/**
* The Ok and Cancel buttons of a message dialog.
*
* @since 2.3.0
*/
export type MessageDialogButtonsOkCancel = {
/** The Ok button. */
ok: string
/** The Cancel button. */
cancel: string
}
/**
* The Ok button of a message dialog.
*
* @since 2.3.0
*/
export type MessageDialogButtonsOk = {
/** The Ok button. */
ok: string
}
export type MessageDialogButtonsYesNoCancel = {
/** The Yes button. */
yes: string
/** The No button. */
no: string
/** The Cancel button. */
cancel: string
}
/**
* The Ok and Cancel buttons of a message dialog.
*
* @since 2.3.0
*/
export type MessageDialogButtonsOkCancel = {
/** The Ok button. */
yes: string
/** The Cancel button. */
cancel: string
}
/**
* The Ok button of a message dialog.
*
* @since 2.3.0
*/
export type MessageDialogButtonsOk = {
/** The Ok button. */
yes: string
}


/**
* Custom buttons for a message dialog.
*
* @since 2.3.0
*/
export type MessageDialogCustomButtons =
| MessageDialogButtonsYesNoCancel
| MessageDialogButtonsOkCancel
| MessageDialogButtonsOk

/**
* The buttons of a message dialog.
*
* @since 2.3.0
*/
export type MessageDialogButtons =
| MessageDialogDefaultButtons
| MessageDialogCustomButtons

/**
* @since 2.0.0
*/
@@ -85,8 +151,41 @@ interface MessageDialogOptions {
title?: string
/** The kind of the dialog. Defaults to `info`. */
kind?: 'info' | 'warning' | 'error'
/** The label of the confirm button. */
/**
* The label of the Ok button.
*
* @deprecated Use {@linkcode MessageDialogOptions.buttons} instead.
*/
okLabel?: string
/**
* The buttons of the dialog.
*
* @since 2.3.0
*/
buttons?: MessageDialogButtons
}

/**
* Internal function to convert the buttons to the Rust type.
*/
function buttonsToRust(buttons: MessageDialogButtons | undefined) {
if (buttons === undefined) {
return undefined
}

if (typeof buttons === 'string') {
return buttons
} else if ('ok' in buttons && 'cancel' in buttons) {
return { OkCancelCustom: [buttons.ok, buttons.cancel] }
} else if ('yes' in buttons && 'no' in buttons && 'cancel' in buttons) {
return {
YesNoCancelCustom: [buttons.yes, buttons.no, buttons.cancel]
}
} else if ('ok' in buttons) {
return { OkCustom: buttons.ok }
}

return undefined
}

interface ConfirmDialogOptions {
@@ -202,6 +301,16 @@ async function save(options: SaveDialogOptions = {}): Promise<string | null> {
return await invoke('plugin:dialog|save', { options })
}

/**
* The result of a message dialog.
*
* The result is a string if the dialog has custom buttons,
* otherwise it is one of the default buttons.
*
* @since 2.3.0
*/
export type MessageDialogResult = 'Yes' | 'No' | 'Ok' | 'Cancel' | (string & {})

/**
* Shows a message dialog with an `Ok` button.
* @example
@@ -222,13 +331,15 @@ async function save(options: SaveDialogOptions = {}): Promise<string | null> {
async function message(
message: string,
options?: string | MessageDialogOptions
): Promise<void> {
): Promise<MessageDialogResult> {
const opts = typeof options === 'string' ? { title: options } : options
await invoke('plugin:dialog|message', {

return invoke<MessageDialogResult>('plugin:dialog|message', {
message: message.toString(),
title: opts?.title?.toString(),
kind: opts?.kind,
okButtonLabel: opts?.okLabel?.toString()
okButtonLabel: opts?.okLabel?.toString(),
buttons: buttonsToRust(opts?.buttons)
})
}

41 changes: 22 additions & 19 deletions plugins/dialog/ios/Sources/DialogPlugin.swift
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ struct MessageDialogOptions: Decodable {
var title: String?
let message: String
var okButtonLabel: String?
var noButtonLabel: String?
var cancelButtonLabel: String?
}

@@ -200,36 +201,38 @@ class DialogPlugin: Plugin {
let alert = UIAlertController(
title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert)

let cancelButtonLabel = args.cancelButtonLabel ?? ""
if !cancelButtonLabel.isEmpty {
if let cancelButtonLabel = args.cancelButtonLabel {
alert.addAction(
UIAlertAction(
title: cancelButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in
Logger.error("cancel")

invoke.resolve([
"value": false,
"cancelled": false,
])
}))
invoke.resolve(["value": cancelButtonLabel])
}
)
)
}

let okButtonLabel = args.okButtonLabel ?? (cancelButtonLabel.isEmpty ? "OK" : "")
if !okButtonLabel.isEmpty {
if let noButtonLabel = args.noButtonLabel {
alert.addAction(
UIAlertAction(
title: okButtonLabel, style: UIAlertAction.Style.default,
title: noButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in
Logger.error("ok")

invoke.resolve([
"value": true,
"cancelled": false,
])
}))
invoke.resolve(["value": noButtonLabel])
}
)
)
}

let okButtonLabel = args.okButtonLabel ?? "Ok"
alert.addAction(
UIAlertAction(
title: okButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in
invoke.resolve(["value": okButtonLabel])
}
)
)

manager.viewController?.present(alert, animated: true, completion: nil)
}
}
Loading