Menuについて - React Ariaの実装読むぞ
こんにちは、フロントエンドエンジニアの mehm8128 です。
今日は Menu について書いていきます。ついに半分です。
使用例
ドキュメントからそのまま取ってきています。
function MenuButton<T extends object>(props: MenuButtonProps<T>) {
// Create state based on the incoming props
let state = useMenuTriggerState(props);
// Get props for the button and menu elements
let ref = React.useRef(null);
let { menuTriggerProps, menuProps } = useMenuTrigger<T>({}, state, ref);
return (
<>
<Button
{...menuTriggerProps}
buttonRef={ref}
style={{ height: 30, fontSize: 14 }}
>
{props.label}
<span aria-hidden="true" style={{ paddingLeft: 5 }}>
▼
</span>
</Button>
{state.isOpen && (
<Popover state={state} triggerRef={ref} placement="bottom start">
<Menu {...props} {...menuProps} />
</Popover>
)}
</>
);
}
本題
APG はこちらです。
サブメニュー
useSubmenuTrigger
などを利用することで、メニューアイテムをホバーしてさらに子のメニューを表示できるようになります。これについて、フォーカス移動とuseSafelyMouseToSubmenu
の 2 点に着目して詳細を見ていこうと思います。
ただし、APG ではサブメニューは避けることが推奨されていました。
Although it is recommended that authors avoid doing so, some implementations of navigation menubars may have menuitem elements that both perform a function and open a submenu.
フォーカス移動
サブメニューのトリガーにフォーカスしている場合、普通のメニューアイテムと同じくEnter
などでもサブメニューを展開できるし、→
キー(水平メニューの場合↓
キー)でも展開できるようになっています。
逆に、サブメニューにフォーカスしているときに、開いたときと逆の矢印キーを押すとサブメニューを閉じることができます。
useSafelyMouseToSubmenu
サブメニューのトリガーにホバーしたときにサブメニューが表示されますが、トリガーからサブメニューのアイテムに移動するまでに、サブメニューではない部分をマウスカーソルが通過することがあります。その際にサブメニューが閉じてしまわないように、React Aria では工夫がされています。この工夫は以下のブログ記事に記載されているので、簡単にまとめます。
React Aria では、トリガー起動時のカーソル位置と、サブメニューの上端と下端(のトリガー起動時のカーソル位置側の各頂点)の 3 点を結ぶ三角形のエリアを想定し、この中をカーソルが移動しているときにはサブメニューを閉じないようにしています(ただ、記事内で書かれているようにこのエリア内でも一定時間カーソル移動がなかった場合には閉じます)。
この判定にはMath.atan2
という関数が用いられています。これはtan
の逆関数であるatan
を少し改善したものになっていて、違いは上記の MDN か、記事内でも参照されているatan2 - Wikipediaをご覧ください。
例えば
で求めることができます。
atan2
を用いて、それぞれの角度(
なお、今回の図の場合
実装はここらへんです。
また、Floating UI というライブラリでも同じように三角形のエリアを考えた、ユーザー体験の向上が行われているようです。
実装はここらへんにありそうでした。
まとめ
明日の担当は @mehm8128 さんで、 Disclosure についての記事です。お楽しみにー
Discussion