【番外編】Focus Management APIについて(実装編)

React Ariaの実装読むぞ


目次
  1. FocusScope
    1. useFocusContainment
    2. useRestoreFocus
    3. useAutoFocus
  2. useFocusManager
  3. について
  4. まとめ
Warn

この記事は他サイトから移行したものです。

Note

この記事は React Aria の実装読むぞ - Qiita Advent Calendar 2024 の 18 日目の記事です。

こんにちは、フロントエンドエンジニアの mehm8128 です。 今日は Focus Management API の実装について書いていきます。

FocusScope #

FocusScopeコンポーネント内で色んな hooks を実行したり、useFocusManagerを実行できるようにするための Provider を提供したりしています。

scopeRefFocusScope内の要素を配列として保持しているようで、以下のuseLayoutEffect内で取得しています。コメントにあるsentinelsというのはstartRefendRefをつけている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 時にnodeToRestoreRefdocument.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 についての記事です。お楽しみにー