From 8f41648f3f6ee7603007b466596cc94d979e61b0 Mon Sep 17 00:00:00 2001 From: Cyril Date: Mon, 22 Jun 2026 15:59:34 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=E2=99=BF=EF=B8=8F(frontend)=20limit=20shar?= =?UTF-8?q?e=20modal=20opening=20announcement=20for=20screen=20readers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Autofocus close and hide modal body from SR when search input is hidden. --- CHANGELOG.md | 1 + .../doc-share/components/DocShareModal.tsx | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aafeb4ab4..45a8074da6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to ### Changed - ♻️(backend) reset collaboration connection in cascade for all children #2507 +- ♿️(frontend) limit share modal opening announcement for screen readers #2452 ### Fixed diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx index 01f1c729a1..7288872d49 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx @@ -40,6 +40,8 @@ import { import { QuickSearchGroupMember } from './DocShareMember'; import { DocShareModalFooter } from './DocShareModalFooter'; +const DEBOUNCE_MS = 300; + const ShareModalStyle = createGlobalStyle` .--docs--doc-share-modal [cmdk-item] { cursor: auto; @@ -84,6 +86,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { const [listHeight, setListHeight] = useState('400px'); const canShare = doc.abilities.accesses_manage && isRootDoc; + const [isContentAccessible, setIsContentAccessible] = useState(canShare); const canViewAccesses = doc.abilities.accesses_view; const showMemberSection = inputValue === '' && selectedUsers.length === 0; const showFooter = selectedUsers.length === 0 && !inputValue; @@ -119,7 +122,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { const onFilter = useDebouncedCallback((str: string) => { setUserQuery(str); - }, 300); + }, DEBOUNCE_MS); const onRemoveUser = (row: User) => { setSelectedUsers((prevState) => { @@ -161,6 +164,20 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { const showInheritedShareContent = inheritedAccesses.length > 0 && showMemberSection && !isRootDoc; + // When the search input is hidden, keep the modal content out of the + // accessibility tree during the opening announcement, then restore it. + useEffect(() => { + if (canShare) { + return; + } + + const id = window.setTimeout(() => { + setIsContentAccessible(true); + }, DEBOUNCE_MS); + + return () => window.clearTimeout(id); + }, [canShare]); + // Invalidate relevant queries to ensure fresh data on modal open useEffect(() => { [ @@ -197,6 +214,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { {t('Share the document')} @@ -206,6 +224,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { > Date: Wed, 15 Jul 2026 11:37:48 +0200 Subject: [PATCH 2/4] =?UTF-8?q?fixup!=20=E2=99=BF=EF=B8=8F(frontend)=20lim?= =?UTF-8?q?it=20share=20modal=20opening=20announcement=20for=20screen=20re?= =?UTF-8?q?aders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/docs/doc-share/components/DocShareModal.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx index 7288872d49..d9d5c372b3 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx @@ -168,9 +168,12 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { // accessibility tree during the opening announcement, then restore it. useEffect(() => { if (canShare) { + setIsContentAccessible(true); return; } + setIsContentAccessible(false); + const id = window.setTimeout(() => { setIsContentAccessible(true); }, DEBOUNCE_MS); From d40b39570c83c30cfcefa7908710119e66e91c12 Mon Sep 17 00:00:00 2001 From: Cyril Date: Wed, 15 Jul 2026 11:39:15 +0200 Subject: [PATCH 3/4] =?UTF-8?q?fixup!=20=E2=99=BF=EF=B8=8F(frontend)=20lim?= =?UTF-8?q?it=20share=20modal=20opening=20announcement=20for=20screen=20re?= =?UTF-8?q?aders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/docs/doc-share/components/DocShareModal.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx index d9d5c372b3..6c2a8f556c 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx @@ -40,7 +40,8 @@ import { import { QuickSearchGroupMember } from './DocShareMember'; import { DocShareModalFooter } from './DocShareModalFooter'; -const DEBOUNCE_MS = 300; +const SEARCH_QUERY_DEBOUNCE_MS = 300; +const ACCESSIBILITY_REEXPOSURE_DELAY_MS = 300; const ShareModalStyle = createGlobalStyle` .--docs--doc-share-modal [cmdk-item] { @@ -122,7 +123,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { const onFilter = useDebouncedCallback((str: string) => { setUserQuery(str); - }, DEBOUNCE_MS); + }, SEARCH_QUERY_DEBOUNCE_MS); const onRemoveUser = (row: User) => { setSelectedUsers((prevState) => { @@ -176,7 +177,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { const id = window.setTimeout(() => { setIsContentAccessible(true); - }, DEBOUNCE_MS); + }, ACCESSIBILITY_REEXPOSURE_DELAY_MS); return () => window.clearTimeout(id); }, [canShare]); From ba0f2393114a607f768c7f50ab3121b909108e51 Mon Sep 17 00:00:00 2001 From: Cyril Date: Tue, 21 Jul 2026 13:51:04 +0200 Subject: [PATCH 4/4] =?UTF-8?q?fixup!=20=E2=99=BF=EF=B8=8F(frontend)=20lim?= =?UTF-8?q?it=20share=20modal=20opening=20announcement=20for=20screen=20re?= =?UTF-8?q?aders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/docs/doc-share/components/DocShareModal.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx index 6c2a8f556c..be866ad790 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx @@ -227,6 +227,9 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { hideCloseButton > + {/* aria-hidden is temporary (300ms) to prevent NVDA from reading + the entire modal body on open when the search input is absent. + autoFocus alone on the close button is not enough. */}