削除の取り消し
undo-delete は、頻繁な破壊的アクションのための公認
**「確認の代わりに取り消し」**パターンです: ダイアログなし — 削除は
即座に実行され、サーバはアイテムを猶予期間の間だけ復元可能に保ち、
結果のトーストが Undo ボタンを運びます。押せば行が元いた場所へ
正確に戻ります。
confirm-action の
対になるレシピで、出荷済みの部品だけから 新規 JavaScript ゼロで
構成され、マークアップのバージョニングポリシーの
下で安定しています。
必要なのは installToast()(自動初期化の
./behaviors
バンドルに同梱)だけです。
別名: ソフトデリート、論理削除、取り消しトースト。
Delete をクリック — 確認はありません — そしてトーストの Undo を
10 秒以内に押すと行が戻ります。サーバは 30 秒の猶予期間を保持します
(トゥームストーンの復元 URL にステートレスに織り込まれています)。
期限切れ後の復元は「Too late」のエラートーストとして返ってきます。
エンドポイントは api/recipes/undo-delete/ 配下にあり、
Reset rows が定型の行を再取得します。
| Item | Actions |
|---|
マークアップ
Section titled “マークアップ”アイテムごとにボタン 1 つ、
request-action の
形 — 意図的に data-hc-confirm なしです:
<tr id="item-42"> <td>Anvil</td> <td> <button class="hc-button" data-size="sm" type="button" data-hx-delete="/items/42" data-hx-target="closest tr" data-hx-swap="outerHTML" data-hx-disabled-elt="this">Delete</button> </td></tr>トゥームストーン
Section titled “トゥームストーン”DELETE のレスポンスは、行を隠れたトゥームストーンに置き換えます。 これは DOM のスロットを保持し、復元の配線を運び、取り消しトーストを 発火します:
<tr id="item-42" hidden data-hx-post="/items/42/restore" data-hx-trigger="item-42:restore from:body" data-hx-swap="outerHTML"></tr>HX-Trigger: {"hc:toast":{"id":"undo-item-42","message":"\"Anvil\" deleted", "variant":"info","duration":10000, "action":{"label":"Undo","event":"item-42:restore"}}}ペアリングキー(item-42:restore)は、サーバが生成する 1 つの
文字列がちょうど 2 か所に現れるものです: トーストの action.event と
トゥームストーンのトリガー。Undo をクリックすると、トーストがその
バブリングイベントを発火し(出荷済み
トーストのアクション
ボタン)、トゥームストーンが from:body 経由でそれを聞いて復元を
POST し、サーバが元の行を返し、それが同じスロットへスワップ
バックします — 位置は保存され、複数の取り消し待ちが交差することは
ありません。
復元レスポンスはトーストの id を再利用するため、取り消しトーストは
その場で「restored」に更新されます。テーブルでは <tr hidden>、
リストでは <li hidden>、カードグリッドでは <div hidden> を使って
ください。
猶予期間: サーバが真実、トーストはヒント
Section titled “猶予期間: サーバが真実、トーストはヒント”サーバは自身の時計でハードデリートします(推奨: 猶予 ≥ トーストの 表示時間。例: トースト 10 秒 / 猶予 30〜60 秒)。トーストの解散や失効は 何も確定しません。失効後の復元は 200-with-truth の原則に従います: ボディ = 再びトゥームストーン、加えてエラートースト(「完全に削除 されました」)— ステータスコードの振り付けはなし。 datagrid-bulk-actions と同じです。
サーバレスポンス契約
Section titled “サーバレスポンス契約”| リクエスト | レスポンス |
|---|---|
DELETE /items/42 | 200 + 隠れたトゥームストーン(ボタンの outerHTML スワップで行を置き換え)+ HX-Trigger の取り消しトースト — action.event がペアリングキーを運びます |
POST /items/42/restore(猶予内) | 200 + 元の行 — 同じスロットへスワップバックします。トーストの id の再利用で取り消しトーストはその場で更新されます。復元は冪等です |
POST /items/42/restore(猶予切れ) | 200 + 再びトゥームストーン + エラートースト(「完全に削除されました」)— 200-with-truth、ステータスコードの振り付けなし |
| 非 JS の削除 | フォームフォールバックを残してください: method="post" + 303(mutating-form の分岐)。取り消し自体はトーストが運ぶため、正直に「利用不可」です |
取り消しか、確認か
Section titled “取り消しか、確認か”| confirm-action | undo-delete | |
|---|---|---|
| 向くケース | まれ・壊滅的・戻しにくい | 頻繁・復元可能 |
| コスト | 毎回のダイアログ | サーバ側の猶予窓 |
| 失敗モード | 確認疲れ | 猶予切れ(エラートースト) |
アクションごとにどちらか 1 つを選んでください — 両方重ねると両方の コストを払い、何も得られません。
合成とデグラデーション
Section titled “合成とデグラデーション”データグリッドの中では、トゥームストーンは行としてスワップインし、
tbody のオブザーバーが削除と復元の両方で roles・オフセット・選択を
導出し直します。トゥームストーンは不活性な残骸で、フルの再描画
(data-region の更新、
ページネーション)があれば刈り取られます。JavaScript なしの環境では、
フォームフォールバックの削除(method="post" + 303、
mutating-form の
分岐)を残してください — 取り消し自体はトーストが運ぶものなので、
正直に「利用不可」です。非 JS の削除に保護が必要なら、サーバサイドの
確認ページを与えてください。
ここでの主張 — 位置を保存する復元、2 つの取り消し待ちにまたがる
ペアリングキーの分離、失効パス — は、実 htmx のブラウザテスト
(test-browser/undo-delete.spec.mjs)で固定されています。
アクセシビリティ
Section titled “アクセシビリティ”- 取り消しトーストは
variant: "info"→role="status"(polite)、 猶予切れの失敗はvariant: "error"→role="alert"です。標準の トーストセマンティクスの ままです。 - Undo ボタンはトースト内の本物の
<button>で、トーストが見えている 間はキーボードで到達できます。猶予窓はトーストより十分長く取って ください — ボタンを逃したキーボードユーザーが失うものが、単に 遅かった場合に失うもの以上にならないように。 - トゥームストーンは
hiddenで、決してフォーカスされません。復元は フォーカスを奪わずに行をスワップバックします。 - Delete ボタンのリクエスト中の
disabled(data-hx-disabled-elt="this")はネイティブ属性で、支援技術が 報告します。
- 確認アクションレシピ — 対になるレシピ。上の判断表。
- トーストレシピ — これが乗っているアクションボタンと id による更新。
- リクエストアクションレシピ — ボタンの形。
- データグリッド一括操作レシピ — 200-with-truth の原則。