Skip to content
fandhe-frontendcore v0.4.3
GitHub

Menu

トリガーのクリックで開くアクション項目リスト(ドロップダウンメニュー)です。fandhe-frontend-headless-ui の menu mod は Root / Trigger / Indicator / Positioner / Content / Arrow / ArrowTip / Item / ItemText / ItemIndicator / ItemGroup / ItemGroupLabel / Separator / TriggerItem / ContextTrigger / CheckboxItem / RadioItemGroup / RadioItem の 18 anatomy パーツを提供する unstyled 部品であり、Themes 版と異なりスタイル(CSS)は一切持ちません。CheckboxItem/RadioItemGroup による checked 状態やサブメニューの入れ子構成にも対応します。本部品は SSR での属性出力と状態機械のみを担い、click → dispatch 配線・キーボード操作・Escape キーでの閉鎖は fandhe-frontend-wasm-full が実 DOM 配線を担います。

キーボード操作

キー対象効果
ArrowDown / ArrowUp / Enter / SpaceTrigger(closed)Menu を開き、先頭(ArrowUp は末尾)の非 disabled 項目を highlight します。
ArrowDown / ArrowUp / Home / EndContent(open)highlight 中の項目を次/前/先頭/末尾の非 disabled 項目へ移動します。既定は循環なし、data-loop-focus="true" で有効化できます。
Enter / SpaceContent(open、highlight 中の項目)highlight 中の項目へ click を合成します。Item は利用者の click ハンドラへ、TriggerItem は "toggle" を dispatch します。CheckboxItem は "toggle"、RadioItem は "select" を dispatch します。
印字可能文字Content(open)typeahead(350ms バッファ)。ItemText 子があればそのテキストを優先してマッチします。
ArrowRightTriggerItem(highlight 中)非 disabled でサブメニューが解決できるときのみサブメニューを展開します。
ArrowLeftサブメニュー内親 TriggerItem へ復帰しサブメニューを閉じます。
Escape—最上位のメニューを閉じます。data-close-on-escape="false" で無効化できます。
Tab / Shift+Tab—配線はありません(ブラウザ既定)。

参考サイトとの差分

ark-ui Menu・Radix Primitives Dropdown Menu・chakra-ui Menu と突合し、ItemText/ItemIndicator の 2 パーツを新設し(16 → 18 anatomy パーツ)、各パーツ関数の呼び出し側 attrs から固定属性キーの偽装を除去する drop_reserved を追加しました。一方、以下は意図的に合わせていません。

  • Radix Portal: DOM 配置の関心のため不採用です。
  • zag の data-placement・Radix content の data-side/data-align: positioner へ attrs 経由で渡す既存設計のため置き換えていません。
  • Radix の data-orientation(content/item): 不採用です。
  • zag content の tabindex="0" + item への実 DOM フォーカス移動: trigger にフォーカスを留め aria-activedescendant + data-highlighted で仮想フォーカスを表現する設計のため未採用です。
  • chakra-ui の ItemCommand(ショートカット表示): Themes 層(fandhe-frontend-pre-styled-ui)の装飾関心のため headless anatomy には持ち込みません。
  • asChild: 本リポジトリ全体で保留継続中の意図的非採用方針です。
  • ark-ui の closeOnSelect/lazyMount/portal: クライアント配置・実行時関心のため未採用です。
  • Escape 後の「trigger へのフォーカス復帰」: 本実装がそもそも trigger からフォーカスを離さない設計のため、構造的に同等の結果になります(追加実装は不要)。

スタイル済みの表示例は Menu を参照してください。

関連 API: fandhe-frontend-headless-ui API

自前 CSS の最小例

Themes 版を使わず本部品を直接使う場合、[data-scope="menu"][data-part="..."] セレクタでスタイルを当てます。positioner/content/item-indicator には [hidden] ガードを必ず含めます。

CSS
[data-scope="menu"][data-part="positioner"][hidden],
[data-scope="menu"][data-part="content"][hidden],
[data-scope="menu"][data-part="item-indicator"][hidden] {
  display: none;
}

[data-scope="menu"][data-part="item"][data-highlighted] {
  background: #eff6ff;
}

[data-scope="menu"][data-part="item"][data-disabled] {
  color: #9ca3af;
}

[data-scope="menu"][data-part="checkbox-item"][data-state="checked"] {
  font-weight: 600;
}

Demo

枠線・余白は docs サイト側のデモ枠であり、fandhe-frontend-headless-ui 自体はスタイルを持ちません。

▾

