Skip to content

Commit

Permalink
♻️ [open-formulieren/open-forms#4546] Refactor aria attributes utilities
Browse files Browse the repository at this point in the history
Added a single helper to set the desired list of aria-describedby IDs
in the element attribute, for each element.
  • Loading branch information
sergei-maertens committed Oct 29, 2024
1 parent ffd967e commit 8b339c5
Showing 1 changed file with 52 additions and 32 deletions.
84 changes: 52 additions & 32 deletions src/formio/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,61 @@ const escapeHtml = source => {
const getAriaDescriptions = element =>
(element.getAttribute('aria-describedby') || '').split(' ').filter(description => !!description);

/**
* Set or remove description IDs from the element's `aria-describedby` attribute.
* @param {HTMLElement} element The element to update.
* @param {string[]} descriptionIds Element IDs to set or remove.
* @param {'present' | 'absent'} state Desired state
* @return {void}
*/
const updateAriaDescriptions = (element, descriptionIds, state) => {
let ariaDescriptions = getAriaDescriptions(element);

switch (state) {
case 'absent': {
ariaDescriptions = ariaDescriptions.filter(
description => !descriptionIds.includes(description)
);
break;
}
case 'present': {
const idsToAdd = descriptionIds.filter(
description => !ariaDescriptions.includes(description)
);
ariaDescriptions.push(...idsToAdd);
break;
}
default: {
throw new Error(`Unknown state: ${state}`);

Check warning on line 47 in src/formio/utils.js

View check run for this annotation

Codecov / codecov/patch

src/formio/utils.js#L46-L47

Added lines #L46 - L47 were not covered by tests
}
}

if (ariaDescriptions.length > 0) {
element.setAttribute('aria-describedby', ariaDescriptions.join(' '));
} else {
element.removeAttribute('aria-describedby');
}
};

/**
* Update the accessible error attributes for the input elements
* @param {HTMLElement[]} elements
* @param {Boolean} hasErrors
* @param {Boolean} hasMessages
* @param {string} messageContainerId
* @return {void}
*/
const setErrorAttributes = (elements, hasErrors, hasMessages, messageContainerId) => {
// Update the attributes 'aria-invalid' and 'aria-describedby' using hasErrors
elements.forEach(element => {
let ariaDescriptions = getAriaDescriptions(element);
const desiredState =
hasErrors & hasMessages
? // The input has an error, but the error message isn't yet part of the ariaDescriptions
'present'
: // The input doesn't have an error, but the error message is still a part of the ariaDescriptions
'absent';

if (hasErrors && hasMessages && !ariaDescriptions.includes(messageContainerId)) {
// The input has an error, but the error message isn't yet part of the ariaDescriptions
ariaDescriptions.push(messageContainerId);
}

if (!hasErrors && ariaDescriptions.includes(messageContainerId)) {
// The input doesn't have an error, but the error message is still a part of the ariaDescriptions
ariaDescriptions = ariaDescriptions.filter(description => description !== messageContainerId);
}

if (ariaDescriptions.length > 0) {
element.setAttribute('aria-describedby', ariaDescriptions.join(' '));
} else {
element.removeAttribute('aria-describedby');
}
updateAriaDescriptions(element, [messageContainerId], desiredState);

if (hasErrors) {
element.setAttribute('aria-invalid', 'true');
Expand All @@ -64,22 +99,7 @@ const linkToSoftRequiredDisplay = (elements, component) => {

// Update the attribute 'aria-describedby' based on whether the component is empty
elements.forEach(element => {
let ariaDescriptions = getAriaDescriptions(element);

softRequiredIds.forEach(id => {
if (isEmpty && !ariaDescriptions.includes(id)) {
ariaDescriptions.push(id);
}
if (!isEmpty && ariaDescriptions.includes(id)) {
ariaDescriptions = ariaDescriptions.filter(description => description !== id);
}
});

if (ariaDescriptions.length > 0) {
element.setAttribute('aria-describedby', ariaDescriptions.join(' '));
} else {
element.removeAttribute('aria-describedby');
}
updateAriaDescriptions(element, softRequiredIds, isEmpty ? 'present' : 'absent');
});
};

Expand Down

0 comments on commit 8b339c5

Please sign in to comment.