Skip to content

Live Examples

Everything on this page runs against the real library — click and see.

Confirm dialog

The classic: await a boolean from the user. Clicking the overlay dismisses and rejects with SummonDismissedError.

result:
View source
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, ref } from 'vue'
import { summon, SummonDismissedError } from 'vue-summon'

import ConfirmDialog from './ConfirmDialog.vue'

const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))

const result = ref('')

async function open() {
  result.value = zh.value ? '等待用户操作…' : 'waiting for user…'
  try {
    const ok = await summon(ConfirmDialog, {
      title: zh.value ? '删除这份文件?' : 'Delete this file?',
      message: zh.value
        ? '此操作无法撤销。对话框通过 Promise 把用户的选择返回给调用方。'
        : "This action cannot be undone. The dialog returns the user's choice to the caller via a Promise.",
    })
    result.value = ok
      ? zh.value
        ? 'resolve(true) → 已确认'
        : 'resolve(true) → confirmed'
      : zh.value
        ? 'resolve(false) → 已取消'
        : 'resolve(false) → canceled'
  } catch (error) {
    if (error instanceof SummonDismissedError) {
      result.value = zh.value
        ? 'reject(SummonDismissedError) → 点击遮罩关闭'
        : 'reject(SummonDismissedError) → dismissed via overlay'
    }
  }
}
</script>

<template>
  <div class="vs-demo">
    <button class="vs-btn vs-btn-brand" @click="open">
      {{ zh ? '打开确认对话框' : 'Open confirm dialog' }}
    </button>
    <div class="vs-demo-result">
      {{ zh ? '结果' : 'result' }}: <strong>{{ result || '—' }}</strong>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed } from 'vue'
import { useSummoned } from 'vue-summon'

defineProps<{ title: string; message: string }>()

const { resolve, dismiss } = useSummoned<boolean>()
const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))
</script>

<template>
  <div class="vs-overlay" @click.self="dismiss()">
    <div class="vs-dialog">
      <h3>{{ title }}</h3>
      <p>{{ message }}</p>
      <div class="vs-dialog-actions">
        <button class="vs-btn vs-btn-ghost" @click="resolve(false)">
          {{ zh ? '取消' : 'Cancel' }}
        </button>
        <button class="vs-btn vs-btn-brand" @click="resolve(true)">
          {{ zh ? '确认' : 'Confirm' }}
        </button>
      </div>
    </div>
  </div>
</template>

Toast

Fire-and-forget, or await how it ended — auto-closed or clicked away.

result:
View source
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, ref } from 'vue'
import { summon } from 'vue-summon'

import ToastCard from './ToastCard.vue'

const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))

const count = ref(0)
const result = ref('')

async function push() {
  count.value += 1
  const message = zh.value ? `这是第 ${count.value} 条通知` : `Notification #${count.value}`
  const reason = await summon(ToastCard, { message })
  result.value = zh.value ? `toast resolve("${reason}")` : `toast resolved with "${reason}"`
}
</script>

<template>
  <div class="vs-demo">
    <button class="vs-btn vs-btn-brand" @click="push">
      {{ zh ? '弹出一个 Toast' : 'Push a toast' }}
    </button>
    <div class="vs-demo-result">
      {{ zh ? '结果' : 'result' }}: <strong>{{ result || '—' }}</strong>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, onBeforeUnmount, onMounted } from 'vue'
import { useSummoned } from 'vue-summon'

defineProps<{ message: string }>()

const { resolve } = useSummoned<string>()
const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))

let timer: ReturnType<typeof setTimeout> | undefined
onMounted(() => {
  timer = setTimeout(() => resolve('auto-closed'), 2600)
})
onBeforeUnmount(() => clearTimeout(timer))
</script>

<template>
  <div class="vs-toast" @click="resolve('clicked')">
    <span class="vs-toast-dot" />
    <span>{{ message }}</span>
    <span style="opacity: 0.55; font-size: 12px">{{ zh ? '点击关闭' : 'click to close' }}</span>
  </div>
</template>

Progress with update()

The promise is also the controller: patch props while the task runs, resolve when done.

result:
View source
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, ref } from 'vue'
import { summon, SummonDismissedError } from 'vue-summon'

import ProgressDialog from './ProgressDialog.vue'

const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))

const running = ref(false)
const result = ref('')

