@formique/semantq 1.1.5 → 1.1.6
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/LowCodeParser.js +591 -406
- package/astToFormique.js +233 -304
- package/formique-semantq.js +4 -4
- package/package.json +1 -1
package/astToFormique.js
CHANGED
|
@@ -404,6 +404,32 @@ gender: { type: 'radio', priority: 10 },
|
|
|
404
404
|
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
// Helper to handle the "Country-State" -> "Country" transformation
|
|
411
|
+
formatLabel(str) {
|
|
412
|
+
if (!str) return "";
|
|
413
|
+
// Capitalize first letter only
|
|
414
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// NEW: Optimized Mapping Loop
|
|
418
|
+
mapFields() {
|
|
419
|
+
const fieldNode = this.ast.find(node => node.type === 'FormFields');
|
|
420
|
+
if (!fieldNode) return [];
|
|
421
|
+
|
|
422
|
+
fieldNode.fields.forEach(field => {
|
|
423
|
+
// Branching Logic: Check for the new fieldType property
|
|
424
|
+
if (field.fieldType === "dynamicSingleSelect") {
|
|
425
|
+
this.buildDynamicSingleSelect(field);
|
|
426
|
+
} else {
|
|
427
|
+
this.buildStandardField(field);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
return this.formSchema;
|
|
432
|
+
}
|
|
407
433
|
|
|
408
434
|
// formDirective , formProperties, formProperty, formFields,
|
|
409
435
|
// optionsAttribute, fieldsAttribute
|
|
@@ -445,18 +471,20 @@ inferInputType(fieldName) {
|
|
|
445
471
|
|
|
446
472
|
|
|
447
473
|
cleanFieldName(str) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
474
|
+
// Get everything before colon (removes :type)
|
|
475
|
+
let rawName = str.split(':')[0];
|
|
476
|
+
|
|
477
|
+
// Remove * and ! markers
|
|
478
|
+
let input_name = rawName.replace(/[*!]/g, '');
|
|
479
|
+
|
|
480
|
+
// Get type after colon (if exists)
|
|
481
|
+
let input_type = str.includes(':') ? str.split(':')[1] : '';
|
|
482
|
+
|
|
456
483
|
return { input_name, input_type };
|
|
457
484
|
}
|
|
458
485
|
|
|
459
486
|
|
|
487
|
+
|
|
460
488
|
cleanToInputType(str) {
|
|
461
489
|
//console.log("STR", str);
|
|
462
490
|
|
|
@@ -642,84 +670,85 @@ getOptionValuesByKey(attributes, targetKey) {
|
|
|
642
670
|
|
|
643
671
|
|
|
644
672
|
buildDynamicSingleSelect(node, rawFieldName) {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
} else {
|
|
660
|
-
inputParams = { validations: {}, attributes: {} }
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
validations = inputParams.validations;
|
|
664
|
-
attributes = inputParams.attributes;
|
|
673
|
+
const attrs = node.attributes || [];
|
|
674
|
+
|
|
675
|
+
// 1. Setup Basic Identity
|
|
676
|
+
const { input_name } = this.cleanFieldName(rawFieldName);
|
|
677
|
+
const cleanName = input_name.toLowerCase().replace(/\s+/g, '-');
|
|
678
|
+
const cleanLabel = this.formatLabel(input_name);
|
|
679
|
+
|
|
680
|
+
// 2. Identify Primary Options
|
|
681
|
+
const optionsAttr = attrs.find(a => a.key === 'options');
|
|
682
|
+
const primaryValues = optionsAttr
|
|
683
|
+
? (Array.isArray(optionsAttr.values)
|
|
684
|
+
? optionsAttr.values.map(v => v.value)
|
|
685
|
+
: optionsAttr.value.value.split(',').map(v => v.trim()))
|
|
686
|
+
: [];
|
|
665
687
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
688
|
+
const mainOptions = primaryValues.map(val => ({
|
|
689
|
+
value: val.toLowerCase().replace(/\s+/g, '-'),
|
|
690
|
+
label: this.formatLabel(val)
|
|
691
|
+
}));
|
|
692
|
+
|
|
693
|
+
// 3. Identify Secondary Scenarios (Attributes whose keys are in primaryValues)
|
|
694
|
+
const scenarioBlocks = attrs
|
|
695
|
+
.filter(a => a.key !== 'options' && primaryValues.includes(a.key))
|
|
696
|
+
.map(attr => {
|
|
697
|
+
const rawVals = Array.isArray(attr.values)
|
|
698
|
+
? attr.values.map(v => v.value)
|
|
699
|
+
: attr.value.value.split(',').map(v => v.trim());
|
|
700
|
+
|
|
701
|
+
return {
|
|
702
|
+
id: attr.key.toLowerCase().replace(/\s+/g, '-'),
|
|
703
|
+
label: attr.key,
|
|
704
|
+
options: rawVals.map(opt => ({
|
|
705
|
+
value: opt.toLowerCase().replace(/\s+/g, '-'),
|
|
706
|
+
label: this.formatLabel(opt)
|
|
707
|
+
}))
|
|
708
|
+
};
|
|
709
|
+
});
|
|
669
710
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
711
|
+
// 4. Determine Labels (Handle "Country-State" split if present)
|
|
712
|
+
let primaryLabel = cleanLabel;
|
|
713
|
+
let secondaryLabel = "Options";
|
|
714
|
+
if (rawFieldName.includes('-')) {
|
|
715
|
+
const parts = rawFieldName.split('-');
|
|
716
|
+
primaryLabel = this.formatLabel(parts[0].replace(/[*!]/g, ''));
|
|
717
|
+
secondaryLabel = this.formatLabel(parts[1].split(':')[0].replace(/[*!]/g, ''));
|
|
718
|
+
}
|
|
719
|
+
const combinedLabel = `${primaryLabel}-${secondaryLabel}`;
|
|
675
720
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
let scenarioBlocks = [];
|
|
682
|
-
|
|
683
|
-
if (optionValues.length > 0) {
|
|
684
|
-
optionValues.forEach(option => {
|
|
685
|
-
let schema = {};
|
|
686
|
-
|
|
687
|
-
// Use the original option string as the ID
|
|
688
|
-
schema['id'] = option;
|
|
689
|
-
schema['label'] = option;
|
|
690
|
-
|
|
691
|
-
// Get sub-options for this category
|
|
692
|
-
const keyOptions = this.getOptionValuesByKey(node.attributes, option);
|
|
693
|
-
let options = [];
|
|
694
|
-
|
|
695
|
-
if (keyOptions.length > 0) {
|
|
696
|
-
keyOptions.forEach(subOption => {
|
|
697
|
-
options.push({
|
|
698
|
-
value: subOption.toLowerCase(),
|
|
699
|
-
label: this.toTitleCase(subOption)
|
|
700
|
-
});
|
|
701
|
-
});
|
|
702
|
-
schema['options'] = options;
|
|
703
|
-
scenarioBlocks.push(schema);
|
|
704
|
-
}
|
|
705
|
-
});
|
|
706
|
-
}
|
|
721
|
+
// 5. Validations & Attributes (Excluding the ones used for scenarios)
|
|
722
|
+
const { validations, attributes } = this.handleAttributes(
|
|
723
|
+
attrs.filter(a => a.key === 'options' || !primaryValues.includes(a.key)),
|
|
724
|
+
rawFieldName
|
|
725
|
+
);
|
|
707
726
|
|
|
708
|
-
|
|
709
|
-
|
|
727
|
+
if (this.isRequired(rawFieldName)) {
|
|
728
|
+
validations['required'] = true;
|
|
729
|
+
}
|
|
710
730
|
|
|
711
|
-
|
|
731
|
+
// 6. Build Final Formique Schema
|
|
732
|
+
const dynamicSchema = [
|
|
733
|
+
"dynamicSingleSelect",
|
|
734
|
+
cleanName,
|
|
735
|
+
combinedLabel,
|
|
736
|
+
validations,
|
|
737
|
+
attributes,
|
|
738
|
+
mainOptions,
|
|
739
|
+
scenarioBlocks
|
|
740
|
+
];
|
|
741
|
+
|
|
742
|
+
this.formSchema.push(dynamicSchema);
|
|
743
|
+
console.log(`Built dynamicSingleSelect: ${cleanName}`);
|
|
712
744
|
}
|
|
713
745
|
|
|
714
746
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
747
|
// Builder methods - implement these according to your needs
|
|
719
748
|
buildDirective(node) {
|
|
720
749
|
//console.log(`Processing FormDirective: ${node.name.value}`);
|
|
721
750
|
|
|
722
|
-
|
|
751
|
+
this.formParams['id'] = node.name.value;
|
|
723
752
|
// Handle directive specific logic
|
|
724
753
|
}
|
|
725
754
|
|
|
@@ -803,144 +832,80 @@ if (key === 'sendTo') {
|
|
|
803
832
|
|
|
804
833
|
buildField(node) {
|
|
805
834
|
const rawFieldName = node.name;
|
|
835
|
+
|
|
836
|
+
// 1. High-Priority check for Dynamic Select
|
|
837
|
+
if (node.fieldType === 'dynamicSingleSelect') {
|
|
838
|
+
this.buildDynamicSingleSelect(node, rawFieldName);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// 2. Resolve Names and Types
|
|
806
843
|
const cleanString = this.cleanFieldName(rawFieldName);
|
|
807
844
|
const cleanFieldName = cleanString.input_name;
|
|
845
|
+
const fieldLabel = this.formatLabel(cleanFieldName);
|
|
808
846
|
|
|
809
847
|
let fieldType;
|
|
810
|
-
|
|
811
|
-
// 1. Determine the Field Type
|
|
812
848
|
if (cleanString.input_type) {
|
|
813
849
|
fieldType = this.cleanToInputType(cleanString.input_type);
|
|
814
850
|
} else {
|
|
815
|
-
|
|
816
|
-
if (
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
// Check for 'manyof' attribute to force 'checkbox' type
|
|
820
|
-
if (attributeKeys.includes('manyof')) {
|
|
821
|
-
fieldType = 'checkbox';
|
|
822
|
-
} else {
|
|
823
|
-
fieldType = this.inputTypeResolver(cleanFieldName, attributeKeys);
|
|
824
|
-
}
|
|
851
|
+
const attributeKeys = node.attributes?.length > 0 ? this.extractAttributeKeys(node.attributes) : [];
|
|
852
|
+
if (attributeKeys.includes('manyof')) {
|
|
853
|
+
fieldType = 'checkbox';
|
|
825
854
|
} else {
|
|
826
|
-
fieldType = this.
|
|
855
|
+
fieldType = this.inputTypeResolver(cleanFieldName, attributeKeys);
|
|
827
856
|
}
|
|
828
857
|
}
|
|
829
858
|
|
|
830
|
-
//
|
|
831
|
-
if (fieldType === 'dynamicSingleSelect') {
|
|
832
|
-
this.buildDynamicSingleSelect(node, rawFieldName);
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
// Initialize Schema Structure
|
|
837
|
-
const fieldSchema = [];
|
|
838
|
-
const fieldLabel = this.toTitleCase(cleanFieldName);
|
|
839
|
-
|
|
840
|
-
// Push Base Definition: [type, name, label]
|
|
841
|
-
fieldSchema.push(fieldType, cleanFieldName, fieldLabel);
|
|
842
|
-
|
|
859
|
+
// 3. Process Attributes & Validations
|
|
843
860
|
let validations = {};
|
|
844
861
|
let attributes = {};
|
|
845
862
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
inputParams = this.handleAttributes(node.attributes, rawFieldName);
|
|
851
|
-
} else {
|
|
852
|
-
inputParams = { validations: {}, attributes: {} };
|
|
863
|
+
if (node.attributes && node.attributes.length > 0) {
|
|
864
|
+
const params = this.handleAttributes(node.attributes, rawFieldName);
|
|
865
|
+
validations = params.validations;
|
|
866
|
+
attributes = params.attributes;
|
|
853
867
|
}
|
|
854
868
|
|
|
855
|
-
|
|
856
|
-
attributes = inputParams.attributes;
|
|
857
|
-
|
|
858
|
-
// 3. Clean up Attributes and Add Required Validation
|
|
859
|
-
// CRITICAL: We only delete the attributes that are used to build the final list
|
|
860
|
-
// but should NOT appear in the attributes object. Dependents/dependsOn SHOULD remain.
|
|
861
|
-
delete attributes.options;
|
|
862
|
-
delete attributes.selected;
|
|
863
|
-
delete attributes.default;
|
|
864
|
-
// delete attributes.dependents; // REMOVED: This attribute needs to be in the final object
|
|
865
|
-
// delete attributes.dependsOn; // REMOVED: This attribute needs to be in the final object
|
|
866
|
-
// delete attributes.condition; // REMOVED: This attribute needs to be in the final object
|
|
867
|
-
delete attributes.manyof; // If 'manyof' is not needed in the final attributes, delete it.
|
|
868
|
-
|
|
869
|
+
// 4. Add required validation from asterisk marker
|
|
869
870
|
if (this.isRequired(rawFieldName)) {
|
|
870
871
|
validations['required'] = true;
|
|
871
872
|
}
|
|
872
873
|
|
|
873
|
-
//
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
// 4. Handle Option-Based Fields (The list of choices)
|
|
878
|
-
if (node.attributes.length > 0 && (fieldType === 'checkbox' || fieldType === 'radio' || fieldType === 'select' || fieldType === 'multipleSelect' || fieldType === 'singleSelect')) {
|
|
879
|
-
|
|
880
|
-
// Helper function to correctly retrieve single string OR array of selected values.
|
|
881
|
-
const getSelectedValues = (attributes) => {
|
|
882
|
-
const isMultiSelect = (fieldType === 'checkbox' || fieldType === 'multipleSelect');
|
|
883
|
-
|
|
884
|
-
// 1. Look for OptionsAttributes (list: selected: a, b)
|
|
885
|
-
const selectedOptionsAttr = node.attributes.find(attr =>
|
|
886
|
-
attr?.type === "OptionsAttribute" && (attr?.key === "selected" || attr?.key === "default")
|
|
887
|
-
);
|
|
888
|
-
|
|
889
|
-
if (selectedOptionsAttr && selectedOptionsAttr.values && selectedOptionsAttr.values.length > 0) {
|
|
890
|
-
const values = selectedOptionsAttr.values.map(v => v.value.toLowerCase());
|
|
891
|
-
return isMultiSelect ? values : values[0]; // array or first item
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// 2. Look for FieldAttributes (single: selected: a)
|
|
895
|
-
const selectedAttr = node.attributes.find(attr =>
|
|
896
|
-
attr?.type === "FieldAttribute" && (attr?.key === "selected" || attr?.key === "default")
|
|
897
|
-
);
|
|
898
|
-
|
|
899
|
-
if (selectedAttr) {
|
|
900
|
-
// Use a safe value retrieval/lower-casing
|
|
901
|
-
const rawValue = selectedAttr.value?.value || selectedAttr.value;
|
|
902
|
-
const value = (typeof rawValue === 'string' ? rawValue.toLowerCase() : rawValue);
|
|
903
|
-
|
|
904
|
-
return isMultiSelect ? [value].filter(Boolean) : value; // array or string
|
|
905
|
-
}
|
|
874
|
+
// 5. SPECIAL: Add 'multiple' attribute for multipleSelect
|
|
875
|
+
if (fieldType === 'multipleSelect') {
|
|
876
|
+
attributes['multiple'] = true;
|
|
877
|
+
}
|
|
906
878
|
|
|
907
|
-
|
|
908
|
-
|
|
879
|
+
// 6. Build base schema: [type, name, label, validations, attributes]
|
|
880
|
+
const fieldSchema = [fieldType, cleanFieldName, fieldLabel, validations, attributes];
|
|
909
881
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
882
|
+
// 7. Handle Options for Radio/Checkbox/Select fields
|
|
883
|
+
const isOptionField = ['checkbox', 'radio', 'select', 'singleSelect', 'multipleSelect'].includes(fieldType);
|
|
884
|
+
if (isOptionField) {
|
|
913
885
|
let options = [];
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
if (isSelected) {
|
|
930
|
-
options.push({value: optValue, label: this.toTitleCase(option), selected: true});
|
|
931
|
-
} else {
|
|
932
|
-
options.push({value: optValue, label: this.toTitleCase(option)});
|
|
933
|
-
}
|
|
934
|
-
});
|
|
935
|
-
|
|
936
|
-
// Push Options Array: [..., validations, attributes, options_array]
|
|
937
|
-
fieldSchema.push(options);
|
|
886
|
+
|
|
887
|
+
// Get options from attributes (already processed) or extract from AST
|
|
888
|
+
if (attributes.options && Array.isArray(attributes.options)) {
|
|
889
|
+
options = attributes.options;
|
|
890
|
+
} else {
|
|
891
|
+
const optionValues = this.extractOptionValues(node.attributes || []);
|
|
892
|
+
options = optionValues.map(opt => ({
|
|
893
|
+
value: opt.toLowerCase().replace(/\s+/g, '-'),
|
|
894
|
+
label: this.formatLabel(opt)
|
|
895
|
+
}));
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (options.length > 0) {
|
|
899
|
+
fieldSchema.push(options);
|
|
938
900
|
}
|
|
901
|
+
|
|
902
|
+
// Remove options from attributes to avoid duplication
|
|
903
|
+
delete attributes.options;
|
|
939
904
|
}
|
|
940
905
|
|
|
941
|
-
//
|
|
906
|
+
// 8. Push to schema
|
|
942
907
|
this.formSchema.push(fieldSchema);
|
|
943
|
-
console.log(
|
|
908
|
+
console.log(`Built field: ${cleanFieldName} (${fieldType})`);
|
|
944
909
|
}
|
|
945
910
|
|
|
946
911
|
|
|
@@ -949,145 +914,109 @@ handleAttributes(attributesAST, fieldName) {
|
|
|
949
914
|
let validations = {};
|
|
950
915
|
let attributes = {};
|
|
951
916
|
|
|
952
|
-
|
|
917
|
+
/**
|
|
918
|
+
* @helper extractValue
|
|
919
|
+
* Safely extracts the raw value from various node types in the AST.
|
|
920
|
+
*/
|
|
953
921
|
const extractValue = (attrValue) => {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
value = attrValue;
|
|
960
|
-
}
|
|
961
|
-
} else {
|
|
962
|
-
value = attrValue;
|
|
922
|
+
if (!attrValue) return "";
|
|
923
|
+
|
|
924
|
+
// 1. If it's a standard Node (StringLiteral, BooleanLiteral, NumberLiteral)
|
|
925
|
+
if (typeof attrValue === 'object' && attrValue.value !== undefined) {
|
|
926
|
+
return attrValue.value;
|
|
963
927
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
}
|
|
928
|
+
|
|
929
|
+
// 2. If it's an OptionsAttribute node containing a values array
|
|
930
|
+
if (attrValue.type === 'OptionsAttribute' && Array.isArray(attrValue.values)) {
|
|
931
|
+
return attrValue.values.map(v => (typeof v === 'object' ? v.value : v)).join(', ');
|
|
969
932
|
}
|
|
970
|
-
|
|
933
|
+
|
|
934
|
+
// 3. Fallback for raw strings or already extracted values
|
|
935
|
+
return attrValue;
|
|
971
936
|
};
|
|
937
|
+
|
|
938
|
+
// --- STEP 1: Identify and Extract Options ---
|
|
939
|
+
// We look for any attribute with the key 'options'
|
|
940
|
+
let allOptions = [];
|
|
941
|
+
const optionsAttrs = attributesAST.filter(attr => attr.key === 'options');
|
|
942
|
+
|
|
943
|
+
for (const attr of optionsAttrs) {
|
|
944
|
+
// Get the value regardless of whether it's a simple attribute or a list
|
|
945
|
+
let rawValue = extractValue(attr.type === 'OptionsAttribute' ? attr : attr.value);
|
|
946
|
+
|
|
947
|
+
if (rawValue && typeof rawValue === 'string') {
|
|
948
|
+
const parts = rawValue.split(',').map(p => p.trim()).filter(p => p);
|
|
949
|
+
allOptions.push(...parts);
|
|
950
|
+
} else if (Array.isArray(rawValue)) {
|
|
951
|
+
allOptions.push(...rawValue);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
972
954
|
|
|
973
|
-
|
|
974
|
-
//
|
|
975
|
-
|
|
955
|
+
if (allOptions.length > 0) {
|
|
956
|
+
// Map to Formique option format: { value, label }
|
|
957
|
+
attributes['options'] = [...new Set(allOptions)].map(opt => ({
|
|
958
|
+
value: opt.toLowerCase().replace(/\s+/g, '-'),
|
|
959
|
+
label: this.formatLabel(opt)
|
|
960
|
+
}));
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// --- STEP 2: Process All Other Attributes ---
|
|
976
964
|
attributesAST.forEach(attr => {
|
|
977
965
|
const key = attr.key;
|
|
966
|
+
if (key === 'options') return; // Already handled above
|
|
978
967
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
// Only skip list-building keys ('selected', 'default', 'options').
|
|
983
|
-
if (this.ignoreAttributes && this.ignoreAttributes.includes(key)) return;
|
|
984
|
-
if (['selected', 'default', 'options'].includes(key)) return;
|
|
985
|
-
|
|
986
|
-
// Categorize key
|
|
987
|
-
if (this.inputAttributes && this.inputAttributes.includes(key)) {
|
|
988
|
-
attributes[key] = value;
|
|
989
|
-
} else if (this.validationAttributes && this.validationAttributes.includes(key)) {
|
|
990
|
-
validations[key] = value;
|
|
991
|
-
} else {
|
|
992
|
-
// For 'dependents', 'dependsOn', 'manyof', and any unrecognized attributes
|
|
993
|
-
|
|
994
|
-
// 💡 CRITICAL FIX: Ensure 'dependents' value is always an array
|
|
995
|
-
if (key === 'dependents') {
|
|
996
|
-
// This handles AST parsing a single value as a FieldAttribute.
|
|
997
|
-
attributes[key] = Array.isArray(value) ? value : [value];
|
|
998
|
-
} else {
|
|
999
|
-
attributes[key] = value;
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
// ----------------------------------------------------------------------
|
|
1004
|
-
// 2. Process OptionsAttribute (List) Nodes for 'dependents', 'accept', and 'dependsOn'
|
|
1005
|
-
// ----------------------------------------------------------------------
|
|
1006
|
-
else if (attr.type === 'OptionsAttribute') {
|
|
1007
|
-
|
|
1008
|
-
// ⭐ NEW/FIXED HANDLING: Process 'dependents' (List of field names)
|
|
1009
|
-
if (key === 'dependents') {
|
|
1010
|
-
const dependentFields = attr.values
|
|
1011
|
-
.map(option => extractValue(option))
|
|
1012
|
-
.filter(Boolean);
|
|
1013
|
-
|
|
1014
|
-
if (dependentFields.length > 0) {
|
|
1015
|
-
attributes[key] = dependentFields;
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
968
|
+
// Extract the value (handles StringLiterals vs OptionsAttributes)
|
|
969
|
+
let value = extractValue(attr.type === 'OptionsAttribute' ? attr : attr.value);
|
|
1018
970
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
if (key === 'dependsOn' && attr.values && attr.values.length >= 2) {
|
|
1032
|
-
const dependsOnValue = extractValue(attr.values[0]);
|
|
1033
|
-
const dependsOnCondition = extractValue(attr.values[1]);
|
|
1034
|
-
|
|
1035
|
-
if (dependsOnValue && dependsOnCondition) {
|
|
1036
|
-
attributes['dependsOn'] = dependsOnValue;
|
|
1037
|
-
attributes['condition'] = dependsOnCondition.toLowerCase();
|
|
1038
|
-
}
|
|
971
|
+
// Skip internal/meta attributes
|
|
972
|
+
if (this.ignoreAttributes?.includes(key)) return;
|
|
973
|
+
if (['selected', 'default'].includes(key)) return;
|
|
974
|
+
|
|
975
|
+
// --- Logic: Conditional Routing ---
|
|
976
|
+
if (key === 'dependsOn') {
|
|
977
|
+
if (typeof value === 'string' && value.includes(',')) {
|
|
978
|
+
const parts = value.split(',').map(p => p.trim());
|
|
979
|
+
attributes['dependsOn'] = parts[0];
|
|
980
|
+
if (parts[1]) attributes['condition'] = parts[1].toLowerCase();
|
|
981
|
+
} else {
|
|
982
|
+
attributes['dependsOn'] = Array.isArray(value) ? value[0] : value;
|
|
1039
983
|
}
|
|
984
|
+
return;
|
|
1040
985
|
}
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
986
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
const optionsAttr = attributesAST.find(attr => attr.type === "OptionsAttribute" && attr.key === "options");
|
|
1048
|
-
if (optionsAttr?.values) {
|
|
1049
|
-
const options = optionsAttr.values.map(option => ({
|
|
1050
|
-
value: extractValue(option),
|
|
1051
|
-
label: extractValue(option) // Simple case: value is also the label
|
|
1052
|
-
}));
|
|
1053
|
-
if (options.length > 0) {
|
|
1054
|
-
attributes['options'] = options;
|
|
987
|
+
if (key === 'condition') {
|
|
988
|
+
attributes['condition'] = typeof value === 'string' ? value.toLowerCase() : value;
|
|
989
|
+
return;
|
|
1055
990
|
}
|
|
1056
|
-
}
|
|
1057
991
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
if (selectedListAttr && selectedListAttr.values) {
|
|
1063
|
-
const selectedValues = selectedListAttr.values
|
|
1064
|
-
.map(option => extractValue(option))
|
|
1065
|
-
.filter(Boolean);
|
|
1066
|
-
if (selectedValues.length > 0) {
|
|
1067
|
-
attributes['selected'] = selectedValues;
|
|
1068
|
-
}
|
|
1069
|
-
} else {
|
|
1070
|
-
const selectedSingleAttr = attributesAST.find(attr => attr.type === "FieldAttribute" && attr.key === "selected");
|
|
1071
|
-
if (selectedSingleAttr) {
|
|
1072
|
-
attributes['selected'] = extractValue(selectedSingleAttr.value);
|
|
992
|
+
if (key === 'dependents') {
|
|
993
|
+
const deps = typeof value === 'string' ? value.split(',').map(v => v.trim()) : value;
|
|
994
|
+
attributes['dependents'] = Array.isArray(deps) ? deps : [deps];
|
|
995
|
+
return;
|
|
1073
996
|
}
|
|
1074
|
-
}
|
|
1075
997
|
|
|
1076
|
-
//
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
998
|
+
// --- Logic: Validation Extraction ---
|
|
999
|
+
if (this.validationAttributes?.includes(key)) {
|
|
1000
|
+
let finalVal = value;
|
|
1001
|
+
// Type Casting for JSON-safe schema
|
|
1002
|
+
if (value === 'true' || value === true) finalVal = true;
|
|
1003
|
+
else if (value === 'false' || value === false) finalVal = false;
|
|
1004
|
+
else if (!isNaN(value) && typeof value === 'string' && value !== '') {
|
|
1005
|
+
finalVal = Number(value);
|
|
1006
|
+
}
|
|
1007
|
+
validations[key] = finalVal;
|
|
1008
|
+
} else {
|
|
1009
|
+
// All other custom attributes (e.g., placeholder, class, etc.)
|
|
1010
|
+
attributes[key] = value;
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1083
1013
|
|
|
1084
|
-
return {
|
|
1085
|
-
validations,
|
|
1086
|
-
attributes
|
|
1087
|
-
};
|
|
1014
|
+
return { validations, attributes };
|
|
1088
1015
|
}
|
|
1089
1016
|
|
|
1090
1017
|
|
|
1018
|
+
|
|
1019
|
+
|
|
1091
1020
|
buildOptionsAttribute(node) {
|
|
1092
1021
|
//console.log(`Processing OptionsAttribute with ${node.values.length} values`);
|
|
1093
1022
|
}
|
package/formique-semantq.js
CHANGED
|
@@ -61,11 +61,11 @@ class Formique extends FormBuilder {
|
|
|
61
61
|
|
|
62
62
|
if (typeof formDefinition === 'string') {
|
|
63
63
|
const ast = LowCodeParser.parse(formDefinition.trim());
|
|
64
|
-
//
|
|
64
|
+
//console.log("AST", JSON.stringify(ast, null,2));
|
|
65
65
|
|
|
66
66
|
const formObjects = new astToFormique(ast);
|
|
67
67
|
|
|
68
|
-
//console.log("formSchema", JSON.stringify(formObjects.formSchema,null,2));
|
|
68
|
+
//console.log("CHECK formSchema", JSON.stringify(formObjects.formSchema,null,2));
|
|
69
69
|
// Assign from formObjects if a string is passed
|
|
70
70
|
formSchema = formObjects.formSchema;
|
|
71
71
|
finalSettings = { ...formSettings, ...formObjects.formSettings };
|
|
@@ -1919,7 +1919,7 @@ const telInputValidationAttributes = [
|
|
|
1919
1919
|
return `\n${match}\n`;
|
|
1920
1920
|
}).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
|
|
1921
1921
|
|
|
1922
|
-
|
|
1922
|
+
this.formMarkUp += formattedHtml;
|
|
1923
1923
|
}
|
|
1924
1924
|
|
|
1925
1925
|
renderDateField(type, name, label, validate, attributes) {
|
|
@@ -3357,7 +3357,7 @@ renderImageField(type, name, label, validate, attributes) {
|
|
|
3357
3357
|
return `\n${match}\n`;
|
|
3358
3358
|
}).replace(/\n\s*\n/g, '\n');
|
|
3359
3359
|
|
|
3360
|
-
|
|
3360
|
+
this.formMarkUp += formattedHtml;
|
|
3361
3361
|
}
|
|
3362
3362
|
|
|
3363
3363
|
|