From 2a65efd9a308506327f963331f93a35ea585bbb0 Mon Sep 17 00:00:00 2001
From: Paul Holden <paulh@moodle.com>
Date: Tue, 27 Aug 2024 15:47:42 +0100
Subject: [PATCH 1/2] MDL-82909 javascript: replace custom loading icon with
 existing module.

---
 lib/amd/build/inplace_editable.min.js     |  2 +-
 lib/amd/build/inplace_editable.min.js.map |  2 +-
 lib/amd/src/inplace_editable.js           | 25 ++++++-----------------
 3 files changed, 8 insertions(+), 21 deletions(-)

diff --git a/lib/amd/build/inplace_editable.min.js b/lib/amd/build/inplace_editable.min.js
index dfe240ecb60..0279b2422c3 100644
--- a/lib/amd/build/inplace_editable.min.js
+++ b/lib/amd/build/inplace_editable.min.js
@@ -12,6 +12,6 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  * @since      3.1
  */
-define("core/inplace_editable",["jquery","core/ajax","core/templates","core/notification","core/str","core/config","core/url","core/form-autocomplete","core/pending","core/local/inplace_editable/events"],(function($,ajax,templates,notification,str,cfg,url,autocomplete,Pending,Events){const updateValue=function(mainelement,value,silent){var pendingId=[mainelement.attr("data-itemid"),mainelement.attr("data-component"),mainelement.attr("data-itemtype")].join("-"),pendingPromise=new Pending(pendingId);addSpinner(mainelement),ajax.call([{methodname:"core_update_inplace_editable",args:{itemid:mainelement.attr("data-itemid"),component:mainelement.attr("data-component"),itemtype:mainelement.attr("data-itemtype"),value:value}}])[0].then((function(data){return templates.render("core/inplace_editable",data).then((function(html,js){var oldvalue=mainelement.attr("data-value"),newelement=$(html);templates.replaceNode(mainelement,newelement,js),silent||newelement.find("[data-inplaceeditablelink]").focus(),Events.notifyElementUpdated(newelement.get(0),data,oldvalue)}))})).then((function(){return pendingPromise.resolve()})).fail((function(ex){var element;(element=mainelement).removeClass("updating"),element.find("img.spinner").hide(),M.util.js_complete(pendingId),Events.notifyElementUpdateFailed(mainelement.get(0),ex,value).defaultPrevented||notification.exception(ex)}))},addSpinner=function(element){element.addClass("updating");var spinner=element.find("img.spinner");spinner.length?spinner.show():(spinner=$("<img/>").attr("src",url.imageUrl("i/loading_small")).addClass("spinner").addClass("smallicon"),element.append(spinner))};return $("body").on("click keypress","[data-inplaceeditable] [data-inplaceeditablelink]",(function(e){if("keypress"!==e.type||13===e.keyCode){var editingEnabledPromise=new Pending("autocomplete-start-editing");e.stopImmediatePropagation(),e.preventDefault();var mainelement=$(this).closest("[data-inplaceeditable]"),turnEditingOff=function(el){el.find("input").off(),el.find("select").off(),el.html(el.attr("data-oldcontent")),el.removeAttr("data-oldcontent"),el.removeClass("inplaceeditingon"),el.find("[data-inplaceeditablelink]").focus(),el.parents('[data-inplace-in-draggable="true"]').attr("draggable",!0).attr("data-inplace-in-draggable",!1)},uniqueId=function(prefix,idlength){var i,uniqid=prefix;for(i=0;i<idlength;i++)uniqid+=String(Math.floor(10*Math.random()));return 0===$("#"+uniqid).length?uniqid:uniqueId(prefix,idlength)};$('[data-inplace-in-draggable="true"]').attr("draggable",!0).attr("data-inplace-in-draggable",!1),$("span.inplaceeditable.inplaceeditingon").each((function(){turnEditingOff($(this))})),function(el){el.addClass("inplaceeditingon"),el.attr("data-oldcontent",el.html());var type=el.attr("data-type"),options=el.attr("data-options");el.parents('[draggable="true"]').attr("data-inplace-in-draggable",!0).attr("draggable",!1),"toggle"===type?function(el,newvalue){turnEditingOff(el),updateValue(el,newvalue)}(el,options):"select"===type?function(el,options){var i,inputelement=$("<select></select>").attr("id",uniqueId("id_inplacevalue_",20)).addClass("custom-select"),lbl=$('<label class="accesshide">'+mainelement.attr("data-editlabel")+"</label>").attr("for",inputelement.attr("id"));for(i in options)inputelement.append($("<option>").attr("value",options[i].key).html(options[i].value));inputelement.val(el.attr("data-value")),el.html("").append(lbl).append(inputelement),inputelement.focus(),inputelement.select(),inputelement.on("keyup change focusout",(function(e){if(!cfg.behatsiterunning||"focusout"!==e.type){if("change"===e.type){var val=inputelement.val();turnEditingOff(el),updateValue(el,val)}("keyup"===e.type&&27===e.keyCode||"focusout"===e.type)&&turnEditingOff(el)}}))}(el,$.parseJSON(options)):"autocomplete"===type?function(el,args){var i,inputelement=$("<select></select>").attr("id",uniqueId("id_inplacevalue_",20)).addClass("form-autocomplete-original-select").addClass("custom-select"),lbl=$('<label class="accesshide">'+mainelement.attr("data-editlabel")+"</label>").attr("for",inputelement.attr("id")),options=args.options,attributes=args.attributes,saveelement=$('<a href="#"></a>'),cancelelement=$('<a href="#"></a>');for(i in options)inputelement.append($("<option>").attr("value",options[i].key).html(options[i].value));attributes.multiple&&inputelement.attr("multiple","true"),inputelement.val(JSON.parse(el.attr("data-value"))),str.get_string("savechanges","core").then((function(s){return templates.renderPix("e/save","core",s)})).then((function(html){saveelement.append(html)})).fail(notification.exception),str.get_string("cancel","core").then((function(s){return templates.renderPix("e/cancel","core",s)})).then((function(html){cancelelement.append(html)})).fail(notification.exception),el.html("").append(lbl).append(inputelement).append(saveelement).append(cancelelement),inputelement.focus(),inputelement.select(),autocomplete.enhance(inputelement,attributes.tags,attributes.ajax,attributes.placeholder,attributes.caseSensitive,attributes.showSuggestions,attributes.noSelectionString).then((function(){el.find("[role=combobox]").focus()})).fail(notification.exception),inputelement.on("keyup",(function(e){("keyup"===e.type&&27===e.keyCode||"focusout"===e.type)&&turnEditingOff(el)})),saveelement.on("click",(function(e){var val=JSON.stringify(inputelement.val());inputelement.empty(),turnEditingOff(el),updateValue(el,val),e.preventDefault()})),cancelelement.on("click",(function(e){inputelement.empty(),turnEditingOff(el),e.preventDefault()}))}(el,$.parseJSON(options)):function(el){str.get_string("edittitleinstructions").done((function(s){var instr=$('<span class="editinstructions">'+s+"</span>").attr("id",uniqueId("id_editinstructions_",20)),inputelement=$('<input type="text"/>').attr("id",uniqueId("id_inplacevalue_",20)).attr("value",el.attr("data-value")).attr("aria-describedby",instr.attr("id")).addClass("ignoredirty").addClass("form-control"),lbl=$('<label class="accesshide">'+mainelement.attr("data-editlabel")+"</label>").attr("for",inputelement.attr("id"));el.html("").append(instr).append(lbl).append(inputelement),inputelement.focus(),inputelement.select(),inputelement.on("keyup keypress focusout",(function(e){if(!cfg.behatsiterunning||"focusout"!==e.type){if("keypress"===e.type&&13===e.keyCode){var val=inputelement.val();turnEditingOff(el),updateValue(el,val)}("keyup"===e.type&&27===e.keyCode||"focusout"===e.type)&&turnEditingOff(el)}}))}))}(el)}(mainelement),editingEnabledPromise.resolve()}})),{getInplaceEditable:function(parent){const element=parent.querySelector("[data-inplaceeditable]");if(element)return{element:element,getValue:function(){return this.element.dataset.value},setValue:function(newvalue){updateValue($(this.element),newvalue,!0)},getItemId:function(){return this.element.dataset.itemid}}}}}));
+define("core/inplace_editable",["jquery","core/ajax","core/templates","core/notification","core/str","core/config","core/url","core/form-autocomplete","core/loadingicon","core/pending","core/local/inplace_editable/events"],(function($,ajax,templates,notification,str,cfg,url,autocomplete,LoadingIcon,Pending,Events){const updateValue=function(mainelement,value,silent){var pendingId=[mainelement.attr("data-itemid"),mainelement.attr("data-component"),mainelement.attr("data-itemtype")].join("-"),pendingPromise=new Pending(pendingId);LoadingIcon.addIconToContainerRemoveOnCompletion(mainelement,pendingPromise),ajax.call([{methodname:"core_update_inplace_editable",args:{itemid:mainelement.attr("data-itemid"),component:mainelement.attr("data-component"),itemtype:mainelement.attr("data-itemtype"),value:value}}])[0].then((function(data){return templates.render("core/inplace_editable",data).then((function(html,js){var oldvalue=mainelement.attr("data-value"),newelement=$(html);templates.replaceNode(mainelement,newelement,js),silent||newelement.find("[data-inplaceeditablelink]").focus(),Events.notifyElementUpdated(newelement.get(0),data,oldvalue)}))})).then((function(){return pendingPromise.resolve()})).fail((function(ex){mainelement.find(".loading-icon").hide(),M.util.js_complete(pendingId),Events.notifyElementUpdateFailed(mainelement.get(0),ex,value).defaultPrevented||notification.exception(ex)}))};return $("body").on("click keypress","[data-inplaceeditable] [data-inplaceeditablelink]",(function(e){if("keypress"!==e.type||13===e.keyCode){var editingEnabledPromise=new Pending("autocomplete-start-editing");e.stopImmediatePropagation(),e.preventDefault();var mainelement=$(this).closest("[data-inplaceeditable]"),turnEditingOff=function(el){el.find("input").off(),el.find("select").off(),el.html(el.attr("data-oldcontent")),el.removeAttr("data-oldcontent"),el.removeClass("inplaceeditingon"),el.find("[data-inplaceeditablelink]").focus(),el.parents('[data-inplace-in-draggable="true"]').attr("draggable",!0).attr("data-inplace-in-draggable",!1)},uniqueId=function(prefix,idlength){var i,uniqid=prefix;for(i=0;i<idlength;i++)uniqid+=String(Math.floor(10*Math.random()));return 0===$("#"+uniqid).length?uniqid:uniqueId(prefix,idlength)};$('[data-inplace-in-draggable="true"]').attr("draggable",!0).attr("data-inplace-in-draggable",!1),$("span.inplaceeditable.inplaceeditingon").each((function(){turnEditingOff($(this))})),function(el){el.addClass("inplaceeditingon"),el.attr("data-oldcontent",el.html());var type=el.attr("data-type"),options=el.attr("data-options");el.parents('[draggable="true"]').attr("data-inplace-in-draggable",!0).attr("draggable",!1),"toggle"===type?function(el,newvalue){turnEditingOff(el),updateValue(el,newvalue)}(el,options):"select"===type?function(el,options){var i,inputelement=$("<select></select>").attr("id",uniqueId("id_inplacevalue_",20)).addClass("custom-select"),lbl=$('<label class="accesshide">'+mainelement.attr("data-editlabel")+"</label>").attr("for",inputelement.attr("id"));for(i in options)inputelement.append($("<option>").attr("value",options[i].key).html(options[i].value));inputelement.val(el.attr("data-value")),el.html("").append(lbl).append(inputelement),inputelement.focus(),inputelement.select(),inputelement.on("keyup change focusout",(function(e){if(!cfg.behatsiterunning||"focusout"!==e.type){if("change"===e.type){var val=inputelement.val();turnEditingOff(el),updateValue(el,val)}("keyup"===e.type&&27===e.keyCode||"focusout"===e.type)&&turnEditingOff(el)}}))}(el,$.parseJSON(options)):"autocomplete"===type?function(el,args){var i,inputelement=$("<select></select>").attr("id",uniqueId("id_inplacevalue_",20)).addClass("form-autocomplete-original-select").addClass("custom-select"),lbl=$('<label class="accesshide">'+mainelement.attr("data-editlabel")+"</label>").attr("for",inputelement.attr("id")),options=args.options,attributes=args.attributes,saveelement=$('<a href="#"></a>'),cancelelement=$('<a href="#"></a>');for(i in options)inputelement.append($("<option>").attr("value",options[i].key).html(options[i].value));attributes.multiple&&inputelement.attr("multiple","true"),inputelement.val(JSON.parse(el.attr("data-value"))),str.get_string("savechanges","core").then((function(s){return templates.renderPix("e/save","core",s)})).then((function(html){saveelement.append(html)})).fail(notification.exception),str.get_string("cancel","core").then((function(s){return templates.renderPix("e/cancel","core",s)})).then((function(html){cancelelement.append(html)})).fail(notification.exception),el.html("").append(lbl).append(inputelement).append(saveelement).append(cancelelement),inputelement.focus(),inputelement.select(),autocomplete.enhance(inputelement,attributes.tags,attributes.ajax,attributes.placeholder,attributes.caseSensitive,attributes.showSuggestions,attributes.noSelectionString).then((function(){el.find("[role=combobox]").focus()})).fail(notification.exception),inputelement.on("keyup",(function(e){("keyup"===e.type&&27===e.keyCode||"focusout"===e.type)&&turnEditingOff(el)})),saveelement.on("click",(function(e){var val=JSON.stringify(inputelement.val());inputelement.empty(),turnEditingOff(el),updateValue(el,val),e.preventDefault()})),cancelelement.on("click",(function(e){inputelement.empty(),turnEditingOff(el),e.preventDefault()}))}(el,$.parseJSON(options)):function(el){str.get_string("edittitleinstructions").done((function(s){var instr=$('<span class="editinstructions">'+s+"</span>").attr("id",uniqueId("id_editinstructions_",20)),inputelement=$('<input type="text"/>').attr("id",uniqueId("id_inplacevalue_",20)).attr("value",el.attr("data-value")).attr("aria-describedby",instr.attr("id")).addClass("ignoredirty").addClass("form-control"),lbl=$('<label class="accesshide">'+mainelement.attr("data-editlabel")+"</label>").attr("for",inputelement.attr("id"));el.html("").append(instr).append(lbl).append(inputelement),inputelement.focus(),inputelement.select(),inputelement.on("keyup keypress focusout",(function(e){if(!cfg.behatsiterunning||"focusout"!==e.type){if("keypress"===e.type&&13===e.keyCode){var val=inputelement.val();turnEditingOff(el),updateValue(el,val)}("keyup"===e.type&&27===e.keyCode||"focusout"===e.type)&&turnEditingOff(el)}}))}))}(el)}(mainelement),editingEnabledPromise.resolve()}})),{getInplaceEditable:function(parent){const element=parent.querySelector("[data-inplaceeditable]");if(element)return{element:element,getValue:function(){return this.element.dataset.value},setValue:function(newvalue){updateValue($(this.element),newvalue,!0)},getItemId:function(){return this.element.dataset.itemid}}}}}));
 
 //# sourceMappingURL=inplace_editable.min.js.map
