From c4fcb03ee3e48e89366939be96725969b8b0eb54 Mon Sep 17 00:00:00 2001
From: Hunter Johnston <64506580+huntabyte@users.noreply.github.com>
Date: Wed, 2 Oct 2024 20:57:44 -0400
Subject: [PATCH] next: `AlertDialog` action changes & Dialog + form docs
(#718)
---
.changeset/famous-suits-wash.md | 5 +++
.../components/alert-dialog-action.svelte | 7 ++-
.../src/lib/bits/dialog/dialog.svelte.ts | 37 ++++++++++++++++
sites/docs/content/components/alert-dialog.md | 44 +++++++++++++++++++
sites/docs/content/components/dialog.md | 43 ++++++++++++++++++
.../content/api-reference/alert-dialog.api.ts | 3 +-
6 files changed, 134 insertions(+), 5 deletions(-)
create mode 100644 .changeset/famous-suits-wash.md
diff --git a/.changeset/famous-suits-wash.md b/.changeset/famous-suits-wash.md
new file mode 100644
index 000000000..29096a933
--- /dev/null
+++ b/.changeset/famous-suits-wash.md
@@ -0,0 +1,5 @@
+---
+"bits-ui": patch
+---
+
+Alert Dialog: `Action` button no longer closes the dialog to accomodate form submission / other asynchronous action
diff --git a/packages/bits-ui/src/lib/bits/alert-dialog/components/alert-dialog-action.svelte b/packages/bits-ui/src/lib/bits/alert-dialog/components/alert-dialog-action.svelte
index 68f430448..439e28466 100644
--- a/packages/bits-ui/src/lib/bits/alert-dialog/components/alert-dialog-action.svelte
+++ b/packages/bits-ui/src/lib/bits/alert-dialog/components/alert-dialog-action.svelte
@@ -3,7 +3,7 @@
import type { ActionProps } from "../index.js";
import { mergeProps } from "$lib/internal/mergeProps.js";
import { useId } from "$lib/internal/useId.js";
- import { useDialogClose } from "$lib/bits/dialog/dialog.svelte.js";
+ import { useAlertDialogAction } from "$lib/bits/dialog/dialog.svelte.js";
let {
children,
@@ -13,8 +13,7 @@
...restProps
}: ActionProps = $props();
- const closeState = useDialogClose({
- variant: box.with(() => "action"),
+ const actionState = useAlertDialogAction({
id: box.with(() => id),
ref: box.with(
() => ref,
@@ -22,7 +21,7 @@
),
});
- const mergedProps = $derived(mergeProps(restProps, closeState.props));
+ const mergedProps = $derived(mergeProps(restProps, actionState.props));
{#if child}
diff --git a/packages/bits-ui/src/lib/bits/dialog/dialog.svelte.ts b/packages/bits-ui/src/lib/bits/dialog/dialog.svelte.ts
index 94df325a2..c57fa754d 100644
--- a/packages/bits-ui/src/lib/bits/dialog/dialog.svelte.ts
+++ b/packages/bits-ui/src/lib/bits/dialog/dialog.svelte.ts
@@ -83,6 +83,10 @@ class DialogRootState {
return new AlertDialogCancelState(props, this);
}
+ createAction(props: DialogActionStateProps) {
+ return new DialogActionState(props, this);
+ }
+
sharedProps = $derived.by(
() =>
({
@@ -170,6 +174,35 @@ class DialogCloseState {
);
}
+type DialogActionStateProps = WithRefProps;
+
+class DialogActionState {
+ #id: DialogActionStateProps["id"];
+ #ref: DialogActionStateProps["ref"];
+ #root: DialogRootState;
+ #attr = $derived.by(() => this.#root.attrs.action);
+
+ constructor(props: DialogActionStateProps, root: DialogRootState) {
+ this.#id = props.id;
+ this.#ref = props.ref;
+ this.#root = root;
+
+ useRefById({
+ id: this.#id,
+ ref: this.#ref,
+ });
+ }
+
+ props = $derived.by(
+ () =>
+ ({
+ id: this.#id.current,
+ [this.#attr]: "",
+ ...this.#root.sharedProps,
+ }) as const
+ );
+}
+
type DialogTitleStateProps = WithRefProps<
ReadableBoxedValues<{
level: 1 | 2 | 3 | 4 | 5 | 6;
@@ -382,3 +415,7 @@ export function useDialogClose(props: DialogCloseStateProps) {
export function useAlertDialogCancel(props: AlertDialogCancelStateProps) {
return getDialogRootContext().createCancel(props);
}
+
+export function useAlertDialogAction(props: DialogActionStateProps) {
+ return getDialogRootContext().createAction(props);
+}
diff --git a/sites/docs/content/components/alert-dialog.md b/sites/docs/content/components/alert-dialog.md
index 98b06a25a..b202a0a01 100644
--- a/sites/docs/content/components/alert-dialog.md
+++ b/sites/docs/content/components/alert-dialog.md
@@ -418,4 +418,48 @@ Dialogs can be nested within each other to create more complex layouts. See the
See the [Dialog](/docs/components/dialog) component for more information on Svelte Transitions with dialog components.
+## Working with Forms
+
+### Form Submission
+
+When using the `AlertDialog` component, often you'll want to submit a form or perform an asynchronous action when the user clicks the `Action` button.
+
+This can be done by waiting for the asynchronous action to complete, then programmatically closing the dialog.
+
+```svelte
+
+
+
+
+
+
+ Confirm your action
+ Are you sure you want to do this?
+
+
+
+
+```
+
+### Inside a Form
+
+If you're using an `AlertDialog` _within_ a form, you'll need to ensure that the `Portal` is disabled or not included in the `AlertDialog` structure. This is because the `Portal` will render the dialog content _outside_ of the form, which will prevent the form from being submitted correctly.
+
diff --git a/sites/docs/content/components/dialog.md b/sites/docs/content/components/dialog.md
index 38a929843..76fc90409 100644
--- a/sites/docs/content/components/dialog.md
+++ b/sites/docs/content/components/dialog.md
@@ -578,4 +578,47 @@ You can then use the `MyDialogOverlay` component alongside the other `Dialog` pr
```
+## Working with Forms
+
+### Form Submission
+
+When using the `Dialog` component, often you'll want to submit a form or perform an asynchronous action and then close the dialog.
+
+This can be done by waiting for the asynchronous action to complete, then programmatically closing the dialog.
+
+```svelte
+
+
+
+
+
+
+ Confirm your action
+ Are you sure you want to do this?
+
+
+
+
+```
+
+### Inside a Form
+
+If you're using a `Dialog` _within_ a form, you'll need to ensure that the `Portal` is disabled or not included in the `Dialog` structure. This is because the `Portal` will render the dialog content _outside_ of the form, which will prevent the form from being submitted correctly.
+
diff --git a/sites/docs/src/lib/content/api-reference/alert-dialog.api.ts b/sites/docs/src/lib/content/api-reference/alert-dialog.api.ts
index 44b79cd9f..ce6596aa1 100644
--- a/sites/docs/src/lib/content/api-reference/alert-dialog.api.ts
+++ b/sites/docs/src/lib/content/api-reference/alert-dialog.api.ts
@@ -66,7 +66,8 @@ const root = createApiSchema({
const action = createApiSchema({
title: "Action",
- description: "A button used to close the alert dialog by taking an action.",
+ description:
+ "The button responsible for taking an action within the alert dialog. This button does not close the dialog out of the box. See the [Form Submission](#form-submission) documentation for more information.",
props: withChildProps({ elType: "HTMLButtonElement" }),
dataAttributes: [
createDataAttrSchema({