/** ng-forge Best Practices */
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 `
` 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` or `Observable`:\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";
//# sourceMappingURL=instructions.d.ts.map