🪗

Disclosureについて - React Ariaの実装読むぞ

2024/12/13に公開

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

最初にちょっと記事書いていたときはまだ本番環境のドキュメントに存在していなくて、http://localhost:1234/react-aria/useDisclosure.htmlを貼ろうとしていたのですが、11 月のリリースで入ったようなので見れるようになっていました。

https://react-spectrum.adobe.com/react-aria/useDisclosure.html

使用例

ドキュメントからそのまま取ってきています。

function Disclosure(props) {
  let state = useDisclosureState(props);
  let panelRef = React.useRef<HTMLDivElement | null>(null);
  let triggerRef = React.useRef<HTMLButtonElement | null>(null);
  let { buttonProps: triggerProps, panelProps } = useDisclosure(
    props,
    state,
    panelRef
  );
  let { buttonProps } = useButton(triggerProps, triggerRef);
  let { isFocusVisible, focusProps } = useFocusRing();

  return (
    <div className="disclosure">
      <h3>
        <button
          className="trigger"
          ref={triggerRef}
          {...mergeProps(buttonProps, focusProps)}
          style={{ outline: isFocusVisible ? "2px solid dodgerblue" : "none" }}
        >
          <svg viewBox="0 0 24 24">
            <path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
          </svg>
          {props.title}
        </button>
      </h3>
      <div className="panel" ref={panelRef} {...panelProps}>
        <p>{props.children}</p>
      </div>
    </div>
  );
}

本題

APG はこちらです。
https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/

grouprole とaria-属性

ボタンとパネルを結びつけたり、現在 disclosure が開いているかどうかを表したりするために、いくつかのaria-属性が用いられています。

https://github.com/adobe/react-spectrum/blob/3f44370de69e48ee56cbf2bbd8664cee8294e9fe/packages/%40react-aria/disclosure/src/useDisclosure.ts#L95-L98

https://github.com/adobe/react-spectrum/blob/3f44370de69e48ee56cbf2bbd8664cee8294e9fe/packages/%40react-aria/disclosure/src/useDisclosure.ts#L111-L114

aria-expandedの boolean で現在開いているかどうかの状態を表し、aria-controlsでパネル(コンテンツ)と結びつけています。

また、非表示のときはaria-hiddenhidden属性がついています。

https://github.com/adobe/react-spectrum/blob/3f44370de69e48ee56cbf2bbd8664cee8294e9fe/packages/%40react-aria/disclosure/src/useDisclosure.ts#L116-L117

grouprole が用いられているのは、detail要素の暗黙の ARIA role がgrouprole だからです。

https://w3c.github.io/html-aria/#el-details

hidden="until-found"

hidden="until-found"がつけられています。

https://github.com/adobe/react-spectrum/blob/3f44370de69e48ee56cbf2bbd8664cee8294e9fe/packages/%40react-aria/disclosure/src/useDisclosure.ts#L71-L84

詳しい説明は MDN に任せるのですが、disclosure が閉じている状態でもページ内検索などでは disclosure 内のコンテンツがヒットするようにし、その結果コンテンツまでスクロールされたらhidden属性を外してコンテンツを表示するようにする、というものです。

https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/hidden#hidden_until_found_状態

ページ内検索やフラグメントナビゲーション(URL の後ろに#をつけるやつ)で対象のコンテンツを表示しようとしたときにbeforematchイベントが発火され、それを購読して以下のコードの箇所で処理を行っています。

https://github.com/adobe/react-spectrum/blob/993de98adad65e48bcebad8ac835f5c9e0c94c85/packages/%40react-aria/disclosure/src/useDisclosure.ts#L55-L69

なお、React 側がまだ対応していないので先ほどのコードのようにuseLayoutEffect内で無理やり属性をつけていたり、Firefox と Safari がまだbeforematchサポートしていないので対応しているブラウザのみで処理を行うようなロジックになっています。
https://github.com/facebook/react/pull/24741
https://caniuse.com/mdn-html_global_attributes_hidden_until-found_value
https://caniuse.com/mdn-api_element_beforematch_event

まとめ

明日の担当は @mehm8128 さんで、番外編 テストについての記事です。お楽しみにー

Discussion