【番外編】Focus Management APIについて(実装編) - React Ariaの実装読むぞ
こんにちは、フロントエンドエンジニアの 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 さんの記事をご覧ください。
まとめ
明日の担当は @mehm8128 さんで、 ProgressBar についての記事です。お楽しみにー
Discussion