フィルター
stimeo--filter
アクティブなファセットタグの集合で、コレクションの項目を表示/非表示する。
stimeo--filter コントローラは、ファセットタグでコレクションを絞り込む。タグの切り替え方法(ネイティブのチェックボックス/ラジオ、または stimeo--toggle-group のようなボタントグル)からは独立している。on 状態(checked または aria-pressed="true")のコントロールのトークンを集め、各項目の data-stimeo--filter-tokens と照合して hidden を切り替える。match(all/any)で複数トークンの結合を決め、アクティブが無ければ全件を表示する。任意の group コンテナは可視項目が無くなると非表示になり、任意の empty 要素は該当なし時に表示され、stimeo--filter:change を発火する。状態は connect 時に DOM から再導出する(Morph 安全)。振る舞いのみで、見た目は付与しない。
- マルゲリータピザ
- ペパロニピザ
- 野菜カレー
- バッファローウィング
選択した条件に合う料理はありません。
<%# Markup for the filter (faceted list) demo. Behavior only — no consumer JS.
The toggle-group owns the chips' pressed state + roving focus; stimeo--filter
reads the pressed tokens (data-value) and shows/hides items by their
data-stimeo--filter-tokens. match="all" means an item must carry every active
token. The empty element appears when nothing matches; Clear resets the chips. %>
<div class="filter-demo" data-controller="stimeo--filter"
data-stimeo--filter-match-value="all"
data-action="stimeo--toggle-group:change->stimeo--filter#apply">
<div class="filter-demo__bar">
<div class="filter-demo__chips" role="group"
aria-label="<%= t('components.filter.demo.legend') %>"
data-controller="stimeo--toggle-group">
<button type="button" class="filter-demo__chip" aria-pressed="false" tabindex="0"
data-value="veg"
data-stimeo--filter-target="control"
data-stimeo--toggle-group-target="item"
data-action="click->stimeo--toggle-group#toggle
keydown->stimeo--toggle-group#onKeydown">
<%= t("components.filter.demo.vegetarian") %>
</button>
<button type="button" class="filter-demo__chip" aria-pressed="false" tabindex="-1"
data-value="spicy"
data-stimeo--filter-target="control"
data-stimeo--toggle-group-target="item"
data-action="click->stimeo--toggle-group#toggle
keydown->stimeo--toggle-group#onKeydown">
<%= t("components.filter.demo.spicy") %>
</button>
</div>
<button type="button" class="filter-demo__clear" data-action="stimeo--filter#clear">
<%= t("components.filter.demo.clear") %>
</button>
</div>
<%# Each item shows its facet tokens as tags so it is clear why a chip filters it in/out. %>
<%
token_labels = {
"veg" => t("components.filter.demo.vegetarian"),
"spicy" => t("components.filter.demo.spicy"),
}
dishes = [
{ key: "margherita", tokens: "veg" },
{ key: "pepperoni", tokens: "" },
{ key: "curry", tokens: "veg spicy" },
{ key: "wings", tokens: "spicy" },
]
%>
<ul class="filter-demo__list">
<% dishes.each do |dish| %>
<li class="filter-demo__item" data-stimeo--filter-target="item"
data-stimeo--filter-tokens="<%= dish[:tokens] %>">
<span class="filter-demo__name"><%= t("components.filter.demo.#{dish[:key]}") %></span>
<span class="filter-demo__tags">
<% dish[:tokens].split.each do |tok| %>
<span class="filter-demo__tag"><%= token_labels[tok] %></span>
<% end %>
</span>
</li>
<% end %>
</ul>
<p class="filter-demo__empty" role="status" data-stimeo--filter-target="empty" hidden>
<%= t("components.filter.demo.empty") %>
</p>
</div>
/*
* Presentation-only styles for the filter (faceted list) demo.
* The library only toggles `hidden` on items / the empty element and `aria-pressed`
* (via toggle-group) on the chips; everything below is the consumer's look.
*/
.filter-demo {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 22rem;
}
.filter-demo__bar {
display: flex;
align-items: center;
gap: 0.5rem;
}
.filter-demo__chips {
display: inline-flex;
gap: 0.25rem;
}
.filter-demo__chip {
padding: 0.25rem 0.75rem;
color: var(--fg);
background: var(--bg);
border: 1px solid var(--border);
border-radius: 999px;
font: inherit;
font-size: 0.85rem;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.filter-demo__chip:hover {
border-color: var(--accent);
}
/* Pressed look is driven entirely by the library-managed aria-pressed. */
.filter-demo__chip[aria-pressed="true"] {
color: var(--white);
background: var(--accent);
border-color: var(--accent);
}
.filter-demo__clear {
margin-left: auto;
padding: 0.25rem 0.5rem;
color: var(--muted);
background: none;
border: 1px solid var(--border);
border-radius: 0.375rem;
font: inherit;
font-size: 0.85rem;
cursor: pointer;
}
.filter-demo__clear:hover {
color: var(--fg);
border-color: var(--accent);
}
.filter-demo__list {
display: flex;
flex-direction: column;
gap: 0.375rem;
margin: 0;
padding: 0;
list-style: none;
}
.filter-demo__item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
padding: 0.625rem 0.875rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.filter-demo__tags {
display: inline-flex;
gap: 0.25rem;
flex: none;
}
/* The item's facet tokens, shown so it is clear why a chip filters it in or out. */
.filter-demo__tag {
padding: 0.05rem 0.45rem;
color: var(--muted);
background: var(--sidebar-bg);
border: 1px solid var(--border);
border-radius: 999px;
font-size: 0.7rem;
}
/* The library hides non-matching items via the `hidden` attribute; the shared
base reset (base/reset.css) enforces `[hidden] { display: none !important }`,
so no per-demo re-declaration is needed. */
.filter-demo__empty {
margin: 0;
padding: 0.75rem 0.875rem;
color: var(--muted);
border: 1px dashed var(--border);
border-radius: 0.5rem;
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--filter"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
item
必須
|
data-stimeo--filter-tokens で表示/非表示される要素。 |
data-stimeo--filter-target="item" |
control
|
on 状態(checked / aria-pressed)でトークンを供出する要素。 |
data-stimeo--filter-target="control" |
group
|
可視項目が無くなると非表示になる任意のコンテナ。 | data-stimeo--filter-target="group" |
empty
|
該当なし時に表示される任意の要素。 | data-stimeo--filter-target="empty" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
match
|
アクティブトークンの結合方法: all(AND・既定)または any(OR)。 |
data-stimeo--filter-match-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
apply
|
表示を再評価する(ボタントグルのイベントはここへ結線)。 | stimeo--filter#apply |
clear
|
すべてのコントロールを off にして再評価する。 | stimeo--filter#clear |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
評価のたびに発火。detail.active / detail.visible / detail.total を伴う。 |
stimeo--filter:change |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
hidden |
項目 / グループ / 該当なし要素 | 非一致の項目・可視項目ゼロのグループ・該当なし要素で切り替わる。 |