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 参照)
本クレートは 106 の公開モジュール + 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 ' によるトップレベル公開モジュール 集計には計上されない)。106 は 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" を共有する。callout は本文中の補足情報向け静的部品で、alert と異なり live region ではないため role を付与しない、イシュー #994) | button / badge / spinner / alert / callout / card |
| 単純 styled 部品 | skeleton(ローディングプレースホルダー。text/circle/rect の 3 variant、常時 aria-hidden="true"、color-palette/size 軸は非提供、prefers-reduced-motion: reduce でアニメーション停止) | skeleton |
| 単純 styled 部品 | image(写真等の静的コンテンツを表示する <img>。ImageFit(object-fit)/AspectRatio の 2 軸 variant、alt 必須引数。headless-ui avatar の ImageStatus 状態機械とは独立。中立的な表示部品のため color-palette 軸は非提供) | image |
| 単純 styled 部品 | icon(インライン SVG の寸法を統一する <svg> ラッパー。size variant のみ、color: currentColor 継承のため color-palette 軸は非提供。SVG 本体(path 等)は呼び出し側がノード木 API で構築し、外部リソース(href/xlink:href)は本モジュール自身が参照しない) | icon |
| 単純 styled 部品 | separator(区切り線、<hr>。orientation(horizontal/vertical)・variant(solid/dashed)の 2 軸、常時 role="separator"/aria-orientation/data-orientation を出力、color-palette/size 軸は非提供) | 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 / 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 / textarea / native-select |
| 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(popover/tooltip と同型の判断で size/color-palette のいずれも非提供。branch のインデントは CSS custom property(--fandhe-tree-view-indent)で表現し、DOM ネストにより深さ分が自然に累積する) | tree-view |
headless ラッパー(tree_view の派生) | json_tree_view(構造部は tree_view の既存 recipe をそのまま再利用し、JSON 固有の key/value(data-scope="json-tree-view")2 パーツのみを追加する。value の data-kind へ型別配色(string/number/bool/null の 4 種、object/array は既定色のまま)を適用。tree_view と同型の判断で size/color-palette のいずれも非提供) | json-tree-view |
| headless ラッパー | pagination(size/color-palette 両軸提供) | pagination |
| headless ラッパー | steps(size/color-palette 両軸。fandhe_frontend_headless_ui::steps が自由関数を持たず全パーツが Steps の inherent メソッドのため、本モジュールの全パーツ関数が state: &Steps を受け取る点が他コンポーネントと異なる) | 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 で表現する) | drawer |
| headless ラッパー | link / link_overlay / nav_list(状態機械なし。link_overlay は ::before 疑似要素の代わりに overlay 自身を position: absolute; inset: 0; で展開する。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 ラッパー | 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 で行う) | navigation-menu |
pre-styled-ui 単独定義(headless-ui 変更なし、checkbox_card/radio_card §4g と同型の判断) | tab_nav(イシュー #996。新規 anatomy data-scope="tab-nav" を定義し role="tablist"/role="tab" を一切出力しない。見た目は tabs の list/trigger と宣言列を共有(tabs.rs の pub(crate) ヘルパ経由)するが、CSS セレクタは scope が異なるため別ルールになる。現在ページは aria-current="page" で示す。size/color-palette 軸は非提供) | 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、alert と同じ配色マッピング)の 2 軸 variant を持つが、各軸が別 slot へ付与されるため variant_class(単一軸専用 API)をスロットごとに個別に呼ぶ。Toaster 状態機械は再エクスポートしない。タイマー自動 dismiss・ActionTrigger の動作配線は wasm-full 後続のスコープ外) | 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 ラッパー | progress(headless の値状態機械 Progress が持つ Circle/CircleTrack/CircleRange(SVG)へ CSS のみ追加提供。Progress 型はあえて再エクスポートせず、size variant クラス付与のため styled root のみを新設する。circle 自身は headless の inherent メソッドをそのまま呼ばせる(クラス不要)。indeterminate 時の回転アニメーションは [data-part="circle"][data-state="indeterminate"] セレクタ + @keyframes で提供。linear(Track/Range)用の styled ラッパーは対応表(docs/design/component-coverage-map.md)が切り分けたスコープ外) | progress |
| 単純 styled 部品(静的) | tag / kbd / code(tag は variant/size/color-palette の 3 軸 variant を持つ root/label/close-trigger の 3 パーツ。badge と同型の判断。close-trigger は状態機械を持たず data-action 属性の出力のみを担う。kbd/code は variant 軸を持たない単一 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 自由関数をそのまま選択的に再エクスポートする) | 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 は variant(Line/Outline)/size/striped の 3 軸 variant を持ち、striped は新設の StateCondition::NthChildEven で表現する。data_list は orientation(Vertical/Horizontal)の 1 軸のみ) | 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 インライン三角形。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 は非提供。viewport へ overflow: auto + scrollbar-width/scrollbar-color(標準プロパティ)を付与し、stylesheet() が recipe().css() に続けて ::-webkit-scrollbar 系規則を固定文字列として追記する。scrollbar/thumb/corner は JS によるスクロール位置追従がスコープ外のため初期実装では display: none にしてネイティブスクロールバーの装飾で代替する) | scroll-area |
| headless ラッパー | splitter(size variant のみを root へ持ち resize-trigger の厚みへ継承、color-palette はセパレータの強調色にのみ使う。動的値は panel の --fandhe-splitter-size(flex-basis 経由)の 1 点のみ。resize-trigger はネイティブ <div tabindex> が実フォーカスを受けるため FocusVisible state condition で足りる) | splitter |
| 単純 styled 部品 | marquee(ark-ui の Root/Viewport/Content/Item/Edge anatomy を root/content/item の 3 パーツへ縮約(Viewport は root が兼ね、Edge は呼び出し側 CSS で代替可能なため非提供)。content を内部で 2 回複製しシームレスループを実現し、2 個目は常時 aria-hidden。direction(Start/End)の 1 軸 variant のみを root へ付与し content への伝搬は --fandhe-marquee-direction custom property の継承で行う。color-palette/size 軸は非提供。CSS のみ(JS ゼロ)・prefers-reduced-motion: reduce でのアニメーション停止・hover/focus-within での常時一時停止という決定的設計) | 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(Sm/Md/Lg)/shape(Square/Circle/Rounded、既定)の 2 軸 variant。color-palette 軸は非提供(表示する色そのものが value で決まるため)。透過色は background-image の 2 レイヤー(前面に色レイヤー、背面に固定チェッカーボード模様)で表現する) | 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 状態機械はあえて再エクスポートしない) | file-upload |
| headless ラッパー | calendar(size variant のみ、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 状態機械はあえて再エクスポートしない) | date-picker |
| headless ラッパー | timer(clipboard と同型の判断で variant は非提供。item-value に font-variant-numeric: tabular-nums を付与し桁の増減時のレイアウトシフトを防ぐ。completed 状態の item-value を強調色へ切り替え、action-trigger に focus-visible リングを付与する。実 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 属性で決まる静的部品)) | 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 の 4 CSS 変数(既定値つき var())で位置・寸法を表現し、実測値の注入も同後続の責務) | 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 基盤の最初の消費者。軸/グリッド/凡例/ツールチップ/積み上げ/曲線補間は §4j のスコープ) | line-chart / area-chart / sparkline |
| charts(SVG) | charts::bar_chart(縦/横 orientation のグループ棒グラフ。値軸はベースライン 0 起点、カテゴリ軸はバンドレイアウト(両端 10% padding + 系列数で均等割り)。系列色は series_color_var(chart-1〜chart-6 循環)。軸線・グリッド・凡例・ツールチップは §4j のスコープ、本モジュールはカテゴリラベルの最小出力のみ行う) | 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" を本クレートのみで定義する。系列 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 を検証)を持つ) | pie-chart / donut-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 をそのまま再エクスポートし、crop 矩形(整数)のみの決定的状態機械。動的な位置・寸法は --fandhe-image-cropper-x/-y/-w/-h の 4 custom property のみで伝搬し selection が一元的に組み立てる(headless 自由関数 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 パーツ関数を再定義しないモジュール 13 件は glob 再エクスポートを 維持する(現況は 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(tooltip と同型の glob re-export) |
toggle_tip | OpenState / DisclosureAction | state(tooltip と同型の glob re-export) |
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/tree_view/scroll_area/toggle_tip/menubar/json_tree_view/floating_panel/timer/navigation_menuの 13 モジュールが条件を満たすと判定済み(レビュー来歴は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(size, shape, attrs, children) -> Node: styled root パーツ。size(Size::Sm/Md/Lg、既定Md)・shape(AvatarShape::Circle/Rounded/Square、既定Circle)の 2 軸 variant に応じたクラス (fd-avatar--size-<value>/fd-avatar--shape-<value>)を付与する。 呼び出し側attrsのclassは除去してから合成するためclass属性は 常に単一。実体はfandhe_frontend_headless_ui::avatar::rootへ委譲する (呼び出し側data-scope/data-part偽装は headless 側で除去される)。AvatarShape:recipe::VariantValue実装 enum(Sizeと並ぶ本 クレート 2 例目の variant 軸)。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 参照)。
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) |
| avatar | ✓ | – (shape) | — |
| 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 の強調色に反映 |
| popover / tooltip | 提供しない | 提供しない | 配置・寸法が positioning 起因のため提供しない |
| tree-view | 提供しない | 提供しない | popover/tooltip と同型の判断 |
| json-tree-view | 提供しない | 提供しない | tree-view と同型の判断 |
| toggle-tip | 提供しない | 提供しない | popover/tooltip と同型の判断 |
| 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)の追加軸を持つ。striped は StateCondition::NthChildEven で表現 |
| data-list | 提供しない | 提供しない | orientation(Vertical/Horizontal)の 1 軸のみ |
| toast | ✓(placement、group slot) | ✓(status、root slot、alert と同じ配色マッピング) | 各軸が別 slot のため variant_class をスロットごとに個別呼び出し |
| tour | 提供しない | ✓(root slot) | size は overlay 系の寸法を呼び出し側の CSS カスタムプロパティ上書きに委ねるため初版非提供。palette は action-trigger の背景色・スポットライト縁取りの強調色に反映 |
| file-upload | ✓ | – | フォーム入力部品のため color-palette は非提供 |
| toolbar | 提供しない | 提供しない | ボタン・セパレータ・ToggleGroup のグループ化コンテナであり寸法・強調色の variant 対象外(イシュー #991) |
| menubar | 提供しない | 提供しない | 複数 Menu のグループ化コンテナであり寸法・強調色の variant 対象外(イシュー #992) |
| navigation-menu | 提供しない | 提供しない | トリガー起点のナビゲーションパネルであり寸法・強調色の variant 対象外(イシュー #993) |
| tab-nav | 提供しない | 提供しない | ナビゲーションリンク集合であり寸法・強調色の variant 対象外(イシュー #996) |
| checkbox-group | ✓ | ✓ | radio-group と同型(イシュー #997)。item-control の寸法・palette 塗りに反映 |
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-arrow-*/--fandhe-x/--fandhe-y(wasm positioning 契約)には手を触れない。 - 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 なし)は再 エクスポート維持。
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の 3 値 (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 層に 委譲済みで、本モジュールは再実装しない。
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 の padding)のみを持つ。title/descriptionは<div>(見出し 要素<h1>〜<h6>にしない)とし、埋め込み位置に応じて見出しレベルが 変わり得る呼び出し文脈で固定レベルを強制しない(crate::alert::titleと 同型の判断)。indicatorはアイコン等を children として受け取り、外部 リソース・アイコンフォントを本クレートが直接参照することはない。
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(sm/md/lg/xl(既定)/xl2/xl3/xl4、font-size/line-height、視覚サイズ) | なし | タグ選択(意味論)とサイズ variant(視覚)は独立。chakra の 5xl〜7xl はテーマトークン範囲外のため非採用 |
text | root(単一、<p> 固定) | — | TextSize(xs/sm/md(既定)/lg/xl) | なし | — |
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) | Node(infallible) |
charts::tooltip | datum_label(category, series, value) / datum(cx, cy, r, label, attrs) | String / Node(いずれも infallible) |
各モジュールは 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。gridの線種はGridLines(Solid(既定)/Dashed)の 1 軸 variant。tooltipの hover 強調はcrate::recipe::StateCondition::Hover(:hover擬似クラス)を使う唯一の消費者。
SSR ツールチップ方式(JS 不使用)
マウス追従型のリッチツールチップ(recharts <Tooltip> の cursor 追従)は JS ランタイムが必須のためスコープ外。代わりに tooltip::datum がデータ点 (<circle>)へ子 <title> 要素(ブラウザネイティブな hover 表示)と aria-label 属性(同一文字列)を埋め込み、StateCondition::Hover による CSS のみの視覚強調と組み合わせて「ホバーで詳細が分かる」体験を実現する。
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参照)。
4l. theme モジュール: Theme トークン API と upsert_*(イシュー #547/#606/#1138)
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>;
// 追加または上書き(イシュー #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 to_css(&self) -> String;
}色(colors)・影(shadows)はライト/ダーク 2 値、余白(spaces)・ タイポグラフィ(typography)・角丸(radii)はモード非依存の 1 値を取る。
push_* と upsert_* の意味論対比
| API | 同名トークンが既存の場合 | 用途 |
|---|---|---|
push_color / push_space / push_typography / push_radius / push_shadow | ThemeError::DuplicateTokenName を返して拒否(fail-closed) | 新規トークンの追加。意図しない上書きを防ぐ既定挙動 |
upsert_color / upsert_space / upsert_typography / upsert_radius / upsert_shadow | 挿入順(= 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 公開状況・バージョン確認方針を参照)。
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 サイト非掲載のためリンク化 しない)