スライダー
stimeo--slider
つまみ 1 つのスライダー。キーボードでもドラッグでも動かせます。
つまみを 1 つ持つスライダーです。今いくつなのかは読み上げに伝わります。位置は割合として CSS から読めるので、つまみと軌道の描き方は自由です。最小値、最大値、刻み幅、最初の値はマークアップで指定します。キーボードでもドラッグでも動かせます。
実行中
キーボード操作
| キー | 動作 |
|---|---|
| → / ↑ | 値を 1 ステップ増やす。logicalTrack を立てた RTL では左右が入れ替わる。 |
| ← / ↓ | 値を 1 ステップ減らす。logicalTrack を立てた RTL では左右が入れ替わる。 |
| PageUp / PageDown | 値を 10 ステップ分動かす。 |
| Home / End | 最小値 / 最大値へジャンプする。 |
<%# Markup for the slider (APG Slider / single thumb) demo.
stimeo--slider provides aria-valuemin/max/now plus keyboard and drag operation, and
exposes the thumb position fraction (0..1) as the root CSS variable
--stimeo--slider-fraction. The thumb's actual placement is the Playground's CSS
using that variable. %>
<div
class="slider"
data-controller="stimeo--slider"
data-stimeo--slider-min-value="0"
data-stimeo--slider-max-value="100"
data-stimeo--slider-step-value="1"
data-stimeo--slider-value-value="40">
<%# The hidden field carries the value into the form; it is optional. %>
<input type="hidden" name="volume" data-stimeo--slider-target="field" />
<div class="slider__track" data-stimeo--slider-target="track"
data-action="pointerdown->stimeo--slider#onPointerDown">
<div class="slider__range" aria-hidden="true"></div>
<div class="slider__thumb" role="slider" tabindex="0"
aria-label="<%= t("components.slider.demo.label") %>"
data-stimeo--slider-target="thumb"
data-action="keydown->stimeo--slider#onKeydown"></div>
</div>
</div>
<%# Readout: what the component just reported. Purely visual — the component
already announces what a screen reader needs, so this line is aria-hidden to
keep it from being read twice. The value is a code fragment (API identifiers
and values), so only the label is translated. %>
<p class="demo-readout" aria-hidden="true">
<span><%= t("catalog.readout.field") %></span>
<span class="demo-readout__value" data-readout
data-empty-text="<%= t("catalog.readout.empty") %>">—</span>
</p>
/*
* Presentation-only styles for the slider demo.
* The thumb position is computed from the CSS variable the library exposes on the
* root: --stimeo--slider-fraction (0..1).
*/
.slider {
width: min(20rem, 100%);
padding: 0.75rem 0.625rem;
}
.slider__track {
position: relative;
height: 0.375rem;
border-radius: 999px;
background: var(--border);
}
/* Fill from 0 to the current value (the fraction) with the accent color. */
.slider__range {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: calc(var(--stimeo--slider-fraction, 0) * 100%);
border-radius: 999px;
background: var(--accent);
}
/* Place the thumb at the fraction position (translateX(-50%) to center it). */
.slider__thumb {
position: absolute;
top: 50%;
left: calc(var(--stimeo--slider-fraction, 0) * 100%);
width: 1.25rem;
height: 1.25rem;
border-radius: 50%;
background: var(--surface-card);
border: 2px solid var(--accent);
transform: translate(-50%, -50%);
cursor: grab;
}
.slider__thumb:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
// The hidden field mirrors the value, and for a move the user made it emits the
// native `change` a form already listens to — so `stimeo--auto-submit`, form
// validation, or any `change` handler on the `<form>` reacts without knowing what
// this component is. A refresh that follows a morph or a repair is silent, and
// the component reports a value the page moved — a runtime `min` / `max` /
// `step` / `value` change — as `stimeo--slider:reconcile`. This demo reads both
// events, so the readout follows the user's moves and the page's.
// The page chrome uses some of these controllers too (the nav, the tab strip),
// so everything below is scoped to the demo itself.
const demoStage = document.querySelector(".demo-stage") ?? document;
const fieldReadout = demoStage.querySelector("[data-readout]");
const fieldSources = demoStage.querySelectorAll("[data-stimeo--slider-target='field']");
// An input without a `name` is not a submitted control, so it is left out.
const submitted = () =>
Array.from(demoStage.querySelectorAll("[data-stimeo--slider-target='field']"))
.filter((input) => input.name)
.map((input) => `${input.name}=${input.value}`)
.join(" & ") || fieldReadout?.dataset.emptyText || "";
const showSubmitted = () => {
if (fieldReadout) fieldReadout.textContent = submitted();
};
for (const source of fieldSources) source.addEventListener("change", showSubmitted);
demoStage.addEventListener("stimeo--slider:reconcile", showSubmitted);
showSubmitted();
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--slider"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
track
必須
|
レール。押下位置から値を決めドラッグを開始する。 | data-stimeo--slider-target="track" |
thumb
必須
|
ドラッグ可能なつまみ(role=slider)。ARIA 値が現在値を表す。 |
data-stimeo--slider-target="thumb" |
field
|
hidden input へ値を写す任意のターゲット。利用者操作で値が動いたときだけ native change を出す。 | data-stimeo--slider-target="field" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
min
|
最小値(既定0)。Home の移動先。 | data-stimeo--slider-min-value |
max
|
最大値(既定100)。End の移動先。 | data-stimeo--slider-max-value |
step
|
スナップ・刻み幅(既定1)。矢印は1刻み、Page は10倍。 | data-stimeo--slider-step-value |
value
|
現在値(既定0)。範囲外や刻みから外れた宣言は、公開する値(ARIA / fraction / field)だけを丸める。値そのものは、利用者がつまみを動かすまで書かれたまま残る。 | data-stimeo--slider-value-value |
logicalTrack
|
トラックを論理 CSS で組んでいて RTL でミラーするか(既定 false)。立てるとポインタ写像と横矢印の対が論理方向に従う。立てなければ direction は一切読まない。 | data-stimeo--slider-logical-track-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
onKeydown
|
APG 準拠のキー操作(矢印、Page は10倍、Home/End で最小/最大)。 | stimeo--slider#onKeydown |
onPointerDown
|
ポインタドラッグを開始し、位置から値を決める。 | stimeo--slider#onPointerDown |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
利用者の操作(キーボードやドラッグ)で値が動いたときに発火。detail は { value }。 |
stimeo--slider:change |
reconcile
|
ページ側の変更(実行時の min / max / step / value の変更や morph)で公開中の値が動いたときに、1 バッチにつき 1 回発火。detail は change と同じ形。接続時は発火しない。 |
stimeo--slider:reconcile |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
aria-valuenow |
つまみ | 現在値。 |
aria-valuemin / aria-valuemax |
つまみ | 範囲の最小値・最大値。 |
--stimeo--slider-fraction |
ルート要素 | 現在値の割合(0..1)。つまみの配置に使う。 |