From 93f8da1666c4d6b416031b74909656cacfe65eba Mon Sep 17 00:00:00 2001 From: "Calum H." Date: Tue, 9 Jun 2026 22:01:02 +0100 Subject: [PATCH] fix(app): notifications appearing with modal open (#6348) fix: notifications appearing with modal open --- packages/ui/src/components/modal/NewModal.vue | 93 +------------------ .../components/nav/PopupNotificationPanel.vue | 17 ++++ 2 files changed, 19 insertions(+), 91 deletions(-) diff --git a/packages/ui/src/components/modal/NewModal.vue b/packages/ui/src/components/modal/NewModal.vue index aa504b480..40d873b33 100644 --- a/packages/ui/src/components/modal/NewModal.vue +++ b/packages/ui/src/components/modal/NewModal.vue @@ -136,7 +136,6 @@ import { XIcon } from '@modrinth/assets' import { computed, nextTick, onUnmounted, ref } from 'vue' -import { useDebugLogger } from '../../composables/debug-logger' import { useVIntl } from '../../composables/i18n' import { useModalStack } from '../../composables/modal-stack' import { useScrollIndicator } from '../../composables/scroll-indicator' @@ -145,7 +144,6 @@ import { commonMessages } from '../../utils/common-messages' import ButtonStyled from '../base/ButtonStyled.vue' const { formatMessage } = useVIntl() -const debug = useDebugLogger('NewModal') const modalBehavior = injectModalBehavior(null) const { @@ -235,138 +233,56 @@ function getFocusableElements(): HTMLElement[] { } function show(event?: MouseEvent) { - debug('show: start', { - header: props.header, - open: open.value, - visible: visible.value, - stackSize: modalStackSize(), - hasEvent: !!event, - }) props.onShow?.() - debug('show: after onShow', { header: props.header }) const wasEmpty = modalStackSize() === 0 stackDepth.value = modalStackSize() - debug('show: before open=true', { - header: props.header, - wasEmpty, - stackDepth: stackDepth.value, - }) open.value = true - debug('show: after open=true', { - header: props.header, - open: open.value, - modalBodyExists: !!modalBodyRef.value, - }) previousFocusEl = document.activeElement - debug('show: previous focus captured', { - header: props.header, - previousFocusTag: previousFocusEl instanceof HTMLElement ? previousFocusEl.tagName : null, - previousFocusClass: previousFocusEl instanceof HTMLElement ? previousFocusEl.className : null, - }) pushModal() - debug('show: after pushModal', { header: props.header, stackSize: modalStackSize() }) if (wasEmpty) modalBehavior?.onShow?.() - debug('show: after modalBehavior onShow', { header: props.header }) document.body.style.overflow = 'hidden' window.addEventListener('keydown', handleWindowKeyDown) window.addEventListener('mousedown', updateMousePosition) - debug('show: listeners attached', { header: props.header }) if (event) { updateMousePosition(event) } else { mouseX.value = Math.round(window.innerWidth / 2) mouseY.value = Math.round(window.innerHeight / 2) } - debug('show: mouse position set', { - header: props.header, - mouseX: mouseX.value, - mouseY: mouseY.value, - }) setTimeout(() => { - debug('show: timeout before visible=true', { - header: props.header, - open: open.value, - visible: visible.value, - modalBodyExists: !!modalBodyRef.value, - }) visible.value = true - debug('show: timeout after visible=true', { - header: props.header, - open: open.value, - visible: visible.value, - modalBodyExists: !!modalBodyRef.value, - }) nextTick(() => { - debug('show: nextTick focus start', { - header: props.header, - modalBodyExists: !!modalBodyRef.value, - }) const focusable = getFocusableElements() - debug('show: focusable elements', { - header: props.header, - count: focusable.length, - firstTag: focusable[0]?.tagName, - }) if (focusable.length > 0) { focusable[0].focus() } else { modalBodyRef.value?.focus() } - debug('show: nextTick focus done', { header: props.header }) }) }, 50) - debug('show: end', { header: props.header }) } function hide() { - debug('hide: start', { - header: props.header, - open: open.value, - visible: visible.value, - disableClose: props.disableClose, - stackSize: modalStackSize(), - }) if (props.disableClose) { - debug('hide: ignored disableClose', { header: props.header }) return } props.onHide?.() - debug('hide: after onHide', { header: props.header }) visible.value = false - debug('hide: after visible=false', { header: props.header, visible: visible.value }) popModal() - debug('hide: after popModal', { header: props.header, stackSize: modalStackSize() }) if (modalStackSize() === 0) { modalBehavior?.onHide?.() document.body.style.overflow = '' } window.removeEventListener('keydown', handleWindowKeyDown) window.removeEventListener('mousedown', updateMousePosition) - debug('hide: listeners removed', { header: props.header }) if (previousFocusEl instanceof HTMLElement) { - debug('hide: restoring focus', { - header: props.header, - previousFocusTag: previousFocusEl.tagName, - previousFocusClass: previousFocusEl.className, - }) previousFocusEl.focus() } previousFocusEl = null setTimeout(() => { - debug('hide: timeout before open=false', { - header: props.header, - open: open.value, - visible: visible.value, - }) open.value = false - debug('hide: timeout after open=false', { - header: props.header, - open: open.value, - visible: visible.value, - }) }, 300) - debug('hide: end', { header: props.header }) } defineExpose({ @@ -394,12 +310,6 @@ function updateMousePosition(event: { clientX: number; clientY: number }) { } onUnmounted(() => { - debug('unmounted', { - header: props.header, - open: open.value, - visible: visible.value, - stackSize: modalStackSize(), - }) if (open.value) { popModal() window.removeEventListener('keydown', handleWindowKeyDown) @@ -528,11 +438,12 @@ defineOptions({ visibility: hidden; pointer-events: none; transform: translate(v-bind(mouseXOffset), v-bind(mouseYOffset)); - transition: all 0.2s ease-out; + transition: none; &.shown { visibility: visible; transform: translate(0, 0); + transition: all 0.2s ease-out; > .modal-body { opacity: 1; diff --git a/packages/ui/src/components/nav/PopupNotificationPanel.vue b/packages/ui/src/components/nav/PopupNotificationPanel.vue index 15ab6238e..40c5c22c7 100644 --- a/packages/ui/src/components/nav/PopupNotificationPanel.vue +++ b/packages/ui/src/components/nav/PopupNotificationPanel.vue @@ -3,7 +3,9 @@ class="popup-notification-group" :class="{ 'has-sidebar': hasSidebar, + 'has-modal': hasModalActive, }" + :style="notificationGroupStyle" >
(() => popupNotificationManager.getNotifications(), ) +const { stackCount } = useModalStack() +const hasModalActive = computed(() => stackCount.value > 0) +const notificationGroupStyle = computed(() => ({ + zIndex: hasModalActive.value ? 100 + stackCount.value * 10 + 8 : 200, +})) const stopTimer = (n: PopupNotification) => popupNotificationManager.stopNotificationTimer(n) const setNotificationTimer = (n: PopupNotification) => @@ -252,12 +260,21 @@ withDefaults( display: flex; flex-direction: column; gap: 0.75rem; + transition: + opacity 0.2s ease-in-out, + transform 0.2s ease-in-out; } .popup-notification-group.has-sidebar { right: calc(var(--right-bar-width, 0px) + 1.5rem); } +.popup-notification-group.has-modal { + opacity: 0; + pointer-events: none; + transform: translateY(-0.5rem); +} + @media screen and (max-width: 500px) { .popup-notification-group { right: 0.75rem;