MDL-84832 tiny_media: Announce when alt text maxlength is reached

Co-authored-by: Meirza <[email protected]>
This commit is contained in:
Jun Pataleta
2025-05-30 17:09:26 +08:00
co-authored by Meirza
parent a52fbcced6
commit b3619e08e4
5 changed files with 33 additions and 12 deletions
+1
View File
@@ -1297,6 +1297,7 @@ $string['maximumgradex'] = 'Maximum grade: {$a}';
$string['maximumshort'] = 'Max';
$string['maximumupload'] = 'Maximum upload size';
$string['maximumupload_help'] = 'The maximum file size allowed for student uploads to the course. Additionally, you can further restrict the maximum upload size for each activity.';
$string['maxlengthreached'] = 'Maximum character limit of {$a} has been reached';
$string['maxnumberweeks'] = 'Maximum number of sections';
$string['maxnumberweeks_desc'] = 'The maximum value in the number of sections drop-down menu (applies to certain course formats only).';
$string['maxnumcoursesincombo'] = 'Browse <a href="{$a->link}">{$a->numberofcourses} courses</a>.';
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -65,6 +65,7 @@ export class ImageDetails extends MediaBase {
this.canShowDropZone = canShowDropZone;
this.currentUrl = currentUrl;
this.image = image;
this.toggleMaxlengthFeedbackSuffix = false;
}
init = function() {
@@ -206,13 +207,13 @@ export class ImageDetails extends MediaBase {
/**
* Handles changes in the image presentation checkbox and enables/disables the image alt text input accordingly.
*/
presentationChanged() {
async presentationChanged() {
const presentation = this.root.querySelector(Selectors.IMAGE.elements.presentation);
const alt = this.root.querySelector(Selectors.IMAGE.elements.alt);
alt.disabled = presentation.checked;
// Counting the image description characters.
this.handleKeyupCharacterCount();
await this.handleKeyupCharacterCount();
}
/**
@@ -399,10 +400,10 @@ export class ImageDetails extends MediaBase {
}
});
this.root.addEventListener('change', (e) => {
this.root.addEventListener('change', async(e) => {
const presentationEle = e.target.closest(Selectors.IMAGE.elements.presentation);
if (presentationEle) {
this.presentationChanged();
await this.presentationChanged();
}
const constrainEle = e.target.closest(Selectors.IMAGE.elements.constrain);
@@ -421,21 +422,21 @@ export class ImageDetails extends MediaBase {
}
});
this.root.addEventListener('blur', (e) => {
this.root.addEventListener('blur', async(e) => {
if (e.target.nodeType === Node.ELEMENT_NODE) {
const presentationEle = e.target.closest(Selectors.IMAGE.elements.presentation);
if (presentationEle) {
this.presentationChanged();
await this.presentationChanged();
}
}
}, true);
// Character count.
this.root.addEventListener('keyup', (e) => {
this.root.addEventListener('keyup', async(e) => {
const altEle = e.target.closest(Selectors.IMAGE.elements.alt);
if (altEle) {
this.handleKeyupCharacterCount();
await this.handleKeyupCharacterCount();
}
});
@@ -456,10 +457,28 @@ export class ImageDetails extends MediaBase {
});
}
handleKeyupCharacterCount() {
const alt = this.root.querySelector(Selectors.IMAGE.elements.alt).value;
async handleKeyupCharacterCount() {
const altField = this.root.querySelector(Selectors.IMAGE.elements.alt);
const alt = altField.value;
const current = this.root.querySelector('#currentcount');
current.innerHTML = alt.length;
const maxLength = altField.getAttribute('maxlength');
const maxLengthFeedback = document.getElementById('maxlength_feedback');
if (alt.length >= maxLength) {
maxLengthFeedback.textContent = await getString('maxlengthreached', 'core', maxLength);
// Clever (or hacky?;p) way to ensure that the feedback message is announced to screen readers.
const suffix = this.toggleMaxlengthFeedbackSuffix ? '' : '.';
maxLengthFeedback.textContent += suffix;
this.toggleMaxlengthFeedbackSuffix = !this.toggleMaxlengthFeedbackSuffix;
// Clear the feedback message after 4 seconds. This is similar to the default timeout of toast messages
// before disappearing from view. It is important to clear the message to prevent screen reader users from navigating
// into this region and avoiding confusion.
setTimeout(() => {
maxLengthFeedback.textContent = '';
}, 4000);
}
}
/**
@@ -58,6 +58,7 @@
<span id="currentcount">0</span>
<span class="mx-1">/</span>
<span id="maximumcount">{{maxlengthalt}}</span>
<span id="maxlength_feedback" class="visually-hidden" role="status"></span>
</div>
</div>
</div>