@ng-forge/dynamic-forms 0.1.1 → 0.1.3

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.
Files changed (20) hide show
  1. package/README.md +33 -0
  2. package/fesm2022/ng-forge-dynamic-forms-integration.mjs +269 -0
  3. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -0
  4. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-Dcd04rfS.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-B_676NHl.mjs} +989 -1211
  5. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-B_676NHl.mjs.map +1 -0
  6. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-DjrahUgb.mjs → ng-forge-dynamic-forms-page-field.component-YHFQcpCS.mjs} +31 -29
  7. package/fesm2022/ng-forge-dynamic-forms-page-field.component-YHFQcpCS.mjs.map +1 -0
  8. package/fesm2022/ng-forge-dynamic-forms-testing-test-button.harness-BenmCFcM.mjs +31 -0
  9. package/fesm2022/ng-forge-dynamic-forms-testing-test-button.harness-BenmCFcM.mjs.map +1 -0
  10. package/fesm2022/ng-forge-dynamic-forms-testing.mjs +968 -0
  11. package/fesm2022/ng-forge-dynamic-forms-testing.mjs.map +1 -0
  12. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-7QVNm95W.mjs → ng-forge-dynamic-forms-text-field.component-CiYq73aF.mjs} +2 -2
  13. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-7QVNm95W.mjs.map → ng-forge-dynamic-forms-text-field.component-CiYq73aF.mjs.map} +1 -1
  14. package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
  15. package/package.json +17 -3
  16. package/types/ng-forge-dynamic-forms-integration.d.ts +315 -0
  17. package/types/ng-forge-dynamic-forms-testing.d.ts +565 -0
  18. package/types/ng-forge-dynamic-forms.d.ts +353 -549
  19. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-Dcd04rfS.mjs.map +0 -1
  20. package/fesm2022/ng-forge-dynamic-forms-page-field.component-DjrahUgb.mjs.map +0 -1
