UNPKG

buefy

Version:

Lightweight UI components for Vue.js (v3) based on Bulma

125 lines (111 loc) 4.06 kB
import { createApp, h as createElement, inject } from 'vue' import type { App, ComponentPublicInstance, VNode, InjectionKey } from 'vue' import Snackbar from './Snackbar.vue' import type { SnackbarProps } from './Snackbar.vue' import config from '../../utils/config' import { copyAppContext, getComponentFromVNode } from '../../utils/helpers' import { registerComponentProgrammatic } from '../../utils/plugins' export type SnackbarOpenParams = Omit<SnackbarProps, 'message'> & { // programmatically opened snackbar can have VNode(s) as the message message?: string | VNode | (string | VNode)[] onClose?: () => void } // Minimal definition of a programmatically opened snackbar. // // ESLint does not like `{}` as a type but allowed here to make them look // similar to Vue's definition. /* eslint-disable @typescript-eslint/ban-types */ type SnackbarProgrammaticInstance = ComponentPublicInstance< {}, // P {}, // B {}, // D {}, // C { close: () => void } // M > /* eslint-enable @typescript-eslint/ban-types */ class SnackbarProgrammatic { app: App | undefined // may be undefined in the testing environment constructor(app?: App) { this.app = app } open(params: SnackbarOpenParams | string) { if (typeof params === 'string') { params = { message: params } } let slot: SnackbarOpenParams['message'] let { message, ...restParams } = params if (typeof message !== 'string') { slot = message message = undefined } const propsData: SnackbarProps = { type: 'is-success', position: config.defaultSnackbarPosition || 'is-bottom-right', queue: true, message, ...restParams } const container = document.createElement('div') // Vue 3 requires a new app to mount another component const vueInstance = createApp({ data() { return { snackbarVNode: null } }, methods: { close() { const snackbar = getComponentFromVNode(this.snackbarVNode) if (snackbar) { (snackbar as InstanceType<typeof Snackbar>).close() } } }, render() { this.snackbarVNode = createElement( Snackbar, { ...propsData, onClose() { if (typeof propsData.onClose === 'function') { propsData.onClose() } // timeout for the animation complete // before unmounting setTimeout(() => { vueInstance.unmount() }, 150) } }, slot != null ? { default: () => slot } : undefined ) return this.snackbarVNode } }) if (this.app) { copyAppContext(this.app, vueInstance) } else { // adds $buefy global property so that // this.$buefy.globalNoticeInterval is available // eslint-disable-next-line @typescript-eslint/no-explicit-any vueInstance.config.globalProperties.$buefy = {} as any } return vueInstance.mount(container) as SnackbarProgrammaticInstance } } const snackbarInjectionKey = Symbol('Buefy Snackbar') as InjectionKey<SnackbarProgrammatic> export function useSnackbar() { return inject(snackbarInjectionKey)! } const Plugin = { install(Vue: App) { registerComponentProgrammatic(Vue, 'snackbar', new SnackbarProgrammatic(Vue), snackbarInjectionKey) } } export default Plugin export { SnackbarProgrammatic, Snackbar as BSnackbar }