async function start() {
  if (running.value) return
  running.value = true
  result.value = zh.value ? '任务进行中…' : 'task running…'

  const task = summon(ProgressDialog, {
    title: zh.value ? '正在上传文件' : 'Uploading files',
    progress: 0,
  })

  let progress = 0
  const timer = setInterval(() => {
    progress = Math.min(100, progress + 4 + Math.random() * 10)
    task.update({ progress: Math.round(progress) })
    if (progress >= 100) {
      clearInterval(timer)
      setTimeout(() => task.resolve('done'), 350)
    }
  }, 320)

  try {
    await task
    result.value = zh.value ? 'resolve("done") → 上传完成' : 'resolve("done") → upload finished'
  } catch (error) {
    clearInterval(timer)
    if (error instanceof SummonDismissedError) {
      result.value = zh.value ? '任务被用户中止' : 'task aborted by user'
    }
  } finally {
    running.value = false
  }
}
</script>

<template>
  <div class="vs-demo">
    <button class="vs-btn vs-btn-brand" :disabled="running" @click="start">
      {{ running ? (zh ? '进行中…' : 'Running…') : zh ? '开始模拟任务' : 'Start mock task' }}
    </button>
    <div class="vs-demo-result">
      {{ zh ? '结果' : 'result' }}: <strong>{{ result || '—' }}</strong>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed } from 'vue'
import { useSummoned } from 'vue-summon'

const props = defineProps<{ title: string; progress: number }>()

const { dismiss } = useSummoned<string>()
const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))

const percent = computed(() => Math.min(100, Math.max(0, props.progress)))
</script>

<template>
  <div class="vs-overlay">
    <div class="vs-dialog">
      <h3>{{ title }}</h3>
      <div class="vs-progress-track">
        <div class="vs-progress-bar" :style="{ width: `${percent}%` }" />
      </div>
      <p style="margin-bottom: 16px">{{ percent }}%</p>
      <div class="vs-dialog-actions">
        <button class="vs-btn vs-btn-ghost" @click="dismiss()">
          {{ zh ? '取消任务' : 'Abort task' }}
        </button>
      </div>
    </div>
  </div>
</template>

Key dedupe

Summoning twice with the same key returns the same controller — only one instance ever renders.

log:
View source
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, ref } from 'vue'
import { summon } from 'vue-summon'

import KeyDialog from './KeyDialog.vue'

const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))

const log = ref('')

function summonTwice() {
  const first = summon(KeyDialog, {}, { key: 'singleton' })
  const second = summon(KeyDialog, {}, { key: 'singleton' })
  log.value = zh.value
    ? `first === second → ${first === second}(同一个控制器,只渲染一个实例)`
    : `first === second → ${first === second} (same controller, one instance rendered)`
  void first.then(() => {
    log.value = zh.value ? '对话框已 resolve("ok")' : 'dialog resolved with "ok"'
  })
}
</script>

<template>
  <div class="vs-demo">
    <button class="vs-btn vs-btn-brand" @click="summonTwice">
      {{ zh ? '以相同 key 召唤两次' : 'Summon twice with the same key' }}
    </button>
    <div class="vs-demo-result">
      log: <strong>{{ log || '—' }}</strong>
    </div>
  </div>
</template>
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed } from 'vue'
import { useSummoned } from 'vue-summon'

const { resolve } = useSummoned<string>()
const { lang } = useData()
const zh = computed(() => lang.value.startsWith('zh'))
</script>

<template>
  <div class="vs-overlay">
    <div class="vs-dialog">
      <h3>{{ zh ? '唯一实例' : 'A unique instance' }}</h3>
      <p>
        {{
          zh
            ? '这个对话框使用 key: "singleton" 召唤。在它打开期间,再次以相同 key 召唤不会创建新实例,而是返回同一个控制器。'
            : 'This dialog was summoned with key: "singleton". While it is open, summoning again with the same key returns the same controller instead of creating a new instance.'
        }}
      </p>
      <div class="vs-dialog-actions">
        <button class="vs-btn vs-btn-brand" @click="resolve('ok')">
          {{ zh ? '知道了' : 'Got it' }}
        </button>
      </div>
    </div>
  </div>
</template>

Transition

Set a default transition for every summoned instance on the host, or pass a full TransitionProps object:

vue
<template>
  <SummonHost transition="fade" />
  <!-- or -->
  <SummonHost :transition="{ name: 'zoom', mode: 'out-in' }" />
</template>

Released under the MIT License.