package/README.md CHANGED
@@ -7,8 +7,20 @@
7
7
  Core library for building type-safe, dynamic Angular forms with signal forms integration.
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/@ng-forge/dynamic-forms.svg)](https://www.npmjs.com/package/@ng-forge/dynamic-forms)
10
+ [![npm downloads](https://img.shields.io/npm/dm/@ng-forge/dynamic-forms.svg)](https://www.npmjs.com/package/@ng-forge/dynamic-forms)
11
+ [![Angular](https://img.shields.io/badge/Angular-21+-DD0031.svg)](https://angular.dev)
10
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
11
13
 
14
+ > **Experimental API Notice:** This library uses Angular's experimental Signal Forms API.
15
+ > Angular may introduce breaking changes in patch releases. Check the [compatibility matrix](#compatibility) below.
16
+
17
+ ## Compatibility
18
+
19
+ | @ng-forge/dynamic-forms | Angular | Notes |
20
+ | ----------------------- | ------------- | ---------------------------------- |
21
+ | 0.1.1+ | >=21.0.2 | Signal forms API signature changed |
22
+ | 0.1.0 | 21.0.0-21.0.1 | Initial release |
23
+
12
24
  ## Installation
13
25
 
14
26
  ```bash
@@ -61,6 +73,19 @@ export class UserFormComponent {
61
73
  - **i18n Ready** - Observable/Signal support for labels and messages
62
74
  - **Event System** - Custom events for buttons and form actions
63
75
 
76
+ ## UI Integrations
77
+
78
+ This core library requires a UI integration. Choose one:
79
+
80
+ | Package | UI Library |
81
+ | ---------------------------------------------------------------------------------------------------- | ---------------- |
82
+ | [@ng-forge/dynamic-forms-material](https://www.npmjs.com/package/@ng-forge/dynamic-forms-material) | Angular Material |
83
+ | [@ng-forge/dynamic-forms-bootstrap](https://www.npmjs.com/package/@ng-forge/dynamic-forms-bootstrap) | Bootstrap 5 |
84
+ | [@ng-forge/dynamic-forms-primeng](https://www.npmjs.com/package/@ng-forge/dynamic-forms-primeng) | PrimeNG |
85
+ | [@ng-forge/dynamic-forms-ionic](https://www.npmjs.com/package/@ng-forge/dynamic-forms-ionic) | Ionic |
86
+
87
+ Or [create your own](https://ng-forge.com/dynamic-forms/deep-dive/custom-integrations).
88
+
64
89
  ## Documentation
65
90
 
66
91
  - [Installation](https://ng-forge.com/dynamic-forms/installation)
@@ -72,6 +97,14 @@ export class UserFormComponent {
72
97
  - [i18n](https://ng-forge.com/dynamic-forms/core/i18n)
73
98
  - [Custom Integrations](https://ng-forge.com/dynamic-forms/deep-dive/custom-integrations)
74
99
 
100
+ ## Changelog
101
+
102
+ See [GitHub Releases](https://github.com/ng-forge/ng-forge/releases).
103
+
104
+ ## Contributing
105
+
106
+ Contributions are welcome! Please read our [Contributing Guide](https://github.com/ng-forge/ng-forge/blob/main/CONTRIBUTING.md).
107
+
75
108
  ## License
76
109
 
77
110
  MIT © ng-forge
@@ -0,0 +1,269 @@
1
+ import { inject, computed, signal, Injector } from '@angular/core';
2
+ import { FIELD_SIGNAL_CONTEXT, omit, buildBaseInputs, DYNAMIC_FORM_LOGGER, dynamicTextToObservable, interpolateParams } from '@ng-forge/dynamic-forms';
3
+ import { toObservable, toSignal } from '@angular/core/rxjs-interop';
4
+ import { combineLatest, of } from 'rxjs';
5
+ import { switchMap, map } from 'rxjs/operators';
6
+
7
+ /**
8
+ * Resolves the field tree and default validation messages from the form context.
9
+ * Must be called within an injection context.
10
+ *
11
+ * Uses type-safe field tree utilities to access child FieldTrees from the parent form.
12
+ *
13
+ * @param fieldKey The key of the field to resolve
14
+ * @returns The resolved context with field tree and validation messages
15
+ */
16
+ function resolveValueFieldContext(fieldKey) {
17
+ const context = inject(FIELD_SIGNAL_CONTEXT);
18
+ const defaultValidationMessages = context.defaultValidationMessages;
19
+ const formRoot = context.form;
20
+ const fieldTree = formRoot[fieldKey];
21
+ return { fieldTree, defaultValidationMessages };
22
+ }
23
+ /**
24
+ * Builds the base inputs for a value field.
25
+ * This is a helper function used by valueFieldMapper and specialized mappers.
26
+ *
27
+ * @param fieldDef The value field definition
28
+ * @param ctx The resolved value field context
29
+ * @returns Record of input names to values
30
+ */
31
+ function buildValueFieldInputs(fieldDef, ctx) {
32
+ const omittedFields = omit(fieldDef, ['value']);
33
+ const baseInputs = buildBaseInputs(omittedFields);
34
+ const inputs = {
35
+ ...baseInputs,
36
+ validationMessages: fieldDef.validationMessages ?? {},
37
+ };
38
+ if (fieldDef.placeholder !== undefined) {
39
+ inputs['placeholder'] = fieldDef.placeholder;
40
+ }
41
+ if (ctx.defaultValidationMessages !== undefined) {
42
+ inputs['defaultValidationMessages'] = ctx.defaultValidationMessages;
43
+ }
44
+ if (ctx.fieldTree !== undefined) {
45
+ inputs['field'] = ctx.fieldTree;
46
+ }
47
+ return inputs;
48
+ }
49
+ /**
50
+ * Maps a value field definition to component inputs.
51
+ *
52
+ * Value fields are input fields that contribute to the form's value (text, number, etc.).
53
+ * This mapper injects FIELD_SIGNAL_CONTEXT to access the form structure and retrieve the field proxy.
54
+ *
55
+ * For fields with specific properties (select, datepicker, textarea, slider), use the specialized mappers:
56
+ * - selectFieldMapper: for fields with options (select, radio, multi-checkbox)
57
+ * - datepickerFieldMapper: for fields with minDate, maxDate, startAt
58
+ * - textareaFieldMapper: for fields with rows, cols
59
+ * - sliderFieldMapper: for fields with minValue, maxValue, step
60
+ *
61
+ * @param fieldDef The value field definition
62
+ * @returns Signal containing Record of input names to values for ngComponentOutlet
63
+ */
64
+ function valueFieldMapper(fieldDef) {
65
+ const ctx = resolveValueFieldContext(fieldDef.key);
66
+ return computed(() => buildValueFieldInputs(fieldDef, ctx));
67
+ }
68
+
69
+ /**
70
+ * Maps a checkbox/toggle field definition to component inputs.
71
+ *
72
+ * Checkbox fields are checked fields that contribute to the form's value as boolean.
73
+ * This mapper injects FIELD_SIGNAL_CONTEXT to access the form structure and retrieve the field tree.
74
+ *
75
+ * @param fieldDef The checkbox field definition
76
+ * @returns Signal containing Record of input names to values for ngComponentOutlet
77
+ */
78
+ function checkboxFieldMapper(fieldDef) {
79
+ const context = inject(FIELD_SIGNAL_CONTEXT);
80
+ const omittedFields = omit(fieldDef, ['value']);
81
+ const baseInputs = buildBaseInputs(omittedFields);
82
+ const defaultValidationMessages = context.defaultValidationMessages;
83
+ const formRoot = context.form;
84
+ const fieldTree = formRoot[fieldDef.key];
85
+ return computed(() => {
86
+ const inputs = {
87
+ ...baseInputs,
88
+ validationMessages: fieldDef.validationMessages ?? {},
89
+ };
90
+ if (fieldDef.placeholder !== undefined) {
91
+ inputs['placeholder'] = fieldDef.placeholder;
92
+ }
93
+ if (defaultValidationMessages !== undefined) {
94
+ inputs['defaultValidationMessages'] = defaultValidationMessages;
95
+ }
96
+ if (fieldTree !== undefined) {
97
+ inputs['field'] = fieldTree;
98
+ }
99
+ return inputs;
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Maps a datepicker field to component inputs.
105
+ *
106
+ * Extends the base value field mapper by adding datepicker-specific properties:
107
+ * - minDate: Minimum selectable date
108
+ * - maxDate: Maximum selectable date
109
+ * - startAt: Initial date to display when opening the picker
110
+ *
111
+ * @param fieldDef The datepicker field definition
112
+ * @returns Signal containing Record of input names to values for ngComponentOutlet
113
+ */
114
+ function datepickerFieldMapper(fieldDef) {
115
+ const ctx = resolveValueFieldContext(fieldDef.key);
116
+ return computed(() => {
117
+ const inputs = buildValueFieldInputs(fieldDef, ctx);
118
+ // Add datepicker-specific properties
119
+ if (fieldDef.minDate !== undefined) {
120
+ inputs['minDate'] = fieldDef.minDate;
121
+ }
122
+ if (fieldDef.maxDate !== undefined) {
123
+ inputs['maxDate'] = fieldDef.maxDate;
124
+ }
125
+ if (fieldDef.startAt !== undefined) {
126
+ inputs['startAt'] = fieldDef.startAt;
127
+ }
128
+ return inputs;
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Maps a field with options (select, radio, multi-checkbox) to component inputs.
134
+ *
135
+ * Extends the base value field mapper by adding the options property.
136
+ *
137
+ * @param fieldDef The field definition with options
138
+ * @returns Signal containing Record of input names to values for ngComponentOutlet
139
+ */
140
+ function optionsFieldMapper(fieldDef) {
141
+ const ctx = resolveValueFieldContext(fieldDef.key);
142
+ return computed(() => {
143
+ // Cast to BaseValueField - safe because all FieldWithOptions types extend BaseValueField
144
+ const inputs = buildValueFieldInputs(fieldDef, ctx);
145
+ // Add options property
146
+ inputs['options'] = fieldDef.options;
147
+ return inputs;
148
+ });
149
+ }
150
+
151
+ // UI-specific field mappers
152
+
153
+ /**
154
+ * Factory function that creates a signal of resolved validation errors
155
+ * Handles async resolution of DynamicText validation messages
156
+ *
157
+ * @param field - Signal containing FieldTree
158
+ * @param validationMessages - Signal containing custom field-level validation messages
159
+ * @param defaultValidationMessages - Signal containing default validation messages (fallback)
160
+ * @param injector - Optional injector for DynamicText resolution
161
+ * @returns Signal<ResolvedError[]> - Reactively resolved error messages
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * readonly resolvedErrors = createResolvedErrorsSignal(
166
+ * this.field,
167
+ * this.validationMessages,
168
+ * this.defaultValidationMessages
169
+ * );
170
+ * ```
171
+ */
172
+ function createResolvedErrorsSignal(field, validationMessages, defaultValidationMessages = signal(undefined), injector = inject(Injector)) {
173
+ // Get logger from injector
174
+ const logger = injector.get(DYNAMIC_FORM_LOGGER);
175
+ // Ensure validationMessages is never undefined (mappers pass {} if not defined)
176
+ const messages = computed(() => validationMessages() ?? {}, ...(ngDevMode ? [{ debugName: "messages" }] : []));
177
+ const defaultMessages = computed(() => defaultValidationMessages() ?? {}, ...(ngDevMode ? [{ debugName: "defaultMessages" }] : []));
178
+ // Create a computed signal that reads the actual errors from the field
179
+ // This ensures the signal tracks changes to field().errors(), not just the field reference
180
+ const errors = computed(() => {
181
+ const control = field()();
182
+ return control.errors();
183
+ }, ...(ngDevMode ? [{ debugName: "errors" }] : []));
184
+ // Convert signals to observables using toObservable with injector
185
+ const errors$ = toObservable(errors, { injector });
186
+ const messages$ = toObservable(messages, { injector });
187
+ const defaultMessages$ = toObservable(defaultMessages, { injector });
188
+ // Combine observables and process errors
189
+ const resolvedErrors$ = combineLatest([errors$, messages$, defaultMessages$]).pipe(switchMap(([currentErrors, msgs, defaultMsgs]) => {
190
+ // No errors - return empty array
191
+ if (!currentErrors || currentErrors.length === 0) {
192
+ return of([]);
193
+ }
194
+ // Create observable for each error's resolved message
195
+ const errorResolvers = currentErrors.map((error) => resolveErrorMessage(error, msgs, defaultMsgs, injector, logger));
196
+ // Combine all error message observables into single array emission, filtering out nulls
197
+ return errorResolvers.length > 0
198
+ ? combineLatest(errorResolvers).pipe(map((errors) => errors.filter((e) => e !== null)))
199
+ : of([]);
200
+ }));
201
+ // Convert observable back to signal using toSignal with injector
202
+ // toSignal properly handles the injection context and manages subscriptions
203
+ return toSignal(resolvedErrors$, { initialValue: [], injector });
204
+ }
205
+ /**
206
+ * Resolves a single error message from DynamicText sources with fallback logic
207
+ * Priority: field-level message → default message → no message (logs warning)
208
+ * @internal
209
+ */
210
+ function resolveErrorMessage(error, fieldMessages, defaultMessages, injector, logger) {
211
+ // Check for field-level custom message first
212
+ const fieldMessage = fieldMessages[error.kind];
213
+ // Fall back to default message if no field-level message exists
214
+ const defaultMessage = defaultMessages[error.kind];
215
+ // Determine which message to use: field-level → default
216
+ const messageToUse = fieldMessage ?? defaultMessage;
217
+ // If no message found, log warning and return null (will be filtered out)
218
+ if (!messageToUse) {
219
+ logger.warn(`No validation message found for error kind "${error.kind}". ` +
220
+ `Please provide a message via field-level validationMessages or form-level defaultValidationMessages.`);
221
+ return of(null);
222
+ }
223
+ // Convert DynamicText to Observable
224
+ const messageObservable = dynamicTextToObservable(messageToUse, injector);
225
+ // Apply parameter interpolation to support {{param}} syntax
226
+ return messageObservable.pipe(map((msg) => ({
227
+ kind: error.kind,
228
+ message: interpolateParams(msg, error),
229
+ })));
230
+ }
231
+
232
+ /**
233
+ * Computed signal that determines if errors should be displayed
234
+ * Based on field's invalid, touched, and error count
235
+ *
236
+ * @param field - Signal containing FieldTree
237
+ * @returns Signal<boolean> - True if errors should be displayed
238
+ */
239
+ function shouldShowErrors(field) {
240
+ return computed(() => {
241
+ const fieldTree = field();
242
+ const control = fieldTree();
243
+ return control.invalid() && control.touched() && control.errors().length > 0;
244
+ });
245
+ }
246
+
247
+ // Error display utilities for field components
248
+
249
+ /**
250
+ * @ng-forge/dynamic-forms/integration
251
+ *
252
+ * Integration API for UI library authors building field implementations
253
+ * (Material, Bootstrap, PrimeNG, Ionic, etc.)
254
+ *
255
+ * This entrypoint provides:
256
+ * - Specific field type definitions (InputField, SelectField, etc.)
257
+ * - Field mappers for creating FieldTypeDefinition
258
+ * - Error display utilities for field components
259
+ */
260
+ // =============================================================================
261
+ // Field Mappers
262
+ // =============================================================================
263
+
264
+ /**
265
+ * Generated bundle index. Do not edit.
266
+ */
267
+
268
+ export { buildValueFieldInputs, checkboxFieldMapper, createResolvedErrorsSignal, datepickerFieldMapper, optionsFieldMapper, resolveValueFieldContext, shouldShowErrors, valueFieldMapper };
269
+ //# sourceMappingURL=ng-forge-dynamic-forms-integration.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ng-forge-dynamic-forms-integration.mjs","sources":["../../../../packages/dynamic-forms/integration/src/mappers/value/value-field.mapper.ts","../../../../packages/dynamic-forms/integration/src/mappers/checkbox/checkbox-field-mapper.ts","../../../../packages/dynamic-forms/integration/src/mappers/datepicker/datepicker-field-mapper.ts","../../../../packages/dynamic-forms/integration/src/mappers/select/options-field-mapper.ts","../../../../packages/dynamic-forms/integration/src/mappers/index.ts","../../../../packages/dynamic-forms/integration/src/utils/create-resolved-errors-signal.ts","../../../../packages/dynamic-forms/integration/src/utils/should-show-errors.ts","../../../../packages/dynamic-forms/integration/src/utils/index.ts","../../../../packages/dynamic-forms/integration/src/public_api.ts","../../../../packages/dynamic-forms/integration/src/ng-forge-dynamic-forms-integration.ts"],"sourcesContent":["import { BaseValueField } from '@ng-forge/dynamic-forms';\nimport { computed, inject, Signal } from '@angular/core';\nimport { FieldTree } from '@angular/forms/signals';\nimport { buildBaseInputs } from '@ng-forge/dynamic-forms';\nimport { FIELD_SIGNAL_CONTEXT } from '@ng-forge/dynamic-forms';\nimport { omit } from '@ng-forge/dynamic-forms';\nimport { ValidationMessages } from '@ng-forge/dynamic-forms';\n\n/**\n * Context for value field mapping, containing resolved field tree and default validation messages.\n * Used by specialized mappers to avoid duplicate context resolution.\n */\nexport interface ValueFieldContext {\n fieldTree: FieldTree<unknown> | undefined;\n defaultValidationMessages: ValidationMessages | undefined;\n}\n\n/**\n * Resolves the field tree and default validation messages from the form context.\n * Must be called within an injection context.\n *\n * Uses type-safe field tree utilities to access child FieldTrees from the parent form.\n *\n * @param fieldKey The key of the field to resolve\n * @returns The resolved context with field tree and validation messages\n */\nexport function resolveValueFieldContext(fieldKey: string): ValueFieldContext {\n const context = inject(FIELD_SIGNAL_CONTEXT);\n const defaultValidationMessages = context.defaultValidationMessages;\n const formRoot = context.form as Record<string, FieldTree<unknown> | undefined>;\n const fieldTree = formRoot[fieldKey];\n return { fieldTree, defaultValidationMessages };\n}\n\n/**\n * Builds the base inputs for a value field.\n * This is a helper function used by valueFieldMapper and specialized mappers.\n *\n * @param fieldDef The value field definition\n * @param ctx The resolved value field context\n * @returns Record of input names to values\n */\nexport function buildValueFieldInputs<TProps, TValue = unknown>(\n fieldDef: BaseValueField<TProps, TValue>,\n ctx: ValueFieldContext,\n): Record<string, unknown> {\n const omittedFields = omit(fieldDef, ['value']);\n const baseInputs = buildBaseInputs(omittedFields);\n\n const inputs: Record<string, unknown> = {\n ...baseInputs,\n validationMessages: fieldDef.validationMessages ?? {},\n };\n\n if (fieldDef.placeholder !== undefined) {\n inputs['placeholder'] = fieldDef.placeholder;\n }\n\n if (ctx.defaultValidationMessages !== undefined) {\n inputs['defaultValidationMessages'] = ctx.defaultValidationMessages;\n }\n\n if (ctx.fieldTree !== undefined) {\n inputs['field'] = ctx.fieldTree;\n }\n\n return inputs;\n}\n\n/**\n * Maps a value field definition to component inputs.\n *\n * Value fields are input fields that contribute to the form's value (text, number, etc.).\n * This mapper injects FIELD_SIGNAL_CONTEXT to access the form structure and retrieve the field proxy.\n *\n * For fields with specific properties (select, datepicker, textarea, slider), use the specialized mappers:\n * - selectFieldMapper: for fields with options (select, radio, multi-checkbox)\n * - datepickerFieldMapper: for fields with minDate, maxDate, startAt\n * - textareaFieldMapper: for fields with rows, cols\n * - sliderFieldMapper: for fields with minValue, maxValue, step\n *\n * @param fieldDef The value field definition\n * @returns Signal containing Record of input names to values for ngComponentOutlet\n */\nexport function valueFieldMapper<TProps = unknown, TValue = unknown>(\n fieldDef: BaseValueField<TProps, TValue>,\n): Signal<Record<string, unknown>> {\n const ctx = resolveValueFieldContext(fieldDef.key);\n\n return computed(() => buildValueFieldInputs(fieldDef, ctx));\n}\n","import { BaseCheckedField } from '@ng-forge/dynamic-forms';\nimport { FieldDef } from '@ng-forge/dynamic-forms';\nimport { computed, inject, Signal } from '@angular/core';\nimport { FieldTree } from '@angular/forms/signals';\nimport { buildBaseInputs } from '@ng-forge/dynamic-forms';\nimport { FIELD_SIGNAL_CONTEXT } from '@ng-forge/dynamic-forms';\nimport { omit } from '@ng-forge/dynamic-forms';\n\n/**\n * Maps a checkbox/toggle field definition to component inputs.\n *\n * Checkbox fields are checked fields that contribute to the form's value as boolean.\n * This mapper injects FIELD_SIGNAL_CONTEXT to access the form structure and retrieve the field tree.\n *\n * @param fieldDef The checkbox field definition\n * @returns Signal containing Record of input names to values for ngComponentOutlet\n */\nexport function checkboxFieldMapper(fieldDef: BaseCheckedField<unknown>): Signal<Record<string, unknown>> {\n const context = inject(FIELD_SIGNAL_CONTEXT);\n const omittedFields = omit(fieldDef, ['value']) as FieldDef<unknown>;\n const baseInputs = buildBaseInputs(omittedFields);\n const defaultValidationMessages = context.defaultValidationMessages;\n const formRoot = context.form as Record<string, FieldTree<unknown> | undefined>;\n const fieldTree = formRoot[fieldDef.key];\n\n return computed(() => {\n const inputs: Record<string, unknown> = {\n ...baseInputs,\n validationMessages: fieldDef.validationMessages ?? {},\n };\n\n if (fieldDef.placeholder !== undefined) {\n inputs['placeholder'] = fieldDef.placeholder;\n }\n\n if (defaultValidationMessages !== undefined) {\n inputs['defaultValidationMessages'] = defaultValidationMessages;\n }\n\n if (fieldTree !== undefined) {\n inputs['field'] = fieldTree;\n }\n\n return inputs;\n });\n}\n","import { computed, Signal } from '@angular/core';\nimport { DatepickerField } from '../../definitions';\nimport { resolveValueFieldContext, buildValueFieldInputs } from '../value/value-field.mapper';\n\n/**\n * Maps a datepicker field to component inputs.\n *\n * Extends the base value field mapper by adding datepicker-specific properties:\n * - minDate: Minimum selectable date\n * - maxDate: Maximum selectable date\n * - startAt: Initial date to display when opening the picker\n *\n * @param fieldDef The datepicker field definition\n * @returns Signal containing Record of input names to values for ngComponentOutlet\n */\nexport function datepickerFieldMapper<TProps>(fieldDef: DatepickerField<TProps>): Signal<Record<string, unknown>> {\n const ctx = resolveValueFieldContext(fieldDef.key);\n\n return computed(() => {\n const inputs = buildValueFieldInputs(fieldDef, ctx);\n\n // Add datepicker-specific properties\n if (fieldDef.minDate !== undefined) {\n inputs['minDate'] = fieldDef.minDate;\n }\n if (fieldDef.maxDate !== undefined) {\n inputs['maxDate'] = fieldDef.maxDate;\n }\n if (fieldDef.startAt !== undefined) {\n inputs['startAt'] = fieldDef.startAt;\n }\n\n return inputs;\n });\n}\n","import { computed, Signal } from '@angular/core';\nimport { SelectField } from '../../definitions';\nimport { RadioField } from '../../definitions';\nimport { MultiCheckboxField } from '../../definitions';\nimport { BaseValueField } from '@ng-forge/dynamic-forms';\nimport { resolveValueFieldContext, buildValueFieldInputs } from '../value/value-field.mapper';\n\n/**\n * Field types that have an options property.\n */\nexport type FieldWithOptions<T = unknown, TProps = unknown> =\n | SelectField<T, TProps>\n | RadioField<T, TProps>\n | MultiCheckboxField<T, TProps>;\n\n/**\n * Maps a field with options (select, radio, multi-checkbox) to component inputs.\n *\n * Extends the base value field mapper by adding the options property.\n *\n * @param fieldDef The field definition with options\n * @returns Signal containing Record of input names to values for ngComponentOutlet\n */\nexport function optionsFieldMapper<T, TProps>(fieldDef: FieldWithOptions<T, TProps>): Signal<Record<string, unknown>> {\n const ctx = resolveValueFieldContext(fieldDef.key);\n\n return computed(() => {\n // Cast to BaseValueField - safe because all FieldWithOptions types extend BaseValueField\n const inputs = buildValueFieldInputs(fieldDef as BaseValueField<TProps, unknown>, ctx);\n\n // Add options property\n inputs['options'] = fieldDef.options;\n\n return inputs;\n });\n}\n","// UI-specific field mappers\nexport { valueFieldMapper, buildValueFieldInputs, resolveValueFieldContext } from './value/value-field.mapper';\nexport type { ValueFieldContext } from './value/value-field.mapper';\nexport { checkboxFieldMapper } from './checkbox/checkbox-field-mapper';\nexport { datepickerFieldMapper } from './datepicker/datepicker-field-mapper';\nexport { optionsFieldMapper } from './select/options-field-mapper';\nexport type { FieldWithOptions } from './select/options-field-mapper';\n","import { computed, inject, Injector, signal, Signal } from '@angular/core';\nimport { toObservable, toSignal } from '@angular/core/rxjs-interop';\nimport { FieldTree, ValidationError } from '@angular/forms/signals';\nimport { combineLatest, Observable, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { ValidationMessages } from '@ng-forge/dynamic-forms';\nimport { dynamicTextToObservable } from '@ng-forge/dynamic-forms';\nimport { interpolateParams } from '@ng-forge/dynamic-forms';\nimport { DYNAMIC_FORM_LOGGER, DynamicFormLogger } from '@ng-forge/dynamic-forms';\n\n/**\n * Resolved validation error with interpolated message\n */\nexport interface ResolvedError {\n kind: string;\n message: string;\n}\n\n/**\n * Factory function that creates a signal of resolved validation errors\n * Handles async resolution of DynamicText validation messages\n *\n * @param field - Signal containing FieldTree\n * @param validationMessages - Signal containing custom field-level validation messages\n * @param defaultValidationMessages - Signal containing default validation messages (fallback)\n * @param injector - Optional injector for DynamicText resolution\n * @returns Signal<ResolvedError[]> - Reactively resolved error messages\n *\n * @example\n * ```typescript\n * readonly resolvedErrors = createResolvedErrorsSignal(\n * this.field,\n * this.validationMessages,\n * this.defaultValidationMessages\n * );\n * ```\n */\nexport function createResolvedErrorsSignal<T>(\n field: Signal<FieldTree<T>>,\n validationMessages: Signal<ValidationMessages | undefined>,\n defaultValidationMessages: Signal<ValidationMessages | undefined> = signal(undefined),\n injector = inject(Injector),\n): Signal<ResolvedError[]> {\n // Get logger from injector\n const logger = injector.get(DYNAMIC_FORM_LOGGER);\n\n // Ensure validationMessages is never undefined (mappers pass {} if not defined)\n const messages = computed(() => validationMessages() ?? {});\n const defaultMessages = computed(() => defaultValidationMessages() ?? {});\n\n // Create a computed signal that reads the actual errors from the field\n // This ensures the signal tracks changes to field().errors(), not just the field reference\n const errors = computed(() => {\n const control = field()();\n return control.errors();\n });\n\n // Convert signals to observables using toObservable with injector\n const errors$ = toObservable(errors, { injector });\n const messages$ = toObservable(messages, { injector });\n const defaultMessages$ = toObservable(defaultMessages, { injector });\n\n // Combine observables and process errors\n const resolvedErrors$ = combineLatest([errors$, messages$, defaultMessages$]).pipe(\n switchMap(([currentErrors, msgs, defaultMsgs]) => {\n // No errors - return empty array\n if (!currentErrors || currentErrors.length === 0) {\n return of([]);\n }\n\n // Create observable for each error's resolved message\n const errorResolvers = currentErrors.map((error: ValidationError) => resolveErrorMessage(error, msgs, defaultMsgs, injector, logger));\n\n // Combine all error message observables into single array emission, filtering out nulls\n return errorResolvers.length > 0\n ? combineLatest(errorResolvers).pipe(map((errors) => errors.filter((e): e is ResolvedError => e !== null)))\n : of([]);\n }),\n );\n\n // Convert observable back to signal using toSignal with injector\n // toSignal properly handles the injection context and manages subscriptions\n return toSignal(resolvedErrors$, { initialValue: [] as ResolvedError[], injector });\n}\n\n/**\n * Resolves a single error message from DynamicText sources with fallback logic\n * Priority: field-level message → default message → no message (logs warning)\n * @internal\n */\nfunction resolveErrorMessage(\n error: ValidationError,\n fieldMessages: ValidationMessages,\n defaultMessages: ValidationMessages,\n injector: Injector,\n logger: DynamicFormLogger,\n): Observable<ResolvedError | null> {\n // Check for field-level custom message first\n const fieldMessage = fieldMessages[error.kind];\n\n // Fall back to default message if no field-level message exists\n const defaultMessage = defaultMessages[error.kind];\n\n // Determine which message to use: field-level → default\n const messageToUse = fieldMessage ?? defaultMessage;\n\n // If no message found, log warning and return null (will be filtered out)\n if (!messageToUse) {\n logger.warn(\n `No validation message found for error kind \"${error.kind}\". ` +\n `Please provide a message via field-level validationMessages or form-level defaultValidationMessages.`,\n );\n return of(null);\n }\n\n // Convert DynamicText to Observable\n const messageObservable = dynamicTextToObservable(messageToUse, injector);\n\n // Apply parameter interpolation to support {{param}} syntax\n return messageObservable.pipe(\n map((msg) => ({\n kind: error.kind,\n message: interpolateParams(msg, error),\n })),\n );\n}\n","import { Signal, computed } from '@angular/core';\nimport { FieldTree } from '@angular/forms/signals';\n\n/**\n * Computed signal that determines if errors should be displayed\n * Based on field's invalid, touched, and error count\n *\n * @param field - Signal containing FieldTree\n * @returns Signal<boolean> - True if errors should be displayed\n */\nexport function shouldShowErrors<T>(field: Signal<FieldTree<T>>): Signal<boolean> {\n return computed(() => {\n const fieldTree = field();\n const control = fieldTree();\n return control.invalid() && control.touched() && control.errors().length > 0;\n });\n}\n","// Error display utilities for field components\nexport { createResolvedErrorsSignal } from './create-resolved-errors-signal';\nexport type { ResolvedError } from './create-resolved-errors-signal';\nexport { shouldShowErrors } from './should-show-errors';\n","/**\n * @ng-forge/dynamic-forms/integration\n *\n * Integration API for UI library authors building field implementations\n * (Material, Bootstrap, PrimeNG, Ionic, etc.)\n *\n * This entrypoint provides:\n * - Specific field type definitions (InputField, SelectField, etc.)\n * - Field mappers for creating FieldTypeDefinition\n * - Error display utilities for field components\n */\n\n// =============================================================================\n// Field Type Definitions\n// =============================================================================\n\nexport type {\n ButtonField,\n EventArgs,\n CheckboxField,\n DatepickerField,\n DatepickerProps,\n InputField,\n InputProps,\n InputType,\n InputTypeToValueType,\n StringInputType,\n MultiCheckboxField,\n RadioField,\n SelectField,\n SelectProps,\n SliderField,\n TextareaField,\n TextareaProps,\n ToggleField,\n} from './definitions';\n\n// =============================================================================\n// Field Mappers\n// =============================================================================\n\nexport {\n valueFieldMapper,\n buildValueFieldInputs,\n resolveValueFieldContext,\n checkboxFieldMapper,\n datepickerFieldMapper,\n optionsFieldMapper,\n} from './mappers';\n\nexport type { ValueFieldContext, FieldWithOptions } from './mappers';\n\n// =============================================================================\n// Error Display Utilities\n// =============================================================================\n\nexport { createResolvedErrorsSignal, shouldShowErrors } from './utils';\nexport type { ResolvedError } from './utils';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public_api';\n"],"names":[],"mappings":"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"}