Skip to content

Commit ffb056d

Browse files
authoredApr 27, 2022
fix(core): inherit aria attributes on host elements (#25156) (#25169)
1 parent 2d97249 commit ffb056d

File tree

10 files changed

+142
-28
lines changed

10 files changed

+142
-28
lines changed
 

‎core/src/components/back-button/back-button.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { Component, ComponentInterface, Element, Host, Prop, h } from '@stencil/
22

33
import { config } from '../../global/config';
44
import { getIonMode } from '../../global/ionic-global';
5-
import { AnimationBuilder, Color } from '../../interface';
6-
import { ButtonInterface } from '../../utils/element-interface';
7-
import { inheritAttributes } from '../../utils/helpers';
5+
import type { AnimationBuilder, Color } from '../../interface';
6+
import type { ButtonInterface } from '../../utils/element-interface';
7+
import { inheritAriaAttributes } from '../../utils/helpers';
88
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
99

1010
/**
@@ -67,7 +67,7 @@ export class BackButton implements ComponentInterface, ButtonInterface {
6767
@Prop() routerAnimation: AnimationBuilder | undefined;
6868

6969
componentWillLoad() {
70-
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
70+
this.inheritedAttributes = inheritAriaAttributes(this.el);
7171

7272
if (this.defaultHref === undefined) {
7373
this.defaultHref = config.get('backButtonDefaultHref');

‎core/src/components/button/button.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, h } from '@stencil/core';
22

33
import { getIonMode } from '../../global/ionic-global';
4-
import { AnimationBuilder, Color, RouterDirection } from '../../interface';
5-
import { AnchorInterface, ButtonInterface } from '../../utils/element-interface';
6-
import { hasShadowDom, inheritAttributes } from '../../utils/helpers';
4+
import type { AnimationBuilder, Color, RouterDirection } from '../../interface';
5+
import type { AnchorInterface, ButtonInterface } from '../../utils/element-interface';
6+
import { hasShadowDom, inheritAriaAttributes } from '../../utils/helpers';
77
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
88

99
/**
@@ -135,7 +135,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
135135
this.inToolbar = !!this.el.closest('ion-buttons');
136136
this.inListHeader = !!this.el.closest('ion-list-header');
137137
this.inItem = !!this.el.closest('ion-item') || !!this.el.closest('ion-item-divider');
138-
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
138+
this.inheritedAttributes = inheritAriaAttributes(this.el);
139139
}
140140

141141
private get hasIconOnly() {

‎core/src/components/header/header.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Component, ComponentInterface, Element, Host, Prop, h, writeTask } from '@stencil/core';
22

33
import { getIonMode } from '../../global/ionic-global';
4-
import { inheritAttributes } from '../../utils/helpers';
4+
import { inheritAriaAttributes } from '../../utils/helpers';
55
import { hostContext } from '../../utils/theme';
66

77
import { cloneElement, createHeaderIndex, handleContentScroll, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity } from './header.utils';
@@ -46,7 +46,7 @@ export class Header implements ComponentInterface {
4646
@Prop() translucent = false;
4747

4848
componentWillLoad() {
49-
this.inheritedAttributes = inheritAttributes(this.el, ['role']);
49+
this.inheritedAttributes = inheritAriaAttributes(this.el);
5050
}
5151

5252
async componentDidLoad() {

‎core/src/components/input/input.tsx

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h } from '@stencil/core';
22

33
import { getIonMode } from '../../global/ionic-global';
4-
import { AutocompleteTypes, Color, InputChangeEventDetail, StyleEventDetail, TextFieldTypes } from '../../interface';
5-
import { debounceEvent, findItemLabel, inheritAttributes } from '../../utils/helpers';
4+
import type {
5+
AutocompleteTypes,
6+
Color,
7+
InputChangeEventDetail,
8+
StyleEventDetail,
9+
TextFieldTypes,
10+
} from '../../interface';
11+
import { debounceEvent, findItemLabel, inheritAriaAttributes, inheritAttributes } from '../../utils/helpers';
612
import { createColorClasses } from '../../utils/theme';
713

814
/**
@@ -234,7 +240,10 @@ export class Input implements ComponentInterface {
234240
}
235241

236242
componentWillLoad() {
237-
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label', 'tabindex', 'title']);
243+
this.inheritedAttributes = {
244+
...inheritAriaAttributes(this.el),
245+
...inheritAttributes(this.el, ['tabindex', 'title']),
246+
};
238247
}
239248

240249
connectedCallback() {

‎core/src/components/menu-button/menu-button.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { Component, ComponentInterface, Element, Host, Listen, Prop, State, h }
22

33
import { config } from '../../global/config';
44
import { getIonMode } from '../../global/ionic-global';
5-
import { Color } from '../../interface';
6-
import { ButtonInterface } from '../../utils/element-interface';
7-
import { inheritAttributes } from '../../utils/helpers';
5+
import type { Color } from '../../interface';
6+
import type { ButtonInterface } from '../../utils/element-interface';
7+
import { inheritAriaAttributes } from '../../utils/helpers';
88
import { menuController } from '../../utils/menu-controller';
99
import { createColorClasses, hostContext } from '../../utils/theme';
1010
import { updateVisibility } from '../menu-toggle/menu-toggle-util';
@@ -58,7 +58,7 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
5858
@Prop() type: 'submit' | 'reset' | 'button' = 'button';
5959

6060
componentWillLoad() {
61-
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
61+
this.inheritedAttributes = inheritAriaAttributes(this.el);
6262
}
6363

6464
componentDidLoad() {

‎core/src/components/menu/menu.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getIonMode } from '../../global/ionic-global';
55
import { Animation, Gesture, GestureDetail, MenuChangeEventDetail, MenuI, Side } from '../../interface';
66
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
77
import { GESTURE_CONTROLLER } from '../../utils/gesture';
8-
import { assert, clamp, inheritAttributes, isEndSide as isEnd } from '../../utils/helpers';
8+
import { assert, clamp, inheritAriaAttributes, isEndSide as isEnd } from '../../utils/helpers';
99
import { menuController } from '../../utils/menu-controller';
1010

1111
const iosEasing = 'cubic-bezier(0.32,0.72,0,1)';
@@ -213,7 +213,7 @@ AFTER:
213213
}
214214

215215
componentWillLoad() {
216-
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
216+
this.inheritedAttributes = inheritAriaAttributes(this.el);
217217
}
218218

219219
async componentDidLoad() {

‎core/src/components/range/range.tsx

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, State, Watch, h } from '@stencil/core';
22

33
import { getIonMode } from '../../global/ionic-global';
4-
import { Color, Gesture, GestureDetail, KnobName, RangeChangeEventDetail, RangeValue, StyleEventDetail } from '../../interface';
5-
import { clamp, debounceEvent, getAriaLabel, inheritAttributes, renderHiddenInput } from '../../utils/helpers';
4+
import type {
5+
Color,
6+
Gesture,
7+
GestureDetail,
8+
KnobName,
9+
RangeChangeEventDetail,
10+
RangeValue,
11+
StyleEventDetail,
12+
} from '../../interface';
13+
import { clamp, debounceEvent, getAriaLabel, inheritAriaAttributes, renderHiddenInput } from '../../utils/helpers';
614
import { createColorClasses, hostContext } from '../../utils/theme';
715

816
/**
@@ -205,7 +213,7 @@ export class Range implements ComponentInterface {
205213
*/
206214
this.rangeId = (this.el.hasAttribute('id')) ? this.el.getAttribute('id')! : `ion-r-${rangeIds++}`;
207215

208-
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
216+
this.inheritedAttributes = inheritAriaAttributes(this.el);
209217
}
210218

