diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.js b/src/bundle/Resources/public/js/scripts/admin.notifications.js index c8d5e91c1..c54586508 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.js @@ -4,7 +4,34 @@ import { appendNotification } from './helpers/notification.helper'; (function (global, doc, ibexa) { const notificationsContainer = doc.querySelector('.ibexa-notifications-container'); + const behindModalContainer = doc.querySelector('.ibexa-notifications-container--behind-modal'); const notifications = JSON.parse(notificationsContainer.dataset.notifications); + const moveNotificationsBehindModal = (event) => { + if (event.defaultPrevented) { + return; + } + + behindModalContainer.append(...notificationsContainer.children); + }; + const restoreNotifications = () => { + if (doc.querySelector('.modal.show') || !behindModalContainer.childElementCount) { + return; + } + + notificationsContainer.prepend(...behindModalContainer.children); + }; + const syncBehindModalOffset = ([entry]) => { + behindModalContainer.style.setProperty('--ibexa-notifications-height', `${entry.borderBoxSize[0].blockSize}px`); + }; + const restoreOnBackdropRemoval = (mutations) => { + const isBackdropRemoved = mutations.some(({ removedNodes }) => + [...removedNodes].some((node) => node.classList?.contains('modal-backdrop')), + ); + + if (isBackdropRemoved) { + restoreNotifications(); + } + }; const addNotification = ({ detail }) => { const { onShow, label, message, customIconPath = '' } = detail; const config = ibexa.adminUiConfig.notifications[label]; @@ -33,5 +60,13 @@ import { appendNotification } from './helpers/notification.helper'; messages.forEach((message) => addNotification({ detail: { label, message } })); }); + const notificationsResizeObserver = new ResizeObserver(syncBehindModalOffset); + const backdropMutationObserver = new MutationObserver(restoreOnBackdropRemoval); + + notificationsResizeObserver.observe(notificationsContainer); + backdropMutationObserver.observe(doc.body, { childList: true }); + doc.body.addEventListener('ibexa-notify', addNotification, false); + doc.addEventListener('show.bs.modal', moveNotificationsBehindModal, false); + doc.addEventListener('hidden.bs.modal', restoreNotifications, false); })(window, window.document, window.ibexa); diff --git a/src/bundle/Resources/public/scss/_notifications.scss b/src/bundle/Resources/public/scss/_notifications.scss index 594ebd536..b2e1758c5 100644 --- a/src/bundle/Resources/public/scss/_notifications.scss +++ b/src/bundle/Resources/public/scss/_notifications.scss @@ -11,6 +11,11 @@ .ids-alert { margin-bottom: calculateRem(16px); } + + &--behind-modal { + z-index: $ibexa-backdrop-zindex - 1; + transform: translateY(calc(-1 * var(--ibexa-notifications-height, 0px))); + } } .ibexa-notification-list { diff --git a/src/bundle/Resources/views/themes/admin/ui/notifications_container.html.twig b/src/bundle/Resources/views/themes/admin/ui/notifications_container.html.twig index 81b814376..d64ff1c97 100644 --- a/src/bundle/Resources/views/themes/admin/ui/notifications_container.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/notifications_container.html.twig @@ -1,6 +1,9 @@ {# Toast container: `app.flashes` are rendered on load and each notification type gets its own template, which admin.notifications.js clones with the `message` placeholder replaced. + + The second container is stacked below the modal backdrop; admin.notifications.js moves the toasts + that are already on screen into it while a modal is open. #}
+