コンテンツにスキップ

削除の取り消し

undo-delete は、頻繁な破壊的アクションのための公認 **「確認の代わりに取り消し」**パターンです: ダイアログなし — 削除は 即座に実行され、サーバはアイテムを猶予期間の間だけ復元可能に保ち、 結果のトーストが Undo ボタンを運びます。押せば行が元いた場所へ 正確に戻ります。 confirm-action の 対になるレシピで、出荷済みの部品だけから 新規 JavaScript ゼロで 構成され、マークアップのバージョニングポリシーの 下で安定しています。

必要なのは installToast()(自動初期化の ./behaviors バンドルに同梱)だけです。

別名: ソフトデリート、論理削除、取り消しトースト。

Delete をクリック — 確認はありません — そしてトーストの Undo を 10 秒以内に押すと行が戻ります。サーバは 30 秒の猶予期間を保持します (トゥームストーンの復元 URL にステートレスに織り込まれています)。 期限切れ後の復元は「Too late」のエラートーストとして返ってきます。 エンドポイントは api/recipes/undo-delete/ 配下にあり、 Reset rows が定型の行を再取得します。

ItemActions

アイテムごとにボタン 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>

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 と同じです。

リクエストレスポンス
DELETE /items/42200 + 隠れたトゥームストーン(ボタンの 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 の分岐)。取り消し自体はトーストが運ぶため、正直に「利用不可」です
confirm-actionundo-delete
向くケースまれ・壊滅的・戻しにくい頻繁・復元可能
コスト毎回のダイアログサーバ側の猶予窓
失敗モード確認疲れ猶予切れ(エラートースト)

アクションごとにどちらか 1 つを選んでください — 両方重ねると両方の コストを払い、何も得られません。

データグリッドの中では、トゥームストーンは行としてスワップインし、 tbody のオブザーバーが削除と復元の両方で roles・オフセット・選択を 導出し直します。トゥームストーンは不活性な残骸で、フルの再描画 (data-region の更新、 ページネーション)があれば刈り取られます。JavaScript なしの環境では、 フォームフォールバックの削除(method="post" + 303mutating-form の 分岐)を残してください — 取り消し自体はトーストが運ぶものなので、 正直に「利用不可」です。非 JS の削除に保護が必要なら、サーバサイドの 確認ページを与えてください。

ここでの主張 — 位置を保存する復元、2 つの取り消し待ちにまたがる ペアリングキーの分離、失効パス — は、実 htmx のブラウザテスト (test-browser/undo-delete.spec.mjs)で固定されています。

  • 取り消しトーストは variant: "info"role="status"(polite)、 猶予切れの失敗は variant: "error"role="alert" です。標準の トーストセマンティクスの ままです。
  • Undo ボタンはトースト内の本物の <button> で、トーストが見えている 間はキーボードで到達できます。猶予窓はトーストより十分長く取って ください — ボタンを逃したキーボードユーザーが失うものが、単に 遅かった場合に失うもの以上にならないように。
  • トゥームストーンは hidden で、決してフォーカスされません。復元は フォーカスを奪わずに行をスワップバックします。
  • Delete ボタンのリクエスト中の disabled (data-hx-disabled-elt="this")はネイティブ属性で、支援技術が 報告します。