fix(form-core): aria-required for compatible roles

This commit is contained in:
Konstantinos Norgias 2021-03-15 12:16:06 +01:00 committed by Thijs Louisse
parent fa9cdffc8f
commit aa47817465
3 changed files with 112 additions and 1 deletions

View file

@ -0,0 +1,5 @@
---
'@lion/form-core': patch
---
fix: prevent a11y violations when applying aria-required

View file

@ -9,6 +9,33 @@ export class Required extends Validator {
return 'Required'; return 'Required';
} }
/**
* In order to prevent accessibility violations, the aria-required attribute will
* be combined with compatible aria roles: https://www.w3.org/TR/wai-aria/#aria-required
*/
static get _compatibleRoles() {
return [
'combobox',
'gridcell',
'input',
'listbox',
'radiogroup',
'select',
'spinbutton',
'textarea',
'textbox',
'tree',
];
}
/**
* In order to prevent accessibility violations, the aria-required attribute will
* be combined with compatible platform input elements
*/
static get _compatibleTags() {
return ['input', 'select', 'textarea'];
}
/** /**
* We don't have an execute function, since the Required validator is 'special'. * We don't have an execute function, since the Required validator is 'special'.
* The outcome depends on the modelValue of the FormControl and * The outcome depends on the modelValue of the FormControl and
@ -21,9 +48,14 @@ export class Required extends Validator {
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
onFormControlConnect(formControl) { onFormControlConnect(formControl) {
if (formControl._inputNode) { if (formControl._inputNode) {
const role = formControl._inputNode.getAttribute('role') || '';
const elementTagName = formControl._inputNode.tagName.toLowerCase();
const ctor = /** @type {typeof Required} */ (this.constructor);
if (ctor._compatibleRoles.includes(role) || ctor._compatibleTags.includes(elementTagName)) {
formControl._inputNode.setAttribute('aria-required', 'true'); formControl._inputNode.setAttribute('aria-required', 'true');
} }
} }
}
/** /**
* @param {FormControlHost & HTMLElement} formControl * @param {FormControlHost & HTMLElement} formControl

View file

@ -0,0 +1,74 @@
import { expect, fixture, html, unsafeStatic, defineCE } from '@open-wc/testing';
import { LionField } from '@lion/form-core';
import { Required } from '../../src/validate/validators/Required.js';
/**
* @typedef {import('../../types/FormControlMixinTypes.js').FormControlHost} FormControlHost
* @typedef {import('../../types/FormControlMixinTypes.js').HTMLElementWithValue} HTMLElementWithValue
*/
/** @type {HTMLElementWithValue} */
let inputNodeTag;
class RequiredElement extends LionField {
connectedCallback() {
const inputNode = document.createElement('input');
inputNode.slot = 'input';
this.appendChild(inputNode);
super.connectedCallback();
}
get _inputNode() {
return inputNodeTag || super._inputNode;
}
}
const tagString = defineCE(RequiredElement);
const tag = unsafeStatic(tagString);
describe('Required validation', async () => {
const validator = new Required();
it('get aria-required attribute if element is part of the right tag names', async () => {
const el = /** @type {FormControlHost & HTMLElement} */ (await fixture(
html`<${tag}></${tag}>`,
));
Required._compatibleTags.forEach(tagName => {
inputNodeTag = /** @type {HTMLElementWithValue} */ (document.createElement(tagName));
validator.onFormControlConnect(el);
expect(el._inputNode).to.have.attribute('aria-required', 'true');
});
// When incompatible tags are used, aria-required will not be added
// @ts-ignore
inputNodeTag = /** @type {HTMLDivElementWithValue} */ (document.createElement('div'));
validator.onFormControlConnect(el);
expect(el._inputNode).to.not.have.attribute('aria-required');
});
it('get aria-required attribute if element is part of the right roles', async () => {
const el = /** @type {FormControlHost & HTMLElement} */ (await fixture(
html`<${tag}></${tag}>`,
));
Required._compatibleRoles.forEach(role => {
// @ts-ignore
inputNodeTag = /** @type {HTMLElementWithValue} */ (document.createElement('div'));
inputNodeTag.setAttribute('role', role);
validator.onFormControlConnect(el);
expect(el._inputNode).to.have.attribute('aria-required', 'true');
});
// When incompatible roles are used, aria-required will not be added
// @ts-ignore
inputNodeTag = /** @type {HTMLElementWithValue} */ (document.createElement('div'));
inputNodeTag.setAttribute('role', 'group');
validator.onFormControlConnect(el);
expect(el._inputNode).to.not.have.attribute('aria-required');
});
});