レンジスライダー
stimeo--range-slider
下限・上限の 2 つのつまみが相互に制約し合うレンジスライダー。
stimeo--range-slider コントローラは WAI-ARIA の Multi-Thumb Slider パターンを実装します。 2 つのつまみ(start / end)は相互に制約し、start が end を超えないよう保ちます。各つまみの可動範囲は aria-valuemin/max で支援技術へ動的に伝わり、範囲の割合はルート要素の CSS 変数 --stimeo-range-start / --stimeo-range-end(0..1)で公開され、つまみとレンジの配置は consumer が行えます。最小値・最大値・ステップ・初期の start/end は data-*-value 属性で指定します。ライブラリは挙動のみを提供します。
キーボード操作
| キー | 動作 |
|---|---|
| → / ↑ | フォーカス中のつまみを 1 ステップ増やす(相手のつまみは超えない)。 |
| ← / ↓ | フォーカス中のつまみを 1 ステップ減らす(相手のつまみは超えない)。 |
| PageUp / PageDown | フォーカス中のつまみを 10 ステップ分動かす。 |
| Home / End | フォーカス中のつまみを可動範囲の最小 / 最大へジャンプする。 |
<%# Markup for the range-slider (APG Slider, multi-thumb) demo.
stimeo--range-slider handles the two thumbs' (start / end) mutual constraint
(lower ≤ upper), keyboard / drag operation, and dynamic updates of each thumb's
aria-valuemin/max/now, and exposes the range as the root CSS variables
--stimeo-range-start / --stimeo-range-end (0..1). The thumb and range placement
is the Playground's CSS using those variables. %>
<div
class="range-slider"
data-controller="stimeo--range-slider"
data-stimeo--range-slider-min-value="0"
data-stimeo--range-slider-max-value="100"
data-stimeo--range-slider-step-value="1"
data-stimeo--range-slider-start-value="20"
data-stimeo--range-slider-end-value="80">
<div class="range-slider__track" data-stimeo--range-slider-target="track"
data-action="pointerdown->stimeo--range-slider#onPointerDown">
<div class="range-slider__range" aria-hidden="true"></div>
<div class="range-slider__thumb" role="slider" tabindex="0"
aria-label="<%= t("components.range_slider.demo.min_label") %>"
data-stimeo--range-slider-target="startThumb"
data-action="keydown->stimeo--range-slider#onKeydown"></div>
<div class="range-slider__thumb" role="slider" tabindex="0"
aria-label="<%= t("components.range_slider.demo.max_label") %>"
data-stimeo--range-slider-target="endThumb"
data-action="keydown->stimeo--range-slider#onKeydown"></div>
</div>
<output class="range-slider__output" data-range-slider-output aria-live="polite">20 – 80</output>
</div>
/*
* Presentation-only styles for the range-slider demo.
* The thumb and range positions are computed from the CSS variables the library
* exposes on the root: --stimeo-range-start / --stimeo-range-end (0..1).
*/
.range-slider {
width: min(22rem, 100%);
padding: 0.75rem 0.625rem;
}
.range-slider__track {
position: relative;
height: 0.375rem;
border-radius: 999px;
background: var(--border);
}
/* Fill from start to end with the accent color. */
.range-slider__range {
position: absolute;
top: 0;
height: 100%;
left: calc(var(--stimeo-range-start, 0) * 100%);
width: calc((var(--stimeo-range-end, 1) - var(--stimeo-range-start, 0)) * 100%);
border-radius: 999px;
background: var(--accent);
}
/* Place each thumb at its fraction position (translateX(-50%) to center it). */
.range-slider__thumb {
position: absolute;
top: 50%;
width: 1.25rem;
height: 1.25rem;
border-radius: 50%;
background: var(--surface-card);
border: 2px solid var(--accent);
transform: translate(-50%, -50%);
cursor: grab;
}
.range-slider__thumb[data-stimeo--range-slider-target="startThumb"] {
left: calc(var(--stimeo-range-start, 0) * 100%);
}
.range-slider__thumb[data-stimeo--range-slider-target="endThumb"] {
left: calc(var(--stimeo-range-end, 1) * 100%);
}
.range-slider__thumb:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.range-slider__output {
display: block;
margin-top: 0.75rem;
font-variant-numeric: tabular-nums;
color: var(--text-muted, var(--color-text-muted));
}
// Demo that subscribes to the range-slider change event and reflects the selected range
// into a readout. The library provides behavior only, so the value display is the
// consumer's (this script's) job.
document.querySelectorAll('[data-controller~="stimeo--range-slider"]').forEach((root) => {
const output = root.querySelector('[data-range-slider-output]');
if (!output) return;
root.addEventListener('stimeo--range-slider:change', (e) => {
output.textContent = `${e.detail.start} – ${e.detail.end}`;
});
});
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--range-slider"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
track
必須
|
レール。押すと最寄りのつまみが動きドラッグが始まる。 | data-stimeo--range-slider-target="track" |
startThumb
必須
|
下限(最小)側のつまみ(role=slider)。end つまみで上限が制限される。 |
data-stimeo--range-slider-target="startThumb" |
endThumb
必須
|
上限(最大)側のつまみ(role=slider)。start つまみで下限が制限される。 |
data-stimeo--range-slider-target="endThumb" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
min
|
範囲の最小値(既定0)。 | data-stimeo--range-slider-min-value |
max
|
範囲の最大値(既定100)。 | data-stimeo--range-slider-max-value |
step
|
スナップ・刻み幅(既定1)。矢印は1刻み、Page は10倍。 | data-stimeo--range-slider-step-value |
start
|
下限つまみの初期値(既定0)。end 以下に保たれる。 | data-stimeo--range-slider-start-value |
end
|
上限つまみの初期値(既定100)。start 以上に保たれる。 | data-stimeo--range-slider-end-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
onKeydown
|
フォーカス中つまみのキー操作(矢印・Page・Home/End)。交差しない。 | stimeo--range-slider#onKeydown |
onPointerDown
|
トラックのドラッグを開始し、押下点に近いつまみを動かす。 | stimeo--range-slider#onPointerDown |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
ユーザー操作の変更で発火。detail に start と end を載せる。 | stimeo--range-slider:change |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
aria-valuenow |
各つまみ | そのつまみの現在値。 |
aria-valuemin / aria-valuemax |
各つまみ | 相手のつまみで動的に制限した可動範囲。 |
--stimeo-range-start / --stimeo-range-end |
ルート要素 | 各値の割合(0..1)。つまみとレンジの配置に使う。 |