MDL-85041 core: Improve autocomplete/tag a11y for assistive tech
- Updated autocomplete behavior to announce dynamic changes via ARIA live regions - Fixed focus management for keyboard and screen reader navigation - Added proper role/aria tags for interactive tag components
This commit is contained in:
@@ -1819,6 +1819,7 @@ $string['remoteappuser'] = 'Remote {$a} User';
|
||||
$string['remove'] = 'Remove';
|
||||
$string['removeadmin'] = 'Remove admin';
|
||||
$string['removecreator'] = 'Remove course creator';
|
||||
$string['removed'] = 'Removed {$a}';
|
||||
$string['removestudent'] = 'Remove student';
|
||||
$string['removeteacher'] = 'Remove teacher';
|
||||
$string['rename'] = 'Rename';
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -237,6 +237,10 @@ define([
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const selectedItemText = item[0].childNodes[2].textContent?.trim();
|
||||
announceChanges(state.selectionId, selectedItemText, 'removed');
|
||||
|
||||
// Rerender the selection list.
|
||||
return updateSelectionList(options, state, originalSelect)
|
||||
.then(function() {
|
||||
@@ -620,6 +624,8 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
announceChanges(state.selectionId, query.trim(), 'added');
|
||||
|
||||
return updateSelectionList(options, state, originalSelect)
|
||||
.then(function() {
|
||||
// Notify that the selection changed.
|
||||
@@ -647,9 +653,10 @@ define([
|
||||
* @param {Object} options The original options for the autocomplete.
|
||||
* @param {Object} state State variables for the autocomplete.
|
||||
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
|
||||
* @param {string|null} selectedItem The item to be selected.
|
||||
* @return {Promise}
|
||||
*/
|
||||
var selectCurrentItem = function(options, state, originalSelect) {
|
||||
var selectCurrentItem = function(options, state, originalSelect, selectedItem) {
|
||||
// Find the elements in the page.
|
||||
var inputElement = $(document.getElementById(state.inputId));
|
||||
var suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
@@ -670,6 +677,8 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
announceChanges(state.selectionId, selectedItem, 'added');
|
||||
|
||||
return updateSelectionList(options, state, originalSelect)
|
||||
.then(function() {
|
||||
// Notify that the selection changed.
|
||||
@@ -810,8 +819,9 @@ define([
|
||||
var suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
if ((inputElement.attr('aria-expanded') === "true") &&
|
||||
(suggestionsElement.children('[aria-selected=true]').length > 0)) {
|
||||
const selectedItemText = suggestionsElement.children('[aria-selected=true]')[0].textContent.trim();
|
||||
// If the suggestion list has an active item, select it.
|
||||
pendingJsPromise.resolve(selectCurrentItem(options, state, originalSelect));
|
||||
pendingJsPromise.resolve(selectCurrentItem(options, state, originalSelect, selectedItemText));
|
||||
} else if (options.tags) {
|
||||
// If tags are enabled, create a tag.
|
||||
pendingJsPromise.resolve(createItem(options, state, originalSelect));
|
||||
@@ -930,12 +940,13 @@ define([
|
||||
activateItem(current, state)
|
||||
.then(function() {
|
||||
// And select it.
|
||||
return selectCurrentItem(options, state, originalSelect);
|
||||
const selectedItemText = element[0].textContent.trim();
|
||||
return selectCurrentItem(options, state, originalSelect, selectedItemText);
|
||||
})
|
||||
.then(function() {
|
||||
return pendingPromise.resolve();
|
||||
})
|
||||
.catch();
|
||||
.catch(notification.exception);
|
||||
});
|
||||
var selectionElement = $(document.getElementById(state.selectionId));
|
||||
|
||||
@@ -1283,6 +1294,41 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Announces changes to a tag in the autocomplete form.
|
||||
*
|
||||
* Updates the text content of a status element to inform users about the addition or removal
|
||||
* of a tag. This is useful for accessibility purposes, ensuring screen readers can notify users
|
||||
* of changes in the autocomplete component.
|
||||
*
|
||||
* @param {string} selectionId - The ID of the selection element used to locate the announcer element.
|
||||
* @param {string|null|undefined} tagname - The name of the tag that was added or removed.
|
||||
* @param {string} action - The action performed on the tag (e.g., "added" or "removed").
|
||||
*/
|
||||
const announceChanges = async(selectionId, tagname, action) => {
|
||||
if (!tagname) {
|
||||
return;
|
||||
}
|
||||
|
||||
const status = document.getElementById(`${selectionId}-announcer`);
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await str.get_string(action, 'core', tagname);
|
||||
status.textContent = result;
|
||||
} catch (error) {
|
||||
notification.exception(error);
|
||||
}
|
||||
|
||||
// Remove the status message after 4 seconds to prevent screen readers from announcing it.
|
||||
setTimeout(() => {
|
||||
status.textContent = '';
|
||||
}, 4000);
|
||||
|
||||
};
|
||||
|
||||
return {
|
||||
// Public variables and functions.
|
||||
enhanceField: enhanceField,
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
], "noSelectionString": "No selection" }
|
||||
}}
|
||||
<span class="sr-only" id="{{selectionId}}-label">{{#str}}selecteditems, form{{/str}}</span>
|
||||
<div id="{{selectionId}}-announcer" aria-live="polite" role="status" class="sr-only"></div>
|
||||
<div{{!
|
||||
}} class="form-autocomplete-selection w-100 {{#multiple}}form-autocomplete-multiple{{/multiple}}"{{!
|
||||
}} id="{{selectionId}}"{{!
|
||||
|
||||
Reference in New Issue
Block a user