Props & results
Props & results
openModal accepts a typed props object and returns a typed promise. With one explicit generic on each side, the entire flow is type-checked.
Passing props
OpenModalOptions.props is typed as ExtractComponentProps<C> — derived from the component's defineProps, so TypeScript enforces the same contract as a normal usage:
<script setup lang="ts">
const props = defineProps<{
userId: number
readonly?: boolean
}>()
</script>
openModal(UserModal, {
props: {
userId: 42, // required: number ✓
readonly: true // optional: boolean ✓
// badProp: 'x' // ✗ Type error
}
})
Props are plain data — they are not reactive after the modal opens. For live updates, use event listeners or a shared store.
Event listeners
Pass an on map alongside props. Keys are emit event names; the package wires them through to the rendered component.
openModal(EditDialog, {
props: { id: 42 },
on: {
progress: (pct: number) => console.log('progress', pct),
'update:value': (v: string) => setValue(v)
}
})
Inside the modal, emit normally:
<script setup lang="ts">
const emit = defineEmits<{
progress: [pct: number]
'update:value': [v: string]
}>()
</script>
Listeners passed to openModal live for the lifetime of that handle and are discarded when the modal closes. For listeners that survive across re-opens, use useModal.
Returning a result
The flow:
openModal<TResult>(Component)returns aModalHandle<TResult>.- Inside the modal:
const { confirm, close } = useModalContext<TResult>(). confirm(data)→ handle resolves withdata.close()→ handle rejects withModalClosedError.
<script setup lang="ts">
import { ModalRoot, ModalContent, useModalContext } from '@kolirt/vue-modal'
defineOptions({ modalGroup: 'default' })
const props = defineProps<{ title: string }>()
interface SaveResult {
id: number
slug: string
}
const { confirm, close } = useModalContext<SaveResult>()
function save() {
confirm({ id: 1, slug: 'my-post' })
}
</script>
<template>
<ModalRoot class="root">
<ModalContent class="card">
<h2>{{ props.title }}</h2>
<button @click="close()">Cancel</button>
<button @click="save">Save</button>
</ModalContent>
</ModalRoot>
</template>
import { openModal } from '@kolirt/vue-modal'
import SaveDialog from './SaveDialog.vue'
const result = await openModal<{ id: number; slug: string }>(SaveDialog, {
props: { title: 'Save post' }
}).catch(() => null)
if (result) {
console.log(result.id, result.slug) // fully typed
}
The result type is not inferred from the component — annotate the same generic on both openModal<T> and useModalContext<T>.
Resolving from outside the modal
Pass { success: true, data } to any close call to resolve from the outside:
const handle = openModal<string>(MyDialog)
// later, from anywhere:
closeModalById(handle.id, { success: true, data: 'resolved externally' })
const value = await handle // 'resolved externally'
ModalClosedError
Dismissed modals (Esc, overlay click, close()) reject with ModalClosedError. Import it to distinguish dismiss from unexpected errors:
import { openModal, ModalClosedError } from '@kolirt/vue-modal'
try {
await openModal(MyDialog)
} catch (e) {
if (e instanceof ModalClosedError) {
// normal dismiss
} else {
throw e
}
}
