90 lines
2.2 KiB
Vue
90 lines
2.2 KiB
Vue
<script setup lang="ts">
|
||
import { nextTick, onBeforeUnmount, onMounted, ref, useId, watch } from "vue";
|
||
import DsIconButton from "../actions/DsIconButton.vue";
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
title: string;
|
||
description?: string;
|
||
closeLabel?: string;
|
||
closeOnBackdrop?: boolean;
|
||
}>(),
|
||
{
|
||
description: undefined,
|
||
closeLabel: "Cerrar diálogo",
|
||
closeOnBackdrop: true,
|
||
},
|
||
);
|
||
const model = defineModel<boolean>({ default: false });
|
||
const emit = defineEmits<{ close: []; cancel: [] }>();
|
||
const dialog = ref<HTMLDialogElement | null>(null);
|
||
const titleId = useId();
|
||
const descriptionId = useId();
|
||
let previousFocus: HTMLElement | null = null;
|
||
|
||
async function syncDialog(open: boolean) {
|
||
await nextTick();
|
||
if (open && dialog.value && !dialog.value.open) {
|
||
previousFocus = document.activeElement as HTMLElement | null;
|
||
dialog.value.showModal();
|
||
} else if (!open && dialog.value?.open) {
|
||
dialog.value.close();
|
||
previousFocus?.focus();
|
||
}
|
||
}
|
||
|
||
watch(model, syncDialog);
|
||
onMounted(() => syncDialog(model.value));
|
||
|
||
function requestClose(cancelled = false) {
|
||
model.value = false;
|
||
if (cancelled) emit("cancel");
|
||
emit("close");
|
||
}
|
||
|
||
function onCancel(event: Event) {
|
||
event.preventDefault();
|
||
requestClose(true);
|
||
}
|
||
|
||
function onBackdrop(event: MouseEvent) {
|
||
if (props.closeOnBackdrop && event.target === dialog.value)
|
||
requestClose(true);
|
||
}
|
||
|
||
onBeforeUnmount(() => {
|
||
if (dialog.value?.open) dialog.value.close();
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<dialog
|
||
ref="dialog"
|
||
class="ds-dialog"
|
||
:aria-labelledby="titleId"
|
||
:aria-describedby="description ? descriptionId : undefined"
|
||
@cancel="onCancel"
|
||
@click="onBackdrop"
|
||
>
|
||
<div class="ds-dialog__header">
|
||
<div>
|
||
<h2 :id="titleId" class="ds-dialog__title">{{ title }}</h2>
|
||
<p
|
||
v-if="description"
|
||
:id="descriptionId"
|
||
class="ds-dialog__description"
|
||
>
|
||
{{ description }}
|
||
</p>
|
||
</div>
|
||
<DsIconButton :label="closeLabel" @click="requestClose(true)"
|
||
>×</DsIconButton
|
||
>
|
||
</div>
|
||
<div class="ds-dialog__body"><slot /></div>
|
||
<footer v-if="$slots.footer" class="ds-dialog__footer">
|
||
<slot name="footer" :close="requestClose" />
|
||
</footer>
|
||
</dialog>
|
||
</template>
|