fandhe-frontend-pre-styled-ui API
1. 目的とトレーサビリティ
本ドキュメントは fandhe-frontend-pre-styled-ui(chakra-ui 参考の pre-styled UI コンポーネント層)の公開 API 表面をまとめる。fandhe-frontend-headless-ui(ark-ui 相当の下層、docs/api/headless-ui-api.md)の上に、テーマトークン・variant API・静的 CSS 生成を重ね、styled 部品を実装する 2 層構造の上層を担う。
2. モジュール一覧(repo main 時点。crates.io 公開状況は §2a 参照)
本クレートは 121 の公開モジュール(grep -c '^pub mod ' crates/pre-styled-ui/src/lib.rs の実測。collapsible はイシュー #1682/#1683、field はイシュー #1684、fieldset はイシュー #1686、input_group はイシュー #2063、item はイシュー #2066、button_group はイシュー #2060、command はイシュー #2070、sidebar はイシュー #2073、message はイシュー #2106、bubble はイシュー #2109、attachment はイシュー #2112、marker はイシュー #2115、questionnaire はイシュー #2119、message_scroller はイシュー #2123 で追加)+ charts サブモジュール群を持つ(charts::bar_chart/charts::bar_list/charts::bar_segment/ charts::scatter_chart/charts::radar_chart/charts::axis/charts::grid/ charts::legend/charts::tooltip/charts::pie/charts::data/ charts::scale/charts::svg は既存の pub mod charts; 配下のサブモジュールであり、grep -E '^pub mod ' によるトップレベル公開モジュール集計には計上されない)。118 は grep -c '^pub mod ' crates/pre-styled-ui/src/lib.rs の実測値である。モジュール一覧・本数の正は下表と上記実測値・各モジュール冒頭 rustdoc とする。部品ごとの詳細(anatomy・Demo・Examples・キーボード操作)は本表に複製せず、各部品ページ(/themes/<kebab>/)へ委譲する。
2a. crates.io 公開状況とドキュメント対象バージョン(イシュー #1118)
本ドキュメントは repo main(開発中の最新コード)を対象として記述しています。crates.io への公開はリリース運用(.github/workflows/release.yml の workflow_dispatch による手動実行、mode: publish の明示選択)を経るため、repo main の到達点から遅延することがあります。利用者が Cargo.toml に指定した実際のバージョンに何のモジュールが含まれるかは、本ドキュメントではなく https://docs.rs/fandhe-frontend-pre-styled-ui/<version>(例: https://docs.rs/fandhe-frontend-pre-styled-ui/0.5.0)で確認してください。
本節は執筆時点(イシュー #1118 対応時)のスナップショットとして、乖離の実例を記録します。crates.io 最新公開バージョンは fandhe-frontend-pre-styled-ui 0.5.0(依存する fandhe-frontend-headless-ui は 0.4.0)であり、本節冒頭の「106 モジュール」(repo main、v0.37.0 時点)に対し、0.5.0 時点では typography・navigation 系モジュール(heading / text / link / breadcrumb / nav_list / separator / list 等)を含む多くのモジュールが未収録です(約 15 モジュール、イシューフィードバックの実測)。この遅延を解消する crates.io への追加公開は、本イシューのコード変更とは別の運用アクションとして実施します(承認境界は .claude/rules/ci.md の release ワークフロー節を参照。本ドキュメントの自動更新は行わないため、公開が進み次第この節を追随更新してください)。
| 分類 | モジュール | 部品ページ |
|---|---|---|
| 基盤 | theme | — |
| 基盤 | css | — |
| 基盤 | recipe(詳細は pre-styled-recipe-api.md) | — |
| 基盤 | stylesheet(CSS 集約・配布ヘルパ、§4a 参照) | — |
| 単純 styled 部品 | button / badge / spinner / alert / callout / card(button は icon_button/close_button を icon-only 修飾 variant として提供。独立部品ではなく button recipe の非公開 variant であり data-scope="button" を共有する。icon_size_for がボタン size からアイコン size を決定的に写像し(イシュー #1674)、close_button はこれを内蔵する。callout は本文中の補足情報向け静的部品で、alert と異なり live region ではないため role を付与しない、イシュー #994。spinner の spinner_decorative(aria-hidden の装飾用、#2051 で pub(crate) から公開化)は Button 末尾配置・Badge・Empty state 等の合成に使う。button/badge は共通 shape 軸(recipe::Shape、ButtonProps/BadgeProps の shape: Option<Shape>、既定 None)を持つ。Pill/Circle とも新規トークンなしで既存 --fandhe-radius-full を参照する、イシュー #3117) | button / badge / spinner / alert / callout / card |
| 単純 styled 部品 | skeleton(ローディングプレースホルダー。形状軸 variant(text/circle/rect)+ アニメーション軸 animation(pulse(既定)/shine/none、イシュー #1566)、常時 aria-hidden="true"、color-palette/size 軸は非提供、prefers-reduced-motion: reduce でアニメーション停止) | skeleton |
| 単純 styled 部品 | image(写真等の静的コンテンツを表示する <img>。ImageFit(object-fit)/AspectRatio(Auto/Square/Landscape/Portrait/Video)/ImageShape(角丸、Square/Rounded/Circle)の 3 軸 variant、alt 必須引数。base は height: auto を持つ(イシュー #1562)。headless-ui avatar の ImageStatus 状態機械とは独立。中立的な表示部品のため color-palette 軸は非提供) | image |
| 単純 styled 部品 | icon(インライン SVG の寸法を統一する <svg> ラッパー。size variant のみ(Xs〜Xl の 5 段、既定 Md。イシュー #1561 で chakra-ui 同名段の実寸へ是正)、color: currentColor 継承のため color-palette 軸は非提供。SVG 本体(path 等)は呼び出し側がノード木 API で構築し、外部リソース(href/xlink:href)は本モジュール自身が参照しない) | icon |
| 単純 styled 部品 | separator(区切り線、<hr>。orientation(horizontal/vertical)・variant(solid/dashed/dotted、イシュー #1585 で dotted 追加)の 2 軸、常時 role="separator"/aria-orientation/data-orientation を出力、罫線の太さは --fandhe-separator-thickness(既定 1px、イシュー #1585)の上書きで変更、color-palette/size 軸は非提供。group/label(pre-styled-only、イシュー #2053、shadcn/ui 突合で補完したラベル付き区切り線パート)。group_with(SeparatorLabelPosition: center〔既定〕/start、イシュー #3137)でラベル位置を opt-in で切り替え可能) | separator |
| 単純 styled 部品 | highlight(テキスト中の一致語句を <mark> で強調する <span> + <mark>。query(複数可)・ignore_case(ASCII 限定)・match_all の 3 プロパティ。一致判定は正規表現不使用の決定的な部分文字列検索のみ(ReDoS 非該当)。color-palette/size 軸は非提供) | highlight |
| 単純 styled 部品 | visually_hidden(視覚的には隠すが支援技術には読ませ続けるテキストコンテナ。variant 軸を持たず clip 手法の CSS のみ。aria-hidden を一切出力しない) | visually-hidden |
| 単純 styled 部品 | skip_nav(WCAG 2.1 SC 2.4.1 Bypass Blocks 対応の「本文へスキップ」リンク。link/content の 2 slot recipe。link は visually_hidden の clip 手法を base に持ち :focus-visible でのみ視覚的に復元する。docs-site の全ページレイアウトへ実適用済み) | skip-nav |
| headless ラッパー | dialog / tabs / accordion / menu / select(dialog はイシュー #2193 で close-trigger に [data-variant="text"] state 規則を追加し、close_trigger_with_variant で footer 内の平文ボタンとしても再利用可能。アイコン専用契約(data-variant 非出力の既存 close_trigger)は不変。menu はイシュー #2203 で item の data-danger(destructive 項目、呼び出し側が attrs 経由で付与する pre-styled-only の存在属性)が data-highlighted と同時に立つ場合の背景色合成を StateCondition::AttrAll(値なし存在属性同士の AND)で実装済み) | dialog / tabs / accordion / menu / select |
| headless ラッパー | popover / tooltip | popover / tooltip |
| headless ラッパー | switch | switch |
| headless ラッパー | radio_group(§4c 参照) | radio-group |
| headless ラッパー | avatar(§4b 参照) | avatar |
| headless ラッパー | checkbox(§4e 参照) | checkbox |
| 静的フォーム部品 | input / textarea / native_select(§4f 参照)。input は共通 shape 軸(InputProps.shape: Option<Shape>、既定 None)を持ち Pill のみ登録(Circle は未登録、イシュー #3117) | input / textarea / native-select |
| 静的フォーム部品 | field(§4f-1 参照。ラベル・補助テキスト・エラーテキストの型階層、orientation 軸のみ) | field |
| 静的フォーム部品 | fieldset(§4f-2 参照。<fieldset>/<legend> グループコンテナ、size 軸のみ) | fieldset |
| 静的フォーム部品 | input_group(§4f-3 参照。入力欄の前後 addon、軸なし) | input-group |
| headless ラッパー | item(§4f-4 参照。media + title/description + actions からなる汎用リスト行。variant/size は headless の data-variant/data-size を AttrEq 参照するのみで class ベース軸を持たない) | item |
| headless ラッパー | command(§4f-5 参照。cmdk 由来のコマンドパレット。headless 10 パーツ + pre-styled-only footer の計 11 パーツ構成、軸なし) | command |
| headless ラッパー | message(§4f-6 参照。会話 1 発言。6 パーツ構成、軸なし。data-role/data-align/data-loading/data-error を AttrEq/Attr 参照するのみ) | message |
| headless ラッパー | bubble(§4f-7 参照。チャット吹き出し 1 個。6 パーツ構成、軸なし。data-variant/data-align/data-group-position/data-selected/data-state を AttrEq/Attr/AttrEqAll 参照するのみ) | bubble |
| headless ラッパー | attachment(§4f-8 参照。添付ファイル 1 件。8 パーツ構成、軸なし。data-variant/data-state/data-disabled を AttrEq/Attr 参照するのみ) | attachment |
| headless ラッパー | marker(§4f-9 参照。会話中の注記行。3 パーツ構成、軸なし。data-variant/data-tone を AttrEq 参照するのみ) | marker |
| headless ラッパー | questionnaire(§4f-10 参照。多段質問 UI。11 パーツ構成、軸なし。data-state/data-answered/data-skipped/data-invalid/data-disabled/data-complete を AttrEq/Attr 参照するのみ) | questionnaire |
| headless ラッパー | message_scroller(§4f-11 参照。会話ログのスクロールコンテナ。6 パーツ構成、軸なし。data-stuck/data-has-new/data-visible/hidden/data-loading/data-disabled を AttrEq/Attr 参照するのみ) | message-scroller |
| headless ラッパー | data_table(§4f-12 参照。表の操作 UI。8 パーツ構成、軸なし。data-loading/data-empty/data-sort/data-state/data-hidden を Attr/AttrEq 参照するのみ) | data-table |
| headless ラッパー | sidebar(§4m 参照。アプリシェル用サイドバー。22 パーツ構成、variant/collapsible/side は headless の data-variant/data-collapsible/data-side を AttrEq 参照するのみで class ベース軸を持たない) | sidebar |
| headless ラッパー | number_input(§4d 参照、size variant のみ・color-palette 軸は非提供) | number-input |
| headless ラッパー | pin_input(size variant のみ) | pin-input |
| headless ラッパー | password_input(src/password_input.rs 冒頭 rustdoc 参照) | password-input |
| headless ラッパー | slider(size/color-palette 両軸提供。動的値は --fandhe-slider-percent custom property の 1 点のみで伝搬) | slider |
| headless ラッパー | rating_group(size/color-palette 両軸、星形 indicator は clip-path インライン表現) | rating-group |
| headless ラッパー | segment_group(§4d 参照、size variant のみ・color-palette 軸は非提供。状態機械は radio_group へ全委譲) | segment-group |
| headless ラッパー | tags_input(size variant のみ。フォーム入力部品のため color-palette 軸は非提供) | tags-input |
| headless ラッパー | editable(size variant のみ・color-palette 軸は非提供) | editable |
| headless ラッパー | listbox(size variant のみ・color-palette 軸は非提供。常時展開(trigger/positioner なし)で select とは責務境界が異なる。詳細は src/listbox.rs 参照) | listbox |
| headless ラッパー | toggle / toggle_group(実フォーカスをネイティブ <button> 自身が受けるため data-focus-visible 配線ではなく FocusVisible state condition で対応。size/color-palette 両軸提供) | toggle / toggle-group |
| カード型選択 UI(styled バリエーション) | checkbox_card / radio_card(§4g 参照。headless-ui は変更なし、pre-styled 層で新規 anatomy checkbox-card/radio-card を定義。状態機械は headless Checkbox/RadioGroup を再利用) | checkbox-card / radio-card |
| headless ラッパー | combobox(select と同型の size variant のみ・color-palette 軸は非提供。状態機械は state::Disclosure + state::SingleSelect + state::TextInput の合成。フォーカスは input が保持するため :focus-visible を input へ、:focus-within を control へ登録する) | combobox |
| headless ラッパー | tree_view(イシュー #1578 で root へのみクラスを付与する size variant(xs〜xl、既定 md。行密度・文字サイズを切り替える)を新設。color-palette 軸は非提供(popover/tooltip と同型の判断)。branch のインデントは CSS custom property(--fandhe-tree-view-indent)で表現し、DOM ネストにより深さ分が自然に累積する(size では上書きしない)) | tree-view |
headless ラッパー(tree_view の派生) | json_tree_view(構造部は tree_view の既存 recipe をそのまま再利用するため tree_view の size variant を継承する。JSON 固有の key/colon/value(data-scope="json-tree-view")3 パーツのうち key/value の型別配色のみを追加する。colon はイシュー #1661 で headless 側に新設されたが本モジュールでは意図的に未スタイル。value の data-kind へ型別配色(string/number/boolean/null の 4 種、object/array は既定色のまま)を適用。本モジュール固有の size/color-palette 軸は非提供) | json-tree-view |
| headless ラッパー | pagination(size/color-palette 両軸提供。root_with 経由で連結表示 variant(PaginationVariant、既定 Separated、イシュー #3136)を opt-in 提供) | pagination |
| headless ラッパー | steps(size/color-palette 両軸。fandhe_frontend_headless_ui::steps が自由関数を持たず全パーツが Steps の inherent メソッドのため、本モジュールの全パーツ関数が state: &Steps を受け取る点が他コンポーネントと異なる。PR #1814 codex-review 対応で追加した body(data-scope="steps" data-part="body")のみ headless に対応物を持たない pre-styled-ui 専用のグルーピングパーツで、state: &Steps を取らずプレーンな <div> を直接構築する。縦向き(Orientation::Vertical)で root が flex-direction: row へ切り替わる際、list 以外を body でまとめて root 直下を list/body の 2 要素に保つ契約〔steps::root rustdoc 参照〕。イシュー #3139 で StepsVariant(Circle 既定/Bar/Panel/Dot)を純追加し、root_with/list_with/item_with/indicator_with/separator_with が Circle 以外のとき該当 slot へ単独クラスを付与する。Circle は既存関数とバイト一致) | steps |
| headless ラッパー | breadcrumb(状態機械なし。size/BreadcrumbVariant(link の下線表示切り替え)の 2 軸 variant を root のみへ付与し、link への伝搬は root スコープ CSS custom property の継承で行う) | breadcrumb |
| headless ラッパー | carousel(size variant のみ・color-palette 軸は非提供(選択・チェック状態を示す部品ではないため)。item-group の transform は --fandhe-carousel-index CSS カスタムプロパティ 1 点のみで伝搬し、data-orientation="vertical" で translateX/translateY を切り替える。autoplay は初期実装スコープ外) | carousel |
| headless ラッパー | drawer(dialog の変種。状態機械は headless の dialog::Dialog をそのまま再利用し新規状態機械は作らない。size(drawer の占有幅/高さ)variant のみを root へ付与し color-palette 軸は非提供。placement(start/end/top/bottom)は variant ではなく headless 層が出力する data-placement に連動する CSS で表現する。dialog と対称の close-trigger text variant(イシュー #2193)も持つが、fandhe-frontend-wasm-full が drawer scope を未配線のため現状 inert。イシュー #3128 で dialog::body/dialog::footer と同型の pre-styled-only body/footer パートを追加した。headless anatomy は変更せず、content の attrs へ data-has-body を渡したときのみ opt-in で flex column 化し body が残り高さを埋めてスクロールする。イシュー #3129 で pre-styled-only header パート(全幅アクセント帯のレイアウト)と、header/description/close_trigger への data-tone="accent" state・content/close_trigger への data-close-outside(外側 close-trigger 配置)を追加した。いずれも headless anatomy 無変更の opt-in 属性で、既存呼び出しには影響しない) | drawer |
| headless ラッパー | link / link_overlay / nav_list(状態機械なし。link_overlay は ::before 疑似要素の代わりに overlay 自身を position: absolute; inset: 0; で展開する。イシュー #1580 で overlay に :focus-visible の共通フォーカスリング・cursor: pointer を追加し、root/overlay 双方へ border-radius: inherit を連鎖させて root の角丸へフォーカスリングが追従するようにした。nav_list は fandhe-frontend-docs-site::nav.rs::sidebar が直接使う想定のため、root 以外(heading/list/item/link)は headless 自由関数をそのまま選択的に再エクスポートする) | link / link-overlay / nav-list |
| headless ラッパー | action_bar(size/color-palette 軸は非提供。positioner の position: fixed; bottom: ...; left: 50%; transform: translateX(-50%) による画面下部固定配置と data-state 連動の見た目切り替えのみを提供する。z-index: 900(menu/select の dropdown positioner(10)より上、dialog backdrop(1000)より下)) | action-bar |
| headless ラッパー | toolbar(イシュー #991。size/color-palette 軸は非提供。root の data-orientation="vertical" で flex-direction: column へ切り替え、separator は aria-orientation の値(toolbar 自身と直交)で向き別の太さを出し分ける。押下状態の管理は独自 CSS を持たず既存の toggle_group recipe と同型の data-state="on" 強調のみ提供する) | toolbar |
| headless ラッパー | button_group(§4d-1 参照。size/color-palette 軸は非提供、root の data-orientation で横並び/縦積み切替、直接の子の角丸連結・境界線二重描画解消は raw CSS 追記で行う。イシュー #2060) | button-group |
| headless ラッパー | menubar(イシュー #992。size/color-palette 軸は非提供。複数 menu を水平(または垂直)に並べるコンテナ。root の data-orientation="vertical" で flex-direction: column へ切り替え、per-menu ラッパーである menu パーツが position: relative(positioner の containing block)を担う。開いている trigger/sub-trigger の視覚強調・virtual focus の highlight 表示は menu recipe と同型) | menubar |
| headless ラッパー | navigation_menu(イシュー #993。size/color-palette 軸は非提供。トリガー起点で開閉するナビゲーションパネル。item に position: relative、content に position: absolute; top: 100%; left: 0; を宣言する一般的なナビゲーションドロップダウン構成。list の align-items は center ではなく flex-start を既定にし、content 展開時に他項目が縦ずれする回帰を構造的に防ぐ。開いている trigger の視覚強調は data-state="open"、アクティブリンクの強調は data-current で行う。イシュー #2187: root へ position: relative を追加し、ルートレベル indicator の絶対配置基準点とする。indicator は --fandhe-navigation-menu-indicator-x/-width(horizontal)・-y/-height(vertical)の CSS 変数契約を既定 0px フォールバック付きで宣言し、実座標が書き込まれない間は不可視(pointer-events: none)。実座標の書き込みは fandhe-frontend-wasm-full の責務) | navigation-menu |
pre-styled-ui 単独定義(headless-ui 変更なし、checkbox_card/radio_card §4g と同型の判断) | tab_nav(イシュー #996。新規 anatomy data-scope="tab-nav" を定義し role="tablist"/role="tab" を一切出力しない。見た目は自前の宣言列から生成する(イシュー #1541 で tabs.rs の pub(crate) ヘルパ共有を解消)。現在ページは aria-current="page" で示す。size 軸を持ち、root_with 経由で pill/bar variant(TabNavVariant、イシュー #3125/#3126)・color-palette 軸を opt-in 提供) | tab-nav |
| headless ラッパー | checkbox_group(イシュー #997。単一選択版 radio_group と対称の構造。item-hidden-input slot を持たず、利用時は checkbox::stylesheet() も併せて読み込む必要がある。size/color-palette 両軸提供) | checkbox-group |
| headless ラッパー | toast(placement(group slot)/status(root slot、ColorPalette 6 役割束ね〔palette_scale_declarations〕による淡色面配色。イシュー #1544)の 2 軸 variant を持つが、各軸が別 slot へ付与されるため variant_class(単一軸専用 API)をスロットごとに個別に呼ぶ。Toaster 状態機械は再エクスポートしない。#1545: action-trigger/close-trigger の hover/focus/disabled・root の mount 時 enter 遷移(@keyframes fd-toast-enter)・group/root のスタック配置固定幅化を追加。close-trigger はアイコン専用契約への破壊的変更(0.64.0)。タイマー自動 dismiss・ActionTrigger の動作配線は wasm-full 後続のスコープ外。イシュー #2543: toast_motion(motion feature 配下)で stack 表示(積層・hover/focus-within 展開)を opt-in 追加。STACK_ATTR/stack_group_keyed/Theme::to_css_with_toast_motion。イシュー #3142: pre-styled-only content/actions パート + ACTIONS_COLUMN_ATTR で右端アクション列(全高 1 ボタン、または縦 2 分割)を opt-in 追加) | toast |
| headless ラッパー | hover_card(popover/tooltip と同型の判断で variant は非提供。構造上最も近い先行例は tooltip。content の開閉連動・--fandhe-reference-width 非消費・focus-visible リングを継承する) | hover-card |
| headless ラッパー | toggle_tip(popover/tooltip と同型の判断で size/color-palette のいずれも非提供。「見た目は Tooltip・挙動は Popover」の変種であり、content の視覚系は tooltip と同一値。状態機械は state::Disclosure) | toggle-tip |
| headless ラッパー | collapsible(glob 再エクスポート。参照 3 サイト(chakra-ui/Ark UI/Radix Primitives)のいずれも size/variant/color-palette を持たないため variant 軸は非提供。開閉時の高さトランジションは @starting-style/transition-behavior: allow-discrete + fandhe-frontend-wasm-full の実測高さ CSS 変数で JS 有効時のみ適用(イシュー #2192、案 C)。イシュー #1682/#1683) | collapsible |
| headless ラッパー | progress(イシュー #1564。headless の値状態機械 Progress が持つ Track/Range(linear)と Circle/CircleTrack/CircleRange(SVG、circular)の両方へ CSS を追加提供。Progress 型はあえて再エクスポートせず、ProgressProps(size/variant/color-palette の 3 軸)を付与する styled root と、determinate 時のみ --fandhe-progress-percent を付与する styled range の 2 つを新設する。track/circle 系は headless の inherent メソッドをそのまま呼ばせる(クラス不要)。indeterminate 時のアニメーション(linear は横スライド・circular は回転)は [data-part="..."][data-state="indeterminate"] セレクタ + @keyframes で提供し、prefers-reduced-motion: reduce で停止する。イシュー #1688: circular indeterminate は circle 全体の回転に加え、circle-range へ固定長の弧〔stroke-dasharray〕を与えて塗り色の完全リングと complete 状態を視覚的に区別できるようにする。イシュー #2049: shadcn/ui と突合し ProgressVariant::Plain(枠線なし中立トラック)を純追加。イシュー #3140: headless-ui の anatomy には存在しない pre-styled-only パート marker_group/marker(マイルストーンの目盛りラベル)を純追加。marker は value と現在値の比較で data-state(under-value/at-value/over-value)を自前算出し、配置は値に比例した絶対配置ではなく CSS Grid の等間隔列が担う) | progress |
| 単純 styled 部品(静的) | tag / kbd / code(tag は variant(Solid/Subtle(既定)/Outline/Surface の 4 値、イシュー #1573 で Surface を追加)/size/color-palette の 3 軸 variant を持つ root/label/close-trigger の 3 パーツ。badge と同型の判断。close-trigger は状態機械を持たず data-action 属性の出力のみを担う。code は variant 軸を持たない単一 slot。kbd は variant(raised(既定)/subtle/outline の 3 値、イシュー #1436)/size/color-palette の 3 軸 variant を持つ root slot に加え、group〔shadcn/ui KbdGroup 相当、イシュー #2048〕の pre-styled-only 2 slot 構成) | tag / kbd / code |
| 状態機械を要しない静的部品 | status / empty_state(§4h 参照。status は size/color-palette の 2 軸、empty_state は card と同型の中立コンテナで color-palette 軸は非提供) | status / empty-state |
| headless ラッパー | clipboard(hover_card/toggle_tip と同型の判断で variant は非提供。Indicator の可視性切り替えは avatar の image/fallback と同型の data-state 多層防御パターン。navigator.clipboard.writeText 実配線は fandhe-frontend-wasm-full::headless_clipboard が提供) | clipboard |
| タイポグラフィ静的部品 | heading / text / em / mark / blockquote / list / quote / strong(§4i 参照。素の HTML 意味論(h1〜h6/p/em/mark/blockquote/ul・ol・li/q/strong)をそのまま styled 化。headless 状態機械は要しない。quote/strong はイシュー #995 で追加。quote は短いインライン引用〔blockquote とは役割が異なる〕、strong は重要性の強調〔em の文法的強勢とは役割が異なる〕) | heading / text / em / mark / blockquote / list / quote / strong |
| headless ラッパー | qr_code(headless の外部依存ゼロ QR Model 2 エンコーダ(crates/headless-ui/src/qr_code.rs)へ CSS のみ追加提供。size variant のみ・color-palette 軸は非提供(前景/背景色は固定トークンに閉じ、低コントラスト組み合わせを誘発しないための意図的判断)。Frame/Pattern/Overlay は headless 自由関数をそのまま選択的に再エクスポートする。overlay は root の --fandhe-qr-code-overlay-size(--fandhe-qr-code-size の 1/3、chakra-ui 準拠)custom property により中央固定表示される、イシュー #1565) | qr-code |
| headless ラッパー(Button recipe 流用) | download_trigger(a[download] 属性による静的ダウンロードトリガー。独自 CSS 宣言を持たず crate::button::recipe_with_scope("download-trigger") へ委譲し、variant/size/color-palette の宣言・既定値を Button と共有する。disabled/loading は a 要素の意味論に存在しないため非提供) | download-trigger |
| 状態機械を持たない静的表示部品 | table / data_list(card と同型。headless-ui 側に対応する anatomy を持たず本クレートで新規 anatomy table/data-list を定義する。table は 10 パーツ(root/header/body/footer/row/column-header/cell/row-header〔<th scope="row">、イシュー #2825〕/caption/scroll-area)で、variant(Line/Outline)/size(Size 5 段、既定 Md)/striped/sticky_header/interactive の 5 軸 variant を持ち、striped は新設の StateCondition::NthChildEven で表現、sticky_header(イシュー #1571)は column-header を position: sticky にする。interactive(イシュー #2052、shadcn/ui 突合)は row に bg-muted の hover 背景を付ける opt-in variant。呼び出し側が付与する共有語彙 data-selected(行選択、accent-subtle/accent-fg-subtle)・data-align(start/center/end、headless positioning.rs と共有)の消費側規則も持つ。scroll_area(イシュー #1572、chakra Table.ScrollArea 相当)は状態を持たない専用パーツで root を包むスクロール枠を提供する。data_list は orientation(Vertical/Horizontal)/variant(Subtle/Bold)/size(Size 5 段、既定 Md)の 3 軸を持つ、イシュー #1559) | table / data-list |
| 静的部品(新規 anatomy) | stat / timeline(ark-ui に対応する headless anatomy が存在しないため pre-styled-ui 層で新規 anatomy data-scope="stat"/"timeline" を定義。stat は <dl>/<dt>/<dd> を使い size variant のみ・color-palette 軸は非提供、増減 indicator は rating_group と同型の clip-path インライン三角形(イシュー #1568 で chakra-ui v3 基準(xs=lg〜xl=4xl の等差進行・letter-spacing/line-height・indicator 余白)へ調整済み)。stat はイシュー #3138 で delta パート(矢印なし・tone(StatDeltaTone: neutral/success/danger)軸で意味色のみを示す増減率表示)を純追加した。tone 軸は delta パートのみへ付与し default_variant を登録しないため root の class 出力には影響しない。timeline は <ol>/<li> を使い variant(TimelineVariant: solid/subtle/outline/plain)/size/color-palette の 3 軸を root のみへ付与し indicator/separator へは CSS custom property の継承で伝搬) | stat / timeline |
| headless ラッパー | floating_panel(fandhe_frontend_headless_ui::floating_panel の Root/Trigger/Positioner/Content/Header/Title/Control/StageTrigger/CloseTrigger/Body 10 anatomy パーツと FloatingPanel 状態機械をそのまま再エクスポートし CSS のみ追加提供する薄いラッパー。variant(size/color-palette)は非提供。content の開閉 data-state に加え body の data-stage="minimized"(折り畳み)・positioner の data-stage="maximized"(ビューポート全面表示)を CSS で切り替える。positioner は position: fixed を基点に headless 側の --fandhe-x/--fandhe-y を transform: translate3d(...) で反映し、z-index は dialog モーダル層(1000/1001)未満・menu/popover の dropdown 層(10)超の専用 tier(900)を割り当てる) | floating-panel |
| headless ラッパー | scroll_area(状態機械なし。variant は非提供(イシュー #1584 で再確認)。viewport へ overflow: auto + scrollbar-width/scrollbar-color(標準プロパティ)を付与し、stylesheet() が recipe().css() に続けて ::-webkit-scrollbar 系規則を固定文字列として追記する。scrollbar/thumb/corner は JS によるスクロール位置追従がスコープ外のため初期実装では display: none にしてネイティブスクロールバーの装飾で代替する。thumb 色は custom property --fandhe-scroll-area-thumb-bg(既定 fg-subtle → border-emphasized → border のフォールバック連鎖、hover/focus 時は --fandhe-scroll-area-thumb-hover-bg = fg → 同じ fg-subtle → border-emphasized → border の連鎖へ強調)で一元化されており、利用側が root へ再定義するだけで chakra variant="hover" 相当の見た目を variant 軸を新設せず再現できる。フォーカスリングは focus_ring_declarations(Token/Inset)による canonical 表現、イシュー #1584。イシュー #2054(shadcn/ui 突合)で viewport の opt-in 属性 2 個を補完: data-orientation="horizontal"(横スクロール、headless data_attrs::data_orientation と共有する既存語彙、content を display: flex; width: max-content; にする子結合子規則)・data-fade(端フェード、mask-image の linear-gradient。animation-timeline: scroll() 対応ブラウザでは @supports 配下でスクロール量連動、非対応時は両端固定フェード。custom property --fandhe-scroll-area-fade-size(既定 min(12%, 2.5rem))/--fandhe-scroll-area-fade-reveal(既定 2rem)/--fandhe-scroll-area-fade-start/--fandhe-scroll-area-fade-end を持つ)) | scroll-area |
| headless ラッパー | splitter(size variant のみを root へ持ち resize-trigger の厚みと panel の余白(--fandhe-splitter-panel-padding)へ継承、color-palette はセパレータの強調色にのみ使う。動的値は panel の --fandhe-splitter-size(flex-basis 経由)の 1 点のみ。resize-trigger はネイティブ <div tabindex> が実フォーカスを受けるため FocusVisible state condition で足りる。root は 1px 外枠・角丸・背景を持つ) | splitter |
| 単純 styled 部品 | marquee(ark-ui の Root/Viewport/Content/Item/Edge anatomy を root/content/item の 3 パーツへ縮約(Viewport は root が兼ね、Edge は呼び出し側 CSS で代替可能なため非提供。イシュー #1582 で root の mask-image による opt-in フェード(--fandhe-marquee-fade、既定 0px)を追加提供。イシュー #1583 で root の内側余白 opt-in フック(--fandhe-marquee-padding、既定 0)を追加提供)。content を内部で 2 回複製しシームレスループを実現し、2 個目は常時 aria-hidden。direction(Start/End)の 1 軸 variant のみを root へ付与し content への伝搬は --fandhe-marquee-direction custom property の継承で行う。color-palette/size 軸は非提供。gap は既定で --fandhe-space-4 へフォールバックする。CSS のみ(JS ゼロ)・hover/focus-within での常時一時停止・prefers-reduced-motion: reduce でのアニメーション停止に加え複製 content の除去・可視コピーの折り返し全文表示・両端フェード解除(イシュー #1583)という決定的設計) | marquee |
| headless ラッパー | date_input(fandhe_frontend_headless_ui::date_input の Label/Control/SegmentGroup/Segment/HiddenInput を選択的再エクスポートし、状態機械 DateInput はあえて再エクスポートしない。size variant のみを root へ持ち --fandhe-date-input-* custom property 経由で segment/segment-group へ継承、color-palette は非提供。segment はネイティブ <input> ではなく div role="spinbutton" のため FocusVisible state condition で足りる) | date-input |
| 単純 styled 部品(静的) | color_swatch(headless-ui に対応する anatomy を新設しない(headless 列は「—」のまま)。色値は fandhe_frontend_headless_ui::color::Color 型のみを受け取り(本モジュールが再エクスポート)、任意文字列を受け取る API は持たない。size(Xs〜Xl の 5 段、既定 Md。イシュー #1558 で chakra-ui 同名段の実寸へ是正)/shape(Square/Circle/Rounded、既定)の 2 軸 variant。color-palette 軸は非提供(表示する色そのものが value で決まるため)。透過色は background-image の 2 レイヤー(前面に色レイヤー、背面に固定チェッカーボード模様)で表現する。内側 1px の輪郭リング(box-shadow: inset、--fandhe-color-border-muted トークン参照)を持ち、淡色・低アルファ色でも外形が判別できる(イシュー #1558)。base は inline-flex 中央寄せ) | color-swatch |
| headless ラッパー(canvas 非依存) | color_picker(fandhe_frontend_headless_ui::color_picker::ColorPicker(HSV + アルファ + Disclosure)はあえて再エクスポートしない(headless-ui から直接 import する)。動的値は custom property の注入 5 箇所のみ: area_background の --fandhe-color-picker-hue-color・area_thumb の --fandhe-color-picker-x/-y・channel_slider_track(Channel::Alpha のみ)の --fandhe-color-picker-alpha-color・channel_slider_thumb の --fandhe-color-picker-thumb-percent・trigger の --fandhe-color-picker-preview。size/color-palette variant・saturation-slider/value-slider 専用スタイル(2 次元 area が代替)は非提供(最小サブセット、スコープ外は crates/pre-styled-ui/src/color_picker.rs rustdoc 参照)) | color-picker |
| headless ラッパー | file_upload(size variant のみ・color-palette 軸は非提供(フォーム入力部品)。実操作対象の dropzone へ :focus-visible を登録(ネイティブ <input type="file"> は視覚的に非表示にするため)。hidden-input slot は CSS 非登録。FileUpload 状態機械はあえて再エクスポートしない。styled root は (size, &FileUploadProps, dragging, attrs, children) へシグネチャ変更し、headless 側の破壊的変更(FileUploadProps/ItemType 新設、参照突合イシュー #1609)へ追随した) | file-upload |
| headless ラッパー | calendar(root_with 経由で variant(outline / plain)・cell-size(compact / large)を opt-in 提供(#3132)。color-palette 軸は非提供。day-trigger の data-selected/data-today/data-outside-month/data-disabled を CSS で切り替える。Calendar 状態機械はあえて再エクスポートしない。day_trigger の date 引数向けに PlainDate/Weekday(fandhe_frontend_headless_ui::date)も再エクスポートする) | calendar |
| headless ラッパー | date_picker(popover 基盤(state::Disclosure)を再利用する crate::calendar と同型の判断。size variant のみ。content 内部に crate::calendar の styled パーツを合成する想定。DatePicker 状態機械はあえて再エクスポートしない。イシュー #1627 以降、styled root は &DatePickerProps を追加引数として headless root へ透過する) | date-picker |
| headless ラッパー | timer(clipboard と同型の判断で variant は非提供。item-value に font-variant-numeric: tabular-nums を付与し桁の増減時のレイアウトシフトを防ぐ。root の data-state(completed / paused)に応じて --fandhe-timer-value-color 経由で item-value の色を切り替え、action-trigger は outline 風ボタン(hover / focus-visible リング / disabled / transition)を持つ。実 tick 駆動(setInterval)は fandhe-frontend-wasm-full::headless_timer が提供する) | timer |
| 静的部品(新規 anatomy、charts 基盤上層) | charts::scatter_chart / charts::radar_chart(charts(ChartData/LinearScale/SVG ヘルパー)の上に実装。headless-ui は変更なし、pre-styled 層で新規 anatomy data-scope="scatter-chart"/"radar-chart" を定義。scatter_chart は ChartData では表現できない (x, y) 数値ペアの集合を独自の ScatterData/ScatterSeries で表現し、x/y 双方の LinearScale で 2 軸写像する。radar_chart は ChartData(カテゴリ = 軸、系列 = ポリゴン)をそのまま使い、軸 index i・軸数 n から頂点角度 θ_i = -π/2 + i・2π/n(12 時方向開始・時計回り)を算出する private ヘルパへ角度→座標変換を一元化する。軸数 3 未満は ChartError::TooFewAxes、負値は ChartError::NegativeValue、プロット領域が小さすぎる場合は ChartError::PlotAreaTooSmall として構築時に拒否する(fail-closed)。size/color-palette variant は非提供(色は系列インデックスからの series_color_var インライン fill 属性で決まる静的部品)。イシュー #2085 で radar_chart へ grid/fill/dots/axis-label/radius-axis の静的バリアントを純追加した(下記「RadarChart の shadcn/ui 突合バリアント」節参照)) | scatter-chart / radar-chart |
| headless ラッパー | tour(fandhe_frontend_headless_ui::tour が自由関数を持たず全パーツが Tour の inherent メソッドのため、本モジュールの全パーツ関数が state: &Tour を受け取る点は steps と同型。color-palette 軸のみ提供、size 軸は初版スコープ外(overlay 系の寸法は呼び出し側の CSS カスタムプロパティ上書きに委ねる)。backdrop/spotlight/positioner は position: fixed の全面オーバーレイで、closed 時 [hidden] を明示規則で display: none に固定する。positioner は data-side/data-align に応じた静的フォールバック配置のみ(実座標追従は fandhe-frontend-wasm-full 後続の責務)。spotlight は位置・寸法の --fandhe-tour-spotlight-x/-y/-width/-height に加え、角丸・縁取り幅の --fandhe-tour-spotlight-radius/-ring-width(イシュー #1550 新設)を持つ計 6 CSS 変数(既定値つき var())で表現し、実測値の注入は同後続の責務。縁取り色は var(--fandhe-palette, var(--fandhe-color-accent, #3182ce))(Theme の accent light 既定値へのリテラルフォールバックを含む三段)に連動し、backdrop/spotlight の暗幕・z-index は --fandhe-color-bg-overlay/--fandhe-z-index-overlay、positioner は --fandhe-z-index-modal トークンへ揃えている(イシュー #1550)。イシュー #1551(0.65.0、破壊的変更)で content に border/radius-lg、scope 付き上書き変数 --fandhe-tour-content-padding/-content-max-width(既定値つき)を追加し、close-trigger を固定正方(--fandhe-space-8)+ overflow: hidden のアイコン専用ゴーストボタンへ変更した(呼び出し側は 1 グリフ + aria-label で渡す契約。テキスト children を渡す既存呼び出しは切り詰められる観測可能な破壊的変更)。action-trigger は solid palette 小ボタンへ是正し、文字色を --fandhe-palette-fg(未選択時 --fandhe-color-accent-fg)へ、hover(--fandhe-hover-bg 経由)・disabled([disabled]/[data-disabled])・transition_declarations・focus_ring_declarations(FocusRingColor::Palette, ...) を追加した。両トリガーの :focus-visible は canonical ヘルパ経由(生 outline 直書きは撤去) | tour |
| 基盤(外部依存ゼロ SVG 生成) | charts(data/scale/svg。ChartData/Series・LinearScale・svg::{fmt_coord, ViewBox, svg_root, PathBuilder, circle, line, rect, group, svg_text} を提供する消費者向け基盤のみ。自身は UI コンポーネントを持たない。詳細は docs/design/charts-foundation-design.md 参照) | charts |
charts 基盤の消費者(新規 anatomy) | line_chart / area_chart / sparkline(§4k 参照。charts 基盤の最初の消費者。area_chart はイシュー #2081 で、line_chart はイシュー #2083 で、いずれも charts::curve(曲線補間ジオメトリ、両者で共有)を用いた曲線/データ点/軸・グリッドの静的バリアントを追加済み(area_chart はさらに積み上げ/グラデーション、line_chart はさらに値/カテゴリラベル)。sparkline の曲線補間は引き続き別イシューのスコープ) | line-chart / area-chart / sparkline |
| charts(SVG) | charts::bar_chart(縦/横 orientation のグループ棒グラフ。値軸はベースライン 0 起点、カテゴリ軸はバンドレイアウト(両端 10% padding + 系列数で均等割り)。系列色は series_color_var(chart-1〜chart-6 循環)。イシュー #2082 で shadcn/ui Charts(bar)と突合し、BarStack(積み上げ Normal/Expand)・corner_radius(角丸)・BarLabel(値ラベル Outside/Inside)・active_index(強調表示)・color_by_category・highlight_negative・show_value_axis/show_grid/show_category_labels を BarChartProps へ純追加。凡例は引き続き呼び出し側が charts::legend を並べる。ツールチップは対象外(#2086)、詳細は bar_chart.rs rustdoc「shadcn/ui 突合(イシュー #2082)」節参照) | bar-chart |
| charts(HTML) | charts::bar_list(単一系列のランキング型バーリスト。バー幅は系列内最大値に対する比率(--fandhe-bar-list-percent custom property)。最大値 0 は全バー幅 0% を決定的に描画) | bar-list |
| charts(HTML) | charts::bar_segment(単一系列の構成比 100% 積み上げバー + 凡例。セグメント幅は系列合計に対する比率(--fandhe-bar-segment-percent custom property)、配色はカテゴリ index で series_color_var を循環。系列合計 0 は ChartError::ZeroTotal で構築時に拒否) | bar-segment |
| 単純 styled 部品(新規 anatomy、charts 基盤の初のチャート部品) | pie_chart / donut_chart(charts 基盤(charts::pie の円弧ジオメトリ・charts::svg::PathBuilder::arc_to)を用いた円グラフ・ドーナツグラフ。ark-ui に対応する headless anatomy がないため新規 anatomy data-scope="pie-chart"/"donut-chart" を本クレートのみで定義する。stacked: false(既定)は系列 1 本専用(data.series().len() != 1 は PieChartError::MultiSeries で fail-closed 拒否)。size variant のみ、color-palette 軸は非提供(セグメント配色は charts::series_color_var の chart-1〜6 循環で決まるため)。donut_chart は追加で inner_ratio(既定 0.6、0.0 < ratio < 1.0 を検証)を持つ。イシュー #2084 で shadcn/ui Charts(pie)と突合したバリアント補完は下記「PieChart / DonutChart の shadcn/ui 突合バリアント」節参照) | pie-chart / donut-chart |
| 単純 styled 部品(新規 anatomy、イシュー #2079) | radial_chart(shadcn/ui Charts Radial 相当の同心リング型グラフ。charts::pie の環状セクタジオメトリ(角丸端は本イシューで新設した charts::pie::annulus_sector_rounded_path)を再利用する。ark-ui に対応する headless anatomy がないため新規 anatomy data-scope="radial-chart" を本クレートのみで定義する。pie_chart/donut_chart と異なり複数系列の積み上げ(stacked)に対応。プロパティは度数法・12 時方向 0°・時計回り正(start_angle_deg/end_angle_deg、既定 0.0..360.0)。inner_ratio(既定 0.3)・corner_radius(既定 0.0)・show_track/show_labels/show_grid・center_text を持つ。size variant のみ、color-palette 軸は非提供。エラー型は既存 PieChartError/ChartError とは独立の RadialChartError(InvalidAngleRange/InvalidInnerRatio/InvalidCornerRadius/NegativeValue/ZeroTotal)。docs サイトページは #2080 で登録) | radial-chart |
| headless ラッパー(非採用の再導入) | angle_slider(size/palette variant のため styled root(slider と同型)を再定義し、pub use ...::* ではなく必要な識別子のみを選択的に再エクスポートする。動的な回転角は --fandhe-angle custom property の 1 点のみで伝搬し thumb_styled が一元的に組み立てる(headless 自由関数 thumb は事故防止のため意図的に非公開のまま内部委譲)。状態機械 AngleSlider は slider の Slider 非再エクスポートと同型の判断であえて再エクスポートしない) | angle-slider |
| headless ラッパー(非採用の再導入) | signature_pad(canvas を使わない決定的 SVG path 方式。root/control/segment/clear_trigger を本モジュールで再定義する qr_code と同型の選択的 re-export(label/segment_path/guide/hidden_input はそのまま再エクスポート)。raw_html() を使用せず、CSS 宣言値はすべてコンパイル時静的リテラル。wasm 配線済み) | signature-pad |
| headless ラッパー(非採用の再導入) | image_cropper(Root/Viewport/Image/Grid/Handle をそのまま再エクスポート(ImageCropperProps/GridAxis/KeyModifiers/action_for_key 等イシュー #1610 の新規公開項目も含む)し、crop 矩形(整数)のみの決定的状態機械。動的な位置・寸法は --fandhe-image-cropper-x/-y/-w/-h の 4 custom property のみで伝搬し selection が一元的に組み立てる(headless 自由関数 selection は意図的に非公開。イシュー #1610 で state/props 引数が増え、フォーカス可能な要素が handle から selection へ移ったため :focus-visible のフォーカスリングも selection へ移設)。状態機械 ImageCropper は slider と同型の判断であえて再エクスポートしない。canvas 実切り出し・pointer ドラッグ配線はスコープ外) | image-cropper |
| headless 由来ユーティリティ(本クレートに固有モジュールなし) | format / Locale | 本クレート自身は format モジュールを持たず、クレートルート再エクスポート pub use fandhe_frontend_headless_ui;(§3a)経由で fandhe_frontend_pre_styled_ui::fandhe_frontend_headless_ui::format::{format_byte, format_number, format_time, format_relative_time} および Locale(En/Ja)へ到達できる。API 詳細は本クレートで二重管理せず docs/api/headless-ui-api.md を正とする(部品ページなし) |
各 headless ラッパーモジュールは対応する fandhe_frontend_headless_ui モジュールの anatomy パーツ・状態機械を薄く再エクスポートし、stylesheet()(モジュールにより css())で既定 CSS を追加提供する共通設計方針を採る。詳細・スコープ外事項は各モジュール冒頭の rustdoc または対応する部品ページを参照(例: switch は src/switch.rs、avatar/ radio_group は §4b/§4c、checkbox は §4e、input/textarea/ native_select は §4f)。switch/radio_group/checkbox の size/ color-palette variant 拡張の詳細は §4c・§4d・§4e を参照。
クレートルート再エクスポート(fandhe_frontend_headless_ui / fandhe_frontend_core / OpenState / Orientation ほか)は §3a を参照。
examples/headless-pre-styled-ui は本クレート v0.4.0(fandhe-frontend-pre-styled-ui = "0.4.0"、crates.io バージョン依存)へ統合済みである。旧来 headless-ui の data-scope/data-part/data-state セレクタへ手書きで当てていたコンポーネント CSS は撤去され、src/main.rs の build_stylesheet() が Theme/SlotRecipe から生成した CSS を stylesheet::StyleSheet で集約し dist/assets/ui.css へ書き出す方式へ切り替え済み。static/ui.css はショーケースページ固有の骨格レイアウトのみを保持する形で残存する。
3. 不変条件(実装済み・骨格に記載済み、src/lib.rs 参照)
- コンポーネントは
fandhe_frontend_headless_ui経由でfandhe_frontend_core::Nodeを返す通常の Rust 関数として実装する(REQ-5、マクロ DSL は採用しない)。 - 出力は
fandhe_frontend_core::renderの既定エスケープを必ず経由する。raw_html()の使用はstylesheet::StyleSheet::style_element内のレビュー済み 1 箇所(#[expect(clippy::disallowed_methods, ...)]付き)に限定する(§4a 参照)。新たなエスケープ迂回経路を作らない。 #によりクレート全体でunsafeを機械的に禁止する。- 外部依存は
fandhe-frontend-headless-ui(path)のみ。fandhe-frontend-coreへの直接依存は宣言しない(headless-ui 経由で間接的に利用する。fandhe-frontend-coreはスモークテスト用の dev-dependency としてのみ許容する)。
これらの不変条件は実装済み各モジュール(§2 参照)でも維持されている(.claude/rules/coding-rust.md・docs/api/headless-ui-api.md §6 と同一の制約を上層でも維持する)。
3a. headless 型の再エクスポート契約
fandhe-frontend-headless-ui の 7 モジュール(tabs/accordion/dialog/ menu/select/popover/tooltip)を薄くラップする各 pre-styled-ui モジュールは、pre-styled-ui のみへの依存でラッパーを呼び出せることを保証する契約として、以下を明示 pub use で再エクスポートする(棚卸し表、crates/pre-styled-ui/src/{tabs,accordion,dialog,menu,select,popover,tooltip}.rs の各ファイル冒頭の pub use 直後のコメント参照)。tabs/accordion/ dialog/menu/select の 5 モジュールは size variant クラス付与のため styled root(tabs のみ tabs)を各モジュールで新設しており、headless 自由関数 root(tabs は tabs/tabs_with_root_attrs)との名前衝突を避けるため選択的 re-export とする(§4d 参照)。popover/tooltip を含め、styled パーツ関数を再定義しないモジュール 12 件は glob 再エクスポートを維持する(tree_view はイシュー #1578 で size variant 導入に伴い選択的 re-export(規約 A)へ移行したため本数から外れた。現況は popover/tooltip の 2 件に留まらない。一覧・維持条件は §3c 参照)。
| pre-styled-ui モジュール | 再エクスポートする headless 型 | 由来 |
|---|---|---|
tabs | Orientation | data_attrs |
accordion | OpenState / SingleSelectAction / MultiSelectAction | state |
dialog | OpenState / DisclosureAction | state |
menu | OpenState / DisclosureAction / CheckableAction / SingleSelectAction | state |
select | OpenState | state |
popover | OpenState / DisclosureAction | state |
tooltip | OpenState / DisclosureAction | state |
combobox | OpenState | state(select と同型の選択的 re-export) |
tree_view | OpenState / MultiSelectAction / SingleSelectAction | state(イシュー #1578 で size variant 導入に伴い本体の再エクスポートは選択的(規約 A)へ移行したが、state 由来の型はこれまでどおり明示再エクスポートする) |
toggle_tip | OpenState / DisclosureAction | state(tooltip と同型の glob re-export) |
collapsible | OpenState / DisclosureAction | state(toggle_tip と同型の glob re-export、イシュー #1682) |
select は共通 shape 軸(recipe::Shape)・見た目 variant 軸(SelectVariant)・選択インジケータ位置軸(select::ItemIndicatorPlacement)を root_with(size, shape: Option<Shape>, variant: SelectVariant, item_indicator_placement: ItemIndicatorPlacement, state, props, attrs, children) で提供する(既存 root(size, state, props, attrs, children) はシグネチャ不変のまま shape: None + SelectVariant::Outline + ItemIndicatorPlacement::End で root_with へ委譲、shape はイシュー #3117、variant はイシュー #3121、item_indicator_placement はイシュー #3124)。Pill は root の CSS 変数 --fandhe-select-trigger-radius を上書きし、trigger の border-radius へ伝搬する。Circle は未登録(select の trigger は value-text + indicator を横並びにする構造で真円にする用途がないため)。SelectVariant(Outline〔既定〕/Subtle)は native_select:: NativeSelectVariant と同型の軸で、Subtle は root の CSS 変数 --fandhe-select-trigger-bg/--fandhe-select-trigger-border-color を上書きし trigger を淡色背景・枠線なしにする。Outline(既定)は default_variant を登録していないため class を一切出さず、root() の既存出力はバイト単位で不変。ItemIndicatorPlacement::Start(既定は End)は root の CSS 変数 3 本(--fandhe-select-item-indicator-order/ --fandhe-select-item-indicator-margin-left/ --fandhe-select-item-indicator-placeholder-display)を切り替える(order + 空き枠方式)。item-indicator は選択状態にかかわらず常に DOM へ出力され非選択時のみ hidden 属性が付く既存ノードをそのまま空き枠に転用する: state 規則([data-state])で order: -1・margin-left: 0 を全項目のインジケータへ適用し、[hidden] 規則で非選択項目のインジケータを visibility: hidden のまま同じ幅を占有させる。絶対配置・疑似要素は使わないため item の padding 等の既存宣言には一切触れず、既存フック --fandhe-select-item-padding による余白調整はそのまま効く。End(既定)は class を一切出さず既存出力はバイト不変。
ActivationMode/TabItem/TabsProps(tabs)・DialogRole/ContentIds(dialog)・SelectAction(select)は各 headless モジュール内定義のため既存の glob 再エクスポートで到達可能であり、追加の再エクスポートは不要(モジュール自身の impl Component の Action として使う場合を含む)。
加えて、クレートルート(crates/pre-styled-ui/src/lib.rs)から次を再エクスポートする。
pub use fandhe_frontend_headless_ui;: headless 層クレートそのもの。headless-ui が core に対して行う再エクスポートと同型のエスケープハッチであり、各ラッパーモジュールの glob では届かない headless API 全域(positioning/aria等)への到達路を確保する。pub use fandhe_frontend_headless_ui::fandhe_frontend_core;:Nodeを組み立てる core API(el/text/render等)への推移的再エクスポート。fandhe_frontend_pre_styled_ui::fandhe_frontend_core::{el, text, render, Node}という単独依存パスを完結させる(Cargo.tomlへfandhe-frontend-coreへの直接依存を追加しない、不変条件 4 を維持)。pub use fandhe_frontend_headless_ui::{OpenState, Orientation};: ラッパー呼び出しに頻出する状態値。pre-styled-ui 単独依存での import を可能にする。
セキュリティ上の注意(REQ-1、.claude/rules/security.md A03): fandhe_frontend_pre_styled_ui::fandhe_frontend_core 経由で raw_html() へ到達できる経路が増えるが、raw_html() 自体は既存の明示的オプトイン API であり、本契約は新たな迂回経路を作らない(headless-ui が確立した既存パターンの推移)。pre-styled-ui 内部の不変条件(raw_html() の使用は [stylesheet::StyleSheet::style_element] 内の 1 箇所限定)は「使用」に関する規約であり、pub use によるクレート到達性の追加はこれに抵触しない。
固定テストは crates/pre-styled-ui/tests/headless_reexports.rs(import を fandhe_frontend_pre_styled_ui:: パスのみに限定し、コンパイルと実行時アサーションの両方で契約を固定する)。
3b. interactive 層の再エクスポート契約
fandhe-frontend-headless-ui は pub use fandhe_frontend_interactive;(クレート再エクスポート)を持ち、fandhe-frontend-pre-styled-ui はそれを推移的に pub use fandhe_frontend_headless_ui::fandhe_frontend_interactive; で再エクスポートする。ルートへの個別型再エクスポート(Component 等をルート直下へ置く構成)は行わない。
棚卸し表(クレート再エクスポートにより全到達可能)
| 項目 | 到達パス |
|---|---|
Component | fandhe_frontend_pre_styled_ui::fandhe_frontend_interactive::Component |
Hydrate | 同上 ::Hydrate |
dispatch | 同上 ::dispatch |
HydrateError | 同上 ::HydrateError |
render_for_hydration | 同上 ::render_for_hydration |
HYDRATE_ATTR_PREFIX | 同上 ::HYDRATE_ATTR_PREFIX |
codec モジュール | 同上 ::codec |
DirtyTracked | 同上 ::DirtyTracked |
同型で fandhe_frontend_headless_ui::fandhe_frontend_interactive::{...}(headless-ui 単独依存経由)でも到達可能。
固定テスト
crates/headless-ui/tests/interactive_reexport.rs: headless-ui のクレート再エクスポート到達性を、styled Dialog 相当(headless のDialog)の SSR → dispatch 往復と、改ざん属性によるHydrateError到達(panic しない)で固定する。crates/pre-styled-ui/tests/interactive_reexports.rs: pre-styled-ui の推移的再エクスポート到達性を、styled Dialog/Accordion/Switch の SSR/hydration/dispatch 往復とHydrateError到達で固定する。import はfandhe_frontend_pre_styled_ui::パスのみに限定する。crates/pre-styled-ui/tests/headless_reexports.rsは import をfandhe_frontend_pre_styled_ui::fandhe_frontend_interactive::{...}(再エクスポート経由)に限定し、契約テストとしての純度を保つ。
セキュリティ上の注意(REQ-1)
fandhe_frontend_interactive は raw_html() を公開せず、Component::view/render_for_hydration の戻り値は Node のみで既定エスケープを必ず経由する(interactive の不変条件 1)。本再エクスポートは新たな出力経路・エスケープ迂回を一切作らない。Hydrate::from_hydration_attrs は DOM 属性を改ざんされうる入力として扱い panic せず HydrateError を返す契約(interactive 不変条件 3)も、再エクスポートで弱まらないことを固定テストで検証している。
3c. 再エクスポートの形式規約(glob / 選択的 / shadowing、イシュー #1062)
各 styled モジュールが headless の対応モジュールを再エクスポートする形式(glob 併用か選択的個別かの選択、および暗黙 shadowing の禁止)の規約は crates/pre-styled-ui/src/lib.rs「headless 再エクスポートの形式規約(イシュー #1062)」節を正とし、本書では二重管理しない。要約:
- 既定は選択的個別再エクスポート(規約 A)。styled パーツ関数を再定義するモジュールは、headless の同名自由関数・未スタイル inherent メソッドを持つ状態機械型を再エクスポートしない(fail-closed。
avatar/breadcrumb等の既存モジュールがこの運用実体)。 - glob 再エクスポート(規約 B)は「トップレベル
pub項目がstylesheet()/css()のみ・variant 軸を提供しない・CSS 到達が[data-scope]/[data-part]属性セレクタのみ」の 4 条件(マーカーコメントREEXPORT-GLOB-REVIEWED:を含む)を満たす場合のみ許可する。イシュー #1062 のレビューにより、現時点でaction_bar/popover/hover_card/tooltip/toolbar/scroll_area/toggle_tip/menubar/json_tree_view/floating_panel/timer/navigation_menu/collapsible(イシュー #1682)の 13 モジュールが条件を満たすと判定済み(tree_viewはイシュー #1578 でsizevariant を持つに至り規約 A(選択的)へ移行したため本一覧から外れた。レビュー来歴はdocs/internal/pre-styled-ui-implementation-notes.md§3c 参照)。 - glob 由来の名前をローカル定義・明示
pub useで上書きする暗黙 shadowing は禁止する(規約 C)。同名を styled 側で提供したい場合は規約 A(選択的)へ移行する。
機械検知は crates/pre-styled-ui/tests/reexport_policy.rs(glob 一覧の双方向一致・マーカーコメント有無・許可 pub 項目・root 再定義との併存禁止の 4 検査、いずれも fail-closed)が担う。
4. 設計方針
- テーマトークン: 色・スペーシング等のデザイントークンとダークモード切り替えの基盤。chakra-ui の
system/recipe相当の設計を参考にしつつ、静的 SSR 出力(ビルド時に確定する CSS)を前提とする。詳細はthemeモジュール rustdoc を参照。トークン API・既定値上書き(upsert_*)の詳細は §4l 参照。 - variant API・静的 CSS 生成: chakra-ui の slot recipe 相当。コンポーネントの見た目バリエーション(size/variant/colorPalette 等)を型安全に選択し、対応する静的 CSS を生成する。詳細は
pre-styled-recipe-api.mdを参照。 - styled 部品: 単純な部品(Button 等)に加え、headless-ui の Accordion/Dialog/Popover/Tooltip/Switch/RadioGroup/Avatar 等をラップした styled 版を提供する(一覧は §2 の表を参照)。
4a. stylesheet::StyleSheet(recipe / theme CSS の書き出し・埋め込みヘルパ)
SlotRecipe::css()・Theme::to_css()・各 styled 部品の css()/stylesheet() は決定的な CSS 文字列を返すのみで、その先の配布は呼び出し側任せだった(examples/headless-pre-styled-ui の手書き static/ui.css コピーが実例)。stylesheet::StyleSheet はこれを集約し、2 つの配布経路を提供する。
StyleSheet::new()/push_css(&mut self, css: &str) -> Result<(), StylesheetError>: 唯一の fallible な取り込み口。<を含む、または改行・タブ・復帰以外の制御文字を含む入力はErr(StylesheetError::CssRejected { .. })になる(fail-closed)。push_recipe(&mut self, recipe: &SlotRecipe)/push_theme(&mut self, theme: &Theme): 生成側 allowlist 検証(<を構成不能にする)に依拠した infallible な薄いラッパ。as_css(&self) -> &str: 取り込んだ CSS 全量。write_css_file(&self, path: &Path) -> std::io::Result<()>: 静的.cssファイルへの書き出し(SSG・ビルドスクリプト向け。親ディレクトリを自動作成)。style_element(&self) -> Node: SSR 用<style>要素ノード。本クレートでraw_html()を使用する唯一の箇所(§3 の不変条件 2 の例外)であり、呼び出し文に#[expect(clippy::disallowed_methods, reason = "ESCAPE-REVIEWED: ...")]を付与済み。StyleSheetは private フィールドのみで構成され、検証済み CSS 以外から構築する経路を公開しないため、呼び出し側へエスケープ迂回経路を公開しない。
use fandhe_frontend_pre_styled_ui::stylesheet::StyleSheet;
use fandhe_frontend_pre_styled_ui::theme::Theme;
let mut sheet = StyleSheet::new();
sheet.push_theme(&Theme::default());
sheet.push_css(&fandhe_frontend_pre_styled_ui::button::css()).unwrap();
// SSG: 静的ファイルとして配信する
sheet.write_css_file(std::path::Path::new("static/ui.css")).unwrap();
// SSR: <style> 要素として埋め込む(render() が既定エスケープを適用する
// 他のノードと同様に合成できる)
let _style_node = sheet.style_element();4b. avatar(Avatar の styled ラッパー)
fandhe_frontend_headless_ui::avatar(Root/Image/Fallback の 3 anatomy パーツと Avatar 状態機械)を薄く再利用し、stylesheet() で既定 CSS を追加提供する(設計方針は crate::dialog/crate::tooltip と同じ、src/avatar.rs 冒頭の rustdoc 参照)。
- 選択的 re-export(
Avatar型は再エクスポートしない):fallback/image/AvatarAction/ImageStatusを headless 層からそのまま再エクスポートする。styledrootは本モジュールで variant クラス付与のために再定義するため、pub use ...::*ではなく選択的 re-export とする(headless の自由関数rootとの名前衝突を避けるため)。状態機械Avatarはあえて再エクスポートしない:Avatar::root()は headless 自由関数rootへそのまま委譲するのみでsize/shapevariant クラスを一切付与しないため、再エクスポートすると呼び出し側が styled 層のつもりでAvatar::root()を呼びレイアウトが静かに崩れる事故を誘発する。Avatarによる状態管理・hydration が必要な呼び出し側はfandhe_frontend_headless_ui::avatar::Avatarを直接 import すること。 root(&AvatarProps, attrs, children) -> Node: styled root パーツ(イシュー #1554 でroot(size, shape, attrs, children)の位置引数 4 軸から Props 構造体へ破壊的変更。0.x の破壊的変更のためマイナーバンプ)。AvatarPropsのsize(Size::Xs〜Xl、既定Md)・shape(AvatarShape::Circle/Rounded/Square、既定Circle)・variant(AvatarVariant::Subtle/Solid/Outline、既定Subtle)・palette(ColorPalette6 値、既定Neutral)の 4 軸 variant に応じたクラス(fd-avatar--size-<value>/fd-avatar--shape-<value>/fd-avatar--variant-<value>/fd-avatar--color-palette-<value>)を付与する。呼び出し側attrsのclassは除去してから合成するためclass属性は常に単一。実体はfandhe_frontend_headless_ui::avatar::rootへ委譲する(呼び出し側data-scope/data-part偽装は headless 側で除去される)。AvatarShape:recipe::VariantValue実装 enum(Sizeと並ぶ本クレート 2 例目の variant 軸)。AvatarVariant(イシュー #1554 で新設):Subtle(既定)/Solid/Outlineの 3 値。BadgeVariant/KbdVariantと同名の語彙(BadgeVariantはイシュー #1555 でSurfaceを追加しSolid/Subtle(既定)/Outline/Surfaceの 4 値)。AvatarProps(イシュー #1554 で新設):size/shape/variant/paletteの 4 フィールドを持つrootの設定構造体(KbdPropsと同型)。DefaultはMd/Circle/Subtle/Neutral。イシュー #2044(shadcn/ui 突合)でstacked/with_badge(いずれもbool、既定false)の 2 フィールドを追加した(0.x の破壊的変更のためマイナーバンプ。既定値のためAvatarProps::default()のclass出力・golden CSS は不変)。group(attrs, children) -> Node(イシュー #2044 で新設): pre-styled-onlygroupパート(<div>)。複数のroot(stacked: true)を重ねて表示するレイアウト専用パートで、headless-ui の anatomy には存在しない(crate::dialog::footerと同型)。残数表示(+N)は独立パートを新設せずroot(Subtle/Neutral, stacked: true) + fallback("+3")の組み合わせで表現する。badge(&AvatarBadgeProps, attrs, children) -> Node(イシュー #2044 で新設): pre-styled-onlybadgeパート(<span>)。root(with_badge: true)の右下に絶対配置される状態ドット。size(Size::Xs〜Xl、既定Md)・palette(ColorPalette6 値、既定Accent)に応じたクラス(fd-avatar--size-<value>/fd-avatar--color-palette-<value>)のみを付与する(variant_classesを経由しないためfd-avatar--shape-/fd-avatar--variant-の既定値補完は付与されない)。AvatarBadgeProps(イシュー #2044 で新設):size/palette/placement(イシュー #3130 で追加)の 3 フィールドを持つbadgeの設定構造体。DefaultはMd/Accent/BottomEnd。AvatarBadgePlacement(イシュー #3130 で新設):BottomEnd(既定、従来の右下)/TopEnd(右上)の 2 値。badge()はBottomEnd以外のときだけfd-avatar--placement-<value>クラスを追加する(既定呼び出しのclass出力は変更前と完全一致)。group_with(&AvatarGroupProps, attrs, children) -> Node(イシュー #3130 で新設):groupの拡張版。props.stackingがLastOnTop(既定)のときはgroup(attrs, children)へそのまま委譲し出力が完全一致する。FirstOnTopのときはfd-avatar--stacking-first-on-topクラス(isolation: isolate)を付与し、直下のNode::Element子へ先頭ほど大きいz-indexを持つ固定クラス(fd-avatar--stack-order-pos<1..12>、AvatarStackOrder)を付与して先頭のrootを最前面にする。厳格 CSP(style-src-attr非許可)下でも無効化されないよう inlinestyle="z-index: ..."は採らない(PR #3563 codex-review P1 指摘)。z-indexの固定クラスは1〜MAX_STACK_ORDER(12)の 12 段までしか持てないため、子の数nが 12 を超える構成では、n - i(0 始まり位置i)をそのまま使わず「先頭 12 件(i < min(n, 12))にmin(n, 12) - iで一意な降順値を割り当て、それを超える末尾側(既に見た目上最背面に埋もれている子)だけを最背面側の固定値pos1へ合流させる」方式で先頭側の一意な重なり順を保つ(n <= 12のときは従来どおりn - iと同じ値になる)。:nth-child(n)等の子孫セレクタはSlotRecipeが生成できない(イシュー #708)ため、CSS だけでは実現できない。「先頭ほど前面」を全件で保証できる上限は公開定数GROUP_FIRST_ON_TOP_MAX_CHILDREN(12)で示す。13 件以上では 12 件目以降がpos1へ合流し DOM 順(後ろほど前面)で描画されるため、呼び出し側で表示件数をこの値以下に絞る(PR #3563 codex-review P2 指摘)。AvatarGroupStacking(イシュー #3130 で新設):LastOnTop(既定、DOM 順のまま)/FirstOnTop(先頭が最前面)の 2 値。AvatarGroupProps(イシュー #3130 で新設):stacking1 フィールドを持つgroup_withの設定構造体。DefaultはLastOnTop。- 大サイズ(56px 超)は新規 API なし: 共通
Sizeenum は 5 段方針のため拡張しない(イシュー #3130)。rootの呼び出し側attrsへ inlinestyle(例:width: 4rem; height: 4rem; font-size: var(--fandhe-font-font-size-xl))を渡すとクラス規則より優先して上書きできる。厳格な CSP(style-src-attrに'unsafe-inline'が無い環境)では利用者側のスタイルシートで外側の要素から上書きすること。 stylesheet() -> String: この styled Avatar の静的 CSS 全量を返す(決定的)。image/fallbackの base 規則はdisplayを宣言せず、headless 層が付与するhidden存在属性(UA 既定[hidden] { display: none })による JS なし SSR の表示制御を壊さない。data-state="hidden"一致時のdisplay: noneはSlotRecipe::state経由で多層防御として追加登録する(src/avatar.rs冒頭の rustdoc 参照)。with_badge: trueの root のみ既定overflow: hiddenを解除する(イシュー #2044)。
4c. styled RadioGroup ラッパー
radio_group モジュールは fandhe_frontend_headless_ui::radio_group の Label/Item/ItemControl/ItemText/ItemHiddenInput 5 anatomy パーツと RadioGroup 状態機械を選択的に再エクスポートし、stylesheet() で既定 CSS を追加提供する(設計方針は他 headless ラッパーと同じ、src/radio_group.rs 冒頭の rustdoc 参照)。
item-hidden-inputの視覚的非表示化: headless 層はネイティブ<input type="radio">にaria/data-*のみを設定し視覚的な非表示化を行わない契約のため、styled 層が visually-hidden パターン(position: absolute+ 1px クリップ、selectモジュールのhidden-select規則と同一の 9 宣言)で覆い隠し、item-controlをカスタムラジオ円として描画する。フォーム送信・キーボード操作・グループ内排他選択はネイティブ semantics のまま維持される。StateCondition::FocusWithinの追加:item-hidden-inputを視覚的に隠すと、ネイティブのフォーカスリングも見えなくなる。実フォーカスは隠された<input>にあり、item(<label>、input の祖先)へ:focus-withinを当てるのが CSS 的に成立する唯一の経路のため、recipe::StateConditionへFocusWithin(:focus-within擬似クラス)を追加した(既存のAttr/AttrEq/FocusVisibleに次ぐ 4 つ目の状態条件)。root(size, palette, disabled, orientation, labelled_by, attrs, children) -> Node: styled root パーツ。size(Size::Sm/Md/Lg、既定Md)・palette(ColorPalette5 値、既定Accent)の 2 軸 variant クラス(fd-radio-group--size-<value>/fd-radio-group--color-palette-<value>)を付与する。headless 自由関数rootとの名前衝突を避けるため本モジュールで再定義し、pub use ...::*ではなく選択的 re-export とする。RadioGroup状態機械は inherentroot()を持たないため(item 系メソッドのみ)、avatarのAvatarと異なりそのまま再エクスポートを維持する。
4d. 複合部品の variant 統一方針・variant 表
単純部品(button/badge/spinner)・avatar に続き、headless 状態機械を持つ複合部品ラッパーへ size/color-palette variant を拡張する際の統一方針は crates/pre-styled-ui/src/lib.rs 冒頭の rustdoc「複合部品の variant 統一方針」節が正本。要旨:
- クラスは root slot のみに付与し、子孫パーツへの伝搬は root が登録する CSS custom property の通常の継承で行う(
SlotRecipeへ子孫セレクタ機構は追加しない)。 var()には Md/Accent 相当のフォールバック値を書き、headless 直接利用でも現行外観を維持する。sizeはフォーム操作部品・トリガー系へ、color-paletteは選択・チェック状態を示す部品へ提供する。popover/tooltip は配置・寸法が positioning 起因のため提供しない。
| 部品 | size | color-palette | 備考 |
|---|---|---|---|
| button/badge/spinner | ✓ | ✓ | button は icon-only 修飾 variant(icon_button/close_button)を追加。専用の icon/close-button 行は設けない: data-scope="button" を共有する variant 拡張であり別部品ではないため |
| callout | ✓ | ✓ | 本文中の補足情報。alert と異なり live region ではない(イシュー #994)。size は root の --fandhe-callout-* へ一本化し text(attrs, children) から size 引数を廃止した(破壊的変更、イシュー #1556) |
| alert | ✓ | –(status) | status(Info/Success/Warning/Error/Neutral、内部で ColorPalette へ写像)に加え variant(Subtle/Surface/Solid/Outline/AccentBorder、既定 Subtle)を持つ(イシュー #1553、AccentBorder はイシュー #3131)。action(pre-styled-only レイアウトパート、イシュー #2043) |
| avatar | ✓ | ✓(既定 Neutral) | variant(subtle/solid/outline)・shape の 2 軸も併せ持つ(イシュー #1554) |
| switch | ✓ | ✓ | — |
| radio-group | ✓ | ✓ | — |
| checkbox | ✓ | ✓ | — |
| password-input | ✓ | ✓ | — |
| input / textarea / native-select | ✓ | – | フォーム入力は選択・チェック状態を示す部品ではないため color-palette は非提供 |
| tabs | ✓ | ✓(selected trigger の強調色) | — |
| accordion / dialog / menu / select | ✓ | – | — |
| number-input | ✓ | – | フォーム入力部品のため color-palette は非提供 |
| pin-input | ✓ | – | palette は第 2 弾展開のフォローアップ |
| rating-group | ✓ | ✓ | 星形 indicator の寸法・点灯色に反映 |
| toggle | ✓ | ✓ | — |
| toggle-group | ✓ | ✓ | root のみへクラス付与 |
| segment-group | ✓ | – | 選択状態は indicator の移動 + 文字強調で表現するため color-palette は非提供 |
| tags-input | ✓ | – | フォーム入力部品のため color-palette は非提供 |
| editable | ✓ | – | フォーム操作部品のため color-palette は非提供 |
| checkbox-card / radio-card | ✓ | ✓ | カード外観・選択強調・ドット色に反映(§4g 参照) |
| pagination | ✓ | ✓ | 現在ページの強調色に反映。root scope の CSS custom property は --fandhe-pagination-item-size/-item-font-size |
| steps | ✓ | ✓ | indicator の寸法・current/complete の強調色に反映。root scope の CSS custom property は --fandhe-steps-indicator-size(indicator の寸法)・--fandhe-steps-indicator-font-size(indicator 内ラベルの文字サイズ)・--fandhe-steps-font-size(イシュー #1540 で追加。trigger/prev-trigger/next-trigger のラベル文字サイズ)の 3 個 |
| popover / tooltip | 提供しない | 提供しない | 配置・寸法が positioning 起因のため提供しない |
| tree-view | ✓ | 提供しない | イシュー #1578 で root のみへクラス付与する size variant(xs〜xl、既定 md)を新設。行密度・文字サイズに反映。color-palette は popover/tooltip と同型の判断で非提供 |
| json-tree-view | tree-view の size を継承 | 提供しない | 構造部は tree-view の recipe を再利用するため tree-view の size variant をそのまま継承する(key/value 固有の size 軸は持たない) |
| toggle-tip | 提供しない | 提供しない | popover/tooltip と同型の判断 |
| collapsible | 提供しない | 提供しない | 参照 3 サイト(chakra-ui / Radix Primitives / ark-ui)いずれも size/variant/colorPalette を持たないため非提供(イシュー #1682) |
| breadcrumb | ✓ | – (BreadcrumbVariant: link の下線表示切り替え) | アクセント色による選択・チェック状態を示す部品ではないため color-palette は非提供 |
| drawer | ✓ | – | dialog と同じく選択・チェック状態を示す部品ではないため color-palette は非提供。root scope の CSS custom property は --fandhe-drawer-size。placement(start/end/top/bottom)は variant 軸ではなく headless 層が出力する data-placement に連動する CSS で表現する |
| link | 提供しない | 提供しない | LinkVariant(下線表示切り替え)のみの単軸 variant。インラインテキストリンクは寸法・強調色の variant 対象外 |
| link-overlay / nav-list | 提供しない | 提供しない | 構造・意味論部品のため variant 軸を持たない |
| table | ✓ | 提供しない | 選択・チェック状態を示す部品ではないため color-palette は非提供。TableVariant(Line/Outline)・striped(bool)・sticky_header(bool)・interactive(bool、イシュー #2052)の追加軸を持つ。striped は StateCondition::NthChildEven で表現、interactive は opt-in の行 hover |
| data-list | ✓(Size 5 段、既定 Md) | 提供しない | orientation(Vertical/Horizontal)・DataListVariant(Subtle/Bold)の追加軸を持つ(イシュー #1559) |
| toast | ✓(placement、group slot) | ✓(status、root slot、ColorPalette 6 役割淡色面配色、イシュー #1544。alert と宣言の中身は共有しない) | 各軸が別 slot のため variant_class をスロットごとに個別呼び出し |
| tour | 提供しない | ✓(root slot) | size は overlay 系の寸法を呼び出し側の CSS カスタムプロパティ上書きに委ねるため初版非提供。palette は action-trigger の背景・文字色(--fandhe-palette-fg)・両トリガーのフォーカスリング・スポットライト縁取りの強調色に反映(イシュー #1551 で action-trigger の文字色反映を追加) |
| file-upload | ✓ | – | フォーム入力部品のため color-palette は非提供 |
| toolbar | 提供しない | 提供しない | ボタン・セパレータ・ToggleGroup のグループ化コンテナであり寸法・強調色の variant 対象外(イシュー #991) |
| menubar | 提供しない | 提供しない | 複数 Menu のグループ化コンテナであり寸法・強調色の variant 対象外(イシュー #992) |
| navigation-menu | 提供しない | 提供しない | トリガー起点のナビゲーションパネルであり寸法・強調色の variant 対象外(イシュー #993) |
| tab-nav | ✓ | 提供しない | size 軸(xs〜xl、既定 md)を持つ(イシュー #1541)。palette は祖先の --fandhe-palette を継承する経路のみで軸としては非提供 |
| checkbox-group | ✓ | ✓ | radio-group と同型(イシュー #997)。item-control の寸法・palette 塗りに反映 |
| card | ✓ | 提供しない | 中立コンテナのため palette 非提供(イシュー #606)。size(xs〜xl、既定 md)は root の --fandhe-card-* へ一本化し padding/角丸/title の文字サイズへ連動する。size 軸新設に伴い root の第 1 引数を impl Into<CardProps> へ拡張した(イシュー #1557)。From<CardVariant> for CardProps により従来の root(CardVariant::Elevated, …) はそのまま有効で、size を指定する場合は root(CardProps { variant, size }, …) を渡す(後方互換の純追加)。イシュー #2046 で shadcn/ui と突合し、パーツ数を 6 → 8 へ拡張した(action/cover を純追加)。header/footer へ data-has-action/data-bordered を opt-in で渡すと、それぞれ action の grid 配置・1px 区切り線が有効になる(既定出力は不変) |
| button_group | 提供しない | 提供しない | 子の寸法に従属するグループ化コンテナ(イシュー #2060) |
tabs/accordion/dialog/menu/select の実装詳細:
- クラスは root slot のみに付与する。
- root スコープの CSS custom property: tabs
--fandhe-tabs-trigger-padding/-content-padding、accordion--fandhe-accordion-trigger-padding/-content-padding、dialog--fandhe-dialog-content-padding/-content-max-width/-title-font-size、menu--fandhe-menu-trigger-padding/-item-padding/-content-padding、select--fandhe-select-trigger-padding/-item-padding/-content-padding。menu/select の--fandhe-reference-width/--fandhe-x/--fandhe-y(wasm positioning 契約)には手を触れない。--fandhe-arrow-*はイシュー #2210 で menu(および popover/tooltip)のarrow/arrow-tipが消費するようになったため対象外(sizevariant はこれらへ影響しないという本節の趣旨は不変)。 - tabs の
color-paletteは選択中 trigger の強調色(border-bottom-color: var(--fandhe-palette, var(--fandhe-color-accent)))にのみ反映する。 Dialog/Menu/Select(inherentroot()を持つ状態機械型)は未スタイル root の静かな適用漏れ防止のため選択的 re-export へ切り替えている。Accordion/MultiAccordion(inherent root なし)は再エクスポート維持。dialog::footer(pre-styled-only レイアウトパート、data-scope="dialog"配下の headless-ui 非由来 9 番目の part。alert-dialog 構成のアクション列の横並び配置のみを担い、確認/キャンセルの強調色は既存のbuttonの variant/palette 組み合わせで表現する。イシュー #1690)。alert::action(pre-styled-only レイアウトパート、data-scope="alert"配下の headless-ui 非由来 6 番目の part。shadcn/ui のAlertAction相当を root 末尾の flex 兄弟として右寄せ(RTL では左寄せ)配置するのみを担い、アクションの機能配線は呼び出し側が行う。イシュー #2043)。
4d-1. button_group(連結ボタングループ、イシュー #2060、headless
anatomy は #2059)
- 公開 API:
root(orientation, label, attrs, children)/separator(group_orientation, attrs, children)/text(attrs, children)/stylesheet()。Orientationはfandhe_frontend_headless_ui::data_attrs::Orientationの選択的再エクスポート(規約 A)。 - size/variant/color-palette いずれの軸も提供しない(上記 §4d 表参照、
docs/design/pre-styled-ui-focus-ring-and-size-conventions.md§4 (d) 「子の寸法に従属するレイアウト部品」)。 - 先頭・末尾以外の隣接要素の角丸・開始側境界線幅を [
crate::css::serialize_rule] による raw CSS 追記で無効化する(SlotRecipeは子結合子を表現できないため)。対象は button / field-input / button-group-text / menu-trigger / select-trigger の 5 種を明示列挙し、> *:not(:first-child)のような汎用セレクタは特異度の衝突が呼び出し側の CSS 連結順に依存してしまうため採らない。 - ネスト(グループの中にグループ)は親側
:has()による gap 付与を採らず(#2226)、角丸連結対象へ含めず先頭以外の内側グループへ margin のみ付与する代替表現とする(意図的非採用、docs/policy/intentional-non-adoption.mdの評価軸に従う)。既定はこの margin 間隔のままだが、内側rootへ呼び出し側が値なし属性data-attachedを付与すると opt-in で間隔を打ち消し、接続辺の角丸・境界線を外側 root と共有する連結表示にする(イシュー #3135、縦横混在の入力欄連結に使う)。接続辺の判定規則(PR #3569): 外側 root の直接の子を通常の子 N・attached 内側 root A・間隔を保つ通常の内側 root D の 3 種に分け、隣接 2 兄弟の辺はどちらも D でないときに限り接続する(N–N / N–A / A–A は接続、D に面した辺は角丸を保ち、D 自身の先頭側 margin と D 直後の通常の子への margin で間隔を付ける)。末尾辺は後続兄弟を参照するため:not(:has(+ D))(Selectors Level 4)で判定する。表と根拠はcrates/pre-styled-ui/src/button_group.rsモジュール doc「接続辺の判定規則」節を正とする。 - バリデーション・送信処理等のアプリケーションロジックは内包しない(
.claude/rules/coding-rust.md§3.25)。 - docs サイト: button-group(Themes)/ button-group(Primitives)。
4d. data-focus-visible によるキーボード専用フォーカスリング
hidden-input パターン(実フォーカスが visually-hidden なネイティブ <input> にあり、リングを見せたい視覚パーツと分離している構成)は、擬似クラス(:focus-visible/:focus-within)だけでは表現しきれない。switch(root > control の兄弟配置。:focus-within すら不成立)と radio_group(item の :focus-within は成立するが、マウス操作でも発火する包括的なフォールバックでしかない)の 2 モジュールがこの補完を導入した。
fandhe-frontend-headless-uiのdata_attrs::data_focus_visibleがdata-focus-visible存在属性の SSR 静的表現(常に属性なし)を契約する(data_highlightedと同型、crates/headless-ui/src/switch.rs/radio_group.rs/checkbox.rsのフォーカスリング契約 doc 参照)。fandhe-frontend-wasm-fullの focus 配線(focus_visibleモジュール、keynav/eventsと同じ 2 層構成)が hidden-input の focusin/focusout と:focus-visible判定に基づき、境界パーツ(switch:root、radio_group:item)とその配下で同一data-scopeを共有するパーツ(switch:control、radio_group:item-control)の双方へ付け外しする。fandhe-frontend-pre-styled-uiはcontrol/item-controlslot へStateCondition::Attr("data-focus-visible")の状態規則を登録し、selectのtrigger(StateCondition::FocusVisible)と同じ視覚言語(outline: 2px solid var(--fandhe-color-accent))でリングを表現する。RadioGroup のitem:focus-withinは wasm なしでも成立する no-JS フォールバックとして維持し、data-focus-visibleはその補完(wasm 配線時のキーボード専用リング)として独立に共存する。checkboxもswitchのcontrolと同型のStateCondition::Attr("data-focus-visible")規則を実装済み(詳細は §4e)。
4e. styled Checkbox ラッパー
checkbox モジュールは fandhe_frontend_headless_ui::checkbox の root/control/indicator/label/hidden-input 5 anatomy パーツを選択的に再エクスポートし、stylesheet() で既定 CSS を追加提供する(設計方針は §4c/§4d と同型、src/checkbox.rs 冒頭の rustdoc 参照)。
root(size, palette, props, attrs, children) -> Node: styled root パーツ。size(Size::Sm/Md/Lg、既定Md)・palette(ColorPalette5 値、既定Accent)の 2 軸 variant クラス(fd-checkbox--size-<value>/fd-checkbox--color-palette-<value>)を付与する。headless 自由関数rootはチェック状態を含むCheckboxPropsを受け取るため(switch/radio_groupの bool 個別引数と異なる形)、styledrootも&CheckboxPropsを第 3 引数に取る。headlessCheckbox状態機械(inherentroot()を持つ)はswitch::Switchと同じ理由(未スタイル root の静かな適用漏れ防止)で再エクスポートしない。indicatorのhidden属性意味論の維持: headlessindicatorは unchecked 時にhidden存在属性で非表示化する契約を持つ。styled recipe のindicatorbase 規則にdisplay宣言を一切含めないことで、UA stylesheet の[hidden] { display: none }を上書きしない(テストindicator_base_has_no_display_declarationで固定)。checked/ indeterminate 時の見た目切り替えはborder/transform/width/heightの組み合わせで表現し、displayを使わない。data-focus-visibleフォーカスリング:controlslot へswitchのcontrolと同一の宣言(outline: 2px solid var(--fandhe-color-accent); outline-offset: 2px;)を登録する。属性の付け外しは headless/wasm 層の責務(fandhe-frontend-wasm-fullの focus 配線に("checkbox", "hidden-input") => Some("root")のマッピングが登録済み)であり、本モジュールでの wasm 層変更は不要だった。
4f. 静的フォーム部品 input/textarea/native_select
input/textarea/native_select の 3 モジュールは状態機械を持たない(ブラウザネイティブ挙動をそのまま尊重する)。fandhe_frontend_headless_ui::field の input/textarea/select の 3 パーツへ variant/size variant クラスと既定 CSS を重ねる薄い委譲層で、アクセシビリティ配線(id・ネイティブ disabled/required/readonly・aria-invalid・aria-describedby・data-*)は headless field::* へ全面委譲する(詳細は src/input.rs 冒頭の rustdoc 参照)。
fieldscope を共有する recipe 設計:SlotRecipeが生成する CSS セレクタは[data-scope="<scope>"][data-part="<slot>"]固定であり、headlessfield::*が実際にレンダリングするdata-scope="field"と一致させる必要がある。そのため 3 モジュールは独自の scope を新設せず"field"を共有し、slot を"input"/"textarea"/"select"のみ個別に宣言する(slot が相互排他のためセレクタ・宣言は衝突しない)。- 各モジュールの API 形:
input(&InputProps, &FieldProps<'_>, extra_attrs)のように、見た目 variant(InputProps)とアクセシビリティ props(headless から再エクスポートしたFieldProps/FieldIds)を別引数として受け取る(ark-ui/chakra-ui が見た目 props とフォーム状態 props を分離する構成に合わせる)。 variant軸:Outline(既定)/Subtle/Flushed/SubtleFlushed(淡色背景+下線のみ、inputのみ、イシュー #3122)の 4 値(native_selectのみFlushedの代わりに枠なしのPlain)。color-palette軸を提供しない: §4d「複合部品の variant 統一方針」の基準 3(color-paletteは選択・チェック状態を示す部品へ提供する)に従い、フォーム入力は該当しないため提供しない。フォーカスリングのアクセントはvar(--fandhe-color-accent)の直接参照のみで表現する。textareaのautoresizeフックへの応答: headlessfield::textareaのautoresize: boolは SSR 時点でdata-autoresize=""存在属性のみを出力する宣言的フック(実際の高さ調整は CSR/wasm 層またはスタイルの責務)。textareaモジュールは[data-autoresize]状態規則としてfield-sizing: content+resize: noneを登録し、この宣言的フックへ styled 層として応答する。native_selectはネイティブ矢印を維持する: chakra-ui のNativeSelectはカスタム矢印アイコンを重ねるためappearance: noneを使う構成が一般的だが、本モジュールは「ブラウザネイティブ挙動を尊重する」という設計原則に従いappearance宣言を持たず、ネイティブの矢印・開閉挙動をそのまま残す最小サブセットとする。<select readonly>が HTML 仕様上無効なためネイティブreadonlyを出力しない判断は headless 層に委譲済みで、本モジュールは再実装しない。native_selectは option/optgroup を system color で着色する(イシュー #2204):option/optgroup/optgroup > optionの背景色をselectと同じ--fandhe-color-bgトークン(Canvasフォールバック)で着色する。SlotRecipeが子孫セレクタを表現できないためrecipe().css()の外側へ固定 CSS 文字列を追記する(field/statusと同型のパターン)。文字色(color)は明示せずselectの継承に委ねる(option:disabledの UA 既定減色を保つため)。
4f-1. field(ラベル・補助テキスト・エラーテキストの型階層、イシュー #1684)
field モジュールは fandhe_frontend_headless_ui::field の anatomy へ、ラベル・補助テキスト・エラーテキスト・必須マークの型階層と root の余白レイアウトを重ねる薄い委譲層である。input/textarea/native_select(§4f)と同じく "field" recipe scope を共有するが、宣言する slot は root/label/helper-text/error-text/required-indicator/group/ content/title/separator/separator-line/separator-content の 11 つ(末尾 6 つはイシュー #2185 で純追加。既存 5 slot の CSS 出力バイトは不変)のみで、input/textarea/select slot は宣言しない(§4f の 3 モジュールが引き続き所有する)。
- 公開 API:
root(&FieldRootProps, &FieldProps<'_>, attrs, children)(見た目 variant クラスを重ねて headlessfield::rootへ委譲)、root_with_label_placement(&FieldRootProps, FieldLabelPlacement, &FieldProps<'_>, attrs, children)(イシュー #3134、下記参照。rootは内部でroot_with_label_placement(.., FieldLabelPlacement::Outside, ..)へ委譲する)、FieldLabelPlacement(label-placement軸、Outside既定/Inset/Overlap)、FieldOrientation(orientation軸、Vertical既定 /Horizontal/Responsive、イシュー #2199 でResponsiveを追加)、FieldRootProps。label/helper_text/error_text/required_indicator/group/content/title/separator/FieldIds/FieldPropsは headless からの選択的再エクスポート(見た目は属性セレクタのみで到達するため styled 側の再定義は不要)。group/content/title/separatorはイシュー #2185 で純追加した拡張パーツ(shadcn/ui FieldGroup/ FieldContent/FieldTitle/FieldSeparator 相当)。 - イシュー #2185 の追加着装:
groupの間隔は space トークン--fandhe-space-6、separatorの線描画はborder-*(crate::separatorの--fandhe-separator-thickness上書き契約を共有)、titleはlabelと同じ型階層(disabled 減光・invalid 配色も同一規則)を採用する(判断根拠はfield.rsモジュール doc「採用したもの(イシュー #2185)」節参照)。 orientation="responsive"(イシュー #2199):groupをcrate::recipe::SlotRecipe::container_slotとして宣言し、rootへcrate::recipe::SlotRecipe::container_variant(ContainerBreakpoint:: Md= 448px、Horizontalと同一の宣言を共有)を登録した。groupの inline サイズが 448px 以上のときのみHorizontalと同じ配置へ切り替わり、groupの外に置いた場合は常に縦積みのまま(mobile-first の安全な劣化)。受け入れ条件が述べるdata-orientation="responsive"は実装しない: headlessfield::rootはdata-orientationを意図的に持たず、本モジュールも独自data-*を出力しない契約(crates/pre-styled-ui/tests/data_attr_vocabulary.rs)のため、既存のhorizontalと同じくクラスfd-field--orientation-responsiveとして語彙化した。判断根拠はfield.rsモジュール doc「orientation="responsive"」節・docs/design/pre-styled-ui-scale-tokens.md§3.7 参照。helper-textのtext-wrap: balance(イシュー #2160):css()はerror-text > ulと同型の静的追記を 2 種持つ。1 つは既存のerror-text > ul(イシュー #2014)、もう 1 つは本イシューで追加したhelper-textの horizontal/responsive 時text-wrap: balance(shadcn/uiFieldDescriptionのgroup-has-[[data-orientation= horizontal]]/field相当をクラス条件へ読み替え)。既存 variant の出力はバイト同一のまま末尾への純追加。判断根拠はfield.rsモジュール doc「helper-textのtext-wrap: balance」節参照。orientation軸のみ:size/color-palette軸は持たない(子の寸法に従属するレイアウト部品の root は size 軸を持たないという規約、フォーム入力系は palette 非提供という §4f と同じ判断)。- 意図的非採用: hover(
root/labelは非インタラクティブ)・focus ring(実フォーカスはコントロール側。FieldLabelPlacement::Inset/Overlapはinput自身の outline を消すため、代わりにrootの:focus-withinへ focus ring を付与する例外を持つ、下記参照)・transition(状態遷移する視覚がない)・data-readonly/data-invalidによるラベル色変更(chakra-ui v3 も持たない)。 FieldLabelPlacement(ラベル配置、イシュー #3134):orientationと独立な opt-in variant。Insetはroot自身を枠線・背景を持つ box とし、内側のlabel/inputをリセットする(inputのみ対象、textarea/selectは対象外)。inset_stack(data-part="inset-stack"の wrapper)の直下に縦に並べたInsetのroot同士だけが+/:has(+ ...)セレクタで枠線を共有して連結する(+は親の gap を区別できないため、暗黙の隣接ではなく wrapper を契約とし、group内では連結しない)。Overlapはラベルをrootの枠線の上へ絶対配置で重ね、--fandhe-field-label-bg(既定var(--fandhe-color-bg))でラベル背景を地の色へ合わせられる。枠線をrootが描く両 variant は枠線に結び付く状態表示(invalid のborder-color・focus のリング)をroot側へ写し、input側の outline を打ち消して二重表示を避ける(disabled は子パーツの既存減衰のみで root へは重ねない。readonlyはinputと同じく視覚宣言なし。4 状態 × 2 variant の対応表はfield.rsモジュール doc「状態表示の対応表」節)。いずれもorientation = Verticalでの使用のみを前提とし、Horizontal/Responsiveとの併用・forms_motionの floating label との併用は対象外。既定(Outside)はクラスを出力せずroot()の既存出力をバイト単位で不変に保つ。判断根拠・CSS 宣言の詳細はfield.rsモジュール doc「ラベル配置(イシュー #3134)」節参照。- バリデーション責務外:
docs/policy/intentional-non-adoption.md§3.25 規則 1 のとおり、本モジュールは headless が出すdata-invalid/data-disabled/data-requiredを CSS セレクタとして参照するだけで、値の妥当性判定・送信処理は実装しない。 - docs サイト: field(イシュー #1685 で
/themes/field/ページ登録・showcase Demo・SPEC_TABLES原稿を追加)。
4f-2. fieldset(<fieldset>/<legend> グループコンテナ、イシュー #1686)
fieldset モジュールは fandhe_frontend_headless_ui::fieldset の anatomy(root/legend/helper-text/error-text の 4 パーツ)へ、UA 既定の <fieldset>/<legend> 枠線・padding のリセットと size 軸による余白・文字サイズの段階化を重ねる薄い委譲層である。
- 公開 API:
root(&FieldsetRootProps, &FieldsetProps<'_>, attrs, children)(見た目 variant クラスを重ねて headlessfieldset::rootへ委譲)、FieldsetRootProps。legend/helper_text/error_text/legend_with_variant/LegendVariant/FieldsetPropsは headless からの選択的再エクスポート。 size軸のみ:Sm/Md/Lgの 3 段のみ登録(chakra-ui v3Fieldset.Rootのsizeprop に対応、既定Md)。Xs/Xlは未登録。orientation/color-palette軸は持たない。- legend variant(イシュー #2214): shadcn/ui
FieldLegendのvariant(legend=大 /label=小)と突合し、legend_with_variantが出力する[data-variant="label"]を CSS セレクタとして参照してsize軸の 1 段下(sm→xs/md→sm/lg→md)のフォントサイズへ切り替える。--fandhe-fieldset-legend-label-font-size(rootのsizevariant が設定する CSS カスタムプロパティ)を継承して値を受け取る。既存legend(data-variantを出力しない)はバイト単位で不変で、[data-variant="legend"]の dead セレクタは持たない。 - 意図的非採用: chakra-ui v3 の
Contentサブパートは headless anatomy に存在しないため実装しない(headless anatomy 変更はスコープ外)。rootへのネイティブdisabled二重表現防止のためdisabled_declarations()を付与しない。legendへのdata-invalid色反映なし(chakra-ui v3 も非対応)。Radix Primitives / Radix Themes のいずれにもFieldsetが存在しないため対応なし。 - バリデーション責務外:
docs/policy/intentional-non-adoption.md§3.25 規則 1 のとおり、本モジュールは headless が出すdata-invalid/data-disabledを CSS セレクタとして参照するだけで、値の妥当性判定・送信処理は実装しない。 - docs サイト: fieldset(イシュー #1687 でページ登録・showcase Demo・
SPEC_TABLES原稿を追加)。
4f-3. input_group(入力欄前後の addon、イシュー #2063、headless anatomy は #2062)
input_group モジュールは fandhe_frontend_headless_ui::input_group の anatomy(root/addon/text/button の 4 パーツ)へ、shadcn/ui の Input Group 相当の見た目(コンテナ側 1 本の枠線・角丸・:focus-within フォーカスリングと、内側 Input/ Textarea/ NativeSelect/ Select の枠線なしリセット)を重ねる薄い委譲層である(NativeSelect/Select は root の直接の子として配置した場合のみリセットが効く、イシュー #3123)。
- 公開 API:
root/addon/text/buttonの 4 関数はいずれも見た目クラスを付与せず(下記「軸なし」参照)、呼び出し側classをdrop_class_attrで除去してから headless 同名関数へそのまま委譲する(同名再定義、[crate::visually_hidden] と同型のパターン)。InputGroupAlign/InputGroupPropsは headless からの選択的再エクスポート。stylesheet()(css()ではない)が静的 CSS 全量を返す。 - 軸なし:
size/variant/color-paletteいずれも提供しない(docs/design/pre-styled-ui-focus-ring-and-size-conventions.md§4 (d)「子の寸法に従属するレイアウト部品」に該当。寸法・文字サイズは内側の Input/Textarea のsizeに従属する)。 - raw CSS 追記:
SlotRecipeは子孫セレクタを表現できないため、stylesheet()はroot > field::input/root > field::textareaの枠線・角丸・背景リセットと:focus-visibleの outline 無効化をserialize_ruleで追記する(crate::toggle_group/crate::number_inputと同型のパターン)。イシュー #3123 でroot > field::select(native_select)・root > select::root(styled select、枠線リセット自体は子孫のtriggerパーツへ適用)の追加リセットを同型のパターンで末尾へ純追加した。native_select/select は input/textarea と異なりflex: 1 1 0%を与えず内容幅のまま(flex: 0 1 auto)インライン配置する。 - 意図的非採用: shadcn の
:has(control:focus-visible)によるリング限定(SlotRecipe/raw CSS のいずれも:has()の先例を持たないためroot単純な:focus-withinで代替)、InputGroupButtonのvariant/size軸(ghost 相当の固定見た目のみ)、addon クリックで input へフォーカスを移す JS 配線(fandhe-frontend-wasm-full側の関心、親 #2061 に該当 sub-issue なし)はいずれもスコープ外とする。 - バリデーション責務外:
docs/policy/intentional-non-adoption.md§3.25 規則 1 のとおり、本モジュールは headless が出すdata-invalid/data-disabled/data-alignを CSS セレクタとして参照するだけで、値の妥当性判定・送信処理は実装しない。 - docs サイト: input-group(イシュー #2063 でページ登録・showcase Demo・
SPEC_TABLES原稿を追加)。
4f-4. item(media + title/description + actions の汎用リスト行、イシュー #2066、headless anatomy は #2065)
item モジュールは fandhe_frontend_headless_ui::item の anatomy(root/media/content/title/description/actions/header/ footer/group/separator の 10 パーツ)へ、shadcn/ui の Item 相当の見た目を重ねる薄い委譲層である。
- 公開 API: 10 関数はいずれも見た目クラスを付与せず、呼び出し側
classをdrop_class_attrで除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::input_groupと同型のパターン)。ItemRootProps/ItemVariant/ItemSize/ItemMediaVariantは headless からの選択的再エクスポート。stylesheet()(css()ではない)が静的 CSS 全量を返す。 - variant/size は headless の
data-*をAttrEqで参照するのみ(意図的差分その 1): headlessitem::rootが固定出力するdata-variant(default/outline/muted)/data-size(default/sm)、item::mediaのdata-variant(default/icon/image)をStateCondition::AttrEqで参照するのみで、class ベースのSlotRecipe::variant/SlotRecipe::size_variantsは持たない(docs/design/pre-styled-ui-data-attr-vocabulary.md§2.2「役割 B: 参照のみ」)。docs/design/pre-styled-ui-focus-ring-and-size-conventions.md§4 (b) が container 部品に求める 5 段Size軸は、item の size が headless 層で shadcn 語彙(2 値)へ固定済みのため二重符号化を避けて新設しない。 - リンク時 hover:
[href]状態 + custom property 間接参照(意図的差分その 2):SlotRecipeは[href]:hoverの複合条件を表現できないため、crate::badge::linkと同じStateCondition::Attr("href")パターンを踏襲する。rootbase で--fandhe-item-bg/--fandhe-item-hover-bgの 2 個の custom property を定義し、hover 規則はbackground: var(--fandhe-item-hover-bg, var(--fandhe-item-bg))のように fallback 付きで参照する(fallback を欠くとhrefを持たないdivroot で背景が透明化する回帰があり、golden テストで固定済み)。 - raw CSS 追記:
SlotRecipeは子孫セレクタを表現できないため、stylesheet()はmedia[data-variant="image"] > imgへのobject-fit: coverリセットをserialize_ruleで追記する(crate::input_groupと同型のパターン)。 - バリデーション責務外:
docs/policy/intentional-non-adoption.md§3.25 規則 1 のとおり、本モジュールはアプリケーションロジックを実装しない。 - docs サイト: item(イシュー #2066 でページ登録・showcase Demo・
SPEC_TABLES原稿を追加)。
4f-5. command(cmdk 由来のコマンドパレット、イシュー #2070、headless anatomy は #2068)
command モジュールは fandhe_frontend_headless_ui::command の anatomy(root/input/list/empty/group/group-heading/item/shortcut/ separator/dialog の 10 パーツ)へ、入力欄・リスト・group 見出し・選択行の背景・shortcut の右寄せ・dialog 型の幅という shadcn/ui Command 相当の意匠を重ねる薄い委譲層である。pre-styled-only の footer パート(イシュー #3143)を加えた計 11 パーツ構成。
- 公開 API: headless 10 関数はいずれも見た目クラスを付与せず、呼び出し側
classをdrop_class_attrで除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::itemと同型のパターン)。filter_items(純粋関数、combobox::filter_optionsへ全委譲)とOpenState(headless からの再エクスポート)のみを選択的に公開する。headless の状態機械Command/CommandActionは再エクスポートしない(crate::combobox/crate::select/crate::menuと同じ判断)。stylesheet()(css()ではない)が静的 CSS 全量を返す。 - 軸を持たない:
size/variant/color-paletteいずれの軸も提供しない(docs/design/pre-styled-ui-focus-ring-and-size-conventions.md§4 (d)「子の寸法に従属するレイアウト部品」相当。headless にも shadcn/ui にも軸が無い)。 emptyの表示切替 CSS: headlesscommand::emptyはpresentがtrueのときのみdata-emptyを出力しhiddenは付与しない(SSR 決定性契約)。本モジュールはemptyslot を既定display: noneにし、[data-empty]が付いたときのみdisplay: blockへ切り替える。itemの選択表現とホバー除外: 選択行の背景は[data-selected](data-highlightedは使わない)で表す。StateCondition::HoverExceptAttrで選択行を hover 対象から除外し、選択色が hover の淡色背景で洗い流されないようにする(crate::comboboxのitemhover と同型の対策)。dialogの[hidden]・幅トークン・backdrop 不在: closed 時のhiddenを確実に非表示化するため.state("dialog", StateCondition::Attr("hidden"), [display: none])を明示登録する(crate::dialogのpositionerと同じ fail-closed)。幅は--fandhe-command-dialog-max-width(既定 32rem)。headlessdialogパーツは単一要素のため独立したbackdropは持たず、本モジュールも追加しない。shortcut内へのkbd合成: API を増やさず、shortcutのchildrenへcrate::kbd::kbdを渡す使い方でkbdを合成する。- pre-styled-only
footerパート(イシュー #3143): headless-ui の anatomy には存在しないレイアウト専用パート。fandhe_frontend_headless_ui::anatomy::Anatomy::partを直接呼び出して組み立てる(crate::drawerのbody/footer/headerと同型、headless anatomy は不変)。本モジュールの「見た目クラスを一切付与せずclass属性自体が出力から消える」契約を踏襲するため、crate::drawer::footerと異なり呼び出し側classもdrop_class_attrで除去する。rootの直接の子としてlist/emptyの後ろに置く想定で、childrenへcrate::kbd::kbdとテキストを組んでキー操作ヒントを表す(shortcutと同じ合成方式)。キー操作そのものの配線は持たない。呼び出し側はヒント 1 件(kbd+ 説明テキスト)をspan等 1 つの子要素へまとめてから渡す(footer自体のflex-wrap: wrapは子要素単位でしか折り返さないため)。 - raw CSS 追記:
SlotRecipeは子結合子セレクタを表現できないため、stylesheet()は dialog 内 root の二重枠を解除する[data-scope="command"][data-part="dialog"] > [data-scope="command"][data-part="root"]規則と、footer直接の子(呼び出し側が組む 1 ヒント分のspan等)をinline-flexのまとまり(min-width: 0・max-width: 100%・overflow-wrap: anywhere)にする[data-scope="command"][data-part="footer"] > *規則、その中のkbdだけをwhite-space: nowrapにする[data-scope="command"][data-part="footer"] > * kbd規則をserialize_ruleで追記する。kbdと説明文は同じ行にまとまったまま、ヒントがコンテナ幅を超えると説明文が折り返す(overflow-wrapはwhite-space: nowrapと同じ規則では効かないため分離する。#3143 Codex P2 再指摘・PR #3582 Codex P2 / Cursor Bugbot 指摘対応)。 - バリデーション責務外: 絞り込み配線・Enter 実行・Cmd/Ctrl+K のグローバルショートカット・フォーカストラップは
fandhe-frontend-wasm-fullの責務として実装しない(docs/policy/intentional-non-adoption.md§3.25 規則 1)。 - docs サイト: command(イシュー #2070 でページ登録・showcase Demo・
SPEC_TABLES原稿を追加)。
4f-6. message(AI チャット UI の会話 1 発言、イシュー #2106、headless anatomy は #2105)
message モジュールは fandhe_frontend_headless_ui::message の anatomy(root/avatar/header/content/footer/group の 6 パーツ)へ、発言者の役割・整列で背景色を切り替える shadcn/ui Message 相当の意匠を重ねる薄い委譲層である。
- 公開 API: 6 関数はいずれも見た目クラスを付与せず、呼び出し側
classをdrop_class_attrで除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::item/crate::commandと同型のパターン)。MessageRootProps/MessageRole/MessageAlign(headless からの再エクスポート)のみを選択的に公開する。stylesheet()(css()ではない)が静的 CSS 全量を返す。 - 軸を持たない:
role/align/loading/errorは headless が固定出力するdata-role/data-align/data-loading/data-errorをStateCondition::AttrEq/Attrで参照するのみで、class ベースのSlotRecipe::variantは持たない(docs/design/pre-styled-ui-data-attr-vocabulary.md§2.2「役割 B: 参照のみ」、crate::itemと同型の判断)。 - role 別の意匠:
data-role="user"はcontentの背景を accent subtle、"assistant"は muted、"system"は透明 + 斜体 + 控えめ文字色へ切り替える。data-align="end"はrootを右寄せ + 行反転する(role から独立した軸、headless message.rs モジュール doc「会話系 4 部品の共通語彙」参照)。 - loading/error の視覚差:
data-loadingはrootを半透明化(opacity: 0.7)するのみでアニメーションは付けない。data-errorはcontentの背景・文字色・枠線を危険色(--fandhe-color-danger-*)へ切り替える。いずれも見た目のみで、判定・再送はアプリ責務。 group連続発言のまとめと raw CSS 追記:SlotRecipeは子結合子セレクタを表現できないため、stylesheet()はgroup配下 2 件目以降のrootの余白を詰める規則と、avatarをvisibility: hidden(display: noneではなく幅を残す)で省略する規則をserialize_ruleで追記する(crate::itemのmedia[data-variant="image"] > img追記と同型のパターン)。aria-live/aria-busyは付与しない: headless 契約の継承(headless message.rs モジュール doc「aria-live/aria-busy を付けない理由」参照)。通知が必要な場合は呼び出し側が自前でaria-liveリージョンを合成する。- バリデーション責務外: 送信処理・Markdown レンダリング・再送判定は実装しない(
docs/policy/intentional-non-adoption.md§3.25 規則 1)。 - docs サイト: message(イシュー #2106 でページ登録・showcase Demo・
SPEC_TABLES原稿を追加)。
4f-7. bubble(AI チャット UI の吹き出し 1 個、イシュー #2109、headless anatomy は #2108)
bubble モジュールは fandhe_frontend_headless_ui::bubble の anatomy(root/content/reactions/reaction/collapse-trigger/ collapse-content の 6 パーツ)へ、塗り・枠線・無装飾の 3 形態と連続発言の角丸連結を重ねる薄い委譲層である。
- 公開 API: 6 関数はいずれも見た目クラスを付与せず、呼び出し側
classをdrop_class_attrで除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::messageと同型のパターン)。BubbleRootProps/BubbleVariant/BubbleGroupPosition(headless からの再エクスポート)に加え、MessageAlign(会話系部品共通語彙)・OpenState(crate::collapsibleと同じ再エクスポート方式)のみを選択的に公開する。stylesheet()が静的 CSS 全量を返す。 - 軸を持たない:
variant/align/group-position/selected/stateは headless が固定出力するdata-variant/data-align/data-group-position/data-selected/data-stateをStateCondition::AttrEq/Attr/AttrEqAllで参照するのみで、class ベースのSlotRecipe::variantは持たない(docs/design/pre-styled-ui-data-attr-vocabulary.md§2.2「役割 B: 参照のみ」、crate::messageと同型の判断)。 - variant 別の意匠:
data-variant="solid"は accent 塗り(既定)、"outline"は枠線のみ、"plain"は無装飾(背景・枠線なし)。rootが公開する--fandhe-bubble-bg/--fandhe-bubble-fg/--fandhe-bubble-borderの 3 custom property を切り替える形で実装し、呼び出し側の上書きフックとしても機能する。 - 角丸連結:
data-align(start/end)×data-group-position(first/middle/last。singleは base の角丸のまま)の複合条件をStateCondition::AttrEqAllのみで表現し、crate::messageと異なり raw CSS 追記は不要(root自身の 2 属性の組み合わせのみで完結するため)。「何番目か」の算出は利用者責務のまま変わらない。 ColorPalette軸は持たない: 実際の色調選択(アクセント・破壊的操作色等)は headless 側の判断ではColorPalette軸の責務とされているが、本イシューのスコープには含めず、上記 3 custom property を上書きフックとして残した(軸追加は後続提案、.claude/rules/coding-rust.md§3.25 規則 2 参照)。- 高さトランジション(イシュー #2192 の共通機構を #2282 で適用):
collapse-contentの開閉は@starting-style/transition-behavior: allow-discreteによる高さトランジション(collapsible/accordionと同型)。calc-size()対応ブラウザではhiddenの切り替えだけで遷移が成立し、fandhe-frontend-wasm-fullの実測高さ CSS 変数(--fandhe-content-height)への書き込みは不要。calc-size()未対応ブラウザでは共通 preset の@supports not (...)が常に遷移を無効化するため、実測値を同期してもhiddenによる即時切り替えのままになる(autoフォールバック)。加えてfandhe-frontend-wasm-fullのMAPPING_TABLEに(bubble, collapse-trigger)の配線が無いため、wire_headless_component経由のクリックではhiddenの切り替え自体が発火しない(呼び出し側が独自に切り替える経路を用意する必要がある)。トリガー未配線と CSS の遷移条件は独立した別々の前提である。 reactions/reactionは非インタラクティブ: 押下・集計・トグルは実装しない(docs/policy/intentional-non-adoption.md§3.25 規則 1)。- docs サイト: bubble(イシュー #2109 でページ登録・showcase Demo・
SPEC_TABLES原稿を追加)。
4f-8. attachment(添付ファイル 1 件の表示、イシュー #2112、headless anatomy は #2111)
attachment モジュールは fandhe_frontend_headless_ui::attachment の anatomy(root/media/content/name/meta/progress/actions/ action の 8 パーツ)へ、横並びの行カード(file 形態)・縦積みのサムネイルカード(image 形態)の 2 意匠とアップロード失敗時の枠色を重ねる薄い委譲層である。
- 公開 API: 8 関数はいずれも見た目クラスを付与せず、呼び出し側
classをdrop_class_attrで除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::bubbleと同型のパターン)。AttachmentRootProps/AttachmentVariant/AttachmentState(headless からの再エクスポート)のみを選択的に公開する。stylesheet()が静的 CSS 全量を返す。 - 軸を持たない:
variant/state/disabledは headless が固定出力するdata-variant/data-state/data-disabledをStateCondition::AttrEq/Attrで参照するのみで、class ベースのSlotRecipe::variantは持たない(docs/design/pre-styled-ui-data-attr-vocabulary.md§2.2「役割 B: 参照のみ」、crate::bubbleと同型の判断)。 file/imageの意匠:file(既定)は横並びの行カード(mediaは固定サイズの角丸アイコン枠、actionsは末尾へ常時表示)。imageは縦積みのサムネイルカード(mediaを正方形で敷き詰め、子imgはobject-fit: cover)。actionsの hover 表示とタッチ端末対策:image形態のactionsは既定で隠れroot:hover/root:focus-withinで表示するが、このopacity: 0の既定非表示規則そのものを@media (hover: hover)配下へ限定する。hover 機構を持たない端末(タッチ端末等)では規則が適用されず CSS の初期値opacity: 1(常時表示)のまま残るため、「見えないボタンで操作不能になる」到達不能を避ける(src/attachment.rsモジュール doc「actionsの hover 表示とタッチ端末対策」節参照)。data-state="error":rootの枠色・metaの文字色をvar(--fandhe-color-danger)へ切り替える(root/metaは別 slot のため raw CSS で追記)。progressスロット: attachment scope の単純なスロットでありcrate::progress::Progressを委譲しない。アップロード進捗を表示するときは呼び出し側がスタイル済みprogress(root/track/range)を中身へ入れ子にする契約(headless 契約を継承)。action: ゴーストボタン(type="button"固定・labelが空文字列でないときのみaria-label)。disabledはrootの disabled(opacity 0.5)とは独立し、二重減衰を避けるためcursor: not-allowedのみを適用する。ColorPalette軸は持たない: 本イシューのスコープに含まれない(軸追加は後続提案、.claude/rules/coding-rust.md§3.25 規則 2 参照)。- docs サイト: attachment(イシュー #2112 でページ登録・showcase Demo・
SPEC_TABLES原稿を追加)。
4f-9. marker(会話中の注記行、イシュー #2115、headless anatomy は #2114)
marker モジュールは fandhe_frontend_headless_ui::marker の anatomy(root/icon/content の 3 パーツ)へ、インライン注記表示(note 形態)・行下の境界線(divider 形態)・中央ラベル + 左右の線(label 形態)の 3 意匠と tone 別の文字色・線色を重ねる薄い委譲層である。
- 公開 API: 3 関数はいずれも見た目クラスを付与せず、呼び出し側
classをdrop_class_attrで除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::attachmentと同型のパターン)。MarkerRootProps/MarkerVariant/MarkerTone(headless からの再エクスポート)のみを選択的に公開する。stylesheet()が静的 CSS 全量を返す。 - 軸を持たない:
variant/toneは headless が固定出力するdata-variant/data-toneをStateCondition::AttrEqで参照するのみで、class ベースのSlotRecipe::variant(ColorPalette軸)は持たない(docs/design/pre-styled-ui-data-attr-vocabulary.md§2.2「役割 B: 参照のみ」、crate::attachmentと同型の判断)。 - 区切り線は疑似要素を使わない:
divider形態は DOM を増やさずrootへborder-bottomの state 規則を付与するのみ。label形態は呼び出し側childrenをcrate::separator::separator(horizontal・Solid、aria-hidden="true")2 個で挟んでから headless へ委譲する(区切り線を疑似要素・専用 DOM ではなく separator パート再利用で描画する設計、src/marker.rsモジュール doc「区切り線の描画方式」節参照)。 data-tone別の色:neutral(既定・base のみ)/info/warning/dangerの 4 値それぞれに文字色(--fandhe-color-<tone>-fg-subtle)と線色用 custom property--fandhe-marker-line(--fandhe-color-<tone>-muted)を割り当てる。ColorPalette軸は持たない: 本イシューのスコープに含まれない(軸追加は後続提案、.claude/rules/coding-rust.md§3.25 規則 2 参照)。- docs サイト: marker(イシュー #2115 でページ登録・showcase Demo・
SPEC_TABLES原稿を追加)。
4f-10. questionnaire(多段質問 UI、イシュー #2119、headless anatomy は #2117、wasm-full 配線は #2118)
questionnaire モジュールは fandhe_frontend_headless_ui::questionnaire の anatomy(root/progress/question/prompt/description/options/ freeform/actions/back/next/skip の 11 パーツ)へ、質問カード・進捗ゲージ・選択肢の choice card 風表示・completed/upcoming の視覚差を重ねる薄い委譲層である。
- 公開 API: 11 関数すべてが
state: &Questionnaire(headless-ui の状態機械)を第 1 引数に取り、内部でstate.<part>(...)へ委譲する(crate::stepsと同型。headless 側に自由関数がないため)。見た目クラスは付与せず、呼び出し側classをdrop_class_attrで除去してから委譲する。QuestionProps/QuestionnaireAction(headless からの再エクスポート)のみを選択的に公開する。Questionnaire状態機械自体は再エクスポートしない(呼び出し側はfandhe_frontend_headless_ui:: questionnaire::Questionnaireを直接 import する)。stylesheet()が静的 CSS 全量を返す。 - 軸を持たない:
size/colorPaletteは持たず、headless が固定出力するdata-state(active/completed/upcoming)・data-answered/data-skipped/data-invalid/data-disabled/data-completeをStateCondition::Attr/AttrEqで参照するのみ。data-state="active"は base と同値のため state 規則を持たない。 progressの塗り: styledprogressを入れ子にせず、step/countから百分率を計算した--fandhe-questionnaire-percentcustom property をstyleへ設定し、CSS のlinear-gradientで塗り幅を表現する(crate::progress::rangeのpercent_style/drop_style_attrと同型のパターン)。既知の制約:fandhe-frontend-wasm-full(イシュー #2118)はクライアント側遷移でaria-valuenow/aria-valuetextを更新するが本 custom property は更新しないため、遷移直後はアプリ側再描画までゲージが古い値を示す。- 選択肢の choice card 風表示:
optionsslot 配下の[data-scope="radio-group"][data-part="item"]/[data-scope="checkbox-group"][data-part="item"]をカード状に整形する子孫セレクタをstylesheet()に含む(SlotRecipeが子孫セレクタを表現できないための raw CSS 追記、crate::markerと同型)。item 自体の基本規則は含まないため、crate::radio_group::stylesheet/crate::checkbox_group::stylesheetの併用が必要。 ColorPalette軸は持たない: 本イシューのスコープに含まれない(軸追加は後続提案、.claude/rules/coding-rust.md§3.25 規則 2 参照)。- docs サイト: questionnaire(イシュー #2119 でページ登録・showcase Demo・
SPEC_TABLES原稿を追加)。
4f-11. message_scroller(会話ログのスクロールコンテナ、イシュー #2123、headless anatomy は #2121、wasm-full 配線は #2122)
message_scroller モジュールは fandhe_frontend_headless_ui::message_scroller の anatomy(root/ viewport/content/anchor/jump-to-latest/load-more の 6 パーツ)へ、高さ確保・ネイティブスクロール・端フェード・浮遊 jump-to-latest ボタン・履歴読み込みトリガーの意匠を重ねる薄い委譲層である。
- 公開 API: 6 関数はいずれも見た目クラスを付与せず、呼び出し側
classをdrop_class_attrで除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::messageと同型のパターン)。MessageScrollerRootProps/MessageScrollerStuck(headless からの再エクスポート)のみを選択的に公開する。stylesheet()が静的 CSS 全量を返す。 - 軸を持たない:
data-stuck(bottom/free)・data-has-new・jump_to_latestのdata-visible/hidden・load_moreのdata-loading/data-disabledはいずれも headless が固定出力するものをStateCondition::AttrEq/Attrで参照するのみで、class ベースのSlotRecipe::variantは持たない(docs/design/ pre-styled-ui-data-attr-vocabulary.md§2.2「役割 B: 参照のみ」、crate::messageと同型の判断)。 - 端フェードは既定 on:
viewportはmask-imageによる両端フェードを base 宣言として持つ(crate::scroll_areaのdata-fadeは opt-in だが、本部品は会話ログ専用の新規部品であり既定挙動を変える既存利用者が存在しないため既定 on にした)。--fandhe-message-scroller-fade-start/-endを0pxへ上書きすれば片端無効化できる。root[data-stuck= "bottom"] > viewportの raw CSS 追記(子結合子)で末尾フェードを0pxへ上書きし、最下部に張り付いているときは最新メッセージを霞ませない。crate::scroll_areaが持つ@supports (animation-timeline: scroll())によるスクロール量連動アニメーションは採用しない(意図的非採用、src/message_scroller.rsモジュール doc「端フェードの採否」節参照)。 hidden属性の上書き:jump-to-latestの base 宣言がdisplay: inline-flexを持つため、UA スタイルシートの[hidden] { display: none }を詳細度で上書きしてしまう。.state("jump-to-latest", StateCondition::Attr("hidden"), [display: none])を明示登録して回避する(crate::command/crate::bubbleと同型の論法)。root[data-has-new] > jump-to-latestの raw CSS 追記:SlotRecipeは別 slot の宣言を切り替える規則を組めないため(crate::messageと同型の制約)、serialize_ruleによる子結合子規則でdata-has-new付きroot配下のjump-to-latestをアクセント色へ強調する。load-moreの spinner:SlotRecipe::pseudo_element(イシュー #2201)は状態条件と合成できないため[data-loading]::beforeは DSL で書けない。かわりにcrate::buttonと同型で、styledload_moreがloading == trueのときcrate::spinner::spinner_decorative(Size::Sm・ColorPalette::Neutral、装飾的・aria-hiddenのみ)を children 先頭へ埋め込む。aria-live/aria-busy/aria-posinset/aria-setsizeを付与しない理由: headless 判断の継承。通知タイミング・総数の伝達はアプリ固有の判断であり、本モジュールは見た目のみを担う。ColorPalette軸は持たない: 本イシューのスコープに含まれない(軸追加は後続提案、.claude/rules/coding-rust.md§3.25 規則 2 参照)。- docs サイト: message-scroller(イシュー #2123 でページ登録・showcase Demo・
SPEC_TABLES原稿を追加)。 - スコープ外: wasm-full 配線(最下部追従・新着検知・履歴読み込み時の位置維持・
data-*の実行時更新)は #2122。
4f-12. data_table(表の操作 UI、イシュー #2127、親 #2124、headless anatomy は #2125、wasm-full 配線は #2126)
data_table モジュールは fandhe_frontend_headless_ui::data_table の anatomy(root/toolbar/column-header/sort-trigger/select-all/ select-row/footer/selection-count の 8 パーツ)へ、shadcn/ui Data Table 相当(toolbar・ソート可能列ヘッダー・行選択・非表示列・footer のページング/件数表示)の意匠を重ねる薄い委譲層である。
- 公開 API: 8 関数はいずれも見た目クラスを付与せず、呼び出し側
classをdrop_class_attrで除去してから headless 同名関数へそのまま委譲する(同名再定義、crate::message_scrollerと同型のパターン)。DataTable/DataTableAction/DataTableProps/ColumnProps/ColumnHeaderProps/SortDirection/column_attrs/column_header_attrs/row_attrs(headless からの再エクスポート)を選択的に公開する。stylesheet()が静的 CSS 全量を返す。 - 軸を持たない:
ColorPalette/Size等の見た目 variant を公開しない(本イシューのスコープ外)。data-loading/data-empty/data-sort/data-state/data-hiddenはいずれも headless が固定出力するものをStateCondition::Attr/AttrEqで参照するのみで、class ベースのSlotRecipe::variantは持たない(docs/design/ pre-styled-ui-data-attr-vocabulary.md§2.2「役割 B: 参照のみ」)。 - 表本体は
tablemod をそのまま使う: headless doc「イシュータイトルとの差分」節の契約どおり、<table>/<thead>/<tbody>/<tr>は本モジュールでは新規に作らずcrate::tableを使う。列ヘッダー/セル/行の表示状態(ソート・列非表示・行選択)はcolumn_attrs/column_header_attrs/row_attrs(node を作らない属性ヘルパ)をtable::column_header/table::cell/table::rowのattrsへ渡すことで同一要素に 2 つのdata-scopeを載せずに合成する。本モジュール自身が持つcolumn_header/sort_trigger(th/buttonノードを実際に生成するパーツ)は Primitives 経路(自前 CSS を書く利用者・headless 単独利用)向けであり、Themes 経路(table::column_headerへ委譲する側)の Demo には現れない。select_all/select_rowは<table>セル階層に直接載るth/tdであるため、Themes 経路でもそのまま使う。 - ソート方向アイコンは raw CSS
::afterで表現:SlotRecipe::stateは状態条件付きの疑似要素を DSL で表現できない(§4f-11 節と同型の制約)ため、sort-triggerの[data-sort="ascending"|"descending"|"none"] ::afterをserialize_ruleによる raw CSS 追記でstylesheet()へ連結する。content値はすべて&'static strリテラル。 - 選択行背景は自前で持たない:
row_attrsが付与するdata-selectedはcrate::tableのrowslot が既に持つ state 規則(イシュー #2052)がそのまま適用される。本モジュールはrowslot を持たない。 select-all/select-rowは--fandhe-table-*を参照しない:data-scope="data-table"の要素でありtablescope ではないため、独自の--fandhe-data-table-*変数のみを参照する(crates/docs-site/tests/css_var_scope_prefix.rs契約)。empty/skeletonの本体を持たない:empty-state/skeletonの本体はcrate::empty_state/crate::skeletonが担う。本モジュールはrootのdata-empty/data-loading(+aria-busy="true")という表示状態のみを持つ。- docs サイト: data-table(イシュー #2127 でページ登録・showcase Demo・
SPEC_TABLES原稿を追加)。 - スコープ外:
fandhe-frontend-wasm-fullの DOM 配線(sort-trigger click → dispatch、indeterminate、data-hidden反映、ページングdata-disabled)は #2126。ColorPalette/Size軸の追加、column-headerslot とtable::column-headerの統合(同一要素 2 scope 禁止のため現設計を維持)は必要になれば別イシュー。
4g. checkbox_card/radio_card(カード型選択 UI)
chakra-ui の forms/checkbox-card.md/forms/radio-card.md 相当。ark-ui には対応する headless anatomy が存在しない(chakra-ui 独自の slot recipe)ため、fandhe-frontend-headless-ui には手を入れず、pre-styled-ui 層のみで新規 anatomy data-scope="checkbox-card"/"radio-card" を定義する(crate::card が pre-styled 層で独自 anatomy data-scope="card" を持つ先例と同型の構成、詳細は各モジュール冒頭の rustdoc 参照)。
- 状態機械の再利用(新規状態機械は作らない):
checkbox_cardはfandhe_frontend_headless_ui::checkbox::{Checkbox, CheckboxProps, CheckedState}を、radio_cardはfandhe_frontend_headless_ui::radio_group::RadioGroupをそのまま利用する。Checkbox/RadioGroup自体は再エクスポートしない(checkbox/radio_groupモジュールと同じ「未スタイル root の静かな適用漏れ防止」判断。呼び出し側は headless モジュールを直接 import する)。 - anatomy パーツ構成:
checkbox_cardはroot(<label>)/control/content/label/description/addon/indicator(チェックボックス外枠)/indicator-check(チェックマーク本体、checkbox::indicator相当)/hidden-inputの 9 パーツ。radio_cardはroot(role="radiogroup")/label/item(<label>)/item-control/item-content/item-text/item-description/item-addon/item-indicator(ラジオ円、radio_group::item_control相当)/item-hidden-inputの 10 パーツ。chakra-ui の単一 Indicator を「外枠 + マーク」の 2 要素に分けるのは、SlotRecipeが疑似要素を持たず既存 checkbox/radio-group の実証済み border/transform/box-shadow 描画をそのまま再利用するため。 hidden-input/item-hidden-inputの属性契約: 対応する headless モジュール(crates/headless-ui/src/checkbox.rs/radio_group.rs)のhidden_input/item_hidden_inputと同一ロジックで出力する(両ファイルを合わせて確認する契約)。size/color-palette軸: §4d の統一方針に従いrootへのみクラスを付与し、--fandhe-checkbox-card-*/--fandhe-radio-card-*の root スコープ custom property 経由で子孫パーツへ伝搬する。- フォーカスリング: 実フォーカスは hidden-input が受けるため、
radio_groupのitemと同型のStateCondition::FocusWithin(no-JS フォールバック)のみをroot(checkbox_card)/item(radio_card)へ登録する。data-focus-visible(wasm 配線によるキーボード操作専用リング)はcrates/wasm-full/src/focus_visible.rsの(scope, part)マッピングに"checkbox-card"/"radio-card"が未登録のため現状スコープ外。
4h. 静的部品 status/empty_state
chakra-ui の feedback/status.md/feedback/empty-state.md 相当。状態機械を要しない静的マークアップ部品であり、fandhe-frontend-headless-ui には手を入れない(headless anatomy 自体が存在しないため checkbox_card/ radio_card と同型に pre-styled 層のみで新規 anatomy を定義する)。
status(scope"status"、root/indicatorの 2 パーツ):size(ドット径・フォントサイズ)/color-palette(Alert/Badge/Spinner と同じ--fandhe-palette-*セマンティック色)の 2 軸 variant をrootへ付与する。indicatorの直径はrootの variant が設定する--fandhe-status-dot-sizecustom property を継承経由で参照する(§4d の「root variant が子孫スコープの custom property を設定する」統一方針と同型)。role/aria-liveは付与しない: ラベルテキスト自体が状態を伝える静的表示であり、[spinner::spinner] のような非同期読み込み中の live region 告知とは用途が異なる(呼び出し側が動的な状態変化を告知したい場合はattrsへ明示的にrole/aria-liveを足す設計)。empty_state(scope"empty-state"、root/content/indicator/title/description/actionsの 6 パーツ):crate::cardと同型の中立レイアウトコンテナでありcolor-palette軸は提供しない。sizeは root の--fandhe-empty-state-*custom property 経由で padding・contentの gap・indicator/title/descriptionの文字サイズを連動させる(イシュー #1560、chakra-uiEmptyStateの size スケールに整合)。title/descriptionは<div>(見出し要素<h1>〜<h6>にしない)とし、埋め込み位置に応じて見出しレベルが変わり得る呼び出し文脈で固定レベルを強制しない(crate::alert::titleと同型の判断)。indicatorはアイコン等を children として受け取り、外部リソース・アイコンフォントを本クレートが直接参照することはない。イシュー #2047(shadcn/uiEmpty突合)でrootのvariant(EmptyStateVariant、既定Plain。Outlineは破線枠、Subtleは淡色単色背景)とindicatorのvariant(indicator_with関数、EmptyStateIndicatorVariant、既定Plain。Boxedはbg-mutedの角丸タイル)を追加した。破壊的変更:variantはEmptyStatePropsの必須フィールドとして追加したため、フィールド全列挙の構造体リテラルEmptyStateProps { size }は本変更以降コンパイルできない。既存の呼び出しはEmptyStateProps { size, ..Default::default() }(またはEmptyStateProps { size, variant: EmptyStateVariant::Plain })へ移行する必要がある。バイト不変なのは「既定EmptyStateProps::default()(variant: EmptyStateVariant::Plain)を渡したときの CSS 出力・HTML 出力」および既存の CSS ブロック自体であり、Outline/Subtle/Boxedの各 variant 用に CSS ブロックが新規追加されている点は不変ではない。shadcn のEmptyHeader(indicator/title/description のグルーピング用 slot)は非追随(contentの gap + section-gap で同等の視覚リズムを実現済み)。
4i. タイポグラフィ静的部品(Heading / Text / Em / Mark / Blockquote / List)
chakra-ui typography/heading.md / text.md / em.md / mark.md / blockquote.md / list.md 相当の 6 静的部品。headless 状態機械を要しない「単一 recipe / slot recipe 静的部品」(badge/skeleton と同型)で、h1-h6/p/em/mark/blockquote/ul・ol・li の素の HTML 意味論をそのまま styled 化する。
variant 表
| モジュール | パーツ | タグ選択 | variant 軸 | colorPalette | 備考 |
|---|---|---|---|---|---|
heading | root(単一) | HeadingLevel(h1〜h6、意味論レベル) | HeadingSize(xs/sm/md/lg/xl(既定)/xl2/xl3/xl4/xl5/xl6(イシュー #2440)、font-size/line-height、視覚サイズ)・HeadingWeight(normal/medium/semibold(既定)/bold、イシュー #2056) | なし | タグ選択(意味論)とサイズ/ウェイト variant(視覚)は独立。chakra の 7xl のみテーマトークン範囲外のため非採用。data-bordered(値なし・呼び出し側 attrs 経由の opt-in 状態、shadcn h2 の下罫線相当)も持つ |
text | root(単一、<p> 固定) | — | TextSize(xs/sm/md(既定)/lg/xl/xl2/xl3/xl4/xl5/xl6(イシュー #2440)、イシュー #1442)・TextWeight(normal(既定)/medium/semibold/bold、イシュー #1442)・TextVariant(plain(既定)/muted、イシュー #2055。muted は fg-muted トークンを使う前景色軸) | なし | — |
em | root(単一、<em> 固定) | — | なし | なし | variant 軸を持たない最小部品(link_overlay と同型) |
mark | root(単一、<mark> 固定) | — | MarkVariant(subtle(既定)/solid/text/plain) | あり(5 値) | badge と同型の単一 recipe パターン |
blockquote | root(<figure>)/content(<blockquote>)/caption(<figcaption>) | — | BlockquoteVariant(subtle(既定)/solid/plain) | あり(5 値、root のみ) | content が素の <blockquote> のため引用の HTML 意味論を保つ |
list | root(<ul>/<ol>)/item(<li>)/indicator(<span aria-hidden="true">) | ListType(Unordered(既定)/Ordered) | ListVariant(marker(既定)/plain) | なし | indicator は常時 aria-hidden="true"(呼び出し側が外せない fail-closed、skeleton と同型) |
heading/list の「タグ選択」は variant クラスではなく、レンダリングする HTML タグそのものを選ぶ引数である点に注意(recipe::VariantValue を実装しない)。
4j. charts 軸・グリッド・凡例・ツールチップ
chakra-ui charts/axes.md / cartesian-grid.md / legend.md / tooltip.md 相当。charts 基盤(crates/pre-styled-ui/src/charts/{data,scale,svg}.rs)の最初の消費者であり、pre_styled_ui::charts::{axis, grid, legend, tooltip} の 4 サブモジュールとして実装する(新規トップレベルモジュールは追加しない。詳細な設計判断は docs/design/charts-foundation-design.md 参照)。
API 一覧
| モジュール | 主な公開関数 | 戻り値 |
|---|---|---|
charts::axis | y_axis(scale, ticks, x, props) / x_axis_linear(scale, ticks, y, props) / x_axis_categories(range, categories, y, props) | Result<Node, ChartError> |
charts::grid | cartesian_grid(x_range, y_range, x_positions, y_positions, props) | Result<Node, ChartError> |
charts::legend | legend(data: &ChartData, props: &LegendProps) / category_legend(data, props)(LegendProps は title/hide_marker/align/marker〔イシュー #2086〕/hidden_series/hidden_categories/controls〔イシュー #2133〕。各 item は <button aria-pressed> を持つ trigger slot) | Node(infallible) |
charts::tooltip | datum_label(category, series, value) / datum_label_lines(heading, entries, footer)(複数行、イシュー #2086) / datum(cx, cy, r, label, attrs) | String / String / Node(いずれも infallible) |
charts::tooltip(indicator/hover 強調、イシュー #2131) | layer_with(data, active, &TooltipLayerProps) / layer_from_entries_with(entries, active, &TooltipLayerProps)(TooltipLayerProps { indicator: TooltipIndicator }。既定 Dot は既存 layer/layer_from_entries とバイト一致) / frame_with(&FrameProps, children)(FrameProps { has_active: bool }) | Node(いずれも infallible) |
charts::data(系列設定、イシュー #2077) | Series::with_label(label) / with_color(SeriesColor) / with_icon(Node) / display_label() | Series / &str |
charts::data(系列色、イシュー #2077) | SeriesColor::token(name) / chart_slot(1..=6) / palette(ColorPalette) / ChartData::series_color_var(index) | Result<SeriesColor, ThemeError>(palette のみ infallible) / String |
各モジュールは css() を公開し、stylesheet.rs の一元化リスト(all_styled_component_css)へ "charts/axis" 等のキーで登録済み。
anatomy / recipe
axis/grid/tooltipは scope"chart"を共有する(slot 名が互いに素なため CSS セレクタは衝突しない、SlotRecipeは scope の一意性を要求しない)。slot:x-axis/y-axis/axis-line/tick-line/tick-label(axis)・grid/grid-line(grid)・datum(tooltip)。legendは独立 scope"chart-legend"を持つ(SVG 外の通常 HTML<ul>/<li>/<span>のため)。slot:root/title/item/marker/label/icon(イシュー #2077、Series::icon指定時にmarkerの代わりに描画される代替スロット。shadcn/uiChartConfig.icon相当)。align(rootslot、Start(既定)/Center/End)・marker(markerslot、Circle(既定)/Square)の 2 variant 軸をイシュー #2086(shadcn/ui Charts 突合)で純追加した。gridの線種はGridLines(Solid(既定)/Dashed)の 1 軸 variant。tooltipの hover 強調はcrate::recipe::StateCondition::Hover(:hover擬似クラス)を使う唯一の消費者。base で背景色ハロー(--fandhe-color-bg)を敷き、hover で前景色(--fandhe-color-fg)へ切り替える方式(イシュー #1593)。
SSR ツールチップ方式(JS 不使用)
マウス追従型のリッチツールチップ(recharts <Tooltip> の cursor 追従)は JS ランタイムが必須のためスコープ外(#2128 系〔#2129 が SSR ツールチップ DOM・hit-area、#2130 が JS 配線〕が実装を担う)。代わりに tooltip::datum がデータ点(<circle>)へ子 <title> 要素(ブラウザネイティブな hover 表示)と aria-label 属性(同一文字列)を埋め込み、StateCondition::Hover による CSS のみの視覚強調と組み合わせて「ホバーで詳細が分かる」体験を実現する。静的テキストは 1 行の datum_label と複数行の datum_label_lines(見出し省略可・整形済み値・footer、イシュー #2086、shadcn/ui chart-tooltip-default/-advanced 相当)の 2 択。icon 合成は #2129 のスコープ外であり本モジュールには含まれない。
indicator バリアント・hover 強調(イシュー #2131)
tooltip-indicator(ツールチップの色見本)の見た目は TooltipIndicator(Dot〔既定〕/Line/Dashed/None)から選べる。layer_with/ layer_from_entries_with(layer/layer_from_entries の indicator 選択版)が TooltipLayerProps { indicator } を受け取る。Dot(既定)/None は CSS class を持たず(golden 純追加原則)、None は tooltip-indicator の <span> 自体を省略する(shadcn/ui hideIndicator 相当)。
hover 強調(active な点の拡張・非 active 点の減光)は「祖先の状態 → 継承される custom property → 各要素が消費」方式(table::root の --fandhe-table-stripe-bg と同型、SlotRecipe が子孫・兄弟セレクタを持たないため):
| 要素 | 属性 | CSS |
|---|---|---|
tooltip::frame / 8 個の chart 部品それぞれの root | data-has-active(tooltip::frame_with の FrameProps { has_active: true } で明示できる) | --fandhe-chart-inactive-opacity: 0.4(pie/donut/radial は追加で --fandhe-chart-active-scale: 1.05)を宣言 |
視覚要素(datum/point/bar/segment) | data-index(既存語彙) | opacity: var(--fandhe-chart-inactive-opacity, 1) + transition。祖先が data-has-active を持たなければ 1 へフォールバックし常時フル不透明 |
| 同要素 | data-active(既存語彙) | opacity: 1 で上記を上書き。donut の外径拡張済み active_index との二重拡大を避けるため、pie/donut/radial は transform: scale(var(--fandhe-chart-active-scale, 1))(root[data-has-active] が無ければ 1 で拡大なし)、他は固定 scale(1.5) |
実際のマウス操作で data-has-active を付け外しする wasm-full 側の配線は本イシュー(#2131)の時点では未実装(#2130 は data-active/hidden の付け外しのみ実装し、祖先属性の付け外しはスコープに含まれなかった)。このため上記の減光 CSS は、SSR が [FrameProps { has_active: true }] を明示する静的 Demo(/themes/charts/)以外の経路では発火しない。prefers-reduced-motion: reduce は Theme::to_css の --fandhe-motion-duration-*: 0ms 一括上書きで自動対応するため、本モジュールには @media を追加していない。
系列設定(label/color/icon、イシュー #2077)
shadcn/ui の ChartConfig(系列キー → label/color/icon を 1 箇所で定義する設定オブジェクト)相当の機能を、charts::data::Series の任意フィールド(Series::with_label/with_color/with_icon)として提供する。ChartData の消費者(legend・line_chart/area_chart/bar_chart/ radar_chart)はいずれも ChartData::series_color_var(index) を経由して色を解決するため、系列 1 箇所への設定が全消費者へ一元的に反映される(chakra-ui useChart の series: [{ name, color, label }] と同型の設計。shadcn のような並列の ChartConfig マップは設けない)。色は charts::data::SeriesColor(theme.rs の TokenName allowlist を通過した色トークン名のみを保持する newtype)経由でのみ指定でき、style/fill/ stroke へ渡る値は var(--fandhe-color-<name>) の固定形以外を生成しない(REQ-1 相当)。--fandhe-color-chart-1〜-6(6 段階)は shadcn/ui の --chart-1〜-5(5 段階)のスーパーセットのため変更していない(Phase 0 イシュー #2005、docs/design/color-token-system.md §9.2 で決定済み)。
4k. LineChart / AreaChart / Sparkline(charts 基盤の消費者)
docs/design/charts-foundation-design.md が提供する charts::data::ChartData(カテゴリ + 系列の値モデル)・charts::scale::LinearScale(線形座標写像)・charts::svg(SVG ノード木ヘルパー、fmt_coord/ViewBox/svg_root/ PathBuilder)を消費し、「プロット領域(折れ線・面・スパーク)のみを描く自己完結 SVG」として実装する。軸・グリッド・凡例・ツールチップ(chakra の CartesianGrid/XAxis/YAxis/ChartLegend/ChartTooltip 相当)は §4j のスコープであり本 3 部品には含まれない。
API 概要
| モジュール | 入力 (*Props) | 出力関数 | 既定 viewBox |
|---|---|---|---|
line_chart | data: &ChartData / aria_label / width / height / size | line_chart(&props, attrs) -> Result<Node, ChartError> | 300 × 150 |
area_chart | 同上 | area_chart(&props, attrs) -> Result<Node, ChartError> | 300 × 150 |
sparkline | values: &[f64] / aria_label / width / height / size | sparkline(&props, attrs) -> Result<Node, ChartError> | 112 × 48(chakra w={28} h={12} トークン相当) |
いずれも *Props::new(data_or_values, aria_label) が既定寸法・Size::Md で組み立てる便利コンストラクタを提供する。
variant 表
| 軸 | 値 | 適用パーツ | 効果 |
|---|---|---|---|
size(Size) | Sm/Md(既定)/Lg | root | --fandhe-<scope>-height custom property 経由で plot(svg)の CSS 表示高さを切替(viewBox の描画座標系とは独立、qr_code と同型) |
color-palette 軸は非提供。系列色は charts::series_color_var(index)(var(--fandhe-color-chart-1..6) を系列数に応じて循環)を stroke/fill 属性へ直接付与する(CSS variant ではなく描画時の固定色指定、複数系列を同時に描く都合上 SlotRecipe の軸機構に載せない判断)。
座標写像・エッジケース
- x 軸: カテゴリ index を等間隔配置(単一カテゴリは中央 1 点)。
- y 軸:
ChartData::domain()(フラットデータの非退化パディング込み)をLinearScale::new(domain, (height, 0.0))で写像(nice()は適用しない)。 - 単一カテゴリ(
n == 1): 折れ線・面のいずれも生成せず、中央に半径固定のcircleマーカーのみを描く。 - 負値・フラットデータ:
charts基盤のdomain()/fmt_coordの契約に従い決定的に描画する(golden テストcrates/pre-styled-ui/tests/charts_line_area_sparkline.rs参照)。
AreaChart の shadcn/ui 突合バリアント(イシュー #2081)
AreaChartProps は shadcn/ui Charts(area、10 バリアント)と突合した以下の追加フィールドを持つ(すべて既定値で #2081 以前の出力と完全に同一の HTML を生成する、golden 純追加原則)。
| フィールド | 型 | 既定 | 効果 |
|---|---|---|---|
curve | AreaCurve(Linear/Natural/Step) | Linear | 曲線種。Natural は d3-shape curveNatural 相当の自然三次スプライン(charts::curve::natural_control_points)、Step は区間中点で段差になる補間(charts::curve::step_points)。n == 2 の Natural は直線へ退化する |
stack | AreaStack(None/Normal/Expand) | None | 積み上げ。Normal/Expand は全系列の負値を ChartError::NegativeValue として拒否する。Expand は domain (0.0, 1.0) 固定でカテゴリ合計比率を描く |
fill | AreaFill(Solid/Gradient) | Solid | 塗り。Gradient は系列ごとの <linearGradient>(縦方向、stop-opacity 0.8→0.1)を <defs> に生成し series-area へ url(#...) 参照を張る |
gradient_id | &str | "fandhe-area" | fill: Gradient 時の <linearGradient id> 接頭辞。css::is_valid_identifier を満たさなければ ChartError::InvalidGradientId。同一ページに複数チャートを置く場合は呼び出し側が一意化する |
show_x_axis / show_y_axis / show_grid | bool | false | X 軸(カテゴリラベル)・Y 軸(nice() 適用済み数値目盛。stack: AreaStack::Expand は domain 固定のため nice() 対象外)・水平グリッド線の描画有無。プロット領域の余白は軸ラベル単位で個別に確保する(show_y_axis のみ Y 側 40px、show_x_axis のみ X 側 24px。show_grid 単独は余白を確保しない)。いずれか 1 つでも true なら余白差し引き後の領域が 0 以下かを検査し、0 以下なら ChartError::PlotAreaTooSmall。stack: AreaStack::Normal/Expand にも同じ規則で適用される(積み上げ時に無視される欠陥の是正)。Expand の Y 軸目盛ラベルはカテゴリ合計比率を % 表示する |
意図的に対応しない点(crate::area_chart モジュール doc「意図的に合わせなかった点」参照): series-area の既定 fill-opacity: 0.2(chakra-ui 値、shadcn の 0.4 は不採用)・dots/label/横向きレイアウト・tickFormatter(アプリ側整形の責務)・マウス追従ツールチップ/期間切替/凡例トグル等の実行時インタラクション(#2128/#2132)。
LineChart の shadcn/ui 突合バリアント(イシュー #2083)
LineChartProps は shadcn/ui Charts(line、10 バリアント)と突合した以下の追加フィールドを持つ(すべて既定値で #2083 以前の出力と完全に同一の HTML を生成する、golden 純追加原則)。curve は area_chart と同じ charts::Curve(charts::curve モジュールへ統合済み)を共有する。
| フィールド | 型 | 既定 | 効果 |
|---|---|---|---|
curve | charts::Curve(Linear/Natural/Step) | Linear | 曲線種。area_chart::AreaCurve と同じジオメトリ(charts::curve::line_path_d)を共有する |
dots | LineDots(None/Filled/Hollow) | None | データ点マーカー。Filled は系列色(または color_by_category 有効時はカテゴリ色)で塗りつぶし、Hollow は背景色塗り + 系列色輪郭の静的近似(shadcn chart-line-dots-custom の任意アイコン形状は非対応) |
label | LineLabel(None/Value/Category) | None | データ点上のラベル。Value は fmt_coord 済みの値、Category はカテゴリ名。有効時はプロット領域上側へ LABEL_TOP_MARGIN(20px)を確保する |
color_by_category | bool | false | dots 有効時、点の色をカテゴリ index で charts::series_color_var の 6 色循環へ切り替える(shadcn chart-line-dots-colors 相当)。false は系列色のまま |
show_x_axis / show_y_axis / show_grid | bool | false | X 軸・Y 軸・水平グリッド線の描画有無。area_chart と同じ余白規則(show_y_axis のみ左 40px、show_x_axis のみ下 24px)を共有する。label の上余白とは独立に確保する。いずれかの余白差し引き後の領域が 0 以下なら ChartError::PlotAreaTooSmall |
意図的に対応しない点(crate::line_chart モジュール doc「意図的に合わせなかった点」参照): tickFormatter(アプリ側整形の責務)・任意アイコンによるカスタム点形状・activeDot(hover 時の点拡大)・マウス追従ツールチップ・hit-area data-*(#2128)・期間切替・凡例トグル(#2132)・点ごとの任意色(color_by_category のトークン循環で代替)・積み上げ・横向き(shadcn line registry に存在しない)。
RadarChart の shadcn/ui 突合バリアント(イシュー #2085)
RadarChartProps は shadcn/ui Charts(radar、14 バリアント)と突合した以下の追加フィールドを持つ(すべて既定値で #2085 以前の出力と完全に同一の HTML を生成する、golden 純追加原則)。
| フィールド | 型 | 既定 | 効果 |
|---|---|---|---|
grid | RadarGrid(Polygon/Circle/None) | Polygon | 同心グリッドの形状。None は一切描画しない(chart-radar-grid-none 相当。スポークは既定どおり残る) |
grid_rings | RadarGridRings(Ticks/Outer) | Ticks | グリッドの同心リング本数。Outer は外周(plot_radius)の 1 本のみ(chart-radar-grid-custom の静的近似、shadcn polarRadius の px 直接指定は非対応) |
grid_fill | RadarGridFill(None/Series) | None | グリッドの塗り。Series は先頭系列色(fill-opacity: 0.2)で塗る(chart-radar-grid-fill/-grid-circle-fill 相当) |
spokes | bool | true | スポーク(中心 → 各軸頂点の線)の描画有無(shadcn radialLines 相当) |
fill | RadarFill(Solid/None) | Solid | 系列ポリゴンの塗り。None は輪郭のみ(chart-radar-lines-only 相当) |
dots | bool | false | データ点マーカー(半径 4.0、shadcn dot.r 相当)の描画有無(chart-radar-dots 相当) |
axis_label | RadarAxisLabel(Category/ValueAndCategory) | Category | 軸ラベルの内容。ValueAndCategory は各系列の値を / 区切りで併記した 2 行(chart-radar-label-custom の静的近似) |
radius_axis | bool | false | 半径軸(値目盛ラベル)の描画有無。角度は軸 0/1 中間角に固定(shadcn angle prop の任意指定は非対応、chart-radar-radius の静的近似) |
意図的に対応しない点(crate::radar_chart モジュール doc「意図的に合わせなかった点」参照): fill-opacity は shadcn の 0.6 ではなく既存の 0.2 を維持(既存 golden の色味変更禁止・area_chart との統一)・tickFormatter(アプリ側整形の責務)・グリッド外周の px 直接指定・半径軸の角度指定・マウス追従ツールチップ/hover 強調(#2086/#2128)。凡例の系列トグルの SSR 構造(range/hidden_series)は #2133 で追加済み。ChartLegend/icon は radar 部品へ内包せず charts::legend との合成で表現する(Series::with_icon、イシュー #2077)。
PieChart / DonutChart の shadcn/ui 突合バリアント(イシュー #2084)
PieChartProps/DonutChartProps は shadcn/ui Charts(pie、11 バリアント)と突合した以下の追加フィールドを持つ(すべて既定値で #2084 以前の出力と完全に同一の HTML/CSS を生成する、golden 純追加原則)。PieSeparator/ PieLabelContent/PieLabelPosition は pie_chart モジュールで定義し、donut_chart は pub use で共有する。
| フィールド | 対象 | 型 | 既定 | 効果 |
|---|---|---|---|---|
separator | 両方 | PieSeparator(Line/None) | Line | セグメント間セパレータ。None は segment の stroke を none にする(shadcn chart-pie-separator-none) |
label_content | 両方 | PieLabelContent(Category/Value) | Category | show_labels 有効時のラベル内容。Value は fmt_coord 済みの値 |
label_position | 両方 | PieLabelPosition(Inside/Outside) | Inside | show_labels 有効時のラベル配置。Outside は外径を OUTSIDE_LABEL_OUTER_RADIUS(34、viewBox 単位)へ縮小し、引き出し線(label-line slot)+ 外側ラベル(outside-label slot、data-align="start"/"end")を描画する(shadcn chart-pie-label) |
stacked | pie_chart のみ | bool | false | true なら複数系列をリング(0 が最内周)として描画する(shadcn chart-pie-stacked)。各セグメントへ系列名を data-series 属性として付与する。false は従来どおり単一系列専用 |
active_index | donut_chart のみ | Option<usize> | None | 強調表示するセグメントのカテゴリ index(shadcn chart-pie-donut-active)。当該セグメントへ data-active 存在属性を付与し外径を維持、非活性セグメントの外径を ACTIVE_INSET(4)だけ縮める(viewBox の外径 45 が上限のため、活性側を拡張せず非活性側を縮める逆転で表現する)。範囲外は PieChartError::InvalidActiveIndex |
center_text | donut_chart のみ | Option<PieCenterText> | None | 中央テキスト(value・任意の label)。radial_chart::RadialCenterText と同型で、合計値の算出・数値整形は呼び出し側の責務(shadcn chart-pie-donut-text) |
新設 pub 関数 charts::legend::category_legend(data, props) -> Node(shadcn chart-pie-legend)は ChartData のカテゴリ 1 件を凡例 item 1 件として描画し、マーカー色を charts::series_color_var(カテゴリ index)で扇形と一致させる(既存 legend() は系列単位のため円グラフのカテゴリ単位凡例には使えない)。
意図的に対応しない点(crate::pie_chart/crate::donut_chart モジュール doc「意図的に合わせなかった点」参照): pie 側の active_index(shadcn に pie-active は存在しない)・カテゴリごとの任意色(ChartData の色モデルは系列単位)・chart-pie-label-list の fill-background(ダーク時可読性の既存判断 #1594/#1596 を維持し fg + 背景色ハローのまま)・弧に沿った回転(引き出しラベルは水平テキストのまま)。マウス追従ツールチップ・hover 強調・hit-area data-*(#2128)・chart-pie-interactive(期間切替)・凡例の系列トグル(#2132)は対象外。
4l. theme モジュール: Theme トークン API と upsert_*(イシュー #547/#606/#1138/#1423/#1678/#2197)
API 一覧
impl Theme {
pub fn empty() -> Self;
pub fn default() -> Self; // 標準トレイト実装
// 追加専用(既存名は ThemeError::DuplicateTokenName で拒否)
pub fn push_color(&mut self, name: &str, light: &str, dark: &str) -> Result<(), ThemeError>;
pub fn push_space(&mut self, name: &str, value: &str) -> Result<(), ThemeError>;
pub fn push_typography(&mut self, name: &str, value: &str) -> Result<(), ThemeError>;
pub fn push_radius(&mut self, name: &str, value: &str) -> Result<(), ThemeError>;
pub fn push_shadow(&mut self, name: &str, light: &str, dark: &str) -> Result<(), ThemeError>;
pub fn push_z_index(&mut self, name: &str, value: &str) -> Result<(), ThemeError>; // イシュー #1423
pub fn push_size(&mut self, name: &str, value: &str) -> Result<(), ThemeError>; // イシュー #1678
pub fn push_breakpoint(&mut self, name: &str, value: &str) -> Result<(), ThemeError>; // イシュー #2197
// 追加または上書き(イシュー #1138。DuplicateTokenName を返さない)
pub fn upsert_color(&mut self, name: &str, light: &str, dark: &str) -> Result<(), ThemeError>;
pub fn upsert_space(&mut self, name: &str, value: &str) -> Result<(), ThemeError>;
pub fn upsert_typography(&mut self, name: &str, value: &str) -> Result<(), ThemeError>;
pub fn upsert_radius(&mut self, name: &str, value: &str) -> Result<(), ThemeError>;
pub fn upsert_shadow(&mut self, name: &str, light: &str, dark: &str) -> Result<(), ThemeError>;
pub fn upsert_z_index(&mut self, name: &str, value: &str) -> Result<(), ThemeError>; // イシュー #1423
pub fn upsert_size(&mut self, name: &str, value: &str) -> Result<(), ThemeError>; // イシュー #1678
pub fn upsert_breakpoint(&mut self, name: &str, value: &str) -> Result<(), ThemeError>; // イシュー #2197
pub fn to_css(&self) -> String;
}
// var(--fandhe-...) 参照ヘルパ(自由関数、`Theme` の inherent メソッドではない)
pub fn z_index_var(name: &str) -> Result<String, ThemeError>; // イシュー #1423
pub fn size_var(name: &str) -> Result<String, ThemeError>; // イシュー #1678
pub fn breakpoint_var(name: &str) -> Result<String, ThemeError>; // イシュー #2197。参照専用(下記注記参照)色(colors)・影(shadows)はライト/ダーク 2 値、余白(spaces)・タイポグラフィ(typography)・角丸(radii)・重なり順(z-indices、イシュー #1423 で新設)・size(イシュー #1678 で新設)・breakpoint(イシュー #2197 で新設)はモード非依存の 1 値を取る。
breakpoint トークンは参照専用(イシュー #2197): CSS custom property は @media プレリュードで使えないため、push_breakpoint/ upsert_breakpoint で登録した値は crate::recipe::SlotRecipe::breakpoint が生成する @media (min-width: ...) の閾値には一切影響しない。閾値の唯一の定義元は crate::recipe::Breakpoint::min_width()(const fn)であり、テーマトークンはこの値と Theme::default() 構築時に自動的に同期する(2 箇所の手打ちドリフトを構造的に防ぐ)。JS の matchMedia・利用者の独自スタイルシートから読む用途を想定する。
Theme::default() の既定色トークン一覧(イシュー #1422)
DEFAULT_COLORS は 55 件: bg 系 5 件(bg/bg-subtle/bg-muted/ bg-emphasized/bg-overlay)、fg 系 3 件(fg/fg-muted/fg-subtle)、border 系 4 件(border/border-muted/border-subtle/ border-emphasized)、ステータス系統(accent/info/success/warning/ danger)各 6 件(<p>/-emphasized/-fg/-subtle/-muted/ -fg-subtle)、neutral 系統 6 件(同型)、focus-ring 1 件、chart-1〜chart-6 の系列配色 6 件。chakra-ui semantic token / Radix Themes の色スケールとの対応表・「どの部品がどの semantic 名を使うべきか」の方針・WCAG コントラスト検証手段は docs/design/color-token-system.md を正とする(本 API リファレンスでは値を複製しない)。
push_* と upsert_* の意味論対比
| API | 同名トークンが既存の場合 | 用途 |
|---|---|---|
push_color / push_space / push_typography / push_radius / push_shadow / push_z_index / push_size / push_breakpoint | ThemeError::DuplicateTokenName を返して拒否(fail-closed) | 新規トークンの追加。意図しない上書きを防ぐ既定挙動 |
upsert_color / upsert_space / upsert_typography / upsert_radius / upsert_shadow / upsert_z_index / upsert_size / upsert_breakpoint | 挿入順(= Theme::to_css の出力順)を保ったまま値を in-place 置換。存在しなければ末尾追加 | 既存トークン(既定パレット含む)の明示的な上書き。DuplicateTokenName を返すことはない |
Theme::default() の既定値を差し替える正規経路
push_* は同名トークンを常に DuplicateTokenName で拒否するため、Theme::default() が持つ既定パレット(bg/fg/accent/font-body 等)を差し替える正規経路が存在しなかった(イシュー #1118 で判明した欠落)。upsert_*(イシュー #1138)がこの欠落を解消する正規経路であり、push_* では代替できない。
Theme::to_css 出力への反映
upsert_* で置換・追加した値は、Theme::to_css の既存の出力構造(:root の light 値ブロック・@media (prefers-color-scheme: dark) の dark 値ブロック・:root[data-theme="..."] による明示上書きブロック)へそのまま反映される。内部表現は Vec による挿入順保持のため、出力は呼び出し順序にのみ依存する決定的な文字列になる。
使用例(イシュー #1118 の実シナリオ)
use fandhe_frontend_pre_styled_ui::theme::Theme;
let mut theme = Theme::default();
// 既定の font-body を日本語フォントスタックへ差し替える(正規経路)
theme.upsert_typography("font-body", "Noto Sans JP, system-ui, sans-serif")?;
// アクセント色の上書き(light / dark 2 値)
theme.upsert_color("accent", "#0f766e", "#2dd4bf")?;
let css = theme.to_css();値はいずれも [CssValue::new] の allowlist(ASCII 英数字・空白・# % . , ( ) - _ のみ許可)を通過する文字列に限る。引用符(" ')は allowlist で拒否されるため、フォント名は引用符なしで書く。
セキュリティ上の不変条件(REQ-1 相当、CSS 文脈)
upsert_* も push_* と同一の allowlist 検証(TokenName::new / CssValue::new)を唯一の入口とし、迂回経路を持たない。: ; { } を拒否するため宣言追加・セレクタ脱出・url(javascript:...) は構成不可能であり、< > / " ' を拒否するため </style> 脱出も構成不可能である。検証は全引数に対して完了してから書き込むため、検証失敗時は Theme を一切変更しない(部分書き込みなし)。固定テストは crates/pre-styled-ui/src/theme.rs 内ユニットテスト・crates/pre-styled-ui/tests/theme_css.rs・crates/pre-styled-ui/tests/theme_injection.rs。
upsert_* は「重複拒否の無効化」ではなく、既存トークンを明示的に上書きするための独立したオプトイン API である(push_* の fail-closed 挙動自体は変更しない)。
upsert API は fandhe-frontend-pre-styled-ui v0.38.0 以降に収録される(§2a の crates.io 公開状況・バージョン確認方針を参照)。
push_z_index / upsert_z_index / z_index_var(重なり順トークン、イシュー #1423)は v0.41.0 以降に収録される。既定スケール(hide/base/docked/ dropdown/sticky/popover/overlay/modal/skip-nav/toast/tooltip/ max の 12 件)・部品ごとの割り当て方針・見送り項目の詳細は docs/design/pre-styled-ui-scale-tokens.md を参照。radii(8 段)・shadows(6 段)・spaces(15 段)の拡充も同バージョンから収録される。
push_size / upsert_size / size_var(size variant 軸トークン、イシュー #1678)は v0.44.0 以降に収録される。既定スケール(control-height/ control-padding-x/control-font-size の 3 系統 × xs/sm/md/lg/xl の 5 段、計 15 件)・判断記録の詳細は docs/design/pre-styled-ui-size-and-color-palette-axes.md を参照。
recipe::Size / recipe::ColorPalette の共通軸拡張(イシュー #1678)
recipe::Size(5 段: Xs/Sm/Md/Lg/Xl)・recipe::ColorPalette(6 値: Accent/Info/Success/Warning/Danger/Neutral)は styled 部品が共用する 2 つの標準 variant 軸である。イシュー #1678 で既存段・既存値へ純追加した拡張であり、各部品の実際の登録段は部品ごとに異なる(本書 §4d の variant 表・各部品モジュールの rustdoc が実際の登録内容の正)。recipe::palette_scale_declarations(6 役割版、palette_declarations の 3 役割はそのまま維持)も同イシューで新設した。判断根拠・非採用事項・再評価トリガーの詳細は docs/design/pre-styled-ui-size-and-color-palette-axes.md を参照。
recipe::Breakpoint / SlotRecipe::breakpoint(イシュー #2197)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Breakpoint { Sm, Md, Lg, Xl }
impl Breakpoint {
pub const ALL: [Breakpoint; 4];
pub const fn value(self) -> &'static str; // "sm" / "md" / "lg" / "xl"
pub const fn min_width(self) -> &'static str; // "640px" / "768px" / "1024px" / "1280px"
}
impl SlotRecipe {
pub fn breakpoint(
self,
slot: &'static str,
bp: Breakpoint,
declarations: Vec<Declaration>,
) -> Self; // builder、自己消費
}StateCondition と並ぶ条件だが、variant 軸でも状態条件でもない別カテゴリのため独立した enum とした。SlotRecipe::breakpoint が登録した規則は SlotRecipe::css() の出力で「states の後・hover ブロック(@media (hover: hover))の前」に、Breakpoint::ALL の昇順(sm → xl、mobile-first)で @media (min-width: <bp.min_width()>) ブロックとして出力される(1 breakpoint = 1 ブロック、同一 breakpoint 内は登録順)。セレクタは base と同じ詳細度 [data-scope][data-part](0,2,0)のため、同一 slot・同一プロパティを variant(0,3,0)が宣言していると variant が常に勝つ。breakpoint × variant / breakpoint × state の複合条件(@media 内の .fd-* クラス・:hover 規則)は未実装(スコープ外、docs/design/pre-styled-ui-scale-tokens.md §3.6/§7 参照)。段の値(chakra-ui v3・shadcn/ui〔Tailwind v4〕と sm 以外で完全一致、2xl は見送り)の採用根拠は同文書 §3.6 を参照。最初の消費者は breadcrumb の list(gap を Breakpoint::Sm で拡張、イシュー #2198)。
recipe::ContainerBreakpoint / SlotRecipe::container_slot / container / container_variant(イシュー #2199)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContainerBreakpoint { Sm, Md, Lg, Xl }
impl ContainerBreakpoint {
pub const ALL: [ContainerBreakpoint; 4];
pub const fn value(self) -> &'static str; // "sm" / "md" / "lg" / "xl"
pub const fn min_width(self) -> &'static str; // "384px" / "448px" / "512px" / "576px"
}
impl SlotRecipe {
pub fn container_slot(self, slot: &'static str) -> Self; // builder、自己消費
pub fn container(
self,
slot: &'static str,
cb: ContainerBreakpoint,
declarations: Vec<Declaration>,
) -> Self; // builder、自己消費
pub fn container_variant<V: VariantValue>(
self,
v: V,
slot: &'static str,
cb: ContainerBreakpoint,
declarations: Vec<Declaration>,
) -> Self; // builder、自己消費
}Breakpoint(viewport 幅の @media)と並列・独立の enum で、ContainerBreakpoint は要素自身の inline サイズを基準にする @container クエリを表現する。container_slot(slot) は container-type: inline-size; container-name: fd-<scope>-<slot>; を当該 slot の base ブロック群の直後へ出力し、container()/ container_variant() が登録した規則は SlotRecipe::css() の出力で「breakpoints の後・hover ブロックの前」に、ContainerBreakpoint::ALL の昇順(sm → xl、mobile-first)で @container fd-<scope>-<container_slot> (min-width: <cb.min_width()>) ブロックとして出力される(1 段 = 1 ブロック、同一段内は登録順)。container() は base と同じ詳細度 [data-scope][data-part](0,2,0)のセレクタ、container_variant() は variant と同じ [data-scope][data-part].fd-<scope>--<axis>-<value>(0,3,0)のセレクタを使う。container_slot() を呼んでいない、または宣言した slot が未宣言・不正識別子の場合は container()/container_variant() に登録された規則を一切出力しない(fail-closed、孤児 @container を出さない)。段の値(shadcn/ui〔Tailwind v4〕既定のコンテナクエリスケールと一致、3xs〜xs・2xl 以上は見送り)・container slot の決め方(無名 @container を不採用とする理由を含む)の採用根拠は docs/design/pre-styled-ui-scale-tokens.md §3.7 を参照。最初の消費者は field の orientation="responsive"(group を container slot・root を container_variant の対象、イシュー #2199)。
4m. sidebar(イシュー #2073、親 #2071。headless anatomy は #2072)
fandhe_frontend_headless_ui::sidebar(22 パーツ: provider/root/ header/content/footer/separator/input/group/group-label/ group-content/group-action/menu/menu-item/menu-button/ menu-action/menu-badge/menu-sub/menu-sub-item/menu-sub-button/ rail/trigger/inset)へ、variant(sidebar/floating/inset)3 種・collapsible(offcanvas/icon/none)3 種・side(left/right)2 種・モバイル drawer 表示という shadcn/ui Sidebar の意匠を重ねる薄い委譲層。command/item と同型の位置付けで、size/variant/color-palette いずれの軸クラスも持たない(見た目は headless の data-* を属性セレクタで参照するのみ)。
- 公開 API: 22 パーツの同名ラッパー関数(
headless-uiと同一シグネチャ、呼び出し側classはdrop_class_attrで除去して委譲)+stylesheet()+menu_skeleton(show_icon, attrs)(下記参照)。状態機械 [fandhe_frontend_headless_ui::sidebar::Sidebar]/SidebarAction/SidebarStateと静的 props 型(SidebarProps/SidebarCollapsible/SidebarVariant/SidebarSide/SidebarMenuButtonProps/SidebarMenuButtonSize/SidebarMenuButtonVariant/SidebarMenuSubButtonProps/SidebarMenuSubButtonSize)を選択的に再エクスポートする(provider/root/rail/triggerが&Sidebarを引数に取るため、commandとは異なりdrawerと同型で状態機械を再エクスポートする)。 - 軸を持たない理由:
command/input_groupと同型(子の寸法に従属するレイアウト部品。headless にも shadcn/ui にも軸が無い)。 --fandhe-sidebar-*(scope 接頭辞)と--fandhe-color-sidebar-*(色トークン)の使い分け: 幅トークン(--fandhe-sidebar-width/--fandhe-sidebar-width-icon/--fandhe-sidebar-width-mobile、--fandhe-drawer-sizeと同型のフォールバック付き)は recipe scope 接頭辞 custom property。色はdocs/design/color-token-system.md§9.3 の決定に従いTheme::default()が追加する--fandhe-color-sidebar-*7 ロール(sidebar-bg/sidebar-fg/sidebar-accent/sidebar-accent-fg/sidebar-muted/sidebar-border/sidebar-focus-ring)を参照する。menu_skeleton(ローディング装飾): headless モジュール doc の申し送りどおり、ランダム幅を持つ shadcn/ui 実装は SSR 決定性を壊すため採用せず、[crate::skeleton::skeleton](Circle〔show_iconがtrueのときのみ〕+Text)を固定幅で合成する決定的なfandhe_frontend_headless_ui::sidebar::menu_itemを返す。自前のdata-*は一切出力しない。- icon 折りたたみ時のテキスト非表示: headless
menu_buttonはテキストをspanで包まないため、data-collapsible="icon"かつ折りたたみ時はmenu-buttonをoverflow: hidden+ 固定幅で視覚的に切り落とす(アイコンをchildrenの先頭に置く呼び出し規約が前提。改善はスコープ外、後述)。 - モバイル + collapsed の詳細度調整:
rootの折りたたみ幅規則(属性 4 個)がdata-mobile単独(属性 3 個)より詳細度で勝つため、recipe().css()に加え raw CSS(属性 4 個 + ソース順で後)で[data-mobile][data-state="collapsed"]の幅を固定し、開閉はtransformのみで表現する。 backdropを追加しない理由: headless anatomy にbackdropパーツが無いため(commandのdialogと同型の判断)。- 責務境界(
docs/policy/intentional-non-adoption.md§3.25 規則 1): Cmd/Ctrl+B のグローバルショートカット・モバイル判定(メディアクエリ)・menu-buttonの tooltip hover 配線はfandhe-frontend-wasm-full(後続イシュー #2074)の責務として実装しない。 - スコープ外: #2074(wasm-full 配線、Cmd/Ctrl+B・モバイル判定・tooltip hover)・
backdropパーツの新設・menu-buttonテキストの headless 側 span 化による改善・@media (min-width)対応(breakpoint 機構 #2196/#2197 が未実装のため)。 - docs サイト: sidebar(イシュー #2075 でページ登録・showcase Demo・
SPEC_TABLES原稿を追加)。
5. 関連ドキュメント
docs/api/headless-ui-api.md: 本クレートの下層。Format 系 / Locale(§4e)は本クレートに対応モジュールを持たないため、掲載は同ドキュメントを正とする(本クレートからの到達経路は §2 表「headless 由来ユーティリティ」行・§3a 参照)docs/api/component-api.md:Node/el/text/raw_html/renderの凍結 API 表面docs/api/pre-styled-recipe-api.md: variant API・静的 CSS 生成の詳細examples/headless-pre-styled-ui/README.md: 本クレート v0.4.0 へ統合済みのショーケースサンプル(§2 参照).claude/skills/chakra-ui/: 設計時の参考にした chakra-ui リファレンススキルdocs/internal/pre-styled-ui-implementation-notes.md: 実装経緯・ロードマップ・トレーサビリティの記録(docs サイト非掲載のためリンク化しない)