編集中離脱ガード
stimeo--dirty-form
フォームの変更を検知し、未保存のまま離脱しようとしたら警告する(unload+Turbo 遷移)。
stimeo--dirty-form コントローラは connect 時にフォームの値をスナップショットし、値が変わればフォームに data-dirty を付与(基準値へ戻れば解除)、dirty の間だけフルアンロード(beforeunload)と Turbo 遷移(turbo:before-visit)の両方をガードします。Turbo 遷移では cancelable な stimeo--dirty-form:guard を発火し、利用側がキャンセル(または confirmBridge 指定)すると遷移を止め、そうでなければネイティブ confirm(message) にフォールバックします。送信成功(markClean アクション、または turbo:submit-end)で dirty を解除します。状態が変わると stimeo--dirty-form:dirty も発火します。ライブラリは挙動のみで、確認 UI は描画せず(Confirm Bridge と組み合わせ)、値の保存もしません(Persist と組み合わせ)。beforeunload は dirty の間だけ結線し、すべてのリスナは disconnect(Turbo 遷移含む)で解除します。
<%# Dirty-form demo: edit the field and the form gets data-dirty (shown as a badge).
While dirty, leaving is guarded (beforeunload + Turbo turbo:before-visit → a native
confirm). "Save" calls the markClean action to clear the dirty state (this catalog
has no server, so it stands in for a successful save). The library owns the
data-dirty hook and the guards; this demo only styles the badge. %>
<div class="dirty-form-demo">
<form class="dirty-form" data-controller="stimeo--dirty-form">
<label class="dirty-form__field">
<span><%= t("components.dirty_form.demo.label") %></span>
<input type="text" name="title" value="<%= t('components.dirty_form.demo.value') %>">
</label>
<div class="dirty-form__bar">
<span class="dirty-form__badge"><%= t("components.dirty_form.demo.unsaved") %></span>
<button type="button" class="demo-trigger" data-action="stimeo--dirty-form#markClean">
<%= t("components.dirty_form.demo.save") %>
</button>
</div>
</form>
</div>
/*
* Presentation-only styles for the dirty-form demo.
* The library toggles data-dirty on the form; this CSS reveals the "unsaved" badge
* while that hook is present.
*/
.dirty-form-demo {
max-width: 28rem;
}
.dirty-form {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.dirty-form__field {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.dirty-form__field input {
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
font: inherit;
}
.dirty-form__bar {
display: flex;
align-items: center;
gap: 0.75rem;
}
/* The badge appears only while the form is dirty. */
.dirty-form__badge {
display: none;
font-size: 0.8rem;
font-weight: 600;
color: var(--accent);
}
.dirty-form[data-dirty] .dirty-form__badge {
display: inline-block;
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--dirty-form"
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
message
|
Turbo 遷移時のネイティブ confirm に使う確認メッセージ。 |
data-stimeo--dirty-form-message-value |
confirmBridge
|
confirm の代わりに Turbo 遷移を止め、確認を Confirm Bridge に委ねるか。 |
data-stimeo--dirty-form-confirm-bridge-value |
アクション
| 名前 | アクション |
|---|---|
markClean
|
stimeo--dirty-form#markClean |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
dirty
|
dirty 状態が変化したときに発火。detail.dirty を伴う。 |
stimeo--dirty-form:dirty |
guard
|
cancelable。dirty 中の Turbo 遷移で発火。preventDefault で遷移を中断。 |
stimeo--dirty-form:guard |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
data-dirty |
フォーム(ルート) | connect 時の基準値と異なる間に付与。 |