MDL-86512 core: Ensure bulk action selection count announcement

When the bulk actions are first rendered, the item count in the
aria-live region will not be announced by screen readers as aria-live
regions need to be present in the DOM and changed before screen readers
can announce their content.

This commit ensures that the item selection count will be announced
when the bulk actions get added to the DOM by calling
`updateBulkItemSelection()` after rendering the bulk actions HTML. This
removes the non-breaking space in the template, effectively causing a
change in DOM without affecting the visuals, and allowing screen readers
to announce the contents of the item count.
This commit is contained in:
Jun Pataleta
2025-09-16 22:39:00 +08:00
parent 09999184d4
commit 280d9de9c3
4 changed files with 11 additions and 3 deletions
+1 -1
View File
@@ -5,6 +5,6 @@ define("core/bulkactions/bulk_actions",["exports","core/templates","core/str","c
* @module core/bulkactions/bulk_actions
* @copyright 2023 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};const Selectors_stickyFooterContainer="#sticky-footer",Selectors_selectedItemsCountContainer='[data-type="bulkactions"] [data-for="bulkcount"]',Selectors_cancelBulkActionModeElement='[data-type="bulkactions"] [data-action="bulkcancel"]',Selectors_bulkModeContainer='[data-type="bulkactions"]',Selectors_bulkActionsContainer='[data-type="bulkactions"] [data-for="bulktools"]';return _exports.default=class{constructor(){let maxButtons=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;if(_defineProperty(this,"initialStickyFooterContent",null),_defineProperty(this,"selectedItems",[]),_defineProperty(this,"isBulkActionsModeEnabled",!1),_defineProperty(this,"maxButtons",5),!this.getStickyFooterContainer())throw new Error("Sticky footer not found.");this.initialStickyFooterContent=this.getStickyFooterContainer().innerHTML,maxButtons&&(this.maxButtons=maxButtons),this.registerItemSelectChangeEvent((async()=>{this.selectedItems=this.getSelectedItems(),this.selectedItems.length>0?this.isBulkActionsModeEnabled?await this.updateBulkItemSelection():await this.enableBulkActionsMode():this.disableBulkActionsMode()}))}getBulkActions(){throw new Error("getBulkActions() must be implemented in ".concat(this.constructor.name))}getSelectedItems(){throw new Error("getSelectedItems() must be implemented in ".concat(this.constructor.name))}registerItemSelectChangeEvent(eventHandler){throw new Error("registerItemSelectChangeEvent(".concat(eventHandler,") must be implemented in ").concat(this.constructor.name))}deselectItem(selectedItem){throw new Error("deselectItem(".concat(selectedItem,") must be implemented in ").concat(this.constructor.name))}getStickyFooterContainer(){return document.querySelector(Selectors_stickyFooterContainer)}async enableBulkActionsMode(){(0,_stickyFooter.enableStickyFooter)(),this.getStickyFooterContainer().innerHTML=await this.renderBulkActions();const bulkModeContainer=this.getStickyFooterContainer().querySelector(Selectors_bulkModeContainer),bulkActionsContainer=bulkModeContainer.querySelector(Selectors_bulkActionsContainer);this.getBulkActions().forEach((bulkAction=>{bulkAction.registerListenerEvents(bulkActionsContainer),bulkAction.setSelectedItems(this.selectedItems)})),bulkModeContainer.addEventListener("click",(e=>{e.target.closest(Selectors_cancelBulkActionModeElement)&&(this.selectedItems.forEach((item=>{this.deselectItem(item)})),this.disableBulkActionsMode())})),this.isBulkActionsModeEnabled=!0}disableBulkActionsMode(){this.initialStickyFooterContent.length>0?this.getStickyFooterContainer().innerHTML=this.initialStickyFooterContent:(0,_stickyFooter.disableStickyFooter)(),this.isBulkActionsModeEnabled=!1}async renderBulkActions(){const data={bulkselectioncount:this.selectedItems.length,actions:[],moreactions:[],hasmoreactions:!1},bulkActions=this.getBulkActions(),showMoreButton=bulkActions.length>this.maxButtons;return(await Promise.all(bulkActions.map(((bulkAction,index)=>bulkAction.renderBulkActionTrigger(showMoreButton&&index>=this.maxButtons-1,index))))).forEach(((actionTrigger,index)=>{showMoreButton&&index>=this.maxButtons-1?data.moreactions.push({actiontrigger:actionTrigger}):data.actions.push({actiontrigger:actionTrigger})})),data.hasmoreactions=data.moreactions.length>0,_templates.default.render("core/bulkactions/bulk_actions",data)}async updateBulkItemSelection(){const bulkSelection=await(0,_str.get_string)("bulkselection","core",this.selectedItems.length);document.querySelector(Selectors_selectedItemsCountContainer).innerHTML=bulkSelection}},_exports.default}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};const Selectors_stickyFooterContainer="#sticky-footer",Selectors_selectedItemsCountContainer='[data-type="bulkactions"] [data-for="bulkcount"]',Selectors_cancelBulkActionModeElement='[data-type="bulkactions"] [data-action="bulkcancel"]',Selectors_bulkModeContainer='[data-type="bulkactions"]',Selectors_bulkActionsContainer='[data-type="bulkactions"] [data-for="bulktools"]';return _exports.default=class{constructor(){let maxButtons=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;if(_defineProperty(this,"initialStickyFooterContent",null),_defineProperty(this,"selectedItems",[]),_defineProperty(this,"isBulkActionsModeEnabled",!1),_defineProperty(this,"maxButtons",5),!this.getStickyFooterContainer())throw new Error("Sticky footer not found.");this.initialStickyFooterContent=this.getStickyFooterContainer().innerHTML,maxButtons&&(this.maxButtons=maxButtons),this.registerItemSelectChangeEvent((async()=>{this.selectedItems=this.getSelectedItems(),this.selectedItems.length>0?this.isBulkActionsModeEnabled?await this.updateBulkItemSelection():await this.enableBulkActionsMode():this.disableBulkActionsMode()}))}getBulkActions(){throw new Error("getBulkActions() must be implemented in ".concat(this.constructor.name))}getSelectedItems(){throw new Error("getSelectedItems() must be implemented in ".concat(this.constructor.name))}registerItemSelectChangeEvent(eventHandler){throw new Error("registerItemSelectChangeEvent(".concat(eventHandler,") must be implemented in ").concat(this.constructor.name))}deselectItem(selectedItem){throw new Error("deselectItem(".concat(selectedItem,") must be implemented in ").concat(this.constructor.name))}getStickyFooterContainer(){return document.querySelector(Selectors_stickyFooterContainer)}async enableBulkActionsMode(){(0,_stickyFooter.enableStickyFooter)(),this.getStickyFooterContainer().innerHTML=await this.renderBulkActions();const bulkModeContainer=this.getStickyFooterContainer().querySelector(Selectors_bulkModeContainer),bulkActionsContainer=bulkModeContainer.querySelector(Selectors_bulkActionsContainer);this.getBulkActions().forEach((bulkAction=>{bulkAction.registerListenerEvents(bulkActionsContainer),bulkAction.setSelectedItems(this.selectedItems)})),bulkModeContainer.addEventListener("click",(e=>{e.target.closest(Selectors_cancelBulkActionModeElement)&&(this.selectedItems.forEach((item=>{this.deselectItem(item)})),this.disableBulkActionsMode())})),this.isBulkActionsModeEnabled=!0,setTimeout((async()=>{await this.updateBulkItemSelection()}),300)}disableBulkActionsMode(){this.initialStickyFooterContent.length>0?this.getStickyFooterContainer().innerHTML=this.initialStickyFooterContent:(0,_stickyFooter.disableStickyFooter)(),this.isBulkActionsModeEnabled=!1}async renderBulkActions(){const data={bulkselectioncount:this.selectedItems.length,actions:[],moreactions:[],hasmoreactions:!1},bulkActions=this.getBulkActions(),showMoreButton=bulkActions.length>this.maxButtons;return(await Promise.all(bulkActions.map(((bulkAction,index)=>bulkAction.renderBulkActionTrigger(showMoreButton&&index>=this.maxButtons-1,index))))).forEach(((actionTrigger,index)=>{showMoreButton&&index>=this.maxButtons-1?data.moreactions.push({actiontrigger:actionTrigger}):data.actions.push({actiontrigger:actionTrigger})})),data.hasmoreactions=data.moreactions.length>0,_templates.default.render("core/bulkactions/bulk_actions",data)}async updateBulkItemSelection(){const bulkSelection=await(0,_str.get_string)("bulkselection","core",this.selectedItems.length);document.querySelector(Selectors_selectedItemsCountContainer).innerHTML=bulkSelection}},_exports.default}));
//# sourceMappingURL=bulk_actions.min.js.map
File diff suppressed because one or more lines are too long
+8
View File
@@ -172,6 +172,14 @@ export default class BulkActions {
}
});
this.isBulkActionsModeEnabled = true;
// Calling `renderBulkActions()` already renders the item selection count.
// Because of this, screen readers will not announce the item selection count on first load given that aria-live regions
// must be present in the DOM and have their contents changed before screen readers can announce their content.
// So, we call `updateBulkItemSelection()` after a short delay to ensure that screen readers announce the item count.
setTimeout(async() => {
await this.updateBulkItemSelection();
}, 300);
}
/**
@@ -87,7 +87,7 @@
</button>
</div>
<div data-for="bulkcount" role="status">
{{#str}} bulkselection, core, {{bulkselectioncount}} {{/str}}
&nbsp;{{#str}} bulkselection, core, {{bulkselectioncount}} {{/str}}
</div>
</div>
</div>