@ng-forge/dynamic-form-mcp 0.9.0-next.1 → 0.9.0-next.11
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 +29 -8
- package/package.json +6 -6
- package/src/index.js +29 -8
- package/src/registry/documentation.d.ts.map +1 -1
- package/src/registry/instructions.d.ts +1 -1
- package/src/registry/instructions.d.ts.map +1 -1
- package/src/registry/wrappers.d.ts +1 -1
- package/src/registry/wrappers.d.ts.map +1 -1
package/bin/ng-forge-mcp.js
CHANGED
|
@@ -780,7 +780,7 @@ var VALIDATORS = [
|
|
|
780
780
|
{
|
|
781
781
|
type: "custom",
|
|
782
782
|
category: "custom",
|
|
783
|
-
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.",
|
|
783
|
+
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`.",
|
|
784
784
|
parameters: {
|
|
785
785
|
functionName: {
|
|
786
786
|
name: "functionName",
|
|
@@ -832,7 +832,7 @@ var VALIDATORS = [
|
|
|
832
832
|
{
|
|
833
833
|
type: "async",
|
|
834
834
|
category: "async",
|
|
835
|
-
description: "Async validator using registered async function (resource-based). Register the function via customFnConfig.asyncValidators.",
|
|
835
|
+
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`.",
|
|
836
836
|
parameters: {
|
|
837
837
|
functionName: {
|
|
838
838
|
name: "functionName",
|
|
@@ -855,7 +855,7 @@ var VALIDATORS = [
|
|
|
855
855
|
{
|
|
856
856
|
type: "http",
|
|
857
857
|
category: "http",
|
|
858
|
-
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).",
|
|
858
|
+
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.",
|
|
859
859
|
parameters: {
|
|
860
860
|
functionName: {
|
|
861
861
|
name: "functionName",
|
|
@@ -1309,6 +1309,12 @@ var DOCUMENTATION = [
|
|
|
1309
1309
|
url: `${BASE_URL}/validation/custom-validators`
|
|
1310
1310
|
},
|
|
1311
1311
|
{ id: "validation-reference", title: "Validation Reference", category: "validation", url: `${BASE_URL}/validation/reference` },
|
|
1312
|
+
{
|
|
1313
|
+
id: "validation-hidden-fields",
|
|
1314
|
+
title: "Validation for Hidden Fields",
|
|
1315
|
+
category: "validation",
|
|
1316
|
+
url: `${BASE_URL}/validation/hidden-fields`
|
|
1317
|
+
},
|
|
1312
1318
|
// Dynamic Behavior
|
|
1313
1319
|
{ id: "conditional-logic", title: "Conditional Logic", category: "dynamic-behavior", url: `${BASE_URL}/dynamic-behavior/overview` },
|
|
1314
1320
|
{ id: "value-derivation", title: "Value Derivation", category: "dynamic-behavior", url: `${BASE_URL}/dynamic-behavior/derivation` },
|
|
@@ -1396,7 +1402,7 @@ var DOCUMENTATION = [
|
|
|
1396
1402
|
// packages/dynamic-form-mcp/src/registry/wrappers.ts
|
|
1397
1403
|
var WRAPPER_AUTHORING_CONTRACT = `All wrapper components MUST:
|
|
1398
1404
|
|
|
1399
|
-
1. Implement \`
|
|
1405
|
+
1. Implement \`FieldWrapper\` from \`@ng-forge/dynamic-forms\`.
|
|
1400
1406
|
2. Expose \`readonly fieldComponent = viewChild.required('fieldComponent', { read: ViewContainerRef });\`
|
|
1401
1407
|
\u2014 this is the slot where the inner wrapper (or the field) is rendered
|
|
1402
1408
|
imperatively by the outlet.
|
|
@@ -2136,7 +2142,9 @@ const config: FormConfig = {
|
|
|
2136
2142
|
|
|
2137
2143
|
Every field MUST have:
|
|
2138
2144
|
|
|
2139
|
-
- \`key\`:
|
|
2145
|
+
- \`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.
|
|
2146
|
+
- **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.
|
|
2147
|
+
- **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.
|
|
2140
2148
|
- \`type\`: One of the registered field types
|
|
2141
2149
|
- \`label\`: Human-readable label (for accessibility)
|
|
2142
2150
|
|
|
@@ -2403,6 +2411,8 @@ Invokes a registered custom function by name:
|
|
|
2403
2411
|
|
|
2404
2412
|
Register functions via \`customFnConfig.customFunctions\`.
|
|
2405
2413
|
|
|
2414
|
+
> **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\`).
|
|
2415
|
+
|
|
2406
2416
|
#### Async Custom Condition
|
|
2407
2417
|
|
|
2408
2418
|
Invokes a registered async function that returns \`Promise<boolean>\` or \`Observable<boolean>\`:
|
|
@@ -2510,7 +2520,7 @@ Available adapters:
|
|
|
2510
2520
|
|
|
2511
2521
|
1. **Missing labels** - Always include labels for accessibility
|
|
2512
2522
|
2. **Missing validation messages** - Users need clear feedback
|
|
2513
|
-
3. **Duplicate keys** -
|
|
2523
|
+
3. **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.
|
|
2514
2524
|
4. **Select without options** - Select/radio fields must have options
|
|
2515
2525
|
5. **Array without fields** - Array fields need a fields template
|
|
2516
2526
|
6. **Group without fields** - Group fields need child fields
|
|
@@ -3976,7 +3986,12 @@ var BaseFieldDefSchema = z14.object({
|
|
|
3976
3986
|
* Whether to exclude this field's value from submission output when readonly.
|
|
3977
3987
|
* Overrides both the global and form-level settings.
|
|
3978
3988
|
*/
|
|
3979
|
-
excludeValueIfReadonly: z14.boolean().optional()
|
|
3989
|
+
excludeValueIfReadonly: z14.boolean().optional(),
|
|
3990
|
+
/**
|
|
3991
|
+
* Whether to run validation when this field is hidden.
|
|
3992
|
+
* Inherited from the parent field (and form/global defaults). Defaults to false.
|
|
3993
|
+
*/
|
|
3994
|
+
validateWhenHidden: z14.boolean().optional()
|
|
3980
3995
|
});
|
|
3981
3996
|
|
|
3982
3997
|
// internal/dynamic-forms-zod/src/lib/schemas/field/field-with-validation.schema.ts
|
|
@@ -4950,7 +4965,13 @@ var FormOptionsSchema = z38.object({
|
|
|
4950
4965
|
* Whether to exclude values of readonly fields from submission output.
|
|
4951
4966
|
* Overrides the global withValueExclusionDefaults() setting for this form.
|
|
4952
4967
|
*/
|
|
4953
|
-
excludeValueIfReadonly: z38.boolean().optional()
|
|
4968
|
+
excludeValueIfReadonly: z38.boolean().optional(),
|
|
4969
|
+
/**
|
|
4970
|
+
* Whether to run validation while a field is hidden.
|
|
4971
|
+
* Acts as the root inherited value for the form. Per-field
|
|
4972
|
+
* validateWhenHidden overrides cascade through the field tree.
|
|
4973
|
+
*/
|
|
4974
|
+
validateWhenHidden: z38.boolean().optional()
|
|
4954
4975
|
});
|
|
4955
4976
|
|
|
4956
4977
|
// internal/dynamic-forms-zod/src/lib/schemas/form/schema-definition.schema.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-forge/dynamic-form-mcp",
|
|
3
|
-
"version": "0.9.0-next.
|
|
3
|
+
"version": "0.9.0-next.11",
|
|
4
4
|
"description": "MCP server for ng-forge dynamic forms - AI-assisted form schema generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,11 +51,11 @@
|
|
|
51
51
|
"zod": "^3.25.76"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@ng-forge/dynamic-forms": "^0.9.0-next.
|
|
55
|
-
"@ng-forge/dynamic-forms-bootstrap": "^0.9.0-next.
|
|
56
|
-
"@ng-forge/dynamic-forms-ionic": "^0.9.0-next.
|
|
57
|
-
"@ng-forge/dynamic-forms-material": "^0.9.0-next.
|
|
58
|
-
"@ng-forge/dynamic-forms-primeng": "^0.9.0-next.
|
|
54
|
+
"@ng-forge/dynamic-forms": "^0.9.0-next.11",
|
|
55
|
+
"@ng-forge/dynamic-forms-bootstrap": "^0.9.0-next.11",
|
|
56
|
+
"@ng-forge/dynamic-forms-ionic": "^0.9.0-next.11",
|
|
57
|
+
"@ng-forge/dynamic-forms-material": "^0.9.0-next.11",
|
|
58
|
+
"@ng-forge/dynamic-forms-primeng": "^0.9.0-next.11",
|
|
59
59
|
"tsx": "^4.19.0"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/src/index.js
CHANGED
|
@@ -778,7 +778,7 @@ var VALIDATORS = [
|
|
|
778
778
|
{
|
|
779
779
|
type: "custom",
|
|
780
780
|
category: "custom",
|
|
781
|
-
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.",
|
|
781
|
+
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`.",
|
|
782
782
|
parameters: {
|
|
783
783
|
functionName: {
|
|
784
784
|
name: "functionName",
|
|
@@ -830,7 +830,7 @@ var VALIDATORS = [
|
|
|
830
830
|
{
|
|
831
831
|
type: "async",
|
|
832
832
|
category: "async",
|
|
833
|
-
description: "Async validator using registered async function (resource-based). Register the function via customFnConfig.asyncValidators.",
|
|
833
|
+
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`.",
|
|
834
834
|
parameters: {
|
|
835
835
|
functionName: {
|
|
836
836
|
name: "functionName",
|
|
@@ -853,7 +853,7 @@ var VALIDATORS = [
|
|
|
853
853
|
{
|
|
854
854
|
type: "http",
|
|
855
855
|
category: "http",
|
|
856
|
-
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).",
|
|
856
|
+
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.",
|
|
857
857
|
parameters: {
|
|
858
858
|
functionName: {
|
|
859
859
|
name: "functionName",
|
|
@@ -1307,6 +1307,12 @@ var DOCUMENTATION = [
|
|
|
1307
1307
|
url: `${BASE_URL}/validation/custom-validators`
|
|
1308
1308
|
},
|
|
1309
1309
|
{ id: "validation-reference", title: "Validation Reference", category: "validation", url: `${BASE_URL}/validation/reference` },
|
|
1310
|
+
{
|
|
1311
|
+
id: "validation-hidden-fields",
|
|
1312
|
+
title: "Validation for Hidden Fields",
|
|
1313
|
+
category: "validation",
|
|
1314
|
+
url: `${BASE_URL}/validation/hidden-fields`
|
|
1315
|
+
},
|
|
1310
1316
|
// Dynamic Behavior
|
|
1311
1317
|
{ id: "conditional-logic", title: "Conditional Logic", category: "dynamic-behavior", url: `${BASE_URL}/dynamic-behavior/overview` },
|
|
1312
1318
|
{ id: "value-derivation", title: "Value Derivation", category: "dynamic-behavior", url: `${BASE_URL}/dynamic-behavior/derivation` },
|
|
@@ -1394,7 +1400,7 @@ var DOCUMENTATION = [
|
|
|
1394
1400
|
// packages/dynamic-form-mcp/src/registry/wrappers.ts
|
|
1395
1401
|
var WRAPPER_AUTHORING_CONTRACT = `All wrapper components MUST:
|
|
1396
1402
|
|
|
1397
|
-
1. Implement \`
|
|
1403
|
+
1. Implement \`FieldWrapper\` from \`@ng-forge/dynamic-forms\`.
|
|
1398
1404
|
2. Expose \`readonly fieldComponent = viewChild.required('fieldComponent', { read: ViewContainerRef });\`
|
|
1399
1405
|
\u2014 this is the slot where the inner wrapper (or the field) is rendered
|
|
1400
1406
|
imperatively by the outlet.
|
|
@@ -2138,7 +2144,9 @@ const config: FormConfig = {
|
|
|
2138
2144
|
|
|
2139
2145
|
Every field MUST have:
|
|
2140
2146
|
|
|
2141
|
-
- \`key\`:
|
|
2147
|
+
- \`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.
|
|
2148
|
+
- **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.
|
|
2149
|
+
- **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.
|
|
2142
2150
|
- \`type\`: One of the registered field types
|
|
2143
2151
|
- \`label\`: Human-readable label (for accessibility)
|
|
2144
2152
|
|
|
@@ -2405,6 +2413,8 @@ Invokes a registered custom function by name:
|
|
|
2405
2413
|
|
|
2406
2414
|
Register functions via \`customFnConfig.customFunctions\`.
|
|
2407
2415
|
|
|
2416
|
+
> **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\`).
|
|
2417
|
+
|
|
2408
2418
|
#### Async Custom Condition
|
|
2409
2419
|
|
|
2410
2420
|
Invokes a registered async function that returns \`Promise<boolean>\` or \`Observable<boolean>\`:
|
|
@@ -2512,7 +2522,7 @@ Available adapters:
|
|
|
2512
2522
|
|
|
2513
2523
|
1. **Missing labels** - Always include labels for accessibility
|
|
2514
2524
|
2. **Missing validation messages** - Users need clear feedback
|
|
2515
|
-
3. **Duplicate keys** -
|
|
2525
|
+
3. **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.
|
|
2516
2526
|
4. **Select without options** - Select/radio fields must have options
|
|
2517
2527
|
5. **Array without fields** - Array fields need a fields template
|
|
2518
2528
|
6. **Group without fields** - Group fields need child fields
|
|
@@ -3981,7 +3991,12 @@ var BaseFieldDefSchema = z14.object({
|
|
|
3981
3991
|
* Whether to exclude this field's value from submission output when readonly.
|
|
3982
3992
|
* Overrides both the global and form-level settings.
|
|
3983
3993
|
*/
|
|
3984
|
-
excludeValueIfReadonly: z14.boolean().optional()
|
|
3994
|
+
excludeValueIfReadonly: z14.boolean().optional(),
|
|
3995
|
+
/**
|
|
3996
|
+
* Whether to run validation when this field is hidden.
|
|
3997
|
+
* Inherited from the parent field (and form/global defaults). Defaults to false.
|
|
3998
|
+
*/
|
|
3999
|
+
validateWhenHidden: z14.boolean().optional()
|
|
3985
4000
|
});
|
|
3986
4001
|
|
|
3987
4002
|
// internal/dynamic-forms-zod/src/lib/schemas/field/field-with-validation.schema.ts
|
|
@@ -4955,7 +4970,13 @@ var FormOptionsSchema = z38.object({
|
|
|
4955
4970
|
* Whether to exclude values of readonly fields from submission output.
|
|
4956
4971
|
* Overrides the global withValueExclusionDefaults() setting for this form.
|
|
4957
4972
|
*/
|
|
4958
|
-
excludeValueIfReadonly: z38.boolean().optional()
|
|
4973
|
+
excludeValueIfReadonly: z38.boolean().optional(),
|
|
4974
|
+
/**
|
|
4975
|
+
* Whether to run validation while a field is hidden.
|
|
4976
|
+
* Acts as the root inherited value for the form. Per-field
|
|
4977
|
+
* validateWhenHidden overrides cascade through the field tree.
|
|
4978
|
+
*/
|
|
4979
|
+
validateWhenHidden: z38.boolean().optional()
|
|
4959
4980
|
});
|
|
4960
4981
|
|
|
4961
4982
|
// internal/dynamic-forms-zod/src/lib/schemas/form/schema-definition.schema.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentation.d.ts","sourceRoot":"","sources":["../../../../../packages/dynamic-form-mcp/src/registry/documentation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,aAAa,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"documentation.d.ts","sourceRoot":"","sources":["../../../../../packages/dynamic-form-mcp/src/registry/documentation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,aAAa,EAAE,OAAO,EAgIlC,CAAC"}
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* This content is served via the ng-forge://instructions resource
|
|
5
5
|
* to guide AI assistants in generating FormConfig objects.
|
|
6
6
|
*/
|
|
7
|
-
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`: Unique identifier (except for `row` type which is layout-only)\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 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`, 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\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\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#### 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#### 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## 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** - Each key must be unique within its scope\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_form_config` 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_get_field_schema` to see available types.\n\n## Validation Before Use\n\nAlways validate your FormConfig using the `ngforge_validate_form_config` 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_form_config({\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_get_field_schema`:\n\n```typescript\nngforge_get_field_schema({ uiIntegration: 'material' })\n```\n";
|
|
7
|
+
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 - **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 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`, 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\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\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#### 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## 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_form_config` 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_get_field_schema` to see available types.\n\n## Validation Before Use\n\nAlways validate your FormConfig using the `ngforge_validate_form_config` 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_form_config({\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_get_field_schema`:\n\n```typescript\nngforge_get_field_schema({ uiIntegration: 'material' })\n```\n";
|
|
8
8
|
//# 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;;;;;GAKG;AAEH,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../../../../packages/dynamic-form-mcp/src/registry/instructions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,YAAY,q7dAiexB,CAAC"}
|
|
@@ -21,6 +21,6 @@ import type { WrapperInfo } from './index.js';
|
|
|
21
21
|
* Surfaced by `ngforge_lookup topic="wrappers"` and any wrapper-scoped
|
|
22
22
|
* lookup so consumers can't miss them.
|
|
23
23
|
*/
|
|
24
|
-
export declare const WRAPPER_AUTHORING_CONTRACT = "All wrapper components MUST:\n\n1. Implement `
|
|
24
|
+
export declare const WRAPPER_AUTHORING_CONTRACT = "All wrapper components MUST:\n\n1. Implement `FieldWrapper` from `@ng-forge/dynamic-forms`.\n2. Expose `readonly fieldComponent = viewChild.required('fieldComponent', { read: ViewContainerRef });`\n \u2014 this is the slot where the inner wrapper (or the field) is rendered\n imperatively by the outlet.\n3. Place the `#fieldComponent` template ref UNCONDITIONALLY. It cannot\n live inside `@if`, `@defer`, `@for`, `@switch`, `<ng-template>`,\n or any structural directive that delays view creation. The outlet reads\n the `ViewContainerRef` immediately after creation \u2014 if the slot isn't\n materialised yet, `viewChild.required` throws `NG0951`\n (REQUIRED_QUERY_NO_VALUE), the wrapper chain is torn down, and the field\n renders bare.\n4. Declare every config key the wrapper consumes as an Angular `input()`.\n Unknown keys are silently dropped \u2014 the outlet probes\n `reflectComponentType(...).inputs` before calling `setInput()`.\n Runtime typos in config will NOT throw; rely on TypeScript (via\n `FieldRegistryWrappers` module augmentation) to catch them.\n5. Optionally accept `fieldInputs: input<WrapperFieldInputs>()` to read\n the wrapped field's mapper outputs and read-only form state.";
|
|
25
25
|
export declare const WRAPPERS: WrapperInfo[];
|
|
26
26
|
//# sourceMappingURL=wrappers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrappers.d.ts","sourceRoot":"","sources":["../../../../../packages/dynamic-form-mcp/src/registry/wrappers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,
|
|
1
|
+
{"version":3,"file":"wrappers.d.ts","sourceRoot":"","sources":["../../../../../packages/dynamic-form-mcp/src/registry/wrappers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,stCAmByB,CAAC;AAEjE,eAAO,MAAM,QAAQ,EAAE,WAAW,EAwIjC,CAAC"}
|