fix(core): inherit aria attributes on host elements (#25156)

Resolves #20127
This commit is contained in:
Sean Perkins
2022-04-21 10:50:56 -04:00
committed by GitHub
parent c6afacbb7d
commit 611832b0d5
11 changed files with 119 additions and 19 deletions

View File

@ -7,7 +7,7 @@ import { getIonMode } from '../../global/ionic-global';
import type { AnimationBuilder, Color } from '../../interface';
import type { ButtonInterface } from '../../utils/element-interface';
import type { Attributes } from '../../utils/helpers';
import { inheritAttributes } from '../../utils/helpers';
import { inheritAriaAttributes } from '../../utils/helpers';
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
/**
@ -70,7 +70,7 @@ export class BackButton implements ComponentInterface, ButtonInterface {
@Prop() routerAnimation: AnimationBuilder | undefined;
componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
this.inheritedAttributes = inheritAriaAttributes(this.el);
if (this.defaultHref === undefined) {
this.defaultHref = config.get('backButtonDefaultHref');

View File

@ -5,7 +5,7 @@ import { chevronForwardOutline, ellipsisHorizontal } from 'ionicons/icons';
import { getIonMode } from '../../global/ionic-global';
import type { AnimationBuilder, BreadcrumbCollapsedClickEventDetail, Color, RouterDirection } from '../../interface';
import type { Attributes } from '../../utils/helpers';
import { inheritAttributes } from '../../utils/helpers';
import { inheritAriaAttributes } from '../../utils/helpers';
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
/**
@ -124,7 +124,7 @@ export class Breadcrumb implements ComponentInterface {
@Event() collapsedClick!: EventEmitter<BreadcrumbCollapsedClickEventDetail>;
componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
this.inheritedAttributes = inheritAriaAttributes(this.el);
}
private isClickable(): boolean {

View File

@ -5,7 +5,7 @@ import { getIonMode } from '../../global/ionic-global';
import type { AnimationBuilder, Color, RouterDirection } from '../../interface';
import type { AnchorInterface, ButtonInterface } from '../../utils/element-interface';
import type { Attributes } from '../../utils/helpers';
import { hasShadowDom, inheritAttributes } from '../../utils/helpers';
import { inheritAriaAttributes, hasShadowDom } from '../../utils/helpers';
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
/**
@ -137,7 +137,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
this.inToolbar = !!this.el.closest('ion-buttons');
this.inListHeader = !!this.el.closest('ion-list-header');
this.inItem = !!this.el.closest('ion-item') || !!this.el.closest('ion-item-divider');
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
this.inheritedAttributes = inheritAriaAttributes(this.el);
}
private get hasIconOnly() {

View File

@ -4,7 +4,7 @@ import { Component, Element, Host, Prop, h, writeTask } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { findIonContent, getScrollElement, printIonContentErrorMsg } from '../../utils/content';
import type { Attributes } from '../../utils/helpers';
import { inheritAttributes } from '../../utils/helpers';
import { inheritAriaAttributes } from '../../utils/helpers';
import { hostContext } from '../../utils/theme';
import {
@ -55,7 +55,7 @@ export class Header implements ComponentInterface {
@Prop() translucent = false;
componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['role']);
this.inheritedAttributes = inheritAriaAttributes(this.el);
}
componentDidLoad() {

View File

@ -10,7 +10,7 @@ import type {
TextFieldTypes,
} from '../../interface';
import type { Attributes } from '../../utils/helpers';
import { debounceEvent, findItemLabel, inheritAttributes } from '../../utils/helpers';
import { inheritAriaAttributes, debounceEvent, findItemLabel, inheritAttributes } from '../../utils/helpers';
import { createColorClasses } from '../../utils/theme';
/**
@ -257,7 +257,10 @@ export class Input implements ComponentInterface {
}
componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label', 'tabindex', 'title']);
this.inheritedAttributes = {
...inheritAriaAttributes(this.el),
...inheritAttributes(this.el, ['tabindex', 'title']),
};
}
connectedCallback() {

View File

@ -7,7 +7,7 @@ import { getIonMode } from '../../global/ionic-global';
import type { Color } from '../../interface';
import type { ButtonInterface } from '../../utils/element-interface';
import type { Attributes } from '../../utils/helpers';
import { inheritAttributes } from '../../utils/helpers';
import { inheritAriaAttributes } from '../../utils/helpers';
import { menuController } from '../../utils/menu-controller';
import { createColorClasses, hostContext } from '../../utils/theme';
import { updateVisibility } from '../menu-toggle/menu-toggle-util';
@ -61,7 +61,7 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
@Prop() type: 'submit' | 'reset' | 'button' = 'button';
componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
this.inheritedAttributes = inheritAriaAttributes(this.el);
}
componentDidLoad() {

View File

@ -7,7 +7,7 @@ import type { Animation, Gesture, GestureDetail, MenuChangeEventDetail, MenuI, S
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { GESTURE_CONTROLLER } from '../../utils/gesture';
import type { Attributes } from '../../utils/helpers';
import { assert, clamp, inheritAttributes, isEndSide as isEnd } from '../../utils/helpers';
import { inheritAriaAttributes, assert, clamp, isEndSide as isEnd } from '../../utils/helpers';
import { menuController } from '../../utils/menu-controller';
import { getOverlay } from '../../utils/overlays';
@ -226,7 +226,7 @@ export class Menu implements ComponentInterface, MenuI {
}
componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
this.inheritedAttributes = inheritAriaAttributes(this.el);
}
async componentDidLoad() {

View File

@ -14,7 +14,7 @@ import type {
StyleEventDetail,
} from '../../interface';
import type { Attributes } from '../../utils/helpers';
import { clamp, debounceEvent, getAriaLabel, inheritAttributes, renderHiddenInput } from '../../utils/helpers';
import { inheritAriaAttributes, clamp, debounceEvent, getAriaLabel, renderHiddenInput } from '../../utils/helpers';
import { isRTL } from '../../utils/rtl';
import { createColorClasses, hostContext } from '../../utils/theme';
@ -237,7 +237,7 @@ export class Range implements ComponentInterface {
*/
this.rangeId = this.el.hasAttribute('id') ? this.el.getAttribute('id')! : `ion-r-${rangeIds++}`;
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
this.inheritedAttributes = inheritAriaAttributes(this.el);
}
componentDidLoad() {

View File

@ -4,7 +4,7 @@ import { Build, Component, Element, Event, Host, Method, Prop, State, Watch, h,
import { getIonMode } from '../../global/ionic-global';
import type { Color, StyleEventDetail, TextareaChangeEventDetail } from '../../interface';
import type { Attributes } from '../../utils/helpers';
import { debounceEvent, findItemLabel, inheritAttributes, raf } from '../../utils/helpers';
import { inheritAriaAttributes, debounceEvent, findItemLabel, inheritAttributes, raf } from '../../utils/helpers';
import { createColorClasses } from '../../utils/theme';
/**
@ -220,7 +220,10 @@ export class Textarea implements ComponentInterface {
}
componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['title']);
this.inheritedAttributes = {
...inheritAriaAttributes(this.el),
...inheritAttributes(this.el, ['title']),
};
}
componentDidLoad() {

View File

@ -103,6 +103,74 @@ export const inheritAttributes = (el: HTMLElement, attributes: string[] = []) =>
return attributeObject;
};
/**
* List of available ARIA attributes + `role`.
* Removed deprecated attributes.
* https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes
*/
const ariaAttributes = [
'role',
'aria-activedescendant',
'aria-atomic',
'aria-autocomplete',
'aria-braillelabel',
'aria-brailleroledescription',
'aria-busy',
'aria-checked',
'aria-colcount',
'aria-colindex',
'aria-colindextext',
'aria-colspan',
'aria-controls',
'aria-current',
'aria-describedby',
'aria-description',
'aria-details',
'aria-disabled',
'aria-errormessage',
'aria-expanded',
'aria-flowto',
'aria-haspopup',
'aria-hidden',
'aria-invalid',
'aria-keyshortcuts',
'aria-label',
'aria-labelledby',
'aria-level',
'aria-live',
'aria-multiline',
'aria-multiselectable',
'aria-orientation',
'aria-owns',
'aria-placeholder',
'aria-posinset',
'aria-pressed',
'aria-readonly',
'aria-relevant',
'aria-required',
'aria-roledescription',
'aria-rowcount',
'aria-rowindex',
'aria-rowindextext',
'aria-rowspan',
'aria-selected',
'aria-setsize',
'aria-sort',
'aria-valuemax',
'aria-valuemin',
'aria-valuenow',
'aria-valuetext',
];
/**
* Returns an array of aria attributes that should be copied from
* the shadow host element to a target within the light DOM.
* @param el The element that the attributes should be copied from.
*/
export const inheritAriaAttributes = (el: HTMLElement) => {
return inheritAttributes(el, ariaAttributes);
};
export const addEventListener = (el: any, eventName: string, callback: any, opts?: any) => {
if (typeof (window as any) !== 'undefined') {
const win = window as any;

View File

@ -1,4 +1,4 @@
import { inheritAttributes } from '../helpers';
import { inheritAttributes, inheritAriaAttributes } from '../helpers';
describe('inheritAttributes()', () => {
it('should create an attribute inheritance object', () => {
@ -37,3 +37,29 @@ describe('inheritAttributes()', () => {
});
});
});
describe('inheritAriaAttributes()', () => {
it('should inherit ARIA attributes defined on the HTML element', () => {
const el = document.createElement('div');
el.setAttribute('aria-label', 'myLabel');
el.setAttribute('aria-describedby', 'myDescription');
const attributeObject = inheritAriaAttributes(el);
expect(attributeObject).toEqual({
'aria-label': 'myLabel',
'aria-describedby': 'myDescription',
});
});
it('should inherit the role attribute defined on the HTML element', () => {
const el = document.createElement('div');
el.setAttribute('role', 'button');
const attributeObject = inheritAriaAttributes(el);
expect(attributeObject).toEqual({
role: 'button',
});
});
});