確認ダイアログ橋渡し
stimeo--confirm
Turbo のネイティブ confirm() をアクセシブルな警告ダイアログに置き換える。
stimeo--confirm コントローラは、Turbo が data-turbo-confirm に使うネイティブ window.confirm() をアクセシブルな警告ダイアログ(WAI-ARIA APG Alert Dialog パターン)に置き換えます。共有 FocusTrap を再利用するため、トラップ・フォーカス復帰・Esc 取消を再実装しません。connect で Turbo.config.forms.confirm を Promise を返すメソッドに差し替え、disconnect(Turbo 遷移含む)で原状復帰するので、多重登録やリークが起きません。request アクションで任意のリンク/ボタンのクリックを横取りし、承認時のみ元の送信/遷移を続行することもできます。初期フォーカスは安全側(取消)、Esc で取消します。ダイアログが無い場合はネイティブ confirm にフォールバックします。 stimeo--confirm:open / :resolve を発火します。ライブラリは挙動のみを提供し、ダイアログのマークアップと見た目はこの Playground が持ちます。
アカウントを削除しますか?
キーボード操作
| キー | 動作 |
|---|---|
| Esc | 取消(false で解決)してダイアログを閉じる。 |
| Tab / Shift+Tab | ダイアログ内でフォーカスを循環させる(フォーカストラップ)。 |
| Enter | フォーカス中の承認/取消ボタンを実行する。 |
<%# Markup for the confirm-bridge demo.
The controller intercepts the danger button (request action), shows the accessible
alert dialog with the message, and only continues the form submit when confirmed.
The dialog reuses the shared FocusTrap (trap, restore, Escape=cancel). This
Playground has no backend, so demo.js cancels the real submit and reports the
outcome from the resolve event. %>
<div class="confirm-demo" data-controller="stimeo--confirm"
data-stimeo--confirm-confirm-label-value="<%= t("components.confirm.demo.confirm_label") %>"
data-stimeo--confirm-cancel-label-value="<%= t("components.confirm.demo.cancel_label") %>">
<form class="confirm-demo__form" action="#" method="post" data-confirm-demo="form">
<button type="submit" class="demo-trigger demo-trigger--danger"
data-action="click->stimeo--confirm#request"
data-stimeo--confirm-message-param="<%= t("components.confirm.demo.message") %>">
<%= t("components.confirm.demo.trigger") %>
</button>
</form>
<p class="confirm-demo__result" data-confirm-demo="result" role="status" aria-live="polite"
data-confirmed="<%= t("components.confirm.demo.confirmed") %>"
data-cancelled="<%= t("components.confirm.demo.cancelled") %>"></p>
<div class="confirm-demo__backdrop" data-stimeo--confirm-target="dialog"
role="alertdialog" aria-modal="true" aria-labelledby="confirm-title"
aria-describedby="confirm-message" hidden>
<div class="confirm-demo__dialog">
<h2 id="confirm-title" data-stimeo--confirm-target="title">
<%= t("components.confirm.demo.title") %>
</h2>
<p id="confirm-message" data-stimeo--confirm-target="message"></p>
<div class="confirm-demo__actions">
<button type="button" class="demo-trigger" data-stimeo--confirm-target="cancel"
data-action="click->stimeo--confirm#cancel"></button>
<button type="button" class="demo-trigger demo-trigger--danger"
data-stimeo--confirm-target="confirm"
data-action="click->stimeo--confirm#confirm"></button>
</div>
</div>
</div>
</div>
/*
* Presentation-only styles for the confirm-bridge demo.
* The library only toggles the dialog's hidden attribute and manages focus; this
* CSS owns the modal overlay, the dialog box, and the danger-button affordance.
*/
.confirm-demo {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 32rem;
}
.demo-trigger--danger {
border-color: var(--danger-500);
color: var(--color-accent);
}
.confirm-demo__result {
margin: 0;
min-height: 1.25rem;
font-size: 0.95rem;
color: var(--color-text-muted);
}
.confirm-demo__backdrop {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgb(15 23 42 / 0.5);
z-index: 50;
}
.confirm-demo__backdrop[hidden] {
display: none;
}
.confirm-demo__dialog {
width: min(28rem, calc(100vw - 2rem));
padding: 1.25rem;
border-radius: 0.75rem;
background: var(--surface, var(--surface-card));
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.2);
}
.confirm-demo__dialog h2 {
margin: 0 0 0.5rem;
font-size: 1.1rem;
}
.confirm-demo__actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 1rem;
}
// Consumer-side JS for the confirm-bridge demo (demo-only).
// On confirm, the controller continues the original action by submitting the form.
// This Playground has no backend, so we cancel the real submit and instead report
// the outcome (confirmed / cancelled) from the controller's resolve event.
const form = document.querySelector("[data-confirm-demo='form']");
const result = document.querySelector("[data-confirm-demo='result']");
if (form) {
form.addEventListener("submit", (event) => event.preventDefault());
}
document.addEventListener("stimeo--confirm:resolve", (event) => {
if (!result) return;
result.textContent = event.detail.confirmed
? result.dataset.confirmed
: result.dataset.cancelled;
});
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--confirm"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
dialog
必須
|
トラップ・表示の対象となる警告ダイアログ要素(role="alertdialog")。 |
data-stimeo--confirm-target="dialog" |
title
|
ダイアログ名となる任意の見出し要素。 | data-stimeo--confirm-target="title" |
message
|
確認メッセージを書き込む要素。 | data-stimeo--confirm-target="message" |
confirm
|
承認ボタン。文言は confirmLabel から設定する。 |
data-stimeo--confirm-target="confirm" |
cancel
|
取消ボタン。文言は cancelLabel から設定する。 |
data-stimeo--confirm-target="cancel" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
confirmLabel
|
承認ボタンの文言(既定 OK)。 |
data-stimeo--confirm-confirm-label-value |
cancelLabel
|
取消ボタンの文言(既定 Cancel)。 |
data-stimeo--confirm-cancel-label-value |
initialFocus
|
初期フォーカスのボタン: cancel か confirm(既定 cancel)。 |
data-stimeo--confirm-initial-focus-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
confirm
|
保留中の確認を true で解決して閉じる。 |
stimeo--confirm#confirm |
cancel
|
保留中の確認を false で解決して閉じる。 |
stimeo--confirm#cancel |
request
|
クリックを横取りし、承認後に元のアクションを続行する。 | stimeo--confirm#request |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
open
|
ダイアログを開いたとき発火。detail に message を伴う。 |
stimeo--confirm:open |
resolve
|
確定したとき発火。detail に confirmed(真偽値)を伴う。 |
stimeo--confirm:resolve |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
hidden |
ダイアログ | 確認ダイアログの表示/非表示を切り替える。 |