diff --git a/src/components/moderation/ContentHider.tsx b/src/components/moderation/ContentHider.tsx index 525be31ca..bb4588b74 100644 --- a/src/components/moderation/ContentHider.tsx +++ b/src/components/moderation/ContentHider.tsx @@ -159,6 +159,7 @@ function ContentHiderActive({ control.open() } }} + accessibilityState={{expanded: override}} label={desc.name} accessibilityHint={ modui.noOverride diff --git a/src/features/keyboardShortcuts/index.web.tsx b/src/features/keyboardShortcuts/index.web.tsx index 05699ef81..5e17264ad 100644 --- a/src/features/keyboardShortcuts/index.web.tsx +++ b/src/features/keyboardShortcuts/index.web.tsx @@ -292,6 +292,7 @@ export function KeyboardShortcuts() { if ( action === 'media' && (selected.dataset.keyboardNavigationPost || + selected.dataset.keyboardNavigationWarning || selected.dataset.keyboardNavigationScope === 'messages') ) { const targets = getPostMediaTargets(selected) diff --git a/src/features/keyboardShortcuts/postNavigation.web.test.ts b/src/features/keyboardShortcuts/postNavigation.web.test.ts index 486906dba..7794fd89e 100644 --- a/src/features/keyboardShortcuts/postNavigation.web.test.ts +++ b/src/features/keyboardShortcuts/postNavigation.web.test.ts @@ -616,13 +616,21 @@ it('reveals an embed warning before opening the quoted post', () => {
+
` - const container = post.querySelector('[data-keyboard-navigation-embed-target]')! + const container = post.querySelector( + '[data-keyboard-navigation-embed-target]', + )! const warning = container.querySelector('button')! + const onDetails = jest.fn() + container + .querySelector('[data-testid="warningDetails"]')! + .addEventListener('click', onDetails) const onOpen = jest.fn() warning.addEventListener('click', () => { + warning.setAttribute('aria-expanded', 'true') const quote = document.createElement('button') quote.dataset.testid = 'quotedPostOpenBtn' quote.addEventListener('click', onOpen) @@ -633,4 +641,80 @@ it('reveals an embed warning before opening the quoted post', () => { expect(onOpen).not.toHaveBeenCalled() expect(openPostMediaTarget(getPostMediaTargets(post)[0])).toBe(true) expect(onOpen).toHaveBeenCalledTimes(1) + expect(onDetails).not.toHaveBeenCalled() +}) + +it.each(['quotedPostOpenBtn', 'postMediaOpenBtn', 'postEmbedOpenBtn'])( + 'opens %s before an unrelated post warning', + testID => { + document.body.replaceChildren() + const post = createPost({top: 20}) + post.innerHTML = ` + +
+ +
+ ` + const warning = post.querySelector( + '[data-keyboard-navigation-warning]', + )! + const attachment = post.querySelector( + `[data-testid="${testID}"]`, + )! + const onOpen = jest.fn() + const onWarning = jest.fn() + attachment.addEventListener('click', onOpen) + warning.addEventListener('click', onWarning) + + expect(getPostMediaTargets(post).map(target => target.control)).toEqual([ + attachment, + ]) + expect(clickPostAction(post, 'media')).toBe(true) + expect(onOpen).toHaveBeenCalledTimes(1) + expect(onWarning).not.toHaveBeenCalled() + + attachment.remove() + expect(clickPostAction(post, 'media')).toBe(true) + expect(onWarning).toHaveBeenCalledTimes(1) + }, +) + +it('opens standalone warnings, including warnings that only show details', () => { + const warning = document.createElement('button') + warning.dataset.keyboardNavigationWarning = 'true' + document.body.appendChild(warning) + const onOpen = jest.fn() + warning.addEventListener('click', onOpen) + + expect(clickPostAction(warning, 'media')).toBe(true) + expect(onOpen).toHaveBeenCalledTimes(1) + warning.disabled = true + expect(clickPostAction(warning, 'media')).toBe(false) +}) + +it('ignores revealed and hidden warnings inside embeds', () => { + const post = createPost({top: 20}) + post.innerHTML = ` +
+ + +
+ ` + expect(getPostMediaTargets(post)).toEqual([]) +}) + +it('opens a revealed quote before reaching warnings inside that quote', () => { + const post = createPost({top: 20}) + post.innerHTML = ` +
+
+ +
+
+ ` + expect(getPostMediaTargets(post).map(target => target.control)).toEqual([ + post.querySelector('[data-testid="quotedPostOpenBtn"]'), + ]) }) diff --git a/src/features/keyboardShortcuts/postNavigation.web.ts b/src/features/keyboardShortcuts/postNavigation.web.ts index 9278b3778..f2ea062e7 100644 --- a/src/features/keyboardShortcuts/postNavigation.web.ts +++ b/src/features/keyboardShortcuts/postNavigation.web.ts @@ -53,7 +53,9 @@ export function isPostVisible(element: HTMLElement) { } export function getNavigablePosts() { - const items = Array.from(document.querySelectorAll(POST_SELECTOR)) + const items = Array.from( + document.querySelectorAll(POST_SELECTOR), + ) .filter(isRendered) .filter( element => @@ -173,9 +175,10 @@ export function getPostMediaTargets(element: HTMLElement): PostMediaTarget[] { const selectors = [ '[data-testid="quotedPostOpenBtn"]', '[data-testid="postMediaOpenBtn"]', - '.wsky-post__media [role="button"]', '[data-keyboard-navigation-embed] iframe', '[data-testid="postEmbedOpenBtn"]', + '[data-keyboard-navigation-warning]:not([aria-expanded="true"])', + '.wsky-post__media [role="button"]', '[data-keyboard-navigation-embed] a[href], [data-keyboard-navigation-embed] [role="link"], [data-keyboard-navigation-embed] [role="button"]', ] for (const container of containers) { @@ -185,6 +188,9 @@ export function getPostMediaTargets(element: HTMLElement): PostMediaTarget[] { ).find( candidate => isControlAvailable(candidate) && + !candidate.closest('[aria-hidden="true"], [inert]') && + (!candidate.hasAttribute('data-keyboard-navigation-warning') || + candidate.getAttribute('aria-expanded') !== 'true') && (!boundaries.length || candidate.closest(EMBED_TARGET_SELECTOR) === container), ) @@ -205,7 +211,21 @@ export function getPostMediaTargets(element: HTMLElement): PostMediaTarget[] { break } } - return targets + if (targets.length) return targets + + const warnings = [ + ...(element.matches('[data-keyboard-navigation-warning]') ? [element] : []), + ...element.querySelectorAll( + '[data-keyboard-navigation-warning]', + ), + ].filter( + control => + control.getAttribute('aria-expanded') !== 'true' && + !control.closest('[aria-hidden="true"], [inert]') && + !control.closest('[data-testid="quotedPostOpenBtn"]') && + isControlAvailable(control), + ) + return warnings.map(control => ({kind: 'embed', control})) } function isControlAvailable(control: HTMLElement) {