From 02de5b79fd623b2029efb4b054028a4187f24bd2 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Mon, 13 Nov 2023 14:36:23 +0100 Subject: [PATCH] MDL-79985 output: prepare dropdown components for forms Add new features to the dropdown components (dialog and status) needed for using them in quick forms. Now the dialog dropdown have methods to disable the dropdown button (to disable the field). The status dropdown has one minor style bugfix and the selected item styles are now stored as a data attribute. --- .../content/moodle/components/dropdowns.md | 1 + lib/amd/build/local/dropdown/dialog.min.js | 2 +- lib/amd/build/local/dropdown/dialog.min.js.map | 2 +- lib/amd/build/local/dropdown/status.min.js | 4 ++-- lib/amd/build/local/dropdown/status.min.js.map | 2 +- lib/amd/src/local/dropdown/dialog.js | 12 ++++++++++++ lib/amd/src/local/dropdown/status.js | 9 +++++---- lib/classes/output/local/dropdown/dialog.php | 16 ++++++++++++++++ lib/templates/local/choicelist/option.mustache | 1 + lib/templates/local/dropdown/dialog.mustache | 2 ++ 10 files changed, 42 insertions(+), 9 deletions(-) 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 }}}