Files
bachir 3688bddba8 Affiche les messages Drupal par-dessus les fenêtres modales
La région [data-drupal-messages] vit dans le layout Gin, où des stacking
contexts ancêtres neutralisaient son position:fixed + z-index : tout le
sous-arbre passait sous l'overlay de la modale jQuery UI (enfant direct
du <body>), rendant illisibles les messages insérés pendant l'édition
d'une ligne (MessageCommand -- erreur de répartition, création...).

admin-chrome.js déplace la région en enfant direct du <body> au
chargement et la re-vérifie dès qu'une modale entre dans le DOM ;
admin-chrome.css passe son z-index à 100000, hors d'atteinte du
_moveToTop de jQuery UI (qui ne remonte un dialog que au-dessus des
siblings .ui-front, ce que la région n'est pas).
2026-09-09 12:59:37 +02:00

100 lines
3.7 KiB
JavaScript

/**
* @file
* Non-error Drupal status messages (status/warning) auto-dismiss after a
* couple seconds; error messages always require an explicit click on
* Gin's own dismiss button (.js-message-button-hide) -- something that
* needs attention shouldn't vanish on its own.
*
* Drupal.Message.add() (used both for messages already in the initial
* page render and for MessageCommand-driven AJAX messages) just appends a
* DOM node directly -- it never re-fires Drupal.attachBehaviors(), so a
* behaviors/once()-based approach would miss anything added after the
* initial page load. A MutationObserver sees every insertion path.
*/
(function (Drupal) {
'use strict';
var AUTO_DISMISS_MS = 2000;
function dismiss(el) {
var btn = el.querySelector('.js-message-button-hide');
if (btn) {
btn.click();
} else {
el.style.opacity = 0;
el.classList.add('visually-hidden');
}
}
function handleMessage(el) {
if (el.dataset.figliAutoDismissSeen) return;
el.dataset.figliAutoDismissSeen = '1';
if (el.classList.contains('messages--error')) return;
setTimeout(function () {
dismiss(el);
}, AUTO_DISMISS_MS);
}
function scan(root) {
root.querySelectorAll('.messages-list__item').forEach(handleMessage);
}
/**
* Lifts the [data-drupal-messages] region to a direct <body> child.
*
* Gin renders it deep inside its layout (main.page-content > region
* highlighted), where ancestor stacking contexts neutralize the
* region's position:fixed + high z-index (admin-chrome.css): the whole
* subtree then stacks below the jQuery UI modal overlay -- a direct
* <body> child -- which is why messages inserted while a modal is open
* (MessageCommand-driven, e.g. the répartition-sum error on the
* ligne_comptable modal form) rendered unreadably *under* the overlay.
* As a <body> child, the region's z-index competes directly with the
* overlay/dialog and wins. Core itself puts the fallback wrapper at
* body level (Drupal.Message.defaultWrapper(), misc/message.js), so
* this is also where messages already land on a page without the
* region; MessageCommand re-queries the region at response time, so
* moving it after page load breaks no insertion path.
*/
function liftMessages() {
var region = document.querySelector('[data-drupal-messages]');
if (region && region.parentElement !== document.body) {
document.body.appendChild(region);
}
}
function init() {
liftMessages();
scan(document);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
mutation.addedNodes.forEach(function (node) {
if (node.nodeType !== 1) return;
if (node.classList && node.classList.contains('messages-list__item')) {
handleMessage(node);
}
// A modal (or a re-rendered messages region) entering the DOM:
// re-check the lift right when it matters -- the region must
// already be body-level when the dialog's overlay and any
// MessageCommand insertions show up. matches() first because
// querySelector() never matches the node itself. liftMessages()
// is idempotent, so over-triggering is harmless.
if (node.matches('.ui-dialog, [data-drupal-messages]')
|| node.querySelector('.ui-dialog, [data-drupal-messages]')) {
liftMessages();
}
if (node.querySelectorAll) {
scan(node);
}
});
});
}).observe(document.body, { childList: true, subtree: true });
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})(Drupal);