import { inject, provide } from 'vue'; /** * Use context * * @example * ```ts * // there are three vue files: A.vue, B.vue, C.vue, and A.vue is the parent component of B.vue and C.vue * * // context.ts * import { ref } from 'vue'; * import { useContext } from '@sa/hooks'; * * export const [provideDemoContext, useDemoContext] = useContext('demo', () => { * const count = ref(0); * * function increment() { * count.value++; * } * * function decrement() { * count.value--; * } * * return { * count, * increment, * decrement * }; * }) * ``` // A.vue * ```vue * * * ``` // B.vue * ```vue * * * ```; * * // C.vue is same as B.vue * * @param contextName Context name * @param fn Context function */ export default function useContext, T>( contextName: string, composable: (...args: Arguments) => T ) { const key = Symbol(contextName); /** * Injects the context value. * * @param consumerName - The name of the component that is consuming the context. If provided, the component must be * used within the context provider. * @param defaultValue - The default value to return if the context is not provided. * @returns The context value. */ const useInject = ( consumerName?: N, defaultValue?: T ): N extends null | undefined ? T | null : T => { const value = inject(key, defaultValue); if (consumerName && !value) { throw new Error(`\`${consumerName}\` must be used within \`${contextName}\``); } // @ts-expect-error - we want to return null if the value is undefined or null return value || null; }; const useProvide = (...args: Arguments) => { const value = composable(...args); provide(key, value); return value; }; return [useProvide, useInject] as const; }