MDL-75669 output: Fix subpanels for BS5

- Fix subpanels positioning using data-bs-popper static
- Fix subpanels and dialogs event listener to use Boostrap EventHandler.
  Doing that will avoid Bootstrap from stop propagating the event to the
  subpanels and dialogs listeners.
This commit is contained in:
Mikel Martín
2025-02-17 08:13:48 +01:00
parent 8a8c8ab275
commit 413b9219bf
10 changed files with 22 additions and 12 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,10 +1,10 @@
define("core/local/dropdown/dialog",["exports","theme_boost/bootstrap/dropdown","core/pagehelpers","core/pending"],(function(_exports,_dropdown,_pagehelpers,_pending){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
define("core/local/dropdown/dialog",["exports","theme_boost/bootstrap/dropdown","core/pagehelpers","core/pending","theme_boost/bootstrap/dom/event-handler"],(function(_exports,_dropdown,_pagehelpers,_pending,_eventHandler){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Dropdown status JS controls.
*
* @module core/local/dropdown/dialog
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.getDropdownDialog=_exports.DropdownDialog=void 0,_dropdown=_interopRequireDefault(_dropdown),_pending=_interopRequireDefault(_pending);const Selectors_dropdownButton='[data-for="dropdowndialog_button"]',Selectors_dropdownDialog='[data-for="dropdowndialog_dialog"]';class DropdownDialog{constructor(element){this.element=element,this.button=element.querySelector(Selectors_dropdownButton),this.panel=element.querySelector(Selectors_dropdownDialog)}init(){this.element.dataset.dropdownDialogInitialized||(this.button.addEventListener("keydown",this._buttonKeyHandler.bind(this)),this.panel.addEventListener("keydown",this._contentKeyHandler.bind(this)),this.element.dataset.dropdownDialogInitialized=!0)}_buttonKeyHandler(event){if("ArrowUp"===event.key||"ArrowLeft"===event.key)return event.stopPropagation(),event.preventDefault(),void this.setVisible(!1);"ArrowDown"!==event.key&&"ArrowRight"!==event.key||(event.stopPropagation(),event.preventDefault(),this.setVisible(!0),this._focusPanelContent())}_contentKeyHandler(event){let newFocus=null;"End"===event.key&&(newFocus=(0,_pagehelpers.lastFocusableElement)(this.panel)),"Home"===event.key&&(newFocus=(0,_pagehelpers.firstFocusableElement)(this.panel)),"ArrowUp"!==event.key&&"ArrowLeft"!==event.key||(newFocus=(0,_pagehelpers.previousFocusableElement)(this.panel,!1),newFocus||(newFocus=this.button)),"ArrowDown"!==event.key&&"ArrowRight"!==event.key||(newFocus=(0,_pagehelpers.nextFocusableElement)(this.panel,!1)),null!==newFocus&&(event.stopPropagation(),event.preventDefault(),newFocus.focus())}_focusPanelContent(){const pendingPromise=new _pending.default("core/dropdown/dialog:focuscontent");setTimeout((()=>{const firstFocusable=(0,_pagehelpers.firstFocusableElement)(this.panel);firstFocusable&&firstFocusable.focus(),pendingPromise.resolve()}),100)}setVisible(visible){visible!==this.isVisible()&&_dropdown.default.getOrCreateInstance(this.button).toggle()}isVisible(){return"true"===this.button.getAttribute("aria-expanded")}setButtonContent(content){this.button.innerHTML=content}setButtonDisabled(disabled){disabled?this.button.setAttribute("disabled","disabled"):this.button.removeAttribute("disabled")}getElement(){return this.element}}_exports.DropdownDialog=DropdownDialog;const getDropdownDialog=selector=>{const dropdownElement=document.querySelector(selector);return dropdownElement?new DropdownDialog(dropdownElement):null};_exports.getDropdownDialog=getDropdownDialog;_exports.init=selector=>{const dropdown=getDropdownDialog(selector);if(!dropdown)throw new Error("Dopdown dialog element not found: ".concat(selector));dropdown.init()}}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.getDropdownDialog=_exports.DropdownDialog=void 0,_dropdown=_interopRequireDefault(_dropdown),_pending=_interopRequireDefault(_pending),_eventHandler=_interopRequireDefault(_eventHandler);const Selectors_dropdownButton='[data-for="dropdowndialog_button"]',Selectors_dropdownDialog='[data-for="dropdowndialog_dialog"]';class DropdownDialog{constructor(element){this.element=element,this.button=element.querySelector(Selectors_dropdownButton),this.panel=element.querySelector(Selectors_dropdownDialog)}init(){if(this.element.dataset.dropdownDialogInitialized)return;const dialogButtonSelector="#".concat(this.element.id," ").concat(Selectors_dropdownButton);_eventHandler.default.on(document,"keydown",dialogButtonSelector,this._buttonKeyHandler.bind(this));const dialogSelector="#".concat(this.element.id," ").concat(Selectors_dropdownDialog);_eventHandler.default.on(document,"keydown",dialogSelector,this._contentKeyHandler.bind(this)),this.element.dataset.dropdownDialogInitialized=!0}_buttonKeyHandler(event){if("ArrowUp"===event.key||"ArrowLeft"===event.key)return event.stopPropagation(),event.preventDefault(),void this.setVisible(!1);"ArrowDown"!==event.key&&"ArrowRight"!==event.key||(event.stopPropagation(),event.preventDefault(),this.setVisible(!0),this._focusPanelContent())}_contentKeyHandler(event){let newFocus=null;"End"===event.key&&(newFocus=(0,_pagehelpers.lastFocusableElement)(this.panel)),"Home"===event.key&&(newFocus=(0,_pagehelpers.firstFocusableElement)(this.panel)),"ArrowUp"!==event.key&&"ArrowLeft"!==event.key||(newFocus=(0,_pagehelpers.previousFocusableElement)(this.panel,!1),newFocus||(newFocus=this.button)),"ArrowDown"!==event.key&&"ArrowRight"!==event.key||(newFocus=(0,_pagehelpers.nextFocusableElement)(this.panel,!1)),null!==newFocus&&(event.stopPropagation(),event.preventDefault(),newFocus.focus())}_focusPanelContent(){const pendingPromise=new _pending.default("core/dropdown/dialog:focuscontent");setTimeout((()=>{const firstFocusable=(0,_pagehelpers.firstFocusableElement)(this.panel);firstFocusable&&firstFocusable.focus(),pendingPromise.resolve()}),100)}setVisible(visible){visible!==this.isVisible()&&_dropdown.default.getOrCreateInstance(this.button).toggle()}isVisible(){return"true"===this.button.getAttribute("aria-expanded")}setButtonContent(content){this.button.innerHTML=content}setButtonDisabled(disabled){disabled?this.button.setAttribute("disabled","disabled"):this.button.removeAttribute("disabled")}getElement(){return this.element}}_exports.DropdownDialog=DropdownDialog;const getDropdownDialog=selector=>{const dropdownElement=document.querySelector(selector);return dropdownElement?new DropdownDialog(dropdownElement):null};_exports.getDropdownDialog=getDropdownDialog;_exports.init=selector=>{const dropdown=getDropdownDialog(selector);if(!dropdown)throw new Error("Dopdown dialog element not found: ".concat(selector));dropdown.init()}}));
//# sourceMappingURL=dialog.min.js.map
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -11,6 +11,6 @@ define("core/local/dropdown/status",["exports","core/local/dropdown/dialog"],(fu
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const Selectors_checkedIcon='[data-for="checkedIcon"]',Selectors_option='[role="option"]',Selectors_optionItem="[data-optionnumber]",Selectors_optionIcon=".option-icon",Selectors_selectedOption='[role="option"][aria-selected="true"]',Selectors_uncheckedIcon='[data-for="uncheckedIcon"]',Classes_selected="selected",Classes_disabled="disabled",Classes_hidden="d-none";class DropdownStatus extends _dialog.DropdownDialog{constructor(element){super(element),this.buttonSync="true"==element.dataset.buttonSync,this.updateStatus="true"==element.dataset.updateStatus}init(){super.init(),this.element.dataset.dropdownStatusInitialized||(this.panel.addEventListener("click",this._contentClickHandler.bind(this)),"true"==this.element.dataset.buttonSync&&this.setButtonSyncEnabled(!0),"true"==this.element.dataset.updateStatus&&this.setUpdateStatusEnabled(!0),this.element.dataset.dropdownStatusInitialized=!0)}_contentClickHandler(event){const option=event.target.closest(Selectors_option);option&&"true"!==option.getAttribute("aria-disabled")&&"true"!==option.getAttribute("aria-selected")&&this.isUpdateStatusEnabled()&&this.setSelectedValue(option.dataset.value)}setSelectedValue(value){const selected=this.panel.querySelector(Selectors_selectedOption);if(selected&&selected.dataset.value===value)return;selected&&this._updateOptionChecked(selected,!1);const option=this.panel.querySelector("".concat(Selectors_option,'[data-value="').concat(value,'"]'));option&&this._updateOptionChecked(option,!0),this.isButtonSyncEnabled()&&this.syncButtonText(),this.element.dispatchEvent(new Event("change"))}_updateOptionChecked(option,checked){option.setAttribute("aria-selected",checked.toString()),option.classList.toggle(Classes_selected,checked),option.classList.toggle(Classes_disabled,checked);const optionItem=option.closest(Selectors_optionItem);optionItem&&this._updateOptionItemChecked(optionItem,checked),checked?this.element.dataset.value=option.dataset.value:this.element.dataset.value===option.dataset.value&&delete this.element.dataset.value}_updateOptionItemChecked(optionItem,checked){var _optionItem$dataset$s;const selectedClasses=null!==(_optionItem$dataset$s=optionItem.dataset.selectedClasses)&&void 0!==_optionItem$dataset$s?_optionItem$dataset$s:Classes_selected;for(const selectedClass of selectedClasses.split(" "))optionItem.classList.toggle(selectedClass,checked);checked?optionItem.dataset.selected=checked:null==optionItem||delete optionItem.dataset.selected;const checkedIcon=optionItem.querySelector(Selectors_checkedIcon);checkedIcon&&checkedIcon.classList.toggle(Classes_hidden,!checked);const uncheckedIcon=optionItem.querySelector(Selectors_uncheckedIcon);uncheckedIcon&&uncheckedIcon.classList.toggle(Classes_hidden,checked)}getSelectedValue(){var _selected$dataset$val;const selected=this.panel.querySelector(Selectors_selectedOption);return null!==(_selected$dataset$val=null==selected?void 0:selected.dataset.value)&&void 0!==_selected$dataset$val?_selected$dataset$val:null}setButtonSyncEnabled(value){value?this.element.dataset.buttonSync="true":delete this.element.dataset.buttonSync,value&&this.syncButtonText()}isButtonSyncEnabled(){return"true"==this.element.dataset.buttonSync}syncButtonText(){const selected=this.panel.querySelector(Selectors_selectedOption);if(!selected)return;let newText=selected.textContent;const optionIcon=this._getOptionIcon(selected);optionIcon&&(newText=optionIcon.innerHTML+newText),this.button.innerHTML=newText}setUpdateStatusEnabled(value){value?this.element.dataset.updateStatus="true":delete this.element.dataset.updateStatus}isUpdateStatusEnabled(){return"true"==this.element.dataset.updateStatus}_getOptionIcon(option){const optionItem=option.closest(Selectors_optionItem);return optionItem?optionItem.querySelector(Selectors_optionIcon):null}}_exports.DropdownStatus=DropdownStatus;const getDropdownStatus=selector=>{const dropdownElement=document.querySelector(selector);return dropdownElement?new DropdownStatus(dropdownElement):null};_exports.getDropdownStatus=getDropdownStatus;_exports.init=selector=>{const dropdown=getDropdownStatus(selector);if(!dropdown)throw new Error("Dopdown status element not found: ".concat(selector));dropdown.init()}}));
const Selectors_checkedIcon='[data-for="checkedIcon"]',Selectors_option='[role="option"]',Selectors_optionItem="[data-optionnumber]",Selectors_optionIcon=".option-icon",Selectors_selectedOption='[role="option"][aria-selected="true"]',Selectors_uncheckedIcon='[data-for="uncheckedIcon"]',Classes_selected="selected",Classes_disabled="disabled",Classes_hidden="d-none";class DropdownStatus extends _dialog.DropdownDialog{constructor(element){super(element),this.buttonSync="true"==element.dataset.buttonSync,this.updateStatus="true"==element.dataset.updateStatus}init(){super.init(),this.element.dataset.dropdownStatusInitialized||(this.panel.addEventListener("click",this._contentClickHandler.bind(this)),"true"==this.element.dataset.buttonSync&&this.setButtonSyncEnabled(!0),"true"==this.element.dataset.updateStatus&&this.setUpdateStatusEnabled(!0),this.element.dataset.dropdownStatusInitialized=!0)}_contentClickHandler(event){const option=event.target.closest(Selectors_option);option&&"true"!==option.getAttribute("aria-disabled")&&"true"!==option.getAttribute("aria-selected")&&this.isUpdateStatusEnabled()&&(this.setSelectedValue(option.dataset.value),this.button.focus())}setSelectedValue(value){const selected=this.panel.querySelector(Selectors_selectedOption);if(selected&&selected.dataset.value===value)return;selected&&this._updateOptionChecked(selected,!1);const option=this.panel.querySelector("".concat(Selectors_option,'[data-value="').concat(value,'"]'));option&&this._updateOptionChecked(option,!0),this.isButtonSyncEnabled()&&this.syncButtonText(),this.element.dispatchEvent(new Event("change"))}_updateOptionChecked(option,checked){option.setAttribute("aria-selected",checked.toString()),option.classList.toggle(Classes_selected,checked),option.classList.toggle(Classes_disabled,checked);const optionItem=option.closest(Selectors_optionItem);optionItem&&this._updateOptionItemChecked(optionItem,checked),checked?this.element.dataset.value=option.dataset.value:this.element.dataset.value===option.dataset.value&&delete this.element.dataset.value}_updateOptionItemChecked(optionItem,checked){var _optionItem$dataset$s;const selectedClasses=null!==(_optionItem$dataset$s=optionItem.dataset.selectedClasses)&&void 0!==_optionItem$dataset$s?_optionItem$dataset$s:Classes_selected;for(const selectedClass of selectedClasses.split(" "))optionItem.classList.toggle(selectedClass,checked);checked?optionItem.dataset.selected=checked:null==optionItem||delete optionItem.dataset.selected;const checkedIcon=optionItem.querySelector(Selectors_checkedIcon);checkedIcon&&checkedIcon.classList.toggle(Classes_hidden,!checked);const uncheckedIcon=optionItem.querySelector(Selectors_uncheckedIcon);uncheckedIcon&&uncheckedIcon.classList.toggle(Classes_hidden,checked)}getSelectedValue(){var _selected$dataset$val;const selected=this.panel.querySelector(Selectors_selectedOption);return null!==(_selected$dataset$val=null==selected?void 0:selected.dataset.value)&&void 0!==_selected$dataset$val?_selected$dataset$val:null}setButtonSyncEnabled(value){value?this.element.dataset.buttonSync="true":delete this.element.dataset.buttonSync,value&&this.syncButtonText()}isButtonSyncEnabled(){return"true"==this.element.dataset.buttonSync}syncButtonText(){const selected=this.panel.querySelector(Selectors_selectedOption);if(!selected)return;let newText=selected.textContent;const optionIcon=this._getOptionIcon(selected);optionIcon&&(newText=optionIcon.innerHTML+newText),this.button.innerHTML=newText}setUpdateStatusEnabled(value){value?this.element.dataset.updateStatus="true":delete this.element.dataset.updateStatus}isUpdateStatusEnabled(){return"true"==this.element.dataset.updateStatus}_getOptionIcon(option){const optionItem=option.closest(Selectors_optionItem);return optionItem?optionItem.querySelector(Selectors_optionIcon):null}}_exports.DropdownStatus=DropdownStatus;const getDropdownStatus=selector=>{const dropdownElement=document.querySelector(selector);return dropdownElement?new DropdownStatus(dropdownElement):null};_exports.getDropdownStatus=getDropdownStatus;_exports.init=selector=>{const dropdown=getDropdownStatus(selector);if(!dropdown)throw new Error("Dopdown status element not found: ".concat(selector));dropdown.init()}}));
//# sourceMappingURL=status.min.js.map
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -35,6 +35,7 @@ import {
hide,
unhide,
} from 'core/aria';
import EventHandler from 'theme_boost/bootstrap/dom/event-handler';
const Selectors = {
mainMenu: '[role="menu"]',
@@ -134,7 +135,10 @@ class SubPanel {
this.element.addEventListener('focusin', this._mainElementFocusInHandler.bind(this));
// Menu Item events.
this.menuItem.addEventListener('click', this._menuItemClickHandler.bind(this));
this.menuItem.addEventListener('keydown', this._menuItemKeyHandler.bind(this));
// Use the Bootstrap key handler for the menu item key handler.
// This will avoid Boostrap Dropdown handler to prevent the propagation to the subpanel.
const subpanelMenuItemSelector = `#${this.element.id}${Selectors.subPanelMenuItem}`;
EventHandler.on(document, 'keydown', subpanelMenuItemSelector, this._menuItemKeyHandler.bind(this));
if (!isBehatSite()) {
// Behat in Chrome usually move the mouse over the page when trying clicking a subpanel element.
// If the menu has more than one subpanel this could cause closing the subpanel by mistake.
+7 -2
View File
@@ -29,6 +29,7 @@ import {
nextFocusableElement,
} from 'core/pagehelpers';
import Pending from 'core/pending';
import EventHandler from 'theme_boost/bootstrap/dom/event-handler';
const Selectors = {
dropdownButton: '[data-for="dropdowndialog_button"]',
@@ -60,10 +61,14 @@ export class DropdownDialog {
return;
}
// Use the Bootstrap key handler for the Dropdown button key handler.
// This will avoid Boostrap Dropdown handler to prevent the propagation to the dialog.
// Menu Item events.
this.button.addEventListener('keydown', this._buttonKeyHandler.bind(this));
const dialogButtonSelector = `#${this.element.id} ${Selectors.dropdownButton}`;
EventHandler.on(document, 'keydown', dialogButtonSelector, this._buttonKeyHandler.bind(this));
// Subpanel content events.
this.panel.addEventListener('keydown', this._contentKeyHandler.bind(this));
const dialogSelector = `#${this.element.id} ${Selectors.dropdownDialog}`;
EventHandler.on(document, 'keydown', dialogSelector, this._contentKeyHandler.bind(this));
this.element.dataset.dropdownDialogInitialized = true;
}
+1
View File
@@ -101,6 +101,7 @@ export class DropdownStatus extends DropdownDialog {
}
if (this.isUpdateStatusEnabled()) {
this.setSelectedValue(option.dataset.value);
this.button.focus();
}
}
@@ -57,7 +57,7 @@
{{/itemicon}}
<span class="menu-action-text" id="actionmenuactionsubpanel-{{instance}}">{{{text}}}</span>
</a>
<div class="dropdown-menu dropdown-subpanel-content" role="menu">
<div class="dropdown-menu dropdown-subpanel-content" role="menu" data-bs-popper="static">
{{{subpanelcontent}}}
</div>
</div>