Global events
Global events
The package emits two global lifecycle events you can subscribe to from anywhere — outside any specific modal component. Both come in a global form (every modal) and a group-scoped form (one group only), and both return an unsubscribe function.
import { onModalOpen, onModalClose } from '@kolirt/vue-modal'
| Event | Fires when… | Handler argument |
|---|---|---|
onModalOpen | A modal has just been pushed onto the stack. | ModalItem |
onModalClose | A modal has just been finalized and removed from the stack. | ModalItem |
emit('eventName', …)), use ModalHandle.on/off, the on option of openModal, or useModal(...).on. For pre-close interception inside the modal itself, use onBeforeClose from useModalContext. The functions on this page are the only global listeners exposed by the package.onModalOpen(handler) → () => void / onModalOpen(group, handler) → () => void
Subscribes to modal-open events. The handler runs synchronously every time a modal is pushed onto the stack, immediately after it is added to state.
Signature
type ModalOpenHandler = (modal: ModalItem) => void
function onModalOpen(handler: ModalOpenHandler): () => void
function onModalOpen(group: ModalGroup, handler: ModalOpenHandler): () => void
The ModalItem passed to the handler exposes id, group, component, props, and listeners — see Types.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
group | ModalGroup | no | Scopes the subscription to a single group (typed by your ModalGroupRegistry). Omit for a global subscription across all groups. |
handler | ModalOpenHandler | yes | Called with the freshly opened ModalItem. Errors thrown inside the handler are caught and logged via console.error — they never break the open flow or other subscribers. |
Example — close one group when another opens
import { onModalOpen, closeModalsByGroup } from '@kolirt/vue-modal'
// Whenever a 'main' modal opens, drop everything in 'sidebar'.
const off = onModalOpen('main', () => {
closeModalsByGroup('sidebar', { ignoreGuard: true })
})
// later:
off() // unsubscribe
Example — global telemetry
onModalOpen((modal) => {
analytics.track('modal_open', {
id: modal.id,
group: modal.group,
component: (modal.component as { __name?: string }).__name
})
})
modals.value or groupModals(g).value from inside the handler already includes the newly opened modal. The call is synchronous, so it's safe to act on the open immediately (e.g. close other groups) without waiting for nextTick.onModalClose(handler) → () => void / onModalClose(group, handler) → () => void
Subscribes to modal-close events. The handler runs synchronously when a modal is finalized — after beforeClose guards have passed, the exit animation completed (or was skipped via instantExit), and the modal has been removed from the stack. It fires exactly once per modal instance, regardless of close cause (user dismiss, confirm(...), closeModalById, bulk close, replaceModal).
Signature
type ModalCloseHandler = (modal: ModalItem) => void
function onModalClose(handler: ModalCloseHandler): () => void
function onModalClose(group: ModalGroup, handler: ModalCloseHandler): () => void
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
group | ModalGroup | no | Scopes the subscription to a single group. Omit for a global subscription. |
handler | ModalCloseHandler | yes | Called with the just-finalized ModalItem (the same shape onModalOpen received earlier). Errors thrown inside the handler are caught and logged — they never block the modal promise resolution. |
Timing relative to the modal promise
onModalClose fires before the openModal / useModal.open promise resolves or rejects. If you await openModal(...) and also subscribe via onModalClose, the global handler runs first.
const handle = openModal(MyDialog, { group: 'main' })
onModalClose('main', (m) => {
if (m.id === handle.id) console.log('global handler runs first')
})
try {
await handle
} finally {
console.log('handle settled second')
}
Example — restore focus on close
import { onModalClose } from '@kolirt/vue-modal'
let lastTrigger: HTMLElement | null = null
document.addEventListener('click', (e) => {
if ((e.target as HTMLElement).matches('[data-open-modal]')) {
lastTrigger = e.target as HTMLElement
}
})
onModalClose(() => {
lastTrigger?.focus()
lastTrigger = null
})
Example — clean up per-group side effects
import { onModalOpen, onModalClose } from '@kolirt/vue-modal'
const offOpen = onModalOpen('overlay', () => document.body.classList.add('has-overlay'))
const offClose = onModalClose('overlay', (_modal) => {
// last 'overlay' modal closed? drop the class
// (use `groupModals('overlay').value.length` if you import it)
document.body.classList.remove('has-overlay')
})
Symmetry and lifetime
- Both subscriptions live for the lifetime of the JavaScript module unless you call the returned unsubscribe function. They are not tied to a Vue component scope — if you call them inside
setup(), register the cleanup yourself inonScopeDispose/onBeforeUnmount. - Handlers are invoked in registration order. Global subscribers fire first, then group-scoped subscribers for the matching group.
- One throwing handler does not affect others: each handler is wrapped in try/catch and exceptions are reported via
console.error.
When to prefer reactive state instead
If you only need to render derived UI from the open stack, prefer the reactive State helpers (modals, isOpened, groupModals, isGroupOpen) and watch them. The event API is for side effects that must run exactly when the transition happens — analytics, focus restoration, cross-group coordination, manual DOM tweaks.