\ No newline at end of file
diff --git a/lib/amd/build/inplace_editable.min.js.map b/lib/amd/build/inplace_editable.min.js.map
index b8b3ae99906..b6588518d4d 100644
--- a/lib/amd/build/inplace_editable.min.js.map
+++ b/lib/amd/build/inplace_editable.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"inplace_editable.min.js","sources":["../src/inplace_editable.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 <http://www.gnu.org/licenses/>.\n\n/**\n * AJAX helper for the inline editing a value.\n *\n * This script is automatically included from template core/inplace_editable\n * It registers a click-listener on [data-inplaceeditablelink] link (the \"inplace edit\" icon),\n * then replaces the displayed value with an input field. On \"Enter\" it sends a request\n * to web service core_update_inplace_editable, which invokes the specified callback.\n * Any exception thrown by the web service (or callback) is displayed as an error popup.\n *\n * @module     core/inplace_editable\n * @copyright  2016 Marina Glancy\n * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since      3.1\n */\ndefine(\n    ['jquery',\n        'core/ajax',\n        'core/templates',\n        'core/notification',\n        'core/str',\n        'core/config',\n        'core/url',\n        'core/form-autocomplete',\n        'core/pending',\n        'core/local/inplace_editable/events',\n    ],\n    function($, ajax, templates, notification, str, cfg, url, autocomplete, Pending, Events) {\n\n        const removeSpinner = function(element) {\n            element.removeClass('updating');\n            element.find('img.spinner').hide();\n        };\n\n        /**\n         * Update an inplace editable value.\n         *\n         * @param {Jquery} mainelement the element to update\n         * @param {string} value the new value\n         * @param {bool} silent if true the change won't alter the current page focus\n         * @fires event:core/inplace_editable:updated\n         * @fires event:core/inplace_editable:updateFailed\n         */\n        const updateValue = function(mainelement, value, silent) {\n            var pendingId = [\n                mainelement.attr('data-itemid'),\n                mainelement.attr('data-component'),\n                mainelement.attr('data-itemtype'),\n            ].join('-');\n            var pendingPromise = new Pending(pendingId);\n\n            addSpinner(mainelement);\n            ajax.call([{\n                methodname: 'core_update_inplace_editable',\n                args: {\n                    itemid: mainelement.attr('data-itemid'),\n                    component: mainelement.attr('data-component'),\n                    itemtype: mainelement.attr('data-itemtype'),\n                    value: value,\n                },\n            }])[0]\n                .then(function(data) {\n                    return templates.render('core/inplace_editable', data)\n                        .then(function(html, js) {\n                            var oldvalue = mainelement.attr('data-value');\n                            var newelement = $(html);\n                            templates.replaceNode(mainelement, newelement, js);\n                            if (!silent) {\n                                newelement.find('[data-inplaceeditablelink]').focus();\n                            }\n\n                            // Trigger updated event on the DOM element.\n                            Events.notifyElementUpdated(newelement.get(0), data, oldvalue);\n\n                            return;\n                        });\n                })\n                .then(function() {\n                    return pendingPromise.resolve();\n                })\n                .fail(function(ex) {\n                    removeSpinner(mainelement);\n                    M.util.js_complete(pendingId);\n\n                    // Trigger update failed event on the DOM element.\n                    let updateFailedEvent = Events.notifyElementUpdateFailed(mainelement.get(0), ex, value);\n                    if (!updateFailedEvent.defaultPrevented) {\n                        notification.exception(ex);\n                    }\n                });\n        };\n\n        const addSpinner = function(element) {\n            element.addClass('updating');\n            var spinner = element.find('img.spinner');\n            if (spinner.length) {\n                spinner.show();\n            } else {\n                spinner = $('<img/>')\n                    .attr('src', url.imageUrl('i/loading_small'))\n                    .addClass('spinner').addClass('smallicon')\n                    ;\n                element.append(spinner);\n            }\n        };\n\n        $('body').on('click keypress', '[data-inplaceeditable] [data-inplaceeditablelink]', function(e) {\n            if (e.type === 'keypress' && e.keyCode !== 13) {\n                return;\n            }\n            var editingEnabledPromise = new Pending('autocomplete-start-editing');\n            e.stopImmediatePropagation();\n            e.preventDefault();\n            var target = $(this),\n                mainelement = target.closest('[data-inplaceeditable]');\n\n            var turnEditingOff = function(el) {\n                el.find('input').off();\n                el.find('select').off();\n                el.html(el.attr('data-oldcontent'));\n                el.removeAttr('data-oldcontent');\n                el.removeClass('inplaceeditingon');\n                el.find('[data-inplaceeditablelink]').focus();\n\n                // Re-enable any parent draggable attribute.\n                el.parents(`[data-inplace-in-draggable=\"true\"]`)\n                    .attr('draggable', true)\n                    .attr('data-inplace-in-draggable', false);\n            };\n\n            var turnEditingOffEverywhere = function() {\n                // Re-enable any disabled draggable attribute.\n                $(`[data-inplace-in-draggable=\"true\"]`)\n                    .attr('draggable', true)\n                    .attr('data-inplace-in-draggable', false);\n\n                $('span.inplaceeditable.inplaceeditingon').each(function() {\n                    turnEditingOff($(this));\n                });\n            };\n\n            var uniqueId = function(prefix, idlength) {\n                var uniqid = prefix,\n                    i;\n                for (i = 0; i < idlength; i++) {\n                    uniqid += String(Math.floor(Math.random() * 10));\n                }\n                // Make sure this ID is not already taken by an existing element.\n                if ($(\"#\" + uniqid).length === 0) {\n                    return uniqid;\n                }\n                return uniqueId(prefix, idlength);\n            };\n\n            var turnEditingOnText = function(el) {\n                str.get_string('edittitleinstructions').done(function(s) {\n                    var instr = $('<span class=\"editinstructions\">' + s + '</span>').\n                        attr('id', uniqueId('id_editinstructions_', 20)),\n                        inputelement = $('<input type=\"text\"/>').\n                            attr('id', uniqueId('id_inplacevalue_', 20)).\n                            attr('value', el.attr('data-value')).\n                            attr('aria-describedby', instr.attr('id')).\n                            addClass('ignoredirty').\n                            addClass('form-control'),\n                        lbl = $('<label class=\"accesshide\">' + mainelement.attr('data-editlabel') + '</label>').\n                            attr('for', inputelement.attr('id'));\n                    el.html('').append(instr).append(lbl).append(inputelement);\n\n                    inputelement.focus();\n                    inputelement.select();\n                    inputelement.on('keyup keypress focusout', function(e) {\n                        if (cfg.behatsiterunning && e.type === 'focusout') {\n                            // Behat triggers focusout too often.\n                            return;\n                        }\n                        if (e.type === 'keypress' && e.keyCode === 13) {\n                            // We need 'keypress' event for Enter because keyup/keydown would catch Enter that was\n                            // pressed in other fields.\n                            var val = inputelement.val();\n                            turnEditingOff(el);\n                            updateValue(el, val);\n                        }\n                        if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {\n                            // We need 'keyup' event for Escape because keypress does not work with Escape.\n                            turnEditingOff(el);\n                        }\n                    });\n                });\n            };\n\n            var turnEditingOnToggle = function(el, newvalue) {\n                turnEditingOff(el);\n                updateValue(el, newvalue);\n            };\n\n            var turnEditingOnSelect = function(el, options) {\n                var i,\n                    inputelement = $('<select></select>').\n                        attr('id', uniqueId('id_inplacevalue_', 20)).\n                        addClass('custom-select'),\n                    lbl = $('<label class=\"accesshide\">' + mainelement.attr('data-editlabel') + '</label>')\n                        .attr('for', inputelement.attr('id'));\n                for (i in options) {\n                    inputelement\n                        .append($('<option>')\n                            .attr('value', options[i].key)\n                            .html(options[i].value));\n                }\n                inputelement.val(el.attr('data-value'));\n\n                el.html('')\n                    .append(lbl)\n                    .append(inputelement);\n\n                inputelement.focus();\n                inputelement.select();\n                inputelement.on('keyup change focusout', function(e) {\n                    if (cfg.behatsiterunning && e.type === 'focusout') {\n                        // Behat triggers focusout too often.\n                        return;\n                    }\n                    if (e.type === 'change') {\n                        var val = inputelement.val();\n                        turnEditingOff(el);\n                        updateValue(el, val);\n                    }\n                    if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {\n                        // We need 'keyup' event for Escape because keypress does not work with Escape.\n                        turnEditingOff(el);\n                    }\n                });\n            };\n\n            var turnEditingOnAutocomplete = function(el, args) {\n                var i,\n                    inputelement = $('<select></select>').\n                        attr('id', uniqueId('id_inplacevalue_', 20)).\n                        addClass('form-autocomplete-original-select').\n                        addClass('custom-select'),\n                    lbl = $('<label class=\"accesshide\">' + mainelement.attr('data-editlabel') + '</label>')\n                        .attr('for', inputelement.attr('id')),\n                    options = args.options,\n                    attributes = args.attributes,\n                    saveelement = $('<a href=\"#\"></a>'),\n                    cancelelement = $('<a href=\"#\"></a>');\n\n                for (i in options) {\n                    inputelement\n                        .append($('<option>')\n                            .attr('value', options[i].key)\n                            .html(options[i].value));\n                }\n                if (attributes.multiple) {\n                    inputelement.attr('multiple', 'true');\n                }\n                inputelement.val(JSON.parse(el.attr('data-value')));\n\n                str.get_string('savechanges', 'core').then(function(s) {\n                    return templates.renderPix('e/save', 'core', s);\n                }).then(function(html) {\n                    saveelement.append(html);\n                    return;\n                }).fail(notification.exception);\n\n                str.get_string('cancel', 'core').then(function(s) {\n                    return templates.renderPix('e/cancel', 'core', s);\n                }).then(function(html) {\n                    cancelelement.append(html);\n                    return;\n                }).fail(notification.exception);\n\n                el.html('')\n                    .append(lbl)\n                    .append(inputelement)\n                    .append(saveelement)\n                    .append(cancelelement);\n\n                inputelement.focus();\n                inputelement.select();\n                autocomplete.enhance(inputelement,\n                    attributes.tags,\n                    attributes.ajax,\n                    attributes.placeholder,\n                    attributes.caseSensitive,\n                    attributes.showSuggestions,\n                    attributes.noSelectionString)\n                    .then(function() {\n                        // Focus on the enhanced combobox.\n                        el.find('[role=combobox]').focus();\n                        // Stop eslint nagging.\n                        return;\n                    }).fail(notification.exception);\n\n                inputelement.on('keyup', function(e) {\n                    if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {\n                        // We need 'keyup' event for Escape because keypress does not work with Escape.\n                        turnEditingOff(el);\n                    }\n                });\n                saveelement.on('click', function(e) {\n                    var val = JSON.stringify(inputelement.val());\n                    // We need to empty the node to destroy all event handlers etc.\n                    inputelement.empty();\n                    turnEditingOff(el);\n                    updateValue(el, val);\n                    e.preventDefault();\n                });\n                cancelelement.on('click', function(e) {\n                    // We need to empty the node to destroy all event handlers etc.\n                    inputelement.empty();\n                    turnEditingOff(el);\n                    e.preventDefault();\n                });\n            };\n\n            var turnEditingOn = function(el) {\n                el.addClass('inplaceeditingon');\n                el.attr('data-oldcontent', el.html());\n\n                var type = el.attr('data-type');\n                var options = el.attr('data-options');\n\n                // Input text inside draggable elements disable text selection in some browsers.\n                // To prevent this we temporally disable any parent draggables.\n                el.parents('[draggable=\"true\"]')\n                    .attr('data-inplace-in-draggable', true)\n                    .attr('draggable', false);\n\n                if (type === 'toggle') {\n                    turnEditingOnToggle(el, options);\n                } else if (type === 'select') {\n                    turnEditingOnSelect(el, $.parseJSON(options));\n                } else if (type === 'autocomplete') {\n                    turnEditingOnAutocomplete(el, $.parseJSON(options));\n                } else {\n                    turnEditingOnText(el);\n                }\n            };\n\n            // Turn editing on for the current element and register handler for Enter/Esc keys.\n            turnEditingOffEverywhere();\n            turnEditingOn(mainelement);\n            editingEnabledPromise.resolve();\n\n        });\n\n\n        return {\n            /**\n             * Return an object to interact with the current inplace editables at a frontend level.\n             *\n             * @param {Element} parent the parent element containing a inplace editable\n             * @returns {Object|undefined} an object to interact with the inplace element, or undefined\n             *                             if no inplace editable is found.\n             */\n            getInplaceEditable: function(parent) {\n                const element = parent.querySelector(`[data-inplaceeditable]`);\n                if (!element) {\n                    return undefined;\n                }\n                // Return an object to interact with the inplace editable.\n                return {\n                    element,\n                    /**\n                     * Get the value from the inplace editable.\n                     *\n                     * @returns {string} the current inplace value\n                     */\n                    getValue: function() {\n                        return this.element.dataset.value;\n                    },\n                    /**\n                     * Force a value change.\n                     *\n                     * @param {string} newvalue the new value\n                     * @fires event:core/inplace_editable:updated\n                     * @fires event:core/inplace_editable:updateFailed\n                     */\n                    setValue: function(newvalue) {\n                        updateValue($(this.element), newvalue, true);\n                    },\n                    /**\n                     * Return the inplace editable itemid.\n                     *\n                     * @returns {string} the current itemid\n                     */\n                    getItemId: function() {\n                        return this.element.dataset.itemid;\n                    },\n                };\n            }\n        };\n    });\n"],"names":["define","$","ajax","templates","notification","str","cfg","url","autocomplete","Pending","Events","updateValue","mainelement","value","silent","pendingId","attr","join","pendingPromise","addSpinner","call","methodname","args","itemid","component","itemtype","then","data","render","html","js","oldvalue","newelement","replaceNode","find","focus","notifyElementUpdated","get","resolve","fail","ex","element","removeClass","hide","M","util","js_complete","notifyElementUpdateFailed","defaultPrevented","exception","addClass","spinner","length","show","imageUrl","append","on","e","type","keyCode","editingEnabledPromise","stopImmediatePropagation","preventDefault","this","closest","turnEditingOff","el","off","removeAttr","parents","uniqueId","prefix","idlength","i","uniqid","String","Math","floor","random","each","options","newvalue","turnEditingOnToggle","inputelement","lbl","key","val","select","behatsiterunning","turnEditingOnSelect","parseJSON","attributes","saveelement","cancelelement","multiple","JSON","parse","get_string","s","renderPix","enhance","tags","placeholder","caseSensitive","showSuggestions","noSelectionString","stringify","empty","turnEditingOnAutocomplete","done","instr","turnEditingOnText","turnEditingOn","getInplaceEditable","parent","querySelector","getValue","dataset","setValue","getItemId"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"inplace_editable.min.js","sources":["../src/inplace_editable.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 <http://www.gnu.org/licenses/>.\n\n/**\n * AJAX helper for the inline editing a value.\n *\n * This script is automatically included from template core/inplace_editable\n * It registers a click-listener on [data-inplaceeditablelink] link (the \"inplace edit\" icon),\n * then replaces the displayed value with an input field. On \"Enter\" it sends a request\n * to web service core_update_inplace_editable, which invokes the specified callback.\n * Any exception thrown by the web service (or callback) is displayed as an error popup.\n *\n * @module     core/inplace_editable\n * @copyright  2016 Marina Glancy\n * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since      3.1\n */\ndefine(\n    ['jquery',\n        'core/ajax',\n        'core/templates',\n        'core/notification',\n        'core/str',\n        'core/config',\n        'core/url',\n        'core/form-autocomplete',\n        'core/loadingicon',\n        'core/pending',\n        'core/local/inplace_editable/events',\n    ],\n    function($, ajax, templates, notification, str, cfg, url, autocomplete, LoadingIcon, Pending, Events) {\n\n        const removeSpinner = function(element) {\n            element.find('.loading-icon').hide();\n        };\n\n        /**\n         * Update an inplace editable value.\n         *\n         * @param {Jquery} mainelement the element to update\n         * @param {string} value the new value\n         * @param {bool} silent if true the change won't alter the current page focus\n         * @fires event:core/inplace_editable:updated\n         * @fires event:core/inplace_editable:updateFailed\n         */\n        const updateValue = function(mainelement, value, silent) {\n            var pendingId = [\n                mainelement.attr('data-itemid'),\n                mainelement.attr('data-component'),\n                mainelement.attr('data-itemtype'),\n            ].join('-');\n\n            var pendingPromise = new Pending(pendingId);\n            LoadingIcon.addIconToContainerRemoveOnCompletion(mainelement, pendingPromise);\n\n            ajax.call([{\n                methodname: 'core_update_inplace_editable',\n                args: {\n                    itemid: mainelement.attr('data-itemid'),\n                    component: mainelement.attr('data-component'),\n                    itemtype: mainelement.attr('data-itemtype'),\n                    value: value,\n                },\n            }])[0]\n                .then(function(data) {\n                    return templates.render('core/inplace_editable', data)\n                        .then(function(html, js) {\n                            var oldvalue = mainelement.attr('data-value');\n                            var newelement = $(html);\n                            templates.replaceNode(mainelement, newelement, js);\n                            if (!silent) {\n                                newelement.find('[data-inplaceeditablelink]').focus();\n                            }\n\n                            // Trigger updated event on the DOM element.\n                            Events.notifyElementUpdated(newelement.get(0), data, oldvalue);\n\n                            return;\n                        });\n                })\n                .then(function() {\n                    return pendingPromise.resolve();\n                })\n                .fail(function(ex) {\n                    removeSpinner(mainelement);\n                    M.util.js_complete(pendingId);\n\n                    // Trigger update failed event on the DOM element.\n                    let updateFailedEvent = Events.notifyElementUpdateFailed(mainelement.get(0), ex, value);\n                    if (!updateFailedEvent.defaultPrevented) {\n                        notification.exception(ex);\n                    }\n                });\n        };\n\n        $('body').on('click keypress', '[data-inplaceeditable] [data-inplaceeditablelink]', function(e) {\n            if (e.type === 'keypress' && e.keyCode !== 13) {\n                return;\n            }\n            var editingEnabledPromise = new Pending('autocomplete-start-editing');\n            e.stopImmediatePropagation();\n            e.preventDefault();\n            var target = $(this),\n                mainelement = target.closest('[data-inplaceeditable]');\n\n            var turnEditingOff = function(el) {\n                el.find('input').off();\n                el.find('select').off();\n                el.html(el.attr('data-oldcontent'));\n                el.removeAttr('data-oldcontent');\n                el.removeClass('inplaceeditingon');\n                el.find('[data-inplaceeditablelink]').focus();\n\n                // Re-enable any parent draggable attribute.\n                el.parents(`[data-inplace-in-draggable=\"true\"]`)\n                    .attr('draggable', true)\n                    .attr('data-inplace-in-draggable', false);\n            };\n\n            var turnEditingOffEverywhere = function() {\n                // Re-enable any disabled draggable attribute.\n                $(`[data-inplace-in-draggable=\"true\"]`)\n                    .attr('draggable', true)\n                    .attr('data-inplace-in-draggable', false);\n\n                $('span.inplaceeditable.inplaceeditingon').each(function() {\n                    turnEditingOff($(this));\n                });\n            };\n\n            var uniqueId = function(prefix, idlength) {\n                var uniqid = prefix,\n                    i;\n                for (i = 0; i < idlength; i++) {\n                    uniqid += String(Math.floor(Math.random() * 10));\n                }\n                // Make sure this ID is not already taken by an existing element.\n                if ($(\"#\" + uniqid).length === 0) {\n                    return uniqid;\n                }\n                return uniqueId(prefix, idlength);\n            };\n\n            var turnEditingOnText = function(el) {\n                str.get_string('edittitleinstructions').done(function(s) {\n                    var instr = $('<span class=\"editinstructions\">' + s + '</span>').\n                        attr('id', uniqueId('id_editinstructions_', 20)),\n                        inputelement = $('<input type=\"text\"/>').\n                            attr('id', uniqueId('id_inplacevalue_', 20)).\n                            attr('value', el.attr('data-value')).\n                            attr('aria-describedby', instr.attr('id')).\n                            addClass('ignoredirty').\n                            addClass('form-control'),\n                        lbl = $('<label class=\"accesshide\">' + mainelement.attr('data-editlabel') + '</label>').\n                            attr('for', inputelement.attr('id'));\n                    el.html('').append(instr).append(lbl).append(inputelement);\n\n                    inputelement.focus();\n                    inputelement.select();\n                    inputelement.on('keyup keypress focusout', function(e) {\n                        if (cfg.behatsiterunning && e.type === 'focusout') {\n                            // Behat triggers focusout too often.\n                            return;\n                        }\n                        if (e.type === 'keypress' && e.keyCode === 13) {\n                            // We need 'keypress' event for Enter because keyup/keydown would catch Enter that was\n                            // pressed in other fields.\n                            var val = inputelement.val();\n                            turnEditingOff(el);\n                            updateValue(el, val);\n                        }\n                        if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {\n                            // We need 'keyup' event for Escape because keypress does not work with Escape.\n                            turnEditingOff(el);\n                        }\n                    });\n                });\n            };\n\n            var turnEditingOnToggle = function(el, newvalue) {\n                turnEditingOff(el);\n                updateValue(el, newvalue);\n            };\n\n            var turnEditingOnSelect = function(el, options) {\n                var i,\n                    inputelement = $('<select></select>').\n                        attr('id', uniqueId('id_inplacevalue_', 20)).\n                        addClass('custom-select'),\n                    lbl = $('<label class=\"accesshide\">' + mainelement.attr('data-editlabel') + '</label>')\n                        .attr('for', inputelement.attr('id'));\n                for (i in options) {\n                    inputelement\n                        .append($('<option>')\n                            .attr('value', options[i].key)\n                            .html(options[i].value));\n                }\n                inputelement.val(el.attr('data-value'));\n\n                el.html('')\n                    .append(lbl)\n                    .append(inputelement);\n\n                inputelement.focus();\n                inputelement.select();\n                inputelement.on('keyup change focusout', function(e) {\n                    if (cfg.behatsiterunning && e.type === 'focusout') {\n                        // Behat triggers focusout too often.\n                        return;\n                    }\n                    if (e.type === 'change') {\n                        var val = inputelement.val();\n                        turnEditingOff(el);\n                        updateValue(el, val);\n                    }\n                    if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {\n                        // We need 'keyup' event for Escape because keypress does not work with Escape.\n                        turnEditingOff(el);\n                    }\n                });\n            };\n\n            var turnEditingOnAutocomplete = function(el, args) {\n                var i,\n                    inputelement = $('<select></select>').\n                        attr('id', uniqueId('id_inplacevalue_', 20)).\n                        addClass('form-autocomplete-original-select').\n                        addClass('custom-select'),\n                    lbl = $('<label class=\"accesshide\">' + mainelement.attr('data-editlabel') + '</label>')\n                        .attr('for', inputelement.attr('id')),\n                    options = args.options,\n                    attributes = args.attributes,\n                    saveelement = $('<a href=\"#\"></a>'),\n                    cancelelement = $('<a href=\"#\"></a>');\n\n                for (i in options) {\n                    inputelement\n                        .append($('<option>')\n                            .attr('value', options[i].key)\n                            .html(options[i].value));\n                }\n                if (attributes.multiple) {\n                    inputelement.attr('multiple', 'true');\n                }\n                inputelement.val(JSON.parse(el.attr('data-value')));\n\n                str.get_string('savechanges', 'core').then(function(s) {\n                    return templates.renderPix('e/save', 'core', s);\n                }).then(function(html) {\n                    saveelement.append(html);\n                    return;\n                }).fail(notification.exception);\n\n                str.get_string('cancel', 'core').then(function(s) {\n                    return templates.renderPix('e/cancel', 'core', s);\n                }).then(function(html) {\n                    cancelelement.append(html);\n                    return;\n                }).fail(notification.exception);\n\n                el.html('')\n                    .append(lbl)\n                    .append(inputelement)\n                    .append(saveelement)\n                    .append(cancelelement);\n\n                inputelement.focus();\n                inputelement.select();\n                autocomplete.enhance(inputelement,\n                    attributes.tags,\n                    attributes.ajax,\n                    attributes.placeholder,\n                    attributes.caseSensitive,\n                    attributes.showSuggestions,\n                    attributes.noSelectionString)\n                    .then(function() {\n                        // Focus on the enhanced combobox.\n                        el.find('[role=combobox]').focus();\n                        // Stop eslint nagging.\n                        return;\n                    }).fail(notification.exception);\n\n                inputelement.on('keyup', function(e) {\n                    if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {\n                        // We need 'keyup' event for Escape because keypress does not work with Escape.\n                        turnEditingOff(el);\n                    }\n                });\n                saveelement.on('click', function(e) {\n                    var val = JSON.stringify(inputelement.val());\n                    // We need to empty the node to destroy all event handlers etc.\n                    inputelement.empty();\n                    turnEditingOff(el);\n                    updateValue(el, val);\n                    e.preventDefault();\n                });\n                cancelelement.on('click', function(e) {\n                    // We need to empty the node to destroy all event handlers etc.\n                    inputelement.empty();\n                    turnEditingOff(el);\n                    e.preventDefault();\n                });\n            };\n\n            var turnEditingOn = function(el) {\n                el.addClass('inplaceeditingon');\n                el.attr('data-oldcontent', el.html());\n\n                var type = el.attr('data-type');\n                var options = el.attr('data-options');\n\n                // Input text inside draggable elements disable text selection in some browsers.\n                // To prevent this we temporally disable any parent draggables.\n                el.parents('[draggable=\"true\"]')\n                    .attr('data-inplace-in-draggable', true)\n                    .attr('draggable', false);\n\n                if (type === 'toggle') {\n                    turnEditingOnToggle(el, options);\n                } else if (type === 'select') {\n                    turnEditingOnSelect(el, $.parseJSON(options));\n                } else if (type === 'autocomplete') {\n                    turnEditingOnAutocomplete(el, $.parseJSON(options));\n                } else {\n                    turnEditingOnText(el);\n                }\n            };\n\n            // Turn editing on for the current element and register handler for Enter/Esc keys.\n            turnEditingOffEverywhere();\n            turnEditingOn(mainelement);\n            editingEnabledPromise.resolve();\n\n        });\n\n\n        return {\n            /**\n             * Return an object to interact with the current inplace editables at a frontend level.\n             *\n             * @param {Element} parent the parent element containing a inplace editable\n             * @returns {Object|undefined} an object to interact with the inplace element, or undefined\n             *                             if no inplace editable is found.\n             */\n            getInplaceEditable: function(parent) {\n                const element = parent.querySelector(`[data-inplaceeditable]`);\n                if (!element) {\n                    return undefined;\n                }\n                // Return an object to interact with the inplace editable.\n                return {\n                    element,\n                    /**\n                     * Get the value from the inplace editable.\n                     *\n                     * @returns {string} the current inplace value\n                     */\n                    getValue: function() {\n                        return this.element.dataset.value;\n                    },\n                    /**\n                     * Force a value change.\n                     *\n                     * @param {string} newvalue the new value\n                     * @fires event:core/inplace_editable:updated\n                     * @fires event:core/inplace_editable:updateFailed\n                     */\n                    setValue: function(newvalue) {\n                        updateValue($(this.element), newvalue, true);\n                    },\n                    /**\n                     * Return the inplace editable itemid.\n                     *\n                     * @returns {string} the current itemid\n                     */\n                    getItemId: function() {\n                        return this.element.dataset.itemid;\n                    },\n                };\n            }\n        };\n    });\n"],"names":["define","$","ajax","templates","notification","str","cfg","url","autocomplete","LoadingIcon","Pending","Events","updateValue","mainelement","value","silent","pendingId","attr","join","pendingPromise","addIconToContainerRemoveOnCompletion","call","methodname","args","itemid","component","itemtype","then","data","render","html","js","oldvalue","newelement","replaceNode","find","focus","notifyElementUpdated","get","resolve","fail","ex","hide","M","util","js_complete","notifyElementUpdateFailed","defaultPrevented","exception","on","e","type","keyCode","editingEnabledPromise","stopImmediatePropagation","preventDefault","this","closest","turnEditingOff","el","off","removeAttr","removeClass","parents","uniqueId","prefix","idlength","i","uniqid","String","Math","floor","random","length","each","addClass","options","newvalue","turnEditingOnToggle","inputelement","lbl","append","key","val","select","behatsiterunning","turnEditingOnSelect","parseJSON","attributes","saveelement","cancelelement","multiple","JSON","parse","get_string","s","renderPix","enhance","tags","placeholder","caseSensitive","showSuggestions","noSelectionString","stringify","empty","turnEditingOnAutocomplete","done","instr","turnEditingOnText","turnEditingOn","getInplaceEditable","parent","element","querySelector","getValue","dataset","setValue","getItemId"],"mappings":"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"}
\ No newline at end of file
diff --git a/lib/amd/src/inplace_editable.js b/lib/amd/src/inplace_editable.js
index 96d9acd5bd8..105c8fcb3d0 100644
--- a/lib/amd/src/inplace_editable.js
+++ b/lib/amd/src/inplace_editable.js
@@ -36,14 +36,14 @@ define(
         'core/config',
         'core/url',
         'core/form-autocomplete',
+        'core/loadingicon',
         'core/pending',
         'core/local/inplace_editable/events',
     ],