Features

  • トリガー起点のオーバーレイ + アクション項目リスト。Root / Trigger / Indicator / Positioner / Content / Arrow / ArrowTip / Item / ItemText / ItemIndicator / ItemGroup / ItemGroupLabel / Separator / TriggerItem / ContextTrigger / CheckboxItem / RadioItemGroup / RadioItem の 18 anatomy パーツを持つ(menu.rs モジュール doc。ItemText/ItemIndicator はイシュー #1651 で参考サイト〔ark-ui/Radix Primitives/chakra-ui〕と突合し追加)。
  • 各パーツ関数は呼び出し側 attrs に含まれる固定属性キー(data-state/role/aria-* 等)を除去してから合成する(drop_reserved、イシュー #1651)。id/aria-labelledby/aria-controls のような Option 引数経由の正規キーは除去対象に含まない。
  • サブメニューは親 Menu インスタンスの content 内に子 Menu インスタンス由来の TriggerItem/Positioner/Content を入れ子で配置して表現し、親子双方に aria-haspopup="menu" を付与する(「haspopup 連鎖」でネストを支援技術へ伝える)。
  • CheckboxItem/RadioItemGroup は Menu の開閉状態とは独立した checked 状態機械(MenuCheckboxItem/MenuRadioItemGroup、Checkable/SingleSelect を埋め込む)を持つ。
  • ContextTrigger は右クリック起点のトリガーであり、ARIA 属性を一切付与しない(右クリックは SSR/no-JS では成立せず、ARIA を付けると JS なしで実現できない操作性を誤って約束するため)。
  • 開閉は Disclosure を埋め込んだ状態機械 Menu が管理する(dispatch は "open"/"close"/"toggle")。
  • 参考サイトとの意図的な差分(イシュー #1651): Portal(DOM 配置)/ data-placement・data-side・data-align の positioner 集約 / data-orientation(content・item)/ chakra ItemCommand(ショートカット表示)/ asChild はいずれも非採用。キーボードは trigger にフォーカスを留めたまま aria-activedescendant + data-highlighted で仮想フォーカスを表現する設計(#583)のため、Escape 後の「trigger へのフォーカス復帰」は構造的に不要で、Tab は無配線(zag は Tab で閉じるが本実装は閉じない)。
  • checkbox-item/radio-item は crates/wasm-full/src/keynav.rs の highlight・typeahead・Enter/Space の click 合成対象に含まれ、crates/wasm-full/src/headless.rs::MAPPING_TABLE の対応行("toggle"/"select")を経由して MenuCheckboxItem/MenuRadioItemGroup へ dispatch される(イシュー #2205 で配線済み)。checked 状態の aria-checked/data-state への DOM 反映は wire_headless_component の on_update で呼び出し側が行う契約のまま。

Anatomy

root
  trigger
    indicator
  positioner
    content
      arrow
        arrow-tip
      item-group
        item-group-label
        item
      separator
      checkbox-item
        item-indicator
        item-text
      radio-item-group
        radio-item
      trigger-item
  context-trigger

API Reference

Arguments

NameTypeDefaultDescription
trigger(disabled)bool—true のとき native disabled 存在属性 + data-disabled を付与する。
trigger(controls)Option<&str>—Some のとき aria-controls で content と関連付ける。
item(value)&str—data-value として出力される項目識別値。
item(disabled)bool—true のとき aria-disabled="true"+data-disabled を付与する(div ベースのため native disabled は持たない)。
item(highlighted)bool—キーボードナビゲーションのフォーカス位置。true のとき data-highlighted を付与する。
item_text(disabled) / item_text(highlighted)bool—親 item 系パーツの状態を data-disabled/data-highlighted として装飾用に複製する(checked 状態は持たない)。
item_indicator(checked)bool—checked_data_state を data-state へ反映し、false のとき hidden 存在属性を付与する。aria-hidden="true" を固定付与(装飾アイコン、二重読み上げ防止)。
trigger_item(sub_state)OpenState—このトリガーが開閉するサブメニュー側の状態(呼び出し側は子 Menu インスタンスの state() を注入する)。
checkbox_item(checked) / radio_item(checked)bool—role="menuitemcheckbox"/"menuitemradio" の aria-checked(true/false のみ、indeterminate 非対応)。

Data Attributes

PartAttributeObserved Values
checkbox-itemdata-statechecked, unchecked
checkbox-itemdata-valuebookmarks, urls
contentdata-stateclosed, open
context-triggerdata-stateclosed
indicatordata-stateclosed, open
itemdata-disabled(値なし)
itemdata-highlighted(値なし)
itemdata-valuecopy, delete, email, message, only, paste, rename
item-indicatordata-statechecked, unchecked
positionerdata-stateclosed, open
radio-itemdata-statechecked, unchecked
radio-itemdata-valueasc, desc
rootdata-stateclosed, open
triggerdata-disabled(値なし)
triggerdata-stateclosed, open
trigger-itemdata-stateopen

Examples

ItemGroup を使わない単純な Item 列

ItemGroup/Separator を省き、Item のみを並べた最小構成です。

CheckboxItem/RadioItem + ItemIndicator/ItemText

ItemIndicator/ItemText(イシュー #1651 で追加)を CheckboxItem・RadioItemGroup と組み合わせる例です。

自前 CSS の最小例

data-scope/data-part 属性セレクタで自前 CSS を当てる最小例です。headless-ui 自体はスタイルを持ちません。

CSS
[data-scope="menu"][data-part="positioner"][hidden],
[data-scope="menu"][data-part="content"][hidden],
[data-scope="menu"][data-part="item-indicator"][hidden] {
  display: none;
}
[data-scope="menu"][data-part="item"][data-highlighted] {
  background: #eff6ff;
}
[data-scope="menu"][data-part="item"][data-disabled] {
  color: #9ca3af;
}
[data-scope="menu"][data-part="checkbox-item"][data-state="checked"] {
  font-weight: 600;
}

Accessibility

Keyboard Interactions

KeyDescription
ArrowDown / ArrowUp / Enter / Space(closed の trigger)Menu を開き、先頭(ArrowUp は末尾)の非 disabled 項目を highlight する(handle_menu_or_select_trigger_keydown、crates/wasm-full/src/keynav.rs)。
ArrowDown / ArrowUp / Home / End(open)highlight 中の項目を次/前/先頭/末尾の非 disabled 項目へ移動する(data-highlighted + content の aria-activedescendant を更新)。既定は循環なし、content の data-loop-focus="true" で有効化できる。
Enter / Space(open、highlight 中の項目)highlight 中の項目へ click を合成する。item は利用者の click ハンドラへ、trigger-item は "toggle" を dispatch する。checkbox-item は "toggle"、radio-item は "select" を dispatch する(crates/wasm-full/src/headless.rs::MAPPING_TABLE、イシュー #2205)。
印字可能文字typeahead(350ms バッファ)。item-text 子があればそのテキストを優先してマッチする。
ArrowRighthighlight 中の項目が trigger-item かつ非 disabled・サブメニューが解決できるときのみサブメニューを展開する(それ以外は menubar 層のトリガー間移動へフォールバックする合図を返す、イシュー #1073)。
ArrowLeftサブメニュー内で親 trigger-item へ復帰しサブメニューを閉じる(トップレベルでは menubar 層のトリガー間移動へフォールバックする合図を返す)。
Escape最上位オーバーレイを閉じる(overlay::close_on_escape_for、data-close-on-escape="false" で無効化できる)。本実装はフォーカスを trigger から離さない設計のため、参考サイトの「Escape 後に trigger へフォーカス復帰」と結果同等になる。
Tab / Shift+Tab配線なし(ブラウザ既定)。zag は Tab で閉じるが本実装は閉じない。

WAI-ARIA

AttributeDescription
aria-haspopup="menu"trigger(およびサブメニューの trigger-item)に固定付与。
aria-expanded / aria-controlstrigger に付与。開閉状態と対応する content の id を表す。
role="menu"content に固定付与。aria-labelledby は labelledby が Some のときのみ出力される。
role="menuitem" / role="menuitemcheckbox" / role="menuitemradio"item/checkbox-item/radio-item にそれぞれ固定付与。
role="group"item-group/radio-item-group に固定付与。labelledby が Some のときのみ aria-labelledby が付与される。
aria-checkedcheckbox-item/radio-item に付与(true/false のみ、indeterminate 非対応)。
aria-hidden="true"item-indicator に固定付与(装飾アイコン、親 checkbox-item/radio-item 自身の aria-checked と重複読み上げしないため)。
aria-orientation="horizontal"separator に固定付与。
aria-disabled="true"item/trigger-item/checkbox-item/radio-item の disabled 時に付与(div ベースのため native disabled は持たない)。
aria-activedescendantcontent に付与(実行時に crates/wasm-full/src/keynav.rs が書く。SSR 出力は行わない)。
(ContextTrigger の ARIA 省略)右クリックは SSR/no-JS では成立せず、ARIA を付けると JS なしで実現できない操作性を誤って約束するため、context-trigger は ARIA 属性を一切持たない。