警告ダイアログ
stimeo--alert-dialog
割り込みの確認モーダル。バックドロップでは閉じず、confirm/cancel を発火する。
stimeo--alert-dialog コントローラは WAI-ARIA の Alert Dialog パターンを実装する。フォーカストラップ・背景スクロールロック・背景の inert・フォーカス復帰というモーダルの振る舞いは dialog と同じで、割り込み確認のための違いが 2 点ある。バックドロップクリックでは閉じないこと、確定/取消の明示アクションがあり stimeo--alert-dialog:confirm と stimeo--alert-dialog:cancel(reason は user / escape)を発火することだ。開くと最も破壊的でない操作(既定では initialFocus ターゲット、無ければ最初のフォーカス可能要素)へフォーカスを移し、Escape は取消として閉じてトリガーへ復帰する。利用側は確認文言と「確定時に何をするか」だけを書けばよい。
この項目を削除しますか?
この操作は取り消せません。
キーボード操作
| キー | 動作 |
|---|---|
| Esc | 取消として閉じ、フォーカスをトリガーへ戻す。 |
| Tab / Shift+Tab | ダイアログ内でフォーカスを循環(トラップ)。 |
<%# Markup for the alert-dialog demo.
role="alertdialog" conveys urgency and interrupts until an explicit confirm/cancel
response is given. Unlike a plain dialog it does not close on a backdrop click.
The library handles focus move, focus trap, Esc (cancel), background scroll lock,
background inert, returning focus to the trigger, and firing confirm / cancel
events. Styling lives in demo.css. %>
<div class="alert-dialog" data-controller="stimeo--alert-dialog">
<button
type="button"
class="alert-dialog__trigger"
data-stimeo--alert-dialog-target="trigger"
data-action="click->stimeo--alert-dialog#open">
<%= t("components.alert_dialog.demo.open_button") %>
</button>
<%# A live region that announces the latest response; demo.js reflects confirm / cancel.
For bilingual support the copy is resolved with I18n server-side and passed via
data-* attributes, which demo.js only reads (never hardcoded in JS). %>
<p
class="alert-dialog__status"
role="status"
data-alert-dialog-status
data-confirm-message="<%= t("components.alert_dialog.demo.confirmed_message") %>"
data-cancel-message-user="<%= t(
"components.alert_dialog.demo.cancelled_message",
reason: t("components.alert_dialog.demo.cancel_reason_user")
) %>"
data-cancel-message-escape="<%= t(
"components.alert_dialog.demo.cancelled_message",
reason: t("components.alert_dialog.demo.cancel_reason_escape")
) %>"
hidden></p>
<%# The role="alertdialog" + aria-modal region. Starts hidden (closed).
aria-labelledby points to the title, aria-describedby to the body message. %>
<div
class="alert-dialog__backdrop"
role="alertdialog"
aria-modal="true"
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-desc"
data-stimeo--alert-dialog-target="dialog"
hidden>
<div class="alert-dialog__panel">
<h3 id="alert-dialog-title" class="alert-dialog__title">
<%= t("components.alert_dialog.demo.confirm_title") %>
</h3>
<p id="alert-dialog-desc"><%= t("components.alert_dialog.demo.confirm_desc") %></p>
<div class="alert-dialog__actions">
<%# Default focus goes to the least-destructive action: Cancel. %>
<button
type="button"
data-stimeo--alert-dialog-target="initialFocus"
data-action="click->stimeo--alert-dialog#cancel">
<%= t("components.alert_dialog.demo.cancel") %>
</button>
<button
type="button"
class="alert-dialog__danger"
data-action="click->stimeo--alert-dialog#confirm">
<%= t("components.alert_dialog.demo.confirm") %>
</button>
</div>
</div>
</div>
</div>
/*
* Presentation-only styles for the alert-dialog demo.
* Stimeo UI itself ships behavior only (no CSS), so the styling lives here.
* Open/close is the library toggling the dialog element's hidden attribute.
*/
.alert-dialog__trigger,
.alert-dialog__danger {
padding: 0.5rem 1rem;
font-size: 1rem;
cursor: pointer;
border: 1px solid var(--border-interactive);
border-radius: 0.375rem;
background: var(--surface-card);
}
/* The confirm button is a destructive action, so show it in a warning color (visual only). */
.alert-dialog__danger {
border-color: var(--danger-700);
background: var(--danger-700);
color: var(--white);
}
.alert-dialog__status {
margin: 0.75rem 0 0;
font-size: 0.875rem;
color: var(--color-text-muted);
}
.alert-dialog__status[hidden] {
display: none;
}
/* Use the role="alertdialog" region as a centered backdrop. */
.alert-dialog__backdrop {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
background: rgb(0 0 0 / 0.5);
z-index: 50;
}
/* Setting display explicitly overrides the hidden attribute's default display:none, so
re-declare display:none while closed to honor the library's hidden control. */
.alert-dialog__backdrop[hidden] {
display: none;
}
.alert-dialog__panel {
width: min(28rem, 100%);
padding: 1.5rem;
background: var(--surface-card);
border-radius: 0.5rem;
box-shadow: 0 20px 50px rgb(15 23 42 / 0.25);
}
.alert-dialog__title {
margin: 0 0 0.5rem;
font-size: 1.125rem;
}
.alert-dialog__actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 1.5rem;
}
.alert-dialog__actions button {
padding: 0.5rem 1rem;
font: inherit;
cursor: pointer;
border-radius: 0.375rem;
border: 1px solid var(--border-strong);
background: var(--surface-card);
}
.alert-dialog__actions .alert-dialog__danger {
border-color: var(--danger-700);
background: var(--danger-700);
color: var(--white);
}
// Consumer-side JS for the alert-dialog demo (optional).
// The library fires stimeo--alert-dialog:confirm on confirm and
// stimeo--alert-dialog:cancel (with detail { reason: "user" | "escape" }) on
// cancel. The consumer subscribes to decide "what to do on confirm". Here we only
// reflect the latest response in the status live region (the real work is the
// subscriber's responsibility — this is just an example).
// For bilingual support the copy is resolved server-side via I18n and read from the
// status element's data-* attributes (never hardcoded in JS).
document.addEventListener("stimeo--alert-dialog:confirm", () => {
const status = document.querySelector("[data-alert-dialog-status]");
if (status) showStatus(status.dataset.confirmMessage);
});
document.addEventListener("stimeo--alert-dialog:cancel", (event) => {
const status = document.querySelector("[data-alert-dialog-status]");
if (!status) return;
// reason is "escape" (cancelled with the Esc key) or "user" (the Cancel button).
const message =
event.detail.reason === "escape"
? status.dataset.cancelMessageEscape
: status.dataset.cancelMessageUser;
showStatus(message);
});
// Show the latest response message in the status element.
function showStatus(message) {
const status = document.querySelector("[data-alert-dialog-status]");
if (!status) return;
status.textContent = message;
status.hidden = false;
}
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--alert-dialog"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
trigger
必須
|
アラートダイアログを開くボタン。閉じるとフォーカスがここへ戻る。 | data-stimeo--alert-dialog-target="trigger" |
dialog
必須
|
role="alertdialog" のモーダル要素。hidden で表示制御し、メッセージと確認/キャンセルボタンを含む。 |
data-stimeo--alert-dialog-target="dialog" |
initialFocus
|
開いたときにフォーカスする要素。慣習上もっとも破壊的でない操作(例: キャンセル)。 | data-stimeo--alert-dialog-target="initialFocus" |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
cancel
|
ダイアログを閉じ、reason user で cancel を発火する。 |
stimeo--alert-dialog#cancel |
confirm
|
ダイアログを閉じ、confirm を発火する。 |
stimeo--alert-dialog#confirm |
open
|
ダイアログを開き、initialFocus へフォーカスを移動して Tab を閉じ込め、背景スクロールをロックする。背景クリックでは閉じない。 | stimeo--alert-dialog#open |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
cancel
|
キャンセル時に発火。detail.reason は user(キャンセル操作)か escape(Escape キー)。 |
stimeo--alert-dialog:cancel |
confirm
|
ユーザーが操作を確認したときに発火する。 | stimeo--alert-dialog:confirm |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
hidden |
ダイアログ | 閉じているときは付与、開くと除去。 |
inert |
背景の兄弟要素 | 開いている間、ダイアログ外の要素へ付与。 |
overflow |
document.body | 開いている間 hidden(スクロールロック)。 |