211219
componentDidLoad() {

‎core/src/components/textarea/textarea.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h, readTask } from '@stencil/core';
22

33
import { getIonMode } from '../../global/ionic-global';
4-
import { Color, StyleEventDetail, TextareaChangeEventDetail } from '../../interface';
5-
import { debounceEvent, findItemLabel, inheritAttributes, raf } from '../../utils/helpers';
4+
import type { Color, StyleEventDetail, TextareaChangeEventDetail } from '../../interface';
5+
import { debounceEvent, findItemLabel, inheritAriaAttributes, inheritAttributes, raf } from '../../utils/helpers';
66
import { createColorClasses } from '../../utils/theme';
77

88
/**
@@ -214,7 +214,10 @@ export class Textarea implements ComponentInterface {
214214
}
215215

216216
componentWillLoad() {
217-
this.inheritedAttributes = inheritAttributes(this.el, ['title']);
217+
this.inheritedAttributes = {
218+
...inheritAriaAttributes(this.el),
219+
...inheritAttributes(this.el, ['title']),
220+
};
218221
}
219222

220223
componentDidLoad() {

‎core/src/utils/helpers.ts

+70-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,74 @@ export const inheritAttributes = (el: HTMLElement, attributes: string[] = []) =>
5151
return attributeObject;
5252
}
5353

54+
/**
55+
* List of available ARIA attributes + `role`.
56+
* Removed deprecated attributes.
57+
* https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes
58+
*/
59+
const ariaAttributes = [
60+
'role',
61+
'aria-activedescendant',
62+
'aria-atomic',
63+
'aria-autocomplete',
64+
'aria-braillelabel',
65+
'aria-brailleroledescription',
66+
'aria-busy',
67+
'aria-checked',
68+
'aria-colcount',
69+
'aria-colindex',
70+
'aria-colindextext',
71+
'aria-colspan',
72+
'aria-controls',
73+
'aria-current',
74+
'aria-describedby',
75+
'aria-description',
76+
'aria-details',
77+
'aria-disabled',
78+
'aria-errormessage',
79+
'aria-expanded',
80+
'aria-flowto',
81+
'aria-haspopup',
82+
'aria-hidden',
83+
'aria-invalid',
84+
'aria-keyshortcuts',
85+
'aria-label',
86+
'aria-labelledby',
87+
'aria-level',
88+
'aria-live',
89+
'aria-multiline',
90+
'aria-multiselectable',
91+
'aria-orientation',
92+
'aria-owns',
93+
'aria-placeholder',
94+
'aria-posinset',
95+
'aria-pressed',
96+
'aria-readonly',
97+
'aria-relevant',
98+
'aria-required',
99+
'aria-roledescription',
100+
'aria-rowcount',
101+
'aria-rowindex',
102+
'aria-rowindextext',
103+
'aria-rowspan',
104+
'aria-selected',
105+
'aria-setsize',
106+
'aria-sort',
107+
'aria-valuemax',
108+
'aria-valuemin',
109+
'aria-valuenow',
110+
'aria-valuetext',
111+
];
112+
113+
/**
114+
* Returns an array of aria attributes that should be copied from
115+
* the shadow host element to a target within the light DOM.
116+
* @param el The element that the attributes should be copied from.
117+
*/
118+
export const inheritAriaAttributes = (el: HTMLElement) => {
119+
return inheritAttributes(el, ariaAttributes);
120+
};
121+
54122
export const addEventListener = (el: any, eventName: string, callback: any, opts?: any) => {
55123
if (typeof (window as any) !== 'undefined') {
56124
const win = window as any;
@@ -164,8 +232,8 @@ export const getAriaLabel = (componentEl: HTMLElement, inputId: string): { label
164232
labelText = label.textContent;
165233
label.setAttribute('aria-hidden', 'true');
166234

167-
// if there is no label, check to see if the user has provided
168-
// one by setting an id on the component and using the label element
235+
// if there is no label, check to see if the user has provided
236+
// one by setting an id on the component and using the label element
169237
} else if (componentId.trim() !== '') {
170238
label = document.querySelector(`label[for="${componentId}"]`);
171239

‎core/src/utils/test/attributes.spec.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { inheritAttributes } from '../helpers';
1+
import { inheritAttributes, inheritAriaAttributes } from '../helpers';
22

33
describe('inheritAttributes()', () => {
44
it('should create an attribute inheritance object', () => {
@@ -37,3 +37,29 @@ describe('inheritAttributes()', () => {
3737
});
3838
});
3939
});
40+
41+
describe('inheritAriaAttributes()', () => {
42+
it('should inherit ARIA attributes defined on the HTML element', () => {
43+
const el = document.createElement('div');
44+
el.setAttribute('aria-label', 'myLabel');
45+
el.setAttribute('aria-describedby', 'myDescription');
46+
47+
const attributeObject = inheritAriaAttributes(el);
48+
49+
expect(attributeObject).toEqual({
50+
'aria-label': 'myLabel',
51+
'aria-describedby': 'myDescription',
52+
});
53+
});
54+
55+
it('should inherit the role attribute defined on the HTML element', () => {
56+
const el = document.createElement('div');
57+
el.setAttribute('role', 'button');
58+
59+
const attributeObject = inheritAriaAttributes(el);
60+
61+
expect(attributeObject).toEqual({
62+
role: 'button',
63+
});
64+
});
65+
});

0 commit comments

Comments
 (0)
Please sign in to comment.