レイジーツリー
レイジーツリーは深い枝を先送りします:
ツリーコンポーネントは
空のグループつきで描画され、ユーザーが枝を初めて展開したときに、
子が サーバレンダリングされた <li class="hc-tree__item">
フラグメントとして届きます。installTree() は枝が開くとバブリングする
hc:treeexpand を発火し、htmx が once トリガーで待ち受けて
リクエストを所有します — ビヘイビアはネットワークに触れません。
枝を展開してみてください(行をクリックするか →)—
api/recipes/lazy-tree/ 配下のデモエンドポイントから、子がサーバ
レンダリングされた treeitem として届きます。once により、畳んで
再展開しても再取得されません。Restricted は契約のアクセス拒否
分岐のデモです: 小さな空でない 200 フラグメント(「No access」)が
グループを満たし — それが aria-busy をクリアし — HX-Trigger
ヘッダーがエラートーストを出します。once トリガーは消費済みなので、
再展開してもリトライされない点に注意してください。
- Reports
- Assets
- Restricted
- readme.md
レイジーな枝
Section titled “レイジーな枝”<li class="hc-tree__item" aria-expanded="false" data-hx-get="/nodes/42/children" data-hx-target="find .hc-tree__group" data-hx-swap="innerHTML" data-hx-trigger="hc:treeexpand once"> <span class="hc-tree__row"> <span class="hc-tree__toggle" aria-hidden="true"></span> <span class="hc-tree__label">Reports</span> </span> <ul class="hc-tree__group"></ul></li>hc:treeexpand once— htmx は初回の展開でだけ取得します。onceがすべての鍵です: 再度畳んで開いても、読み込み済みの子が 追加リクエストなしで表示されます。これがないと、展開のたびに再取得と 再スワップが起こり、サブツリーが吹き飛びます。find .hc-tree__group—findはアイテムの内側の最初の一致、 つまり自身のグループに解決されます。innerHTML— グループを埋め、ビヘイビアが観測する<ul>を 保ちます。リクエストの実行中、ビヘイビアは空のグループにaria-busy="true"を付けます(CSS のスピナーが描画され、子が届くと クリアされます)。aria-expandedは必須です — 枝であることは属性で宣言され、 ビヘイビアがhc:treeexpandを発火するのは枝の上だけです。
サーバレスポンス契約
Section titled “サーバレスポンス契約”グループの innerHTML だけ — treeitem のフラグメント群を返します:
<li class="hc-tree__item"> <span class="hc-tree__row"> <span class="hc-tree__label"><a href="/reports/summary">summary.pdf</a></span> </span></li>レスポンス内の入れ子のレイジーな枝は再帰します: フラグメントに
同じ 4 属性と空のグループを与えてください。htmx はセトリング時に
新しい data-hx-* 属性を処理し、ビヘイビアが roles とロービングタブ
インデックスを再適用するため、スワップインされたアイテムは即座に
キーボード操作可能です(届いた直後に → で降下できます)。
htmx は非 2xx レスポンスをスワップしないため、エラー時には枝は空の
ままです — そして once トリガーは消費済みです: 再展開しても
リトライされません。この制約には正直でいてください:
- 失敗は
HX-Triggerトーストで 可視化し、サーバ側の信頼性を優先してください。サブツリーが本当に よく失敗するなら、eager に描画するほうが良いです。 - このレシピはリトライの振り付けで制約を隠す代わりに、制約を ドキュメント化します。
サーバ自身が失敗に応答する場合(たとえば権限拒否の枝)は、空で
ない 200 フラグメント — 「No access」のリーフ 1 つ — とエラー
トーストで応答してください。ビヘイビアは子が届いたときにグループの
aria-busy をクリアするため、空のボディではスピナーが固まったままに
なります。フラグメントはクリアと理由の提示を同時に果たします。空は
本当に空の枝のためにとっておき、そうした枝は次のフル描画で展開不可
(aria-expanded なし)としてマークしてください。
アクセシビリティ
Section titled “アクセシビリティ”- スワップインした子は APG のツリーパターンを保ちます:
installTree()がrole="treeitem"/role="group"を再適用し、 新しい項目を roving tabindex に組み込みます — 届いた瞬間から キーボードで移動できます。 - 遅延読み込みはキーボード契約を何も変えません: 閉じたレイジーな枝で → を押すと開き(フェッチが発火)、子が届けば → でその中へ降りていけます。
- リクエスト中、空のグループは
aria-busy="true"を持ち、保留状態が 公開されます。描画される CSS のみのスピナーはprefers-reduced-motionでは無効になります。 - 枝であることは
aria-expandedが宣言します — スクリーンリーダーが 読み上げる状態は、ビヘイビアがトグルするのと同じ属性です。
プログレッシブエンハンスメント
Section titled “プログレッシブエンハンスメント”JavaScript なしでは、マークアップは素の入れ子リストです — 描画された すべてのリンクが機能し続けます。サーバでツリーを完全展開で描画する (レイジーロードは純粋なエンハンスメント)か、各枝のラベルにノード別 ページへの本物のリンクを与えてください: リンクこそが フォールバックです。