コンテンツにスキップ

レイジーツリー

レイジーツリーは深い枝を先送りします: ツリーコンポーネント空のグループつきで描画され、ユーザーが枝を初めて展開したときに、 子が サーバレンダリングされた <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 トリガーは消費済みなので、 再展開してもリトライされない点に注意してください。

  • readme.md
<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__groupfind はアイテムの内側の最初の一致、 つまり自身のグループに解決されます。
  • innerHTML — グループを埋め、ビヘイビアが観測する <ul> を 保ちます。リクエストの実行中、ビヘイビアは空のグループに aria-busy="true" を付けます(CSS のスピナーが描画され、子が届くと クリアされます)。
  • aria-expanded は必須です — 枝であることは属性で宣言され、 ビヘイビアが hc:treeexpand を発火するのは枝の上だけです。

グループの 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 なし)としてマークしてください。

  • スワップインした子は APG のツリーパターンを保ちます: installTree()role="treeitem" / role="group" を再適用し、 新しい項目を roving tabindex に組み込みます — 届いた瞬間から キーボードで移動できます。
  • 遅延読み込みはキーボード契約を何も変えません: 閉じたレイジーな枝で を押すと開き(フェッチが発火)、子が届けば でその中へ降りていけます。
  • リクエスト中、空のグループは aria-busy="true" を持ち、保留状態が 公開されます。描画される CSS のみのスピナーは prefers-reduced-motion では無効になります。
  • 枝であることは aria-expanded が宣言します — スクリーンリーダーが 読み上げる状態は、ビヘイビアがトグルするのと同じ属性です。

プログレッシブエンハンスメント

Section titled “プログレッシブエンハンスメント”

JavaScript なしでは、マークアップは素の入れ子リストです — 描画された すべてのリンクが機能し続けます。サーバでツリーを完全展開で描画する (レイジーロードは純粋なエンハンスメント)か、各枝のラベルにノード別 ページへの本物のリンクを与えてください: リンクこそが フォールバックです。

  • ツリー — コンポーネント本体: マークアップ、キーボードモデル、状態、トークン。
  • レイジーパネル — 同じ「必要になるまで遅延」を平坦な領域に。
  • トースト — 消費済みトリガーのエラー可視化。