@developer_tribe/react-builder 0.1.17 → 0.1.18
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/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +6 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/types/Node.d.ts +1 -1
- package/dist/utils/patterns.d.ts +10 -0
- package/package.json +1 -1
- package/scripts/prebuild/utils/createGeneratedProps.js +39 -4
- package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +49 -7
- package/src/AttributesEditor.tsx +235 -14
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +7 -1
- package/src/build-components/OnboardButton/pattern.json +8 -1
- package/src/types/Node.ts +8 -1
- package/src/utils/patterns.ts +36 -0
package/dist/types/Node.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type NodeDefaultAttribute = Record<string, boolean | string | number | null | undefined | string[]
|
|
1
|
+
export type NodeDefaultAttribute = Record<string, boolean | string | number | null | undefined | string[] | Record<string, unknown> | Array<Record<string, unknown>>>;
|
|
2
2
|
export type Node<T = NodeDefaultAttribute> = null | undefined | string | {} | Node<T>[] | NodeData<T>;
|
|
3
3
|
export interface NodeData<T = Record<string, unknown>> {
|
|
4
4
|
type: string;
|
package/dist/utils/patterns.d.ts
CHANGED
|
@@ -6,7 +6,17 @@ type Pattern = {
|
|
|
6
6
|
children: unknown;
|
|
7
7
|
attributes: Record<string, string | string[]>;
|
|
8
8
|
};
|
|
9
|
+
types?: Record<string, Record<string, string | string[]>>;
|
|
9
10
|
};
|
|
10
11
|
export declare function getPatternByType(type?: string | null): Pattern | undefined;
|
|
11
12
|
export declare function getAttributeSchema(type?: string | null): Record<string, string | string[]> | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Returns the schema of a custom complex type declared under a component pattern's `types` block.
|
|
15
|
+
* For example, OnboardButton.pattern.types.EventObject
|
|
16
|
+
*/
|
|
17
|
+
export declare function getTypeSchema(componentType?: string | null, typeName?: string | null): Record<string, string | string[]> | undefined;
|
|
18
|
+
/** Utility: returns true if the type name refers to a primitive scalar */
|
|
19
|
+
export declare function isPrimitiveType(typeName: string): boolean;
|
|
20
|
+
/** Utility: parse `X[]` forms and return the item type if present */
|
|
21
|
+
export declare function getArrayItemType(typeName: string): string | null;
|
|
12
22
|
export {};
|
package/package.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
3
|
import { ensureDir } from './ensureDir.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { formatWithPrettier } from './formatWithPrettier.js';
|
|
5
|
+
|
|
6
|
+
// Helpers
|
|
7
|
+
const isPrimitive = t => t === 'string' || t === 'number' || t === 'boolean';
|
|
8
|
+
const getArrayItem = t =>
|
|
9
|
+
typeof t === 'string' && t.endsWith('[]') ? t.slice(0, -2) : null;
|
|
10
|
+
|
|
11
|
+
// Convert attribute type spec to TS type. Supports enum arrays, primitives, CustomType, and CustomType[]
|
|
12
|
+
function tsTypeFromAttributeType(attrType, allTypes) {
|
|
6
13
|
if (attrType === 'string') return 'string';
|
|
7
14
|
if (attrType === 'number') return 'number';
|
|
8
15
|
if (attrType === 'boolean') return 'boolean';
|
|
@@ -10,9 +17,19 @@ function tsTypeFromAttributeType(attrType) {
|
|
|
10
17
|
const literals = attrType.map(v => JSON.stringify(v)).join(' | ');
|
|
11
18
|
return literals.length > 0 ? literals : 'string';
|
|
12
19
|
}
|
|
20
|
+
if (typeof attrType === 'string') {
|
|
21
|
+
const item = getArrayItem(attrType);
|
|
22
|
+
if (item) {
|
|
23
|
+
if (isPrimitive(item)) return `${item}[]`;
|
|
24
|
+
// Custom type array
|
|
25
|
+
return `${item}Generated[]`;
|
|
26
|
+
}
|
|
27
|
+
if (isPrimitive(attrType)) return attrType;
|
|
28
|
+
// Custom object type
|
|
29
|
+
return `${attrType}Generated`;
|
|
30
|
+
}
|
|
13
31
|
return 'string';
|
|
14
32
|
}
|
|
15
|
-
import { formatWithPrettier } from './formatWithPrettier.js';
|
|
16
33
|
|
|
17
34
|
export async function createGeneratedProps(
|
|
18
35
|
componentDir,
|
|
@@ -25,8 +42,24 @@ export async function createGeneratedProps(
|
|
|
25
42
|
|
|
26
43
|
const { pattern, allowUnknownAttributes } = patternJson;
|
|
27
44
|
const attributes = pattern.attributes || {};
|
|
45
|
+
const allTypes = patternJson.types || {};
|
|
46
|
+
|
|
47
|
+
// Emit custom type interfaces if present
|
|
48
|
+
const customTypeEntries = Object.entries(allTypes);
|
|
49
|
+
const customTypeBlocks = customTypeEntries.map(([typeName, schema]) => {
|
|
50
|
+
const fields = Object.entries(schema).map(([k, t]) => {
|
|
51
|
+
const tsType = tsTypeFromAttributeType(t, allTypes);
|
|
52
|
+
return ` ${k}?: ${tsType};`;
|
|
53
|
+
});
|
|
54
|
+
return (
|
|
55
|
+
`export interface ${typeName}Generated {\n` +
|
|
56
|
+
(fields.length ? fields.join('\n') + '\n' : '') +
|
|
57
|
+
`}\n`
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
28
61
|
const attributeLines = Object.entries(attributes).map(([key, t]) => {
|
|
29
|
-
const tsType = tsTypeFromAttributeType(t);
|
|
62
|
+
const tsType = tsTypeFromAttributeType(t, allTypes);
|
|
30
63
|
return ` ${key}?: ${tsType};`;
|
|
31
64
|
});
|
|
32
65
|
|
|
@@ -48,6 +81,8 @@ export async function createGeneratedProps(
|
|
|
48
81
|
// Re-export a component props helper to avoid repeating the local type in each component file
|
|
49
82
|
`import type { NodeData } from '../../types/Node';\n` +
|
|
50
83
|
`\n` +
|
|
84
|
+
(customTypeBlocks.length ? customTypeBlocks.join('\n') + '\n' : '') +
|
|
85
|
+
`\n` +
|
|
51
86
|
`export interface ${componentName}PropsGenerated {\n` +
|
|
52
87
|
` child: ${normalizedChildTsType};\n` +
|
|
53
88
|
` attributes: {\n` +
|
|
@@ -116,19 +116,61 @@ async function validatePatternJson(componentDir, componentName) {
|
|
|
116
116
|
);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
// Helpers for validating custom types
|
|
120
|
+
const isPrimitive = t => t === 'string' || t === 'number' || t === 'boolean';
|
|
121
|
+
const hasCustomTypes = typeof data.types === 'object' && data.types != null;
|
|
122
|
+
const isCustomType = t =>
|
|
123
|
+
hasCustomTypes &&
|
|
124
|
+
typeof data.types[t] === 'object' &&
|
|
125
|
+
data.types[t] != null;
|
|
126
|
+
const isEnumArray = v =>
|
|
127
|
+
Array.isArray(v) && v.every(x => typeof x === 'string');
|
|
128
|
+
const isValidTypeRef = t => {
|
|
129
|
+
if (typeof t !== 'string') return false;
|
|
130
|
+
if (isPrimitive(t)) return true;
|
|
131
|
+
// Support arrays like X[]
|
|
132
|
+
if (t.endsWith('[]')) {
|
|
133
|
+
const base = t.slice(0, -2);
|
|
134
|
+
return isPrimitive(base) || isCustomType(base);
|
|
135
|
+
}
|
|
136
|
+
// Custom type name
|
|
137
|
+
return isCustomType(t);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Validate attributes accept primitive, enum array, or custom type refs (including X[])
|
|
119
141
|
for (const [attrName, attrType] of Object.entries(pattern.attributes)) {
|
|
120
|
-
const
|
|
121
|
-
typeof attrType === 'string' &&
|
|
122
|
-
(attrType
|
|
123
|
-
|
|
124
|
-
: Array.isArray(attrType) && attrType.every(v => typeof v === 'string');
|
|
125
|
-
if (!isValidType) {
|
|
142
|
+
const ok =
|
|
143
|
+
(typeof attrType === 'string' && isValidTypeRef(attrType)) ||
|
|
144
|
+
isEnumArray(attrType);
|
|
145
|
+
if (!ok) {
|
|
126
146
|
return fail(
|
|
127
|
-
`[${componentName}] pattern.json -> 'pattern.attributes.${attrName}' must be 'string' | 'number' | 'boolean' | string[]`
|
|
147
|
+
`[${componentName}] pattern.json -> 'pattern.attributes.${attrName}' must be 'string' | 'number' | 'boolean' | string[] | CustomType | CustomType[]`
|
|
128
148
|
);
|
|
129
149
|
}
|
|
130
150
|
}
|
|
131
151
|
|
|
152
|
+
// If types block exists, validate its shape (only primitives or enum arrays for fields)
|
|
153
|
+
if (hasCustomTypes) {
|
|
154
|
+
for (const [typeName, typeSchema] of Object.entries(data.types)) {
|
|
155
|
+
if (typeof typeSchema !== 'object' || typeSchema == null) {
|
|
156
|
+
return fail(
|
|
157
|
+
`[${componentName}] pattern.json -> 'types.${typeName}' must be an object`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
for (const [fieldName, fieldType] of Object.entries(typeSchema)) {
|
|
161
|
+
const fieldOk =
|
|
162
|
+
(typeof fieldType === 'string' &&
|
|
163
|
+
(isPrimitive(fieldType) || isValidTypeRef(fieldType))) ||
|
|
164
|
+
isEnumArray(fieldType);
|
|
165
|
+
if (!fieldOk) {
|
|
166
|
+
return fail(
|
|
167
|
+
`[${componentName}] pattern.json -> 'types.${typeName}.${fieldName}' must be 'string' | 'number' | 'boolean' | string[] | CustomType | CustomType[]`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
132
174
|
return data;
|
|
133
175
|
}
|
|
134
176
|
|
package/src/AttributesEditor.tsx
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Node, NodeData, NodeDefaultAttribute } from './types/Node';
|
|
3
3
|
import { isNodeString } from './utils/analyseNode';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getAttributeSchema,
|
|
6
|
+
getTypeSchema,
|
|
7
|
+
getArrayItemType,
|
|
8
|
+
isPrimitiveType,
|
|
9
|
+
} from './utils/patterns';
|
|
5
10
|
|
|
6
11
|
type AttributesEditorProps = {
|
|
7
12
|
node: Node;
|
|
@@ -13,12 +18,16 @@ function Field({
|
|
|
13
18
|
type,
|
|
14
19
|
value,
|
|
15
20
|
onChange,
|
|
21
|
+
componentType,
|
|
16
22
|
}: {
|
|
17
23
|
name: string;
|
|
18
24
|
type: string | string[];
|
|
19
25
|
value: any;
|
|
20
26
|
onChange: (v: any) => void;
|
|
27
|
+
// The current node's component type is needed to resolve custom type schemas
|
|
28
|
+
componentType?: string;
|
|
21
29
|
}) {
|
|
30
|
+
// Render enum selector
|
|
22
31
|
if (Array.isArray(type)) {
|
|
23
32
|
return (
|
|
24
33
|
<select
|
|
@@ -35,6 +44,188 @@ function Field({
|
|
|
35
44
|
</select>
|
|
36
45
|
);
|
|
37
46
|
}
|
|
47
|
+
|
|
48
|
+
// Arrays: detect X[] (including string[]/number[]/boolean[]/CustomType[])
|
|
49
|
+
const itemType = typeof type === 'string' ? getArrayItemType(type) : null;
|
|
50
|
+
if (itemType) {
|
|
51
|
+
const arr: any[] = Array.isArray(value) ? value : [];
|
|
52
|
+
|
|
53
|
+
// Primitive arrays with add/remove controls
|
|
54
|
+
if (isPrimitiveType(itemType)) {
|
|
55
|
+
return (
|
|
56
|
+
<div style={{ display: 'grid', gap: 8 }}>
|
|
57
|
+
{arr.map((item, idx) => (
|
|
58
|
+
<div
|
|
59
|
+
key={idx}
|
|
60
|
+
style={{ display: 'flex', gap: 8, alignItems: 'center' }}
|
|
61
|
+
>
|
|
62
|
+
{itemType === 'number' ? (
|
|
63
|
+
<input
|
|
64
|
+
type="number"
|
|
65
|
+
value={item ?? ''}
|
|
66
|
+
onChange={(e) => {
|
|
67
|
+
const next = [...arr];
|
|
68
|
+
next[idx] =
|
|
69
|
+
e.target.value === ''
|
|
70
|
+
? undefined
|
|
71
|
+
: Number(e.target.value);
|
|
72
|
+
onChange(next);
|
|
73
|
+
}}
|
|
74
|
+
className="input"
|
|
75
|
+
style={{ flex: 1 }}
|
|
76
|
+
/>
|
|
77
|
+
) : itemType === 'boolean' ? (
|
|
78
|
+
<input
|
|
79
|
+
type="checkbox"
|
|
80
|
+
checked={Boolean(item)}
|
|
81
|
+
onChange={(e) => {
|
|
82
|
+
const next = [...arr];
|
|
83
|
+
next[idx] = e.target.checked;
|
|
84
|
+
onChange(next);
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
) : (
|
|
88
|
+
<input
|
|
89
|
+
type="text"
|
|
90
|
+
value={item ?? ''}
|
|
91
|
+
onChange={(e) => {
|
|
92
|
+
const next = [...arr];
|
|
93
|
+
next[idx] =
|
|
94
|
+
e.target.value === '' ? undefined : e.target.value;
|
|
95
|
+
onChange(next);
|
|
96
|
+
}}
|
|
97
|
+
className="input"
|
|
98
|
+
style={{ flex: 1 }}
|
|
99
|
+
/>
|
|
100
|
+
)}
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
onClick={() => {
|
|
104
|
+
const next = arr.filter((_, i) => i !== idx);
|
|
105
|
+
onChange(next.length ? next : undefined);
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
remove
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
))}
|
|
112
|
+
<div>
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
onClick={() => {
|
|
116
|
+
const next = [
|
|
117
|
+
...arr,
|
|
118
|
+
itemType === 'boolean'
|
|
119
|
+
? false
|
|
120
|
+
: itemType === 'number'
|
|
121
|
+
? 0
|
|
122
|
+
: '',
|
|
123
|
+
];
|
|
124
|
+
onChange(next);
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
add
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Object arrays with nested editors
|
|
135
|
+
const schema = getTypeSchema(componentType, itemType) ?? {};
|
|
136
|
+
return (
|
|
137
|
+
<div style={{ display: 'grid', gap: 8 }}>
|
|
138
|
+
{arr.map((item, idx) => {
|
|
139
|
+
const obj = (item ?? {}) as Record<string, unknown>;
|
|
140
|
+
return (
|
|
141
|
+
<div
|
|
142
|
+
key={idx}
|
|
143
|
+
style={{ border: '1px solid #ddd', borderRadius: 6, padding: 8 }}
|
|
144
|
+
>
|
|
145
|
+
<div
|
|
146
|
+
style={{
|
|
147
|
+
display: 'grid',
|
|
148
|
+
gridTemplateColumns: '1fr 1fr',
|
|
149
|
+
gap: 8,
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
{Object.entries(schema).map(([fieldName, fieldType]) => (
|
|
153
|
+
<React.Fragment key={fieldName}>
|
|
154
|
+
<div style={{ alignSelf: 'center' }}>{fieldName}</div>
|
|
155
|
+
<Field
|
|
156
|
+
name={fieldName}
|
|
157
|
+
type={fieldType}
|
|
158
|
+
value={obj?.[fieldName as keyof typeof obj]}
|
|
159
|
+
onChange={(val) => {
|
|
160
|
+
const next = [...arr];
|
|
161
|
+
const nextObj = { ...(obj ?? {}), [fieldName]: val };
|
|
162
|
+
next[idx] = nextObj;
|
|
163
|
+
onChange(next);
|
|
164
|
+
}}
|
|
165
|
+
componentType={componentType}
|
|
166
|
+
/>
|
|
167
|
+
</React.Fragment>
|
|
168
|
+
))}
|
|
169
|
+
</div>
|
|
170
|
+
<div style={{ marginTop: 8 }}>
|
|
171
|
+
<button
|
|
172
|
+
type="button"
|
|
173
|
+
onClick={() => {
|
|
174
|
+
const next = arr.filter((_, i) => i !== idx);
|
|
175
|
+
onChange(next.length ? next : undefined);
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
remove
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
})}
|
|
184
|
+
<div>
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
onClick={() => {
|
|
188
|
+
const empty: Record<string, unknown> = {};
|
|
189
|
+
const next = [...arr, empty];
|
|
190
|
+
onChange(next);
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
add
|
|
194
|
+
</button>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Non-array complex object types defined under pattern `types`
|
|
201
|
+
if (typeof type === 'string' && !isPrimitiveType(type)) {
|
|
202
|
+
const schema = getTypeSchema(componentType, type);
|
|
203
|
+
if (schema) {
|
|
204
|
+
const obj = (value ?? {}) as Record<string, unknown>;
|
|
205
|
+
return (
|
|
206
|
+
<div
|
|
207
|
+
style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}
|
|
208
|
+
>
|
|
209
|
+
{Object.entries(schema).map(([fieldName, fieldType]) => (
|
|
210
|
+
<React.Fragment key={fieldName}>
|
|
211
|
+
<div style={{ alignSelf: 'center' }}>{fieldName}</div>
|
|
212
|
+
<Field
|
|
213
|
+
name={fieldName}
|
|
214
|
+
type={fieldType}
|
|
215
|
+
value={obj?.[fieldName as keyof typeof obj]}
|
|
216
|
+
onChange={(val) => {
|
|
217
|
+
const nextObj = { ...(obj ?? {}), [fieldName]: val };
|
|
218
|
+
onChange(nextObj);
|
|
219
|
+
}}
|
|
220
|
+
componentType={componentType}
|
|
221
|
+
/>
|
|
222
|
+
</React.Fragment>
|
|
223
|
+
))}
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
38
229
|
if (type === 'number') {
|
|
39
230
|
return (
|
|
40
231
|
<input
|
|
@@ -56,21 +247,50 @@ function Field({
|
|
|
56
247
|
/>
|
|
57
248
|
);
|
|
58
249
|
}
|
|
250
|
+
// Legacy support: string[]
|
|
59
251
|
if (type === 'string[]') {
|
|
60
|
-
const
|
|
252
|
+
const arr: string[] = Array.isArray(value) ? value : [];
|
|
61
253
|
return (
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
254
|
+
<div style={{ display: 'grid', gap: 8 }}>
|
|
255
|
+
{arr.map((item, idx) => (
|
|
256
|
+
<div
|
|
257
|
+
key={idx}
|
|
258
|
+
style={{ display: 'flex', gap: 8, alignItems: 'center' }}
|
|
259
|
+
>
|
|
260
|
+
<input
|
|
261
|
+
type="text"
|
|
262
|
+
value={item ?? ''}
|
|
263
|
+
onChange={(e) => {
|
|
264
|
+
const next = [...arr];
|
|
265
|
+
next[idx] = e.target.value;
|
|
266
|
+
onChange(next);
|
|
267
|
+
}}
|
|
268
|
+
className="input"
|
|
269
|
+
style={{ flex: 1 }}
|
|
270
|
+
/>
|
|
271
|
+
<button
|
|
272
|
+
type="button"
|
|
273
|
+
onClick={() => {
|
|
274
|
+
const next = arr.filter((_, i) => i !== idx);
|
|
275
|
+
onChange(next.length ? next : undefined);
|
|
276
|
+
}}
|
|
277
|
+
>
|
|
278
|
+
remove
|
|
279
|
+
</button>
|
|
280
|
+
</div>
|
|
281
|
+
))}
|
|
282
|
+
<div>
|
|
283
|
+
<button
|
|
284
|
+
type="button"
|
|
285
|
+
onClick={() => {
|
|
286
|
+
const next = [...arr, ''];
|
|
287
|
+
onChange(next);
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
add
|
|
291
|
+
</button>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
74
294
|
);
|
|
75
295
|
}
|
|
76
296
|
return (
|
|
@@ -114,6 +334,7 @@ export function AttributesEditor({ node, onChange }: AttributesEditorProps) {
|
|
|
114
334
|
};
|
|
115
335
|
onChange(next);
|
|
116
336
|
}}
|
|
337
|
+
componentType={data?.type}
|
|
117
338
|
/>
|
|
118
339
|
</React.Fragment>
|
|
119
340
|
))}
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import type { NodeData } from '../../types/Node';
|
|
4
4
|
|
|
5
|
+
export interface EventObjectGenerated {
|
|
6
|
+
type?: 'Permission' | 'Navigate';
|
|
7
|
+
permission?: string;
|
|
8
|
+
next_page_key?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
export interface OnboardButtonPropsGenerated {
|
|
6
12
|
child: string;
|
|
7
13
|
attributes: {
|
|
@@ -10,7 +16,7 @@ export interface OnboardButtonPropsGenerated {
|
|
|
10
16
|
button_background_color?: string;
|
|
11
17
|
flex?: number;
|
|
12
18
|
targetIndex?: number;
|
|
13
|
-
events?:
|
|
19
|
+
events?: EventObjectGenerated[];
|
|
14
20
|
};
|
|
15
21
|
}
|
|
16
22
|
|
|
@@ -10,7 +10,14 @@
|
|
|
10
10
|
"button_background_color": "string",
|
|
11
11
|
"flex": "number",
|
|
12
12
|
"targetIndex": "number",
|
|
13
|
-
"events": "
|
|
13
|
+
"events": "EventObject[]"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"types": {
|
|
17
|
+
"EventObject": {
|
|
18
|
+
"type": ["Permission", "Navigate"],
|
|
19
|
+
"permission": "string",
|
|
20
|
+
"next_page_key": "string"
|
|
14
21
|
}
|
|
15
22
|
}
|
|
16
23
|
}
|
package/src/types/Node.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
export type NodeDefaultAttribute = Record<
|
|
2
2
|
string,
|
|
3
|
-
|
|
3
|
+
| boolean
|
|
4
|
+
| string
|
|
5
|
+
| number
|
|
6
|
+
| null
|
|
7
|
+
| undefined
|
|
8
|
+
| string[]
|
|
9
|
+
| Record<string, unknown>
|
|
10
|
+
| Array<Record<string, unknown>>
|
|
4
11
|
>;
|
|
5
12
|
|
|
6
13
|
export type Node<T = NodeDefaultAttribute> =
|
package/src/utils/patterns.ts
CHANGED
|
@@ -26,6 +26,10 @@ type Pattern = {
|
|
|
26
26
|
children: unknown;
|
|
27
27
|
attributes: Record<string, string | string[]>;
|
|
28
28
|
};
|
|
29
|
+
// Optional custom complex types referenced by attributes like "X[]" or "X"
|
|
30
|
+
// Each entry maps a type name (e.g., "EventObject") to its field schema
|
|
31
|
+
// where the inner record maps fieldName -> primitive type or enum options
|
|
32
|
+
types?: Record<string, Record<string, string | string[]>>;
|
|
29
33
|
};
|
|
30
34
|
|
|
31
35
|
const patterns: Pattern[] = [
|
|
@@ -61,3 +65,35 @@ export function getAttributeSchema(
|
|
|
61
65
|
const p = getPatternByType(type);
|
|
62
66
|
return p?.pattern.attributes;
|
|
63
67
|
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns the schema of a custom complex type declared under a component pattern's `types` block.
|
|
71
|
+
* For example, OnboardButton.pattern.types.EventObject
|
|
72
|
+
*/
|
|
73
|
+
export function getTypeSchema(
|
|
74
|
+
componentType?: string | null,
|
|
75
|
+
typeName?: string | null,
|
|
76
|
+
): Record<string, string | string[]> | undefined {
|
|
77
|
+
if (!componentType || !typeName) return undefined;
|
|
78
|
+
const p = getPatternByType(componentType);
|
|
79
|
+
// Some JSON imports may not type-check "types" so we fallback to any access
|
|
80
|
+
const types: Record<string, Record<string, string | string[]>> | undefined = (
|
|
81
|
+
p as unknown as {
|
|
82
|
+
types?: Record<string, Record<string, string | string[]>>;
|
|
83
|
+
}
|
|
84
|
+
)?.types;
|
|
85
|
+
return types?.[typeName];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Utility: returns true if the type name refers to a primitive scalar */
|
|
89
|
+
export function isPrimitiveType(typeName: string): boolean {
|
|
90
|
+
return (
|
|
91
|
+
typeName === 'string' || typeName === 'number' || typeName === 'boolean'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Utility: parse `X[]` forms and return the item type if present */
|
|
96
|
+
export function getArrayItemType(typeName: string): string | null {
|
|
97
|
+
if (typeof typeName !== 'string') return null;
|
|
98
|
+
return typeName.endsWith('[]') ? typeName.slice(0, -2) : null;
|
|
99
|
+
}
|