fix(modal): open dialog reliably in electron desktop

Replace the nextTick-based showModal() call with a watch on the template
ref so the dialog is opened exactly when the <dialog> element mounts.
The previous implementation could silently skip showModal() if the mount
was deferred past the first nextTick, leaving the dialog in the DOM with
opacity: 0 and no click target. Observed in the Vikunja Desktop v2.3.0
Electron build where the search (quick actions) button was unresponsive.

Closes #2590
This commit is contained in:
kolaente 2026-04-11 18:17:17 +02:00 committed by kolaente
parent f29f985386
commit e932ee759a
1 changed files with 16 additions and 14 deletions

View File

@ -61,7 +61,7 @@
<script lang="ts" setup>
import BaseButton from '@/components/base/BaseButton.vue'
import {ref, useAttrs, watch, onBeforeUnmount, onMounted, nextTick} from 'vue'
import {ref, useAttrs, watch, onBeforeUnmount} from 'vue'
const props = withDefaults(defineProps<{
enabled?: boolean,
@ -96,14 +96,12 @@ function openDialog() {
}
previouslyFocused.value = document.activeElement
showDialog.value = true
nextTick(() => {
const dialog = dialogRef.value
if (dialog) {
delete dialog.dataset.closing
dialog.showModal()
}
document.body.style.overflow = 'hidden'
})
document.body.style.overflow = 'hidden'
// The actual `showModal()` call happens in the `watch(dialogRef, )`
// below, which fires the moment Vue mounts the <dialog>. We cannot call
// it synchronously here because the element is not in the DOM yet
// (v-if="showDialog" only just became true), and we cannot rely on a
// single nextTick because the mount can be deferred past it (#2590).
}
function closeDialog() {
@ -136,13 +134,17 @@ watch(
closeDialog()
}
},
{immediate: false},
{immediate: true},
)
onMounted(() => {
if (props.enabled) {
openDialog()
}
// Actually call showModal() the moment the <dialog> element is mounted.
// `dialogRef` is populated by Vue during the render flush after
// `showDialog.value = true`, so this fires deterministically, no matter
// how many flushes the renderer needs (see #2590).
watch(dialogRef, (dialog) => {
if (!dialog) return
delete dialog.dataset.closing
dialog.showModal()
})
onBeforeUnmount(() => {