ページネーション
stimeo--pagination
現在ページ管理・aria-current 同期・境界での前後ボタン無効化を担うページ送り。
stimeo--pagination コントローラは、ナビゲーションランドマーク上でページ送りを管理する。現在ページと総ページ数を保持し、現在ページのボタンにのみ aria-current="page" を同期(他からは除去)、先頭/末尾で前後ボタンを無効化する。無効化でフォーカスを失わないよう、無効化の前にフォーカスを反対側の操作要素へ移す。変更時は stimeo--pagination:change を発火するので、利用側はリスト差し替えや URL 更新を行える。ページボタンの生成や省略表示はマークアップの責務。ライブラリは振る舞いのみ。
全 5 ページ中 1 ページ目
キーボード操作
| キー | 動作 |
|---|---|
| Enter / Space | ページ/前/次のボタンを活性化する。 |
| Tab | ネイティブのタブ順で各コントロールを移動(独自ロービングなし)。 |
<%# Markup for the pagination demo.
Built on nav + aria-label and aria-current="page", the library handles current-page
management, boundary control of the prev/next buttons, and firing the change event.
Data fetching and URL rewriting are the consumer's (here demo.js just updates the
results area). %>
<nav
class="pagination"
data-controller="stimeo--pagination"
aria-label="<%= t("components.pagination.demo.label") %>"
data-stimeo--pagination-page-value="1"
data-stimeo--pagination-total-value="5">
<button
type="button"
class="pagination__button"
data-stimeo--pagination-target="prev"
data-action="click->stimeo--pagination#prev">
<%= t("components.pagination.demo.prev") %>
</button>
<% (1..5).each do |page| %>
<button
type="button"
class="pagination__button pagination__page"
data-page="<%= page %>"
<%= "aria-current=\"page\"".html_safe if page == 1 %>
data-stimeo--pagination-target="page"
data-action="click->stimeo--pagination#select"><%= page %></button>
<% end %>
<button
type="button"
class="pagination__button"
data-stimeo--pagination-target="next"
data-action="click->stimeo--pagination#next">
<%= t("components.pagination.demo.next") %>
</button>
</nav>
<p
class="pagination__status"
data-pagination-status
data-template="<%= t("components.pagination.demo.status_template") %>"
aria-live="polite">
<%= t("components.pagination.demo.status_template", page: 1, total: 5) %>
</p>
/*
* Presentation-only styles for the pagination demo.
* The library toggles the current-page highlight via aria-current="page" and the
* boundary disabling via the disabled attribute. The consumer's CSS just reacts to
* those state hooks.
*/
.pagination {
display: flex;
flex-wrap: wrap;
gap: 0.375rem;
align-items: center;
}
.pagination__button {
min-width: 2.25rem;
padding: 0.4rem 0.6rem;
border: 1px solid var(--border-strong);
border-radius: 0.375rem;
background: var(--surface, var(--surface-card));
color: var(--fg, var(--color-text));
font: inherit;
cursor: pointer;
}
.pagination__button:hover:not(:disabled) {
border-color: var(--accent, var(--color-primary));
}
.pagination__button:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.pagination__page[aria-current="page"] {
border-color: var(--accent, var(--color-primary));
background: var(--accent, var(--color-primary));
color: var(--white);
font-weight: 600;
}
.pagination__status {
margin-top: 0.75rem;
font-size: 0.875rem;
color: var(--color-text-muted);
}
// pagination change-event subscription demo (consumer-side JS).
//
// The core controller (stimeo--pagination) only handles current-page state,
// aria-current sync, disabling prev/next at the boundaries, and firing change.
// Actually swapping the list or updating the URL is the consumer's job. Here we
// subscribe to change and update the "Page X of Y" results area.
document.querySelectorAll('[data-controller~="stimeo--pagination"]').forEach((nav) => {
const status = nav.parentElement?.querySelector('[data-pagination-status]');
if (!status) return;
const template = status.dataset.template || 'Page %{page} of %{total}';
nav.addEventListener('stimeo--pagination:change', (event) => {
const { page, total } = event.detail;
status.textContent = template
.replace('%{page}', String(page))
.replace('%{total}', String(total));
});
});
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--pagination"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
page
必須
|
data-page を持つページボタン。現在のページに aria-current="page" が付く。 |
data-stimeo--pagination-target="page" |
prev
|
前ページボタン。1 ページ目で disabled になる。 | data-stimeo--pagination-target="prev" |
next
|
次ページボタン。最終ページ(total)で disabled になる。 | data-stimeo--pagination-target="next" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
page
|
現在のページ番号(1 始まり、[1, total] にクランプ)。既定値は 1。 | data-stimeo--pagination-page-value |
total
|
総ページ数。既定値は 1。 | data-stimeo--pagination-total-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
next
|
次のページへ進む(total にクランプ)。 | stimeo--pagination#next |
prev
|
前のページへ戻る(1 にクランプ)。 | stimeo--pagination#prev |
select
|
クリックされたボタンの data-page を読み取り、そのページを現在にする。 |
stimeo--pagination#select |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
現在のページが変わると発火する。detail に { page, total, previous } を含む。 |
stimeo--pagination:change |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
aria-current |
ページボタン | 現在ページのボタンにのみ "page"。 |
disabled |
前へ/次へ | 先頭で前へ、末尾で次へを無効化。 |