【番外編】Focus Management APIについて(実装編)
React Ariaの実装読むぞ
この記事は他サイトから移行したものです。
この記事は React Aria の実装読むぞ - Qiita Advent Calendar 2024 の 18 日目の記事です。
こんにちは、フロントエンドエンジニアの mehm8128 です。 今日は Focus Management API の実装について書いていきます。
FocusScope #
FocusScopeコンポーネント内で色んな hooks を実行したり、useFocusManagerを実行できるようにするための Provider を提供したりしています。
scopeRefにFocusScope内の要素を配列として保持しているようで、以下のuseLayoutEffect内で取得しています。コメントにあるsentinelsというのはstartRefとendRefをつけているspan要素のことで、これを目印にしてここからここまで、というのを決めているようです。
ここで取得したscopeRefは次から見ていくuseFocusContainmentなどの hooks にも渡されています。
それでは、FocusScopeに渡すことができる props であるcontain, restoreFocus, autoFocusに関係する hooks を見ていきます。
useFocusContainment #
focus containment を実現する hook です。
onKeyDown関数で Tab キーによるフォーカス移動をe.preventDefault()した上で、 TreeWalker API(参考: Radio と Checkbox について - React Aria の実装読むぞ)などを用いて、最後の tabbable な要素から最初の tabbable な要素にフォーカスを移動する処理などが実装されています。
useRestoreFocus #
フォーカスの復元を実現する hook です。
mount 時にnodeToRestoreRefにdocument.activeElementで取得した現在フォーカスされている(このFocusScope外で最後にフォーカスされた)要素を入れておき、記憶しておきます。
つまり、RFC に書かれていたように「FocusScope内で最後にフォーカスを持っていた要素をそのFocusScopeが記憶しておく」のではなく、「現在アクティブな(内側にフォーカスされている要素を持っている)FocusScopeが、その外で最後にフォーカスを持っていた要素を記憶しておく」ような実装になっているのだと理解しました。
例えばダイアログとそのトリガーボタンだと、トリガーボタンが押されてフォーカスがダイアログ内に移動したときに、トリガーボタンに最後にフォーカスがあったということをダイアログとトリガーボタンを囲っているFocusScopeが記憶しているのではなく、ダイアログだけを囲っているFocusScopeが新しく記憶し、そのFocusScopeが unmount されたタイミングでその記憶している要素にフォーカスを戻すようになっているということです。
こっそり追記しておいたのですが、Toast について - React Aria の実装読むぞの記事で言及していた疑問もこれで解消されました。
フォーカスの復元処理はここらへんでrestoreFocusToElement関数で行っているようです。
また、FocusScopeコンポーネント内で、アクティブなFocusScopeの変更も行っています。
useAutoFocus #
auto focus を実現する hook です。
mount 時にgetFirstInScope関数を用いて、FocusScope内の最初の tabbable な要素にフォーカスします。なお、tabbable な要素が見つからなかったら最初の focusable な要素にフォーカスします。
useFocusManager #
useFocusManagerは親のFocusScopeから context を受け取って色んなメソッドを実行できるようになっています。ここらへんで TreeWalker API を使って実装されています。
focusgroupについて #
本当は昨日の記事で書く予定だったのですが、書く時間がなかったのでこの記事で補足します。
Open UI に、focusgroupという HTML 属性の Proposal があります。これは現在 ref などを用いて Programmically にキーボード操作によるフォーカス移動をしているのを、HTML 属性だけで制御できるようにするというものです。詳しくは僕もまだ読めていないので、Open UI の Proposal や azukiazusa さんの記事をご覧ください。
https://open-ui.org/components/focusgroup.explainer/ https://azukiazusa.dev/blog/focusgroup-arrow-key-focus-navigation/
まとめ #
明日の担当は @mehm8128 さんで、 ProgressBar についての記事です。お楽しみにー