diff --git a/packages/ui/src/components/va-modal/hooks/useModal.ts b/packages/ui/src/components/va-modal/hooks/useModal.ts index d61a3f0700..8545562ef7 100644 --- a/packages/ui/src/components/va-modal/hooks/useModal.ts +++ b/packages/ui/src/components/va-modal/hooks/useModal.ts @@ -1,14 +1,10 @@ -import { getCurrentInstance } from 'vue' import { createModalInstance } from '../modal' import { ModalOptions } from '../types' +import { getCurrentApp } from '../../../services/current-app' /** This hook can be used without plugin used */ export const useModal = () => { - const appContext = getCurrentInstance()?.appContext - - if (!appContext) { - throw new Error('useModal can be used only in setup function. You can use app.config.globalProperties.$vaModal outside setup function') - } + const appContext = getCurrentApp()._context /** * @param options can be message string or options object diff --git a/packages/ui/src/components/va-modal/types.ts b/packages/ui/src/components/va-modal/types.ts index 87b51ecd95..96461dc044 100644 --- a/packages/ui/src/components/va-modal/types.ts +++ b/packages/ui/src/components/va-modal/types.ts @@ -1,6 +1,5 @@ import { ExtractComponentPropTypes } from '../../utils/component-options' import VaModal from './VaModal.vue' -import { ExtractPropTypes } from 'vue' export type ModalSize = 'small' | 'medium' | 'large' @@ -15,4 +14,4 @@ export type ModalEmits = { 'onUpdate:modelValue'?: (value: boolean) => void; } -export type ModalOptions = Partial & ModalEmits, 'anchorClass'>> +export type ModalOptions = Partial, 'anchorClass'>> & ModalEmits diff --git a/packages/ui/src/components/va-toast/hooks/useToast.ts b/packages/ui/src/components/va-toast/hooks/useToast.ts index e93eda7ea2..2be50a910b 100644 --- a/packages/ui/src/components/va-toast/hooks/useToast.ts +++ b/packages/ui/src/components/va-toast/hooks/useToast.ts @@ -1,10 +1,9 @@ -import { getCurrentInstance } from 'vue' - import { createToastInstance, closeById, closeAllNotifications, NotificationOptions } from '../toast' +import { getCurrentApp } from '../../../services/current-app' /** This hook can be used without plugin used */ export const useToast = () => { - const appContext = getCurrentInstance()?.appContext + const appContext = getCurrentApp()._context const createdInThisSetupContext: string[] = [] diff --git a/packages/ui/src/services/current-app.ts b/packages/ui/src/services/current-app.ts index 1b6675ef28..089d20161e 100644 --- a/packages/ui/src/services/current-app.ts +++ b/packages/ui/src/services/current-app.ts @@ -1,17 +1,52 @@ -import type { App } from 'vue' -import { inject as vueInject } from 'vue' +import type { App, provide as vueProvide } from 'vue' +import { inject as vueInject, getCurrentInstance } from 'vue' /** * Similar to `getCurrentInstance` but for plugins, so we can use inject in plugins. */ let app: App | null +let singleApp: App | null | undefined -export const setCurrentApp = (instance: App | null) => { app = instance } -export const getCurrentApp = () => app +export const setCurrentApp = (instance: App | null) => { + app = instance -/** Wrapper around vue inject, so it can be used in plugins */ + if (singleApp && instance !== singleApp) { + // This means that user has multiple apps on page. + singleApp = null + } else { + singleApp = instance + } +} + +/** + * Returns current app if Vuestic UI is used in single app mode. + * + * @throws Error if Vuestic UI is used in multiple apps. + * @throws Error if Vuestic UI plugin is not installed. + */ +export const getCurrentApp = () => { + const app = getCurrentInstance()?.appContext.app + if (app) { return app } + + if (singleApp === undefined) { + throw new Error('Vuestic UI plugin is not installed.') + } + if (singleApp === null) { + throw new Error('Vuestic UI is used in multiple apps. You`re not allowed to use composable outside of setup function context.') + } + return singleApp +} + +/** Wrapper around vue inject, so we can use it in plugins and outside of setup context if only one app is used */ export const inject = ((key: string, value?: any) => { const app = getCurrentApp()?._context.provides[key] return app || vueInject(key, value) -}) as unknown as typeof vueInject +}) as typeof vueInject + +/** Wrapper around vue provide, so we can use it in plugins and outside of setup context if only one app is used */ +export const provide = ((key: string, value: any) => { + const provides = getCurrentInstance()?.appContext.provides || getCurrentApp()._context.provides + + provides[key] = value +}) as typeof vueProvide diff --git a/packages/ui/src/services/global-config/global-config.ts b/packages/ui/src/services/global-config/global-config.ts index 2982688b86..991b90d892 100644 --- a/packages/ui/src/services/global-config/global-config.ts +++ b/packages/ui/src/services/global-config/global-config.ts @@ -1,19 +1,18 @@ import cloneDeep from 'lodash/cloneDeep.js' -import { ref, getCurrentInstance } from 'vue' +import { Ref, ref } from 'vue' import { GlobalConfig, GlobalConfigUpdater, PartialGlobalConfig, ProvidedGlobalConfig } from './types' import { getComponentsDefaultConfig } from '../component-config' import { getIconDefaultConfig } from '../icon' import { getColorDefaultConfig } from '../color' import { getI18nConfigDefaults } from '../i18n' import { getBreakpointDefaultConfig } from '../breakpoint' -import { getGlobalProperty } from '../vue-plugin/utils' -import { getCurrentApp, inject } from '../current-app' +import { inject, provide } from '../current-app' import { mergeDeep } from '../../utils/merge-deep' import { getColorsClassesDefaultConfig } from '../colors-classes' export const GLOBAL_CONFIG = Symbol('GLOBAL_CONFIG') -export const createGlobalConfig = () => { +export const createGlobalConfig = (): ProvidedGlobalConfig => { const globalConfig = ref({ colors: getColorDefaultConfig(), icons: getIconDefaultConfig(), @@ -27,7 +26,7 @@ export const createGlobalConfig = () => { * TODO: if this try won't be success, may be remake to provide/inject */ routerComponent: undefined, - }) + }) as Ref const getGlobalConfig = (): GlobalConfig => globalConfig.value const setGlobalConfig = (updater: GlobalConfig | GlobalConfigUpdater) => { @@ -48,16 +47,6 @@ export const createGlobalConfig = () => { } } -const provideForCurrentApp = (provide: T) => { - const provides = getCurrentInstance()?.appContext.provides || getCurrentApp()?._context.provides - - if (!provides) { throw new Error('Vue app not found for provide') } - - provides[GLOBAL_CONFIG] = provide - - return provide -} - /** Use this function if you don't want to throw error if hook used outside setup function by useGlobalConfig */ export function useGlobalConfig () { let injected = inject(GLOBAL_CONFIG) as ProvidedGlobalConfig @@ -65,7 +54,7 @@ export function useGlobalConfig () { if (!injected) { injected = createGlobalConfig() - provideForCurrentApp(injected) + provide(GLOBAL_CONFIG, injected) } return injected diff --git a/packages/ui/src/utils/component-options/types.ts b/packages/ui/src/utils/component-options/types.ts index c3071b11a8..617bc3fe02 100644 --- a/packages/ui/src/utils/component-options/types.ts +++ b/packages/ui/src/utils/component-options/types.ts @@ -22,7 +22,7 @@ declare type ExtractDefineComponentPropsType = true extends boolean ? { export type ExtractComponentProps = true extends boolean ? ExtractDefineComponentPropsType : never export type ExtractComponentEmits = T extends ComponentOptionsBase ? E: [] -type UnPropType = T extends PropType ? P : never -export type ExtractComponentPropTypes = { - [K in keyof ExtractComponentProps]: UnPropType[K]['type']> -} +export type ExtractComponentPropTypes = + T extends (props: infer P, ...args: any) => any ? P : + T extends new () => { $props: infer P } ? NonNullable

: + {}