fix(app): notifications appearing with modal open (#6348)

fix: notifications appearing with modal open
This commit is contained in:
Calum H.
2026-06-09 22:01:02 +01:00
committed by GitHub
parent f474940321
commit 93f8da1666
2 changed files with 19 additions and 91 deletions
+2 -91
View File
@@ -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;
@@ -3,7 +3,9 @@
class="popup-notification-group"
:class="{
'has-sidebar': hasSidebar,
'has-modal': hasModalActive,
}"
:style="notificationGroupStyle"
>
<transition-group name="popup-notifs">
<div
@@ -155,6 +157,7 @@ import {
} from '@modrinth/assets'
import { computed } from 'vue'
import { useModalStack } from '../../composables/modal-stack'
import {
injectPopupNotificationManager,
type PopupNotification,
@@ -169,6 +172,11 @@ const popupNotificationManager = injectPopupNotificationManager()
const notifications = computed<PopupNotification[]>(() =>
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;