@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
package/src/codegen/parser.ts
CHANGED
|
@@ -6,7 +6,20 @@ import {
|
|
|
6
6
|
SelectInputInfo,
|
|
7
7
|
ActionButtonInfo,
|
|
8
8
|
} from "./types";
|
|
9
|
+
import { qualifyPropNames } from "./propQualification";
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Sanitizes a component name into a valid camelCase prop name
|
|
13
|
+
*
|
|
14
|
+
* Removes special characters, handles spaces, and converts to camelCase.
|
|
15
|
+
*
|
|
16
|
+
* @param name - Raw component name from Figma/design tool
|
|
17
|
+
* @returns Valid JavaScript property name
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* sanitizePropName("My Component!") // "myComponent"
|
|
21
|
+
* sanitizePropName("user-name") // "userName"
|
|
22
|
+
*/
|
|
10
23
|
export function sanitizePropName(name: string): string {
|
|
11
24
|
// Convert to camelCase and remove invalid characters
|
|
12
25
|
const cleaned = name
|
|
@@ -350,219 +363,8 @@ export function findSlidersAndDataBindings(pageData: any): SliderInfo[] {
|
|
|
350
363
|
} as ComponentInfo & { _parentPath: string[] });
|
|
351
364
|
}
|
|
352
365
|
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
const propNameGroups = new Map<
|
|
356
|
-
string,
|
|
357
|
-
Array<ComponentInfo & { _parentPath: string[] }>
|
|
358
|
-
>();
|
|
359
|
-
components.forEach((comp) => {
|
|
360
|
-
const compWithPath = comp as ComponentInfo & {
|
|
361
|
-
_parentPath: string[];
|
|
362
|
-
};
|
|
363
|
-
const baseName = comp.propName;
|
|
364
|
-
if (!propNameGroups.has(baseName)) {
|
|
365
|
-
propNameGroups.set(baseName, []);
|
|
366
|
-
}
|
|
367
|
-
propNameGroups.get(baseName)!.push(compWithPath);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
// Second pass: for each group with duplicates, find minimal distinguishing paths
|
|
371
|
-
// and ensure all qualified names are unique
|
|
372
|
-
propNameGroups.forEach((group, _baseName) => {
|
|
373
|
-
if (group.length === 1) {
|
|
374
|
-
// No duplicates, keep the simple name
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// First, find minimal distinguishing paths for all components
|
|
379
|
-
group.forEach((comp) => {
|
|
380
|
-
const otherPaths = group
|
|
381
|
-
.filter((c) => c.id !== comp.id)
|
|
382
|
-
.map((c) => c._parentPath || []);
|
|
383
|
-
|
|
384
|
-
const minimalPath = findMinimalDistinguishingPath(
|
|
385
|
-
comp._parentPath || [],
|
|
386
|
-
otherPaths
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
// Use the minimal distinguishing path to qualify the name
|
|
390
|
-
comp.propName = generateQualifiedPropName(
|
|
391
|
-
comp.name || "item",
|
|
392
|
-
minimalPath
|
|
393
|
-
);
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
// Check if qualified names are still duplicates and expand paths if needed
|
|
397
|
-
let hasDuplicates = true;
|
|
398
|
-
let iteration = 0;
|
|
399
|
-
const maxIterations = 10; // Safety limit
|
|
400
|
-
|
|
401
|
-
while (hasDuplicates && iteration < maxIterations) {
|
|
402
|
-
iteration++;
|
|
403
|
-
const qualifiedNameGroups = new Map<
|
|
404
|
-
string,
|
|
405
|
-
Array<ComponentInfo & { _parentPath: string[] }>
|
|
406
|
-
>();
|
|
407
|
-
group.forEach((comp) => {
|
|
408
|
-
if (!qualifiedNameGroups.has(comp.propName)) {
|
|
409
|
-
qualifiedNameGroups.set(comp.propName, []);
|
|
410
|
-
}
|
|
411
|
-
qualifiedNameGroups.get(comp.propName)!.push(comp);
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
hasDuplicates = false;
|
|
415
|
-
// For each group of still-duplicated qualified names, expand their paths
|
|
416
|
-
qualifiedNameGroups.forEach((dupGroup, _qualifiedName) => {
|
|
417
|
-
if (dupGroup.length > 1) {
|
|
418
|
-
hasDuplicates = true;
|
|
419
|
-
// Expand the distinguishing path for each duplicate
|
|
420
|
-
dupGroup.forEach((comp) => {
|
|
421
|
-
// Find a longer distinguishing path by comparing with others in the duplicate group
|
|
422
|
-
const fullPath = comp._parentPath || [];
|
|
423
|
-
const otherFullPaths = dupGroup
|
|
424
|
-
.filter((c) => c.id !== comp.id)
|
|
425
|
-
.map((c) => c._parentPath || []);
|
|
426
|
-
|
|
427
|
-
// Find where this path diverges from others in the duplicate group
|
|
428
|
-
let commonPrefixLength = 0;
|
|
429
|
-
const maxCommonLength = Math.min(
|
|
430
|
-
fullPath.length,
|
|
431
|
-
...otherFullPaths.map((p) => p.length)
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
for (let i = 0; i < maxCommonLength; i++) {
|
|
435
|
-
const thisPart = fullPath[i];
|
|
436
|
-
const allMatch = otherFullPaths.every((otherPath) => {
|
|
437
|
-
return otherPath[i] === thisPart;
|
|
438
|
-
});
|
|
439
|
-
if (allMatch) {
|
|
440
|
-
commonPrefixLength++;
|
|
441
|
-
} else {
|
|
442
|
-
break;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Use progressively more of the distinguishing suffix until unique
|
|
447
|
-
const distinguishingSuffix =
|
|
448
|
-
fullPath.slice(commonPrefixLength);
|
|
449
|
-
|
|
450
|
-
// Try expanding the distinguishing suffix until we find a unique name
|
|
451
|
-
let foundUnique = false;
|
|
452
|
-
for (
|
|
453
|
-
let suffixLength = 1;
|
|
454
|
-
suffixLength <= distinguishingSuffix.length;
|
|
455
|
-
suffixLength++
|
|
456
|
-
) {
|
|
457
|
-
const expandedPath = distinguishingSuffix.slice(
|
|
458
|
-
0,
|
|
459
|
-
suffixLength
|
|
460
|
-
);
|
|
461
|
-
const testQualifiedName = generateQualifiedPropName(
|
|
462
|
-
comp.name || "item",
|
|
463
|
-
expandedPath
|
|
464
|
-
);
|
|
465
|
-
|
|
466
|
-
// Check if this qualified name is unique among ALL components (not just duplicates)
|
|
467
|
-
const isUnique = components.every((otherComp) => {
|
|
468
|
-
if (otherComp.id === comp.id) return true;
|
|
469
|
-
// If other component is in the same duplicate group, compare expanded paths
|
|
470
|
-
if (dupGroup.some((c) => c.id === otherComp.id)) {
|
|
471
|
-
const otherFullPath =
|
|
472
|
-
(
|
|
473
|
-
otherComp as ComponentInfo & {
|
|
474
|
-
_parentPath: string[];
|
|
475
|
-
}
|
|
476
|
-
)._parentPath || [];
|
|
477
|
-
const otherCommonPrefixLength = Math.min(
|
|
478
|
-
commonPrefixLength,
|
|
479
|
-
otherFullPath.length
|
|
480
|
-
);
|
|
481
|
-
const otherDistinguishingSuffix = otherFullPath.slice(
|
|
482
|
-
otherCommonPrefixLength
|
|
483
|
-
);
|
|
484
|
-
const otherExpandedPath =
|
|
485
|
-
otherDistinguishingSuffix.slice(0, suffixLength);
|
|
486
|
-
const otherQualifiedName = generateQualifiedPropName(
|
|
487
|
-
otherComp.name || "item",
|
|
488
|
-
otherExpandedPath
|
|
489
|
-
);
|
|
490
|
-
return testQualifiedName !== otherQualifiedName;
|
|
491
|
-
}
|
|
492
|
-
// For components outside the duplicate group, just check the final prop name
|
|
493
|
-
return testQualifiedName !== otherComp.propName;
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
if (isUnique) {
|
|
497
|
-
comp.propName = testQualifiedName;
|
|
498
|
-
foundUnique = true;
|
|
499
|
-
break;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// If we couldn't find a unique name with the distinguishing suffix,
|
|
504
|
-
// use the distinguishing suffix we found (it's the minimal we can do)
|
|
505
|
-
// We'll handle truly identical paths with numeric suffixes in the final pass
|
|
506
|
-
if (!foundUnique) {
|
|
507
|
-
// Use the distinguishing suffix - it's the minimal distinguishing part
|
|
508
|
-
// Even if it's not globally unique yet, it's better than the full path
|
|
509
|
-
comp.propName = generateQualifiedPropName(
|
|
510
|
-
comp.name || "item",
|
|
511
|
-
distinguishingSuffix.length > 0
|
|
512
|
-
? distinguishingSuffix
|
|
513
|
-
: []
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// Final check: if there are still duplicates after using full paths,
|
|
522
|
-
// and they have identical paths, use numeric suffixes as last resort
|
|
523
|
-
const finalQualifiedNameGroups = new Map<
|
|
524
|
-
string,
|
|
525
|
-
Array<ComponentInfo & { _parentPath: string[] }>
|
|
526
|
-
>();
|
|
527
|
-
group.forEach((comp) => {
|
|
528
|
-
if (!finalQualifiedNameGroups.has(comp.propName)) {
|
|
529
|
-
finalQualifiedNameGroups.set(comp.propName, []);
|
|
530
|
-
}
|
|
531
|
-
finalQualifiedNameGroups.get(comp.propName)!.push(comp);
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
finalQualifiedNameGroups.forEach(
|
|
535
|
-
(finalDupGroup, finalQualifiedName) => {
|
|
536
|
-
if (finalDupGroup.length > 1) {
|
|
537
|
-
// Check if all duplicates have identical paths
|
|
538
|
-
const allPathsIdentical = finalDupGroup.every((comp) => {
|
|
539
|
-
const thisPath = comp._parentPath || [];
|
|
540
|
-
return finalDupGroup.every((otherComp) => {
|
|
541
|
-
if (otherComp.id === comp.id) return true;
|
|
542
|
-
const otherPath = otherComp._parentPath || [];
|
|
543
|
-
return arraysEqual(thisPath, otherPath);
|
|
544
|
-
});
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
// Only use numeric suffixes if paths are truly identical
|
|
548
|
-
if (allPathsIdentical) {
|
|
549
|
-
let index = 0;
|
|
550
|
-
finalDupGroup.forEach((comp) => {
|
|
551
|
-
if (index > 0) {
|
|
552
|
-
comp.propName = `${finalQualifiedName}${index + 1}`;
|
|
553
|
-
}
|
|
554
|
-
index++;
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
);
|
|
560
|
-
|
|
561
|
-
// Remove the temporary _parentPath property
|
|
562
|
-
group.forEach((comp) => {
|
|
563
|
-
delete (comp as any)._parentPath;
|
|
564
|
-
});
|
|
565
|
-
});
|
|
366
|
+
// Qualify duplicate prop names using minimal distinguishing paths
|
|
367
|
+
qualifyPropNames(components);
|
|
566
368
|
|
|
567
369
|
// If we have an image component, remove color components with similar names
|
|
568
370
|
// (they're usually placeholders/backgrounds)
|
|
@@ -623,6 +425,15 @@ export function findSlidersAndDataBindings(pageData: any): SliderInfo[] {
|
|
|
623
425
|
return sliders;
|
|
624
426
|
}
|
|
625
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Finds standalone data-bound components (not in sliders)
|
|
430
|
+
*
|
|
431
|
+
* Locates components tagged with encore:data (but not encore:data:array)
|
|
432
|
+
* for standalone data binding at the page level.
|
|
433
|
+
*
|
|
434
|
+
* @param pageData - Page definition from Encore service
|
|
435
|
+
* @returns Array of component metadata
|
|
436
|
+
*/
|
|
626
437
|
export function findStandaloneComponents(pageData: any): ComponentInfo[] {
|
|
627
438
|
const components: ComponentInfo[] = [];
|
|
628
439
|
const sliderIds = new Set<string>();
|
|
@@ -722,209 +533,21 @@ export function findStandaloneComponents(pageData: any): ComponentInfo[] {
|
|
|
722
533
|
body.forEach((node: any) => traverse(node));
|
|
723
534
|
}
|
|
724
535
|
|
|
725
|
-
//
|
|
726
|
-
|
|
727
|
-
const propNameGroups = new Map<
|
|
728
|
-
string,
|
|
729
|
-
Array<ComponentInfo & { _parentPath: string[] }>
|
|
730
|
-
>();
|
|
731
|
-
components.forEach((comp) => {
|
|
732
|
-
const compWithPath = comp as ComponentInfo & { _parentPath: string[] };
|
|
733
|
-
const baseName = comp.propName;
|
|
734
|
-
if (!propNameGroups.has(baseName)) {
|
|
735
|
-
propNameGroups.set(baseName, []);
|
|
736
|
-
}
|
|
737
|
-
propNameGroups.get(baseName)!.push(compWithPath);
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
// Second pass: for each group with duplicates, find minimal distinguishing paths
|
|
741
|
-
// and ensure all qualified names are unique
|
|
742
|
-
propNameGroups.forEach((group, _baseName) => {
|
|
743
|
-
if (group.length === 1) {
|
|
744
|
-
// No duplicates, keep the simple name
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// First, find minimal distinguishing paths for all components
|
|
749
|
-
group.forEach((comp) => {
|
|
750
|
-
const otherPaths = group
|
|
751
|
-
.filter((c) => c.id !== comp.id)
|
|
752
|
-
.map((c) => c._parentPath || []);
|
|
753
|
-
|
|
754
|
-
const minimalPath = findMinimalDistinguishingPath(
|
|
755
|
-
comp._parentPath || [],
|
|
756
|
-
otherPaths
|
|
757
|
-
);
|
|
758
|
-
|
|
759
|
-
// Use the minimal distinguishing path to qualify the name
|
|
760
|
-
comp.propName = generateQualifiedPropName(
|
|
761
|
-
comp.name || "item",
|
|
762
|
-
minimalPath
|
|
763
|
-
);
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
// Check if qualified names are still duplicates and expand paths if needed
|
|
767
|
-
let hasDuplicates = true;
|
|
768
|
-
let iteration = 0;
|
|
769
|
-
const maxIterations = 10; // Safety limit
|
|
770
|
-
|
|
771
|
-
while (hasDuplicates && iteration < maxIterations) {
|
|
772
|
-
iteration++;
|
|
773
|
-
const qualifiedNameGroups = new Map<
|
|
774
|
-
string,
|
|
775
|
-
Array<ComponentInfo & { _parentPath: string[] }>
|
|
776
|
-
>();
|
|
777
|
-
group.forEach((comp) => {
|
|
778
|
-
if (!qualifiedNameGroups.has(comp.propName)) {
|
|
779
|
-
qualifiedNameGroups.set(comp.propName, []);
|
|
780
|
-
}
|
|
781
|
-
qualifiedNameGroups.get(comp.propName)!.push(comp);
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
hasDuplicates = false;
|
|
785
|
-
// For each group of still-duplicated qualified names, expand their paths
|
|
786
|
-
qualifiedNameGroups.forEach((dupGroup, _qualifiedName) => {
|
|
787
|
-
if (dupGroup.length > 1) {
|
|
788
|
-
hasDuplicates = true;
|
|
789
|
-
// Expand the distinguishing path for each duplicate
|
|
790
|
-
dupGroup.forEach((comp) => {
|
|
791
|
-
// Find a longer distinguishing path by comparing with others in the duplicate group
|
|
792
|
-
const fullPath = comp._parentPath || [];
|
|
793
|
-
const otherFullPaths = dupGroup
|
|
794
|
-
.filter((c) => c.id !== comp.id)
|
|
795
|
-
.map((c) => c._parentPath || []);
|
|
796
|
-
|
|
797
|
-
// Find where this path diverges from others in the duplicate group
|
|
798
|
-
let commonPrefixLength = 0;
|
|
799
|
-
const maxCommonLength = Math.min(
|
|
800
|
-
fullPath.length,
|
|
801
|
-
...otherFullPaths.map((p) => p.length)
|
|
802
|
-
);
|
|
803
|
-
|
|
804
|
-
for (let i = 0; i < maxCommonLength; i++) {
|
|
805
|
-
const thisPart = fullPath[i];
|
|
806
|
-
const allMatch = otherFullPaths.every((otherPath) => {
|
|
807
|
-
return otherPath[i] === thisPart;
|
|
808
|
-
});
|
|
809
|
-
if (allMatch) {
|
|
810
|
-
commonPrefixLength++;
|
|
811
|
-
} else {
|
|
812
|
-
break;
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// Use progressively more of the distinguishing suffix until unique
|
|
817
|
-
const distinguishingSuffix = fullPath.slice(commonPrefixLength);
|
|
818
|
-
|
|
819
|
-
// Try expanding the distinguishing suffix until we find a unique name
|
|
820
|
-
let foundUnique = false;
|
|
821
|
-
for (
|
|
822
|
-
let suffixLength = 1;
|
|
823
|
-
suffixLength <= distinguishingSuffix.length;
|
|
824
|
-
suffixLength++
|
|
825
|
-
) {
|
|
826
|
-
const expandedPath = distinguishingSuffix.slice(0, suffixLength);
|
|
827
|
-
const testQualifiedName = generateQualifiedPropName(
|
|
828
|
-
comp.name || "item",
|
|
829
|
-
expandedPath
|
|
830
|
-
);
|
|
831
|
-
|
|
832
|
-
// Check if this qualified name is unique among ALL components (not just duplicates)
|
|
833
|
-
const isUnique = components.every((otherComp) => {
|
|
834
|
-
if (otherComp.id === comp.id) return true;
|
|
835
|
-
// If other component is in the same duplicate group, compare expanded paths
|
|
836
|
-
if (dupGroup.some((c) => c.id === otherComp.id)) {
|
|
837
|
-
const otherFullPath =
|
|
838
|
-
(otherComp as ComponentInfo & { _parentPath: string[] })
|
|
839
|
-
._parentPath || [];
|
|
840
|
-
const otherCommonPrefixLength = Math.min(
|
|
841
|
-
commonPrefixLength,
|
|
842
|
-
otherFullPath.length
|
|
843
|
-
);
|
|
844
|
-
const otherDistinguishingSuffix = otherFullPath.slice(
|
|
845
|
-
otherCommonPrefixLength
|
|
846
|
-
);
|
|
847
|
-
const otherExpandedPath = otherDistinguishingSuffix.slice(
|
|
848
|
-
0,
|
|
849
|
-
suffixLength
|
|
850
|
-
);
|
|
851
|
-
const otherQualifiedName = generateQualifiedPropName(
|
|
852
|
-
otherComp.name || "item",
|
|
853
|
-
otherExpandedPath
|
|
854
|
-
);
|
|
855
|
-
return testQualifiedName !== otherQualifiedName;
|
|
856
|
-
}
|
|
857
|
-
// For components outside the duplicate group, just check the final prop name
|
|
858
|
-
return testQualifiedName !== otherComp.propName;
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
if (isUnique) {
|
|
862
|
-
comp.propName = testQualifiedName;
|
|
863
|
-
foundUnique = true;
|
|
864
|
-
break;
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
// If we couldn't find a unique name with the distinguishing suffix,
|
|
869
|
-
// use the full path to ensure uniqueness (even if it makes names longer)
|
|
870
|
-
if (!foundUnique) {
|
|
871
|
-
comp.propName = generateQualifiedPropName(
|
|
872
|
-
comp.name || "item",
|
|
873
|
-
fullPath
|
|
874
|
-
);
|
|
875
|
-
}
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
// Final check: if there are still duplicates after using full paths,
|
|
882
|
-
// and they have identical paths, use numeric suffixes as last resort
|
|
883
|
-
const finalQualifiedNameGroups = new Map<
|
|
884
|
-
string,
|
|
885
|
-
Array<ComponentInfo & { _parentPath: string[] }>
|
|
886
|
-
>();
|
|
887
|
-
group.forEach((comp) => {
|
|
888
|
-
if (!finalQualifiedNameGroups.has(comp.propName)) {
|
|
889
|
-
finalQualifiedNameGroups.set(comp.propName, []);
|
|
890
|
-
}
|
|
891
|
-
finalQualifiedNameGroups.get(comp.propName)!.push(comp);
|
|
892
|
-
});
|
|
893
|
-
|
|
894
|
-
finalQualifiedNameGroups.forEach((finalDupGroup, finalQualifiedName) => {
|
|
895
|
-
if (finalDupGroup.length > 1) {
|
|
896
|
-
// Check if all duplicates have identical paths
|
|
897
|
-
const allPathsIdentical = finalDupGroup.every((comp) => {
|
|
898
|
-
const thisPath = comp._parentPath || [];
|
|
899
|
-
return finalDupGroup.every((otherComp) => {
|
|
900
|
-
if (otherComp.id === comp.id) return true;
|
|
901
|
-
const otherPath = otherComp._parentPath || [];
|
|
902
|
-
return arraysEqual(thisPath, otherPath);
|
|
903
|
-
});
|
|
904
|
-
});
|
|
905
|
-
|
|
906
|
-
// Only use numeric suffixes if paths are truly identical
|
|
907
|
-
if (allPathsIdentical) {
|
|
908
|
-
let index = 0;
|
|
909
|
-
finalDupGroup.forEach((comp) => {
|
|
910
|
-
if (index > 0) {
|
|
911
|
-
comp.propName = `${finalQualifiedName}${index + 1}`;
|
|
912
|
-
}
|
|
913
|
-
index++;
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
});
|
|
918
|
-
|
|
919
|
-
// Remove the temporary _parentPath property
|
|
920
|
-
group.forEach((comp) => {
|
|
921
|
-
delete (comp as any)._parentPath;
|
|
922
|
-
});
|
|
923
|
-
});
|
|
536
|
+
// Qualify duplicate prop names using minimal distinguishing paths
|
|
537
|
+
qualifyPropNames(components);
|
|
924
538
|
|
|
925
539
|
return components;
|
|
926
540
|
}
|
|
927
541
|
|
|
542
|
+
/**
|
|
543
|
+
* Finds input groups (radio button-like components) in the page
|
|
544
|
+
*
|
|
545
|
+
* Locates stateful sets tagged with encore:input-group for coordinated
|
|
546
|
+
* selection behavior.
|
|
547
|
+
*
|
|
548
|
+
* @param pageData - Page definition from Encore service
|
|
549
|
+
* @returns Array of input group metadata
|
|
550
|
+
*/
|
|
928
551
|
export function findInputGroups(pageData: any): InputGroupInfo[] {
|
|
929
552
|
const groupsMap = new Map<string, InputGroupInfo>();
|
|
930
553
|
|
|
@@ -985,6 +608,15 @@ export function findInputGroups(pageData: any): InputGroupInfo[] {
|
|
|
985
608
|
return Array.from(groupsMap.values());
|
|
986
609
|
}
|
|
987
610
|
|
|
611
|
+
/**
|
|
612
|
+
* Finds forms and their input fields in the page
|
|
613
|
+
*
|
|
614
|
+
* Locates containers tagged with encore:form and extracts all input
|
|
615
|
+
* components within them for form submission handling.
|
|
616
|
+
*
|
|
617
|
+
* @param pageData - Page definition from Encore service
|
|
618
|
+
* @returns Array of form metadata with input fields
|
|
619
|
+
*/
|
|
988
620
|
export function findForms(pageData: any): FormInfo[] {
|
|
989
621
|
const forms: FormInfo[] = [];
|
|
990
622
|
|
|
@@ -1215,48 +847,7 @@ export function findStandaloneSelectInputs(
|
|
|
1215
847
|
* Qualifies select input prop names to ensure uniqueness.
|
|
1216
848
|
*/
|
|
1217
849
|
function qualifySelectInputs(selectInputs: SelectInputInfo[]): void {
|
|
1218
|
-
|
|
1219
|
-
string,
|
|
1220
|
-
Array<SelectInputInfo & { _parentPath: string[] }>
|
|
1221
|
-
>();
|
|
1222
|
-
|
|
1223
|
-
selectInputs.forEach((input) => {
|
|
1224
|
-
const inputWithPath = input as SelectInputInfo & { _parentPath: string[] };
|
|
1225
|
-
const baseName = input.propName;
|
|
1226
|
-
if (!propNameGroups.has(baseName)) {
|
|
1227
|
-
propNameGroups.set(baseName, []);
|
|
1228
|
-
}
|
|
1229
|
-
propNameGroups.get(baseName)!.push(inputWithPath);
|
|
1230
|
-
});
|
|
1231
|
-
|
|
1232
|
-
propNameGroups.forEach((group, _baseName) => {
|
|
1233
|
-
if (group.length === 1) {
|
|
1234
|
-
delete (group[0] as any)._parentPath;
|
|
1235
|
-
return;
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
// Find minimal distinguishing paths for duplicates
|
|
1239
|
-
group.forEach((input) => {
|
|
1240
|
-
const otherPaths = group
|
|
1241
|
-
.filter((i) => i.id !== input.id)
|
|
1242
|
-
.map((i) => i._parentPath || []);
|
|
1243
|
-
|
|
1244
|
-
const minimalPath = findMinimalDistinguishingPath(
|
|
1245
|
-
input._parentPath || [],
|
|
1246
|
-
otherPaths
|
|
1247
|
-
);
|
|
1248
|
-
|
|
1249
|
-
input.propName = generateQualifiedPropName(
|
|
1250
|
-
input.name || "input",
|
|
1251
|
-
minimalPath
|
|
1252
|
-
);
|
|
1253
|
-
});
|
|
1254
|
-
|
|
1255
|
-
// Clean up
|
|
1256
|
-
group.forEach((input) => {
|
|
1257
|
-
delete (input as any)._parentPath;
|
|
1258
|
-
});
|
|
1259
|
-
});
|
|
850
|
+
qualifyPropNames(selectInputs);
|
|
1260
851
|
}
|
|
1261
852
|
|
|
1262
853
|
/**
|
|
@@ -1349,50 +940,7 @@ export function findActionButtons(pageData: any): ActionButtonInfo[] {
|
|
|
1349
940
|
* Qualifies action button prop names to ensure uniqueness.
|
|
1350
941
|
*/
|
|
1351
942
|
function qualifyActionButtons(buttons: ActionButtonInfo[]): void {
|
|
1352
|
-
|
|
1353
|
-
string,
|
|
1354
|
-
Array<ActionButtonInfo & { _parentPath: string[] }>
|
|
1355
|
-
>();
|
|
1356
|
-
|
|
1357
|
-
buttons.forEach((button) => {
|
|
1358
|
-
const buttonWithPath = button as ActionButtonInfo & {
|
|
1359
|
-
_parentPath: string[];
|
|
1360
|
-
};
|
|
1361
|
-
const baseName = button.propName;
|
|
1362
|
-
if (!propNameGroups.has(baseName)) {
|
|
1363
|
-
propNameGroups.set(baseName, []);
|
|
1364
|
-
}
|
|
1365
|
-
propNameGroups.get(baseName)!.push(buttonWithPath);
|
|
1366
|
-
});
|
|
1367
|
-
|
|
1368
|
-
propNameGroups.forEach((group, _baseName) => {
|
|
1369
|
-
if (group.length === 1) {
|
|
1370
|
-
delete (group[0] as any)._parentPath;
|
|
1371
|
-
return;
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
// Find minimal distinguishing paths for duplicates
|
|
1375
|
-
group.forEach((button) => {
|
|
1376
|
-
const otherPaths = group
|
|
1377
|
-
.filter((b) => b.id !== button.id)
|
|
1378
|
-
.map((b) => b._parentPath || []);
|
|
1379
|
-
|
|
1380
|
-
const minimalPath = findMinimalDistinguishingPath(
|
|
1381
|
-
button._parentPath || [],
|
|
1382
|
-
otherPaths
|
|
1383
|
-
);
|
|
1384
|
-
|
|
1385
|
-
button.propName = generateQualifiedPropName(
|
|
1386
|
-
button.name || "button",
|
|
1387
|
-
minimalPath
|
|
1388
|
-
);
|
|
1389
|
-
});
|
|
1390
|
-
|
|
1391
|
-
// Clean up
|
|
1392
|
-
group.forEach((button) => {
|
|
1393
|
-
delete (button as any)._parentPath;
|
|
1394
|
-
});
|
|
1395
|
-
});
|
|
943
|
+
qualifyPropNames(buttons);
|
|
1396
944
|
}
|
|
1397
945
|
|
|
1398
946
|
/**
|
|
@@ -1401,214 +949,6 @@ function qualifyActionButtons(buttons: ActionButtonInfo[]): void {
|
|
|
1401
949
|
*/
|
|
1402
950
|
export function qualifyFormInputs(forms: FormInfo[]): void {
|
|
1403
951
|
forms.forEach((form) => {
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
// Group inputs by base prop name
|
|
1407
|
-
const propNameGroups = new Map<
|
|
1408
|
-
string,
|
|
1409
|
-
Array<FormInfo["inputs"][0] & { _parentPath: string[] }>
|
|
1410
|
-
>();
|
|
1411
|
-
|
|
1412
|
-
inputs.forEach((input) => {
|
|
1413
|
-
const inputWithPath = input as FormInfo["inputs"][0] & {
|
|
1414
|
-
_parentPath: string[];
|
|
1415
|
-
};
|
|
1416
|
-
const baseName = input.propName;
|
|
1417
|
-
if (!propNameGroups.has(baseName)) {
|
|
1418
|
-
propNameGroups.set(baseName, []);
|
|
1419
|
-
}
|
|
1420
|
-
propNameGroups.get(baseName)!.push(inputWithPath);
|
|
1421
|
-
});
|
|
1422
|
-
|
|
1423
|
-
// For each group with duplicates, find minimal distinguishing paths
|
|
1424
|
-
propNameGroups.forEach((group, _baseName) => {
|
|
1425
|
-
if (group.length === 1) {
|
|
1426
|
-
// No duplicates, keep the simple name
|
|
1427
|
-
// Remove the temporary _parentPath property
|
|
1428
|
-
delete (group[0] as any)._parentPath;
|
|
1429
|
-
return;
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
// Find minimal distinguishing paths for all inputs
|
|
1433
|
-
group.forEach((input) => {
|
|
1434
|
-
const otherPaths = group
|
|
1435
|
-
.filter((i) => i.id !== input.id)
|
|
1436
|
-
.map((i) => i._parentPath || []);
|
|
1437
|
-
|
|
1438
|
-
const minimalPath = findMinimalDistinguishingPath(
|
|
1439
|
-
input._parentPath || [],
|
|
1440
|
-
otherPaths
|
|
1441
|
-
);
|
|
1442
|
-
|
|
1443
|
-
// Use the minimal distinguishing path to qualify the name
|
|
1444
|
-
input.propName = generateQualifiedPropName(
|
|
1445
|
-
input.name || "input",
|
|
1446
|
-
minimalPath
|
|
1447
|
-
);
|
|
1448
|
-
});
|
|
1449
|
-
|
|
1450
|
-
// Check if qualified names are still duplicates and expand paths if needed
|
|
1451
|
-
let hasDuplicates = true;
|
|
1452
|
-
let iteration = 0;
|
|
1453
|
-
const maxIterations = 10; // Safety limit
|
|
1454
|
-
|
|
1455
|
-
while (hasDuplicates && iteration < maxIterations) {
|
|
1456
|
-
iteration++;
|
|
1457
|
-
const qualifiedNameGroups = new Map<
|
|
1458
|
-
string,
|
|
1459
|
-
Array<FormInfo["inputs"][0] & { _parentPath: string[] }>
|
|
1460
|
-
>();
|
|
1461
|
-
group.forEach((input) => {
|
|
1462
|
-
if (!qualifiedNameGroups.has(input.propName)) {
|
|
1463
|
-
qualifiedNameGroups.set(input.propName, []);
|
|
1464
|
-
}
|
|
1465
|
-
qualifiedNameGroups.get(input.propName)!.push(input);
|
|
1466
|
-
});
|
|
1467
|
-
|
|
1468
|
-
hasDuplicates = false;
|
|
1469
|
-
// For each group of still-duplicated qualified names, expand their paths
|
|
1470
|
-
qualifiedNameGroups.forEach((dupGroup, _qualifiedName) => {
|
|
1471
|
-
if (dupGroup.length > 1) {
|
|
1472
|
-
hasDuplicates = true;
|
|
1473
|
-
// Expand the distinguishing path for each duplicate
|
|
1474
|
-
dupGroup.forEach((input) => {
|
|
1475
|
-
const fullPath = input._parentPath || [];
|
|
1476
|
-
const otherFullPaths = dupGroup
|
|
1477
|
-
.filter((i) => i.id !== input.id)
|
|
1478
|
-
.map((i) => i._parentPath || []);
|
|
1479
|
-
|
|
1480
|
-
// Find where this path diverges from others in the duplicate group
|
|
1481
|
-
let commonPrefixLength = 0;
|
|
1482
|
-
const maxCommonLength = Math.min(
|
|
1483
|
-
fullPath.length,
|
|
1484
|
-
...otherFullPaths.map((p) => p.length)
|
|
1485
|
-
);
|
|
1486
|
-
|
|
1487
|
-
for (let i = 0; i < maxCommonLength; i++) {
|
|
1488
|
-
const thisPart = fullPath[i];
|
|
1489
|
-
const allMatch = otherFullPaths.every((otherPath) => {
|
|
1490
|
-
return otherPath[i] === thisPart;
|
|
1491
|
-
});
|
|
1492
|
-
if (allMatch) {
|
|
1493
|
-
commonPrefixLength++;
|
|
1494
|
-
} else {
|
|
1495
|
-
break;
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
// Use progressively more of the distinguishing suffix until unique
|
|
1500
|
-
const distinguishingSuffix = fullPath.slice(commonPrefixLength);
|
|
1501
|
-
|
|
1502
|
-
// Try expanding the distinguishing suffix until we find a unique name
|
|
1503
|
-
let foundUnique = false;
|
|
1504
|
-
for (
|
|
1505
|
-
let suffixLength = 1;
|
|
1506
|
-
suffixLength <= distinguishingSuffix.length;
|
|
1507
|
-
suffixLength++
|
|
1508
|
-
) {
|
|
1509
|
-
const expandedPath = distinguishingSuffix.slice(
|
|
1510
|
-
0,
|
|
1511
|
-
suffixLength
|
|
1512
|
-
);
|
|
1513
|
-
const testQualifiedName = generateQualifiedPropName(
|
|
1514
|
-
input.name || "input",
|
|
1515
|
-
expandedPath
|
|
1516
|
-
);
|
|
1517
|
-
|
|
1518
|
-
// Check if this qualified name is unique among ALL inputs in this form
|
|
1519
|
-
const isUnique = inputs.every((otherInput) => {
|
|
1520
|
-
if (otherInput.id === input.id) return true;
|
|
1521
|
-
// If other input is in the same duplicate group, compare expanded paths
|
|
1522
|
-
if (dupGroup.some((i) => i.id === otherInput.id)) {
|
|
1523
|
-
const otherFullPath =
|
|
1524
|
-
(
|
|
1525
|
-
otherInput as FormInfo["inputs"][0] & {
|
|
1526
|
-
_parentPath: string[];
|
|
1527
|
-
}
|
|
1528
|
-
)._parentPath || [];
|
|
1529
|
-
const otherCommonPrefixLength = Math.min(
|
|
1530
|
-
commonPrefixLength,
|
|
1531
|
-
otherFullPath.length
|
|
1532
|
-
);
|
|
1533
|
-
const otherDistinguishingSuffix = otherFullPath.slice(
|
|
1534
|
-
otherCommonPrefixLength
|
|
1535
|
-
);
|
|
1536
|
-
const otherExpandedPath = otherDistinguishingSuffix.slice(
|
|
1537
|
-
0,
|
|
1538
|
-
suffixLength
|
|
1539
|
-
);
|
|
1540
|
-
const otherQualifiedName = generateQualifiedPropName(
|
|
1541
|
-
otherInput.name || "input",
|
|
1542
|
-
otherExpandedPath
|
|
1543
|
-
);
|
|
1544
|
-
return testQualifiedName !== otherQualifiedName;
|
|
1545
|
-
}
|
|
1546
|
-
// For inputs outside the duplicate group, just check the final prop name
|
|
1547
|
-
return testQualifiedName !== otherInput.propName;
|
|
1548
|
-
});
|
|
1549
|
-
|
|
1550
|
-
if (isUnique) {
|
|
1551
|
-
input.propName = testQualifiedName;
|
|
1552
|
-
foundUnique = true;
|
|
1553
|
-
break;
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
// If we couldn't find a unique name with the distinguishing suffix,
|
|
1558
|
-
// use the distinguishing suffix we found (it's the minimal we can do)
|
|
1559
|
-
if (!foundUnique) {
|
|
1560
|
-
input.propName = generateQualifiedPropName(
|
|
1561
|
-
input.name || "input",
|
|
1562
|
-
distinguishingSuffix.length > 0 ? distinguishingSuffix : []
|
|
1563
|
-
);
|
|
1564
|
-
}
|
|
1565
|
-
});
|
|
1566
|
-
}
|
|
1567
|
-
});
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
// Final check: if there are still duplicates after using full paths,
|
|
1571
|
-
// and they have identical paths, use numeric suffixes as last resort
|
|
1572
|
-
const finalQualifiedNameGroups = new Map<
|
|
1573
|
-
string,
|
|
1574
|
-
Array<FormInfo["inputs"][0] & { _parentPath: string[] }>
|
|
1575
|
-
>();
|
|
1576
|
-
group.forEach((input) => {
|
|
1577
|
-
if (!finalQualifiedNameGroups.has(input.propName)) {
|
|
1578
|
-
finalQualifiedNameGroups.set(input.propName, []);
|
|
1579
|
-
}
|
|
1580
|
-
finalQualifiedNameGroups.get(input.propName)!.push(input);
|
|
1581
|
-
});
|
|
1582
|
-
|
|
1583
|
-
finalQualifiedNameGroups.forEach((finalDupGroup, finalQualifiedName) => {
|
|
1584
|
-
if (finalDupGroup.length > 1) {
|
|
1585
|
-
// Check if all duplicates have identical paths
|
|
1586
|
-
const allPathsIdentical = finalDupGroup.every((input) => {
|
|
1587
|
-
const thisPath = input._parentPath || [];
|
|
1588
|
-
return finalDupGroup.every((otherInput) => {
|
|
1589
|
-
if (otherInput.id === input.id) return true;
|
|
1590
|
-
const otherPath = otherInput._parentPath || [];
|
|
1591
|
-
return arraysEqual(thisPath, otherPath);
|
|
1592
|
-
});
|
|
1593
|
-
});
|
|
1594
|
-
|
|
1595
|
-
// Only use numeric suffixes if paths are truly identical
|
|
1596
|
-
if (allPathsIdentical) {
|
|
1597
|
-
let index = 0;
|
|
1598
|
-
finalDupGroup.forEach((input) => {
|
|
1599
|
-
if (index > 0) {
|
|
1600
|
-
input.propName = `${finalQualifiedName}${index + 1}`;
|
|
1601
|
-
}
|
|
1602
|
-
index++;
|
|
1603
|
-
});
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
});
|
|
1607
|
-
|
|
1608
|
-
// Remove the temporary _parentPath property
|
|
1609
|
-
group.forEach((input) => {
|
|
1610
|
-
delete (input as any)._parentPath;
|
|
1611
|
-
});
|
|
1612
|
-
});
|
|
952
|
+
qualifyPropNames(form.inputs);
|
|
1613
953
|
});
|
|
1614
954
|
}
|