From 374e5a8b65e93b62329d11e690cfb524fcc906e0 Mon Sep 17 00:00:00 2001 From: tischsoic Date: Mon, 14 Sep 2026 12:01:28 +0200 Subject: [PATCH 1/5] IBX-12552: Let the bulk action modal cover the notifications Notifications sit at z-index 10300, far above the modal stack, so the toast reporting a partial bulk operation stayed lit and clickable on top of the modal its own link had opened. It now drops below the backdrop while that modal is shown; notifications keep covering every other backdrop. Co-Authored-By: Claude --- src/bundle/Resources/public/scss/_notifications.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bundle/Resources/public/scss/_notifications.scss b/src/bundle/Resources/public/scss/_notifications.scss index 594ebd536..1dbc6663b 100644 --- a/src/bundle/Resources/public/scss/_notifications.scss +++ b/src/bundle/Resources/public/scss/_notifications.scss @@ -11,6 +11,10 @@ .ids-alert { margin-bottom: calculateRem(16px); } + + body:has(.ibexa-bulk-action-failed-modal.show) & { + z-index: $ibexa-backdrop-zindex - 1; + } } .ibexa-notification-list { From 932920fe3db116a0f1df2ac575f9366fa35c6140 Mon Sep 17 00:00:00 2001 From: tischsoic Date: Mon, 14 Sep 2026 12:46:37 +0200 Subject: [PATCH 2/5] IBX-12552: Limited the dimming to the alert that opened the modal Lowering the notifications container put every visible notification behind the backdrop, and a single notification cannot be lowered on its own: the container is position: fixed, so it establishes a stacking context its children cannot leave. The notification carrying the link is marked when it is created and, while that modal is shown, is covered with the backdrop's own colour and stops taking pointer events; the other notifications are untouched. Co-Authored-By: Claude --- .../Resources/public/scss/_notifications.scss | 14 ++++++++++++-- .../src/modules/sub-items/sub.items.module.js | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/bundle/Resources/public/scss/_notifications.scss b/src/bundle/Resources/public/scss/_notifications.scss index 1dbc6663b..d3d26b16e 100644 --- a/src/bundle/Resources/public/scss/_notifications.scss +++ b/src/bundle/Resources/public/scss/_notifications.scss @@ -1,3 +1,4 @@ +@use 'sass:color'; @use '@ibexa-admin-ui/src/bundle/Resources/public/scss/custom.scss' as *; @use '@ibexa-admin-ui/src/bundle/Resources/public/scss/functions/calculate.rem' as *; @@ -12,8 +13,17 @@ margin-bottom: calculateRem(16px); } - body:has(.ibexa-bulk-action-failed-modal.show) & { - z-index: $ibexa-backdrop-zindex - 1; + body:has(.ibexa-bulk-action-failed-modal.show) & .ibexa-notification--bulk-action-failed { + position: relative; + pointer-events: none; + + &::after { + content: ''; + position: absolute; + inset: 0; + border-radius: inherit; + background-color: color.change($ibexa-color-black, $alpha: 0.5); + } } } diff --git a/src/bundle/ui-dev/src/modules/sub-items/sub.items.module.js b/src/bundle/ui-dev/src/modules/sub-items/sub.items.module.js index 3389c38d8..df570c4a6 100644 --- a/src/bundle/ui-dev/src/modules/sub-items/sub.items.module.js +++ b/src/bundle/ui-dev/src/modules/sub-items/sub.items.module.js @@ -998,6 +998,8 @@ export default class SubItemsModule extends Component { return; } + notificationNode.classList.add('ibexa-notification--bulk-action-failed'); + showModalBtn.addEventListener('click', this.props.showBulkActionFailedModal.bind(null, modalTableTitle, failedItemsData)); }); } From 81ac26d8dd178da32c3484212f814cdd3eb85558 Mon Sep 17 00:00:00 2001 From: tischsoic Date: Mon, 14 Sep 2026 12:53:20 +0200 Subject: [PATCH 3/5] IBX-12552: Moved the dimming rule to its own stylesheet It belongs with the modal it reacts to rather than with the notifications container, next to the other modal partials. Co-Authored-By: Claude --- .../public/scss/_bulk-action-failed-modal.scss | 17 +++++++++++++++++ .../Resources/public/scss/_notifications.scss | 14 -------------- src/bundle/Resources/public/scss/ibexa.scss | 1 + 3 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 src/bundle/Resources/public/scss/_bulk-action-failed-modal.scss diff --git a/src/bundle/Resources/public/scss/_bulk-action-failed-modal.scss b/src/bundle/Resources/public/scss/_bulk-action-failed-modal.scss new file mode 100644 index 000000000..747b88346 --- /dev/null +++ b/src/bundle/Resources/public/scss/_bulk-action-failed-modal.scss @@ -0,0 +1,17 @@ +@use 'sass:color'; +@use '@ibexa-admin-ui/src/bundle/Resources/public/scss/custom.scss' as *; + +body:has(.ibexa-bulk-action-failed-modal.show) { + .ibexa-notification--bulk-action-failed { + position: relative; + pointer-events: none; + + &::after { + content: ''; + position: absolute; + inset: 0; + border-radius: inherit; + background-color: color.change($ibexa-color-black, $alpha: 0.5); + } + } +} diff --git a/src/bundle/Resources/public/scss/_notifications.scss b/src/bundle/Resources/public/scss/_notifications.scss index d3d26b16e..594ebd536 100644 --- a/src/bundle/Resources/public/scss/_notifications.scss +++ b/src/bundle/Resources/public/scss/_notifications.scss @@ -1,4 +1,3 @@ -@use 'sass:color'; @use '@ibexa-admin-ui/src/bundle/Resources/public/scss/custom.scss' as *; @use '@ibexa-admin-ui/src/bundle/Resources/public/scss/functions/calculate.rem' as *; @@ -12,19 +11,6 @@ .ids-alert { margin-bottom: calculateRem(16px); } - - body:has(.ibexa-bulk-action-failed-modal.show) & .ibexa-notification--bulk-action-failed { - position: relative; - pointer-events: none; - - &::after { - content: ''; - position: absolute; - inset: 0; - border-radius: inherit; - background-color: color.change($ibexa-color-black, $alpha: 0.5); - } - } } .ibexa-notification-list { diff --git a/src/bundle/Resources/public/scss/ibexa.scss b/src/bundle/Resources/public/scss/ibexa.scss index 0b4c8a3d8..ba4ff464d 100644 --- a/src/bundle/Resources/public/scss/ibexa.scss +++ b/src/bundle/Resources/public/scss/ibexa.scss @@ -19,6 +19,7 @@ @use 'limitation-picker'; @use 'main-nav'; @use 'modals'; +@use 'bulk-action-failed-modal'; @use 'notifications'; @use 'alert-adapter'; @use 'instant-filter'; From 33f43157b9bf3d3148d66028e46bd3e7e08cd939 Mon Sep 17 00:00:00 2001 From: tischsoic Date: Fri, 18 Sep 2026 14:36:46 +0200 Subject: [PATCH 4/5] IBX-12552: Reverted the per-notification dimming The design team settled the rule: notifications already on screen when a modal opens go behind its backdrop, and notifications raised afterwards stay above it. That needs real stacking rather than an overlay painted on one notification. Co-Authored-By: Claude --- .../public/scss/_bulk-action-failed-modal.scss | 17 ----------------- src/bundle/Resources/public/scss/ibexa.scss | 1 - .../src/modules/sub-items/sub.items.module.js | 2 -- 3 files changed, 20 deletions(-) delete mode 100644 src/bundle/Resources/public/scss/_bulk-action-failed-modal.scss diff --git a/src/bundle/Resources/public/scss/_bulk-action-failed-modal.scss b/src/bundle/Resources/public/scss/_bulk-action-failed-modal.scss deleted file mode 100644 index 747b88346..000000000 --- a/src/bundle/Resources/public/scss/_bulk-action-failed-modal.scss +++ /dev/null @@ -1,17 +0,0 @@ -@use 'sass:color'; -@use '@ibexa-admin-ui/src/bundle/Resources/public/scss/custom.scss' as *; - -body:has(.ibexa-bulk-action-failed-modal.show) { - .ibexa-notification--bulk-action-failed { - position: relative; - pointer-events: none; - - &::after { - content: ''; - position: absolute; - inset: 0; - border-radius: inherit; - background-color: color.change($ibexa-color-black, $alpha: 0.5); - } - } -} diff --git a/src/bundle/Resources/public/scss/ibexa.scss b/src/bundle/Resources/public/scss/ibexa.scss index ba4ff464d..0b4c8a3d8 100644 --- a/src/bundle/Resources/public/scss/ibexa.scss +++ b/src/bundle/Resources/public/scss/ibexa.scss @@ -19,7 +19,6 @@ @use 'limitation-picker'; @use 'main-nav'; @use 'modals'; -@use 'bulk-action-failed-modal'; @use 'notifications'; @use 'alert-adapter'; @use 'instant-filter'; diff --git a/src/bundle/ui-dev/src/modules/sub-items/sub.items.module.js b/src/bundle/ui-dev/src/modules/sub-items/sub.items.module.js index df570c4a6..3389c38d8 100644 --- a/src/bundle/ui-dev/src/modules/sub-items/sub.items.module.js +++ b/src/bundle/ui-dev/src/modules/sub-items/sub.items.module.js @@ -998,8 +998,6 @@ export default class SubItemsModule extends Component { return; } - notificationNode.classList.add('ibexa-notification--bulk-action-failed'); - showModalBtn.addEventListener('click', this.props.showBulkActionFailedModal.bind(null, modalTableTitle, failedItemsData)); }); } From d1600bc097b714ecf11f5c3b2b4d025401e00f92 Mon Sep 17 00:00:00 2001 From: tischsoic Date: Fri, 18 Sep 2026 14:39:07 +0200 Subject: [PATCH 5/5] IBX-12552: Stacked notifications around the modal backdrop Notifications that are already on screen when a modal opens move into a second container placed below the backdrop, and return when the last modal closes; anything raised while the modal is open is appended to the original container and stays above it. They cannot be ordered within one container, because it is position: fixed and so establishes a stacking context its children cannot leave. The second container is offset by the height of the first one, so the two keep reading as a single column. Bootstrap's own events drive it, which also covers the React popups built on them; the backdrop leaving the document is used as the restore trigger as well, because a popup unmounted by React dispatches its hidden event on a detached node. Co-Authored-By: Claude --- .../public/js/scripts/admin.notifications.js | 35 +++++++++++++++++++ .../Resources/public/scss/_notifications.scss | 5 +++ .../ui/notifications_container.html.twig | 4 +++ 3 files changed, 44 insertions(+) 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. #}
+