@memberjunction/react-runtime 2.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +3 -0
- package/README.md +224 -0
- package/dist/compiler/babel-config.d.ts +40 -0
- package/dist/compiler/babel-config.d.ts.map +1 -0
- package/dist/compiler/babel-config.js +52 -0
- package/dist/compiler/component-compiler.d.ts +22 -0
- package/dist/compiler/component-compiler.d.ts.map +1 -0
- package/dist/compiler/component-compiler.js +188 -0
- package/dist/compiler/index.d.ts +3 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +13 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/registry/component-registry.d.ts +32 -0
- package/dist/registry/component-registry.d.ts.map +1 -0
- package/dist/registry/component-registry.js +197 -0
- package/dist/registry/component-resolver.d.ts +29 -0
- package/dist/registry/component-resolver.d.ts.map +1 -0
- package/dist/registry/component-resolver.js +112 -0
- package/dist/registry/index.d.ts +3 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +7 -0
- package/dist/runtime/component-hierarchy.d.ts +44 -0
- package/dist/runtime/component-hierarchy.d.ts.map +1 -0
- package/dist/runtime/component-hierarchy.js +162 -0
- package/dist/runtime/component-wrapper.d.ts +18 -0
- package/dist/runtime/component-wrapper.d.ts.map +1 -0
- package/dist/runtime/component-wrapper.js +108 -0
- package/dist/runtime/error-boundary.d.ts +6 -0
- package/dist/runtime/error-boundary.d.ts.map +1 -0
- package/dist/runtime/error-boundary.js +139 -0
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +31 -0
- package/dist/runtime/prop-builder.d.ts +16 -0
- package/dist/runtime/prop-builder.d.ts.map +1 -0
- package/dist/runtime/prop-builder.js +161 -0
- package/dist/types/index.d.ts +98 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/package.json +36 -0
- package/src/compiler/babel-config.ts +97 -0
- package/src/compiler/component-compiler.ts +366 -0
- package/src/compiler/index.ts +15 -0
- package/src/index.ts +125 -0
- package/src/registry/component-registry.ts +379 -0
- package/src/registry/component-resolver.ts +275 -0
- package/src/registry/index.ts +7 -0
- package/src/runtime/component-hierarchy.ts +346 -0
- package/src/runtime/component-wrapper.ts +249 -0
- package/src/runtime/error-boundary.ts +242 -0
- package/src/runtime/index.ts +45 -0
- package/src/runtime/prop-builder.ts +290 -0
- package/src/types/index.ts +226 -0
- package/tsconfig.json +37 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Component hierarchy registration utilities for MemberJunction React Runtime.
|
|
3
|
+
* Provides functionality to register a hierarchy of components from Skip component specifications.
|
|
4
|
+
* @module @memberjunction/react-runtime/hierarchy
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
CompilationResult,
|
|
9
|
+
CompileOptions,
|
|
10
|
+
ComponentStyles,
|
|
11
|
+
RuntimeContext
|
|
12
|
+
} from '../types';
|
|
13
|
+
import { ComponentCompiler } from '../compiler';
|
|
14
|
+
import { ComponentRegistry } from '../registry';
|
|
15
|
+
|
|
16
|
+
import { ComponentSpec } from '../registry/component-resolver';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Result of a hierarchy registration operation
|
|
20
|
+
*/
|
|
21
|
+
export interface HierarchyRegistrationResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
registeredComponents: string[];
|
|
24
|
+
errors: ComponentRegistrationError[];
|
|
25
|
+
warnings: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Error information for component registration
|
|
30
|
+
*/
|
|
31
|
+
export interface ComponentRegistrationError {
|
|
32
|
+
componentName: string;
|
|
33
|
+
error: string;
|
|
34
|
+
phase: 'compilation' | 'registration' | 'validation';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options for hierarchy registration
|
|
39
|
+
*/
|
|
40
|
+
export interface HierarchyRegistrationOptions {
|
|
41
|
+
/** Component styles to apply to all components */
|
|
42
|
+
styles?: ComponentStyles;
|
|
43
|
+
/** Namespace for component registration */
|
|
44
|
+
namespace?: string;
|
|
45
|
+
/** Version for component registration */
|
|
46
|
+
version?: string;
|
|
47
|
+
/** Whether to continue on errors */
|
|
48
|
+
continueOnError?: boolean;
|
|
49
|
+
/** Whether to override existing components */
|
|
50
|
+
allowOverride?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Utility class for registering component hierarchies
|
|
55
|
+
*/
|
|
56
|
+
export class ComponentHierarchyRegistrar {
|
|
57
|
+
constructor(
|
|
58
|
+
private compiler: ComponentCompiler,
|
|
59
|
+
private registry: ComponentRegistry,
|
|
60
|
+
private runtimeContext: RuntimeContext
|
|
61
|
+
) {}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Registers a complete component hierarchy from a root specification
|
|
65
|
+
* @param rootSpec - The root component specification
|
|
66
|
+
* @param options - Registration options
|
|
67
|
+
* @returns Registration result with details about success/failures
|
|
68
|
+
*/
|
|
69
|
+
async registerHierarchy(
|
|
70
|
+
rootSpec: ComponentSpec,
|
|
71
|
+
options: HierarchyRegistrationOptions = {}
|
|
72
|
+
): Promise<HierarchyRegistrationResult> {
|
|
73
|
+
const {
|
|
74
|
+
styles,
|
|
75
|
+
namespace = 'Global',
|
|
76
|
+
version = 'v1',
|
|
77
|
+
continueOnError = true,
|
|
78
|
+
allowOverride = true
|
|
79
|
+
} = options;
|
|
80
|
+
|
|
81
|
+
const registeredComponents: string[] = [];
|
|
82
|
+
const errors: ComponentRegistrationError[] = [];
|
|
83
|
+
const warnings: string[] = [];
|
|
84
|
+
|
|
85
|
+
// Register the root component
|
|
86
|
+
const rootResult = await this.registerSingleComponent(
|
|
87
|
+
rootSpec,
|
|
88
|
+
{ styles, namespace, version, allowOverride }
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (rootResult.success) {
|
|
92
|
+
registeredComponents.push(rootSpec.componentName);
|
|
93
|
+
} else {
|
|
94
|
+
errors.push(rootResult.error!);
|
|
95
|
+
if (!continueOnError) {
|
|
96
|
+
return { success: false, registeredComponents, errors, warnings };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Register child components recursively
|
|
101
|
+
const childComponents = rootSpec.childComponents || rootSpec.components || [];
|
|
102
|
+
if (childComponents.length > 0) {
|
|
103
|
+
const childResult = await this.registerChildComponents(
|
|
104
|
+
childComponents,
|
|
105
|
+
{ styles, namespace, version, continueOnError, allowOverride },
|
|
106
|
+
registeredComponents,
|
|
107
|
+
errors,
|
|
108
|
+
warnings
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
success: errors.length === 0,
|
|
114
|
+
registeredComponents,
|
|
115
|
+
errors,
|
|
116
|
+
warnings
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Registers a single component from a specification
|
|
122
|
+
* @param spec - Component specification
|
|
123
|
+
* @param options - Registration options
|
|
124
|
+
* @returns Registration result for this component
|
|
125
|
+
*/
|
|
126
|
+
async registerSingleComponent(
|
|
127
|
+
spec: ComponentSpec,
|
|
128
|
+
options: {
|
|
129
|
+
styles?: ComponentStyles;
|
|
130
|
+
namespace?: string;
|
|
131
|
+
version?: string;
|
|
132
|
+
allowOverride?: boolean;
|
|
133
|
+
}
|
|
134
|
+
): Promise<{ success: boolean; error?: ComponentRegistrationError }> {
|
|
135
|
+
const { styles, namespace = 'Global', version = 'v1', allowOverride = true } = options;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
// Skip if no component code
|
|
139
|
+
if (!spec.componentCode) {
|
|
140
|
+
return {
|
|
141
|
+
success: true,
|
|
142
|
+
error: undefined
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Check if component already exists
|
|
147
|
+
const existingComponent = this.registry.get(spec.componentName, namespace, version);
|
|
148
|
+
if (existingComponent && !allowOverride) {
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
error: {
|
|
152
|
+
componentName: spec.componentName,
|
|
153
|
+
error: `Component already registered in ${namespace}/${version}`,
|
|
154
|
+
phase: 'registration'
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Compile the component
|
|
160
|
+
const compileOptions: CompileOptions = {
|
|
161
|
+
componentName: spec.componentName,
|
|
162
|
+
componentCode: spec.componentCode,
|
|
163
|
+
styles
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const compilationResult = await this.compiler.compile(compileOptions);
|
|
167
|
+
|
|
168
|
+
if (!compilationResult.success) {
|
|
169
|
+
return {
|
|
170
|
+
success: false,
|
|
171
|
+
error: {
|
|
172
|
+
componentName: spec.componentName,
|
|
173
|
+
error: compilationResult.error?.message || 'Unknown compilation error',
|
|
174
|
+
phase: 'compilation'
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Create component factory
|
|
180
|
+
const componentFactory = compilationResult.component!.component(this.runtimeContext, styles);
|
|
181
|
+
|
|
182
|
+
// Register the component
|
|
183
|
+
this.registry.register(
|
|
184
|
+
spec.componentName,
|
|
185
|
+
componentFactory.component,
|
|
186
|
+
namespace,
|
|
187
|
+
version
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
return { success: true };
|
|
191
|
+
|
|
192
|
+
} catch (error) {
|
|
193
|
+
return {
|
|
194
|
+
success: false,
|
|
195
|
+
error: {
|
|
196
|
+
componentName: spec.componentName,
|
|
197
|
+
error: error instanceof Error ? error.message : String(error),
|
|
198
|
+
phase: 'registration'
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Recursively registers child components
|
|
206
|
+
* @param children - Array of child component specifications
|
|
207
|
+
* @param options - Registration options
|
|
208
|
+
* @param registeredComponents - Array to track registered components
|
|
209
|
+
* @param errors - Array to collect errors
|
|
210
|
+
* @param warnings - Array to collect warnings
|
|
211
|
+
*/
|
|
212
|
+
private async registerChildComponents(
|
|
213
|
+
children: ComponentSpec[],
|
|
214
|
+
options: HierarchyRegistrationOptions,
|
|
215
|
+
registeredComponents: string[],
|
|
216
|
+
errors: ComponentRegistrationError[],
|
|
217
|
+
warnings: string[]
|
|
218
|
+
): Promise<void> {
|
|
219
|
+
for (const child of children) {
|
|
220
|
+
// Register this child
|
|
221
|
+
const childResult = await this.registerSingleComponent(child, {
|
|
222
|
+
styles: options.styles,
|
|
223
|
+
namespace: options.namespace,
|
|
224
|
+
version: options.version,
|
|
225
|
+
allowOverride: options.allowOverride
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
if (childResult.success) {
|
|
229
|
+
if (child.componentCode) {
|
|
230
|
+
registeredComponents.push(child.componentName);
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
errors.push(childResult.error!);
|
|
234
|
+
if (!options.continueOnError) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Register nested children recursively
|
|
240
|
+
const nestedChildren = child.childComponents || child.components || [];
|
|
241
|
+
if (nestedChildren.length > 0) {
|
|
242
|
+
await this.registerChildComponents(
|
|
243
|
+
nestedChildren,
|
|
244
|
+
options,
|
|
245
|
+
registeredComponents,
|
|
246
|
+
errors,
|
|
247
|
+
warnings
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Convenience function to register a component hierarchy
|
|
256
|
+
* @param rootSpec - The root component specification
|
|
257
|
+
* @param compiler - Component compiler instance
|
|
258
|
+
* @param registry - Component registry instance
|
|
259
|
+
* @param runtimeContext - Runtime context with React and other libraries
|
|
260
|
+
* @param options - Registration options
|
|
261
|
+
* @returns Registration result
|
|
262
|
+
*/
|
|
263
|
+
export async function registerComponentHierarchy(
|
|
264
|
+
rootSpec: ComponentSpec,
|
|
265
|
+
compiler: ComponentCompiler,
|
|
266
|
+
registry: ComponentRegistry,
|
|
267
|
+
runtimeContext: RuntimeContext,
|
|
268
|
+
options: HierarchyRegistrationOptions = {}
|
|
269
|
+
): Promise<HierarchyRegistrationResult> {
|
|
270
|
+
const registrar = new ComponentHierarchyRegistrar(compiler, registry, runtimeContext);
|
|
271
|
+
return registrar.registerHierarchy(rootSpec, options);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Validates a component specification before registration
|
|
276
|
+
* @param spec - Component specification to validate
|
|
277
|
+
* @returns Array of validation errors (empty if valid)
|
|
278
|
+
*/
|
|
279
|
+
export function validateComponentSpec(spec: ComponentSpec): string[] {
|
|
280
|
+
const errors: string[] = [];
|
|
281
|
+
|
|
282
|
+
if (!spec.componentName) {
|
|
283
|
+
errors.push('Component specification must have a componentName');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// If componentCode is provided, do basic validation
|
|
287
|
+
if (spec.componentCode) {
|
|
288
|
+
if (typeof spec.componentCode !== 'string') {
|
|
289
|
+
errors.push(`Component code for ${spec.componentName} must be a string`);
|
|
290
|
+
}
|
|
291
|
+
if (spec.componentCode.trim().length === 0) {
|
|
292
|
+
errors.push(`Component code for ${spec.componentName} cannot be empty`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Validate child components recursively
|
|
297
|
+
const children = spec.childComponents || spec.components || [];
|
|
298
|
+
children.forEach((child, index) => {
|
|
299
|
+
const childErrors = validateComponentSpec(child);
|
|
300
|
+
childErrors.forEach(error => {
|
|
301
|
+
errors.push(`Child ${index} (${child.componentName || 'unnamed'}): ${error}`);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return errors;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Flattens a component hierarchy into a list of all components
|
|
310
|
+
* @param rootSpec - The root component specification
|
|
311
|
+
* @returns Array of all component specifications in the hierarchy
|
|
312
|
+
*/
|
|
313
|
+
export function flattenComponentHierarchy(rootSpec: ComponentSpec): ComponentSpec[] {
|
|
314
|
+
const components: ComponentSpec[] = [rootSpec];
|
|
315
|
+
|
|
316
|
+
const children = rootSpec.childComponents || rootSpec.components || [];
|
|
317
|
+
children.forEach(child => {
|
|
318
|
+
components.push(...flattenComponentHierarchy(child));
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return components;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Counts the total number of components in a hierarchy
|
|
326
|
+
* @param rootSpec - The root component specification
|
|
327
|
+
* @param includeEmpty - Whether to include components without code
|
|
328
|
+
* @returns Total component count
|
|
329
|
+
*/
|
|
330
|
+
export function countComponentsInHierarchy(
|
|
331
|
+
rootSpec: ComponentSpec,
|
|
332
|
+
includeEmpty: boolean = false
|
|
333
|
+
): number {
|
|
334
|
+
let count = 0;
|
|
335
|
+
|
|
336
|
+
if (includeEmpty || rootSpec.componentCode) {
|
|
337
|
+
count = 1;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const children = rootSpec.childComponents || rootSpec.components || [];
|
|
341
|
+
children.forEach(child => {
|
|
342
|
+
count += countComponentsInHierarchy(child, includeEmpty);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
return count;
|
|
346
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview React component wrapper utilities.
|
|
3
|
+
* Provides HOCs and utilities for wrapping components with additional functionality.
|
|
4
|
+
* @module @memberjunction/react-runtime/runtime
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ComponentProps, ComponentLifecycle } from '../types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Options for wrapping a component
|
|
11
|
+
*/
|
|
12
|
+
export interface WrapperOptions {
|
|
13
|
+
/** Component display name */
|
|
14
|
+
displayName?: string;
|
|
15
|
+
/** Enable performance monitoring */
|
|
16
|
+
enableProfiling?: boolean;
|
|
17
|
+
/** Enable props logging */
|
|
18
|
+
logProps?: boolean;
|
|
19
|
+
/** Lifecycle hooks */
|
|
20
|
+
lifecycle?: ComponentLifecycle;
|
|
21
|
+
/** Default props */
|
|
22
|
+
defaultProps?: Partial<ComponentProps>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Wraps a React component with additional functionality
|
|
27
|
+
* @param React - React library instance
|
|
28
|
+
* @param Component - Component to wrap
|
|
29
|
+
* @param options - Wrapper options
|
|
30
|
+
* @returns Wrapped component
|
|
31
|
+
*/
|
|
32
|
+
export function wrapComponent(React: any, Component: any, options: WrapperOptions = {}): any {
|
|
33
|
+
const {
|
|
34
|
+
displayName,
|
|
35
|
+
enableProfiling = false,
|
|
36
|
+
logProps = false,
|
|
37
|
+
lifecycle = {},
|
|
38
|
+
defaultProps = {}
|
|
39
|
+
} = options;
|
|
40
|
+
|
|
41
|
+
// Create wrapper component
|
|
42
|
+
const WrappedComponent = React.forwardRef((props: any, ref: any) => {
|
|
43
|
+
const mergedProps = { ...defaultProps, ...props };
|
|
44
|
+
|
|
45
|
+
// Log props if enabled
|
|
46
|
+
React.useEffect(() => {
|
|
47
|
+
if (logProps) {
|
|
48
|
+
console.log(`[${displayName || Component.name}] Props:`, mergedProps);
|
|
49
|
+
}
|
|
50
|
+
}, [mergedProps]);
|
|
51
|
+
|
|
52
|
+
// Lifecycle: beforeMount
|
|
53
|
+
React.useEffect(() => {
|
|
54
|
+
if (lifecycle.beforeMount) {
|
|
55
|
+
lifecycle.beforeMount();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Lifecycle: afterMount
|
|
59
|
+
if (lifecycle.afterMount) {
|
|
60
|
+
lifecycle.afterMount();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Lifecycle: beforeUnmount
|
|
64
|
+
return () => {
|
|
65
|
+
if (lifecycle.beforeUnmount) {
|
|
66
|
+
lifecycle.beforeUnmount();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
// Lifecycle: beforeUpdate/afterUpdate
|
|
72
|
+
const prevPropsRef = React.useRef(mergedProps);
|
|
73
|
+
React.useEffect(() => {
|
|
74
|
+
const prevProps = prevPropsRef.current;
|
|
75
|
+
|
|
76
|
+
if (lifecycle.beforeUpdate) {
|
|
77
|
+
lifecycle.beforeUpdate(prevProps, mergedProps);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Schedule afterUpdate
|
|
81
|
+
if (lifecycle.afterUpdate) {
|
|
82
|
+
Promise.resolve().then(() => {
|
|
83
|
+
lifecycle.afterUpdate!(prevProps, mergedProps);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
prevPropsRef.current = mergedProps;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Render with profiling if enabled
|
|
91
|
+
if (enableProfiling && React.Profiler) {
|
|
92
|
+
return React.createElement(
|
|
93
|
+
React.Profiler,
|
|
94
|
+
{
|
|
95
|
+
id: displayName || Component.name || 'WrappedComponent',
|
|
96
|
+
onRender: (id: string, phase: string, actualDuration: number) => {
|
|
97
|
+
console.log(`[Profiler] ${id} (${phase}): ${actualDuration.toFixed(2)}ms`);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
React.createElement(Component, { ...mergedProps, ref })
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return React.createElement(Component, { ...mergedProps, ref });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Set display name
|
|
108
|
+
WrappedComponent.displayName = displayName || `Wrapped(${Component.displayName || Component.name || 'Component'})`;
|
|
109
|
+
|
|
110
|
+
return WrappedComponent;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Creates a memoized version of a component
|
|
115
|
+
* @param React - React library instance
|
|
116
|
+
* @param Component - Component to memoize
|
|
117
|
+
* @param propsAreEqual - Optional comparison function
|
|
118
|
+
* @returns Memoized component
|
|
119
|
+
*/
|
|
120
|
+
export function memoizeComponent(
|
|
121
|
+
React: any,
|
|
122
|
+
Component: any,
|
|
123
|
+
propsAreEqual?: (prevProps: any, nextProps: any) => boolean
|
|
124
|
+
): any {
|
|
125
|
+
return React.memo(Component, propsAreEqual);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Creates a lazy-loaded component
|
|
130
|
+
* @param React - React library instance
|
|
131
|
+
* @param loader - Function that returns a promise resolving to the component
|
|
132
|
+
* @param fallback - Optional loading fallback
|
|
133
|
+
* @returns Lazy component
|
|
134
|
+
*/
|
|
135
|
+
export function lazyComponent(
|
|
136
|
+
React: any,
|
|
137
|
+
loader: () => Promise<{ default: any }>,
|
|
138
|
+
fallback?: any
|
|
139
|
+
): any {
|
|
140
|
+
const LazyComponent = React.lazy(loader);
|
|
141
|
+
|
|
142
|
+
if (fallback) {
|
|
143
|
+
return (props: any) => React.createElement(
|
|
144
|
+
React.Suspense,
|
|
145
|
+
{ fallback },
|
|
146
|
+
React.createElement(LazyComponent, props)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return LazyComponent;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Injects additional props into a component
|
|
155
|
+
* @param React - React library instance
|
|
156
|
+
* @param Component - Component to inject props into
|
|
157
|
+
* @param injectedProps - Props to inject
|
|
158
|
+
* @returns Component with injected props
|
|
159
|
+
*/
|
|
160
|
+
export function injectProps(React: any, Component: any, injectedProps: any): any {
|
|
161
|
+
return React.forwardRef((props: any, ref: any) => {
|
|
162
|
+
const mergedProps = { ...injectedProps, ...props };
|
|
163
|
+
return React.createElement(Component, { ...mergedProps, ref });
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Creates a component that renders conditionally
|
|
169
|
+
* @param React - React library instance
|
|
170
|
+
* @param Component - Component to render conditionally
|
|
171
|
+
* @param condition - Condition function or boolean
|
|
172
|
+
* @param fallback - Optional fallback component
|
|
173
|
+
* @returns Conditional component
|
|
174
|
+
*/
|
|
175
|
+
export function conditionalComponent(
|
|
176
|
+
React: any,
|
|
177
|
+
Component: any,
|
|
178
|
+
condition: boolean | ((props: any) => boolean),
|
|
179
|
+
fallback?: any
|
|
180
|
+
): any {
|
|
181
|
+
return (props: any) => {
|
|
182
|
+
const shouldRender = typeof condition === 'function' ? condition(props) : condition;
|
|
183
|
+
|
|
184
|
+
if (shouldRender) {
|
|
185
|
+
return React.createElement(Component, props);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return fallback || null;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Creates a component with default error handling
|
|
194
|
+
* @param React - React library instance
|
|
195
|
+
* @param Component - Component to wrap
|
|
196
|
+
* @param errorHandler - Error handling function
|
|
197
|
+
* @returns Component with error handling
|
|
198
|
+
*/
|
|
199
|
+
export function withErrorHandler(
|
|
200
|
+
React: any,
|
|
201
|
+
Component: any,
|
|
202
|
+
errorHandler: (error: Error) => void
|
|
203
|
+
): any {
|
|
204
|
+
return class extends React.Component {
|
|
205
|
+
componentDidCatch(error: Error, errorInfo: any) {
|
|
206
|
+
errorHandler(error);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
render() {
|
|
210
|
+
return React.createElement(Component, this.props);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Creates a portal wrapper component
|
|
217
|
+
* @param React - React library instance
|
|
218
|
+
* @param ReactDOM - ReactDOM library instance
|
|
219
|
+
* @param Component - Component to render in portal
|
|
220
|
+
* @param container - DOM container element or selector
|
|
221
|
+
* @returns Portal component
|
|
222
|
+
*/
|
|
223
|
+
export function portalComponent(
|
|
224
|
+
React: any,
|
|
225
|
+
ReactDOM: any,
|
|
226
|
+
Component: any,
|
|
227
|
+
container: Element | string
|
|
228
|
+
): any {
|
|
229
|
+
return (props: any) => {
|
|
230
|
+
const [mountNode, setMountNode] = React.useState(null as Element | null);
|
|
231
|
+
|
|
232
|
+
React.useEffect(() => {
|
|
233
|
+
const node = typeof container === 'string'
|
|
234
|
+
? document.querySelector(container)
|
|
235
|
+
: container;
|
|
236
|
+
|
|
237
|
+
setMountNode(node);
|
|
238
|
+
}, []);
|
|
239
|
+
|
|
240
|
+
if (!mountNode || !ReactDOM.createPortal) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return ReactDOM.createPortal(
|
|
245
|
+
React.createElement(Component, props),
|
|
246
|
+
mountNode
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
}
|