forked from bem/bem-react
-
Notifications
You must be signed in to change notification settings - Fork 0
/
di.tsx
253 lines (207 loc) · 6.32 KB
/
di.tsx
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
import React, {
ReactNode,
FC,
ComponentType,
createContext,
useContext,
useRef,
createElement,
} from 'react'
export type RegistryContext = Record<string, Registry>
export const registryContext = createContext<RegistryContext>({})
const RegistriesConsumer = registryContext.Consumer
const RegistryProvider = registryContext.Provider
export function withRegistry(
...registries: Registry[]
): <P extends {}>(Component: ComponentType<P>) => FC<P>
export function withRegistry() {
// Use arguments instead of rest-arguments to get faster and more compact code.
const registries: Registry[] = [].slice.call(arguments)
return function WithRegistry<P extends {}>(Component: ComponentType<P>) {
const RegistryResolver: FC<P> = (props) => {
const providedRegistriesRef = useRef<RegistryContext | null>(null)
return (
<RegistriesConsumer>
{(contextRegistries) => {
if (providedRegistriesRef.current === null) {
const providedRegistries = { ...contextRegistries }
for (let i = 0; i < registries.length; i++) {
const registry = registries[i]
const overrides = providedRegistries[registry.id]
// eslint-disable-next-line no-nested-ternary
providedRegistries[registry.id] = registry.overridable
? overrides
? registry.merge(overrides)
: registry
: registry && overrides
? overrides.merge(registry)
: registry
}
providedRegistriesRef.current = providedRegistries
}
return (
<RegistryProvider value={providedRegistriesRef.current}>
{/* Use createElement instead of jsx to avoid __assign from tslib. */}
{createElement(Component, props)}
</RegistryProvider>
)
}}
</RegistriesConsumer>
)
}
if (__DEV__) {
const resolverValue = registries.map((registry) => registry.id).join(', ')
// TODO: Use setDisplayName util.
RegistryResolver.displayName = `RegistryResolver(${resolverValue})`
}
return RegistryResolver
}
}
export interface IRegistryConsumerProps {
id: string
children: (registry: any) => ReactNode
}
export const RegistryConsumer: FC<IRegistryConsumerProps> = (props) => (
<RegistriesConsumer>
{(registries) => {
if (__DEV__) {
if (!registries[props.id]) {
throw new Error(`Registry with id '${props.id}' not found.`)
}
}
return props.children(registries[props.id].snapshot())
}}
</RegistriesConsumer>
)
/**
* @deprecated consider using 'RegistryConsumer' instead
*/
export const ComponentRegistryConsumer = RegistryConsumer
export const useRegistries = () => {
return useContext(registryContext)
}
export function useRegistry(id: string) {
const registries = useRegistries()
return registries[id].snapshot()
}
/**
* @deprecated consider using 'useRegistry' instead
*/
export const useComponentRegistry = useRegistry
export interface IRegistryOptions {
id: string
overridable?: boolean
}
const registryOverloadMark = 'RegistryOverloadMark'
type SimpleOverload<T> = (Base: T) => T
interface IRegistryEntityOverload<T> {
$symbol: typeof registryOverloadMark
overload: SimpleOverload<T>
}
type IRegistryEntity<T = any> = T | IRegistryEntityOverload<T>
export type IRegistryEntities = Record<string, IRegistryEntity>
function withOverload<T>(overload: SimpleOverload<T>): IRegistryEntityOverload<T> {
return {
$symbol: registryOverloadMark,
overload,
}
}
function isOverload<T>(entity: IRegistryEntity<T>): entity is IRegistryEntityOverload<T> {
return (entity as IRegistryEntityOverload<T>).$symbol === registryOverloadMark
}
export class Registry {
id: string
overridable: boolean
private entities: IRegistryEntities = {}
constructor({ id, overridable = true }: IRegistryOptions) {
this.id = id
this.overridable = overridable
}
/**
* Set registry entry by id.
*
* @param id entry id
* @param entity valid registry entity
*/
set<T>(id: string, entity: T) {
this.entities[id] = entity
return this
}
/**
* Set extender for registry entry by id.
*
* @param id entry id
* @param overload valid registry entity extender
*/
extends<T>(id: string, overload: SimpleOverload<T>) {
this.entities[id] = withOverload(overload)
return this
}
/**
* Set react entities in registry via object literal.
*
* @param entitiesSet set of valid registry entities
*/
fill(entitiesSet: IRegistryEntities) {
for (const key in entitiesSet) {
this.entities[key] = entitiesSet[key]
}
return this
}
/**
* Get entry from registry by id.
*
* @param id entry id
*/
get<T>(id: string): IRegistryEntity<T> {
if (__DEV__) {
if (!this.entities[id]) {
throw new Error(`Entry with id '${id}' not found.`)
}
}
return this.entities[id]
}
/**
* Returns raw entities from registry.
*/
snapshot(): IRegistryEntities {
return this.entities
}
/**
* Override entities by external registry.
* @internal
*
* @param otherRegistry external registry
*/
merge(otherRegistry?: Registry) {
const clone = new Registry({ id: this.id, overridable: this.overridable })
clone.fill(this.entities)
if (!otherRegistry) return clone
const otherRegistryEntities = otherRegistry.snapshot()
for (const entityName in otherRegistryEntities) {
if (!otherRegistryEntities.hasOwnProperty(entityName)) continue
clone.entities[entityName] = this.mergeEntities(
clone.entities[entityName],
otherRegistryEntities[entityName],
)
}
return clone
}
/**
* Returns extended or replaced entity
*
* @param base base implementation
* @param overrides overridden implementation
*/
private mergeEntities(base: IRegistryEntity, overrides: IRegistryEntity): IRegistryEntity {
if (isOverload(overrides)) {
if (!base) return overrides
if (isOverload(base)) {
// If both entities are hocs, then create compose-hoc
return withOverload((Base) => overrides.overload(base.overload(Base)))
}
return overrides.overload(base)
}
return overrides
}
}