スピナー
stimeo--spinner
ライブリージョン・aria-busy・ちらつき防止タイマーで読み込み表示を切り替える。
stimeo--spinner コントローラはライブリージョン+aria-busy の慣行に従います。start / stop で role="status" の indicator を切り替え、indicator はテキストを持つ(アイコンのみに依存しない)ことでスクリーンリーダーが読み込みを通知でき、制御対象領域には aria-busy を反映します。2 つのタイマーがちらつきを抑えます。delay は短時間で完了する処理ではスピナーを出さず、minDuration は一度出したら知覚できる程度は表示を維持します。stimeo--spinner:show / :hide を発火し、両タイマーは disconnect(Turbo 遷移含む)で破棄されます。ライブラリは挙動のみを提供し、視覚的スピナーはこの Playground が持ちます。
「読み込み開始」でローディング状態に入り、「読み込み停止」で終了します。ちらつき防止のため、スピナーは約 150ms 遅れて現れ、いったん表示されると最低約 600ms は維持されます(このため「読み込み停止」の直後はスピナーがわずかに残ってから消えます。これは正しい挙動です)。
<%# Markup for the spinner (loading indicator) demo.
The indicator holds text in a role="status" live region, with the visual spinner
placed alongside as aria-hidden="true". The region reflects aria-busy. A show delay
and a minimum display time suppress flicker. The Start/Stop buttons call the
controller's methods directly. %>
<div
class="spinner-demo"
data-controller="stimeo--spinner"
data-stimeo--spinner-delay-value="150"
data-stimeo--spinner-min-duration-value="600">
<div class="spinner-demo__controls">
<button
class="demo-trigger"
type="button"
data-action="click->stimeo--spinner#start">
<%= t("components.spinner.demo.start") %>
</button>
<button
class="demo-trigger"
type="button"
data-action="click->stimeo--spinner#stop">
<%= t("components.spinner.demo.stop") %>
</button>
</div>
<p class="spinner-demo__hint"><%= t("components.spinner.demo.hint") %></p>
<div
class="spinner"
role="status"
aria-live="polite"
hidden
data-stimeo--spinner-target="indicator">
<span class="spinner__icon" aria-hidden="true"></span>
<span data-stimeo--spinner-target="message"><%= t("components.spinner.demo.loading") %></span>
</div>
<div
class="spinner-demo__region"
aria-busy="false"
data-stimeo--spinner-target="region">
<%= t("components.spinner.demo.content") %>
</div>
</div>
/*
* Presentation-only styles for the spinner demo.
* This CSS owns the visual spinner's rotation; the library only toggles
* hidden / aria-busy / data-state (idle / pending / loading).
*/
.spinner-demo {
display: flex;
flex-direction: column;
gap: 1rem;
max-width: 32rem;
}
.spinner-demo__controls {
display: flex;
gap: 0.5rem;
}
/* Explanatory caption: clarifies the intentional delay / minimum-display timing. */
.spinner-demo__hint {
margin: 0;
font-size: 0.85rem;
line-height: 1.5;
color: var(--color-text-muted);
}
.spinner {
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.95rem;
color: var(--fg);
}
.spinner__icon {
width: 1.1rem;
height: 1.1rem;
border: 2px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spinner-rotate 0.7s linear infinite;
}
@keyframes spinner-rotate {
to {
transform: rotate(360deg);
}
}
.spinner-demo__region {
min-height: 3rem;
padding: 0.75rem 1rem;
border: 1px dashed var(--border);
border-radius: 0.375rem;
color: var(--fg);
}
/* Dim the target region while loading so the visual matches aria-busy. */
.spinner-demo__region[aria-busy="true"] {
opacity: 0.5;
}
@media (prefers-reduced-motion: reduce) {
.spinner__icon {
animation: none;
}
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--spinner"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
indicator
|
スピナーとして表示・非表示する、テキストを持つ role="status" ライブリージョン。 |
data-stimeo--spinner-target="indicator" |
region
|
aria-busy で読み込み状態を反映する被制御リージョン。 |
data-stimeo--spinner-target="region" |
message
|
インジケーター内の、スクリーンリーダーが読み上げるテキスト要素。 | data-stimeo--spinner-target="message" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
delay
|
開始後にスピナーを抑止するミリ秒。高速処理では表示しない(既定 0)。 | data-stimeo--spinner-delay-value |
minDuration
|
表示後のスピナーをちらつかせない最小表示ミリ秒(既定 0)。 | data-stimeo--spinner-min-duration-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
start
|
読み込みを開始し、busy を立て delay 後にスピナーを表示する。 | stimeo--spinner#start |
stop
|
読み込みを終了し、busy を下げ minDuration 後にスピナーを隠す。 |
stimeo--spinner#stop |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
hide
|
スピナーが隠れ idle に戻ったときに発火。 | stimeo--spinner:hide |
show
|
スピナーが表示されたときに発火。 | stimeo--spinner:show |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
hidden |
indicator | 非表示中は付与し、表示時に外す。 |
aria-busy |
region | 読み込み中は "true"。 |
data-state |
ルート要素 | "idle" / "pending"(遅延待ち)/ "loading"。 |