@ng-forge/dynamic-form-mcp 1.1.0-next.2 → 1.1.0-next.4
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/bin/ng-forge-mcp.js
CHANGED
|
@@ -757,12 +757,20 @@ var FIELD_TYPES = [
|
|
|
757
757
|
];
|
|
758
758
|
|
|
759
759
|
// packages/dynamic-form-mcp/src/registry/validators.ts
|
|
760
|
+
var WHEN_PARAMETER = {
|
|
761
|
+
when: {
|
|
762
|
+
name: "when",
|
|
763
|
+
type: "ConditionalExpression",
|
|
764
|
+
description: "Optional condition gating the validator. Accepts the same shapes as logic conditions: fieldValue comparisons, and/or composites, expression strings, functionName (registered condition), or http. Built-in validators apply the condition natively through Angular Signal Forms, so constraint metadata (e.g. field().maxLength()) stays in sync with the condition reactively. Cross-field conditions are supported. Constraints: http conditions require provideHttpClient() at the application level, and http/async conditions cannot be nested inside and/or composites (schema build throws).",
|
|
765
|
+
required: false
|
|
766
|
+
}
|
|
767
|
+
};
|
|
760
768
|
var VALIDATORS = [
|
|
761
769
|
{
|
|
762
770
|
type: "required",
|
|
763
771
|
category: "built-in",
|
|
764
772
|
description: "Validates that a field has a non-empty value",
|
|
765
|
-
parameters: {},
|
|
773
|
+
parameters: { ...WHEN_PARAMETER },
|
|
766
774
|
example: `{ type: 'required' }
|
|
767
775
|
// or shorthand: required: true`
|
|
768
776
|
},
|
|
@@ -770,7 +778,7 @@ var VALIDATORS = [
|
|
|
770
778
|
type: "email",
|
|
771
779
|
category: "built-in",
|
|
772
780
|
description: "Validates email format",
|
|
773
|
-
parameters: {},
|
|
781
|
+
parameters: { ...WHEN_PARAMETER },
|
|
774
782
|
example: `{ type: 'email' }
|
|
775
783
|
// or shorthand: email: true`
|
|
776
784
|
},
|
|
@@ -779,6 +787,7 @@ var VALIDATORS = [
|
|
|
779
787
|
category: "built-in",
|
|
780
788
|
description: "Validates minimum numeric value",
|
|
781
789
|
parameters: {
|
|
790
|
+
...WHEN_PARAMETER,
|
|
782
791
|
value: {
|
|
783
792
|
name: "value",
|
|
784
793
|
type: "number",
|
|
@@ -794,6 +803,7 @@ var VALIDATORS = [
|
|
|
794
803
|
category: "built-in",
|
|
795
804
|
description: "Validates maximum numeric value",
|
|
796
805
|
parameters: {
|
|
806
|
+
...WHEN_PARAMETER,
|
|
797
807
|
value: {
|
|
798
808
|
name: "value",
|
|
799
809
|
type: "number",
|
|
@@ -809,6 +819,7 @@ var VALIDATORS = [
|
|
|
809
819
|
category: "built-in",
|
|
810
820
|
description: "Validates minimum string length",
|
|
811
821
|
parameters: {
|
|
822
|
+
...WHEN_PARAMETER,
|
|
812
823
|
value: {
|
|
813
824
|
name: "value",
|
|
814
825
|
type: "number",
|
|
@@ -824,6 +835,7 @@ var VALIDATORS = [
|
|
|
824
835
|
category: "built-in",
|
|
825
836
|
description: "Validates maximum string length",
|
|
826
837
|
parameters: {
|
|
838
|
+
...WHEN_PARAMETER,
|
|
827
839
|
value: {
|
|
828
840
|
name: "value",
|
|
829
841
|
type: "number",
|
|
@@ -832,13 +844,16 @@ var VALIDATORS = [
|
|
|
832
844
|
}
|
|
833
845
|
},
|
|
834
846
|
example: `{ type: 'maxLength', value: 100 }
|
|
835
|
-
// or shorthand: maxLength: 100
|
|
847
|
+
// or shorthand: maxLength: 100
|
|
848
|
+
// conditional: only enforced while accountType is 'personal'
|
|
849
|
+
{ type: 'maxLength', value: 100, when: { type: 'fieldValue', fieldPath: 'accountType', operator: 'equals', value: 'personal' } }`
|
|
836
850
|
},
|
|
837
851
|
{
|
|
838
852
|
type: "pattern",
|
|
839
853
|
category: "built-in",
|
|
840
854
|
description: "Validates against a regular expression pattern",
|
|
841
855
|
parameters: {
|
|
856
|
+
...WHEN_PARAMETER,
|
|
842
857
|
value: {
|
|
843
858
|
name: "value",
|
|
844
859
|
type: "string | RegExp",
|
|
@@ -854,6 +869,7 @@ var VALIDATORS = [
|
|
|
854
869
|
category: "custom",
|
|
855
870
|
description: "Custom synchronous validator using registered function or expression. IMPORTANT: The validator returns { kind: 'errorKind' } on failure. The actual error MESSAGE is defined in 'validationMessages' at the FIELD level, NOT in the validator config. NOTE: TypeScript-authored configs may also use an inline `fn: CustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName`.",
|
|
856
871
|
parameters: {
|
|
872
|
+
...WHEN_PARAMETER,
|
|
857
873
|
functionName: {
|
|
858
874
|
name: "functionName",
|
|
859
875
|
type: "string",
|
|
@@ -906,6 +922,7 @@ var VALIDATORS = [
|
|
|
906
922
|
category: "async",
|
|
907
923
|
description: "Async validator using registered async function (resource-based). Register the function via customFnConfig.asyncValidators. NOTE: TypeScript-authored configs may also use an inline `fn: AsyncCustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName`.",
|
|
908
924
|
parameters: {
|
|
925
|
+
...WHEN_PARAMETER,
|
|
909
926
|
functionName: {
|
|
910
927
|
name: "functionName",
|
|
911
928
|
type: "string",
|
|
@@ -929,6 +946,7 @@ var VALIDATORS = [
|
|
|
929
946
|
category: "http",
|
|
930
947
|
description: "HTTP validator \u2014 supports two modes: (1) Declarative (fully JSON-serializable, no function registration) using http + responseMapping properties, or (2) Function-based using functionName to reference a registered HTTP validator function. Query param values and (optionally) body values are expressions evaluated against the form context. Response mapping uses validWhen (truthy = valid) and errorKind (maps to field-level validationMessages). NOTE: TypeScript-authored configs may also use an inline `fn: HttpCustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName` or declarative mode.",
|
|
931
948
|
parameters: {
|
|
949
|
+
...WHEN_PARAMETER,
|
|
932
950
|
functionName: {
|
|
933
951
|
name: "functionName",
|
|
934
952
|
type: "string",
|
|
@@ -2829,6 +2847,18 @@ ALWAYS provide validation messages. Use template variables:
|
|
|
2829
2847
|
- \`{{min}}\`, \`{{max}}\` - for min/max
|
|
2830
2848
|
- \`{{requiredPattern}}\` - for pattern
|
|
2831
2849
|
|
|
2850
|
+
TypeScript-authored configs may also use error-aware message functions: any \`validationMessages\` or \`defaultValidationMessages\` value can be \`(error: ValidationError) => DynamicText\` instead of a string/Observable/Signal. The function receives the validation error (kind plus params like \`maxLength\`), so i18n layers can receive interpolation params natively:
|
|
2851
|
+
|
|
2852
|
+
\`\`\`typescript
|
|
2853
|
+
validationMessages: {
|
|
2854
|
+
maxLength: (error) => transloco.selectTranslate('validation.maxLength', {
|
|
2855
|
+
requiredLength: 'maxLength' in error ? error.maxLength : undefined,
|
|
2856
|
+
}),
|
|
2857
|
+
}
|
|
2858
|
+
\`\`\`
|
|
2859
|
+
|
|
2860
|
+
Function messages are NOT JSON-serializable. JSON-driven configs MUST keep using string templates with \`{{param}}\` interpolation; MCP-generated configs should always emit string templates.
|
|
2861
|
+
|
|
2832
2862
|
## Conditional Logic
|
|
2833
2863
|
|
|
2834
2864
|
Use \`logic\` array for conditional behavior:
|
|
@@ -8903,7 +8933,36 @@ validators: [{
|
|
|
8903
8933
|
}]
|
|
8904
8934
|
\`\`\`
|
|
8905
8935
|
|
|
8906
|
-
**Built-in Kinds:** required, email, min, max, minlength, maxlength, pattern
|
|
8936
|
+
**Built-in Kinds:** required, email, min, max, minlength, maxlength, pattern
|
|
8937
|
+
|
|
8938
|
+
## Template parameters
|
|
8939
|
+
|
|
8940
|
+
String messages interpolate error params with \`{{param}}\`:
|
|
8941
|
+
|
|
8942
|
+
\`\`\`typescript
|
|
8943
|
+
validationMessages: {
|
|
8944
|
+
minLength: 'Must be at least {{requiredLength}} characters',
|
|
8945
|
+
min: 'Must be at least {{min}}',
|
|
8946
|
+
max: 'Must be at most {{max}}',
|
|
8947
|
+
pattern: 'Does not match {{requiredPattern}}',
|
|
8948
|
+
}
|
|
8949
|
+
\`\`\`
|
|
8950
|
+
|
|
8951
|
+
## Error-aware message functions (TypeScript configs only)
|
|
8952
|
+
|
|
8953
|
+
Any \`validationMessages\` or \`defaultValidationMessages\` value can be a function \`(error: ValidationError) => DynamicText\` instead of a string. The function receives the validation error (its \`kind\` plus params such as \`maxLength\`), so an i18n layer gets the interpolation params natively:
|
|
8954
|
+
|
|
8955
|
+
\`\`\`typescript
|
|
8956
|
+
validationMessages: {
|
|
8957
|
+
maxLength: (error) => transloco.selectTranslate('validation.maxLength', {
|
|
8958
|
+
requiredLength: 'maxLength' in error ? error.maxLength : undefined,
|
|
8959
|
+
}),
|
|
8960
|
+
}
|
|
8961
|
+
\`\`\`
|
|
8962
|
+
|
|
8963
|
+
A resolver may return static text, an \`Observable\`, or a \`Signal\`, and those stay reactive. Returning a plain string after synchronously reading a signal severs reactivity \u2014 return the \`Signal\`/\`Observable\` itself instead.
|
|
8964
|
+
|
|
8965
|
+
**\u26A0\uFE0F Not JSON-serializable.** Function messages only work in TypeScript-authored configs. JSON-driven configs (and MCP-generated output) MUST use string templates with \`{{param}}\` interpolation.`
|
|
8907
8966
|
},
|
|
8908
8967
|
buttons: {
|
|
8909
8968
|
brief: `**submit:** \`{ type: 'submit', key: 'submit', label: 'Submit' }\`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-forge/dynamic-form-mcp",
|
|
3
|
-
"version": "1.1.0-next.
|
|
3
|
+
"version": "1.1.0-next.4",
|
|
4
4
|
"description": "MCP server for ng-forge dynamic forms - AI-assisted form schema generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,11 +54,11 @@
|
|
|
54
54
|
"zod-to-json-schema": "^3.23.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@ng-forge/dynamic-forms": "^1.1.0-next.
|
|
58
|
-
"@ng-forge/dynamic-forms-bootstrap": "^1.1.0-next.
|
|
59
|
-
"@ng-forge/dynamic-forms-ionic": "^1.1.0-next.
|
|
60
|
-
"@ng-forge/dynamic-forms-material": "^1.1.0-next.
|
|
61
|
-
"@ng-forge/dynamic-forms-primeng": "^1.1.0-next.
|
|
57
|
+
"@ng-forge/dynamic-forms": "^1.1.0-next.4",
|
|
58
|
+
"@ng-forge/dynamic-forms-bootstrap": "^1.1.0-next.4",
|
|
59
|
+
"@ng-forge/dynamic-forms-ionic": "^1.1.0-next.4",
|
|
60
|
+
"@ng-forge/dynamic-forms-material": "^1.1.0-next.4",
|
|
61
|
+
"@ng-forge/dynamic-forms-primeng": "^1.1.0-next.4",
|
|
62
62
|
"tsx": "^4.19.0"
|
|
63
63
|
}
|
|
64
64
|
}
|
package/src/index.js
CHANGED
|
@@ -755,12 +755,20 @@ var FIELD_TYPES = [
|
|
|
755
755
|
];
|
|
756
756
|
|
|
757
757
|
// packages/dynamic-form-mcp/src/registry/validators.ts
|
|
758
|
+
var WHEN_PARAMETER = {
|
|
759
|
+
when: {
|
|
760
|
+
name: "when",
|
|
761
|
+
type: "ConditionalExpression",
|
|
762
|
+
description: "Optional condition gating the validator. Accepts the same shapes as logic conditions: fieldValue comparisons, and/or composites, expression strings, functionName (registered condition), or http. Built-in validators apply the condition natively through Angular Signal Forms, so constraint metadata (e.g. field().maxLength()) stays in sync with the condition reactively. Cross-field conditions are supported. Constraints: http conditions require provideHttpClient() at the application level, and http/async conditions cannot be nested inside and/or composites (schema build throws).",
|
|
763
|
+
required: false
|
|
764
|
+
}
|
|
765
|
+
};
|
|
758
766
|
var VALIDATORS = [
|
|
759
767
|
{
|
|
760
768
|
type: "required",
|
|
761
769
|
category: "built-in",
|
|
762
770
|
description: "Validates that a field has a non-empty value",
|
|
763
|
-
parameters: {},
|
|
771
|
+
parameters: { ...WHEN_PARAMETER },
|
|
764
772
|
example: `{ type: 'required' }
|
|
765
773
|
// or shorthand: required: true`
|
|
766
774
|
},
|
|
@@ -768,7 +776,7 @@ var VALIDATORS = [
|
|
|
768
776
|
type: "email",
|
|
769
777
|
category: "built-in",
|
|
770
778
|
description: "Validates email format",
|
|
771
|
-
parameters: {},
|
|
779
|
+
parameters: { ...WHEN_PARAMETER },
|
|
772
780
|
example: `{ type: 'email' }
|
|
773
781
|
// or shorthand: email: true`
|
|
774
782
|
},
|
|
@@ -777,6 +785,7 @@ var VALIDATORS = [
|
|
|
777
785
|
category: "built-in",
|
|
778
786
|
description: "Validates minimum numeric value",
|
|
779
787
|
parameters: {
|
|
788
|
+
...WHEN_PARAMETER,
|
|
780
789
|
value: {
|
|
781
790
|
name: "value",
|
|
782
791
|
type: "number",
|
|
@@ -792,6 +801,7 @@ var VALIDATORS = [
|
|
|
792
801
|
category: "built-in",
|
|
793
802
|
description: "Validates maximum numeric value",
|
|
794
803
|
parameters: {
|
|
804
|
+
...WHEN_PARAMETER,
|
|
795
805
|
value: {
|
|
796
806
|
name: "value",
|
|
797
807
|
type: "number",
|
|
@@ -807,6 +817,7 @@ var VALIDATORS = [
|
|
|
807
817
|
category: "built-in",
|
|
808
818
|
description: "Validates minimum string length",
|
|
809
819
|
parameters: {
|
|
820
|
+
...WHEN_PARAMETER,
|
|
810
821
|
value: {
|
|
811
822
|
name: "value",
|
|
812
823
|
type: "number",
|
|
@@ -822,6 +833,7 @@ var VALIDATORS = [
|
|
|
822
833
|
category: "built-in",
|
|
823
834
|
description: "Validates maximum string length",
|
|
824
835
|
parameters: {
|
|
836
|
+
...WHEN_PARAMETER,
|
|
825
837
|
value: {
|
|
826
838
|
name: "value",
|
|
827
839
|
type: "number",
|
|
@@ -830,13 +842,16 @@ var VALIDATORS = [
|
|
|
830
842
|
}
|
|
831
843
|
},
|
|
832
844
|
example: `{ type: 'maxLength', value: 100 }
|
|
833
|
-
// or shorthand: maxLength: 100
|
|
845
|
+
// or shorthand: maxLength: 100
|
|
846
|
+
// conditional: only enforced while accountType is 'personal'
|
|
847
|
+
{ type: 'maxLength', value: 100, when: { type: 'fieldValue', fieldPath: 'accountType', operator: 'equals', value: 'personal' } }`
|
|
834
848
|
},
|
|
835
849
|
{
|
|
836
850
|
type: "pattern",
|
|
837
851
|
category: "built-in",
|
|
838
852
|
description: "Validates against a regular expression pattern",
|
|
839
853
|
parameters: {
|
|
854
|
+
...WHEN_PARAMETER,
|
|
840
855
|
value: {
|
|
841
856
|
name: "value",
|
|
842
857
|
type: "string | RegExp",
|
|
@@ -852,6 +867,7 @@ var VALIDATORS = [
|
|
|
852
867
|
category: "custom",
|
|
853
868
|
description: "Custom synchronous validator using registered function or expression. IMPORTANT: The validator returns { kind: 'errorKind' } on failure. The actual error MESSAGE is defined in 'validationMessages' at the FIELD level, NOT in the validator config. NOTE: TypeScript-authored configs may also use an inline `fn: CustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName`.",
|
|
854
869
|
parameters: {
|
|
870
|
+
...WHEN_PARAMETER,
|
|
855
871
|
functionName: {
|
|
856
872
|
name: "functionName",
|
|
857
873
|
type: "string",
|
|
@@ -904,6 +920,7 @@ var VALIDATORS = [
|
|
|
904
920
|
category: "async",
|
|
905
921
|
description: "Async validator using registered async function (resource-based). Register the function via customFnConfig.asyncValidators. NOTE: TypeScript-authored configs may also use an inline `fn: AsyncCustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName`.",
|
|
906
922
|
parameters: {
|
|
923
|
+
...WHEN_PARAMETER,
|
|
907
924
|
functionName: {
|
|
908
925
|
name: "functionName",
|
|
909
926
|
type: "string",
|
|
@@ -927,6 +944,7 @@ var VALIDATORS = [
|
|
|
927
944
|
category: "http",
|
|
928
945
|
description: "HTTP validator \u2014 supports two modes: (1) Declarative (fully JSON-serializable, no function registration) using http + responseMapping properties, or (2) Function-based using functionName to reference a registered HTTP validator function. Query param values and (optionally) body values are expressions evaluated against the form context. Response mapping uses validWhen (truthy = valid) and errorKind (maps to field-level validationMessages). NOTE: TypeScript-authored configs may also use an inline `fn: HttpCustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName` or declarative mode.",
|
|
929
946
|
parameters: {
|
|
947
|
+
...WHEN_PARAMETER,
|
|
930
948
|
functionName: {
|
|
931
949
|
name: "functionName",
|
|
932
950
|
type: "string",
|
|
@@ -2859,6 +2877,18 @@ ALWAYS provide validation messages. Use template variables:
|
|
|
2859
2877
|
- \`{{min}}\`, \`{{max}}\` - for min/max
|
|
2860
2878
|
- \`{{requiredPattern}}\` - for pattern
|
|
2861
2879
|
|
|
2880
|
+
TypeScript-authored configs may also use error-aware message functions: any \`validationMessages\` or \`defaultValidationMessages\` value can be \`(error: ValidationError) => DynamicText\` instead of a string/Observable/Signal. The function receives the validation error (kind plus params like \`maxLength\`), so i18n layers can receive interpolation params natively:
|
|
2881
|
+
|
|
2882
|
+
\`\`\`typescript
|
|
2883
|
+
validationMessages: {
|
|
2884
|
+
maxLength: (error) => transloco.selectTranslate('validation.maxLength', {
|
|
2885
|
+
requiredLength: 'maxLength' in error ? error.maxLength : undefined,
|
|
2886
|
+
}),
|
|
2887
|
+
}
|
|
2888
|
+
\`\`\`
|
|
2889
|
+
|
|
2890
|
+
Function messages are NOT JSON-serializable. JSON-driven configs MUST keep using string templates with \`{{param}}\` interpolation; MCP-generated configs should always emit string templates.
|
|
2891
|
+
|
|
2862
2892
|
## Conditional Logic
|
|
2863
2893
|
|
|
2864
2894
|
Use \`logic\` array for conditional behavior:
|
|
@@ -8980,7 +9010,36 @@ validators: [{
|
|
|
8980
9010
|
}]
|
|
8981
9011
|
\`\`\`
|
|
8982
9012
|
|
|
8983
|
-
**Built-in Kinds:** required, email, min, max, minlength, maxlength, pattern
|
|
9013
|
+
**Built-in Kinds:** required, email, min, max, minlength, maxlength, pattern
|
|
9014
|
+
|
|
9015
|
+
## Template parameters
|
|
9016
|
+
|
|
9017
|
+
String messages interpolate error params with \`{{param}}\`:
|
|
9018
|
+
|
|
9019
|
+
\`\`\`typescript
|
|
9020
|
+
validationMessages: {
|
|
9021
|
+
minLength: 'Must be at least {{requiredLength}} characters',
|
|
9022
|
+
min: 'Must be at least {{min}}',
|
|
9023
|
+
max: 'Must be at most {{max}}',
|
|
9024
|
+
pattern: 'Does not match {{requiredPattern}}',
|
|
9025
|
+
}
|
|
9026
|
+
\`\`\`
|
|
9027
|
+
|
|
9028
|
+
## Error-aware message functions (TypeScript configs only)
|
|
9029
|
+
|
|
9030
|
+
Any \`validationMessages\` or \`defaultValidationMessages\` value can be a function \`(error: ValidationError) => DynamicText\` instead of a string. The function receives the validation error (its \`kind\` plus params such as \`maxLength\`), so an i18n layer gets the interpolation params natively:
|
|
9031
|
+
|
|
9032
|
+
\`\`\`typescript
|
|
9033
|
+
validationMessages: {
|
|
9034
|
+
maxLength: (error) => transloco.selectTranslate('validation.maxLength', {
|
|
9035
|
+
requiredLength: 'maxLength' in error ? error.maxLength : undefined,
|
|
9036
|
+
}),
|
|
9037
|
+
}
|
|
9038
|
+
\`\`\`
|
|
9039
|
+
|
|
9040
|
+
A resolver may return static text, an \`Observable\`, or a \`Signal\`, and those stay reactive. Returning a plain string after synchronously reading a signal severs reactivity \u2014 return the \`Signal\`/\`Observable\` itself instead.
|
|
9041
|
+
|
|
9042
|
+
**\u26A0\uFE0F Not JSON-serializable.** Function messages only work in TypeScript-authored configs. JSON-driven configs (and MCP-generated output) MUST use string templates with \`{{param}}\` interpolation.`
|
|
8984
9043
|
},
|
|
8985
9044
|
buttons: {
|
|
8986
9045
|
brief: `**submit:** \`{ type: 'submit', key: 'submit', label: 'Submit' }\`
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** ng-forge Best Practices */
|
|
2
|
-
export declare const INSTRUCTIONS = "# ng-forge Dynamic Forms - Best Practices\n\nYou MUST follow these practices when generating FormConfig objects for ng-forge.\n\n## Core Principles\n\n1. **Configuration-driven**: ng-forge wraps Angular Reactive Forms. Define structure declaratively via `FormConfig`, not imperatively.\n2. **Type-safe**: Use TypeScript interfaces for form values. The library provides full type inference.\n3. **Validation-first**: Always include validation and user-friendly error messages.\n\n## FormConfig Structure\n\n```typescript\nconst config: FormConfig = {\n // Optional: Default validation messages for all fields\n defaultValidationMessages: {\n required: 'This field is required',\n email: 'Please enter a valid email',\n minLength: 'Must be at least {{requiredLength}} characters',\n },\n // Required: Array of field definitions\n fields: [\n // ... field definitions\n ],\n};\n```\n\n## Field Definition Rules\n\n### Required Properties\n\nEvery field MUST have:\n\n- `key`: Identifier, unique within its group scope (the same leaf key may repeat inside different groups, e.g. `createA.name` and `createB.name`). Page/row/array containers do not introduce a scope. Buttons (which don't bind to form values) don't need unique keys.\n - **DOM id / data-testid composition:** keys are joined with `_` through group ancestors to form the rendered DOM id. A leaf `street` inside group `address` renders as `id=\"address_street\"`. Inside an array item, the item index is appended after: `id=\"address_street_0\"`. Use this exact form for `for=`, `aria-describedby`, `aria-labelledby`, or CSS selectors that target group-nested fields.\n - **Form-level prefix (multiple forms on one page):** set `options.idPrefix` to scope every id to a form instance \u2014 it becomes the OUTERMOST segment: `{idPrefix}_{group}_{key}_{index}` (e.g. `billing_address_street_0`). When omitted, a lone form stays unprefixed, but if two or more forms are mounted at once the library auto-prefixes each (`df-1`, `df-2`, \u2026) so their ids don't collide. Account for an explicit prefix when writing `for=`/`aria-*`/selectors.\n - **Avoid `_` in keys when they could collide with a group-prefixed id.** A top-level key `foo_bar` and a leaf `bar` inside group `foo` would both render as `id=\"foo_bar\"`. The validator catches this collision at boot, but it's easier to just not put `_` in keys that share a name with an existing group + leaf pair.\n- `type`: One of the registered field types\n- `label`: Human-readable label (for accessibility)\n\n### Value Fields\n\nFor fields that collect user input (`input`, `textarea`, `select`, `checkbox`, `radio`, `datepicker`, `toggle`, `slider`):\n\n```typescript\n{\n key: 'email',\n type: 'input',\n label: 'Email Address',\n required: true, // Shorthand validator\n email: true, // Shorthand validator\n placeholder: 'user@example.com', // Field-level, not inside props\n validationMessages: { // Always provide clear messages\n required: 'Email is required',\n email: 'Please enter a valid email'\n },\n props: {\n type: 'email', // HTML input type\n hint: 'We will never share your email' // UI library specific\n }\n}\n```\n\n### Select/Radio Fields\n\nMUST include `options` array:\n\n```typescript\n{\n key: 'country',\n type: 'select',\n label: 'Country',\n required: true,\n placeholder: 'Select a country',\n options: [\n { value: 'us', label: 'United States' },\n { value: 'uk', label: 'United Kingdom' }\n ]\n}\n```\n\n### Container Fields\n\n#### Row (Horizontal Layout)\n\nUse for side-by-side fields. Row fields do NOT create nested values - children are flat.\n\n```typescript\n{\n key: 'nameRow',\n type: 'row',\n fields: [\n { key: 'firstName', type: 'input', label: 'First', col: 6 },\n { key: 'lastName', type: 'input', label: 'Last', col: 6 }\n ]\n}\n// Result: { firstName: '...', lastName: '...' }\n```\n\nUse `col` property (1-12) to control column widths.\n\n#### Group (Nested Object)\n\nUse when you need nested object structure in form values:\n\n```typescript\n{\n key: 'address',\n type: 'group',\n fields: [\n { key: 'street', type: 'input', label: 'Street' },\n { key: 'city', type: 'input', label: 'City' }\n ]\n}\n// Result: { address: { street: '...', city: '...' } }\n```\n\n#### Array (Repeatable Items)\n\nUse for dynamic lists. Each item is wrapped in a `<div class=\"df-array-item\">` with `role=\"group\"` and `aria-label=\"Item N\"` for accessibility. Use `--df-array-item-gap` CSS variable to control spacing between items.\n\n```typescript\n{\n key: 'contacts',\n type: 'array',\n // \u26A0\uFE0F NO label property on arrays!\n fields: [\n {\n key: 'contact',\n type: 'group',\n fields: [\n { key: 'name', type: 'input', label: 'Name', required: true },\n { key: 'phone', type: 'input', label: 'Phone', required: true }\n ]\n }\n ]\n}\n// Result: { contacts: [{ name: '...', phone: '...' }, ...] }\n```\n\n#### Page (Multi-step Forms)\n\nUse for wizard-style forms:\n\n```typescript\n{\n fields: [\n {\n key: 'step1',\n type: 'page',\n fields: [\n { key: 'name', type: 'input', label: 'Name' },\n { key: 'next', type: 'next', label: 'Continue' },\n ],\n },\n {\n key: 'step2',\n type: 'page',\n fields: [\n { key: 'prev', type: 'previous', label: 'Back' },\n { key: 'submit', type: 'submit', label: 'Submit' },\n ],\n },\n ];\n}\n```\n\n## Validation\n\n### Shorthand Validators (Preferred)\n\nUse shorthand properties directly on field definitions:\n\n```typescript\n{\n key: 'age',\n type: 'input',\n label: 'Age',\n required: true,\n min: 18,\n max: 120,\n validationMessages: {\n required: 'Age is required',\n min: 'Must be at least {{min}}',\n max: 'Cannot exceed {{max}}'\n }\n}\n```\n\nAvailable shorthands: `required`, `email`, `min`, `max`, `minLength`, `maxLength`, `pattern`\n\n### Nullable Values\n\nValue fields and checked fields (`checkbox`, `toggle`) accept a `nullable?: boolean` flag. When `true`:\n\n- `value` accepts `null` in addition to the field's normal value type.\n- An omitted `value` resolves to `null` (instead of the type-specific empty default like `''`, `NaN`, `false`, or `[]`).\n- Orthogonal to `required`. `nullable` declares that the **model** accepts `null`; `required` is a **validation** constraint. Angular's `Validators.required` treats `null` as invalid, so a field that is both `nullable` and `required` will fail required-validation when the value is `null` \u2014 the two flags describe different layers (data shape vs. validation), not conflicting semantics.\n- Map OpenAPI `nullable: true` (3.0) or `type: [T, null]` (3.1) to this flag.\n- Container fields (`group`, `array`, `row`, `page`) do NOT support `nullable` \u2014 their null semantics are ambiguous.\n\n```typescript\n{\n key: 'middleName',\n type: 'input',\n label: 'Middle Name',\n nullable: true,\n value: null, // valid; default-resolves to null\n}\n\n{\n key: 'active',\n type: 'checkbox',\n label: 'Active',\n nullable: true,\n value: null, // undecided checkbox state \u2014 model permits null, UI renders unchecked\n}\n```\n\nRead-side caveat: a user clearing a text input reads back as `\"\"`, not `null` \u2014 this is a DOM/Web IDL contract, identical to classic Reactive Forms. `nullable` is a contract for accepted values, not a guarantee of emitted ones.\n\n### Validation Messages\n\nALWAYS provide validation messages. Use template variables:\n\n- `{{requiredLength}}` - for minLength/maxLength\n- `{{min}}`, `{{max}}` - for min/max\n- `{{requiredPattern}}` - for pattern\n\n## Conditional Logic\n\nUse `logic` array for conditional behavior:\n\n```typescript\n{\n key: 'businessName',\n type: 'input',\n label: 'Business Name',\n logic: [\n {\n type: 'hidden',\n condition: {\n type: 'fieldValue',\n fieldPath: 'accountType',\n operator: 'notEquals',\n value: 'business'\n }\n },\n {\n type: 'required',\n condition: {\n type: 'fieldValue',\n fieldPath: 'accountType',\n operator: 'equals',\n value: 'business'\n }\n }\n ]\n}\n```\n\n### Logic Types\n\n- `hidden` - Show/hide field\n- `disabled` - Enable/disable field\n- `required` - Conditional requirement\n\n### Condition Types\n\n#### Field Value Condition\n\nCompares a specific field's value:\n\n```typescript\n{\n type: 'fieldValue',\n fieldPath: 'accountType',\n operator: 'equals',\n value: 'business'\n}\n```\n\n#### Comparison Operators\n\n- `equals`, `notEquals`\n- `greater`, `less`, `greaterOrEqual`, `lessOrEqual`\n- `contains`, `startsWith`, `endsWith`, `matches`\n\n#### JavaScript Condition\n\nEvaluates a JavaScript expression with access to `formValue`, `fieldValue`, `externalData`:\n\n```typescript\n{\n type: 'javascript',\n expression: 'formValue.items.length > 0'\n}\n```\n\n**Condition scoping:** in logic conditions, `formValue` is the whole form value regardless of where the field sits, except inside array items where it is scoped to the current item (use `rootFormValue` for the rest of the form). Reference group-nested fields by their full path, e.g. `formValue.person.firstName`. Note that `derivation` expressions scope differently: there `formValue` is also scoped to the parent group for group-nested fields.\n\n#### Custom Condition\n\nInvokes a registered custom function by name:\n\n```typescript\n{\n type: 'custom',\n functionName: 'myCustomCheck'\n}\n```\n\nRegister functions via `customFnConfig.customFunctions`.\n\n> **TypeScript-authored configs:** the same surface also accepts an inline `fn: CustomFunction` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable). The MCP server emits `functionName` exclusively; consumers writing configs in TS may prefer `fn` when they don't need JSON round-tripping. Same caveat applies to async conditions (`asyncFn`), function derivations (`fn`), async function derivations (`asyncFn`), and validators (`fn`).\n\n#### Async Custom Condition\n\nInvokes a registered async function that returns `Promise<boolean>` or `Observable<boolean>`:\n\n```typescript\n{\n type: 'async',\n asyncFunctionName: 'checkPermission',\n pendingValue: false, // value while async resolution is pending (default: false)\n debounceMs: 300 // debounce for re-evaluation (default: 300)\n}\n```\n\nRegister functions via `customFnConfig.asyncConditions`. The function receives an `EvaluationContext` with access to `formValue`, `fieldValue`, and `externalData`.\n\n#### HTTP Condition\n\nEvaluates based on an HTTP response from a remote server. Fully declarative and JSON-serializable:\n\n```typescript\n{\n type: 'http',\n http: {\n url: '/api/permissions',\n method: 'GET',\n queryParams: {\n userId: 'formValue.userId',\n role: 'formValue.role'\n }\n },\n responseExpression: 'response.canEdit', // extract boolean from response (default: !!response)\n pendingValue: false, // value while HTTP request is in-flight (default: false)\n cacheDurationMs: 30000, // cache duration in ms (default: 30000)\n debounceMs: 300 // debounce for re-evaluation (default: 300)\n}\n```\n\nUse HTTP conditions when visibility, disabled state, or required state depends on server-side data (e.g., permissions, feature flags, inventory checks). The request re-evaluates reactively when dependent form values change.\n\n#### Logical Combinators\n\nCombine conditions with `and` (all must be true) or `or` (at least one must be true):\n\n```typescript\n{\n type: 'and',\n conditions: [\n { type: 'fieldValue', fieldPath: 'country', operator: 'equals', value: 'US' },\n { type: 'fieldValue', fieldPath: 'age', operator: 'greaterOrEqual', value: 18 }\n ]\n}\n```\n\n## UI Library Integration\n\nng-forge supports multiple UI libraries. Configure in `app.config.ts`:\n\n```typescript\nimport { provideDynamicForm } from '@ng-forge/dynamic-forms';\nimport { withMaterialFields } from '@ng-forge/dynamic-forms-material';\n\nexport const appConfig = {\n providers: [provideDynamicForm(...withMaterialFields())],\n};\n```\n\nAvailable adapters:\n\n- `@ng-forge/dynamic-forms-material` - Angular Material\n- `@ng-forge/dynamic-forms-bootstrap` - Bootstrap 5\n- `@ng-forge/dynamic-forms-primeng` - PrimeNG\n- `@ng-forge/dynamic-forms-ionic` - Ionic\n\n### Style Defaults (opt-in)\n\nEach adapter ships an opinionated Sass partial with host-framework colors/typography. Library SCSS uses `var(--df-name, structural-fallback)` so forms render usefully without any opt-in \u2014 only opt in if you want the adapter-themed look.\n\n```scss\n@use '@ng-forge/dynamic-forms-material/styles/defaults' as df;\n\n:root {\n @include df.apply-material-form-defaults;\n}\n```\n\nSubstitute `material` for `bootstrap`, `primeng`, or `ionic`. Material also ships `form-material-defaults-dark` for dark-mode hint/error colors. Ionic ships `apply-ionic-legacy-error-styles` that pairs with `withLegacyStatusClasses()` (see below).\n\n## Provider Features\n\n`provideDynamicForm()` accepts optional `with*` feature configurators alongside the adapter field bundle:\n\n### `withLegacyStatusClasses()` \u2014 opt into legacy ng-* CSS classes\n\nBy default, Angular Signal Forms uses its modern class strategy. If your CSS targets `.ng-touched`, `.ng-invalid`, `.ng-dirty`, etc. on form-bound elements, add this feature to wire up the compat-class strategy:\n\n```typescript\nimport { provideDynamicForm, withLegacyStatusClasses } from '@ng-forge/dynamic-forms';\nimport { withMaterialFields } from '@ng-forge/dynamic-forms-material';\n\nexport const appConfig = {\n providers: [\n provideDynamicForm(\n ...withMaterialFields(),\n withLegacyStatusClasses(),\n ),\n ],\n};\n```\n\nRecommended when consuming Ionic's `apply-ionic-legacy-error-styles` mixin, when using Bootstrap themes that style `is-invalid.ng-touched`, or any custom CSS that depends on the `ng-*` classes.\n\n### Other built-in features\n\n- `withLoggerConfig(options?)` \u2014 configure or disable the dynamic-form logger\n- `withEventFormValue()` \u2014 attach the current form value to every dispatched event\n- `withValueExclusionDefaults(options)` \u2014 set form-wide value exclusion defaults\n- `withValidationExecutionDefaults(options)` \u2014 set form-wide validation execution defaults (e.g. defer until touched)\n\nAll features are exported from `@ng-forge/dynamic-forms`.\n\n## Common Patterns\n\n### Always End with Submit Button\n\n```typescript\n{ type: 'submit', key: 'submit', label: 'Submit', props: { color: 'primary' } }\n```\n\n### Use Text Fields for Headings\n\n```typescript\n{ key: 'sectionTitle', type: 'text', label: 'Personal Info', props: { elementType: 'h3' } }\n```\n\n### Input Types via Props\n\n```typescript\n// Email input\n{ key: 'email', type: 'input', props: { type: 'email' } }\n\n// Password input\n{ key: 'password', type: 'input', props: { type: 'password' } }\n\n// Number input\n{ key: 'age', type: 'input', props: { type: 'number' } }\n\n// Phone input\n{ key: 'phone', type: 'input', props: { type: 'tel' } }\n```\n\n## Anti-patterns to Avoid\n\n1. **Missing labels** - Always include labels for accessibility\n2. **Missing validation messages** - Users need clear feedback\n3. **Duplicate keys within a group scope** - Keys must be unique within the same group. Same key in different groups is fine (different scoped paths). Buttons are exempt.\n4. **Select without options** - Select/radio fields must have options\n5. **Array without fields** - Array fields need a fields template\n6. **Group without fields** - Group fields need child fields\n7. **min > max** - Ensure min values are less than max values\n\n## CRITICAL: UI Library Differences\n\nDifferent UI libraries support different properties. Always validate against the correct library using `ngforge_validate` with the appropriate `uiIntegration` parameter.\n\n### Property Differences by Library\n\n| Property | Material | Bootstrap | PrimeNG | Ionic |\n|----------|----------|-----------|---------|-------|\n| `appearance` | \u2713 (fill, outline) | \u2717 | \u2717 | \u2713 (fill, outline) |\n| `subscriptSizing` | \u2713 | \u2717 | \u2717 | \u2717 |\n| `floatLabel` | \u2713 (auto, always, never) | \u2717 | \u2717 | \u2717 |\n| `hideRequiredMarker` | \u2713 | \u2717 | \u2717 | \u2717 |\n| `hint` | \u2713 | \u2713 (as `helpText`) | \u2713 | \u2713 |\n\n### Container Fields\n\nContainer fields (`page`, `group`, `row`) do NOT support these properties:\n- `label` - Use `text` field type for headings instead\n- `required`, `email`, `min`, `max`, etc. - Validation is for leaf fields only\n\n### Common Mistakes to Avoid\n\n1. **Using `expressions` on fields** - This property is NOT supported on standard fields. Use `derivation` for computed values or `logic` for conditional behavior.\n\n2. **Adding `label` to container fields** - Pages, groups, and rows don't have labels:\n ```typescript\n // \u274C WRONG\n { key: 'address', type: 'group', label: 'Address', fields: [...] }\n\n // \u2705 CORRECT - Use a text field for the heading\n { key: 'addressTitle', type: 'text', label: 'Address', props: { elementType: 'h3' } }\n { key: 'address', type: 'group', fields: [...] }\n ```\n\n3. **Adding `id` to FormConfig** - The root FormConfig does not accept an `id` property.\n\n4. **Putting slider min/max in props** - For slider fields, `min`, `max`, and `step` are field-level properties:\n ```typescript\n // \u274C WRONG\n { key: 'volume', type: 'slider', label: 'Volume', props: { min: 0, max: 100 } }\n\n // \u2705 CORRECT\n { key: 'volume', type: 'slider', label: 'Volume', min: 0, max: 100 }\n ```\n\n5. **Using unsupported field types** - Each UI library supports specific field types. Use `ngforge_lookup` with `depth: \"schema\"` to see available types.\n\n## Validation Before Use\n\nAlways validate your FormConfig using the `ngforge_validate` tool before using it. This tool uses the actual TypeScript/Zod schemas, so if validation passes, the config will work correctly at runtime.\n\n```typescript\n// Always specify the UI integration you're using\nngforge_validate({\n uiIntegration: 'material', // or 'bootstrap', 'primeng', 'ionic'\n config: { fields: [...] }\n})\n```\n\nTo understand what properties are supported for each field type, use `ngforge_lookup` with `depth: \"schema\"`:\n\n```typescript\nngforge_lookup({ topic: 'input', depth: 'schema', uiIntegration: 'material' })\n```\n";
|
|
2
|
+
export declare const INSTRUCTIONS = "# ng-forge Dynamic Forms - Best Practices\n\nYou MUST follow these practices when generating FormConfig objects for ng-forge.\n\n## Core Principles\n\n1. **Configuration-driven**: ng-forge wraps Angular Reactive Forms. Define structure declaratively via `FormConfig`, not imperatively.\n2. **Type-safe**: Use TypeScript interfaces for form values. The library provides full type inference.\n3. **Validation-first**: Always include validation and user-friendly error messages.\n\n## FormConfig Structure\n\n```typescript\nconst config: FormConfig = {\n // Optional: Default validation messages for all fields\n defaultValidationMessages: {\n required: 'This field is required',\n email: 'Please enter a valid email',\n minLength: 'Must be at least {{requiredLength}} characters',\n },\n // Required: Array of field definitions\n fields: [\n // ... field definitions\n ],\n};\n```\n\n## Field Definition Rules\n\n### Required Properties\n\nEvery field MUST have:\n\n- `key`: Identifier, unique within its group scope (the same leaf key may repeat inside different groups, e.g. `createA.name` and `createB.name`). Page/row/array containers do not introduce a scope. Buttons (which don't bind to form values) don't need unique keys.\n - **DOM id / data-testid composition:** keys are joined with `_` through group ancestors to form the rendered DOM id. A leaf `street` inside group `address` renders as `id=\"address_street\"`. Inside an array item, the item index is appended after: `id=\"address_street_0\"`. Use this exact form for `for=`, `aria-describedby`, `aria-labelledby`, or CSS selectors that target group-nested fields.\n - **Form-level prefix (multiple forms on one page):** set `options.idPrefix` to scope every id to a form instance \u2014 it becomes the OUTERMOST segment: `{idPrefix}_{group}_{key}_{index}` (e.g. `billing_address_street_0`). When omitted, a lone form stays unprefixed, but if two or more forms are mounted at once the library auto-prefixes each (`df-1`, `df-2`, \u2026) so their ids don't collide. Account for an explicit prefix when writing `for=`/`aria-*`/selectors.\n - **Avoid `_` in keys when they could collide with a group-prefixed id.** A top-level key `foo_bar` and a leaf `bar` inside group `foo` would both render as `id=\"foo_bar\"`. The validator catches this collision at boot, but it's easier to just not put `_` in keys that share a name with an existing group + leaf pair.\n- `type`: One of the registered field types\n- `label`: Human-readable label (for accessibility)\n\n### Value Fields\n\nFor fields that collect user input (`input`, `textarea`, `select`, `checkbox`, `radio`, `datepicker`, `toggle`, `slider`):\n\n```typescript\n{\n key: 'email',\n type: 'input',\n label: 'Email Address',\n required: true, // Shorthand validator\n email: true, // Shorthand validator\n placeholder: 'user@example.com', // Field-level, not inside props\n validationMessages: { // Always provide clear messages\n required: 'Email is required',\n email: 'Please enter a valid email'\n },\n props: {\n type: 'email', // HTML input type\n hint: 'We will never share your email' // UI library specific\n }\n}\n```\n\n### Select/Radio Fields\n\nMUST include `options` array:\n\n```typescript\n{\n key: 'country',\n type: 'select',\n label: 'Country',\n required: true,\n placeholder: 'Select a country',\n options: [\n { value: 'us', label: 'United States' },\n { value: 'uk', label: 'United Kingdom' }\n ]\n}\n```\n\n### Container Fields\n\n#### Row (Horizontal Layout)\n\nUse for side-by-side fields. Row fields do NOT create nested values - children are flat.\n\n```typescript\n{\n key: 'nameRow',\n type: 'row',\n fields: [\n { key: 'firstName', type: 'input', label: 'First', col: 6 },\n { key: 'lastName', type: 'input', label: 'Last', col: 6 }\n ]\n}\n// Result: { firstName: '...', lastName: '...' }\n```\n\nUse `col` property (1-12) to control column widths.\n\n#### Group (Nested Object)\n\nUse when you need nested object structure in form values:\n\n```typescript\n{\n key: 'address',\n type: 'group',\n fields: [\n { key: 'street', type: 'input', label: 'Street' },\n { key: 'city', type: 'input', label: 'City' }\n ]\n}\n// Result: { address: { street: '...', city: '...' } }\n```\n\n#### Array (Repeatable Items)\n\nUse for dynamic lists. Each item is wrapped in a `<div class=\"df-array-item\">` with `role=\"group\"` and `aria-label=\"Item N\"` for accessibility. Use `--df-array-item-gap` CSS variable to control spacing between items.\n\n```typescript\n{\n key: 'contacts',\n type: 'array',\n // \u26A0\uFE0F NO label property on arrays!\n fields: [\n {\n key: 'contact',\n type: 'group',\n fields: [\n { key: 'name', type: 'input', label: 'Name', required: true },\n { key: 'phone', type: 'input', label: 'Phone', required: true }\n ]\n }\n ]\n}\n// Result: { contacts: [{ name: '...', phone: '...' }, ...] }\n```\n\n#### Page (Multi-step Forms)\n\nUse for wizard-style forms:\n\n```typescript\n{\n fields: [\n {\n key: 'step1',\n type: 'page',\n fields: [\n { key: 'name', type: 'input', label: 'Name' },\n { key: 'next', type: 'next', label: 'Continue' },\n ],\n },\n {\n key: 'step2',\n type: 'page',\n fields: [\n { key: 'prev', type: 'previous', label: 'Back' },\n { key: 'submit', type: 'submit', label: 'Submit' },\n ],\n },\n ];\n}\n```\n\n## Validation\n\n### Shorthand Validators (Preferred)\n\nUse shorthand properties directly on field definitions:\n\n```typescript\n{\n key: 'age',\n type: 'input',\n label: 'Age',\n required: true,\n min: 18,\n max: 120,\n validationMessages: {\n required: 'Age is required',\n min: 'Must be at least {{min}}',\n max: 'Cannot exceed {{max}}'\n }\n}\n```\n\nAvailable shorthands: `required`, `email`, `min`, `max`, `minLength`, `maxLength`, `pattern`\n\n### Nullable Values\n\nValue fields and checked fields (`checkbox`, `toggle`) accept a `nullable?: boolean` flag. When `true`:\n\n- `value` accepts `null` in addition to the field's normal value type.\n- An omitted `value` resolves to `null` (instead of the type-specific empty default like `''`, `NaN`, `false`, or `[]`).\n- Orthogonal to `required`. `nullable` declares that the **model** accepts `null`; `required` is a **validation** constraint. Angular's `Validators.required` treats `null` as invalid, so a field that is both `nullable` and `required` will fail required-validation when the value is `null` \u2014 the two flags describe different layers (data shape vs. validation), not conflicting semantics.\n- Map OpenAPI `nullable: true` (3.0) or `type: [T, null]` (3.1) to this flag.\n- Container fields (`group`, `array`, `row`, `page`) do NOT support `nullable` \u2014 their null semantics are ambiguous.\n\n```typescript\n{\n key: 'middleName',\n type: 'input',\n label: 'Middle Name',\n nullable: true,\n value: null, // valid; default-resolves to null\n}\n\n{\n key: 'active',\n type: 'checkbox',\n label: 'Active',\n nullable: true,\n value: null, // undecided checkbox state \u2014 model permits null, UI renders unchecked\n}\n```\n\nRead-side caveat: a user clearing a text input reads back as `\"\"`, not `null` \u2014 this is a DOM/Web IDL contract, identical to classic Reactive Forms. `nullable` is a contract for accepted values, not a guarantee of emitted ones.\n\n### Validation Messages\n\nALWAYS provide validation messages. Use template variables:\n\n- `{{requiredLength}}` - for minLength/maxLength\n- `{{min}}`, `{{max}}` - for min/max\n- `{{requiredPattern}}` - for pattern\n\nTypeScript-authored configs may also use error-aware message functions: any `validationMessages` or `defaultValidationMessages` value can be `(error: ValidationError) => DynamicText` instead of a string/Observable/Signal. The function receives the validation error (kind plus params like `maxLength`), so i18n layers can receive interpolation params natively:\n\n```typescript\nvalidationMessages: {\n maxLength: (error) => transloco.selectTranslate('validation.maxLength', {\n requiredLength: 'maxLength' in error ? error.maxLength : undefined,\n }),\n}\n```\n\nFunction messages are NOT JSON-serializable. JSON-driven configs MUST keep using string templates with `{{param}}` interpolation; MCP-generated configs should always emit string templates.\n\n## Conditional Logic\n\nUse `logic` array for conditional behavior:\n\n```typescript\n{\n key: 'businessName',\n type: 'input',\n label: 'Business Name',\n logic: [\n {\n type: 'hidden',\n condition: {\n type: 'fieldValue',\n fieldPath: 'accountType',\n operator: 'notEquals',\n value: 'business'\n }\n },\n {\n type: 'required',\n condition: {\n type: 'fieldValue',\n fieldPath: 'accountType',\n operator: 'equals',\n value: 'business'\n }\n }\n ]\n}\n```\n\n### Logic Types\n\n- `hidden` - Show/hide field\n- `disabled` - Enable/disable field\n- `required` - Conditional requirement\n\n### Condition Types\n\n#### Field Value Condition\n\nCompares a specific field's value:\n\n```typescript\n{\n type: 'fieldValue',\n fieldPath: 'accountType',\n operator: 'equals',\n value: 'business'\n}\n```\n\n#### Comparison Operators\n\n- `equals`, `notEquals`\n- `greater`, `less`, `greaterOrEqual`, `lessOrEqual`\n- `contains`, `startsWith`, `endsWith`, `matches`\n\n#### JavaScript Condition\n\nEvaluates a JavaScript expression with access to `formValue`, `fieldValue`, `externalData`:\n\n```typescript\n{\n type: 'javascript',\n expression: 'formValue.items.length > 0'\n}\n```\n\n**Condition scoping:** in logic conditions, `formValue` is the whole form value regardless of where the field sits, except inside array items where it is scoped to the current item (use `rootFormValue` for the rest of the form). Reference group-nested fields by their full path, e.g. `formValue.person.firstName`. Note that `derivation` expressions scope differently: there `formValue` is also scoped to the parent group for group-nested fields.\n\n#### Custom Condition\n\nInvokes a registered custom function by name:\n\n```typescript\n{\n type: 'custom',\n functionName: 'myCustomCheck'\n}\n```\n\nRegister functions via `customFnConfig.customFunctions`.\n\n> **TypeScript-authored configs:** the same surface also accepts an inline `fn: CustomFunction` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable). The MCP server emits `functionName` exclusively; consumers writing configs in TS may prefer `fn` when they don't need JSON round-tripping. Same caveat applies to async conditions (`asyncFn`), function derivations (`fn`), async function derivations (`asyncFn`), and validators (`fn`).\n\n#### Async Custom Condition\n\nInvokes a registered async function that returns `Promise<boolean>` or `Observable<boolean>`:\n\n```typescript\n{\n type: 'async',\n asyncFunctionName: 'checkPermission',\n pendingValue: false, // value while async resolution is pending (default: false)\n debounceMs: 300 // debounce for re-evaluation (default: 300)\n}\n```\n\nRegister functions via `customFnConfig.asyncConditions`. The function receives an `EvaluationContext` with access to `formValue`, `fieldValue`, and `externalData`.\n\n#### HTTP Condition\n\nEvaluates based on an HTTP response from a remote server. Fully declarative and JSON-serializable:\n\n```typescript\n{\n type: 'http',\n http: {\n url: '/api/permissions',\n method: 'GET',\n queryParams: {\n userId: 'formValue.userId',\n role: 'formValue.role'\n }\n },\n responseExpression: 'response.canEdit', // extract boolean from response (default: !!response)\n pendingValue: false, // value while HTTP request is in-flight (default: false)\n cacheDurationMs: 30000, // cache duration in ms (default: 30000)\n debounceMs: 300 // debounce for re-evaluation (default: 300)\n}\n```\n\nUse HTTP conditions when visibility, disabled state, or required state depends on server-side data (e.g., permissions, feature flags, inventory checks). The request re-evaluates reactively when dependent form values change.\n\n#### Logical Combinators\n\nCombine conditions with `and` (all must be true) or `or` (at least one must be true):\n\n```typescript\n{\n type: 'and',\n conditions: [\n { type: 'fieldValue', fieldPath: 'country', operator: 'equals', value: 'US' },\n { type: 'fieldValue', fieldPath: 'age', operator: 'greaterOrEqual', value: 18 }\n ]\n}\n```\n\n## UI Library Integration\n\nng-forge supports multiple UI libraries. Configure in `app.config.ts`:\n\n```typescript\nimport { provideDynamicForm } from '@ng-forge/dynamic-forms';\nimport { withMaterialFields } from '@ng-forge/dynamic-forms-material';\n\nexport const appConfig = {\n providers: [provideDynamicForm(...withMaterialFields())],\n};\n```\n\nAvailable adapters:\n\n- `@ng-forge/dynamic-forms-material` - Angular Material\n- `@ng-forge/dynamic-forms-bootstrap` - Bootstrap 5\n- `@ng-forge/dynamic-forms-primeng` - PrimeNG\n- `@ng-forge/dynamic-forms-ionic` - Ionic\n\n### Style Defaults (opt-in)\n\nEach adapter ships an opinionated Sass partial with host-framework colors/typography. Library SCSS uses `var(--df-name, structural-fallback)` so forms render usefully without any opt-in \u2014 only opt in if you want the adapter-themed look.\n\n```scss\n@use '@ng-forge/dynamic-forms-material/styles/defaults' as df;\n\n:root {\n @include df.apply-material-form-defaults;\n}\n```\n\nSubstitute `material` for `bootstrap`, `primeng`, or `ionic`. Material also ships `form-material-defaults-dark` for dark-mode hint/error colors. Ionic ships `apply-ionic-legacy-error-styles` that pairs with `withLegacyStatusClasses()` (see below).\n\n## Provider Features\n\n`provideDynamicForm()` accepts optional `with*` feature configurators alongside the adapter field bundle:\n\n### `withLegacyStatusClasses()` \u2014 opt into legacy ng-* CSS classes\n\nBy default, Angular Signal Forms uses its modern class strategy. If your CSS targets `.ng-touched`, `.ng-invalid`, `.ng-dirty`, etc. on form-bound elements, add this feature to wire up the compat-class strategy:\n\n```typescript\nimport { provideDynamicForm, withLegacyStatusClasses } from '@ng-forge/dynamic-forms';\nimport { withMaterialFields } from '@ng-forge/dynamic-forms-material';\n\nexport const appConfig = {\n providers: [\n provideDynamicForm(\n ...withMaterialFields(),\n withLegacyStatusClasses(),\n ),\n ],\n};\n```\n\nRecommended when consuming Ionic's `apply-ionic-legacy-error-styles` mixin, when using Bootstrap themes that style `is-invalid.ng-touched`, or any custom CSS that depends on the `ng-*` classes.\n\n### Other built-in features\n\n- `withLoggerConfig(options?)` \u2014 configure or disable the dynamic-form logger\n- `withEventFormValue()` \u2014 attach the current form value to every dispatched event\n- `withValueExclusionDefaults(options)` \u2014 set form-wide value exclusion defaults\n- `withValidationExecutionDefaults(options)` \u2014 set form-wide validation execution defaults (e.g. defer until touched)\n\nAll features are exported from `@ng-forge/dynamic-forms`.\n\n## Common Patterns\n\n### Always End with Submit Button\n\n```typescript\n{ type: 'submit', key: 'submit', label: 'Submit', props: { color: 'primary' } }\n```\n\n### Use Text Fields for Headings\n\n```typescript\n{ key: 'sectionTitle', type: 'text', label: 'Personal Info', props: { elementType: 'h3' } }\n```\n\n### Input Types via Props\n\n```typescript\n// Email input\n{ key: 'email', type: 'input', props: { type: 'email' } }\n\n// Password input\n{ key: 'password', type: 'input', props: { type: 'password' } }\n\n// Number input\n{ key: 'age', type: 'input', props: { type: 'number' } }\n\n// Phone input\n{ key: 'phone', type: 'input', props: { type: 'tel' } }\n```\n\n## Anti-patterns to Avoid\n\n1. **Missing labels** - Always include labels for accessibility\n2. **Missing validation messages** - Users need clear feedback\n3. **Duplicate keys within a group scope** - Keys must be unique within the same group. Same key in different groups is fine (different scoped paths). Buttons are exempt.\n4. **Select without options** - Select/radio fields must have options\n5. **Array without fields** - Array fields need a fields template\n6. **Group without fields** - Group fields need child fields\n7. **min > max** - Ensure min values are less than max values\n\n## CRITICAL: UI Library Differences\n\nDifferent UI libraries support different properties. Always validate against the correct library using `ngforge_validate` with the appropriate `uiIntegration` parameter.\n\n### Property Differences by Library\n\n| Property | Material | Bootstrap | PrimeNG | Ionic |\n|----------|----------|-----------|---------|-------|\n| `appearance` | \u2713 (fill, outline) | \u2717 | \u2717 | \u2713 (fill, outline) |\n| `subscriptSizing` | \u2713 | \u2717 | \u2717 | \u2717 |\n| `floatLabel` | \u2713 (auto, always, never) | \u2717 | \u2717 | \u2717 |\n| `hideRequiredMarker` | \u2713 | \u2717 | \u2717 | \u2717 |\n| `hint` | \u2713 | \u2713 (as `helpText`) | \u2713 | \u2713 |\n\n### Container Fields\n\nContainer fields (`page`, `group`, `row`) do NOT support these properties:\n- `label` - Use `text` field type for headings instead\n- `required`, `email`, `min`, `max`, etc. - Validation is for leaf fields only\n\n### Common Mistakes to Avoid\n\n1. **Using `expressions` on fields** - This property is NOT supported on standard fields. Use `derivation` for computed values or `logic` for conditional behavior.\n\n2. **Adding `label` to container fields** - Pages, groups, and rows don't have labels:\n ```typescript\n // \u274C WRONG\n { key: 'address', type: 'group', label: 'Address', fields: [...] }\n\n // \u2705 CORRECT - Use a text field for the heading\n { key: 'addressTitle', type: 'text', label: 'Address', props: { elementType: 'h3' } }\n { key: 'address', type: 'group', fields: [...] }\n ```\n\n3. **Adding `id` to FormConfig** - The root FormConfig does not accept an `id` property.\n\n4. **Putting slider min/max in props** - For slider fields, `min`, `max`, and `step` are field-level properties:\n ```typescript\n // \u274C WRONG\n { key: 'volume', type: 'slider', label: 'Volume', props: { min: 0, max: 100 } }\n\n // \u2705 CORRECT\n { key: 'volume', type: 'slider', label: 'Volume', min: 0, max: 100 }\n ```\n\n5. **Using unsupported field types** - Each UI library supports specific field types. Use `ngforge_lookup` with `depth: \"schema\"` to see available types.\n\n## Validation Before Use\n\nAlways validate your FormConfig using the `ngforge_validate` tool before using it. This tool uses the actual TypeScript/Zod schemas, so if validation passes, the config will work correctly at runtime.\n\n```typescript\n// Always specify the UI integration you're using\nngforge_validate({\n uiIntegration: 'material', // or 'bootstrap', 'primeng', 'ionic'\n config: { fields: [...] }\n})\n```\n\nTo understand what properties are supported for each field type, use `ngforge_lookup` with `depth: \"schema\"`:\n\n```typescript\nngforge_lookup({ topic: 'input', depth: 'schema', uiIntegration: 'material' })\n```\n";
|
|
3
3
|
//# sourceMappingURL=instructions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../../../../packages/dynamic-form-mcp/src/registry/instructions.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAE9B,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../../../../packages/dynamic-form-mcp/src/registry/instructions.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAE9B,eAAO,MAAM,YAAY,6+lBAwiBxB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../../../../packages/dynamic-form-mcp/src/registry/validators.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAE9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../../../../packages/dynamic-form-mcp/src/registry/validators.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAE9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAgBhD,eAAO,MAAM,UAAU,EAAE,aAAa,EAgQrC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lookup-topics.d.ts","sourceRoot":"","sources":["../../../../../../packages/dynamic-form-mcp/src/tools/data/lookup-topics.ts"],"names":[],"mappings":"AAAA,yBAAyB;AAEzB,4CAA4C;AAC5C,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"lookup-topics.d.ts","sourceRoot":"","sources":["../../../../../../packages/dynamic-form-mcp/src/tools/data/lookup-topics.ts"],"names":[],"mappings":"AAAA,yBAAyB;AAEzB,4CAA4C;AAC5C,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAshFlE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAiDrD,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAuJhD,CAAC"}
|