共有トランジション基盤
stimeo--transition
表示/非表示の enter/leave クラスを段階適用し、transitionend か安全タイマで完了検知。
stimeo--transition コントローラは表示/非表示アニメーションの共有土台で、Headless UI Transition / Alpine x-transition に相当します。enter() は要素を表示し enter と enterFrom クラスを適用、次フレームで enterFrom → enterTo にスワップして CSS トランジションを走らせ、transitionend(または timeout か計算済み duration から導く安全タイマ)で entered に整定します。leave() は対称に動作し hidden を付け直し、toggle() は現在の方向を反転します。要素は data-transition-state(entering / entered / leaving / left)を持ち、完了で entered / left を発火します。挙動のみで、アニメーション自体は利用側 CSS の責務であり、本パーツは stage クラスを「いつ」適用するかだけを制御します。prefers-reduced-motion 下では即時切替(段階適用なし)、割り込み呼び出しは進行中のトランジションを取り消し、connect は半端な stage クラスを除去して表示状態へ整合します。 transitionend リスナ・rAF・安全タイマは disconnect(Turbo 遷移含む)で解放します。
<%# Transition demo: the panel is the controller element; enter/leave class values point
at the demo.css classes that define the timing and start/end states. The toggle button
lives outside the (hideable) panel, so it invokes the controller's toggle action through
the playground's exposed Stimulus app (window.Stimulus) in demo.js. The library only
stages the classes and reflects data-transition-state / hidden; demo.css owns the look. %>
<div class="transition-demo">
<button
type="button"
class="demo-trigger"
data-transition-demo-toggle
aria-expanded="false"
aria-controls="transition-demo-panel">
<%= t("components.transition.demo.toggle") %>
</button>
<div
id="transition-demo-panel"
class="transition-demo__panel"
data-controller="stimeo--transition"
data-stimeo--transition-enter-value="te-enter"
data-stimeo--transition-enter-from-value="te-from"
data-stimeo--transition-enter-to-value="te-to"
data-stimeo--transition-leave-value="te-leave"
data-stimeo--transition-leave-from-value="te-to"
data-stimeo--transition-leave-to-value="te-from"
hidden>
<%= t("components.transition.demo.panel") %>
</div>
</div>
/*
* Presentation-only styles for the transition demo. The library stages the te-* classes
* (named by the controller's enter/leave values) and toggles hidden; these classes define
* the timing and the start/end states. After a transition completes the controller strips
* the stage classes, so the panel rests at the te-to (visible) state by default.
*/
.transition-demo {
display: flex;
flex-direction: column;
gap: 0.75rem;
align-items: flex-start;
}
.transition-demo__panel {
max-width: 22rem;
padding: 1rem;
border: 1px solid var(--border);
border-radius: 0.5rem;
background: var(--surface-subtle);
}
/* Stage classes referenced by the controller's enter/leave value attributes. */
.te-enter {
transition:
opacity 0.3s ease-out,
transform 0.3s ease-out;
}
.te-leave {
transition:
opacity 0.2s ease-in,
transform 0.2s ease-in;
}
.te-from {
opacity: 0;
transform: translateY(-0.5rem);
}
.te-to {
opacity: 1;
transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
.te-enter,
.te-leave {
transition: none;
}
}
// Transition demo (consumer-side JS).
//
// The toggle button sits outside the hideable panel, so a plain data-action can't reach
// the panel's controller. The playground exposes its Stimulus application as
// window.Stimulus, so we fetch the controller instance and call its toggle() action — and
// mirror the open state onto aria-expanded from the controller's entered/left events.
document.querySelectorAll(".transition-demo").forEach((root) => {
const panel = root.querySelector('[data-controller~="stimeo--transition"]');
const button = root.querySelector("[data-transition-demo-toggle]");
if (!panel || !button) return;
// Idempotent: Turbo can re-run this inline module on navigation; wire each root once so a
// single click does not toggle twice (which flashes the panel open then shut).
if (root.dataset.demoWired) return;
root.dataset.demoWired = "1";
button.addEventListener("click", () => {
window.Stimulus?.getControllerForElementAndIdentifier(panel, "stimeo--transition")?.toggle();
});
panel.addEventListener("stimeo--transition:entered", () => {
button.setAttribute("aria-expanded", "true");
});
panel.addEventListener("stimeo--transition:left", () => {
button.setAttribute("aria-expanded", "false");
});
});
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--transition"
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
enter
|
enter 全体に適用するクラス(タイミング/イージング等)。 | data-stimeo--transition-enter-value |
enterFrom
|
enter 開始状態のクラス(最初に適用し次フレームで除去)。 | data-stimeo--transition-enter-from-value |
enterTo
|
enter 終了状態のクラス(次フレームで適用しトランジションを走らせる)。 | data-stimeo--transition-enter-to-value |
leave
|
leave 全体に適用するクラス。 | data-stimeo--transition-leave-value |
leaveFrom
|
leave 開始状態のクラス。 | data-stimeo--transition-leave-from-value |
leaveTo
|
leave 終了状態のクラス。 | data-stimeo--transition-leave-to-value |
timeout
|
完了検知の安全タイマ(ミリ秒、0=計算済み duration から自動)。 | data-stimeo--transition-timeout-value |
アクション
| 名前 | アクション |
|---|---|
enter
|
stimeo--transition#enter |
leave
|
stimeo--transition#leave |
toggle
|
stimeo--transition#toggle |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
entered
|
enter トランジション完了時に発火。 | stimeo--transition:entered |
left
|
leave トランジション完了時に発火。 | stimeo--transition:left |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
data-transition-state |
コントローラ要素 | entering / entered / leaving / left。 |
hidden |
コントローラ要素 | enter 開始で除去、leave 完了で付与。 |