-    function($, ajax, templates, notification, str, cfg, url, autocomplete, Pending, Events) {
+    function($, ajax, templates, notification, str, cfg, url, autocomplete, LoadingIcon, Pending, Events) {
 
         const removeSpinner = function(element) {
-            element.removeClass('updating');
-            element.find('img.spinner').hide();
+            element.find('.loading-icon').hide();
         };
 
         /**
@@ -61,9 +61,10 @@ define(
                 mainelement.attr('data-component'),
                 mainelement.attr('data-itemtype'),
             ].join('-');
-            var pendingPromise = new Pending(pendingId);
 
-            addSpinner(mainelement);
+            var pendingPromise = new Pending(pendingId);
+            LoadingIcon.addIconToContainerRemoveOnCompletion(mainelement, pendingPromise);
+
             ajax.call([{
                 methodname: 'core_update_inplace_editable',
                 args: {
@@ -104,20 +105,6 @@ define(
                 });
         };
 
-        const addSpinner = function(element) {
-            element.addClass('updating');
-            var spinner = element.find('img.spinner');
-            if (spinner.length) {
-                spinner.show();
-            } else {
-                spinner = $('<img/>')
-                    .attr('src', url.imageUrl('i/loading_small'))
-                    .addClass('spinner').addClass('smallicon')
-                    ;
-                element.append(spinner);
-            }
-        };
-
         $('body').on('click keypress', '[data-inplaceeditable] [data-inplaceeditablelink]', function(e) {
             if (e.type === 'keypress' && e.keyCode !== 13) {
                 return;

From cfcaf488bee5c90068fcb376e893a047c7dada3f Mon Sep 17 00:00:00 2001
From: Paul Holden <paulh@moodle.com>
Date: Tue, 27 Aug 2024 15:48:09 +0100
Subject: [PATCH 2/2] MDL-82909 theme_boost: ensure we target immediate loading
 icon only.

Ignoring those used within inplace editable components in the table.
---
 theme/boost/scss/moodle/tables.scss | 2 +-
 theme/boost/style/moodle.css        | 4 ++--
 theme/classic/style/moodle.css      | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/theme/boost/scss/moodle/tables.scss b/theme/boost/scss/moodle/tables.scss
index 56e01faa15d..31ac1aa2d14 100644
--- a/theme/boost/scss/moodle/tables.scss
+++ b/theme/boost/scss/moodle/tables.scss
@@ -71,7 +71,7 @@ table {
     }
 }
 
-.table-dynamic .loading-icon {
+.table-dynamic > .loading-icon {
     position: absolute;
     left: calc(50% - 1.5rem);
     top: 200px;
diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css
index aab7a358abe..a9b13649500 100644
--- a/theme/boost/style/moodle.css
+++ b/theme/boost/style/moodle.css
@@ -35125,12 +35125,12 @@ table .sticky-column {
   background-color: inherit;
 }
 
-.table-dynamic .loading-icon {
+.table-dynamic > .loading-icon {
   position: absolute;
   left: calc(50% - 1.5rem);
   top: 200px;
 }
-.table-dynamic .loading-icon .icon {
+.table-dynamic > .loading-icon .icon {
   max-height: 3rem;
   max-width: 3rem;
   font-size: 3rem;
diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css
index cdd75c24d1d..67d41f27f2b 100644
--- a/theme/classic/style/moodle.css
+++ b/theme/classic/style/moodle.css
@@ -35125,12 +35125,12 @@ table .sticky-column {
   background-color: inherit;
 }
 
-.table-dynamic .loading-icon {
+.table-dynamic > .loading-icon {
   position: absolute;
   left: calc(50% - 1.5rem);
   top: 200px;
 }
-.table-dynamic .loading-icon .icon {
+.table-dynamic > .loading-icon .icon {
   max-height: 3rem;
   max-width: 3rem;
   font-size: 3rem;