From 46838a95b053cc827b78f0d01ef242b0a2892b65 Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Mon, 28 Aug 2023 10:40:46 -0400 Subject: [PATCH 1/6] set focus on scrolltotop of comments --- src/core/client/stream/common/scrollToBeginning.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/client/stream/common/scrollToBeginning.ts b/src/core/client/stream/common/scrollToBeginning.ts index f173fa3612..0171f660fb 100644 --- a/src/core/client/stream/common/scrollToBeginning.ts +++ b/src/core/client/stream/common/scrollToBeginning.ts @@ -12,6 +12,8 @@ function scrollToBeginning( } else { window.scrollTo({ top: getElementWindowTopOffset(window, tab) }); } + // set keyboard focus to Comments button for accessibility + tab.getElementsByTagName("button")[0].focus(); } } From 7225cd3cc885d87e05ca0424edc8d2aa861a2060 Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Mon, 28 Aug 2023 11:08:49 -0400 Subject: [PATCH 2/6] add aria-expanded to manage ignored commenters --- .../tabs/Profile/Preferences/IgnoreUserSettingsContainer.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/client/stream/tabs/Profile/Preferences/IgnoreUserSettingsContainer.tsx b/src/core/client/stream/tabs/Profile/Preferences/IgnoreUserSettingsContainer.tsx index a724eb3088..f26932b43b 100644 --- a/src/core/client/stream/tabs/Profile/Preferences/IgnoreUserSettingsContainer.tsx +++ b/src/core/client/stream/tabs/Profile/Preferences/IgnoreUserSettingsContainer.tsx @@ -82,6 +82,7 @@ const IgnoreUserSettingsContainer: FunctionComponent = ({ viewer }) => { upperCase onClick={toggleManage} className={CLASSES.ignoredCommenters.manageButton} + aria-expanded="true" > Close @@ -103,6 +104,7 @@ const IgnoreUserSettingsContainer: FunctionComponent = ({ viewer }) => { CLASSES.ignoredCommenters.manageButton )} aria-label="Manage ignored commenters" + aria-expanded="false" > Manage From 594e1fa2647c9125b84eec37fe3ab46d5f544ebf Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Mon, 28 Aug 2023 11:22:25 -0400 Subject: [PATCH 3/6] add aria-expanded to other expandable content too --- src/core/client/stream/tabs/Comments/Comment/CommentToggle.tsx | 1 + .../client/stream/tabs/Comments/Comment/IndentedComment.tsx | 1 + .../Comments/Comment/MediaSection/MediaSectionContainer.tsx | 2 ++ 3 files changed, 4 insertions(+) diff --git a/src/core/client/stream/tabs/Comments/Comment/CommentToggle.tsx b/src/core/client/stream/tabs/Comments/Comment/CommentToggle.tsx index a4333d3075..1561332a8b 100644 --- a/src/core/client/stream/tabs/Comments/Comment/CommentToggle.tsx +++ b/src/core/client/stream/tabs/Comments/Comment/CommentToggle.tsx @@ -40,6 +40,7 @@ const CommentToggle: FunctionComponent = (props) => { onClick={props.toggleCollapsed} className={cn(styles.root, CLASSES.comment.collapseToggle.$root)} aria-label={"Expand comment thread"} + aria-expanded="false" > = ({ styles.toggleButton, CLASSES.comment.collapseToggle.$root )} + aria-expanded="true" > = ({ onClick={onToggleExpand} size="small" className={styles.button} + aria-expanded="false" > {media.__typename === "TwitterMedia" && ( @@ -129,6 +130,7 @@ const MediaSectionContainer: FunctionComponent = ({ size="small" iconLeft className={styles.button} + aria-expanded="true" > Date: Mon, 28 Aug 2023 12:48:04 -0400 Subject: [PATCH 4/6] update gotoconvo aria-labels --- src/locales/en-US/stream.ftl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/en-US/stream.ftl b/src/locales/en-US/stream.ftl index d9df263453..6aa895eae6 100644 --- a/src/locales/en-US/stream.ftl +++ b/src/locales/en-US/stream.ftl @@ -450,9 +450,9 @@ comments-featured-label = Featured Comment from {$username} comments-featured-gotoConversation = Go to conversation comments-featured-gotoConversation-label-with-username = - .aria-label = Go to this featured comment by user { $username } in the main comment stream + .aria-label = Go to conversation for this featured comment by user { $username } in the main comment stream comments-featured-gotoConversation-label-without-username = - .aria-label = Go to this featured comment in the main comment stream + .aria-label = Go to conversation for this featured comment in the main comment stream comments-featured-replies = Replies ## Profile Tab From 8d651bda3ba58262cffafe6f880f1c3b82154ebb Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Mon, 28 Aug 2023 14:58:50 -0400 Subject: [PATCH 5/6] focus first keyboard focusable element after scroll to top article --- .../Stream/CommentsLinks/CommentsLinks.tsx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/core/client/stream/tabs/Comments/Stream/CommentsLinks/CommentsLinks.tsx b/src/core/client/stream/tabs/Comments/Stream/CommentsLinks/CommentsLinks.tsx index 074d362785..453a244ab0 100644 --- a/src/core/client/stream/tabs/Comments/Stream/CommentsLinks/CommentsLinks.tsx +++ b/src/core/client/stream/tabs/Comments/Stream/CommentsLinks/CommentsLinks.tsx @@ -5,6 +5,7 @@ import React, { FC, FunctionComponent, useCallback, + useMemo, } from "react"; import { useCoralContext } from "coral-framework/lib/bootstrap"; @@ -57,12 +58,37 @@ const CommentsLinks: FunctionComponent = ({ showGoToProfile, }) => { const { renderWindow, customScrollContainer } = useCoralContext(); + + // Find first keyboard focusable element for accessibility + const firstKeyboardFocusableElement = useMemo(() => { + const container = customScrollContainer ?? renderWindow.document; + const visibleFocusableElements: Element[] = []; + const focusableElements = container.querySelectorAll( + 'a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])' + ); + focusableElements.forEach((el) => { + if ( + !el.hasAttribute("disabled") && + !el.getAttribute("aria-hidden") && + !el.getAttribute("hidden") + ) { + visibleFocusableElements.push(el); + } + }); + return visibleFocusableElements[0]; + }, [renderWindow, customScrollContainer]); + const root = useShadowRootOrDocument(); const onGoToArticleTop = useCallback(() => { if (customScrollContainer) { customScrollContainer.scrollTo({ top: 0 }); } renderWindow.scrollTo({ top: 0 }); + // programmatically apply focus to first keyboard focusable element + // after scroll for accessibility + if (firstKeyboardFocusableElement instanceof HTMLElement) { + firstKeyboardFocusableElement.focus(); + } }, [renderWindow, customScrollContainer]); const onGoToCommentsTop = useCallback(() => { scrollToBeginning(root, renderWindow, customScrollContainer); From 718e56859ee1a90d9dc17149f80c8ed04c72bb4a Mon Sep 17 00:00:00 2001 From: Kathryn Beaty Date: Tue, 29 Aug 2023 11:04:50 -0400 Subject: [PATCH 6/6] improve getting first keyboard focusable element --- .../Stream/CommentsLinks/CommentsLinks.tsx | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/core/client/stream/tabs/Comments/Stream/CommentsLinks/CommentsLinks.tsx b/src/core/client/stream/tabs/Comments/Stream/CommentsLinks/CommentsLinks.tsx index 453a244ab0..567eb0dbce 100644 --- a/src/core/client/stream/tabs/Comments/Stream/CommentsLinks/CommentsLinks.tsx +++ b/src/core/client/stream/tabs/Comments/Stream/CommentsLinks/CommentsLinks.tsx @@ -5,7 +5,6 @@ import React, { FC, FunctionComponent, useCallback, - useMemo, } from "react"; import { useCoralContext } from "coral-framework/lib/bootstrap"; @@ -60,22 +59,27 @@ const CommentsLinks: FunctionComponent = ({ const { renderWindow, customScrollContainer } = useCoralContext(); // Find first keyboard focusable element for accessibility - const firstKeyboardFocusableElement = useMemo(() => { + const getFirstKeyboardFocusableElement = useCallback(() => { const container = customScrollContainer ?? renderWindow.document; - const visibleFocusableElements: Element[] = []; + let firstFocusableElement: Element | null = null; + let counter = 0; const focusableElements = container.querySelectorAll( 'a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])' ); - focusableElements.forEach((el) => { + while ( + firstFocusableElement === null && + counter < focusableElements.length + ) { if ( - !el.hasAttribute("disabled") && - !el.getAttribute("aria-hidden") && - !el.getAttribute("hidden") + !focusableElements[counter].hasAttribute("disabled") && + !focusableElements[counter].getAttribute("aria-hidden") && + !focusableElements[counter].getAttribute("hidden") ) { - visibleFocusableElements.push(el); + firstFocusableElement = focusableElements[counter]; } - }); - return visibleFocusableElements[0]; + counter++; + } + return firstFocusableElement; }, [renderWindow, customScrollContainer]); const root = useShadowRootOrDocument(); @@ -86,10 +90,11 @@ const CommentsLinks: FunctionComponent = ({ renderWindow.scrollTo({ top: 0 }); // programmatically apply focus to first keyboard focusable element // after scroll for accessibility + const firstKeyboardFocusableElement = getFirstKeyboardFocusableElement(); if (firstKeyboardFocusableElement instanceof HTMLElement) { firstKeyboardFocusableElement.focus(); } - }, [renderWindow, customScrollContainer]); + }, [renderWindow, customScrollContainer, getFirstKeyboardFocusableElement]); const onGoToCommentsTop = useCallback(() => { scrollToBeginning(root, renderWindow, customScrollContainer); }, [root, renderWindow, customScrollContainer]);