Skip to content

Instantly share code, notes, and snippets.

@IlCallo
Last active December 6, 2022 14:33
Show Gist options
  • Save IlCallo/d4e3117cb7a856b29b0026c8ad2417a5 to your computer and use it in GitHub Desktop.
Save IlCallo/d4e3117cb7a856b29b0026c8ad2417a5 to your computer and use it in GitHub Desktop.
Hijack QDialog click on backdrop to manage it ourselves
import { QDialog } from 'quasar';
import { computed, Ref, watch } from 'vue';
// Quasar doesn't offer a way to hook into the backdrop click event from inside the dialog component itself,
// so we have to do it ourselves and prevent the default handler from Quasar from firing
export function useDialogBackdropClick(
dialogRef: Ref<QDialog | undefined>,
handler: (event: FocusEvent) => void | Promise<void>,
) {
// Apparently retrieving `document.querySelector('.q-dialog__backdrop')` on onMounted doesn't work,
// possibly because the backdrop element is somehow re-created later on in the Dialog lifecycle, losing all event listeners
// So we obtain it via the dialogRef, which is always updated when its DOM reference changes
const backdropRef = computed(() =>
dialogRef.value?.contentEl.parentElement?.querySelector<HTMLDivElement>(
'.q-dialog__backdrop',
),
);
async function onBackdropClick(event: FocusEvent) {
// Only react to events that are fired on the backdrop element
if (event.target !== backdropRef.value) {
return;
}
// Stops propagation to avoid the listener defined in QDialog from firing
// Since all listeners are executed synchronously, we must add this before calling the async handler
event.stopPropagation();
await handler(event);
// If the handler provides focus to another element, e.g. a dialog,
// that could return the focus to the backdrop as soon as it closes,
// generating a focus loop.
// We prevent this by removing the focus from the backdrop as soon as the handler finishes
(event.target as HTMLDivElement).blur();
}
watch(
backdropRef,
(backdrop) => {
if (backdrop) {
// There's no way to execute our listener before the default handler when adding the listener
// on the backdrop element itself, as listeners are executed in the order they are added
// So we use a capturing listener on the backdrop parent element to prevent the default handler
// from QDialog from firing, as it would close the dialog
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
backdrop.parentElement!.addEventListener('focusin', onBackdropClick, {
capture: true,
});
}
},
{ immediate: true },
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment