オーバーフロー表示
stimeo--overflow-indicator
残りのスクロール余地を検知し、data-overflow-start / data-overflow-end で公開する。
stimeo--overflow-indicator コントローラは APG ウィジェットを持たない状態検知ユーティリティ。viewport のスクロール位置とサイズを(配線済みの scroll アクション、リサイズ用の LayoutObserver、内容変化用の MutationObserver で)監視し、 data-overflow-start / data-overflow-end を同期して、利用側 CSS が「この先に続きがある」端の影や矢印を描けるようにする。任意の前後送りボタンは 1 画面ずつスクロールし(scrollByPage)、余地のない方向のボタンは disabled を同期する。変化時に stimeo--overflow-indicator:change を発火する。data-overflow-* は ARIA 的な意味を持たない。ライブラリは振る舞いのみを提供し、影・矢印の見た目は Playground が所有する。 Observer・リスナは disconnect(Turbo 遷移含む)で解除し、scrollByPage は prefers-reduced-motion を尊重する。
キーボード操作
| キー | 動作 |
|---|---|
| Enter / Space | スクロールボタンを実行(標準ボタン挙動・任意設置)。 |
<%# Markup for the overflow-indicator (horizontal-scroll affordance) demo.
The library exposes the viewport's remaining scroll room via data-overflow-start /
data-overflow-end and syncs the prev/next buttons' disabled to that room. The
shadow/arrow look is drawn by demo.css. %>
<div class="overflow-demo" data-controller="stimeo--overflow-indicator"
data-stimeo--overflow-indicator-orientation-value="horizontal">
<button
type="button"
class="overflow-demo__nav"
aria-label="<%= t("components.overflow_indicator.demo.prev") %>"
data-stimeo--overflow-indicator-direction-param="start"
data-action="click->stimeo--overflow-indicator#scrollByPage">‹</button>
<div class="overflow-demo__viewport"
data-stimeo--overflow-indicator-target="viewport"
data-action="scroll->stimeo--overflow-indicator#update"
tabindex="0"
role="region"
aria-label="<%= t("components.overflow_indicator.demo.label") %>">
<% 10.times do |i| %>
<div class="overflow-demo__card"><%= t(
"components.overflow_indicator.demo.card", number: i + 1
) %></div>
<% end %>
</div>
<button
type="button"
class="overflow-demo__nav"
aria-label="<%= t("components.overflow_indicator.demo.next") %>"
data-stimeo--overflow-indicator-direction-param="end"
data-action="click->stimeo--overflow-indicator#scrollByPage">›</button>
</div>
/* Presentation CSS for overflow-indicator. The library syncs data-overflow-start / -end
and the buttons' disabled, so the edge shadow (mask) and button look are drawn here. */
.overflow-demo {
display: flex;
align-items: center;
gap: 0.5rem;
max-width: 460px;
}
/* Prev/next buttons. The direction with no room becomes disabled, tied to data-overflow-*. */
.overflow-demo__nav {
flex: 0 0 auto;
width: 2rem;
height: 2rem;
border: 1px solid var(--border-strong);
border-radius: 50%;
background: var(--surface-card);
font-size: 1.1rem;
line-height: 1;
cursor: pointer;
color: var(--color-text);
}
.overflow-demo__nav:disabled {
opacity: 0.35;
cursor: default;
}
.overflow-demo__nav:focus-visible {
outline: 2px solid var(--color-primary-hover);
outline-offset: 2px;
}
/* The viewport is the horizontally-scrolling container; it reads data-overflow-* to mask
its edges. */
.overflow-demo__viewport {
display: flex;
gap: 0.75rem;
overflow-x: auto;
padding: 0.5rem;
scroll-behavior: smooth;
/* Edge gradient mask. Only the side with remaining room fades out. */
--mask-start: transparent;
--mask-end: transparent;
-webkit-mask-image: linear-gradient(
to right, var(--mask-start), var(--ink) 12%, var(--ink) 88%, var(--mask-end)
);
mask-image: linear-gradient(
to right,
var(--mask-start),
var(--ink) 12%,
var(--ink) 88%,
var(--mask-end)
);
}
.overflow-demo__viewport:focus-visible {
outline: 2px solid var(--color-primary-hover);
outline-offset: 2px;
}
.overflow-demo__viewport[data-overflow-start="true"] {
--mask-start: transparent;
}
.overflow-demo__viewport[data-overflow-start="false"] {
--mask-start: var(--ink);
}
.overflow-demo__viewport[data-overflow-end="true"] {
--mask-end: transparent;
}
.overflow-demo__viewport[data-overflow-end="false"] {
--mask-end: var(--ink);
}
.overflow-demo__card {
flex: 0 0 auto;
width: 110px;
height: 80px;
display: grid;
place-items: center;
border-radius: 8px;
background: var(--color-primary-hover);
color: var(--white);
font-weight: 600;
}
@media (prefers-reduced-motion: reduce) {
.overflow-demo__viewport {
scroll-behavior: auto;
}
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--overflow-indicator"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
viewport
必須
|
残りスクロール量を計測するスクロール要素。 | data-stimeo--overflow-indicator-target="viewport" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
orientation
|
計測するスクロール軸(horizontal/vertical、既定horizontal)。 |
data-stimeo--overflow-indicator-orientation-value |
threshold
|
端からこのpx以内はスクロール不可とみなす許容量(既定1)。 | data-stimeo--overflow-indicator-threshold-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
scrollByPage
|
directionパラメータ(start/end)方向へ1ページ分スクロールする(reduced motion尊重)。 | stimeo--overflow-indicator#scrollByPage |
update
|
残りスクロール量を再計測し状態を反映する。ビューポートのscrollに接続する。 | stimeo--overflow-indicator#update |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
スクロール可否が変わると発火する。detailにstartとendの真偽値を含む。 | stimeo--overflow-indicator:change |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
data-overflow-start |
viewport | 開始側(左 / 上)に余地があるとき "true"。 |
data-overflow-end |
viewport | 終了側(右 / 下)に余地があるとき "true"。 |