[UI] Several improvements on the Modal component.

- Don't propagate `close` events. This prevents underlying modals from
  being closed on cascade when the current modal is closed.

- Added logic to filter out <ESC> keystrokes that have already targeted
  the outermost open modal, so underlying modals won't be closed.

- Added `:before-close` property. This is a callback that can optionally
  be passed to the component and it will run some custom logic before
  the modal is closed. If it returns false then the modal will stay
  open.
This commit is contained in:
Fabio Manganiello 2024-09-05 01:28:47 +02:00
parent b74b8aa154
commit e17abc34c1
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774

View file

@ -1,7 +1,11 @@
<template> <template>
<div class="modal-container fade-in" :id="id" :class="{hidden: !isVisible}" <div class="modal-container fade-in"
:style="{'--z-index': zIndex}" @click="close"> :class="{hidden: !isVisible}"
<div class="modal" :class="$attrs.class"> :id="id"
:style="{'--z-index': zIndex}"
ref="container"
@click.stop="close">
<div class="modal" :class="$attrs.class" ref="modal">
<div class="content" :style="{'--width': width, '--height': height}" @click.stop> <div class="content" :style="{'--width': width, '--height': height}" @click.stop>
<div class="header" :class="{uppercase: uppercase}" v-if="title"> <div class="header" :class="{uppercase: uppercase}" v-if="title">
<div class="title" v-text="title" v-if="title" /> <div class="title" v-text="title" v-if="title" />
@ -9,11 +13,11 @@
<button v-for="(button, index) in buttons" <button v-for="(button, index) in buttons"
:key="index" :key="index"
:title="button.title" :title="button.title"
@click="button.action"> @click.stop="button.action">
<i :class="button.icon" /> <i :class="button.icon" />
</button> </button>
<button title="Close" alt="Close" @click="close"> <button title="Close" alt="Close" @click.stop="close">
<i class="fas fa-xmark" /> <i class="fas fa-xmark" />
</button> </button>
</div> </div>
@ -27,6 +31,8 @@
</template> </template>
<script> <script>
import { bus } from "@/bus";
export default { export default {
name: "Modal", name: "Modal",
emits: ['close', 'open'], emits: ['close', 'open'],
@ -90,9 +96,10 @@ export default {
data() { data() {
return { return {
timeoutId: undefined, ignoreEscape: false,
prevVisible: this.visible,
isVisible: this.visible, isVisible: this.visible,
prevVisible: this.visible,
timeoutId: undefined,
} }
}, },
@ -103,12 +110,16 @@ export default {
}, },
methods: { methods: {
close() { close(event) {
if (this.beforeClose && !this.beforeClose()) if (this.beforeClose && !this.beforeClose())
return return
if (event)
event.preventDefault()
this.prevVisible = this.isVisible this.prevVisible = this.isVisible
this.isVisible = false this.isVisible = false
bus.emit('modal-close')
}, },
hide() { hide() {
@ -131,28 +142,61 @@ export default {
this.show() this.show()
}, },
onEscape() {
if (!this.isVisible || this.ignoreEscape)
return
const myZIndex = parseInt(getComputedStyle(this.$refs.container).zIndex)
const maxZIndex = Math.max(
...Array.from(
document.querySelectorAll('.modal-container:not(.hidden)')
).map((modal) =>
parseInt(getComputedStyle(modal).zIndex)
)
)
// Close only if it's the outermost modal
if (myZIndex === maxZIndex)
this.close()
},
onKeyUp(event) { onKeyUp(event) {
event.stopPropagation() event.stopPropagation()
if (event.key === 'Escape') { if (event.key === 'Escape') {
this.close() this.onEscape()
} }
}, },
onModalCloseMessage() {
if (!this.isVisible)
return
this.ignoreEscape = true
setTimeout(() => this.ignoreEscape = false, 100)
},
visibleHndl(visible) {
if (!visible)
this.$emit('close')
else
this.$emit('open')
},
},
watch: {
visible(value) {
this.visibleHndl(value)
this.isVisible = value
},
isVisible(value) {
this.visibleHndl(value)
},
}, },
mounted() { mounted() {
const self = this
const visibleHndl = (visible) => {
if (!visible)
self.$emit('close')
else
self.$emit('open')
self.isVisible = visible
}
document.body.addEventListener('keyup', this.onKeyUp) document.body.addEventListener('keyup', this.onKeyUp)
this.$watch(() => this.visible, visibleHndl) bus.on('modal-close', this.onModalCloseMessage)
this.$watch(() => this.isVisible, visibleHndl)
}, },
unmounted() { unmounted() {