@bravostudioai/react 0.1.28 → 0.1.29
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/README.md +553 -0
- package/package.json +1 -1
- package/src/codegen/generator.ts +74 -0
- package/src/codegen/parser.ts +47 -707
- package/src/codegen/propQualification.ts +284 -0
- package/src/components/EncoreApp.tsx +92 -540
- package/src/components/EncoreContextProviders.tsx +60 -0
- package/src/hooks/useFontLoader.ts +84 -0
- package/src/hooks/usePusherUpdates.ts +14 -23
- package/src/hooks/useRepeatingContainers.ts +147 -0
- package/src/index.ts +4 -1
- package/src/lib/dataPatching.ts +78 -0
- package/src/lib/dynamicModules.ts +8 -9
- package/src/lib/fetcher.ts +2 -9
- package/src/lib/logger.ts +53 -0
- package/src/lib/moduleRegistry.ts +3 -1
- package/src/stores/useEncoreState.ts +62 -2
- package/src/version.ts +1 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable prop name qualification utilities
|
|
3
|
+
*
|
|
4
|
+
* When multiple components/inputs have the same base prop name,
|
|
5
|
+
* this module provides utilities to make each name unique by using
|
|
6
|
+
* minimal distinguishing parent paths.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { generateQualifiedPropName, findMinimalDistinguishingPath, arraysEqual } from './parser';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Base interface for items that can have their prop names qualified
|
|
13
|
+
*/
|
|
14
|
+
export interface QualifiableItem {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
propName: string;
|
|
18
|
+
_parentPath?: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Groups items by their prop name
|
|
23
|
+
*/
|
|
24
|
+
function groupByPropName<T extends QualifiableItem>(
|
|
25
|
+
items: T[]
|
|
26
|
+
): Map<string, T[]> {
|
|
27
|
+
const groups = new Map<string, T[]>();
|
|
28
|
+
|
|
29
|
+
items.forEach((item) => {
|
|
30
|
+
const baseName = item.propName;
|
|
31
|
+
if (!groups.has(baseName)) {
|
|
32
|
+
groups.set(baseName, []);
|
|
33
|
+
}
|
|
34
|
+
groups.get(baseName)!.push(item);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return groups;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Applies minimal distinguishing paths to a group of items with duplicate names
|
|
42
|
+
*/
|
|
43
|
+
function applyMinimalPaths<T extends QualifiableItem>(group: T[]): void {
|
|
44
|
+
group.forEach((item) => {
|
|
45
|
+
const otherPaths = group
|
|
46
|
+
.filter((other) => other.id !== item.id)
|
|
47
|
+
.map((other) => other._parentPath || []);
|
|
48
|
+
|
|
49
|
+
const minimalPath = findMinimalDistinguishingPath(
|
|
50
|
+
item._parentPath || [],
|
|
51
|
+
otherPaths
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
item.propName = generateQualifiedPropName(
|
|
55
|
+
item.name || 'item',
|
|
56
|
+
minimalPath
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Expands distinguishing paths iteratively until all names are unique
|
|
63
|
+
*/
|
|
64
|
+
function expandPathsUntilUnique<T extends QualifiableItem>(
|
|
65
|
+
group: T[],
|
|
66
|
+
allItems: T[]
|
|
67
|
+
): void {
|
|
68
|
+
let hasDuplicates = true;
|
|
69
|
+
let iteration = 0;
|
|
70
|
+
const maxIterations = 10; // Safety limit
|
|
71
|
+
|
|
72
|
+
while (hasDuplicates && iteration < maxIterations) {
|
|
73
|
+
iteration++;
|
|
74
|
+
|
|
75
|
+
// Group by current qualified names
|
|
76
|
+
const qualifiedNameGroups = new Map<string, T[]>();
|
|
77
|
+
group.forEach((item) => {
|
|
78
|
+
if (!qualifiedNameGroups.has(item.propName)) {
|
|
79
|
+
qualifiedNameGroups.set(item.propName, []);
|
|
80
|
+
}
|
|
81
|
+
qualifiedNameGroups.get(item.propName)!.push(item);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
hasDuplicates = false;
|
|
85
|
+
|
|
86
|
+
// For each group of duplicated qualified names, expand their paths
|
|
87
|
+
qualifiedNameGroups.forEach((dupGroup) => {
|
|
88
|
+
if (dupGroup.length > 1) {
|
|
89
|
+
hasDuplicates = true;
|
|
90
|
+
|
|
91
|
+
dupGroup.forEach((item) => {
|
|
92
|
+
const fullPath = item._parentPath || [];
|
|
93
|
+
const otherFullPaths = dupGroup
|
|
94
|
+
.filter((other) => other.id !== item.id)
|
|
95
|
+
.map((other) => other._parentPath || []);
|
|
96
|
+
|
|
97
|
+
// Find common prefix length
|
|
98
|
+
let commonPrefixLength = 0;
|
|
99
|
+
const maxCommonLength = Math.min(
|
|
100
|
+
fullPath.length,
|
|
101
|
+
...otherFullPaths.map((p) => p.length)
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < maxCommonLength; i++) {
|
|
105
|
+
const thisPart = fullPath[i];
|
|
106
|
+
const allMatch = otherFullPaths.every((otherPath) => otherPath[i] === thisPart);
|
|
107
|
+
if (allMatch) {
|
|
108
|
+
commonPrefixLength++;
|
|
109
|
+
} else {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Use progressively more of the distinguishing suffix
|
|
115
|
+
const distinguishingSuffix = fullPath.slice(commonPrefixLength);
|
|
116
|
+
|
|
117
|
+
// Try expanding until unique
|
|
118
|
+
let foundUnique = false;
|
|
119
|
+
for (
|
|
120
|
+
let suffixLength = 1;
|
|
121
|
+
suffixLength <= distinguishingSuffix.length;
|
|
122
|
+
suffixLength++
|
|
123
|
+
) {
|
|
124
|
+
const expandedPath = distinguishingSuffix.slice(0, suffixLength);
|
|
125
|
+
const testQualifiedName = generateQualifiedPropName(
|
|
126
|
+
item.name || 'item',
|
|
127
|
+
expandedPath
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Check if unique among ALL items
|
|
131
|
+
const isUnique = allItems.every((otherItem) => {
|
|
132
|
+
if (otherItem.id === item.id) return true;
|
|
133
|
+
|
|
134
|
+
// If in same duplicate group, compare expanded paths
|
|
135
|
+
if (dupGroup.some((d) => d.id === otherItem.id)) {
|
|
136
|
+
const otherFullPath = otherItem._parentPath || [];
|
|
137
|
+
const otherCommonPrefixLength = Math.min(
|
|
138
|
+
commonPrefixLength,
|
|
139
|
+
otherFullPath.length
|
|
140
|
+
);
|
|
141
|
+
const otherDistinguishingSuffix = otherFullPath.slice(
|
|
142
|
+
otherCommonPrefixLength
|
|
143
|
+
);
|
|
144
|
+
const otherExpandedPath = otherDistinguishingSuffix.slice(
|
|
145
|
+
0,
|
|
146
|
+
suffixLength
|
|
147
|
+
);
|
|
148
|
+
const otherQualifiedName = generateQualifiedPropName(
|
|
149
|
+
otherItem.name || 'item',
|
|
150
|
+
otherExpandedPath
|
|
151
|
+
);
|
|
152
|
+
return testQualifiedName !== otherQualifiedName;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// For items outside duplicate group, check final prop name
|
|
156
|
+
return testQualifiedName !== otherItem.propName;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (isUnique) {
|
|
160
|
+
item.propName = testQualifiedName;
|
|
161
|
+
foundUnique = true;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// If still not unique, use the distinguishing suffix we found
|
|
167
|
+
if (!foundUnique) {
|
|
168
|
+
item.propName = generateQualifiedPropName(
|
|
169
|
+
item.name || 'item',
|
|
170
|
+
distinguishingSuffix.length > 0 ? distinguishingSuffix : []
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Applies numeric suffixes as a last resort for items with identical paths
|
|
181
|
+
*/
|
|
182
|
+
function applyNumericSuffixes<T extends QualifiableItem>(group: T[]): void {
|
|
183
|
+
// Group by final qualified names
|
|
184
|
+
const finalQualifiedNameGroups = new Map<string, T[]>();
|
|
185
|
+
group.forEach((item) => {
|
|
186
|
+
if (!finalQualifiedNameGroups.has(item.propName)) {
|
|
187
|
+
finalQualifiedNameGroups.set(item.propName, []);
|
|
188
|
+
}
|
|
189
|
+
finalQualifiedNameGroups.get(item.propName)!.push(item);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
finalQualifiedNameGroups.forEach((finalDupGroup, finalQualifiedName) => {
|
|
193
|
+
if (finalDupGroup.length > 1) {
|
|
194
|
+
// Check if all duplicates have identical paths
|
|
195
|
+
const allPathsIdentical = finalDupGroup.every((item) => {
|
|
196
|
+
const thisPath = item._parentPath || [];
|
|
197
|
+
return finalDupGroup.every((otherItem) => {
|
|
198
|
+
if (otherItem.id === item.id) return true;
|
|
199
|
+
const otherPath = otherItem._parentPath || [];
|
|
200
|
+
return arraysEqual(thisPath, otherPath);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Only use numeric suffixes if paths are truly identical
|
|
205
|
+
if (allPathsIdentical) {
|
|
206
|
+
finalDupGroup.forEach((item, index) => {
|
|
207
|
+
if (index > 0) {
|
|
208
|
+
item.propName = `${finalQualifiedName}${index + 1}`;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Cleans up temporary _parentPath property from an item
|
|
218
|
+
*/
|
|
219
|
+
function cleanupParentPath<T extends QualifiableItem>(item: T): void {
|
|
220
|
+
delete item._parentPath;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Qualifies a single group of items with duplicate prop names
|
|
225
|
+
*/
|
|
226
|
+
function qualifyGroup<T extends QualifiableItem>(
|
|
227
|
+
group: T[],
|
|
228
|
+
allItems: T[]
|
|
229
|
+
): void {
|
|
230
|
+
if (group.length === 1) {
|
|
231
|
+
cleanupParentPath(group[0]);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Step 1: Apply minimal distinguishing paths
|
|
236
|
+
applyMinimalPaths(group);
|
|
237
|
+
|
|
238
|
+
// Step 2: Iteratively expand paths until all are unique
|
|
239
|
+
expandPathsUntilUnique(group, allItems);
|
|
240
|
+
|
|
241
|
+
// Step 3: Apply numeric suffixes as last resort
|
|
242
|
+
applyNumericSuffixes(group);
|
|
243
|
+
|
|
244
|
+
// Step 4: Cleanup temporary properties
|
|
245
|
+
group.forEach(cleanupParentPath);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Qualifies duplicate prop names using minimal distinguishing paths.
|
|
250
|
+
*
|
|
251
|
+
* When multiple components have the same prop name, this function
|
|
252
|
+
* finds the shortest parent path segment that makes each name unique.
|
|
253
|
+
*
|
|
254
|
+
* Algorithm:
|
|
255
|
+
* 1. Group items by base prop name
|
|
256
|
+
* 2. For each group with duplicates:
|
|
257
|
+
* a. Find minimal distinguishing paths
|
|
258
|
+
* b. Iteratively expand paths if duplicates remain
|
|
259
|
+
* c. Apply numeric suffixes if paths are identical
|
|
260
|
+
*
|
|
261
|
+
* @param items - Components/inputs to qualify (will be modified in place)
|
|
262
|
+
* @returns The same items array with qualified propName values
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* const items = [
|
|
266
|
+
* { id: '1', name: 'Title', propName: 'title', _parentPath: ['Header'] },
|
|
267
|
+
* { id: '2', name: 'Title', propName: 'title', _parentPath: ['Footer'] }
|
|
268
|
+
* ];
|
|
269
|
+
* qualifyPropNames(items);
|
|
270
|
+
* // Result: items[0].propName = 'headerTitle', items[1].propName = 'footerTitle'
|
|
271
|
+
*/
|
|
272
|
+
export function qualifyPropNames<T extends QualifiableItem>(
|
|
273
|
+
items: T[]
|
|
274
|
+
): T[] {
|
|
275
|
+
if (items.length === 0) return items;
|
|
276
|
+
|
|
277
|
+
const propNameGroups = groupByPropName(items);
|
|
278
|
+
|
|
279
|
+
propNameGroups.forEach((group) => {
|
|
280
|
+
qualifyGroup(group, items);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return items;
|
|
284
|
+
}
|