Skip to content

Commit 84bf6c9

Browse files
mcreinhardcopybara-github
authored andcommitted
feat: support aria-label attribute on Place Field Link
PiperOrigin-RevId: 577327511
1 parent b69d8bd commit 84bf6c9

File tree

7 files changed

+90
-7
lines changed

7 files changed

+90
-7
lines changed

src/icon_button/icon_button.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,13 @@ export class IconButton extends BaseComponent {
242242
`;
243243
}
244244

245+
protected override updated() {
246+
// If the aria-label attribute is set, hide it from the a11y tree. Otherwise
247+
// the component and its shadow DOM content show up as duplicate nodes with
248+
// the same aria-label.
249+
this.role = this.ariaLabel != null ? 'none' : null;
250+
}
251+
245252
private renderContent() {
246253
const icon = this.icon ||
247254
(!this.hasLabel || this.condensed ? DEFAULT_ICON : undefined);

src/icon_button/icon_button_test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,13 @@ describe('IconButton', () => {
104104
expect(el.variant).toBe('outlined');
105105
expect(el.getAttribute('variant')).toBe('outlined');
106106
});
107+
108+
it('sets role="none" on the host when aria-label is present', async () => {
109+
const el = await prepareState(html`
110+
<gmpx-icon-button aria-label="enter">
111+
</gmpx-icon-button>
112+
`);
113+
114+
expect(el.role).toBe('none');
115+
});
107116
});

src/place_building_blocks/place_directions_button/place_directions_button.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ export class PlaceDirectionsButton extends PlaceDataConsumer {
123123
`;
124124
}
125125

