;
+};
+
+function DialogAction(props: DialogActionProps) {
+ return ;
+}
+
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
{
unregister();
});
- it("serializes concurrent confirmations", async () => {
+ it("preserves each queued confirmation's focus preference through closing", async () => {
const unregister = registerConfirmDialogHost();
- const first = requireConfirmation(requestConfirmDialog("Delete the project?"));
- const second = requireConfirmation(requestConfirmDialog("Delete the worktree too?"));
+ const first = requireConfirmation(
+ requestConfirmDialog("Install the update?", { initialFocus: "confirm" }),
+ );
+ const second = requireConfirmation(
+ requestConfirmDialog("Delete the worktree too?", { initialFocus: "cancel" }),
+ );
respondToConfirmDialog(false);
await expect(first).resolves.toBe(false);
expect(readConfirmDialogState()).toEqual({
status: "closing",
- message: "Delete the project?",
+ message: "Install the update?",
variant: "default",
+ initialFocus: "confirm",
});
completeConfirmDialogClose();
@@ -72,6 +77,7 @@ describe("confirm dialog coordinator", () => {
status: "confirming",
message: "Delete the worktree too?",
variant: "default",
+ initialFocus: "cancel",
});
respondToConfirmDialog(true);
diff --git a/apps/web/src/confirmDialog.ts b/apps/web/src/confirmDialog.ts
index 136d20814406..9ee374f42ed0 100644
--- a/apps/web/src/confirmDialog.ts
+++ b/apps/web/src/confirmDialog.ts
@@ -6,16 +6,19 @@ export type ConfirmDialogState =
readonly status: "confirming";
readonly message: string;
readonly variant: ConfirmDialogVariant;
+ readonly initialFocus: ConfirmDialogOptions["initialFocus"];
}
| {
readonly status: "closing";
readonly message: string;
readonly variant: ConfirmDialogVariant;
+ readonly initialFocus: ConfirmDialogOptions["initialFocus"];
};
type PendingConfirmation = {
readonly message: string;
readonly variant: ConfirmDialogVariant;
+ readonly initialFocus: ConfirmDialogOptions["initialFocus"];
readonly resolve: (confirmed: boolean) => void;
};
@@ -82,6 +85,7 @@ export function requestConfirmDialog(
const pending = {
message,
variant: options?.variant ?? "default",
+ initialFocus: options?.initialFocus,
resolve,
} satisfies PendingConfirmation;
if (activeConfirmation || state.status === "closing") {
@@ -90,7 +94,12 @@ export function requestConfirmDialog(
}
activeConfirmation = pending;
- publish({ status: "confirming", message, variant: pending.variant });
+ publish({
+ status: "confirming",
+ message,
+ variant: pending.variant,
+ initialFocus: pending.initialFocus,
+ });
});
return confirmation;
@@ -106,7 +115,12 @@ export function respondToConfirmDialog(confirmed: boolean): void {
const confirmation = activeConfirmation;
activeConfirmation = null;
confirmation.resolve(confirmed);
- publish({ status: "closing", message: state.message, variant: state.variant });
+ publish({
+ status: "closing",
+ message: state.message,
+ variant: state.variant,
+ initialFocus: state.initialFocus,
+ });
}
export function completeConfirmDialogClose(): void {
@@ -119,7 +133,12 @@ export function completeConfirmDialogClose(): void {
}
activeConfirmation = next;
- publish({ status: "confirming", message: next.message, variant: next.variant });
+ publish({
+ status: "confirming",
+ message: next.message,
+ variant: next.variant,
+ initialFocus: next.initialFocus,
+ });
}
export function resetConfirmDialogForTests(): void {
diff --git a/packages/contracts/src/ipc.ts b/packages/contracts/src/ipc.ts
index dcce2af0b0ea..b770c1021735 100644
--- a/packages/contracts/src/ipc.ts
+++ b/packages/contracts/src/ipc.ts
@@ -1213,6 +1213,8 @@ export type ConfirmDialogVariant = "default" | "destructive";
export interface ConfirmDialogOptions {
readonly variant?: ConfirmDialogVariant;
+ /** Which button takes focus on open. Defaults to Cancel; destructive confirms always keep Cancel. */
+ readonly initialFocus?: "confirm" | "cancel";
}
export interface LocalApi {