ツリービュー
stimeo--tree-view
単一選択のツリー。キーボードで移動・展開 / 折りたたみ・型先読みする。
stimeo--tree-view コントローラは、WAI-ARIA の Tree View(単一選択)パターンを実装する。親子関係は DOM のネスト(treeitem の直下の group がその子)で表す。ツリー全体で 1 つの Tab ストップ(ロービングタブインデックス)。ArrowDown/ArrowUp で可視項目を移動し、Home/End で先頭 / 末尾の可視項目へ、印字可能文字でラベル先頭一致の型先読みをする。 ArrowRight は折りたたみ中の親を展開、展開済みなら最初の子へ。ArrowLeft は展開中の親を折りたたみ、それ以外は親項目へ。Enter/Space / クリックで単一選択する(aria-selected)。 aria-expanded と各子 group の hidden を同期し、stimeo--tree-view:toggle を発火する。選択時は stimeo--tree-view:select を発火する。
-
app
-
controllers
- application_controller.rb
- components_controller.rb
- application.rb
-
controllers
- README.md
- Gemfile
キーボード操作
| キー | 動作 |
|---|---|
| ↓ / ↑ | 次 / 前の可視項目へ。 |
| → | 折りたたみ中の親を展開、展開済みなら最初の子へ。 |
| ← | 展開中の親を折りたたみ、それ以外は親項目へ。 |
| Home / End | 先頭 / 末尾の可視項目へ。 |
| Enter / Space | 項目を選択。 |
| 印字可能文字 | ラベルが先頭一致する次の項目へ型先読み。 |
<%# Markup for the tree-view (tree view / single selection) demo.
Nested role="tree" / "treeitem" / "group" express the hierarchy. Arrows move between
visible items; ArrowRight / ArrowLeft expand/collapse and move between parent/child;
Home/End, typeahead, and Enter/Space select a single item. The library handles roving,
aria-expanded / aria-selected sync, and toggling each group's hidden. Across the whole
tree only one treeitem is tabindex=0. %>
<ul class="tree-view" data-controller="stimeo--tree-view" role="tree"
aria-label="<%= t('components.tree_view.demo.label') %>">
<li
class="tree-view__item"
role="treeitem"
aria-expanded="true"
aria-selected="false"
tabindex="0"
data-stimeo--tree-view-target="item"
data-action="keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick">
<span class="tree-view__label">app</span>
<ul class="tree-view__group" role="group" data-stimeo--tree-view-target="group">
<li
class="tree-view__item"
role="treeitem"
aria-expanded="false"
aria-selected="false"
tabindex="-1"
data-stimeo--tree-view-target="item"
data-action="keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick">
<span class="tree-view__label">controllers</span>
<ul class="tree-view__group" role="group" data-stimeo--tree-view-target="group" hidden>
<li class="tree-view__item" role="treeitem" aria-selected="false" tabindex="-1"
data-stimeo--tree-view-target="item"
data-action="keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick">
<span class="tree-view__label">application_controller.rb</span>
</li>
<li class="tree-view__item" role="treeitem" aria-selected="false" tabindex="-1"
data-stimeo--tree-view-target="item"
data-action="keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick">
<span class="tree-view__label">components_controller.rb</span>
</li>
</ul>
</li>
<li class="tree-view__item" role="treeitem" aria-selected="false" tabindex="-1"
data-stimeo--tree-view-target="item"
data-action="keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick">
<span class="tree-view__label">application.rb</span>
</li>
</ul>
</li>
<li class="tree-view__item" role="treeitem" aria-selected="false" tabindex="-1"
data-stimeo--tree-view-target="item"
data-action="keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick">
<span class="tree-view__label">README.md</span>
</li>
<li class="tree-view__item" role="treeitem" aria-selected="false" tabindex="-1"
data-stimeo--tree-view-target="item"
data-action="keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick">
<span class="tree-view__label">Gemfile</span>
</li>
</ul>
/*
* Presentation-only styles for the tree-view demo.
* The library drives expand/collapse via items' aria-expanded and groups' hidden,
* selection via aria-selected, and roving via tabindex. Here we add the hierarchy
* indentation and the selection / expansion look (the triangle marker, highlight).
*/
.tree-view {
margin: 0;
padding: 0;
list-style: none;
min-width: 18rem;
font-size: 0.9rem;
color: var(--fg, var(--color-text));
}
.tree-view__group {
margin: 0;
padding-left: 1.1rem;
list-style: none;
}
.tree-view__group[hidden] {
display: none;
}
.tree-view__label {
display: block;
padding: 0.25rem 0.4rem;
border-radius: 0.25rem;
cursor: pointer;
}
/* Add an expand/collapse marker to items that have children (not to leaves). */
.tree-view__item[aria-expanded] > .tree-view__label::before {
content: "▸";
display: inline-block;
width: 1rem;
color: var(--color-text-muted);
}
.tree-view__item[aria-expanded="true"] > .tree-view__label::before {
content: "▾";
}
.tree-view__item[aria-selected="true"] > .tree-view__label {
background: var(--vital-100);
font-weight: 600;
}
.tree-view__item:focus {
outline: none;
}
.tree-view__item:focus-visible > .tree-view__label {
outline: 2px solid var(--accent, var(--color-primary));
outline-offset: -2px;
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--tree-view"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
item
必須
|
treeitem。ツリー全体が一つの Tab ストップで、矢印ナビゲーション・開閉・選択を行う。 | data-stimeo--tree-view-target="item" |
group
|
treeitem の子を保持する role=group。hidden 状態が親の aria-expanded と同期する。 |
data-stimeo--tree-view-target="group" |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
onClick
|
クリックされた項目をフォーカスして選択する(ターゲットに最も近い treeitem のみが動作)。 | stimeo--tree-view#onClick |
onKeydown
|
ツリーのキー操作を振り分ける。矢印で移動・開閉、Home/End、Enter/Space で選択、印字可能文字でタイプアヘッド。 | stimeo--tree-view#onKeydown |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
select
|
項目が選択されると発火する。detail に { item } を含む。 |
stimeo--tree-view:select |
toggle
|
親項目が開閉されると発火する。detail に { item, expanded } を含む。 |
stimeo--tree-view:toggle |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
aria-expanded |
親項目 | 子グループの開閉状態。 |
aria-selected |
項目 | 単一選択中の項目に "true"。 |
tabindex |
項目 | ロービング: アクティブ=0 / 他=-1(ツリーは 1 タブストップ)。 |
hidden |
グループ | 親が折りたたみのとき付与。 |