126+
protected override updated() {
127+
// If the aria-label attribute is set, hide it from the a11y tree. Otherwise
128+
// the component and its shadow DOM content show up as duplicate nodes with
129+
// the same aria-label.
130+
this.role = this.ariaLabel != null ? 'none' : null;
131+
}
132+
126133
/** @ignore */
127134
getRequiredFields(): Array<keyof Place> {
128135
return ['displayName', 'formattedAddress', 'location'];

src/place_building_blocks/place_directions_button/place_directions_button_test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,16 @@ describe('PlaceDirectionsButton', () => {
165165
expectedQuery}`);
166166
});
167167
});
168+
169+
it('sets role="none" on the host when aria-label is present', async () => {
170+
const root = env.render(html`
171+
<gmpx-place-directions-button aria-label="Get Directions">
172+
</gmpx-place-directions-button>
173+
`);
174+
await env.waitForStability();
175+
const el = root.querySelector<PlaceDirectionsButton>(
176+
'gmpx-place-directions-button')!;
177+
178+
expect(el.role).toBe('none');
179+
});
168180
});

src/place_building_blocks/place_field_link/README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ import { PlaceFieldLink } from '@googlemaps/extended-component-library/place_bui
2626

2727
## Attributes and properties
2828

29-
| Attribute | Property | Property type | Description | Default | [Reflects?](https://open-wc.org/guides/knowledge/attributes-and-properties/#attribute-and-property-reflection) |
30-
| ------------ | ----------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------- |
31-
| `href-field` | `hrefField` | `LinkField` | The field to link to, formatted as it is on either a `Place` or `PlaceResult`.<br/><br/>Allowed fields are: `googleMapsURI` or `url` for a link to this place on Google Maps; `websiteURI` or `website` for a link to this place's website. | `'websiteURI'` ||
32-
| | `place` | `Place\|PlaceResult\|null\|undefined` | Place data to render, overriding anything provided by context. | ||
33-
| `no-data` | `noData` | `boolean` | This read-only property and attribute indicate whether the component has the required Place data to display itself.<br/><br/>Use the attribute to target CSS rules if you wish to hide this component, or display alternate content, when there's no valid data. | `true` ||
29+
| Attribute | Property | Property type | Description | Default | [Reflects?](https://open-wc.org/guides/knowledge/attributes-and-properties/#attribute-and-property-reflection) |
30+
| ------------ | ----------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | -------------------------------------------------------------------------------------------------------------- |
31+
| `href-field` | `hrefField` | `LinkField` | The field to link to, formatted as it is on either a `Place` or `PlaceResult`.<br/><br/>Allowed fields are: `googleMapsURI` or `url` for a link to this place on Google Maps; `websiteURI` or `website` for a link to this place's website. | `'websiteURI'` ||
32+
| `aria-label` | `ariaLabel` | `string\|null` | The link description that gets read by assistive technology.<br/><br/>Set this to something more descriptive if the link's purpose isn't clear from its text content alone. For example, if the link text is just "Website", then the `aria-label` could be "Website for (business name)". | `null` ||
33+
| | `place` | `Place\|PlaceResult\|null\|undefined` | Place data to render, overriding anything provided by context. | ||
34+
| `no-data` | `noData` | `boolean` | This read-only property and attribute indicate whether the component has the required Place data to display itself.<br/><br/>Use the attribute to target CSS rules if you wish to hide this component, or display alternate content, when there's no valid data. | `true` ||
3435

3536
## Styling
3637

src/place_building_blocks/place_field_link/place_field_link.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import {css, html} from 'lit';
7+
import {css, html, nothing} from 'lit';
88
import {customElement, property} from 'lit/decorators.js';
99
import {when} from 'lit/directives/when.js';
1010

@@ -92,11 +92,22 @@ export class PlaceFieldLink extends PlaceDataConsumer {
9292
@property({type: String, reflect: true, attribute: 'href-field'})
9393
hrefField: LinkField = 'websiteURI';
9494

95+
/**
96+
* The link description that gets read by assistive technology.
97+
*
98+
* Set this to something more descriptive if the link's purpose isn't clear
99+
* from its text content alone. For example, if the link text is just
100+
* "Website", then the `aria-label` could be "Website for (business name)".
101+
*/
102+
@property({attribute: 'aria-label', reflect: true, type: String})
103+
override ariaLabel: string|null = null;
104+
95105
protected override render() {
96106
const href = this.getHref();
97107
// clang-format off
98108
return html`${when(href, () => html`
99-
<a target="_blank" rel="noopener noreferrer" href=${href!}>
109+
<a target="_blank" rel="noopener noreferrer" href=${href!}
110+
aria-label=${this.ariaLabel ?? nothing}>
100111
${when(this.hasContentForSlot(),
101112
() => html`<slot></slot>`,
102113
() => html`${this.getDefaultLinkText(href!)}`,
@@ -106,6 +117,13 @@ export class PlaceFieldLink extends PlaceDataConsumer {
106117
// clang-format on
107118
}
108119

120+
protected override updated() {
121+
// If the aria-label attribute is set, hide it from the a11y tree. Otherwise
122+
// the component and its shadow DOM content show up as duplicate nodes with
123+
// the same aria-label.
124+
this.role = this.ariaLabel != null ? 'none' : null;
125+
}
126+
109127
/** @ignore */
110128
getRequiredFields(): Array<keyof Place> {
111129
return [toPlaceLinkField(this.hrefField)];

src/place_building_blocks/place_field_link/place_field_link_test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,33 @@ describe('PlaceFieldLink', () => {
142142

143143
expect(getHref(el)).toBe('https://www.mywebsite.com/');
144144
});
145+
146+
it(`forwards aria-label to the anchor element`, async () => {
147+
const [el] = await prepareState(html`
148+
<gmpx-place-field-link .place=${fakePlace} aria-label="My Label">
149+
</gmpx-place-field-link>
150+
`);
151+
152+
const anchor = el.renderRoot.querySelector('a')!;
153+
expect(anchor.getAttribute('aria-label')).toBe('My label');
154+
});
155+
156+
it(`omits aria-label from the anchor element when not set`, async () => {
157+
const [el] = await prepareState(html`
158+
<gmpx-place-field-link .place=${fakePlace}>
159+
</gmpx-place-field-link>
160+
`);
161+
162+
const anchor = el.renderRoot.querySelector('a')!;
163+
expect(anchor.hasAttribute('aria-label')).toBeFalse();
164+
});
165+
166+
it('sets role="none" on the host when aria-label is present', async () => {
167+
const [el] = await prepareState(html`
168+
<gmpx-place-field-link .place=${fakePlace} aria-label="My Label">
169+
</gmpx-place-field-link>
170+
`);
171+
172+
expect(el.role).toBe('none');
173+
});
145174
});

0 commit comments

Comments
 (0)