app/components/admin/confirm-dialog.tsx
153 lines and 8 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| ConfirmDialog | 10 to 158 | 8 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:38> | 38 to 68 | 5 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:48> | 48 to 67 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:49> | 49 to 66 | 7 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| cancel | 72 to 75 | 2 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:87> | 87 to 90 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:106> | 106 to 110 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:127> | 127 to 127 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
Imports
It imports app/lib/destructive.mjs. Imported by app/components/admin/media-confirm-dialogs.tsx, app/components/admin/media-trash-controls.tsx, app/components/admin/posts-confirm-dialogs.tsx, app/routes/admin.mentions.tsx, app/routes/admin.posts.$slug.edit.tsx.
Source
import { useEffect, useId, useRef, useState } from "react";
import { Form, Link, useNavigate } from "react-router";
import { CONFIRM_FIELD } from "~/lib/destructive.mjs";
// Capsomer's dialog markup and look, on this component's own behaviour: Capsomer's ConfirmDialog is driven
// from script, and this one is opened by the server's answer, so the second step exists without script too.
// `data-inline`, not `open`: putting `open` in the JSX makes React and showModal() fight over it.
// The intent is a hidden field because a disabled submitter contributes no name and no value.
export function ConfirmDialog({
title,
body,
stake,
requireTyped,
confirmLabel,
/** Where Cancel goes. Carry the current query so filters survive. */
cancelHref,
/** Cancel for a dialog opened from client state rather than the URL, which has nowhere to go. */
onCancel,
children,
}: {
title: string;
body: React.ReactNode;
stake?: string[];
/** Absent for a reversible action: the dialog then confirms without a typed value. */
requireTyped?: string;
confirmLabel: string;
children: React.ReactNode;
} & ({ cancelHref: string; onCancel?: never } | { cancelHref?: never; onCancel: () => void })) {
const ref = useRef<HTMLDialogElement>(null);
const fieldRef = useRef<HTMLInputElement>(null);
const [typed, setTyped] = useState("");
const [hydrated, setHydrated] = useState(false);
const titleId = useId();
const formId = useId();
const navigate = useNavigate();
useEffect(() => {
setHydrated(true);
// Read before showModal() moves focus: the control that opened it, or nothing on a page load.
const active = document.activeElement;
const origin = active instanceof HTMLElement && active !== document.body ? active : null;
const el = ref.current;
if (el && !el.open) el.showModal();
// autoFocus is not enough: React focuses on mount, and the dialog's focus rules run at showModal().
fieldRef.current?.focus();
// Unmounting drops focus on the body. Deferred, so the page Cancel returns to has rendered.
return () => {
window.setTimeout(() => {
if (document.activeElement && document.activeElement !== document.body) return;
// In order: the opener; its menu's button, when the opener was an item in a row menu whose
// popover has since closed and so cannot take focus; the page.
const panel = origin?.closest<HTMLElement>("[popover]");
const candidates = [
origin,
panel?.id
? document.querySelector<HTMLElement>(`[popovertarget="${CSS.escape(panel.id)}"]`)
: null,
document.getElementById("main"),
];
for (const back of candidates) {
if (!back?.isConnected) continue;
back.focus({ preventScroll: true });
if (document.activeElement === back) return;
}
}, 0);
};
}, []);
const satisfied = requireTyped === undefined || typed.trim() === requireTyped;
// Cancel keeps the scroll position: the page behind the dialog is where the reader still is.
const cancel = async () => {
if (cancelHref === undefined) onCancel?.();
else await navigate(cancelHref, { preventScrollReset: true });
};
return (
<dialog
ref={ref}
className="cap-dialog"
data-placement="center"
data-size="md"
role="alertdialog"
data-inline={hydrated ? undefined : ""}
aria-labelledby={titleId}
/* Escape leaves the way Cancel does: a natively closed dialog would leave the page still asking. */
onCancel={async (event) => {
event.preventDefault();
await cancel();
}}
>
{/* The form holds the caller's hidden fields; the typed field and the submit button join it by
`form`, so the dialog's header, body and footer stay its direct children, as Capsomer lays them out. */}
<Form method="post" id={formId}>
{children}
</Form>
<div className="cap-dialog-header">
<h2 className="cap-dialog-title" id={titleId}>
{title}
</h2>
</div>
<div className="cap-dialog-body">
{body}
{stake && stake.length > 0 ? (
<ul>
{stake.map((item) => (
<li key={item}>
<code>{item}</code>
</li>
))}
</ul>
) : null}
{requireTyped === undefined ? null : (
<div className="cap-confirm-typed">
<label htmlFor={`${formId}-typed`}>
Type <b>{requireTyped}</b> to confirm
</label>
{/* From the constant the action also reads, so a rename cannot split the wire name. */}
<input
id={`${formId}-typed`}
ref={fieldRef}
className="cap-input"
form={formId}
type="text"
name={CONFIRM_FIELD}
value={typed}
onChange={(event) => setTyped(event.target.value)}
inputMode="numeric"
autoComplete="off"
spellCheck={false}
required
/>
</div>
)}
</div>
<div className="cap-dialog-footer" data-align="between">
{cancelHref === undefined ? (
<button type="button" className="cap-btn" onClick={onCancel}>
Cancel
</button>
) : (
<Link to={cancelHref} preventScrollReset className="cap-btn">
Cancel
</Link>
)}
<button
type="submit"
form={formId}
className="cap-btn"
data-variant="danger"
disabled={hydrated && !satisfied}
>
{confirmLabel}
</button>
</div>
</dialog>
);
}