fix(form-core): aria-required for compatible roles
This commit is contained in:
parent
fa9cdffc8f
commit
aa47817465
3 changed files with 112 additions and 1 deletions
5
.changeset/lazy-bulldogs-hear.md
Normal file
5
.changeset/lazy-bulldogs-hear.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@lion/form-core': patch
|
||||
---
|
||||
|
||||
fix: prevent a11y violations when applying aria-required
|
||||
|
|
@ -9,6 +9,33 @@ export class Required extends Validator {
|
|||
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'.
|
||||
* The outcome depends on the modelValue of the FormControl and
|
||||
|
|
@ -21,7 +48,12 @@ export class Required extends Validator {
|
|||
// eslint-disable-next-line class-methods-use-this
|
||||
onFormControlConnect(formControl) {
|
||||
if (formControl._inputNode) {
|
||||
formControl._inputNode.setAttribute('aria-required', 'true');
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
74
packages/form-core/test/validate/Required.test.js
Normal file
74
packages/form-core/test/validate/Required.test.js
Normal 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');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue