プレビュー中の揮発要素ガード
stimeo--preview-guard
Turbo がキャッシュのプレビューを表示している間だけ、揮発要素を隠す/プレースホルダ化。
stimeo--preview-guard コントローラ(Hotwire 固有)は、Turbo がプレビューを表示している間(html[data-turbo-preview])だけ、残高・通知件数・時刻などの揮発要素をガードし、戻る/復元時に古いキャッシュのスナップショットが一瞬古い値を見せてしまう事故を防ぎます。<html> の data-turbo-preview を MutationObserver で監視し、存在する間は mode="hide" で visibility:hidden(箱は残しレイアウトを乱さない)、 mode="placeholder" で text を placeholder へ差し替え、data-preview-hidden を付与して hide を発火します。プレビューが解除されると保存した値へ復帰し show を発火します。挙動のみで、最新値の取得は通常レンダリングの責務です。connect 時にプレビュー中なら即時で隠し、フォーカスは移動せず、disconnect(Turbo 遷移含む)で observer を切断し要素を復帰します。
- 残高
- ¥123,456
- 更新
- 12:34:56
「戻る/進む」で Turbo がキャッシュを一瞬表示する間だけ、残高はプレースホルダに・時刻は非表示になり、古い値が一瞬見える事故を防ぎます(上のボタンでその約 1.5 秒を擬似発生)。
<%# Preview-guard demo: data-turbo-preview is a Turbo-internal attribute, so this catalog
has no real preview to show. demo.js toggles it on <html> for ~1.5s to reproduce a
preview window — exactly what Turbo does on a back/restore visit. The balance guards in
placeholder mode (swaps to —) and the timestamp in hide mode (becomes invisible, box
kept). The library only watches the attribute and reflects data-preview-hidden. %>
<div class="preview-guard-demo">
<button type="button" class="demo-trigger" data-preview-guard-demo-toggle>
<%= t("components.preview_guard.demo.toggle") %>
</button>
<dl class="preview-guard-demo__list">
<div class="preview-guard-demo__row">
<dt><%= t("components.preview_guard.demo.balance_label") %></dt>
<dd>
<span
class="preview-guard-demo__value"
data-controller="stimeo--preview-guard"
data-stimeo--preview-guard-mode-value="placeholder"
data-stimeo--preview-guard-placeholder-value="—"
><%= t("components.preview_guard.demo.balance") %></span>
</dd>
</div>
<div class="preview-guard-demo__row">
<dt><%= t("components.preview_guard.demo.updated_label") %></dt>
<dd>
<span
class="preview-guard-demo__value"
data-controller="stimeo--preview-guard"
><%= t("components.preview_guard.demo.updated") %></span>
</dd>
</div>
</dl>
<p class="preview-guard-demo__note"><%= t("components.preview_guard.demo.note") %></p>
</div>
/*
* Presentation-only styles for the preview-guard demo. The library swaps the text
* (placeholder mode) or sets visibility:hidden (hide mode) and reflects
* data-preview-hidden; this CSS only lays out the rows and tints a guarded value so the
* placeholder swap is easy to spot.
*/
.preview-guard-demo {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 24rem;
}
.preview-guard-demo__list {
margin: 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.preview-guard-demo__row {
display: flex;
justify-content: space-between;
gap: 1rem;
padding: 0.5rem 0.75rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
}
.preview-guard-demo__row dt {
color: var(--color-text-muted);
}
.preview-guard-demo__row dd {
margin: 0;
font-variant-numeric: tabular-nums;
}
.preview-guard-demo__value[data-preview-hidden] {
color: var(--color-text-subtle);
}
.preview-guard-demo__note {
margin: 0;
color: var(--color-text-muted);
}
// Preview-guard demo (consumer-side JS).
//
// Turbo sets html[data-turbo-preview] itself while a cached preview is on screen; this
// catalog has no Turbo navigation, so the button toggles that attribute for ~1.5s to
// reproduce the preview window. Every preview-guard on the page reacts (just as they
// would during a real preview), guarding their volatile values until it clears.
document.querySelectorAll(".preview-guard-demo").forEach((root) => {
const button = root.querySelector("[data-preview-guard-demo-toggle]");
if (!button) return;
// Idempotent: Turbo can re-run this inline module on navigation; wire each root once.
if (root.dataset.demoWired) return;
root.dataset.demoWired = "1";
button.addEventListener("click", () => {
document.documentElement.setAttribute("data-turbo-preview", "");
setTimeout(() => {
document.documentElement.removeAttribute("data-turbo-preview");
}, 1500);
});
});
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--preview-guard"
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
placeholder
|
placeholder モードでガード中に表示する代替文字(既定は空)。 |
data-stimeo--preview-guard-placeholder-value |
mode
|
hide(visibility:hidden で箱は維持)/ placeholder(text を差し替え)。既定 hide。 |
data-stimeo--preview-guard-mode-value |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
hide
|
プレビュー突入で要素をガードしたとき発火。 | stimeo--preview-guard:hide |
show
|
プレビュー解除で要素を復帰したとき発火。 | stimeo--preview-guard:show |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
data-preview-hidden |
コントローラ要素 | プレビュー中にガードしている間付与(true)。 |