@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,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview React error boundary creation utilities.
|
|
3
|
+
* Provides platform-agnostic error boundary components for React applications.
|
|
4
|
+
* @module @memberjunction/react-runtime/runtime
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ErrorBoundaryOptions, ComponentError } from '../types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a React error boundary component class
|
|
11
|
+
* @param React - React library instance
|
|
12
|
+
* @param options - Error boundary options
|
|
13
|
+
* @returns Error boundary component class
|
|
14
|
+
*/
|
|
15
|
+
export function createErrorBoundary(React: any, options: ErrorBoundaryOptions = {}): any {
|
|
16
|
+
const {
|
|
17
|
+
onError,
|
|
18
|
+
fallback,
|
|
19
|
+
logErrors = true,
|
|
20
|
+
recovery = 'none'
|
|
21
|
+
} = options;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Error boundary component class
|
|
25
|
+
*/
|
|
26
|
+
return class ErrorBoundary extends React.Component {
|
|
27
|
+
state: { hasError: boolean; error: Error | null; errorInfo: any; retryCount: number };
|
|
28
|
+
|
|
29
|
+
constructor(props: any) {
|
|
30
|
+
super(props);
|
|
31
|
+
this.state = {
|
|
32
|
+
hasError: false,
|
|
33
|
+
error: null,
|
|
34
|
+
errorInfo: null,
|
|
35
|
+
retryCount: 0
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static getDerivedStateFromError(error: Error): any {
|
|
40
|
+
// Update state to trigger fallback UI
|
|
41
|
+
return { hasError: true, error };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
componentDidCatch(error: Error, errorInfo: any) {
|
|
45
|
+
// Log error if enabled
|
|
46
|
+
if (logErrors) {
|
|
47
|
+
console.error('React Error Boundary caught error:', error);
|
|
48
|
+
console.error('Error Info:', errorInfo);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Call custom error handler if provided
|
|
52
|
+
if (onError) {
|
|
53
|
+
try {
|
|
54
|
+
onError(error, errorInfo);
|
|
55
|
+
} catch (handlerError) {
|
|
56
|
+
console.error('Error in custom error handler:', handlerError);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Update state with error details
|
|
61
|
+
this.setState({ errorInfo });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
handleRetry = () => {
|
|
65
|
+
this.setState((prevState: any) => ({
|
|
66
|
+
hasError: false,
|
|
67
|
+
error: null,
|
|
68
|
+
errorInfo: null,
|
|
69
|
+
retryCount: prevState.retryCount + 1
|
|
70
|
+
}));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
handleReset = () => {
|
|
74
|
+
this.setState({
|
|
75
|
+
hasError: false,
|
|
76
|
+
error: null,
|
|
77
|
+
errorInfo: null,
|
|
78
|
+
retryCount: 0
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
render() {
|
|
83
|
+
if (this.state.hasError) {
|
|
84
|
+
// Use custom fallback if provided
|
|
85
|
+
if (fallback) {
|
|
86
|
+
if (typeof fallback === 'function') {
|
|
87
|
+
return fallback({
|
|
88
|
+
error: this.state.error,
|
|
89
|
+
errorInfo: this.state.errorInfo,
|
|
90
|
+
retry: this.handleRetry,
|
|
91
|
+
reset: this.handleReset,
|
|
92
|
+
retryCount: this.state.retryCount
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return fallback;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Default error UI
|
|
99
|
+
const showRetry = recovery === 'retry' && this.state.retryCount < 3;
|
|
100
|
+
const showReset = recovery === 'reset';
|
|
101
|
+
|
|
102
|
+
return React.createElement(
|
|
103
|
+
'div',
|
|
104
|
+
{
|
|
105
|
+
style: {
|
|
106
|
+
padding: '20px',
|
|
107
|
+
backgroundColor: '#f8f8f8',
|
|
108
|
+
border: '1px solid #ddd',
|
|
109
|
+
borderRadius: '4px',
|
|
110
|
+
margin: '10px'
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
React.createElement('h2', { style: { color: '#d32f2f' } }, 'Component Error'),
|
|
114
|
+
React.createElement(
|
|
115
|
+
'p',
|
|
116
|
+
{ style: { color: '#666' } },
|
|
117
|
+
'An error occurred while rendering this component.'
|
|
118
|
+
),
|
|
119
|
+
this.state.error && React.createElement(
|
|
120
|
+
'details',
|
|
121
|
+
{ style: { marginTop: '10px' } },
|
|
122
|
+
React.createElement(
|
|
123
|
+
'summary',
|
|
124
|
+
{ style: { cursor: 'pointer', color: '#333' } },
|
|
125
|
+
'Error Details'
|
|
126
|
+
),
|
|
127
|
+
React.createElement(
|
|
128
|
+
'pre',
|
|
129
|
+
{
|
|
130
|
+
style: {
|
|
131
|
+
backgroundColor: '#f0f0f0',
|
|
132
|
+
padding: '10px',
|
|
133
|
+
marginTop: '10px',
|
|
134
|
+
overflow: 'auto',
|
|
135
|
+
fontSize: '12px'
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
this.state.error.toString(),
|
|
139
|
+
'\n\n',
|
|
140
|
+
this.state.error.stack
|
|
141
|
+
)
|
|
142
|
+
),
|
|
143
|
+
(showRetry || showReset) && React.createElement(
|
|
144
|
+
'div',
|
|
145
|
+
{ style: { marginTop: '10px' } },
|
|
146
|
+
showRetry && React.createElement(
|
|
147
|
+
'button',
|
|
148
|
+
{
|
|
149
|
+
onClick: this.handleRetry,
|
|
150
|
+
style: {
|
|
151
|
+
padding: '8px 16px',
|
|
152
|
+
marginRight: '10px',
|
|
153
|
+
backgroundColor: '#1976d2',
|
|
154
|
+
color: 'white',
|
|
155
|
+
border: 'none',
|
|
156
|
+
borderRadius: '4px',
|
|
157
|
+
cursor: 'pointer'
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
`Retry (${3 - this.state.retryCount} attempts left)`
|
|
161
|
+
),
|
|
162
|
+
showReset && React.createElement(
|
|
163
|
+
'button',
|
|
164
|
+
{
|
|
165
|
+
onClick: this.handleReset,
|
|
166
|
+
style: {
|
|
167
|
+
padding: '8px 16px',
|
|
168
|
+
backgroundColor: '#757575',
|
|
169
|
+
color: 'white',
|
|
170
|
+
border: 'none',
|
|
171
|
+
borderRadius: '4px',
|
|
172
|
+
cursor: 'pointer'
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
'Reset Component'
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return this.props.children;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Creates a functional error boundary wrapper using React hooks
|
|
188
|
+
* @param React - React library instance
|
|
189
|
+
* @param Component - Component to wrap
|
|
190
|
+
* @param options - Error boundary options
|
|
191
|
+
* @returns Wrapped component with error boundary
|
|
192
|
+
*/
|
|
193
|
+
export function withErrorBoundary(React: any, Component: any, options: ErrorBoundaryOptions = {}): any {
|
|
194
|
+
const ErrorBoundaryComponent = createErrorBoundary(React, options);
|
|
195
|
+
|
|
196
|
+
return (props: any) => {
|
|
197
|
+
return React.createElement(
|
|
198
|
+
ErrorBoundaryComponent,
|
|
199
|
+
null,
|
|
200
|
+
React.createElement(Component, props)
|
|
201
|
+
);
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Formats a component error for display or logging
|
|
207
|
+
* @param error - Error to format
|
|
208
|
+
* @param componentName - Name of the component where error occurred
|
|
209
|
+
* @param phase - Phase when error occurred
|
|
210
|
+
* @returns Formatted component error
|
|
211
|
+
*/
|
|
212
|
+
export function formatComponentError(
|
|
213
|
+
error: Error,
|
|
214
|
+
componentName: string,
|
|
215
|
+
phase: ComponentError['phase']
|
|
216
|
+
): ComponentError {
|
|
217
|
+
return {
|
|
218
|
+
message: error.message || 'Unknown error',
|
|
219
|
+
stack: error.stack,
|
|
220
|
+
componentName,
|
|
221
|
+
phase,
|
|
222
|
+
details: {
|
|
223
|
+
name: error.name,
|
|
224
|
+
timestamp: new Date().toISOString()
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Creates a simple error logger for error boundaries
|
|
231
|
+
* @param componentName - Name of the component
|
|
232
|
+
* @returns Error logging function
|
|
233
|
+
*/
|
|
234
|
+
export function createErrorLogger(componentName: string): (error: Error, errorInfo: any) => void {
|
|
235
|
+
return (error: Error, errorInfo: any) => {
|
|
236
|
+
console.group(`🚨 React Component Error: ${componentName}`);
|
|
237
|
+
console.error('Error:', error);
|
|
238
|
+
console.error('Component Stack:', errorInfo.componentStack);
|
|
239
|
+
console.error('Props:', errorInfo.props);
|
|
240
|
+
console.groupEnd();
|
|
241
|
+
};
|
|
242
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Runtime module exports
|
|
3
|
+
* @module @memberjunction/react-runtime/runtime
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
createErrorBoundary,
|
|
8
|
+
withErrorBoundary,
|
|
9
|
+
formatComponentError,
|
|
10
|
+
createErrorLogger
|
|
11
|
+
} from './error-boundary';
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
wrapComponent,
|
|
15
|
+
memoizeComponent,
|
|
16
|
+
lazyComponent,
|
|
17
|
+
injectProps,
|
|
18
|
+
conditionalComponent,
|
|
19
|
+
withErrorHandler,
|
|
20
|
+
portalComponent,
|
|
21
|
+
WrapperOptions
|
|
22
|
+
} from './component-wrapper';
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
buildComponentProps,
|
|
26
|
+
normalizeCallbacks,
|
|
27
|
+
normalizeStyles,
|
|
28
|
+
validateComponentProps,
|
|
29
|
+
mergeProps,
|
|
30
|
+
createPropsTransformer,
|
|
31
|
+
wrapCallbacksWithLogging,
|
|
32
|
+
extractPropPaths,
|
|
33
|
+
PropBuilderOptions
|
|
34
|
+
} from './prop-builder';
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
ComponentHierarchyRegistrar,
|
|
38
|
+
registerComponentHierarchy,
|
|
39
|
+
validateComponentSpec,
|
|
40
|
+
flattenComponentHierarchy,
|
|
41
|
+
countComponentsInHierarchy,
|
|
42
|
+
HierarchyRegistrationResult,
|
|
43
|
+
ComponentRegistrationError,
|
|
44
|
+
HierarchyRegistrationOptions
|
|
45
|
+
} from './component-hierarchy';
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Props builder utilities for React components.
|
|
3
|
+
* Provides utilities for constructing and validating component props.
|
|
4
|
+
* @module @memberjunction/react-runtime/runtime
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ComponentProps, ComponentCallbacks, ComponentStyles } from '../types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Options for building component props
|
|
11
|
+
*/
|
|
12
|
+
export interface PropBuilderOptions {
|
|
13
|
+
/** Validate props before building */
|
|
14
|
+
validate?: boolean;
|
|
15
|
+
/** Merge with existing props */
|
|
16
|
+
merge?: boolean;
|
|
17
|
+
/** Transform data before passing to component */
|
|
18
|
+
transformData?: (data: any) => any;
|
|
19
|
+
/** Transform state before passing to component */
|
|
20
|
+
transformState?: (state: any) => any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Builds component props from various sources
|
|
25
|
+
* @param data - Component data
|
|
26
|
+
* @param userState - User state object
|
|
27
|
+
* @param utilities - Utility functions
|
|
28
|
+
* @param callbacks - Component callbacks
|
|
29
|
+
* @param components - Child components
|
|
30
|
+
* @param styles - Component styles
|
|
31
|
+
* @param options - Builder options
|
|
32
|
+
* @returns Built component props
|
|
33
|
+
*/
|
|
34
|
+
export function buildComponentProps(
|
|
35
|
+
data: any = {},
|
|
36
|
+
userState: any = {},
|
|
37
|
+
utilities: any = {},
|
|
38
|
+
callbacks: ComponentCallbacks = {},
|
|
39
|
+
components: Record<string, any> = {},
|
|
40
|
+
styles?: ComponentStyles,
|
|
41
|
+
options: PropBuilderOptions = {}
|
|
42
|
+
): ComponentProps {
|
|
43
|
+
const {
|
|
44
|
+
validate = true,
|
|
45
|
+
transformData,
|
|
46
|
+
transformState
|
|
47
|
+
} = options;
|
|
48
|
+
|
|
49
|
+
// Transform data if transformer provided
|
|
50
|
+
const transformedData = transformData ? transformData(data) : data;
|
|
51
|
+
const transformedState = transformState ? transformState(userState) : userState;
|
|
52
|
+
|
|
53
|
+
// Build props object
|
|
54
|
+
const props: ComponentProps = {
|
|
55
|
+
data: transformedData,
|
|
56
|
+
userState: transformedState,
|
|
57
|
+
utilities,
|
|
58
|
+
callbacks: normalizeCallbacks(callbacks),
|
|
59
|
+
components,
|
|
60
|
+
styles: normalizeStyles(styles)
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Validate if enabled
|
|
64
|
+
if (validate) {
|
|
65
|
+
validateComponentProps(props);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return props;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Normalizes component callbacks
|
|
73
|
+
* @param callbacks - Raw callbacks object
|
|
74
|
+
* @returns Normalized callbacks
|
|
75
|
+
*/
|
|
76
|
+
export function normalizeCallbacks(callbacks: any): ComponentCallbacks {
|
|
77
|
+
const normalized: ComponentCallbacks = {};
|
|
78
|
+
|
|
79
|
+
// Ensure all callbacks are functions
|
|
80
|
+
if (callbacks.RefreshData && typeof callbacks.RefreshData === 'function') {
|
|
81
|
+
normalized.RefreshData = callbacks.RefreshData;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (callbacks.OpenEntityRecord && typeof callbacks.OpenEntityRecord === 'function') {
|
|
85
|
+
normalized.OpenEntityRecord = callbacks.OpenEntityRecord;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (callbacks.UpdateUserState && typeof callbacks.UpdateUserState === 'function') {
|
|
89
|
+
normalized.UpdateUserState = callbacks.UpdateUserState;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (callbacks.NotifyEvent && typeof callbacks.NotifyEvent === 'function') {
|
|
93
|
+
normalized.NotifyEvent = callbacks.NotifyEvent;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return normalized;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Normalizes component styles
|
|
101
|
+
* @param styles - Raw styles object
|
|
102
|
+
* @returns Normalized styles
|
|
103
|
+
*/
|
|
104
|
+
export function normalizeStyles(styles?: any): any {
|
|
105
|
+
// Pass through the full styles object as-is
|
|
106
|
+
// This allows Skip components to access their full style structure
|
|
107
|
+
// including colors, typography, borders, etc.
|
|
108
|
+
return styles;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Validates component props
|
|
113
|
+
* @param props - Props to validate
|
|
114
|
+
* @throws Error if validation fails
|
|
115
|
+
*/
|
|
116
|
+
export function validateComponentProps(props: ComponentProps): void {
|
|
117
|
+
// Validate data
|
|
118
|
+
if (props.data === null || props.data === undefined) {
|
|
119
|
+
throw new Error('Component props.data cannot be null or undefined');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Validate userState
|
|
123
|
+
if (props.userState === null) {
|
|
124
|
+
throw new Error('Component props.userState cannot be null');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Validate utilities
|
|
128
|
+
if (props.utilities === null) {
|
|
129
|
+
throw new Error('Component props.utilities cannot be null');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Validate callbacks
|
|
133
|
+
if (!props.callbacks || typeof props.callbacks !== 'object') {
|
|
134
|
+
throw new Error('Component props.callbacks must be an object');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Validate callback functions
|
|
138
|
+
for (const [key, value] of Object.entries(props.callbacks)) {
|
|
139
|
+
if (value !== undefined && typeof value !== 'function') {
|
|
140
|
+
throw new Error(`Component callback "${key}" must be a function`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Merges multiple prop objects
|
|
147
|
+
* @param propsList - Array of props to merge
|
|
148
|
+
* @returns Merged props
|
|
149
|
+
*/
|
|
150
|
+
export function mergeProps(...propsList: Partial<ComponentProps>[]): ComponentProps {
|
|
151
|
+
const merged: ComponentProps = {
|
|
152
|
+
data: {},
|
|
153
|
+
userState: {},
|
|
154
|
+
utilities: {},
|
|
155
|
+
callbacks: {},
|
|
156
|
+
components: {},
|
|
157
|
+
styles: {}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
for (const props of propsList) {
|
|
161
|
+
if (props.data) {
|
|
162
|
+
merged.data = { ...merged.data, ...props.data };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (props.userState) {
|
|
166
|
+
merged.userState = { ...merged.userState, ...props.userState };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (props.utilities) {
|
|
170
|
+
merged.utilities = { ...merged.utilities, ...props.utilities };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (props.callbacks) {
|
|
174
|
+
merged.callbacks = { ...merged.callbacks, ...props.callbacks };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (props.components) {
|
|
178
|
+
merged.components = { ...merged.components, ...props.components };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (props.styles) {
|
|
182
|
+
merged.styles = { ...merged.styles, ...props.styles };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return merged;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Creates a props transformer function
|
|
191
|
+
* @param transformations - Map of prop paths to transformer functions
|
|
192
|
+
* @returns Props transformer
|
|
193
|
+
*/
|
|
194
|
+
export function createPropsTransformer(
|
|
195
|
+
transformations: Record<string, (value: any) => any>
|
|
196
|
+
): (props: ComponentProps) => ComponentProps {
|
|
197
|
+
return (props: ComponentProps) => {
|
|
198
|
+
const transformed = { ...props };
|
|
199
|
+
|
|
200
|
+
for (const [path, transformer] of Object.entries(transformations)) {
|
|
201
|
+
const pathParts = path.split('.');
|
|
202
|
+
let current: any = transformed;
|
|
203
|
+
|
|
204
|
+
// Navigate to the parent of the target property
|
|
205
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
206
|
+
if (!current[pathParts[i]]) {
|
|
207
|
+
current[pathParts[i]] = {};
|
|
208
|
+
}
|
|
209
|
+
current = current[pathParts[i]];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Apply transformation
|
|
213
|
+
const lastPart = pathParts[pathParts.length - 1];
|
|
214
|
+
if (current[lastPart] !== undefined) {
|
|
215
|
+
current[lastPart] = transformer(current[lastPart]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return transformed;
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Creates a callback wrapper that adds logging
|
|
225
|
+
* @param callbacks - Original callbacks
|
|
226
|
+
* @param componentName - Component name for logging
|
|
227
|
+
* @returns Wrapped callbacks
|
|
228
|
+
*/
|
|
229
|
+
export function wrapCallbacksWithLogging(
|
|
230
|
+
callbacks: ComponentCallbacks,
|
|
231
|
+
componentName: string
|
|
232
|
+
): ComponentCallbacks {
|
|
233
|
+
const wrapped: ComponentCallbacks = {};
|
|
234
|
+
|
|
235
|
+
if (callbacks.RefreshData) {
|
|
236
|
+
wrapped.RefreshData = () => {
|
|
237
|
+
console.log(`[${componentName}] RefreshData called`);
|
|
238
|
+
callbacks.RefreshData!();
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (callbacks.OpenEntityRecord) {
|
|
243
|
+
wrapped.OpenEntityRecord = (entityName: string, key: any) => {
|
|
244
|
+
console.log(`[${componentName}] OpenEntityRecord called:`, { entityName, key });
|
|
245
|
+
callbacks.OpenEntityRecord!(entityName, key);
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (callbacks.UpdateUserState) {
|
|
250
|
+
wrapped.UpdateUserState = (state: any) => {
|
|
251
|
+
console.log(`[${componentName}] UpdateUserState called:`, state);
|
|
252
|
+
callbacks.UpdateUserState!(state);
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (callbacks.NotifyEvent) {
|
|
257
|
+
wrapped.NotifyEvent = (event: string, data: any) => {
|
|
258
|
+
console.log(`[${componentName}] NotifyEvent called:`, { event, data });
|
|
259
|
+
callbacks.NotifyEvent!(event, data);
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return wrapped;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Extracts props paths used by a component
|
|
268
|
+
* @param componentCode - Component source code
|
|
269
|
+
* @returns Array of prop paths
|
|
270
|
+
*/
|
|
271
|
+
export function extractPropPaths(componentCode: string): string[] {
|
|
272
|
+
const paths: string[] = [];
|
|
273
|
+
|
|
274
|
+
// Simple regex patterns to find prop access
|
|
275
|
+
const patterns = [
|
|
276
|
+
/props\.data\.(\w+)/g,
|
|
277
|
+
/props\.userState\.(\w+)/g,
|
|
278
|
+
/props\.utilities\.(\w+)/g,
|
|
279
|
+
/props\.callbacks\.(\w+)/g
|
|
280
|
+
];
|
|
281
|
+
|
|
282
|
+
for (const pattern of patterns) {
|
|
283
|
+
let match;
|
|
284
|
+
while ((match = pattern.exec(componentCode)) !== null) {
|
|
285
|
+
paths.push(match[0]);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return [...new Set(paths)]; // Remove duplicates
|
|
290
|
+
}
|