From 324bdc666f59815619b1d4c3c0a9fc17834df572 Mon Sep 17 00:00:00 2001 From: Aaron Cannon Date: Mon, 6 Apr 2020 10:01:29 -0500 Subject: [PATCH] dialog: added support for the ariaHideAncestors prop. --- packages/dialog/__tests__/dialog.test.tsx | 53 ++++++++++++++++++++++- packages/dialog/src/index.tsx | 30 ++++++++++--- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/packages/dialog/__tests__/dialog.test.tsx b/packages/dialog/__tests__/dialog.test.tsx index 31e8ce471..e2aeb2d10 100644 --- a/packages/dialog/__tests__/dialog.test.tsx +++ b/packages/dialog/__tests__/dialog.test.tsx @@ -4,7 +4,7 @@ import { useFakeTimers, SinonFakeTimers } from "sinon"; import { axe, toHaveNoViolations } from "jest-axe"; import { fireEvent, render, act, userEvent } from "$test/utils"; import { AxeResults } from "$test/types"; -import { Dialog } from "@reach/dialog"; +import { Dialog, DialogProps } from "@reach/dialog"; function getOverlay(container: Element) { return container.querySelector("[data-reach-dialog-overlay]"); @@ -58,6 +58,39 @@ describe("", () => { ); expect(label).toHaveTextContent("I am the title now"); }); + + it("ARIA hides ancestors by default", async () => { + const { findByTestId } = render( + <> +
Hidden
+ + + ); + const sibling = await findByTestId("sibling"); + expect(isAriaHidden(sibling)).toBe(true); + }); + + it("ARIA hides ancestors when ariaHideAncestors is true", async () => { + const { findByTestId } = render( + <> +
Hidden
+ + + ); + const sibling = await findByTestId("sibling"); + expect(isAriaHidden(sibling)).toBe(true); + }); + + it("doesn't ARIA hide ancestors when ariaHideAncestors is false", async () => { + const { findByTestId } = render( + <> +
Not hidden
+ + + ); + const sibling = findByTestId("sibling"); + expect(isAriaHidden(sibling)).toBe(false); + }); }); describe("user events", () => { @@ -83,7 +116,7 @@ describe("", () => { }); }); -function BasicOpenDialog() { +function BasicOpenDialog(props: DialogProps) { const [showDialog, setShowDialog] = useState(true); return (
@@ -92,6 +125,7 @@ function BasicOpenDialog() { aria-label="Announcement" isOpen={showDialog} onDismiss={() => setShowDialog(false)} + {...props} >
@@ -102,3 +136,18 @@ function BasicOpenDialog() {
); } + +/** + * Checks the element and its ancestors for an aria-hidden attribute. + * If an aria-hidden is found, we assume it is set to true, which should be a + * reasonable assumption for the purposes of these tests. + */ +function isAriaHidden(el?: HTMLElement) { + while (el) { + if (el.hasAttribute("aria-hidden")) { + return true; + } + el = el.parentElement; + } + return false; +} diff --git a/packages/dialog/src/index.tsx b/packages/dialog/src/index.tsx index 31c009716..d7c4f9097 100644 --- a/packages/dialog/src/index.tsx +++ b/packages/dialog/src/index.tsx @@ -28,6 +28,7 @@ const overlayPropTypes = { initialFocusRef: () => null, allowPinchZoom: PropTypes.bool, onDismiss: PropTypes.func, + ariaHideAncestors: PropTypes.bool, }; //////////////////////////////////////////////////////////////////////////////// @@ -92,6 +93,7 @@ const DialogInner = forwardRef( onDismiss = noop, onMouseDown, onKeyDown, + ariaHideAncestors, ...props }, forwardedRef @@ -124,11 +126,11 @@ const DialogInner = forwardRef( mouseDownTarget.current = event.target; } - useEffect( - () => - overlayNode.current ? createAriaHider(overlayNode.current) : void null, - [] - ); + useEffect(() => { + if (overlayNode.current && ariaHideAncestors) { + createAriaHider(overlayNode.current); + } + }, [ariaHideAncestors]); return ( @@ -225,7 +227,14 @@ if (__DEV__) { * @see Docs https://reacttraining.com/reach-ui/dialog#dialog */ export const Dialog = forwardRef(function Dialog( - { isOpen, onDismiss = noop, initialFocusRef, allowPinchZoom, ...props }, + { + isOpen, + onDismiss = noop, + initialFocusRef, + allowPinchZoom, + ariaHideAncestors = true, + ...props + }, forwardedRef ) { return ( @@ -234,6 +243,7 @@ export const Dialog = forwardRef(function Dialog( allowPinchZoom={allowPinchZoom} isOpen={isOpen} onDismiss={onDismiss} + ariaHideAncestors={ariaHideAncestors} > @@ -265,6 +275,13 @@ export type DialogProps = { * @see Docs https://reacttraining.com/reach-ui/dialog#dialog-ondismiss */ onDismiss?: (event?: React.SyntheticEvent) => void; + /** + * By default, all of the nodes at the document.body root hav aria-hidden set + * on them, except for the currently active dialog. + * + * @see Docs https://reacttraining.com/reach-ui/dialog#aria-hiding-other-elements + */ + ariaHideAncestors?: boolean; /** * Accepts any renderable content. * @@ -287,6 +304,7 @@ if (__DEV__) { onDismiss: PropTypes.func, "aria-label": ariaLabelType, "aria-labelledby": ariaLabelType, + ariaHideAncestors: PropTypes.bool, }; }