【番外編】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