-
-
Notifications
You must be signed in to change notification settings - Fork 35
/
createProvide.ts
141 lines (115 loc) · 5.27 KB
/
createProvide.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import type { ComputedRef, InjectionKey } from 'vue'
import { computed, defineComponent, inject, provide, reactive } from 'vue'
function createProvide<ProvideValueType extends object | null>(
rootComponentName: string,
defaultProvide?: ProvideValueType,
) {
const Provide = Symbol(rootComponentName)
const Provider = defineComponent({
name: `${rootComponentName}Provider`,
inheritAttrs: false,
setup(props, { attrs, slots }) {
const value = reactive(attrs) as ProvideValueType
provide(Provide, value)
if (!slots || !slots.default)
throw new Error(`\`${rootComponentName}Provider\` must have a default slot :(`)
return () => slots.default?.()
},
})
function useContext(consumerName: string) {
const provide = inject(Provide)
if (provide)
return provide
if (defaultProvide !== undefined)
return defaultProvide
// if a defaultProvide wasn't specified, it's a required provide.
throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``)
}
return [Provider, useContext] as const
}
/* -------------------------------------------------------------------------------------------------
* createProvideScope
* ----------------------------------------------------------------------------------------------- */
type Scope<C = any> = { [scopeName: string]: { key: InjectionKey<C>; value: C }[] }
type ScopeHook = (scope: Scope) => ComputedRef<{ [__scopeProp: string]: Scope }>
interface CreateScope {
scopeName: string
(): ScopeHook
}
// function createInjectKey<ContextValueType extends object | null>(name: string): InjectionKey<ContextValueType> {
// return Symbol(name) as InjectionKey<ContextValueType>
// }
// type RefType<T> = ComputedRef<T> | Ref<T>
function createProvideScope(scopeName: string, createProvideScopeDeps: CreateScope[] = []) {
let defaultProviders: Scope[] = []
/* -----------------------------------------------------------------------------------------------
* createContext
* --------------------------------------------------------------------------------------------- */
function createProvide<ProvideValueType extends object | null>(
rootComponentName: string,
defaultValue?: ProvideValueType,
) {
const BaseProvideKey: InjectionKey<ProvideValueType | null> = Symbol(rootComponentName)
// const BaseProvide = provide(BaseProvideKey, defaultValue)
const BaseScope = { key: BaseProvideKey, value: defaultValue }
console.log('BaseScope', BaseScope)
const index = defaultProviders.length
defaultProviders = [...defaultProviders, { [scopeName]: [{ key: BaseProvideKey, value: defaultValue }] }]
function Provider(
props: ProvideValueType & { scope: Scope<ProvideValueType> },
) {
const { scope, ...context } = props as any
const Provide = scope?.[scopeName][index] || BaseScope.key as ProvideValueType
const value = computed<ProvideValueType>(() => context)
provide(Provide, value)
}
function useInject(consumerName: string, scope: Scope<ProvideValueType | undefined>): ComputedRef<ProvideValueType> {
const Provide = scope?.[scopeName][index] || BaseScope as ProvideValueType
const provide = inject(Provide.key)
if (provide)
return provide as ComputedRef<ProvideValueType>
if (defaultValue !== undefined)
return computed(() => defaultValue)
// // if a defaultProvide wasn't specified, it's a required provide.
throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``)
}
return [Provider, useInject] as const
}
/* -----------------------------------------------------------------------------------------------
* createScope
* --------------------------------------------------------------------------------------------- */
const createScope: CreateScope = () => {
const scopeProviders = defaultProviders
return function useScope(scope: Scope) {
const providers = scope?.[scopeName] || scopeProviders
return computed(() => ({ [`__scope${scopeName}`]: { ...scope, [scopeName]: providers } }))
}
}
createScope.scopeName = scopeName
return [createProvide, composeContextScopes(createScope, ...createProvideScopeDeps)] as const
}
function composeContextScopes(...scopes: CreateScope[]) {
const baseScope = scopes[0]
if (scopes.length === 1)
return baseScope
const createScope: CreateScope = () => {
const scopeHooks = scopes.map(createScope => ({
useScope: createScope(),
scopeName: createScope.scopeName,
}))
return function useComposedScopes(overrideScopes) {
const nextScopes = scopeHooks.reduce((nextScopes, { useScope, scopeName }) => {
// We are calling a hook inside a callback which React warns against to avoid inconsistent
// renders, however, scoping doesn't have render side effects so we ignore the rule.
const scopeProps = useScope(overrideScopes)
const currentScope = computed(() => scopeProps.value[`__scope${scopeName}`])
return { ...nextScopes, ...currentScope }
}, {})
return computed(() => ({ [`__scope${baseScope.scopeName}`]: nextScopes }))
}
}
createScope.scopeName = baseScope.scopeName
return createScope
}
export { createProvide, createProvideScope }
export type { CreateScope, Scope }