-
Notifications
You must be signed in to change notification settings - Fork 14
Add support for inert attribute
#1964
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
rcj-siteimprove
wants to merge
12
commits into
main
Choose a base branch
from
inert
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
3f30006
Add `isInert` method to `Element`
rcj-siteimprove 7b0dd46
Update `isInert` predicate to include call to `Element#isInert`
rcj-siteimprove 76d23d2
Handle inert attribute when building accessibility tree
rcj-siteimprove 04789b6
Exclude inert elements from `isSuggestedFocusable`
rcj-siteimprove f3fb8b4
Include inert elements in `isProgrammaticallyHidden`
rcj-siteimprove 6c7590f
Remove generic role from inert containers
rcj-siteimprove 4cf1a2d
Add tests showing that R17 handles the inert attribute
rcj-siteimprove 0c573ec
Create neat-ads-happen.md
rcj-siteimprove ae361ff
Extract API
github-actions[bot] 75255ec
Merge branch 'main' into inert
rcj-siteimprove 4d67b3d
Merge branch 'main' into inert
rcj-siteimprove adfaea1
Update packages/alfa-dom/src/node/element.ts
rcj-siteimprove File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| --- | ||
| "@siteimprove/alfa-aria": patch | ||
| "@siteimprove/alfa-dom": patch | ||
| "@siteimprove/alfa-rules": patch | ||
| "@siteimprove/alfa-style": patch | ||
| --- | ||
|
|
||
| **Added:** The `inert` attribute is now supported. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
177 changes: 177 additions & 0 deletions
177
packages/alfa-aria/test/dom/predicate/is-programmatically-hidden.spec.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| import { h } from "@siteimprove/alfa-dom/h"; | ||
| import { test } from "@siteimprove/alfa-test"; | ||
|
|
||
| import { Device } from "@siteimprove/alfa-device"; | ||
|
|
||
| import { DOM } from "../../../dist/index.js"; | ||
|
|
||
| const device = Device.standard(); | ||
| const isProgrammaticallyHidden = DOM.isProgrammaticallyHidden(device); | ||
|
|
||
| test("isProgrammaticallyHidden() returns false for visible elements", (t) => { | ||
| const button = <button>Click me</button>; | ||
| h.document([button]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), false); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for elements with display: none", (t) => { | ||
| const button = <button style={{ display: "none" }}>Hidden</button>; | ||
| h.document([button]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note (non-blocking): |
||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for elements with visibility: hidden", (t) => { | ||
| const button = <button style={{ visibility: "hidden" }}>Hidden</button>; | ||
| h.document([button]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for elements with visibility: collapse", (t) => { | ||
| const button = <button style={{ visibility: "collapse" }}>Hidden</button>; | ||
| h.document([button]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns false for elements with visibility: visible", (t) => { | ||
| const button = <button style={{ visibility: "visible" }}>Visible</button>; | ||
| h.document([button]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), false); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for elements with aria-hidden='true'", (t) => { | ||
| const button = <button aria-hidden="true">Hidden</button>; | ||
| h.document([button]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns false for elements with aria-hidden='false'", (t) => { | ||
| const button = <button aria-hidden="false">Visible</button>; | ||
| h.document([button]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), false); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for inert elements", (t) => { | ||
| const button = <button inert>Inert</button>; | ||
| h.document([button]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for elements inside inert container", (t) => { | ||
| const button = <button>Not inert itself</button>; | ||
| const parent = <div inert>{button}</div>; | ||
| h.document([parent]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for elements with display: none ancestor", (t) => { | ||
| const button = <button>Hidden by ancestor</button>; | ||
| const parent = <div style={{ display: "none" }}>{button}</div>; | ||
| h.document([parent]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for elements with aria-hidden='true' ancestor", (t) => { | ||
| const button = <button>Hidden by ancestor</button>; | ||
| const parent = <div aria-hidden="true">{button}</div>; | ||
| h.document([parent]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for elements with visibility: hidden ancestor", (t) => { | ||
| const button = <button>Hidden by ancestor</button>; | ||
| const parent = <div style={{ visibility: "hidden" }}>{button}</div>; | ||
| h.document([parent]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for elements with visibility: collapse ancestor", (t) => { | ||
| const button = <button>Hidden by ancestor</button>; | ||
| const parent = <div style={{ visibility: "collapse" }}>{button}</div>; | ||
| h.document([parent]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for deeply nested hidden elements", (t) => { | ||
| const button = <button>Deeply hidden</button>; | ||
| const level2 = <div>{button}</div>; | ||
| const level1 = <div aria-hidden="true">{level2}</div>; | ||
| h.document([level1]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns false for elements inside open dialog within inert container", (t) => { | ||
| const button = <button>Should be visible</button>; | ||
| const dialog = <dialog open>{button}</dialog>; | ||
| const parent = <div inert>{dialog}</div>; | ||
| h.document([parent]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), false); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for elements with multiple hiding conditions", (t) => { | ||
| const button = ( | ||
| <button aria-hidden="true" style={{ display: "none" }}> | ||
| Multiple conditions | ||
| </button> | ||
| ); | ||
| h.document([button]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns false for visible elements with visible ancestors", (t) => { | ||
| const button = <button>Visible</button>; | ||
| const parent = <div>{button}</div>; | ||
| const grandparent = <section>{parent}</section>; | ||
| h.document([grandparent]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), false); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns true for elements with display: none even if aria-hidden='false'", (t) => { | ||
| const button = ( | ||
| <button aria-hidden="false" style={{ display: "none" }}> | ||
| Hidden | ||
| </button> | ||
| ); | ||
| h.document([button]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), true); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() returns false for elements with display: block", (t) => { | ||
| const button = <button style={{ display: "block" }}>Visible</button>; | ||
| h.document([button]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(button), false); | ||
| }); | ||
|
|
||
| test("isProgrammaticallyHidden() handles mixed visibility in hierarchy", (t) => { | ||
| const visibleButton = <button>Visible</button>; | ||
| const hiddenButton = <button>Hidden</button>; | ||
| const hiddenDiv = <div aria-hidden="true">{hiddenButton}</div>; | ||
| const parent = ( | ||
| <div> | ||
| {visibleButton} | ||
| {hiddenDiv} | ||
| </div> | ||
| ); | ||
| h.document([parent]); | ||
|
|
||
| t.equal(isProgrammaticallyHidden(visibleButton), false); | ||
| t.equal(isProgrammaticallyHidden(hiddenButton), true); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue: Won't that break for deeply nested dialogs?
The outer
<div>is aContainerdue to itsinertattribute. The inner<div>is aInertdue to the state being set toinertby the parent, this means we never reach thedialog🤔I agree that we should set
inertelements asInertwhen possible, since we work on static snapshot, this will reduce the accessibility tree size and probably make some request faster.Given that we do now have a
Element#isInertpredicate (and this seems to redo part of it), how about something like:inertis set, and no descendant isan open dialognot inert =>Inert;inertis set and some descendant is not inert =>Container;State.inert, then we have aninertContainerancestor:open dialognot inert =>Elementand resetState.inert;InertContainer.This probably requires some caching of "no descendant is not inert". Or maybe we can start by gathering all open dialogs (should be at most 1, iirc) and the set of their ancestors 🤔