メーター
stimeo--meter
meter の ARIA 値属性を同期し、しきい値で区分する。
stimeo--meter コントローラは WAI-ARIA の meter ロールを駆動します。これは既知の範囲内の「ある時点の測定値」(ディスク使用量・バッテリー・スコア等)で、Progress とは区別されます。 aria-valuenow / min / max を同期し、割合を --stimeo-meter-ratio(0–1)として公開し、 low/high しきい値があれば値を low/medium/high の区分として data-state に反映します。状態を色だけで伝えないよう、valueText テンプレートから aria-valuetext を生成して区分をテキストでも伝えます。setValue は amount アクションパラメータか meter:set イベントで値を受け取り、 stimeo--meter:change を発火します。ライブラリは挙動のみを提供し、見た目はこの Playground が持ちます。
<%# Markup for the meter demo.
Syncs role="meter" and aria-value*, and classifies data-state into low/medium/high
via the low/high thresholds. demo.css applies color based on data-state.
aria-valuetext is generated by the library from the {percent}/{state} template.
Like a progressbar, a meter must not contain interactive children, so the preset
buttons sit beside it and update it through the meter:set CustomEvent the
controller listens for (see demo.js) rather than a Stimulus data-action, which
only binds within the controller's own element. %>
<div class="meter-demo">
<div
class="meter"
data-controller="stimeo--meter"
role="meter"
aria-label="<%= t('components.meter.demo.label') %>"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="72"
data-stimeo--meter-value-value="72"
data-stimeo--meter-low-value="50"
data-stimeo--meter-high-value="80"
data-stimeo--meter-value-text-value="<%= t('components.meter.demo.value_text') %>"
data-action="meter:set->stimeo--meter#setValue">
<div class="meter__bar" data-stimeo--meter-target="bar"></div>
</div>
<div class="meter-demo__controls" role="group"
aria-label="<%= t('components.meter.demo.controls_label') %>">
<% [20, 65, 90].each do |amount| %>
<button
class="demo-trigger"
type="button"
data-meter-amount="<%= amount %>">
<%= amount %>
</button>
<% end %>
</div>
</div>
/*
* Presentation-only styles for the meter demo.
* The bar width comes from --stimeo-meter-ratio (0–1) and its color from the
* data-state band (low / medium / high). aria-valuetext is used alongside color
* so meaning never relies on color alone.
*/
.meter-demo {
display: flex;
flex-direction: column;
gap: 1rem;
max-width: 32rem;
}
.meter {
height: 0.75rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
background: var(--surface-subtle);
overflow: hidden;
}
.meter__bar {
height: 100%;
border-radius: inherit;
width: calc(var(--stimeo-meter-ratio, 0) * 100%);
background: var(--slate-500);
transition:
width 0.2s ease,
background-color 0.2s ease;
}
.meter[data-state="low"] .meter__bar {
background: var(--leaf-500);
}
.meter[data-state="medium"] .meter__bar {
background: var(--amber-500);
}
.meter[data-state="high"] .meter__bar {
background: var(--danger-500);
}
.meter-demo__controls {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
@media (prefers-reduced-motion: reduce) {
.meter__bar {
transition: none;
}
}
// meter external-update demo (consumer-side JS).
//
// The core controller (stimeo--meter) syncs the ARIA value attributes and the
// low/medium/high data-state; it does not own any buttons. A meter must not
// contain interactive children, so the preset buttons live next to it, and a
// Stimulus data-action on them would never bind (actions only wire up within the
// controller's own element). The contract for updating from outside is the
// meter:set event, so here each button fires meter:set with its value.
document.querySelectorAll(".meter-demo").forEach((root) => {
const meter = root.querySelector('.meter[data-controller~="stimeo--meter"]');
const controls = root.querySelector(".meter-demo__controls");
if (!meter || !controls) return;
controls.querySelectorAll("[data-meter-amount]").forEach((button) => {
button.addEventListener("click", () => {
const value = Number(button.getAttribute("data-meter-amount"));
meter.dispatchEvent(new CustomEvent("meter:set", { detail: { value } }));
});
});
});
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--meter"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
bar
|
比率のカスタムプロパティから装飾する塗り要素。 | data-stimeo--meter-target="bar" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
value
|
現在の測定値。[min, max] に丸められる(既定 0)。 | data-stimeo--meter-value-value |
min
|
範囲の下限。aria-valuemin に反映(既定 0)。 |
data-stimeo--meter-min-value |
max
|
範囲の上限。aria-valuemax に反映(既定 100)。 |
data-stimeo--meter-max-value |
low
|
以下なら low 区分とする閾値。属性がある場合のみ有効。 |
data-stimeo--meter-low-value |
high
|
以上なら high 区分とする閾値。属性がある場合のみ有効。 |
data-stimeo--meter-high-value |
optimum
|
範囲内の最適値(meter ロール準拠の宣言、既定 0)。 | data-stimeo--meter-optimum-value |
valueText
|
aria-valuetext 用テンプレート。{value}/{percent}/{state} を置換。空なら解除。 |
data-stimeo--meter-value-text-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
setValue
|
アクション引数 amount か detail.value から値を設定し、ARIA と data-state を更新する。 |
stimeo--meter#setValue |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
setValue 後に発火。detail.value / detail.ratio / detail.state(区分)を伴う。 |
stimeo--meter:change |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
aria-valuenow |
ルート要素 | 現在の測定値。 |
--stimeo-meter-ratio |
ルート要素 | 0–1 の割合(利用側 CSS がバー幅に変換)。 |
data-state |
ルート要素 | "low" / "medium" / "high" のしきい値区分。 |