Skip to content

Commit

Permalink
fix: determine accname for elements with slots (#442)
Browse files Browse the repository at this point in the history
  • Loading branch information
geoffrich committed Oct 5, 2020
1 parent d9c62b3 commit 3866289
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 5 deletions.
30 changes: 30 additions & 0 deletions .changeset/plenty-crabs-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
"dom-accessibility-api": patch
---

Correctly determine accessible name when element contains a slot.

Previously, computing the accessible name would only examine child nodes. However, content placed in a slot is is an assigned node, not a child node.

If you have a custom element `custom-button` with a slot:

```html
<button><slot></slot></button>

<!-- accname of inner <button> is 'Custom name' (previously '') -->
<custom-button>Custom name</custom-button>
```

If you have a custom element `custom-button-default` with default content in the slot:

```html
<button><slot>Default name</slot></button>

<!-- accname of inner <button> is 'Custom name' (previously 'Default name') -->
<custom-button-default>Custom name</custom-button-default>

<!-- accname of inner <button> is 'Default name' (previously 'Default name') -->
<custom-button-default></custom-button-default>
```

This is not currently defined in the accname spec but reflects current browser behavior.
55 changes: 55 additions & 0 deletions sources/__tests__/accessible-name.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ function testMarkup(markup, accessibleName) {
expect(testNode).toHaveAccessibleName(accessibleName);
}

function testShadowDomMarkup(markup, accessibleName) {
const container = renderIntoDocument(markup);

const testNode = container
.querySelector("[data-root]")
.shadowRoot.querySelector("[data-test]");
expect(testNode).toHaveAccessibleName(accessibleName);
}

afterEach(cleanup);

describe("to upstream", () => {
Expand Down Expand Up @@ -191,6 +200,52 @@ describe("to upstream", () => {
});
});

describe("slots", () => {
beforeAll(() => {
customElements.define(
"custom-button",
class extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `<button data-test><slot></slot></button>`;
}
}
);

customElements.define(
"custom-button-with-default",
class extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `<button data-test><slot>Default name</slot></button>`;
}
}
);
});

test.each([
[
"no default content",
`<custom-button data-root>Custom name</custom-button>`,
"Custom name",
],
[
"default content",
`<custom-button-with-default data-root></custom-button>`,
"Default name",
],
[
"overridden default content",
`<custom-button-with-default data-root>Custom name</custom-button>`,
"Custom name",
],
])("slot with %s has name", (_, markup, expectedAccessibleName) =>
testShadowDomMarkup(markup, expectedAccessibleName)
);
});

// misc tests
test.each([
[
Expand Down
27 changes: 22 additions & 5 deletions sources/accessible-name-and-description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
isHTMLFieldSetElement,
isHTMLLegendElement,
isHTMLTableElement,
isHTMLSlotElement,
isSVGSVGElement,
isSVGTitleElement,
queryIdRefs,
Expand Down Expand Up @@ -306,11 +307,27 @@ function getLabels(element: Element): HTMLLabelElement[] | null {
});
}

/**
* Gets the contents of a slot used for computing the accname
* @param slot
*/
function getSlotContents(slot: HTMLSlotElement): Node[] {
// Computing the accessible name for elements containing slots is not
// currently defined in the spec. This implementation reflects the
// behavior of NVDA 2020.2/Firefox 81 and iOS VoiceOver/Safari 13.6.
const assignedNodes = slot.assignedNodes();
if (assignedNodes.length === 0) {
// if no nodes are assigned to the slot, it displays the default content
return ArrayFrom(slot.childNodes);
}
return assignedNodes;
}

/**
* implements https://w3c.github.io/accname/#mapping_additional_nd_te
* @param root
* @param [options]
* @parma [options.getComputedStyle] - mock window.getComputedStyle. Needs `content`, `display` and `visibility`
* @param [options.getComputedStyle] - mock window.getComputedStyle. Needs `content`, `display` and `visibility`
*/
export function computeTextAlternative(
root: Element,
Expand Down Expand Up @@ -342,11 +359,11 @@ export function computeTextAlternative(
accumulatedText = `${beforeContent} ${accumulatedText}`;
}

// FIXME: This is not defined in the spec
// FIXME: Including aria-owns is not defined in the spec
// But it is required in the web-platform-test
const childNodes = ArrayFrom(node.childNodes).concat(
queryIdRefs(node, "aria-owns")
);
const childNodes = isHTMLSlotElement(node)
? getSlotContents(node)
: ArrayFrom(node.childNodes).concat(queryIdRefs(node, "aria-owns"));
childNodes.forEach((child) => {
const result = computeTextAlternative(child, {
isEmbeddedInLabel: context.isEmbeddedInLabel,
Expand Down
4 changes: 4 additions & 0 deletions sources/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export function isHTMLLegendElement(
return isElement(node) && getLocalName(node) === "legend";
}

export function isHTMLSlotElement(node: Node | null): node is HTMLSlotElement {
return isElement(node) && getLocalName(node) === "slot";
}

export function isSVGElement(node: Node | null): node is SVGElement {
return isElement(node) && (node as SVGElement).ownerSVGElement !== undefined;
}
Expand Down

0 comments on commit 3866289

Please sign in to comment.