feat: adjust validity evaluation logic

This commit is contained in:
Pengoose 2024-12-18 10:20:02 +09:00
parent 4298372d56
commit 6e7aebee53

View file

@ -461,25 +461,13 @@ export function getElementAccessibleDescription(element: Element, includeHidden:
return accessibleDescription; return accessibleDescription;
} }
// https://html.spec.whatwg.org/multipage/input.html#the-input-element
export const kInputValidityAttributes = ['required', 'pattern', 'min', 'max', 'minlength', 'maxlength', 'step', 'type', 'value'];
export function hasValidationAttributes(element: Element): boolean {
if (!(element instanceof HTMLInputElement))
return false;
return kInputValidityAttributes.some(attr => element.hasAttribute(attr));
}
// https://www.w3.org/TR/wai-aria-1.2/#aria-invalid // https://www.w3.org/TR/wai-aria-1.2/#aria-invalid
export const kAriaInvalidRoles = ['application', 'checkbox', 'combobox', 'gridcell', 'listbox', 'radiogroup', 'slider', 'spinbutton', 'textbox', 'tree', 'columnheader', 'rowheader', 'searchbox', 'switch', 'treegrid']; const kAriaInvalidRoles = ['application', 'checkbox', 'combobox', 'gridcell', 'listbox', 'radiogroup', 'slider', 'spinbutton', 'textbox', 'tree', 'columnheader', 'rowheader', 'searchbox', 'switch', 'treegrid'];
export function getAriaInvalid(element: Element): 'false' | 'true' | 'grammar' | 'spelling' { function getAriaInvalid(element: Element): 'false' | 'true' | 'grammar' | 'spelling' {
const role = getAriaRole(element) || ''; const role = getAriaRole(element) || '';
if (!role || !kAriaInvalidRoles.includes(role)) if (!role || !kAriaInvalidRoles.includes(role))
return 'false'; return 'false';
if (element instanceof HTMLInputElement && hasValidationAttributes(element) && element.validity)
return element.validity.valid ? 'false' : 'true';
const ariaInvalid = element.getAttribute('aria-invalid'); const ariaInvalid = element.getAttribute('aria-invalid');
if (!ariaInvalid || ariaInvalid.trim() === '' || ariaInvalid.toLocaleLowerCase() === 'false') if (!ariaInvalid || ariaInvalid.trim() === '' || ariaInvalid.toLocaleLowerCase() === 'false')
return 'false'; return 'false';
@ -488,6 +476,14 @@ export function getAriaInvalid(element: Element): 'false' | 'true' | 'grammar' |
return 'true'; return 'true';
} }
function getValidityInvalid(element: Element) {
if ('validity' in element){
const validity = element.validity as ValidityState | undefined;
return validity?.valid === false;
}
return false;
}
export function getElementAccessibleErrorMessage(element: Element): string { export function getElementAccessibleErrorMessage(element: Element): string {
// SPEC: https://w3c.github.io/aria/#aria-errormessage // SPEC: https://w3c.github.io/aria/#aria-errormessage
// //
@ -499,9 +495,12 @@ export function getElementAccessibleErrorMessage(element: Element): string {
accessibleErrorMessage = ''; accessibleErrorMessage = '';
const isAriaInvalid = getAriaInvalid(element) !== 'false'; const isAriaInvalid = getAriaInvalid(element) !== 'false';
if (isAriaInvalid) { const isValidityInvalid = getValidityInvalid(element);
if (isAriaInvalid || isValidityInvalid) {
const errorMessageId = element.getAttribute('aria-errormessage'); const errorMessageId = element.getAttribute('aria-errormessage');
const errorMessages = getIdRefs(element, errorMessageId); const errorMessages = getIdRefs(element, errorMessageId);
// Ideally, this should be a separate "embeddedInErrorMessage", but it would follow the exact same rules.
// Relevant vague spec: https://w3c.github.io/core-aam/#ariaErrorMessage.
const parts = errorMessages.map(errorMessage => asFlatString( const parts = errorMessages.map(errorMessage => asFlatString(
getTextAlternativeInternal(errorMessage, { getTextAlternativeInternal(errorMessage, {
visitedElements: new Set(), visitedElements: new Set(),