レーティング
stimeo--rating
星による評価。矢印キーで増減し、端で止まります。選び直しも取り消しもできます。
星のような記号で段階を選ぶ評価です。いくつ選ばれているかは読み上げに伝わります。矢印キーで増減しますが、端まで来たら回り込まずに止まります。段階のある尺度なので、5 から 1 へ飛ぶと戸惑うためです。カーソルを乗せているときとフォーカスしているときは、その位置まで塗ってみせます。選び直しは自由で、選んでいる星をもう一度押せば取り消せます。読むだけの表示にもできます。値は隠しの入力欄へ反映されるので、そのままフォームで送れます。星の絵柄はこのデモの CSS で描いています。
実行中
キーボード操作
| キー | 動作 |
|---|---|
| → / ↑ | 評価を 1 上げる(上限で止まる)。RTL では左右のみ入れ替わる。 |
| ← / ↓ | 評価を 1 下げる(clearable なら 0 まで)。RTL では左右のみ入れ替わる。 |
| Home / End | 最小 / 最大へジャンプ。 |
| Space / Enter | フォーカス中のシンボルを選択(すでにその値なら変化なし)。 |
| Delete / Backspace | clearable のとき評価を解除して 0 に戻す。 |
<%# Markup for the rating (APG Radio Group, ordinal scale) demo.
Each symbol is role="radio" with an aria-label ("3 stars", etc.). Selection is
aria-checked; the currently filled range is data-rating-hover (the selected value or
a hover/focus preview). DOM order defines values 1..N. The star look is the consumer's CSS. %>
<div class="rating-demo-wrap">
<p class="rating-demo__hint"><%= t("components.rating.demo.hint") %></p>
<div class="rating-demo" role="radiogroup" aria-label="<%= t('components.rating.demo.label') %>"
data-controller="stimeo--rating"
data-stimeo--rating-value-value="0">
<% (1..5).each do |n| %>
<span class="rating-demo__star" role="radio" aria-checked="false"
aria-label="<%= t('components.rating.demo.star', count: n) %>"
tabindex="<%= n == 1 ? 0 : -1 %>"
data-stimeo--rating-target="symbol"
data-action="click->stimeo--rating#select
mouseenter->stimeo--rating#preview
mouseleave->stimeo--rating#endPreview
focus->stimeo--rating#preview
blur->stimeo--rating#endPreview
keydown->stimeo--rating#onKeydown">
<svg class="rating-demo__icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 2l2.9 6.26 6.86.6-5.2 4.52 1.56 6.72L12 17.1 5.88 20.6l1.56-6.72-5.2-4.52
6.86-.6z"></path>
</svg>
</span>
<% end %>
<input type="hidden" name="rating" value="0" data-stimeo--rating-target="field" />
<%# Subscribe to committed and reconciled values to update this instance's status. %>
<span class="rating-demo__status" aria-live="polite"
data-empty-text="<%= t('components.rating.demo.empty') %>"
data-rated-template="<%= t('components.rating.demo.rated') %>"><%= t(
'components.rating.demo.empty'
) %></span>
</div>
</div>
/*
* Presentation-only styles for the rating demo.
*
* Two fills, because two things are being shown. [data-rating-hover] marks the
* range the controller is currently painting — which is the committed value at
* rest, but the pointer or focus position while previewing. [aria-checked] marks
* the value that is actually committed. Painting both the same way would make a
* star look chosen as soon as it is focused, and Space would appear to do
* nothing; the preview is therefore drawn at reduced strength.
*/
.rating-demo-wrap {
display: flex;
flex-direction: column;
gap: 0.5rem;
align-items: flex-start;
}
.rating-demo__hint {
font-size: 0.8125rem;
color: var(--color-text-muted);
}
.rating-demo {
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.rating-demo__star {
display: inline-flex;
padding: 0.125rem;
border-radius: 0.25rem;
cursor: pointer;
line-height: 0;
}
.rating-demo__star:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.rating-demo__icon {
width: 1.75rem;
height: 1.75rem;
fill: var(--slate-300);
transition:
fill 0.12s ease,
fill-opacity 0.12s ease;
}
/* Preview: the range the pointer or focus is over, not yet committed. */
.rating-demo__star[data-rating-hover] .rating-demo__icon {
fill: var(--amber-500);
fill-opacity: 0.4;
}
/* Committed: the checked symbol and every symbol before it in the scale. */
.rating-demo__star:has(~ .rating-demo__star[aria-checked="true"]) .rating-demo__icon,
.rating-demo__star[aria-checked="true"] .rating-demo__icon {
fill: var(--amber-500);
fill-opacity: 1;
}
.rating-demo__status {
margin-left: 0.5rem;
font-size: 0.85rem;
color: var(--color-text-muted);
}
// Shows committed and controller-reconciled values in the status belonging to
// the event's own rating instance.
function updateRatingStatus(event) {
const rating = event.target instanceof Element ? event.target.closest('.rating-demo') : null;
const status = rating?.querySelector('.rating-demo__status');
if (!status) return;
const value = event.detail.value;
if (value === 0) {
status.textContent = status.dataset.emptyText || '';
} else {
status.textContent = (status.dataset.ratedTemplate || '').replace('{n}', String(value));
}
}
document.addEventListener('stimeo--rating:change', updateRatingStatus);
document.addEventListener('stimeo--rating:reconcile', updateRatingStatus);
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--rating"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
symbol
必須
|
評価シンボル(role=radio)。live DOM 順序が 1 始まりの値を定める。 |
data-stimeo--rating-target="symbol" |
field
|
現在の数値を反映する任意の隠しフィールド。 | data-stimeo--rating-target="field" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
value
|
現在の評価値(既定0)。 | data-stimeo--rating-value-value |
clearable
|
true(既定)なら選択中シンボル再クリックで0に戻り、最小値も0になる。 | data-stimeo--rating-clearable-value |
readonly
|
true で非操作の role=img 表示にする(既定 false)。 |
data-stimeo--rating-readonly-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
endPreview
|
塗りつぶし範囲を選択値に戻す(mouseleave/blur 時)。 | stimeo--rating#endPreview |
onKeydown
|
矢印・Home/End・Space/Enter のキー操作。ラップせずクランプ。 | stimeo--rating#onKeydown |
preview
|
ホバー・フォーカス時に data-rating-hover で塗りをプレビューする。 |
stimeo--rating#preview |
select
|
クリックしたシンボルを選択。clearable かつ選択済みなら0に戻す。 | stimeo--rating#select |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
利用者操作で確定値が変わったときに発火。detail に新しい値を載せる。 | stimeo--rating:change |
reconcile
|
DOM・設定の再調停で確定値が丸められたときに発火。detail は change と同じ形。 | stimeo--rating:reconcile |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
aria-checked |
シンボル | 選択値のシンボルのみ "true"。 |
data-rating-hover |
シンボル | 表示中の塗り範囲(選択値またはプレビュー)に入るシンボルへ付与。 |
tabindex |
シンボル | 選択中(または先頭)が 0、他は -1(ロービング)。 |