diff --git a/admin/tool/componentlibrary/content/moodle/components/dropdowns.md b/admin/tool/componentlibrary/content/moodle/components/dropdowns.md index ca2acb9297f..d1777a7b07e 100644 --- a/admin/tool/componentlibrary/content/moodle/components/dropdowns.md +++ b/admin/tool/componentlibrary/content/moodle/components/dropdowns.md @@ -232,6 +232,7 @@ Both classes provide the following methods: - `setVisible(Boolean)` to open or close the dropdown. - `isVisible()` to know if it is open or closed. - `setButtonContent(String)` to replace the button content. +- `setButtonDisabled(Boolean)` to disable or enable the dropdown button. - `getElement()`to get the main HTMLElement to add eventListeners. The following example uses the module to open the dropdown when an extra button is preset: diff --git a/lib/amd/build/local/dropdown/dialog.min.js b/lib/amd/build/local/dropdown/dialog.min.js index 5d62574de2b..3b3b2542fd4 100644 --- a/lib/amd/build/local/dropdown/dialog.min.js +++ b/lib/amd/build/local/dropdown/dialog.min.js @@ -5,6 +5,6 @@ define("core/local/dropdown/dialog",["exports","jquery","core/pagehelpers","core * @module core/local/dropdown/dialog * @copyright 2023 Ferran Recio * @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,_jquery=_interopRequireDefault(_jquery),_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()&&(0,_jquery.default)(this.button).dropdown("toggle")}isVisible(){return"true"===this.button.getAttribute("aria-expanded")}setButtonContent(content){this.button.innerHTML=content}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,_jquery=_interopRequireDefault(_jquery),_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()&&(0,_jquery.default)(this.button).dropdown("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 \ No newline at end of file diff --git a/lib/amd/build/local/dropdown/dialog.min.js.map b/lib/amd/build/local/dropdown/dialog.min.js.map index 34ac0e790b8..2174c517b7f 100644 --- a/lib/amd/build/local/dropdown/dialog.min.js.map +++ b/lib/amd/build/local/dropdown/dialog.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dialog.min.js","sources":["../../../src/local/dropdown/dialog.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Dropdown status JS controls.\n *\n * @module core/local/dropdown/dialog\n * @copyright 2023 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n// The jQuery module is only used for interacting with Bootstrap 4. It can be removed when MDL-71979 is integrated.\nimport jQuery from 'jquery';\nimport {\n firstFocusableElement,\n lastFocusableElement,\n previousFocusableElement,\n nextFocusableElement,\n} from 'core/pagehelpers';\nimport Pending from 'core/pending';\n\nconst Selectors = {\n dropdownButton: '[data-for=\"dropdowndialog_button\"]',\n dropdownDialog: '[data-for=\"dropdowndialog_dialog\"]',\n};\n\n/**\n * Dropdown dialog class.\n * @private\n */\nexport class DropdownDialog {\n /**\n * Constructor.\n * @param {HTMLElement} element The element to initialize.\n */\n constructor(element) {\n this.element = element;\n this.button = element.querySelector(Selectors.dropdownButton);\n this.panel = element.querySelector(Selectors.dropdownDialog);\n }\n\n /**\n * Initialize the subpanel element.\n *\n * This method adds the event listeners to the subpanel and the position classes.\n */\n init() {\n if (this.element.dataset.dropdownDialogInitialized) {\n return;\n }\n\n // Menu Item events.\n this.button.addEventListener('keydown', this._buttonKeyHandler.bind(this));\n // Subpanel content events.\n this.panel.addEventListener('keydown', this._contentKeyHandler.bind(this));\n\n this.element.dataset.dropdownDialogInitialized = true;\n }\n\n /**\n * Dropdown button key handler.\n * @param {Event} event\n * @private\n */\n _buttonKeyHandler(event) {\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n event.stopPropagation();\n event.preventDefault();\n this.setVisible(false);\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n event.stopPropagation();\n event.preventDefault();\n this.setVisible(true);\n this._focusPanelContent();\n }\n }\n\n /**\n * Sub panel content key handler.\n * @param {Event} event\n * @private\n */\n _contentKeyHandler(event) {\n let newFocus = null;\n\n if (event.key === 'End') {\n newFocus = lastFocusableElement(this.panel);\n }\n if (event.key === 'Home') {\n newFocus = firstFocusableElement(this.panel);\n }\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n newFocus = previousFocusableElement(this.panel, false);\n if (!newFocus) {\n newFocus = this.button;\n }\n }\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n newFocus = nextFocusableElement(this.panel, false);\n }\n if (newFocus !== null) {\n event.stopPropagation();\n event.preventDefault();\n newFocus.focus();\n }\n }\n\n /**\n * Focus on the first focusable element of the subpanel.\n * @private\n */\n _focusPanelContent() {\n const pendingPromise = new Pending('core/dropdown/dialog:focuscontent');\n // Some Bootstrap events are triggered after the click event.\n // To prevent this from affecting the focus we wait a bit.\n setTimeout(() => {\n const firstFocusable = firstFocusableElement(this.panel);\n if (firstFocusable) {\n firstFocusable.focus();\n }\n pendingPromise.resolve();\n }, 100);\n }\n\n /**\n * Set the visibility of a subpanel.\n * @param {Boolean} visible true if the subpanel should be visible.\n */\n setVisible(visible) {\n if (visible === this.isVisible()) {\n return;\n }\n // All jQuery in this code can be replaced when MDL-71979 is integrated.\n jQuery(this.button).dropdown('toggle');\n }\n\n /**\n * Get the visibility of a subpanel.\n * @returns {Boolean} true if the subpanel is visible.\n */\n isVisible() {\n return this.button.getAttribute('aria-expanded') === 'true';\n }\n\n /**\n * Set the content of the button.\n * @param {String} content\n */\n setButtonContent(content) {\n this.button.innerHTML = content;\n }\n\n /**\n * Return the main dropdown HTML element.\n * @returns {HTMLElement} The element.\n */\n getElement() {\n return this.element;\n }\n}\n\n/**\n * Get the dropdown dialog instance from a selector.\n * @param {string} selector The query selector to init.\n * @returns {DropdownDialog|null} The dropdown dialog instance if any.\n */\nexport const getDropdownDialog = (selector) => {\n const dropdownElement = document.querySelector(selector);\n if (!dropdownElement) {\n return null;\n }\n return new DropdownDialog(dropdownElement);\n};\n\n/**\n * Initialize module.\n *\n * @method\n * @param {string} selector The query selector to init.\n */\nexport const init = (selector) => {\n const dropdown = getDropdownDialog(selector);\n if (!dropdown) {\n throw new Error(`Dopdown dialog element not found: ${selector}`);\n }\n dropdown.init();\n};\n"],"names":["Selectors","DropdownDialog","constructor","element","button","querySelector","panel","init","this","dataset","dropdownDialogInitialized","addEventListener","_buttonKeyHandler","bind","_contentKeyHandler","event","key","stopPropagation","preventDefault","setVisible","_focusPanelContent","newFocus","focus","pendingPromise","Pending","setTimeout","firstFocusable","resolve","visible","isVisible","dropdown","getAttribute","setButtonContent","content","innerHTML","getElement","getDropdownDialog","selector","dropdownElement","document","Error"],"mappings":";;;;;;;6NAiCMA,yBACc,qCADdA,yBAEc,2CAOPC,eAKTC,YAAYC,cACHA,QAAUA,aACVC,OAASD,QAAQE,cAAcL,+BAC/BM,MAAQH,QAAQE,cAAcL,0BAQvCO,OACQC,KAAKL,QAAQM,QAAQC,iCAKpBN,OAAOO,iBAAiB,UAAWH,KAAKI,kBAAkBC,KAAKL,YAE/DF,MAAMK,iBAAiB,UAAWH,KAAKM,mBAAmBD,KAAKL,YAE/DL,QAAQM,QAAQC,2BAA4B,GAQrDE,kBAAkBG,UACI,YAAdA,MAAMC,KAAmC,cAAdD,MAAMC,WACjCD,MAAME,kBACNF,MAAMG,2BACDC,YAAW,GAIF,cAAdJ,MAAMC,KAAqC,eAAdD,MAAMC,MACnCD,MAAME,kBACNF,MAAMG,sBACDC,YAAW,QACXC,sBASbN,mBAAmBC,WACXM,SAAW,KAEG,QAAdN,MAAMC,MACNK,UAAW,qCAAqBb,KAAKF,QAEvB,SAAdS,MAAMC,MACNK,UAAW,sCAAsBb,KAAKF,QAExB,YAAdS,MAAMC,KAAmC,cAAdD,MAAMC,MACjCK,UAAW,yCAAyBb,KAAKF,OAAO,GAC3Ce,WACDA,SAAWb,KAAKJ,SAGN,cAAdW,MAAMC,KAAqC,eAAdD,MAAMC,MACnCK,UAAW,qCAAqBb,KAAKF,OAAO,IAE/B,OAAbe,WACAN,MAAME,kBACNF,MAAMG,iBACNG,SAASC,SAQjBF,2BACUG,eAAiB,IAAIC,iBAAQ,qCAGnCC,YAAW,WACDC,gBAAiB,sCAAsBlB,KAAKF,OAC9CoB,gBACAA,eAAeJ,QAEnBC,eAAeI,YAChB,KAOPR,WAAWS,SACHA,UAAYpB,KAAKqB,iCAIdrB,KAAKJ,QAAQ0B,SAAS,UAOjCD,kBACyD,SAA9CrB,KAAKJ,OAAO2B,aAAa,iBAOpCC,iBAAiBC,cACR7B,OAAO8B,UAAYD,QAO5BE,oBACW3B,KAAKL,sDASPiC,kBAAqBC,iBACxBC,gBAAkBC,SAASlC,cAAcgC,iBAC1CC,gBAGE,IAAIrC,eAAeqC,iBAFf,iEAWMD,iBACXP,SAAWM,kBAAkBC,cAC9BP,eACK,IAAIU,kDAA2CH,WAEzDP,SAASvB"} \ No newline at end of file +{"version":3,"file":"dialog.min.js","sources":["../../../src/local/dropdown/dialog.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Dropdown status JS controls.\n *\n * @module core/local/dropdown/dialog\n * @copyright 2023 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n// The jQuery module is only used for interacting with Bootstrap 4. It can be removed when MDL-71979 is integrated.\nimport jQuery from 'jquery';\nimport {\n firstFocusableElement,\n lastFocusableElement,\n previousFocusableElement,\n nextFocusableElement,\n} from 'core/pagehelpers';\nimport Pending from 'core/pending';\n\nconst Selectors = {\n dropdownButton: '[data-for=\"dropdowndialog_button\"]',\n dropdownDialog: '[data-for=\"dropdowndialog_dialog\"]',\n};\n\n/**\n * Dropdown dialog class.\n * @private\n */\nexport class DropdownDialog {\n /**\n * Constructor.\n * @param {HTMLElement} element The element to initialize.\n */\n constructor(element) {\n this.element = element;\n this.button = element.querySelector(Selectors.dropdownButton);\n this.panel = element.querySelector(Selectors.dropdownDialog);\n }\n\n /**\n * Initialize the subpanel element.\n *\n * This method adds the event listeners to the subpanel and the position classes.\n */\n init() {\n if (this.element.dataset.dropdownDialogInitialized) {\n return;\n }\n\n // Menu Item events.\n this.button.addEventListener('keydown', this._buttonKeyHandler.bind(this));\n // Subpanel content events.\n this.panel.addEventListener('keydown', this._contentKeyHandler.bind(this));\n\n this.element.dataset.dropdownDialogInitialized = true;\n }\n\n /**\n * Dropdown button key handler.\n * @param {Event} event\n * @private\n */\n _buttonKeyHandler(event) {\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n event.stopPropagation();\n event.preventDefault();\n this.setVisible(false);\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n event.stopPropagation();\n event.preventDefault();\n this.setVisible(true);\n this._focusPanelContent();\n }\n }\n\n /**\n * Sub panel content key handler.\n * @param {Event} event\n * @private\n */\n _contentKeyHandler(event) {\n let newFocus = null;\n\n if (event.key === 'End') {\n newFocus = lastFocusableElement(this.panel);\n }\n if (event.key === 'Home') {\n newFocus = firstFocusableElement(this.panel);\n }\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n newFocus = previousFocusableElement(this.panel, false);\n if (!newFocus) {\n newFocus = this.button;\n }\n }\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n newFocus = nextFocusableElement(this.panel, false);\n }\n if (newFocus !== null) {\n event.stopPropagation();\n event.preventDefault();\n newFocus.focus();\n }\n }\n\n /**\n * Focus on the first focusable element of the subpanel.\n * @private\n */\n _focusPanelContent() {\n const pendingPromise = new Pending('core/dropdown/dialog:focuscontent');\n // Some Bootstrap events are triggered after the click event.\n // To prevent this from affecting the focus we wait a bit.\n setTimeout(() => {\n const firstFocusable = firstFocusableElement(this.panel);\n if (firstFocusable) {\n firstFocusable.focus();\n }\n pendingPromise.resolve();\n }, 100);\n }\n\n /**\n * Set the visibility of a subpanel.\n * @param {Boolean} visible true if the subpanel should be visible.\n */\n setVisible(visible) {\n if (visible === this.isVisible()) {\n return;\n }\n // All jQuery in this code can be replaced when MDL-71979 is integrated.\n jQuery(this.button).dropdown('toggle');\n }\n\n /**\n * Get the visibility of a subpanel.\n * @returns {Boolean} true if the subpanel is visible.\n */\n isVisible() {\n return this.button.getAttribute('aria-expanded') === 'true';\n }\n\n /**\n * Set the content of the button.\n * @param {String} content\n */\n setButtonContent(content) {\n this.button.innerHTML = content;\n }\n\n /**\n * Set the disabled state of the button.\n * @param {Boolean} disabled\n */\n setButtonDisabled(disabled) {\n if (disabled) {\n this.button.setAttribute('disabled', 'disabled');\n } else {\n this.button.removeAttribute('disabled');\n }\n }\n\n /**\n * Return the main dropdown HTML element.\n * @returns {HTMLElement} The element.\n */\n getElement() {\n return this.element;\n }\n}\n\n/**\n * Get the dropdown dialog instance from a selector.\n * @param {string} selector The query selector to init.\n * @returns {DropdownDialog|null} The dropdown dialog instance if any.\n */\nexport const getDropdownDialog = (selector) => {\n const dropdownElement = document.querySelector(selector);\n if (!dropdownElement) {\n return null;\n }\n return new DropdownDialog(dropdownElement);\n};\n\n/**\n * Initialize module.\n *\n * @method\n * @param {string} selector The query selector to init.\n */\nexport const init = (selector) => {\n const dropdown = getDropdownDialog(selector);\n if (!dropdown) {\n throw new Error(`Dopdown dialog element not found: ${selector}`);\n }\n dropdown.init();\n};\n"],"names":["Selectors","DropdownDialog","constructor","element","button","querySelector","panel","init","this","dataset","dropdownDialogInitialized","addEventListener","_buttonKeyHandler","bind","_contentKeyHandler","event","key","stopPropagation","preventDefault","setVisible","_focusPanelContent","newFocus","focus","pendingPromise","Pending","setTimeout","firstFocusable","resolve","visible","isVisible","dropdown","getAttribute","setButtonContent","content","innerHTML","setButtonDisabled","disabled","setAttribute","removeAttribute","getElement","getDropdownDialog","selector","dropdownElement","document","Error"],"mappings":";;;;;;;6NAiCMA,yBACc,qCADdA,yBAEc,2CAOPC,eAKTC,YAAYC,cACHA,QAAUA,aACVC,OAASD,QAAQE,cAAcL,+BAC/BM,MAAQH,QAAQE,cAAcL,0BAQvCO,OACQC,KAAKL,QAAQM,QAAQC,iCAKpBN,OAAOO,iBAAiB,UAAWH,KAAKI,kBAAkBC,KAAKL,YAE/DF,MAAMK,iBAAiB,UAAWH,KAAKM,mBAAmBD,KAAKL,YAE/DL,QAAQM,QAAQC,2BAA4B,GAQrDE,kBAAkBG,UACI,YAAdA,MAAMC,KAAmC,cAAdD,MAAMC,WACjCD,MAAME,kBACNF,MAAMG,2BACDC,YAAW,GAIF,cAAdJ,MAAMC,KAAqC,eAAdD,MAAMC,MACnCD,MAAME,kBACNF,MAAMG,sBACDC,YAAW,QACXC,sBASbN,mBAAmBC,WACXM,SAAW,KAEG,QAAdN,MAAMC,MACNK,UAAW,qCAAqBb,KAAKF,QAEvB,SAAdS,MAAMC,MACNK,UAAW,sCAAsBb,KAAKF,QAExB,YAAdS,MAAMC,KAAmC,cAAdD,MAAMC,MACjCK,UAAW,yCAAyBb,KAAKF,OAAO,GAC3Ce,WACDA,SAAWb,KAAKJ,SAGN,cAAdW,MAAMC,KAAqC,eAAdD,MAAMC,MACnCK,UAAW,qCAAqBb,KAAKF,OAAO,IAE/B,OAAbe,WACAN,MAAME,kBACNF,MAAMG,iBACNG,SAASC,SAQjBF,2BACUG,eAAiB,IAAIC,iBAAQ,qCAGnCC,YAAW,WACDC,gBAAiB,sCAAsBlB,KAAKF,OAC9CoB,gBACAA,eAAeJ,QAEnBC,eAAeI,YAChB,KAOPR,WAAWS,SACHA,UAAYpB,KAAKqB,iCAIdrB,KAAKJ,QAAQ0B,SAAS,UAOjCD,kBACyD,SAA9CrB,KAAKJ,OAAO2B,aAAa,iBAOpCC,iBAAiBC,cACR7B,OAAO8B,UAAYD,QAO5BE,kBAAkBC,UACVA,cACKhC,OAAOiC,aAAa,WAAY,iBAEhCjC,OAAOkC,gBAAgB,YAQpCC,oBACW/B,KAAKL,sDASPqC,kBAAqBC,iBACxBC,gBAAkBC,SAAStC,cAAcoC,iBAC1CC,gBAGE,IAAIzC,eAAeyC,iBAFf,iEAWMD,iBACXX,SAAWU,kBAAkBC,cAC9BX,eACK,IAAIc,kDAA2CH,WAEzDX,SAASvB"} \ No newline at end of file diff --git a/lib/amd/build/local/dropdown/status.min.js b/lib/amd/build/local/dropdown/status.min.js index 156ced705c6..bd0b50cbb52 100644 --- a/lib/amd/build/local/dropdown/status.min.js +++ b/lib/amd/build/local/dropdown/status.min.js @@ -7,10 +7,10 @@ define("core/local/dropdown/status",["exports","core/local/dropdown/dialog"],(fu * - Update the status of the button when the selected option changes. This will * trigger a "change" event when the status changes. * - * @module core/local/dropdown/dialog + * @module core/local/dropdown/status * @copyright 2023 Ferran Recio * @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_selectedBg="bg-light",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){optionItem.classList.toggle(Classes_selectedBg,checked),optionItem.classList.toggle(Classes_selected,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)}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 \ No newline at end of file diff --git a/lib/amd/build/local/dropdown/status.min.js.map b/lib/amd/build/local/dropdown/status.min.js.map index 003451b4c23..3b4e616baff 100644 --- a/lib/amd/build/local/dropdown/status.min.js.map +++ b/lib/amd/build/local/dropdown/status.min.js.map @@ -1 +1 @@ -{"version":3,"file":"status.min.js","sources":["../../../src/local/dropdown/status.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Dropdown status JS controls.\n *\n * The status controls enable extra configurarions for the dropdown like:\n * - Sync the button text with the selected option.\n * - Update the status of the button when the selected option changes. This will\n * trigger a \"change\" event when the status changes.\n *\n * @module core/local/dropdown/dialog\n * @copyright 2023 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {DropdownDialog} from 'core/local/dropdown/dialog';\n\nconst Selectors = {\n checkedIcon: '[data-for=\"checkedIcon\"]',\n option: '[role=\"option\"]',\n optionItem: '[data-optionnumber]',\n optionIcon: '.option-icon',\n selectedOption: '[role=\"option\"][aria-selected=\"true\"]',\n uncheckedIcon: '[data-for=\"uncheckedIcon\"]',\n};\n\nconst Classes = {\n selectedBg: 'bg-light',\n selected: 'selected',\n disabled: 'disabled',\n hidden: 'd-none',\n};\n\n/**\n * Dropdown dialog class.\n * @private\n */\nexport class DropdownStatus extends DropdownDialog {\n /**\n * Constructor.\n * @param {HTMLElement} element The element to initialize.\n */\n constructor(element) {\n super(element);\n this.buttonSync = element.dataset.buttonSync == 'true';\n this.updateStatus = element.dataset.updateStatus == 'true';\n }\n\n /**\n * Initialize the subpanel element.\n *\n * This method adds the event listeners to the subpanel and the position classes.\n * @private\n */\n init() {\n super.init();\n\n if (this.element.dataset.dropdownStatusInitialized) {\n return;\n }\n\n this.panel.addEventListener('click', this._contentClickHandler.bind(this));\n\n if (this.element.dataset.buttonSync == 'true') {\n this.setButtonSyncEnabled(true);\n }\n if (this.element.dataset.updateStatus == 'true') {\n this.setUpdateStatusEnabled(true);\n }\n\n this.element.dataset.dropdownStatusInitialized = true;\n }\n\n /**\n * Handle click events on the status content.\n * @param {Event} event The event.\n * @private\n */\n _contentClickHandler(event) {\n const option = event.target.closest(Selectors.option);\n if (!option) {\n return;\n }\n if (option.getAttribute('aria-disabled') === 'true') {\n return;\n }\n if (option.getAttribute('aria-selected') === 'true') {\n return;\n }\n if (this.isUpdateStatusEnabled()) {\n this.setSelectedValue(option.dataset.value);\n }\n }\n\n /**\n * Sets the selected value.\n * @param {string} value The value to set.\n */\n setSelectedValue(value) {\n const selected = this.panel.querySelector(Selectors.selectedOption);\n if (selected && selected.dataset.value === value) {\n return;\n }\n if (selected) {\n this._updateOptionChecked(selected, false);\n }\n const option = this.panel.querySelector(`${Selectors.option}[data-value=\"${value}\"]`);\n if (option) {\n this._updateOptionChecked(option, true);\n }\n if (this.isButtonSyncEnabled()) {\n this.syncButtonText();\n }\n // Emit standard radio button event with the selected option.\n this.element.dispatchEvent(new Event('change'));\n }\n\n /**\n * Update the option checked content.\n * @private\n * @param {HTMLElement} option the option element to set\n * @param {Boolean} checked the new checked value\n */\n _updateOptionChecked(option, checked) {\n option.setAttribute('aria-selected', checked.toString());\n option.classList.toggle(Classes.selected, checked);\n option.classList.toggle(Classes.disabled, checked);\n\n const optionItem = option.closest(Selectors.optionItem);\n if (optionItem) {\n this._updateOptionItemChecked(optionItem, checked);\n }\n\n if (checked) {\n this.element.dataset.value = option.dataset.value;\n } else if (this.element.dataset.value === option.dataset.value) {\n delete this.element.dataset.value;\n }\n }\n\n /**\n * Update the option item checked content.\n * @private\n * @param {HTMLElement} optionItem\n * @param {Boolean} checked\n */\n _updateOptionItemChecked(optionItem, checked) {\n optionItem.classList.toggle(Classes.selectedBg, checked);\n optionItem.classList.toggle(Classes.selected, checked);\n if (checked) {\n optionItem.dataset.selected = checked;\n } else {\n delete optionItem?.dataset.selected;\n }\n const checkedIcon = optionItem.querySelector(Selectors.checkedIcon);\n if (checkedIcon) {\n checkedIcon.classList.toggle(Classes.hidden, !checked);\n }\n const uncheckedIcon = optionItem.querySelector(Selectors.uncheckedIcon);\n if (uncheckedIcon) {\n uncheckedIcon.classList.toggle(Classes.hidden, checked);\n }\n }\n\n\n /**\n * Return the selected value.\n * @returns {string|null} The selected value.\n */\n getSelectedValue() {\n const selected = this.panel.querySelector(Selectors.selectedOption);\n return selected?.dataset.value ?? null;\n }\n\n /**\n * Set the button sync value.\n *\n * If the sync is enabled, the button text will show the selected option.\n *\n * @param {Boolean} value The value to set.\n */\n setButtonSyncEnabled(value) {\n if (value) {\n this.element.dataset.buttonSync = 'true';\n } else {\n delete this.element.dataset.buttonSync;\n }\n if (value) {\n this.syncButtonText();\n }\n }\n\n /**\n * Return if the button sync is enabled.\n * @returns {Boolean} The button sync value.\n */\n isButtonSyncEnabled() {\n return this.element.dataset.buttonSync == 'true';\n }\n\n /**\n * Sync the button text with the selected option.\n */\n syncButtonText() {\n const selected = this.panel.querySelector(Selectors.selectedOption);\n if (!selected) {\n return;\n }\n let newText = selected.textContent;\n const optionIcon = this._getOptionIcon(selected);\n if (optionIcon) {\n newText = optionIcon.innerHTML + newText;\n }\n this.button.innerHTML = newText;\n }\n\n /**\n * Set the update status value.\n *\n * @param {Boolean} value The value to set.\n */\n setUpdateStatusEnabled(value) {\n if (value) {\n this.element.dataset.updateStatus = 'true';\n } else {\n delete this.element.dataset.updateStatus;\n }\n }\n\n /**\n * Return if the update status is enabled.\n * @returns {Boolean} The update status value.\n */\n isUpdateStatusEnabled() {\n return this.element.dataset.updateStatus == 'true';\n }\n\n _getOptionIcon(option) {\n const optionItem = option.closest(Selectors.optionItem);\n if (!optionItem) {\n return null;\n }\n return optionItem.querySelector(Selectors.optionIcon);\n }\n\n}\n\n/**\n * Get the dropdown dialog instance form a selector.\n * @param {string} selector The query selector to init.\n * @returns {DropdownStatus|null} The dropdown dialog instance if any.\n */\nexport const getDropdownStatus = (selector) => {\n const dropdownElement = document.querySelector(selector);\n if (!dropdownElement) {\n return null;\n }\n return new DropdownStatus(dropdownElement);\n};\n\n/**\n * Initialize module.\n *\n * @method\n * @param {string} selector The query selector to init.\n */\nexport const init = (selector) => {\n const dropdown = getDropdownStatus(selector);\n if (!dropdown) {\n throw new Error(`Dopdown status element not found: ${selector}`);\n }\n dropdown.init();\n};\n"],"names":["Selectors","Classes","DropdownStatus","DropdownDialog","constructor","element","buttonSync","dataset","updateStatus","init","this","dropdownStatusInitialized","panel","addEventListener","_contentClickHandler","bind","setButtonSyncEnabled","setUpdateStatusEnabled","event","option","target","closest","getAttribute","isUpdateStatusEnabled","setSelectedValue","value","selected","querySelector","_updateOptionChecked","isButtonSyncEnabled","syncButtonText","dispatchEvent","Event","checked","setAttribute","toString","classList","toggle","optionItem","_updateOptionItemChecked","checkedIcon","uncheckedIcon","getSelectedValue","newText","textContent","optionIcon","_getOptionIcon","innerHTML","button","getDropdownStatus","selector","dropdownElement","document","dropdown","Error"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"status.min.js","sources":["../../../src/local/dropdown/status.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Dropdown status JS controls.\n *\n * The status controls enable extra configurarions for the dropdown like:\n * - Sync the button text with the selected option.\n * - Update the status of the button when the selected option changes. This will\n * trigger a \"change\" event when the status changes.\n *\n * @module core/local/dropdown/status\n * @copyright 2023 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {DropdownDialog} from 'core/local/dropdown/dialog';\n\nconst Selectors = {\n checkedIcon: '[data-for=\"checkedIcon\"]',\n option: '[role=\"option\"]',\n optionItem: '[data-optionnumber]',\n optionIcon: '.option-icon',\n selectedOption: '[role=\"option\"][aria-selected=\"true\"]',\n uncheckedIcon: '[data-for=\"uncheckedIcon\"]',\n};\n\nconst Classes = {\n selected: 'selected',\n disabled: 'disabled',\n hidden: 'd-none',\n};\n\n/**\n * Dropdown dialog class.\n * @private\n */\nexport class DropdownStatus extends DropdownDialog {\n /**\n * Constructor.\n * @param {HTMLElement} element The element to initialize.\n */\n constructor(element) {\n super(element);\n this.buttonSync = element.dataset.buttonSync == 'true';\n this.updateStatus = element.dataset.updateStatus == 'true';\n }\n\n /**\n * Initialize the subpanel element.\n *\n * This method adds the event listeners to the subpanel and the position classes.\n * @private\n */\n init() {\n super.init();\n\n if (this.element.dataset.dropdownStatusInitialized) {\n return;\n }\n\n this.panel.addEventListener('click', this._contentClickHandler.bind(this));\n\n if (this.element.dataset.buttonSync == 'true') {\n this.setButtonSyncEnabled(true);\n }\n if (this.element.dataset.updateStatus == 'true') {\n this.setUpdateStatusEnabled(true);\n }\n\n this.element.dataset.dropdownStatusInitialized = true;\n }\n\n /**\n * Handle click events on the status content.\n * @param {Event} event The event.\n * @private\n */\n _contentClickHandler(event) {\n const option = event.target.closest(Selectors.option);\n if (!option) {\n return;\n }\n if (option.getAttribute('aria-disabled') === 'true') {\n return;\n }\n if (option.getAttribute('aria-selected') === 'true') {\n return;\n }\n if (this.isUpdateStatusEnabled()) {\n this.setSelectedValue(option.dataset.value);\n }\n }\n\n /**\n * Sets the selected value.\n * @param {string} value The value to set.\n */\n setSelectedValue(value) {\n const selected = this.panel.querySelector(Selectors.selectedOption);\n if (selected && selected.dataset.value === value) {\n return;\n }\n if (selected) {\n this._updateOptionChecked(selected, false);\n }\n const option = this.panel.querySelector(`${Selectors.option}[data-value=\"${value}\"]`);\n if (option) {\n this._updateOptionChecked(option, true);\n }\n if (this.isButtonSyncEnabled()) {\n this.syncButtonText();\n }\n // Emit standard radio button event with the selected option.\n this.element.dispatchEvent(new Event('change'));\n }\n\n /**\n * Update the option checked content.\n * @private\n * @param {HTMLElement} option the option element to set\n * @param {Boolean} checked the new checked value\n */\n _updateOptionChecked(option, checked) {\n option.setAttribute('aria-selected', checked.toString());\n option.classList.toggle(Classes.selected, checked);\n option.classList.toggle(Classes.disabled, checked);\n\n const optionItem = option.closest(Selectors.optionItem);\n if (optionItem) {\n this._updateOptionItemChecked(optionItem, checked);\n }\n\n if (checked) {\n this.element.dataset.value = option.dataset.value;\n } else if (this.element.dataset.value === option.dataset.value) {\n delete this.element.dataset.value;\n }\n }\n\n /**\n * Update the option item checked content.\n * @private\n * @param {HTMLElement} optionItem\n * @param {Boolean} checked\n */\n _updateOptionItemChecked(optionItem, checked) {\n const selectedClasses = optionItem.dataset.selectedClasses ?? Classes.selected;\n for (const selectedClass of selectedClasses.split(' ')) {\n optionItem.classList.toggle(selectedClass, checked);\n }\n if (checked) {\n optionItem.dataset.selected = checked;\n } else {\n delete optionItem?.dataset.selected;\n }\n const checkedIcon = optionItem.querySelector(Selectors.checkedIcon);\n if (checkedIcon) {\n checkedIcon.classList.toggle(Classes.hidden, !checked);\n }\n const uncheckedIcon = optionItem.querySelector(Selectors.uncheckedIcon);\n if (uncheckedIcon) {\n uncheckedIcon.classList.toggle(Classes.hidden, checked);\n }\n }\n\n\n /**\n * Return the selected value.\n * @returns {string|null} The selected value.\n */\n getSelectedValue() {\n const selected = this.panel.querySelector(Selectors.selectedOption);\n return selected?.dataset.value ?? null;\n }\n\n /**\n * Set the button sync value.\n *\n * If the sync is enabled, the button text will show the selected option.\n *\n * @param {Boolean} value The value to set.\n */\n setButtonSyncEnabled(value) {\n if (value) {\n this.element.dataset.buttonSync = 'true';\n } else {\n delete this.element.dataset.buttonSync;\n }\n if (value) {\n this.syncButtonText();\n }\n }\n\n /**\n * Return if the button sync is enabled.\n * @returns {Boolean} The button sync value.\n */\n isButtonSyncEnabled() {\n return this.element.dataset.buttonSync == 'true';\n }\n\n /**\n * Sync the button text with the selected option.\n */\n syncButtonText() {\n const selected = this.panel.querySelector(Selectors.selectedOption);\n if (!selected) {\n return;\n }\n let newText = selected.textContent;\n const optionIcon = this._getOptionIcon(selected);\n if (optionIcon) {\n newText = optionIcon.innerHTML + newText;\n }\n this.button.innerHTML = newText;\n }\n\n /**\n * Set the update status value.\n *\n * @param {Boolean} value The value to set.\n */\n setUpdateStatusEnabled(value) {\n if (value) {\n this.element.dataset.updateStatus = 'true';\n } else {\n delete this.element.dataset.updateStatus;\n }\n }\n\n /**\n * Return if the update status is enabled.\n * @returns {Boolean} The update status value.\n */\n isUpdateStatusEnabled() {\n return this.element.dataset.updateStatus == 'true';\n }\n\n _getOptionIcon(option) {\n const optionItem = option.closest(Selectors.optionItem);\n if (!optionItem) {\n return null;\n }\n return optionItem.querySelector(Selectors.optionIcon);\n }\n\n}\n\n/**\n * Get the dropdown dialog instance form a selector.\n * @param {string} selector The query selector to init.\n * @returns {DropdownStatus|null} The dropdown dialog instance if any.\n */\nexport const getDropdownStatus = (selector) => {\n const dropdownElement = document.querySelector(selector);\n if (!dropdownElement) {\n return null;\n }\n return new DropdownStatus(dropdownElement);\n};\n\n/**\n * Initialize module.\n *\n * @method\n * @param {string} selector The query selector to init.\n */\nexport const init = (selector) => {\n const dropdown = getDropdownStatus(selector);\n if (!dropdown) {\n throw new Error(`Dopdown status element not found: ${selector}`);\n }\n dropdown.init();\n};\n"],"names":["Selectors","Classes","DropdownStatus","DropdownDialog","constructor","element","buttonSync","dataset","updateStatus","init","this","dropdownStatusInitialized","panel","addEventListener","_contentClickHandler","bind","setButtonSyncEnabled","setUpdateStatusEnabled","event","option","target","closest","getAttribute","isUpdateStatusEnabled","setSelectedValue","value","selected","querySelector","_updateOptionChecked","isButtonSyncEnabled","syncButtonText","dispatchEvent","Event","checked","setAttribute","toString","classList","toggle","optionItem","_updateOptionItemChecked","selectedClasses","selectedClass","split","checkedIcon","uncheckedIcon","getSelectedValue","newText","textContent","optionIcon","_getOptionIcon","innerHTML","button","getDropdownStatus","selector","dropdownElement","document","dropdown","Error"],"mappings":"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"} \ No newline at end of file diff --git a/lib/amd/src/local/dropdown/dialog.js b/lib/amd/src/local/dropdown/dialog.js index e4f9efe8bd5..6c2bbb121ac 100644 --- a/lib/amd/src/local/dropdown/dialog.js +++ b/lib/amd/src/local/dropdown/dialog.js @@ -165,6 +165,18 @@ export class DropdownDialog { this.button.innerHTML = content; } + /** + * Set the disabled state of the button. + * @param {Boolean} disabled + */ + setButtonDisabled(disabled) { + if (disabled) { + this.button.setAttribute('disabled', 'disabled'); + } else { + this.button.removeAttribute('disabled'); + } + } + /** * Return the main dropdown HTML element. * @returns {HTMLElement} The element. diff --git a/lib/amd/src/local/dropdown/status.js b/lib/amd/src/local/dropdown/status.js index 98a75d0232b..d23e57da508 100644 --- a/lib/amd/src/local/dropdown/status.js +++ b/lib/amd/src/local/dropdown/status.js @@ -21,7 +21,7 @@ * - Update the status of the button when the selected option changes. This will * trigger a "change" event when the status changes. * - * @module core/local/dropdown/dialog + * @module core/local/dropdown/status * @copyright 2023 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -38,7 +38,6 @@ const Selectors = { }; const Classes = { - selectedBg: 'bg-light', selected: 'selected', disabled: 'disabled', hidden: 'd-none', @@ -158,8 +157,10 @@ export class DropdownStatus extends DropdownDialog { * @param {Boolean} checked */ _updateOptionItemChecked(optionItem, checked) { - optionItem.classList.toggle(Classes.selectedBg, checked); - optionItem.classList.toggle(Classes.selected, checked); + const selectedClasses = optionItem.dataset.selectedClasses ?? Classes.selected; + for (const selectedClass of selectedClasses.split(' ')) { + optionItem.classList.toggle(selectedClass, checked); + } if (checked) { optionItem.dataset.selected = checked; } else { diff --git a/lib/classes/output/local/dropdown/dialog.php b/lib/classes/output/local/dropdown/dialog.php index b95b2bb3b61..5ba38521979 100644 --- a/lib/classes/output/local/dropdown/dialog.php +++ b/lib/classes/output/local/dropdown/dialog.php @@ -80,6 +80,11 @@ class dialog implements named_templatable, renderable { */ protected $extras = []; + /** + * @var bool if the element is disabled. + */ + protected $disabled = false; + /** * Constructor. * @@ -190,6 +195,14 @@ class dialog implements named_templatable, renderable { $this->dropdownposition = $position; } + /** + * Set the dropdown disabled attribute. + * @param bool $disabled the disabled value + */ + public function set_disabled(bool $disabled) { + $this->disabled = $disabled; + } + /** * Export this data so it can be used as the context for a mustache template (core/inplace_editable). * @@ -220,6 +233,9 @@ class dialog implements named_templatable, renderable { 'dialogclasses' => $this->dropdownwidth, 'extras' => $extras, ]; + if ($this->disabled) { + $data['disabledbutton'] = true; + } // Bootstrap 4 dropdown position still uses left and right literals. $data["position"] = $this->dropdownposition; if (right_to_left()) { diff --git a/lib/templates/local/choicelist/option.mustache b/lib/templates/local/choicelist/option.mustache index b36355ae36f..056cd6338ac 100644 --- a/lib/templates/local/choicelist/option.mustache +++ b/lib/templates/local/choicelist/option.mustache @@ -55,6 +55,7 @@ }} {{#selected}} border bg-primary-light selected {{/selected}}" data-optionnumber="{{optionnumber}}" data-selected="{{selected}}" + data-selected-classes = "border bg-primary-light selected" >
diff --git a/lib/templates/local/dropdown/dialog.mustache b/lib/templates/local/dropdown/dialog.mustache index a73c794282c..38c145be745 100644 --- a/lib/templates/local/dropdown/dialog.mustache +++ b/lib/templates/local/dropdown/dialog.mustache @@ -49,6 +49,7 @@ ], "buttonclasses": "extraclasses", "dialogclasses": "extraclasses", + "disabledbutton": false, "classes": "extraclasses" } }} @@ -78,6 +79,7 @@ aria-haspopup="true" aria-expanded="false" data-for="dropdowndialog_button" + {{#disabledbutton}} disabled {{/disabledbutton}} > {{$ buttoncontent }} {{{ buttoncontent }}}