@ng-forge/dynamic-forms 0.1.3 → 0.3.0
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/README.md +6 -4
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs +121 -4
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-B_676NHl.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-CMSmISSp.mjs} +561 -331
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CMSmISSp.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-YHFQcpCS.mjs → ng-forge-dynamic-forms-page-field.component-rdGD8A9d.mjs} +6 -6
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-rdGD8A9d.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-testing-test-button.harness-BenmCFcM.mjs → ng-forge-dynamic-forms-testing-test-button.harness-0iXHLraC.mjs} +7 -7
- package/fesm2022/ng-forge-dynamic-forms-testing-test-button.harness-0iXHLraC.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-testing.mjs +41 -38
- package/fesm2022/ng-forge-dynamic-forms-testing.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-CiYq73aF.mjs → ng-forge-dynamic-forms-text-field.component-CDxka2KD.mjs} +5 -5
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-CiYq73aF.mjs.map → ng-forge-dynamic-forms-text-field.component-CDxka2KD.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
- package/package.json +4 -4
- package/types/ng-forge-dynamic-forms-integration.d.ts +237 -9
- package/types/ng-forge-dynamic-forms-testing.d.ts +67 -35
- package/types/ng-forge-dynamic-forms.d.ts +263 -99
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-B_676NHl.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-YHFQcpCS.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-testing-test-button.harness-BenmCFcM.mjs.map +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { isSignal, computed, runInInjectionContext, InjectionToken, inject,
|
|
2
|
+
import { isSignal, computed, runInInjectionContext, InjectionToken, inject, signal, Injectable, untracked, input, linkedSignal, ChangeDetectionStrategy, Component, DestroyRef, Injector, model, afterNextRender, makeEnvironmentProviders, Pipe } from '@angular/core';
|
|
3
3
|
import { NgComponentOutlet } from '@angular/common';
|
|
4
|
-
import { submit, email, min, max, minLength, maxLength, pattern, required, validate, validateAsync, validateHttp, disabled, readonly, hidden, applyEach, applyWhenValue, applyWhen, apply, schema, validateTree, form } from '@angular/forms/signals';
|
|
4
|
+
import { submit, email, min, max, minLength, maxLength, pattern, required, validate, validateAsync, validateHttp, disabled, readonly, hidden, applyEach, applyWhenValue, applyWhen, apply, schema, validateTree, form, provideSignalFormsConfig } from '@angular/forms/signals';
|
|
5
5
|
import { toObservable, toSignal, takeUntilDestroyed, outputFromObservable } from '@angular/core/rxjs-interop';
|
|
6
6
|
import { defer, combineLatest, from, map, catchError, of, pipe, switchMap, forkJoin, scan, isObservable, firstValueFrom, EMPTY, Subject, filter } from 'rxjs';
|
|
7
7
|
import { scan as scan$1, map as map$1, filter as filter$1, take, switchMap as switchMap$1, shareReplay } from 'rxjs/operators';
|
|
8
8
|
import { explicitEffect } from 'ngxtension/explicit-effect';
|
|
9
|
+
import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Type guard to check if a value is an array of Signals.
|
|
@@ -73,27 +74,12 @@ function derivedFromDeferred(source, operator, options) {
|
|
|
73
74
|
* // Returns: ''
|
|
74
75
|
* ```
|
|
75
76
|
*
|
|
76
|
-
* @example
|
|
77
|
-
* ```typescript
|
|
78
|
-
* // Usage in component template
|
|
79
|
-
* @Component({
|
|
80
|
-
* template: `
|
|
81
|
-
* <div [class]="getGridClassString(field)" class="form-field">
|
|
82
|
-
* <!-- Field content -->
|
|
83
|
-
* </div>
|
|
84
|
-
* `
|
|
85
|
-
* })
|
|
86
|
-
* export class FieldComponent {
|
|
87
|
-
* field: FieldDef<unknown>;
|
|
88
|
-
* getGridClassString = getGridClassString;
|
|
89
|
-
* }
|
|
90
|
-
* ```
|
|
91
|
-
*
|
|
92
77
|
* @public
|
|
93
78
|
*/
|
|
94
79
|
function getGridClassString(fieldDef) {
|
|
95
|
-
|
|
96
|
-
|
|
80
|
+
const col = fieldDef.col;
|
|
81
|
+
if (typeof col === 'number' && Number.isInteger(col) && col > 0 && col <= 12) {
|
|
82
|
+
return `df-col-${col}`;
|
|
97
83
|
}
|
|
98
84
|
return '';
|
|
99
85
|
}
|
|
@@ -108,7 +94,7 @@ function getGridClassString(fieldDef) {
|
|
|
108
94
|
* @returns Record of input names to values
|
|
109
95
|
*/
|
|
110
96
|
function buildBaseInputs(fieldDef) {
|
|
111
|
-
const { key, label, className, tabIndex, props } = fieldDef;
|
|
97
|
+
const { key, label, className, tabIndex, props, meta } = fieldDef;
|
|
112
98
|
const inputs = {};
|
|
113
99
|
// Always include key - required by components for accessibility and identification
|
|
114
100
|
inputs['key'] = key;
|
|
@@ -133,6 +119,9 @@ function buildBaseInputs(fieldDef) {
|
|
|
133
119
|
if (props !== undefined) {
|
|
134
120
|
inputs['props'] = props;
|
|
135
121
|
}
|
|
122
|
+
if (meta !== undefined) {
|
|
123
|
+
inputs['meta'] = meta;
|
|
124
|
+
}
|
|
136
125
|
return inputs;
|
|
137
126
|
}
|
|
138
127
|
/**
|
|
@@ -150,6 +139,43 @@ function baseFieldMapper(fieldDef) {
|
|
|
150
139
|
return computed(() => buildBaseInputs(fieldDef));
|
|
151
140
|
}
|
|
152
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Merges forwarded props into meta, with meta taking precedence.
|
|
144
|
+
*
|
|
145
|
+
* @param inputs The current inputs record
|
|
146
|
+
* @param propsToMeta Array of prop keys to forward to meta
|
|
147
|
+
* @returns Updated inputs record with merged meta
|
|
148
|
+
*/
|
|
149
|
+
function mergeForwardedPropsToMeta(inputs, propsToMeta) {
|
|
150
|
+
const props = inputs['props'];
|
|
151
|
+
if (!props || propsToMeta.length === 0) {
|
|
152
|
+
return inputs;
|
|
153
|
+
}
|
|
154
|
+
// Extract values to forward from props
|
|
155
|
+
// Values are typed as FieldMeta-compatible since they're forwarded attributes (type, rows, cols, etc.)
|
|
156
|
+
const forwardedValues = {};
|
|
157
|
+
for (const key of propsToMeta) {
|
|
158
|
+
const value = props[key];
|
|
159
|
+
if (value !== undefined && value !== null) {
|
|
160
|
+
// Props being forwarded are known HTML attributes (string, number, boolean)
|
|
161
|
+
forwardedValues[key] = value;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// If nothing to forward, return inputs unchanged
|
|
165
|
+
if (Object.keys(forwardedValues).length === 0) {
|
|
166
|
+
return inputs;
|
|
167
|
+
}
|
|
168
|
+
// Merge: forwarded props first, then meta (meta wins)
|
|
169
|
+
const existingMeta = inputs['meta'];
|
|
170
|
+
const mergedMeta = {
|
|
171
|
+
...forwardedValues,
|
|
172
|
+
...existingMeta,
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
...inputs,
|
|
176
|
+
meta: mergedMeta,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
153
179
|
/**
|
|
154
180
|
* Main field mapper function that uses the field registry to get the appropriate mapper
|
|
155
181
|
* based on the field's type property.
|
|
@@ -157,30 +183,49 @@ function baseFieldMapper(fieldDef) {
|
|
|
157
183
|
* This function must be called within an injection context where FIELD_SIGNAL_CONTEXT
|
|
158
184
|
* is provided, as mappers inject the context to access form state.
|
|
159
185
|
*
|
|
186
|
+
* For componentless fields (no loadComponent and no mapper), returns undefined
|
|
187
|
+
* since there's no component to bind inputs to. Callers should check for undefined
|
|
188
|
+
* and skip rendering logic for such fields.
|
|
189
|
+
*
|
|
190
|
+
* If the field type definition specifies `propsToMeta`, the specified props
|
|
191
|
+
* will be merged into the meta object (with meta taking precedence).
|
|
192
|
+
*
|
|
160
193
|
* @param fieldDef The field definition to map
|
|
161
194
|
* @param fieldRegistry The registry of field type definitions
|
|
162
|
-
* @returns Signal containing Record of input names to values for
|
|
195
|
+
* @returns Signal containing Record of input names to values, or undefined for componentless fields
|
|
163
196
|
*/
|
|
164
197
|
function mapFieldToInputs(fieldDef, fieldRegistry) {
|
|
165
198
|
// Get the field type definition from registry
|
|
166
199
|
const fieldType = fieldRegistry.get(fieldDef.type);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
200
|
+
// Componentless field (no mapper and no loadComponent) - nothing to map
|
|
201
|
+
if (fieldType && !fieldType.loadComponent && !fieldType.mapper) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
// Get the base mapper result
|
|
205
|
+
const mapperResult = fieldType?.mapper ? fieldType.mapper(fieldDef) : baseFieldMapper(fieldDef);
|
|
206
|
+
// If no props forwarding configured, return as-is
|
|
207
|
+
const propsToMeta = fieldType?.propsToMeta;
|
|
208
|
+
if (!propsToMeta || propsToMeta.length === 0) {
|
|
209
|
+
return mapperResult;
|
|
171
210
|
}
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
211
|
+
// Wrap in computed to merge forwarded props into meta
|
|
212
|
+
return computed(() => {
|
|
213
|
+
const inputs = mapperResult();
|
|
214
|
+
return mergeForwardedPropsToMeta(inputs, propsToMeta);
|
|
215
|
+
});
|
|
175
216
|
}
|
|
176
217
|
|
|
177
218
|
/**
|
|
178
219
|
* Resolves a single field definition to a ResolvedField using RxJS.
|
|
179
220
|
* Loads the component asynchronously and maps inputs in the injection context.
|
|
180
221
|
*
|
|
222
|
+
* For componentless fields (e.g., hidden fields), returns undefined since
|
|
223
|
+
* there's nothing to render. These fields still contribute to form values
|
|
224
|
+
* through the form schema.
|
|
225
|
+
*
|
|
181
226
|
* @param fieldDef - The field definition to resolve
|
|
182
227
|
* @param context - The context containing dependencies for resolution
|
|
183
|
-
* @returns Observable that emits ResolvedField or undefined on error
|
|
228
|
+
* @returns Observable that emits ResolvedField or undefined (for componentless fields or on error)
|
|
184
229
|
*/
|
|
185
230
|
function resolveField(fieldDef, context) {
|
|
186
231
|
return from(context.loadTypeComponent(fieldDef.type)).pipe(map((component) => {
|
|
@@ -188,8 +233,16 @@ function resolveField(fieldDef, context) {
|
|
|
188
233
|
if (context.destroyRef.destroyed) {
|
|
189
234
|
return undefined;
|
|
190
235
|
}
|
|
236
|
+
// Componentless fields (e.g., hidden) return undefined - nothing to render
|
|
237
|
+
if (!component) {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
191
240
|
// Run mapper in injection context
|
|
192
241
|
const inputs = runInInjectionContext(context.injector, () => mapFieldToInputs(fieldDef, context.registry));
|
|
242
|
+
// Fields with components should always have inputs (componentless fields are handled above)
|
|
243
|
+
if (!inputs) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
193
246
|
return {
|
|
194
247
|
key: fieldDef.key,
|
|
195
248
|
component,
|
|
@@ -248,7 +301,7 @@ function createFieldResolutionPipe(getContext) {
|
|
|
248
301
|
* @param action - The submission action function
|
|
249
302
|
* @returns A wrapped function that always returns a Promise
|
|
250
303
|
*/
|
|
251
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
304
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Form tree type is dynamic, action signatures vary
|
|
252
305
|
function wrapSubmissionAction(action) {
|
|
253
306
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
254
307
|
return (formTree) => {
|
|
@@ -399,11 +452,11 @@ function memoize(fn, resolverOrOptions) {
|
|
|
399
452
|
const cache = new Map();
|
|
400
453
|
return ((...args) => {
|
|
401
454
|
const key = resolver ? resolver(...args) : JSON.stringify(args);
|
|
402
|
-
|
|
403
|
-
|
|
455
|
+
const cachedValue = cache.get(key);
|
|
456
|
+
if (cachedValue !== undefined) {
|
|
404
457
|
cache.delete(key);
|
|
405
|
-
cache.set(key,
|
|
406
|
-
return
|
|
458
|
+
cache.set(key, cachedValue);
|
|
459
|
+
return cachedValue;
|
|
407
460
|
}
|
|
408
461
|
const result = fn(...args);
|
|
409
462
|
if (cache.size >= maxSize) {
|
|
@@ -534,6 +587,21 @@ const FIELD_SIGNAL_CONTEXT = new InjectionToken('FIELD_SIGNAL_CONTEXT', {
|
|
|
534
587
|
*/
|
|
535
588
|
const ARRAY_CONTEXT = new InjectionToken('ARRAY_CONTEXT');
|
|
536
589
|
|
|
590
|
+
/**
|
|
591
|
+
* Base error class for all Dynamic Forms errors.
|
|
592
|
+
*
|
|
593
|
+
* This class centralizes the `[Dynamic Forms]` prefix, ensuring consistent
|
|
594
|
+
* error messaging across the library without requiring each error site to
|
|
595
|
+
* manually include the prefix.
|
|
596
|
+
*/
|
|
597
|
+
class DynamicFormError extends Error {
|
|
598
|
+
static PREFIX = '[Dynamic Forms]';
|
|
599
|
+
constructor(message) {
|
|
600
|
+
super(`${DynamicFormError.PREFIX} ${message}`);
|
|
601
|
+
this.name = 'DynamicFormError';
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
537
605
|
/**
|
|
538
606
|
* Injection token for the global field type registry.
|
|
539
607
|
*
|
|
@@ -646,37 +714,41 @@ function injectFieldRegistry() {
|
|
|
646
714
|
* Handles both synchronous component references and asynchronous
|
|
647
715
|
* dynamic imports. Automatically extracts default exports from ES modules.
|
|
648
716
|
*
|
|
717
|
+
* Returns `undefined` for componentless field types (e.g., hidden fields)
|
|
718
|
+
* that only contribute to form values without rendering UI.
|
|
719
|
+
*
|
|
649
720
|
* @param name - The name of the field type to load
|
|
650
|
-
* @returns Promise resolving to the component constructor
|
|
721
|
+
* @returns Promise resolving to the component constructor, or undefined for componentless fields
|
|
651
722
|
* @throws {Error} When field type is not registered or loading fails
|
|
652
723
|
*
|
|
653
724
|
* @example
|
|
654
725
|
* ```typescript
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
* const componentRef = vcr.createComponent(
|
|
658
|
-
* } catch (error) {
|
|
659
|
-
* console.error('Failed to load component:', error);
|
|
726
|
+
* const component = await fieldRegistry.loadTypeComponent('input');
|
|
727
|
+
* if (component) {
|
|
728
|
+
* const componentRef = vcr.createComponent(component);
|
|
660
729
|
* }
|
|
730
|
+
* // For componentless fields like 'hidden', component will be undefined
|
|
661
731
|
* ```
|
|
662
732
|
*/
|
|
663
733
|
async loadTypeComponent(name) {
|
|
664
734
|
const fieldType = registry.get(name);
|
|
665
735
|
if (!fieldType) {
|
|
666
|
-
throw new
|
|
736
|
+
throw new DynamicFormError(`Field type "${name}" is not registered`);
|
|
737
|
+
}
|
|
738
|
+
// Componentless field types (e.g., hidden) don't have loadComponent
|
|
739
|
+
if (!fieldType.loadComponent) {
|
|
740
|
+
return undefined;
|
|
667
741
|
}
|
|
668
742
|
// Handle async loading
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}
|
|
743
|
+
try {
|
|
744
|
+
const result = await fieldType.loadComponent();
|
|
745
|
+
// Handle ES module imports - extract default export if needed
|
|
746
|
+
const moduleResult = result;
|
|
747
|
+
return (typeof moduleResult === 'object' && 'default' in moduleResult && moduleResult.default) || result;
|
|
748
|
+
}
|
|
749
|
+
catch (error) {
|
|
750
|
+
throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
|
|
678
751
|
}
|
|
679
|
-
throw new Error(`[Dynamic Forms] Field type "${name}" has no component or loadComponent function`);
|
|
680
752
|
},
|
|
681
753
|
/**
|
|
682
754
|
* Gets all registered field type definitions.
|
|
@@ -735,100 +807,13 @@ function injectFieldRegistry() {
|
|
|
735
807
|
};
|
|
736
808
|
}
|
|
737
809
|
|
|
738
|
-
/**
|
|
739
|
-
* Log levels for the dynamic forms logger.
|
|
740
|
-
* Each level includes all levels above it (more severe).
|
|
741
|
-
*
|
|
742
|
-
* - Off: No logging
|
|
743
|
-
* - Error: Only errors
|
|
744
|
-
* - Warn: Errors + warnings
|
|
745
|
-
* - Info: Errors + warnings + info
|
|
746
|
-
* - Debug: All messages including debug
|
|
747
|
-
*/
|
|
748
|
-
var LogLevel;
|
|
749
|
-
(function (LogLevel) {
|
|
750
|
-
/** Disable all logging */
|
|
751
|
-
LogLevel[LogLevel["Off"] = 0] = "Off";
|
|
752
|
-
/** Only log errors */
|
|
753
|
-
LogLevel[LogLevel["Error"] = 1] = "Error";
|
|
754
|
-
/** Log errors and warnings */
|
|
755
|
-
LogLevel[LogLevel["Warn"] = 2] = "Warn";
|
|
756
|
-
/** Log errors, warnings, and info messages */
|
|
757
|
-
LogLevel[LogLevel["Info"] = 3] = "Info";
|
|
758
|
-
/** Log all messages including debug */
|
|
759
|
-
LogLevel[LogLevel["Debug"] = 4] = "Debug";
|
|
760
|
-
})(LogLevel || (LogLevel = {}));
|
|
761
|
-
|
|
762
|
-
/**
|
|
763
|
-
* Console-based logger implementation.
|
|
764
|
-
* Respects log level configuration to filter messages.
|
|
765
|
-
*/
|
|
766
|
-
class ConsoleLogger {
|
|
767
|
-
level;
|
|
768
|
-
prefix = '[Dynamic Forms]';
|
|
769
|
-
constructor(level) {
|
|
770
|
-
this.level = level;
|
|
771
|
-
}
|
|
772
|
-
debug(message, ...args) {
|
|
773
|
-
if (this.level >= LogLevel.Debug) {
|
|
774
|
-
console.debug(this.prefix, message, ...args);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
info(message, ...args) {
|
|
778
|
-
if (this.level >= LogLevel.Info) {
|
|
779
|
-
console.info(this.prefix, message, ...args);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
warn(message, ...args) {
|
|
783
|
-
if (this.level >= LogLevel.Warn) {
|
|
784
|
-
console.warn(this.prefix, message, ...args);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
error(message, ...args) {
|
|
788
|
-
if (this.level >= LogLevel.Error) {
|
|
789
|
-
console.error(this.prefix, message, ...args);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
/**
|
|
795
|
-
* No-operation logger implementation.
|
|
796
|
-
* All methods are no-ops - used in production by default.
|
|
797
|
-
*/
|
|
798
|
-
class NoopLogger {
|
|
799
|
-
debug() {
|
|
800
|
-
// Intentionally empty
|
|
801
|
-
}
|
|
802
|
-
info() {
|
|
803
|
-
// Intentionally empty
|
|
804
|
-
}
|
|
805
|
-
warn() {
|
|
806
|
-
// Intentionally empty
|
|
807
|
-
}
|
|
808
|
-
error() {
|
|
809
|
-
// Intentionally empty
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
810
|
/**
|
|
814
811
|
* Injection token for the dynamic forms logger.
|
|
815
812
|
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
* - Production mode: NoopLogger (silent)
|
|
819
|
-
*
|
|
820
|
-
* Override by providing a custom logger via withLogger() feature
|
|
821
|
-
* or by providing directly to this token.
|
|
813
|
+
* Provided by provideDynamicForm() with ConsoleLogger as default.
|
|
814
|
+
* Override with withLoggerConfig(false) to disable logging.
|
|
822
815
|
*/
|
|
823
|
-
const
|
|
824
|
-
providedIn: 'root',
|
|
825
|
-
factory: () => {
|
|
826
|
-
if (isDevMode()) {
|
|
827
|
-
return new ConsoleLogger(LogLevel.Warn);
|
|
828
|
-
}
|
|
829
|
-
return new NoopLogger();
|
|
830
|
-
},
|
|
831
|
-
});
|
|
816
|
+
const DynamicFormLogger = new InjectionToken('DynamicFormLogger');
|
|
832
817
|
|
|
833
818
|
/**
|
|
834
819
|
* Registry service that maintains references to root form fields.
|
|
@@ -987,10 +972,10 @@ class RootFormRegistryService {
|
|
|
987
972
|
this._formValueSignals.set(new Map());
|
|
988
973
|
this.formContexts.clear();
|
|
989
974
|
}
|
|
990
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
991
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.
|
|
975
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: RootFormRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
976
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: RootFormRegistryService });
|
|
992
977
|
}
|
|
993
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
978
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: RootFormRegistryService, decorators: [{
|
|
994
979
|
type: Injectable
|
|
995
980
|
}] });
|
|
996
981
|
|
|
@@ -1006,7 +991,7 @@ function isChildFieldContext(context) {
|
|
|
1006
991
|
*/
|
|
1007
992
|
class FieldContextRegistryService {
|
|
1008
993
|
rootFormRegistry = inject(RootFormRegistryService);
|
|
1009
|
-
logger = inject(
|
|
994
|
+
logger = inject(DynamicFormLogger);
|
|
1010
995
|
/**
|
|
1011
996
|
* Creates an evaluation context for a field by combining:
|
|
1012
997
|
* - The field's current value
|
|
@@ -1114,10 +1099,10 @@ class FieldContextRegistryService {
|
|
|
1114
1099
|
logger: this.logger,
|
|
1115
1100
|
};
|
|
1116
1101
|
}
|
|
1117
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
1118
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.
|
|
1102
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FieldContextRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1103
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FieldContextRegistryService });
|
|
1119
1104
|
}
|
|
1120
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
1105
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FieldContextRegistryService, decorators: [{
|
|
1121
1106
|
type: Injectable
|
|
1122
1107
|
}] });
|
|
1123
1108
|
|
|
@@ -1457,10 +1442,10 @@ class FunctionRegistryService {
|
|
|
1457
1442
|
this.clearAsyncValidators();
|
|
1458
1443
|
this.clearHttpValidators();
|
|
1459
1444
|
}
|
|
1460
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
1461
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.
|
|
1445
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FunctionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1446
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FunctionRegistryService });
|
|
1462
1447
|
}
|
|
1463
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
1448
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FunctionRegistryService, decorators: [{
|
|
1464
1449
|
type: Injectable
|
|
1465
1450
|
}] });
|
|
1466
1451
|
|
|
@@ -1607,7 +1592,7 @@ var TokenType;
|
|
|
1607
1592
|
/**
|
|
1608
1593
|
* Parser error with position information
|
|
1609
1594
|
*/
|
|
1610
|
-
class ExpressionParserError extends
|
|
1595
|
+
class ExpressionParserError extends DynamicFormError {
|
|
1611
1596
|
position;
|
|
1612
1597
|
expression;
|
|
1613
1598
|
constructor(message, position, expression) {
|
|
@@ -1705,7 +1690,7 @@ class Tokenizer {
|
|
|
1705
1690
|
this.position++;
|
|
1706
1691
|
}
|
|
1707
1692
|
}
|
|
1708
|
-
throw new ExpressionParserError('
|
|
1693
|
+
throw new ExpressionParserError('Unterminated string literal', start, this.expression);
|
|
1709
1694
|
}
|
|
1710
1695
|
readNumber() {
|
|
1711
1696
|
const start = this.position;
|
|
@@ -1810,7 +1795,7 @@ class Tokenizer {
|
|
|
1810
1795
|
case ']':
|
|
1811
1796
|
return { type: TokenType.RBRACKET, value: ']', position: start };
|
|
1812
1797
|
default:
|
|
1813
|
-
throw new ExpressionParserError(`
|
|
1798
|
+
throw new ExpressionParserError(`Unexpected character: ${char}`, start, this.expression);
|
|
1814
1799
|
}
|
|
1815
1800
|
}
|
|
1816
1801
|
}
|
|
@@ -1834,7 +1819,7 @@ class Parser {
|
|
|
1834
1819
|
this.tokens = tokenizer.tokenize();
|
|
1835
1820
|
this.current = 0;
|
|
1836
1821
|
if (this.isAtEnd()) {
|
|
1837
|
-
throw new ExpressionParserError('
|
|
1822
|
+
throw new ExpressionParserError('Empty expression', 0, this.expression);
|
|
1838
1823
|
}
|
|
1839
1824
|
return this.parseExpression();
|
|
1840
1825
|
}
|
|
@@ -1909,7 +1894,7 @@ class Parser {
|
|
|
1909
1894
|
if (this.match(TokenType.DOT)) {
|
|
1910
1895
|
// Member access: obj.property
|
|
1911
1896
|
if (!this.check(TokenType.IDENTIFIER)) {
|
|
1912
|
-
throw new ExpressionParserError('
|
|
1897
|
+
throw new ExpressionParserError('Expected property name after "."', this.peek().position, this.expression);
|
|
1913
1898
|
}
|
|
1914
1899
|
const property = this.advance().value;
|
|
1915
1900
|
node = { type: 'MemberAccess', object: node, property };
|
|
@@ -1962,7 +1947,7 @@ class Parser {
|
|
|
1962
1947
|
this.consume(TokenType.RPAREN, 'Expected ")" after expression');
|
|
1963
1948
|
return expr;
|
|
1964
1949
|
}
|
|
1965
|
-
throw new ExpressionParserError(`
|
|
1950
|
+
throw new ExpressionParserError(`Unexpected token: ${this.peek().value}`, this.peek().position, this.expression);
|
|
1966
1951
|
}
|
|
1967
1952
|
parseArgumentList() {
|
|
1968
1953
|
const args = [];
|
|
@@ -2013,7 +1998,7 @@ class Parser {
|
|
|
2013
1998
|
consume(type, message) {
|
|
2014
1999
|
if (this.check(type))
|
|
2015
2000
|
return this.advance();
|
|
2016
|
-
throw new ExpressionParserError(
|
|
2001
|
+
throw new ExpressionParserError(message, this.peek().position, this.expression);
|
|
2017
2002
|
}
|
|
2018
2003
|
}
|
|
2019
2004
|
|
|
@@ -2165,7 +2150,7 @@ class Evaluator {
|
|
|
2165
2150
|
case 'ArrayLiteral':
|
|
2166
2151
|
return this.evaluateArrayLiteral(node);
|
|
2167
2152
|
default:
|
|
2168
|
-
throw new ExpressionParserError(`
|
|
2153
|
+
throw new ExpressionParserError(`Unknown node type: ${node.type}`, 0, this.expression);
|
|
2169
2154
|
}
|
|
2170
2155
|
}
|
|
2171
2156
|
evaluateIdentifier(name) {
|
|
@@ -2181,7 +2166,7 @@ class Evaluator {
|
|
|
2181
2166
|
}
|
|
2182
2167
|
// Block access to dangerous properties that could leak information or enable attacks
|
|
2183
2168
|
if (BLOCKED_PROPERTIES.has(node.property)) {
|
|
2184
|
-
throw new ExpressionParserError(`
|
|
2169
|
+
throw new ExpressionParserError(`Property "${node.property}" is not accessible for security reasons`, 0, this.expression);
|
|
2185
2170
|
}
|
|
2186
2171
|
// Allow property access on plain objects and primitives
|
|
2187
2172
|
if (typeof obj === 'object' || typeof obj === 'string' || typeof obj === 'number') {
|
|
@@ -2236,7 +2221,7 @@ class Evaluator {
|
|
|
2236
2221
|
case '<=':
|
|
2237
2222
|
return left <= right;
|
|
2238
2223
|
default:
|
|
2239
|
-
throw new ExpressionParserError(`
|
|
2224
|
+
throw new ExpressionParserError(`Unknown binary operator: ${node.operator}`, 0, this.expression);
|
|
2240
2225
|
}
|
|
2241
2226
|
}
|
|
2242
2227
|
evaluateUnaryOp(node) {
|
|
@@ -2249,23 +2234,23 @@ class Evaluator {
|
|
|
2249
2234
|
case '+':
|
|
2250
2235
|
return +operand;
|
|
2251
2236
|
default:
|
|
2252
|
-
throw new ExpressionParserError(`
|
|
2237
|
+
throw new ExpressionParserError(`Unknown unary operator: ${node.operator}`, 0, this.expression);
|
|
2253
2238
|
}
|
|
2254
2239
|
}
|
|
2255
2240
|
evaluateCallExpression(node) {
|
|
2256
2241
|
// Only allow method calls (member access), not arbitrary function calls
|
|
2257
2242
|
if (node.callee.type !== 'MemberAccess') {
|
|
2258
|
-
throw new ExpressionParserError('
|
|
2243
|
+
throw new ExpressionParserError('Only method calls are allowed, not arbitrary function calls', 0, this.expression);
|
|
2259
2244
|
}
|
|
2260
2245
|
const obj = this.evaluate(node.callee.object);
|
|
2261
2246
|
const methodName = node.callee.property;
|
|
2262
2247
|
// Block access to dangerous properties (must check before method call)
|
|
2263
2248
|
if (BLOCKED_PROPERTIES.has(methodName)) {
|
|
2264
|
-
throw new ExpressionParserError(`
|
|
2249
|
+
throw new ExpressionParserError(`Property "${methodName}" is not accessible for security reasons`, 0, this.expression);
|
|
2265
2250
|
}
|
|
2266
2251
|
// Check if the method is in the instance method whitelist
|
|
2267
2252
|
if (!this.isMethodSafe(obj, methodName)) {
|
|
2268
|
-
throw new ExpressionParserError(`
|
|
2253
|
+
throw new ExpressionParserError(`Method "${methodName}" is not allowed for security reasons`, 0, this.expression);
|
|
2269
2254
|
}
|
|
2270
2255
|
// Evaluate arguments
|
|
2271
2256
|
const args = node.arguments.map((arg) => this.evaluate(arg));
|
|
@@ -2383,7 +2368,7 @@ class ExpressionParser {
|
|
|
2383
2368
|
throw error;
|
|
2384
2369
|
}
|
|
2385
2370
|
// Wrap other errors for consistency
|
|
2386
|
-
throw new ExpressionParserError(`
|
|
2371
|
+
throw new ExpressionParserError(`Error evaluating expression: ${error instanceof Error ? error.message : String(error)}`, 0, expression);
|
|
2387
2372
|
}
|
|
2388
2373
|
}
|
|
2389
2374
|
/**
|
|
@@ -2478,6 +2463,56 @@ function evaluateOrCondition(expression, context) {
|
|
|
2478
2463
|
return expression.conditions.some((condition) => evaluateCondition(condition, context));
|
|
2479
2464
|
}
|
|
2480
2465
|
|
|
2466
|
+
/**
|
|
2467
|
+
* Cache for memoized logic functions, keyed by injection context.
|
|
2468
|
+
* Uses WeakMap with FunctionRegistryService as outer key to automatically clean up.
|
|
2469
|
+
* Inner structure: Map<FieldContextRegistryService, Map<expressionKey, LogicFn>>
|
|
2470
|
+
*/
|
|
2471
|
+
const logicFunctionCache = new WeakMap();
|
|
2472
|
+
/**
|
|
2473
|
+
* Serializes a value to a deterministic string for cache key generation.
|
|
2474
|
+
* Unlike JSON.stringify, this sorts object keys to ensure consistent output
|
|
2475
|
+
* regardless of property insertion order.
|
|
2476
|
+
*/
|
|
2477
|
+
function stableStringify(value) {
|
|
2478
|
+
if (value === null || value === undefined) {
|
|
2479
|
+
return String(value);
|
|
2480
|
+
}
|
|
2481
|
+
if (typeof value !== 'object') {
|
|
2482
|
+
return JSON.stringify(value);
|
|
2483
|
+
}
|
|
2484
|
+
if (Array.isArray(value)) {
|
|
2485
|
+
return '[' + value.map(stableStringify).join(',') + ']';
|
|
2486
|
+
}
|
|
2487
|
+
const obj = value;
|
|
2488
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
2489
|
+
const pairs = sortedKeys.map((key) => JSON.stringify(key) + ':' + stableStringify(obj[key]));
|
|
2490
|
+
return '{' + pairs.join(',') + '}';
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Serializes a conditional expression to a deterministic string key for caching.
|
|
2494
|
+
* Uses stable stringification to ensure equivalent objects produce the same key
|
|
2495
|
+
* regardless of property order.
|
|
2496
|
+
*/
|
|
2497
|
+
function serializeExpression(expression) {
|
|
2498
|
+
return stableStringify(expression);
|
|
2499
|
+
}
|
|
2500
|
+
/**
|
|
2501
|
+
* Gets or creates the cache map for a specific injection context.
|
|
2502
|
+
*/
|
|
2503
|
+
function getContextCache(ctx) {
|
|
2504
|
+
let outerCache = logicFunctionCache.get(ctx.functionRegistry);
|
|
2505
|
+
if (!outerCache) {
|
|
2506
|
+
outerCache = new WeakMap();
|
|
2507
|
+
logicFunctionCache.set(ctx.functionRegistry, outerCache);
|
|
2508
|
+
}
|
|
2509
|
+
let innerCache = outerCache.get(ctx.fieldContextRegistry);
|
|
2510
|
+
if (!innerCache) {
|
|
2511
|
+
innerCache = new Map();
|
|
2512
|
+
outerCache.set(ctx.fieldContextRegistry, innerCache);
|
|
2513
|
+
}
|
|
2514
|
+
return innerCache;
|
|
2515
|
+
}
|
|
2481
2516
|
/**
|
|
2482
2517
|
* Create a logic function from a conditional expression.
|
|
2483
2518
|
*
|
|
@@ -2489,17 +2524,32 @@ function evaluateOrCondition(expression, context) {
|
|
|
2489
2524
|
* NOTE: For validators, use createEvaluationContext directly (with untracked) to prevent
|
|
2490
2525
|
* infinite reactive loops. Validators with cross-field dependencies should be hoisted
|
|
2491
2526
|
* to form-level using validateTree.
|
|
2527
|
+
*
|
|
2528
|
+
* @param expression The conditional expression to evaluate
|
|
2529
|
+
* @returns A LogicFn that evaluates the condition in the context of a field
|
|
2492
2530
|
*/
|
|
2493
2531
|
function createLogicFunction(expression) {
|
|
2494
2532
|
// Inject services during factory creation, not during execution
|
|
2495
2533
|
const functionRegistry = inject(FunctionRegistryService);
|
|
2496
2534
|
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
2497
|
-
|
|
2535
|
+
// Get cache for this injection context
|
|
2536
|
+
const contextCache = getContextCache({ functionRegistry, fieldContextRegistry });
|
|
2537
|
+
// Generate cache key from serialized expression
|
|
2538
|
+
const cacheKey = serializeExpression(expression);
|
|
2539
|
+
// Check cache first
|
|
2540
|
+
const cached = contextCache.get(cacheKey);
|
|
2541
|
+
if (cached) {
|
|
2542
|
+
return cached;
|
|
2543
|
+
}
|
|
2544
|
+
const fn = (ctx) => {
|
|
2498
2545
|
// Create REACTIVE evaluation context for logic functions
|
|
2499
2546
|
// This allows logic to re-evaluate when dependent fields change
|
|
2500
2547
|
const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(ctx, functionRegistry.getCustomFunctions());
|
|
2501
2548
|
return evaluateCondition(expression, evaluationContext);
|
|
2502
2549
|
};
|
|
2550
|
+
// Cache the function
|
|
2551
|
+
contextCache.set(cacheKey, fn);
|
|
2552
|
+
return fn;
|
|
2503
2553
|
}
|
|
2504
2554
|
|
|
2505
2555
|
/**
|
|
@@ -2512,11 +2562,6 @@ const dynamicValueFunctionCache = new WeakMap();
|
|
|
2512
2562
|
* Create a dynamic value function from an expression string.
|
|
2513
2563
|
* Uses secure AST-based parsing instead of eval() or new Function().
|
|
2514
2564
|
*
|
|
2515
|
-
* Optimizations:
|
|
2516
|
-
* - Injects FieldContextRegistryService at creation time, not execution time
|
|
2517
|
-
* - Caches created functions by expression string per injection context
|
|
2518
|
-
* - Expression parsing is already cached by ExpressionParser's LRU cache
|
|
2519
|
-
*
|
|
2520
2565
|
* @param expression The expression string to evaluate
|
|
2521
2566
|
* @returns A LogicFn that evaluates the expression in the context of a field
|
|
2522
2567
|
*/
|
|
@@ -2524,7 +2569,7 @@ function createDynamicValueFunction(expression) {
|
|
|
2524
2569
|
// Inject services during factory creation, not during execution
|
|
2525
2570
|
// This captures the service instance in the closure
|
|
2526
2571
|
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
2527
|
-
const logger = inject(
|
|
2572
|
+
const logger = inject(DynamicFormLogger);
|
|
2528
2573
|
// Get or create cache for this injection context
|
|
2529
2574
|
let contextCache = dynamicValueFunctionCache.get(fieldContextRegistry);
|
|
2530
2575
|
if (!contextCache) {
|
|
@@ -2891,7 +2936,7 @@ function applyCustomValidator(config, fieldPath) {
|
|
|
2891
2936
|
validatorFn = createFunctionValidator(config);
|
|
2892
2937
|
}
|
|
2893
2938
|
else {
|
|
2894
|
-
const logger = inject(
|
|
2939
|
+
const logger = inject(DynamicFormLogger);
|
|
2895
2940
|
logger.warn('Custom validator must have either "expression" or "functionName"');
|
|
2896
2941
|
return;
|
|
2897
2942
|
}
|
|
@@ -2903,7 +2948,7 @@ function applyCustomValidator(config, fieldPath) {
|
|
|
2903
2948
|
});
|
|
2904
2949
|
}
|
|
2905
2950
|
function createFunctionValidator(config) {
|
|
2906
|
-
const logger = inject(
|
|
2951
|
+
const logger = inject(DynamicFormLogger);
|
|
2907
2952
|
const functionName = config.functionName;
|
|
2908
2953
|
if (!functionName) {
|
|
2909
2954
|
logger.warn('Custom validator missing functionName');
|
|
@@ -2919,7 +2964,7 @@ function createFunctionValidator(config) {
|
|
|
2919
2964
|
return (ctx) => validatorFn(ctx, config.params);
|
|
2920
2965
|
}
|
|
2921
2966
|
function createExpressionValidator(config) {
|
|
2922
|
-
const logger = inject(
|
|
2967
|
+
const logger = inject(DynamicFormLogger);
|
|
2923
2968
|
const expression = config.expression;
|
|
2924
2969
|
if (!expression) {
|
|
2925
2970
|
logger.warn('Custom validator missing expression');
|
|
@@ -2930,7 +2975,7 @@ function createExpressionValidator(config) {
|
|
|
2930
2975
|
return (ctx) => {
|
|
2931
2976
|
try {
|
|
2932
2977
|
const evaluationContext = fieldContextRegistry.createEvaluationContext(ctx, functionRegistry.getCustomFunctions());
|
|
2933
|
-
const result = ExpressionParser.evaluate(
|
|
2978
|
+
const result = ExpressionParser.evaluate(expression, evaluationContext);
|
|
2934
2979
|
if (result) {
|
|
2935
2980
|
return null;
|
|
2936
2981
|
}
|
|
@@ -2966,7 +3011,7 @@ function createExpressionValidator(config) {
|
|
|
2966
3011
|
* - onError: Optional handler for resource errors
|
|
2967
3012
|
*/
|
|
2968
3013
|
function applyAsyncValidator(config, fieldPath) {
|
|
2969
|
-
const logger = inject(
|
|
3014
|
+
const logger = inject(DynamicFormLogger);
|
|
2970
3015
|
const registry = inject(FunctionRegistryService);
|
|
2971
3016
|
const validatorConfig = registry.getAsyncValidator(config.functionName);
|
|
2972
3017
|
if (!validatorConfig) {
|
|
@@ -2996,7 +3041,7 @@ function applyAsyncValidator(config, fieldPath) {
|
|
|
2996
3041
|
* - onError: Optional handler for HTTP errors
|
|
2997
3042
|
*/
|
|
2998
3043
|
function applyHttpValidator(config, fieldPath) {
|
|
2999
|
-
const logger = inject(
|
|
3044
|
+
const logger = inject(DynamicFormLogger);
|
|
3000
3045
|
const registry = inject(FunctionRegistryService);
|
|
3001
3046
|
const httpValidatorConfig = registry.getHttpValidator(config.functionName);
|
|
3002
3047
|
if (!httpValidatorConfig) {
|
|
@@ -3085,10 +3130,10 @@ class SchemaRegistryService {
|
|
|
3085
3130
|
}
|
|
3086
3131
|
return schema;
|
|
3087
3132
|
}
|
|
3088
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
3089
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.
|
|
3133
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3134
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SchemaRegistryService });
|
|
3090
3135
|
}
|
|
3091
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
3136
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SchemaRegistryService, decorators: [{
|
|
3092
3137
|
type: Injectable
|
|
3093
3138
|
}] });
|
|
3094
3139
|
|
|
@@ -3135,7 +3180,7 @@ function createTypePredicateFunction(predicate, logger) {
|
|
|
3135
3180
|
* 3. The actual schema application happens at runtime via the schema function
|
|
3136
3181
|
*/
|
|
3137
3182
|
function applySchema(config, fieldPath) {
|
|
3138
|
-
const logger = inject(
|
|
3183
|
+
const logger = inject(DynamicFormLogger);
|
|
3139
3184
|
const schemaRegistry = inject(SchemaRegistryService);
|
|
3140
3185
|
const schema = schemaRegistry.resolveSchema(config.schema);
|
|
3141
3186
|
if (!schema) {
|
|
@@ -3196,6 +3241,7 @@ function createSchemaFunction(schema) {
|
|
|
3196
3241
|
/**
|
|
3197
3242
|
* Type guard for GroupField with proper type narrowing
|
|
3198
3243
|
*/
|
|
3244
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type guard must accept any field type
|
|
3199
3245
|
function isGroupField(field) {
|
|
3200
3246
|
return field.type === 'group' && 'fields' in field;
|
|
3201
3247
|
}
|
|
@@ -3203,16 +3249,58 @@ function isGroupField(field) {
|
|
|
3203
3249
|
/**
|
|
3204
3250
|
* Type guard for ArrayField with proper type narrowing
|
|
3205
3251
|
*/
|
|
3252
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type guard must accept any field type
|
|
3206
3253
|
function isArrayField(field) {
|
|
3207
3254
|
return field.type === 'array' && 'fields' in field;
|
|
3208
3255
|
}
|
|
3209
3256
|
|
|
3257
|
+
/**
|
|
3258
|
+
* Type guard to check if a field is a hidden field.
|
|
3259
|
+
*
|
|
3260
|
+
* @param field - The field to check
|
|
3261
|
+
* @returns True if the field is a HiddenField
|
|
3262
|
+
*/
|
|
3263
|
+
function isHiddenField(field) {
|
|
3264
|
+
return field.type === 'hidden';
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3210
3267
|
/**
|
|
3211
3268
|
* Type guard for RowField with proper type narrowing
|
|
3212
3269
|
*/
|
|
3270
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type guard must accept any field type
|
|
3213
3271
|
function isRowField(field) {
|
|
3214
3272
|
return field.type === 'row' && 'fields' in field && Array.isArray(field.fields);
|
|
3215
3273
|
}
|
|
3274
|
+
/**
|
|
3275
|
+
* Validates that a row field doesn't contain hidden fields.
|
|
3276
|
+
* Hidden fields are not allowed in rows because rows are for horizontal layouts,
|
|
3277
|
+
* and hidden fields don't render anything.
|
|
3278
|
+
*
|
|
3279
|
+
* @param rowField The row field to validate
|
|
3280
|
+
* @returns true if valid (no hidden fields), false otherwise
|
|
3281
|
+
*/
|
|
3282
|
+
function validateRowNesting(rowField) {
|
|
3283
|
+
return !hasHiddenFields(rowField.fields);
|
|
3284
|
+
}
|
|
3285
|
+
/**
|
|
3286
|
+
* Recursively checks if fields contain any hidden fields
|
|
3287
|
+
* @param fields Array of field definitions to check
|
|
3288
|
+
* @returns true if hidden fields found, false otherwise
|
|
3289
|
+
*/
|
|
3290
|
+
function hasHiddenFields(fields) {
|
|
3291
|
+
for (const field of fields) {
|
|
3292
|
+
if (isHiddenField(field)) {
|
|
3293
|
+
return true;
|
|
3294
|
+
}
|
|
3295
|
+
// Check group fields for nested hidden fields
|
|
3296
|
+
if (isGroupField(field)) {
|
|
3297
|
+
if (hasHiddenFields(field.fields)) {
|
|
3298
|
+
return true;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
return false;
|
|
3303
|
+
}
|
|
3216
3304
|
|
|
3217
3305
|
/**
|
|
3218
3306
|
* Type guard for PageField with proper type narrowing
|
|
@@ -3610,74 +3698,9 @@ function isDisplayOnlyField(field) {
|
|
|
3610
3698
|
return field.type === 'text' || isContainerField(field);
|
|
3611
3699
|
}
|
|
3612
3700
|
|
|
3613
|
-
/**
|
|
3614
|
-
* Type guard to check if a value is a DynamicFormFeature
|
|
3615
|
-
*/
|
|
3616
|
-
function isDynamicFormFeature(value) {
|
|
3617
|
-
return (typeof value === 'object' &&
|
|
3618
|
-
value !== null &&
|
|
3619
|
-
'ɵkind' in value &&
|
|
3620
|
-
typeof value.ɵkind === 'string' &&
|
|
3621
|
-
'ɵproviders' in value &&
|
|
3622
|
-
Array.isArray(value.ɵproviders));
|
|
3623
|
-
}
|
|
3624
|
-
/**
|
|
3625
|
-
* Helper to create a feature with proper typing
|
|
3626
|
-
*/
|
|
3627
|
-
function createFeature(kind, providers) {
|
|
3628
|
-
return {
|
|
3629
|
-
ɵkind: kind,
|
|
3630
|
-
ɵproviders: providers,
|
|
3631
|
-
};
|
|
3632
|
-
}
|
|
3633
|
-
|
|
3634
|
-
/**
|
|
3635
|
-
* Configure logging for dynamic forms.
|
|
3636
|
-
*
|
|
3637
|
-
* @example
|
|
3638
|
-
* ```typescript
|
|
3639
|
-
* // Set log level
|
|
3640
|
-
* provideDynamicForm(
|
|
3641
|
-
* ...withMaterialFields(),
|
|
3642
|
-
* withLogger({ level: LogLevel.Debug })
|
|
3643
|
-
* )
|
|
3644
|
-
*
|
|
3645
|
-
* // Disable logging entirely
|
|
3646
|
-
* provideDynamicForm(
|
|
3647
|
-
* ...withMaterialFields(),
|
|
3648
|
-
* withLogger({ level: LogLevel.Off })
|
|
3649
|
-
* )
|
|
3650
|
-
*
|
|
3651
|
-
* // Custom logger implementation
|
|
3652
|
-
* provideDynamicForm(
|
|
3653
|
-
* ...withMaterialFields(),
|
|
3654
|
-
* withLogger({ logger: mySentryLogger })
|
|
3655
|
-
* )
|
|
3656
|
-
* ```
|
|
3657
|
-
*
|
|
3658
|
-
* @param options - Logger configuration options
|
|
3659
|
-
* @returns A DynamicFormFeature that configures logging
|
|
3660
|
-
*/
|
|
3661
|
-
function withLogger(options = {}) {
|
|
3662
|
-
let logger;
|
|
3663
|
-
if (options.logger) {
|
|
3664
|
-
// Use custom logger implementation
|
|
3665
|
-
logger = options.logger;
|
|
3666
|
-
}
|
|
3667
|
-
else if (options.level === LogLevel.Off) {
|
|
3668
|
-
// Explicit disable - use noop
|
|
3669
|
-
logger = new NoopLogger();
|
|
3670
|
-
}
|
|
3671
|
-
else {
|
|
3672
|
-
// Use console logger with specified level (default to Warn)
|
|
3673
|
-
logger = new ConsoleLogger(options.level ?? LogLevel.Warn);
|
|
3674
|
-
}
|
|
3675
|
-
return createFeature('logger', [{ provide: DYNAMIC_FORM_LOGGER, useValue: logger }]);
|
|
3676
|
-
}
|
|
3677
|
-
|
|
3678
3701
|
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
3679
3702
|
function getFieldTreeByKey(ctx, key) {
|
|
3680
|
-
return ctx.
|
|
3703
|
+
return ctx.fieldTree[key];
|
|
3681
3704
|
}
|
|
3682
3705
|
/**
|
|
3683
3706
|
* Creates an Angular signal forms schema from field definitions
|
|
@@ -3697,7 +3720,7 @@ function createSchemaFromFields(fields, registry, crossFieldValidators) {
|
|
|
3697
3720
|
// Inject services for cross-field validation
|
|
3698
3721
|
// These will be available because createSchemaFromFields is called within runInInjectionContext
|
|
3699
3722
|
const functionRegistry = inject(FunctionRegistryService);
|
|
3700
|
-
const logger = inject(
|
|
3723
|
+
const logger = inject(DynamicFormLogger);
|
|
3701
3724
|
return schema((path) => {
|
|
3702
3725
|
for (const fieldDef of fields) {
|
|
3703
3726
|
const valueHandling = getFieldValueHandling(fieldDef.type, registry);
|
|
@@ -3739,7 +3762,7 @@ function createSchemaFromFields(fields, registry, crossFieldValidators) {
|
|
|
3739
3762
|
* This is the key integration point that routes cross-field validation errors
|
|
3740
3763
|
* to the appropriate target fields via Angular's form state system.
|
|
3741
3764
|
*
|
|
3742
|
-
* The validateTree function allows returning errors with a `
|
|
3765
|
+
* The validateTree function allows returning errors with a `fieldTree` property
|
|
3743
3766
|
* that targets specific fields, which Angular automatically routes to those
|
|
3744
3767
|
* fields' errors() signal.
|
|
3745
3768
|
*
|
|
@@ -3765,7 +3788,7 @@ function applyCrossFieldTreeValidator(rootPath, validators, functionRegistry, lo
|
|
|
3765
3788
|
for (const entry of validators) {
|
|
3766
3789
|
const { sourceFieldKey, config } = entry;
|
|
3767
3790
|
try {
|
|
3768
|
-
const error = evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, customFunctions);
|
|
3791
|
+
const error = evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, customFunctions, logger);
|
|
3769
3792
|
if (error) {
|
|
3770
3793
|
errors.push(error);
|
|
3771
3794
|
}
|
|
@@ -3778,7 +3801,7 @@ function applyCrossFieldTreeValidator(rootPath, validators, functionRegistry, lo
|
|
|
3778
3801
|
const customConfig = config;
|
|
3779
3802
|
errors.push({
|
|
3780
3803
|
kind: customConfig.kind || config.type || 'custom',
|
|
3781
|
-
|
|
3804
|
+
fieldTree: targetField,
|
|
3782
3805
|
});
|
|
3783
3806
|
}
|
|
3784
3807
|
}
|
|
@@ -3789,7 +3812,7 @@ function applyCrossFieldTreeValidator(rootPath, validators, functionRegistry, lo
|
|
|
3789
3812
|
/**
|
|
3790
3813
|
* Evaluates a single cross-field validator entry and returns an error if validation fails.
|
|
3791
3814
|
*/
|
|
3792
|
-
function evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, customFunctions) {
|
|
3815
|
+
function evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, customFunctions, logger) {
|
|
3793
3816
|
const { config } = entry;
|
|
3794
3817
|
const fieldValue = formValue[sourceFieldKey];
|
|
3795
3818
|
// Create evaluation context for condition/expression evaluation
|
|
@@ -3798,6 +3821,7 @@ function evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, cust
|
|
|
3798
3821
|
formValue,
|
|
3799
3822
|
fieldPath: sourceFieldKey,
|
|
3800
3823
|
customFunctions,
|
|
3824
|
+
logger,
|
|
3801
3825
|
};
|
|
3802
3826
|
// Check if this is a custom validator (with expression) or a built-in validator (with when condition)
|
|
3803
3827
|
if (config.type === 'custom') {
|
|
@@ -3815,7 +3839,7 @@ function evaluateCustomCrossFieldValidator(config, evaluationContext, sourceFiel
|
|
|
3815
3839
|
if (!config.expression) {
|
|
3816
3840
|
return null;
|
|
3817
3841
|
}
|
|
3818
|
-
const { fieldValue, formValue } = evaluationContext;
|
|
3842
|
+
const { fieldValue, formValue, logger, customFunctions } = evaluationContext;
|
|
3819
3843
|
// First, evaluate the when condition if present
|
|
3820
3844
|
// If the condition is false, the validator doesn't apply (validation passes)
|
|
3821
3845
|
if (config.when) {
|
|
@@ -3823,8 +3847,8 @@ function evaluateCustomCrossFieldValidator(config, evaluationContext, sourceFiel
|
|
|
3823
3847
|
fieldValue,
|
|
3824
3848
|
formValue,
|
|
3825
3849
|
fieldPath: sourceFieldKey,
|
|
3826
|
-
customFunctions:
|
|
3827
|
-
logger
|
|
3850
|
+
customFunctions: customFunctions || {},
|
|
3851
|
+
logger,
|
|
3828
3852
|
});
|
|
3829
3853
|
if (!conditionMet) {
|
|
3830
3854
|
return null; // Condition not met, skip validation
|
|
@@ -3843,7 +3867,7 @@ function evaluateCustomCrossFieldValidator(config, evaluationContext, sourceFiel
|
|
|
3843
3867
|
}
|
|
3844
3868
|
const errorObj = {
|
|
3845
3869
|
kind: config.kind || 'custom',
|
|
3846
|
-
|
|
3870
|
+
fieldTree: targetField,
|
|
3847
3871
|
};
|
|
3848
3872
|
// Evaluate and include errorParams for message interpolation
|
|
3849
3873
|
if (config.errorParams) {
|
|
@@ -3863,7 +3887,7 @@ function evaluateCustomCrossFieldValidator(config, evaluationContext, sourceFiel
|
|
|
3863
3887
|
* First checks the when condition, then applies the built-in validation logic.
|
|
3864
3888
|
*/
|
|
3865
3889
|
function evaluateBuiltInCrossFieldValidator(config, evaluationContext, sourceFieldKey, ctx) {
|
|
3866
|
-
const { fieldValue, formValue } = evaluationContext;
|
|
3890
|
+
const { fieldValue, formValue, logger, customFunctions } = evaluationContext;
|
|
3867
3891
|
// First, evaluate the when condition
|
|
3868
3892
|
// If the condition is false, the validator doesn't apply (validation passes)
|
|
3869
3893
|
if (config.when) {
|
|
@@ -3871,8 +3895,8 @@ function evaluateBuiltInCrossFieldValidator(config, evaluationContext, sourceFie
|
|
|
3871
3895
|
fieldValue,
|
|
3872
3896
|
formValue,
|
|
3873
3897
|
fieldPath: sourceFieldKey,
|
|
3874
|
-
customFunctions:
|
|
3875
|
-
logger
|
|
3898
|
+
customFunctions: customFunctions || {},
|
|
3899
|
+
logger,
|
|
3876
3900
|
});
|
|
3877
3901
|
if (!conditionMet) {
|
|
3878
3902
|
return null; // Condition not met, skip validation
|
|
@@ -3890,7 +3914,7 @@ function evaluateBuiltInCrossFieldValidator(config, evaluationContext, sourceFie
|
|
|
3890
3914
|
}
|
|
3891
3915
|
return {
|
|
3892
3916
|
kind: config.type,
|
|
3893
|
-
|
|
3917
|
+
fieldTree: targetField,
|
|
3894
3918
|
};
|
|
3895
3919
|
}
|
|
3896
3920
|
/**
|
|
@@ -4038,6 +4062,7 @@ class EventBus {
|
|
|
4038
4062
|
* eventBus.dispatch(CustomFormEvent);
|
|
4039
4063
|
* ```
|
|
4040
4064
|
*/
|
|
4065
|
+
// TypeScript limitation: Must use ConstructorParameters which relies on `any` in FormEventConstructor
|
|
4041
4066
|
dispatch(eventConstructor, ...args) {
|
|
4042
4067
|
this.pipeline$.next(new eventConstructor(...args));
|
|
4043
4068
|
}
|
|
@@ -4079,10 +4104,10 @@ class EventBus {
|
|
|
4079
4104
|
}
|
|
4080
4105
|
return this.pipeline$.pipe(filter((event) => event.type === eventType));
|
|
4081
4106
|
}
|
|
4082
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
4083
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.
|
|
4107
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: EventBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4108
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: EventBus });
|
|
4084
4109
|
}
|
|
4085
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
4110
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: EventBus, decorators: [{
|
|
4086
4111
|
type: Injectable
|
|
4087
4112
|
}] });
|
|
4088
4113
|
|
|
@@ -4126,7 +4151,7 @@ class ComponentInitializedEvent {
|
|
|
4126
4151
|
* ```
|
|
4127
4152
|
*/
|
|
4128
4153
|
function createInitializationTracker(eventBus, expectedCount) {
|
|
4129
|
-
return eventBus.on('component-initialized').pipe(scan$1((count
|
|
4154
|
+
return eventBus.on('component-initialized').pipe(scan$1((count) => count + 1, 0), map$1((currentCount) => currentCount >= expectedCount), filter$1((isComplete) => isComplete));
|
|
4130
4155
|
}
|
|
4131
4156
|
/**
|
|
4132
4157
|
* Creates an observable that tracks component initialization progress with detailed status.
|
|
@@ -4151,7 +4176,7 @@ function createInitializationTracker(eventBus, expectedCount) {
|
|
|
4151
4176
|
* ```
|
|
4152
4177
|
*/
|
|
4153
4178
|
function createDetailedInitializationTracker(eventBus, expectedCount) {
|
|
4154
|
-
return eventBus.on('component-initialized').pipe(scan$1((count
|
|
4179
|
+
return eventBus.on('component-initialized').pipe(scan$1((count) => count + 1, 0), map$1((currentCount) => ({
|
|
4155
4180
|
currentCount,
|
|
4156
4181
|
expectedCount,
|
|
4157
4182
|
isComplete: currentCount >= expectedCount,
|
|
@@ -4584,6 +4609,9 @@ function tryCreateSchemaEntry(fieldKey, config) {
|
|
|
4584
4609
|
/** Converts a built-in validator with cross-field expression to a custom validator. */
|
|
4585
4610
|
function convertBuiltInToCustomValidator(config) {
|
|
4586
4611
|
const expression = config.expression;
|
|
4612
|
+
if (!expression) {
|
|
4613
|
+
throw new Error(`Built-in validator ${config.type} missing required expression for cross-field conversion`);
|
|
4614
|
+
}
|
|
4587
4615
|
let validationExpression;
|
|
4588
4616
|
switch (config.type) {
|
|
4589
4617
|
case 'min':
|
|
@@ -4619,7 +4647,8 @@ function convertBuiltInToCustomValidator(config) {
|
|
|
4619
4647
|
* Comprehensive form configuration validator that checks:
|
|
4620
4648
|
* 1. Form mode consistency (paged vs non-paged)
|
|
4621
4649
|
* 2. Page nesting rules
|
|
4622
|
-
* 3.
|
|
4650
|
+
* 3. Row nesting rules (no hidden fields in rows)
|
|
4651
|
+
* 4. Field placement constraints
|
|
4623
4652
|
*/
|
|
4624
4653
|
class FormModeValidator {
|
|
4625
4654
|
/**
|
|
@@ -4659,11 +4688,8 @@ class FormModeValidator {
|
|
|
4659
4688
|
static validateFormConfigurationOrThrow(fields) {
|
|
4660
4689
|
const result = this.validateFormConfiguration(fields);
|
|
4661
4690
|
if (!result.isValid) {
|
|
4662
|
-
const errorMessage = [
|
|
4663
|
-
|
|
4664
|
-
...result.errors.map((error) => ` - ${error}`),
|
|
4665
|
-
].join('\n');
|
|
4666
|
-
throw new Error(errorMessage);
|
|
4691
|
+
const errorMessage = [`Invalid form configuration (${result.mode} mode):`, ...result.errors.map((error) => ` - ${error}`)].join('\n');
|
|
4692
|
+
throw new DynamicFormError(errorMessage);
|
|
4667
4693
|
}
|
|
4668
4694
|
}
|
|
4669
4695
|
/**
|
|
@@ -4689,6 +4715,47 @@ class FormModeValidator {
|
|
|
4689
4715
|
}
|
|
4690
4716
|
}
|
|
4691
4717
|
}
|
|
4718
|
+
// Warn about hidden fields in rows (they work but don't render, which may be confusing)
|
|
4719
|
+
const rowWarnings = this.collectRowHiddenFieldWarnings(fields);
|
|
4720
|
+
warnings.push(...rowWarnings);
|
|
4721
|
+
return warnings;
|
|
4722
|
+
}
|
|
4723
|
+
/**
|
|
4724
|
+
* Recursively collects warnings for hidden fields inside rows
|
|
4725
|
+
* @param fields The form field definitions to check
|
|
4726
|
+
* @param path Current path for warning messages
|
|
4727
|
+
* @returns Array of warning messages
|
|
4728
|
+
*/
|
|
4729
|
+
static collectRowHiddenFieldWarnings(fields, path = '') {
|
|
4730
|
+
if (!fields) {
|
|
4731
|
+
return [];
|
|
4732
|
+
}
|
|
4733
|
+
const warnings = [];
|
|
4734
|
+
for (let i = 0; i < fields.length; i++) {
|
|
4735
|
+
const field = fields[i];
|
|
4736
|
+
const fieldPath = path ? `${path}.fields[${i}]` : `fields[${i}]`;
|
|
4737
|
+
const fieldKey = field.key || 'unknown';
|
|
4738
|
+
if (isRowField(field)) {
|
|
4739
|
+
if (!validateRowNesting(field)) {
|
|
4740
|
+
warnings.push(`Row field at ${fieldPath} (key: "${fieldKey}") contains hidden fields. ` +
|
|
4741
|
+
`Hidden fields in rows don't render anything - consider placing them outside the row.`);
|
|
4742
|
+
}
|
|
4743
|
+
// Continue checking nested rows within the row's children
|
|
4744
|
+
warnings.push(...this.collectRowHiddenFieldWarnings(field.fields, fieldPath));
|
|
4745
|
+
}
|
|
4746
|
+
else if (isPageField(field)) {
|
|
4747
|
+
// Check rows within pages
|
|
4748
|
+
warnings.push(...this.collectRowHiddenFieldWarnings(field.fields, fieldPath));
|
|
4749
|
+
}
|
|
4750
|
+
else if (isGroupField(field)) {
|
|
4751
|
+
// Check rows within groups
|
|
4752
|
+
warnings.push(...this.collectRowHiddenFieldWarnings(field.fields, fieldPath));
|
|
4753
|
+
}
|
|
4754
|
+
else if (isArrayField(field)) {
|
|
4755
|
+
// Check rows within array templates
|
|
4756
|
+
warnings.push(...this.collectRowHiddenFieldWarnings(field.fields, fieldPath));
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4692
4759
|
return warnings;
|
|
4693
4760
|
}
|
|
4694
4761
|
}
|
|
@@ -4885,7 +4952,7 @@ class PageOrchestratorComponent {
|
|
|
4885
4952
|
eventBus = inject(EventBus);
|
|
4886
4953
|
fieldContextRegistry = inject(FieldContextRegistryService);
|
|
4887
4954
|
functionRegistry = inject(FunctionRegistryService);
|
|
4888
|
-
logger = inject(
|
|
4955
|
+
logger = inject(DynamicFormLogger);
|
|
4889
4956
|
/**
|
|
4890
4957
|
* Array of page field definitions to render
|
|
4891
4958
|
*/
|
|
@@ -4898,6 +4965,7 @@ class PageOrchestratorComponent {
|
|
|
4898
4965
|
/**
|
|
4899
4966
|
* Field signal context for child fields
|
|
4900
4967
|
*/
|
|
4968
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- FieldSignalContext is contravariant in TModel, using any allows accepting any form type
|
|
4901
4969
|
fieldSignalContext = input.required(...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : []));
|
|
4902
4970
|
/**
|
|
4903
4971
|
* Computed signal that tracks which pages are hidden.
|
|
@@ -4978,7 +5046,7 @@ class PageOrchestratorComponent {
|
|
|
4978
5046
|
const fieldKey = fieldDef.key;
|
|
4979
5047
|
if (!fieldKey)
|
|
4980
5048
|
continue;
|
|
4981
|
-
// Access the field from the form using
|
|
5049
|
+
// Access the field from the form using bracket notation
|
|
4982
5050
|
const field = form[fieldKey];
|
|
4983
5051
|
if (field && typeof field === 'function') {
|
|
4984
5052
|
const fieldState = field();
|
|
@@ -5143,7 +5211,7 @@ class PageOrchestratorComponent {
|
|
|
5143
5211
|
}
|
|
5144
5212
|
// Check each hidden logic - if ANY condition is true, the page is hidden
|
|
5145
5213
|
for (const logic of hiddenLogic) {
|
|
5146
|
-
// Handle static boolean conditions
|
|
5214
|
+
// Handle static boolean conditions (fast path)
|
|
5147
5215
|
if (typeof logic.condition === 'boolean') {
|
|
5148
5216
|
if (logic.condition) {
|
|
5149
5217
|
return true;
|
|
@@ -5159,8 +5227,8 @@ class PageOrchestratorComponent {
|
|
|
5159
5227
|
}
|
|
5160
5228
|
return false;
|
|
5161
5229
|
}
|
|
5162
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
5163
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
5230
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5231
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: PageOrchestratorComponent, isStandalone: true, selector: "div[page-orchestrator]", inputs: { pageFields: { classPropertyName: "pageFields", publicName: "pageFields", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, fieldSignalContext: { classPropertyName: "fieldSignalContext", publicName: "fieldSignalContext", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-current-page": "state().currentPageIndex", "attr.data-total-pages": "state().totalPages" }, classAttribute: "df-page-orchestrator" }, providers: [
|
|
5164
5232
|
{
|
|
5165
5233
|
provide: FIELD_SIGNAL_CONTEXT,
|
|
5166
5234
|
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
@@ -5190,9 +5258,9 @@ class PageOrchestratorComponent {
|
|
|
5190
5258
|
}
|
|
5191
5259
|
}
|
|
5192
5260
|
}
|
|
5193
|
-
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-
|
|
5261
|
+
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-rdGD8A9d.mjs').then(m => m.default)], () => [import('./ng-forge-dynamic-forms-page-field.component-rdGD8A9d.mjs').then(m => m.default)]] });
|
|
5194
5262
|
}
|
|
5195
|
-
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.0.
|
|
5263
|
+
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.0.8", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [import('./ng-forge-dynamic-forms-page-field.component-rdGD8A9d.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
5196
5264
|
type: Component,
|
|
5197
5265
|
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
5198
5266
|
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
@@ -5261,7 +5329,7 @@ class DynamicForm {
|
|
|
5261
5329
|
rootFormRegistry = inject(RootFormRegistryService);
|
|
5262
5330
|
functionRegistry = inject(FunctionRegistryService);
|
|
5263
5331
|
schemaRegistry = inject(SchemaRegistryService);
|
|
5264
|
-
logger = inject(
|
|
5332
|
+
logger = inject(DynamicFormLogger);
|
|
5265
5333
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
5266
5334
|
// Inputs
|
|
5267
5335
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -5629,13 +5697,13 @@ class DynamicForm {
|
|
|
5629
5697
|
}
|
|
5630
5698
|
onFormReset() {
|
|
5631
5699
|
const defaults = this.defaultValues();
|
|
5632
|
-
//
|
|
5700
|
+
// Type assertion is necessary: Angular Signal Forms expects exact form value type
|
|
5633
5701
|
this.form()().value.set(defaults);
|
|
5634
5702
|
this.value.set(defaults);
|
|
5635
5703
|
}
|
|
5636
5704
|
onFormClear() {
|
|
5637
5705
|
const emptyValue = {};
|
|
5638
|
-
//
|
|
5706
|
+
// Type assertion is necessary: Angular Signal Forms expects exact form value type
|
|
5639
5707
|
this.form()().value.set(emptyValue);
|
|
5640
5708
|
this.value.set(emptyValue);
|
|
5641
5709
|
}
|
|
@@ -5645,8 +5713,8 @@ class DynamicForm {
|
|
|
5645
5713
|
ngOnDestroy() {
|
|
5646
5714
|
this.rootFormRegistry.unregisterForm();
|
|
5647
5715
|
}
|
|
5648
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
5649
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
5716
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5717
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: DynamicForm, isStandalone: true, selector: "form[dynamic-form]", inputs: { config: { classPropertyName: "config", publicName: "dynamic-form", isSignal: true, isRequired: true, transformFunction: null }, formOptions: { classPropertyName: "formOptions", publicName: "formOptions", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted", reset: "reset", cleared: "cleared", events: "events", initialized: "initialized", onPageChange: "onPageChange", onPageNavigationStateChange: "onPageNavigationStateChange" }, host: { attributes: { "novalidate": "" }, listeners: { "submit": "onNativeSubmit($event)" }, properties: { "class.disabled": "disabled()", "class.df-form-paged": "formModeDetection().mode === \"paged\"", "class.df-form-non-paged": "formModeDetection().mode === \"non-paged\"", "attr.data-form-mode": "formModeDetection().mode" }, classAttribute: "df-dynamic-form df-form" }, providers: [EventBus, SchemaRegistryService, FunctionRegistryService, RootFormRegistryService, FieldContextRegistryService], ngImport: i0, template: `
|
|
5650
5718
|
@if (formModeDetection().mode === 'paged') {
|
|
5651
5719
|
<div page-orchestrator [pageFields]="pageFieldDefinitions()" [form]="form()" [fieldSignalContext]="fieldSignalContext()"></div>
|
|
5652
5720
|
} @else {
|
|
@@ -5656,7 +5724,7 @@ class DynamicForm {
|
|
|
5656
5724
|
}
|
|
5657
5725
|
`, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}:host>*{grid-column:1/-1}:host.df-form-spacing-custom{--df-grid-row-gap: var(--form-custom-spacing, 1.5rem)}:host.df-form-gap-none{--df-grid-row-gap: 0}:host.df-form-gap-xs{--df-grid-row-gap: .25rem}:host.df-form-gap-sm{--df-grid-row-gap: .5rem}:host.df-form-gap-md{--df-grid-row-gap: 1rem}:host.df-form-gap-lg{--df-grid-row-gap: 1.5rem}:host.df-form-gap-xl{--df-grid-row-gap: 2rem}:host.df-form-paged{--df-grid-row-gap: 0}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: PageOrchestratorComponent, selector: "div[page-orchestrator]", inputs: ["pageFields", "form", "fieldSignalContext"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5658
5726
|
}
|
|
5659
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
5727
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DynamicForm, decorators: [{
|
|
5660
5728
|
type: Component,
|
|
5661
5729
|
args: [{ selector: 'form[dynamic-form]', imports: [NgComponentOutlet, PageOrchestratorComponent], template: `
|
|
5662
5730
|
@if (formModeDetection().mode === 'paged') {
|
|
@@ -5668,6 +5736,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImpor
|
|
|
5668
5736
|
}
|
|
5669
5737
|
`, providers: [EventBus, SchemaRegistryService, FunctionRegistryService, RootFormRegistryService, FieldContextRegistryService], host: {
|
|
5670
5738
|
class: 'df-dynamic-form df-form',
|
|
5739
|
+
novalidate: '', // Disable browser validation - Angular Signal Forms handles validation
|
|
5671
5740
|
'[class.disabled]': 'disabled()',
|
|
5672
5741
|
'[class.df-form-paged]': 'formModeDetection().mode === "paged"',
|
|
5673
5742
|
'[class.df-form-non-paged]': 'formModeDetection().mode === "non-paged"',
|
|
@@ -5759,7 +5828,6 @@ function pageFieldMapper(fieldDef) {
|
|
|
5759
5828
|
function textFieldMapper(fieldDef) {
|
|
5760
5829
|
const fieldContextRegistry = inject(FieldContextRegistryService);
|
|
5761
5830
|
const functionRegistry = inject(FunctionRegistryService);
|
|
5762
|
-
const logger = inject(DYNAMIC_FORM_LOGGER);
|
|
5763
5831
|
// Build base inputs (static, from field definition)
|
|
5764
5832
|
const baseInputs = buildBaseInputs(fieldDef);
|
|
5765
5833
|
// Create computed signal for hidden state based on logic configuration
|
|
@@ -5814,6 +5882,13 @@ function textFieldMapper(fieldDef) {
|
|
|
5814
5882
|
* { type: 'page', key: 'step1', fields: [...] }
|
|
5815
5883
|
* ```
|
|
5816
5884
|
*/
|
|
5885
|
+
/**
|
|
5886
|
+
* Built-in field types provided by the dynamic form library.
|
|
5887
|
+
*
|
|
5888
|
+
* Each field type is validated at compile time using satisfies, ensuring
|
|
5889
|
+
* type safety of the mapper function while allowing the array to be typed
|
|
5890
|
+
* as FieldTypeDefinition[] for consumer flexibility.
|
|
5891
|
+
*/
|
|
5817
5892
|
const BUILT_IN_FIELDS = [
|
|
5818
5893
|
{
|
|
5819
5894
|
name: 'row',
|
|
@@ -5835,25 +5910,70 @@ const BUILT_IN_FIELDS = [
|
|
|
5835
5910
|
},
|
|
5836
5911
|
{
|
|
5837
5912
|
name: 'page',
|
|
5838
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-
|
|
5913
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-rdGD8A9d.mjs'),
|
|
5839
5914
|
mapper: pageFieldMapper,
|
|
5840
5915
|
valueHandling: 'flatten',
|
|
5841
5916
|
},
|
|
5842
5917
|
{
|
|
5843
5918
|
name: 'text',
|
|
5844
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-
|
|
5919
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-CDxka2KD.mjs'),
|
|
5845
5920
|
mapper: textFieldMapper,
|
|
5846
5921
|
valueHandling: 'exclude',
|
|
5847
5922
|
},
|
|
5923
|
+
{
|
|
5924
|
+
name: 'hidden',
|
|
5925
|
+
// Componentless field - no loadComponent or mapper needed
|
|
5926
|
+
valueHandling: 'include',
|
|
5927
|
+
},
|
|
5848
5928
|
];
|
|
5849
5929
|
|
|
5930
|
+
/**
|
|
5931
|
+
* Type guard to check if a value is a DynamicFormFeature
|
|
5932
|
+
*/
|
|
5933
|
+
function isDynamicFormFeature(value) {
|
|
5934
|
+
return (typeof value === 'object' &&
|
|
5935
|
+
value !== null &&
|
|
5936
|
+
'ɵkind' in value &&
|
|
5937
|
+
typeof value.ɵkind === 'string' &&
|
|
5938
|
+
'ɵproviders' in value &&
|
|
5939
|
+
Array.isArray(value.ɵproviders));
|
|
5940
|
+
}
|
|
5941
|
+
/**
|
|
5942
|
+
* Helper to create a feature with proper typing
|
|
5943
|
+
*/
|
|
5944
|
+
function createFeature(kind, providers) {
|
|
5945
|
+
return {
|
|
5946
|
+
ɵkind: kind,
|
|
5947
|
+
ɵproviders: providers,
|
|
5948
|
+
};
|
|
5949
|
+
}
|
|
5950
|
+
|
|
5951
|
+
/**
|
|
5952
|
+
* Console-based logger implementation.
|
|
5953
|
+
*/
|
|
5954
|
+
class ConsoleLogger {
|
|
5955
|
+
prefix = '[Dynamic Forms]';
|
|
5956
|
+
debug(message, ...args) {
|
|
5957
|
+
console.debug(this.prefix, message, ...args);
|
|
5958
|
+
}
|
|
5959
|
+
info(message, ...args) {
|
|
5960
|
+
console.info(this.prefix, message, ...args);
|
|
5961
|
+
}
|
|
5962
|
+
warn(message, ...args) {
|
|
5963
|
+
console.warn(this.prefix, message, ...args);
|
|
5964
|
+
}
|
|
5965
|
+
error(message, ...args) {
|
|
5966
|
+
console.error(this.prefix, message, ...args);
|
|
5967
|
+
}
|
|
5968
|
+
}
|
|
5969
|
+
|
|
5850
5970
|
/**
|
|
5851
5971
|
* Provider function to configure the dynamic form system with field types and options.
|
|
5852
5972
|
*
|
|
5853
5973
|
* This function creates environment providers that can be used at application or route level
|
|
5854
5974
|
* to register field types. It provides type-safe field registration with automatic type inference.
|
|
5855
5975
|
*
|
|
5856
|
-
* @param items - Field type definitions and/or features (like
|
|
5976
|
+
* @param items - Field type definitions and/or features (like withLoggerConfig)
|
|
5857
5977
|
* @returns Environment providers for dependency injection with type inference
|
|
5858
5978
|
*
|
|
5859
5979
|
* @example
|
|
@@ -5868,12 +5988,12 @@ const BUILT_IN_FIELDS = [
|
|
|
5868
5988
|
*
|
|
5869
5989
|
* @example
|
|
5870
5990
|
* ```typescript
|
|
5871
|
-
* //
|
|
5991
|
+
* // Disable logging
|
|
5872
5992
|
* bootstrapApplication(AppComponent, {
|
|
5873
5993
|
* providers: [
|
|
5874
5994
|
* provideDynamicForm(
|
|
5875
5995
|
* ...withMaterialFields(),
|
|
5876
|
-
*
|
|
5996
|
+
* withLoggerConfig(false)
|
|
5877
5997
|
* )
|
|
5878
5998
|
* ]
|
|
5879
5999
|
* });
|
|
@@ -5881,18 +6001,6 @@ const BUILT_IN_FIELDS = [
|
|
|
5881
6001
|
*
|
|
5882
6002
|
* @example
|
|
5883
6003
|
* ```typescript
|
|
5884
|
-
* // Route-level setup
|
|
5885
|
-
* const routes: Routes = [
|
|
5886
|
-
* {
|
|
5887
|
-
* path: 'form',
|
|
5888
|
-
* component: FormComponent,
|
|
5889
|
-
* providers: [provideDynamicForm(...withMaterialFields())]
|
|
5890
|
-
* }
|
|
5891
|
-
* ];
|
|
5892
|
-
* ```
|
|
5893
|
-
*
|
|
5894
|
-
* @example
|
|
5895
|
-
* ```typescript
|
|
5896
6004
|
* // Custom field types with type inference
|
|
5897
6005
|
* import { CustomFieldType, AnotherFieldType } from './custom-fields';
|
|
5898
6006
|
*
|
|
@@ -5922,14 +6030,20 @@ function provideDynamicForm(...items) {
|
|
|
5922
6030
|
});
|
|
5923
6031
|
// Extract providers from features
|
|
5924
6032
|
const featureProviders = [];
|
|
6033
|
+
const hasLoggerFeature = features.some((feature) => feature.ɵkind === 'logger');
|
|
5925
6034
|
features.forEach((feature) => {
|
|
5926
6035
|
featureProviders.push(...feature.ɵproviders);
|
|
5927
6036
|
});
|
|
6037
|
+
// Default logger provider (ConsoleLogger) if no logger feature was provided
|
|
6038
|
+
const defaultLoggerProvider = hasLoggerFeature ? [] : [{ provide: DynamicFormLogger, useValue: new ConsoleLogger() }];
|
|
5928
6039
|
return makeEnvironmentProviders([
|
|
6040
|
+
...defaultLoggerProvider,
|
|
6041
|
+
// Always provide default Signal Forms classes (ng-touched, ng-invalid, etc.)
|
|
6042
|
+
provideSignalFormsConfig({ classes: NG_STATUS_CLASSES }),
|
|
5929
6043
|
{
|
|
5930
6044
|
provide: FIELD_REGISTRY,
|
|
5931
6045
|
useFactory: () => {
|
|
5932
|
-
const logger = inject(
|
|
6046
|
+
const logger = inject(DynamicFormLogger);
|
|
5933
6047
|
const registry = new Map();
|
|
5934
6048
|
// Add custom field types
|
|
5935
6049
|
fields.forEach((fieldType) => {
|
|
@@ -5946,6 +6060,55 @@ function provideDynamicForm(...items) {
|
|
|
5946
6060
|
]);
|
|
5947
6061
|
}
|
|
5948
6062
|
|
|
6063
|
+
/**
|
|
6064
|
+
* No-operation logger implementation.
|
|
6065
|
+
* All methods are no-ops - used in production by default.
|
|
6066
|
+
*/
|
|
6067
|
+
class NoopLogger {
|
|
6068
|
+
debug() {
|
|
6069
|
+
// Intentionally empty
|
|
6070
|
+
}
|
|
6071
|
+
info() {
|
|
6072
|
+
// Intentionally empty
|
|
6073
|
+
}
|
|
6074
|
+
warn() {
|
|
6075
|
+
// Intentionally empty
|
|
6076
|
+
}
|
|
6077
|
+
error() {
|
|
6078
|
+
// Intentionally empty
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
6081
|
+
|
|
6082
|
+
/**
|
|
6083
|
+
* Configure logging for dynamic forms.
|
|
6084
|
+
*
|
|
6085
|
+
* By default, logging is enabled (ConsoleLogger). Use this feature to disable logging
|
|
6086
|
+
* or configure it conditionally.
|
|
6087
|
+
*
|
|
6088
|
+
* @example
|
|
6089
|
+
* ```typescript
|
|
6090
|
+
* // Disable logging
|
|
6091
|
+
* provideDynamicForm(
|
|
6092
|
+
* ...withMaterialFields(),
|
|
6093
|
+
* withLoggerConfig(false)
|
|
6094
|
+
* )
|
|
6095
|
+
*
|
|
6096
|
+
* // Conditional logging (e.g., only in dev mode)
|
|
6097
|
+
* provideDynamicForm(
|
|
6098
|
+
* ...withMaterialFields(),
|
|
6099
|
+
* withLoggerConfig(() => isDevMode())
|
|
6100
|
+
* )
|
|
6101
|
+
* ```
|
|
6102
|
+
*
|
|
6103
|
+
* @param enabled - Whether logging is enabled. Can be a boolean or a function that returns a boolean. Defaults to true.
|
|
6104
|
+
* @returns A DynamicFormFeature that configures logging
|
|
6105
|
+
*/
|
|
6106
|
+
function withLoggerConfig(enabled = true) {
|
|
6107
|
+
const isEnabled = typeof enabled === 'function' ? enabled() : enabled;
|
|
6108
|
+
const logger = isEnabled ? new ConsoleLogger() : new NoopLogger();
|
|
6109
|
+
return createFeature('logger', [{ provide: DynamicFormLogger, useValue: logger }]);
|
|
6110
|
+
}
|
|
6111
|
+
|
|
5949
6112
|
/**
|
|
5950
6113
|
* No-op logger for when no logger is provided.
|
|
5951
6114
|
* Used as fallback in button logic evaluation to avoid breaking downstream packages.
|
|
@@ -6222,6 +6385,51 @@ function dynamicTextToObservable(value, injector) {
|
|
|
6222
6385
|
return of(String(value));
|
|
6223
6386
|
}
|
|
6224
6387
|
|
|
6388
|
+
/**
|
|
6389
|
+
* Applies meta attributes to a DOM element and tracks which attributes were applied.
|
|
6390
|
+
*
|
|
6391
|
+
* This utility handles:
|
|
6392
|
+
* - Removing previously applied attributes that are no longer in meta
|
|
6393
|
+
* - Setting new attributes from meta
|
|
6394
|
+
* - Converting all values to strings via String()
|
|
6395
|
+
*
|
|
6396
|
+
* @param element - The DOM element to apply attributes to
|
|
6397
|
+
* @param meta - The meta object containing attributes to apply
|
|
6398
|
+
* @param previouslyApplied - Set of attribute names that were previously applied
|
|
6399
|
+
* @returns A new Set containing the attribute names that were applied
|
|
6400
|
+
*
|
|
6401
|
+
* @example
|
|
6402
|
+
* ```typescript
|
|
6403
|
+
* private appliedAttrs = new Set<string>();
|
|
6404
|
+
*
|
|
6405
|
+
* explicitEffect([this.meta], ([meta]) => {
|
|
6406
|
+
* const input = this.el.nativeElement.querySelector('input');
|
|
6407
|
+
* if (input) {
|
|
6408
|
+
* this.appliedAttrs = applyMetaToElement(input, meta, this.appliedAttrs);
|
|
6409
|
+
* }
|
|
6410
|
+
* });
|
|
6411
|
+
* ```
|
|
6412
|
+
*/
|
|
6413
|
+
function applyMetaToElement(element, meta, previouslyApplied) {
|
|
6414
|
+
const newApplied = new Set();
|
|
6415
|
+
// Remove old attributes no longer in meta
|
|
6416
|
+
for (const attr of previouslyApplied) {
|
|
6417
|
+
if (!meta || !(attr in meta) || meta[attr] === undefined) {
|
|
6418
|
+
element.removeAttribute(attr);
|
|
6419
|
+
}
|
|
6420
|
+
}
|
|
6421
|
+
if (!meta)
|
|
6422
|
+
return newApplied;
|
|
6423
|
+
// Apply new attributes
|
|
6424
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
6425
|
+
if (value === undefined || value === null)
|
|
6426
|
+
continue;
|
|
6427
|
+
element.setAttribute(key, String(value));
|
|
6428
|
+
newApplied.add(key);
|
|
6429
|
+
}
|
|
6430
|
+
return newApplied;
|
|
6431
|
+
}
|
|
6432
|
+
|
|
6225
6433
|
/**
|
|
6226
6434
|
* Pipe that handles dynamic text resolution with support for static strings,
|
|
6227
6435
|
* Observables, and Signals.
|
|
@@ -6262,10 +6470,10 @@ class DynamicTextPipe {
|
|
|
6262
6470
|
}
|
|
6263
6471
|
return of(value || '');
|
|
6264
6472
|
}
|
|
6265
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
6266
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.
|
|
6473
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DynamicTextPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
6474
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.8", ngImport: i0, type: DynamicTextPipe, isStandalone: true, name: "dynamicText" });
|
|
6267
6475
|
}
|
|
6268
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
6476
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DynamicTextPipe, decorators: [{
|
|
6269
6477
|
type: Pipe,
|
|
6270
6478
|
args: [{
|
|
6271
6479
|
name: 'dynamicText',
|
|
@@ -6311,7 +6519,7 @@ class RowFieldComponent {
|
|
|
6311
6519
|
fieldRegistry = injectFieldRegistry();
|
|
6312
6520
|
injector = inject(Injector);
|
|
6313
6521
|
eventBus = inject(EventBus);
|
|
6314
|
-
logger = inject(
|
|
6522
|
+
logger = inject(DynamicFormLogger);
|
|
6315
6523
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
6316
6524
|
// Inputs
|
|
6317
6525
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -6368,14 +6576,14 @@ class RowFieldComponent {
|
|
|
6368
6576
|
}
|
|
6369
6577
|
});
|
|
6370
6578
|
}
|
|
6371
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
6372
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
6579
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: RowFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6580
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: RowFieldComponent, isStandalone: true, selector: "div[row-field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "class.disabled": "disabled()", "id": "`${key()}`", "attr.data-testid": "key()" }, classAttribute: "df-field df-row" }, ngImport: i0, template: `
|
|
6373
6581
|
@for (field of resolvedFields(); track field.key) {
|
|
6374
6582
|
<ng-container *ngComponentOutlet="field.component; injector: field.injector; inputs: field.inputs()" />
|
|
6375
6583
|
}
|
|
6376
6584
|
`, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:flex;flex-wrap:wrap;gap:var(--df-grid-gap);align-items:flex-start;width:100%}:host ::ng-deep>*:not([class*=df-col-]){flex:0 0 auto}:host ::ng-deep>.df-col-1{flex:0 0 calc(8.3333333333% - var(--df-grid-gap) * .9166666667);max-width:calc(8.3333333333% - var(--df-grid-gap) * .9166666667)}:host ::ng-deep>.df-col-2{flex:0 0 calc(16.6666666667% - var(--df-grid-gap) * .8333333333);max-width:calc(16.6666666667% - var(--df-grid-gap) * .8333333333)}:host ::ng-deep>.df-col-3{flex:0 0 calc(25% - var(--df-grid-gap) * .75);max-width:calc(25% - var(--df-grid-gap) * .75)}:host ::ng-deep>.df-col-4{flex:0 0 calc(33.3333333333% - var(--df-grid-gap) * .6666666667);max-width:calc(33.3333333333% - var(--df-grid-gap) * .6666666667)}:host ::ng-deep>.df-col-5{flex:0 0 calc(41.6666666667% - var(--df-grid-gap) * .5833333333);max-width:calc(41.6666666667% - var(--df-grid-gap) * .5833333333)}:host ::ng-deep>.df-col-6{flex:0 0 calc(50% - var(--df-grid-gap) * .5);max-width:calc(50% - var(--df-grid-gap) * .5)}:host ::ng-deep>.df-col-7{flex:0 0 calc(58.3333333333% - var(--df-grid-gap) * .4166666667);max-width:calc(58.3333333333% - var(--df-grid-gap) * .4166666667)}:host ::ng-deep>.df-col-8{flex:0 0 calc(66.6666666667% - var(--df-grid-gap) * .3333333333);max-width:calc(66.6666666667% - var(--df-grid-gap) * .3333333333)}:host ::ng-deep>.df-col-9{flex:0 0 calc(75% - var(--df-grid-gap) * .25);max-width:calc(75% - var(--df-grid-gap) * .25)}:host ::ng-deep>.df-col-10{flex:0 0 calc(83.3333333333% - var(--df-grid-gap) * .1666666667);max-width:calc(83.3333333333% - var(--df-grid-gap) * .1666666667)}:host ::ng-deep>.df-col-11{flex:0 0 calc(91.6666666667% - var(--df-grid-gap) * .0833333333);max-width:calc(91.6666666667% - var(--df-grid-gap) * .0833333333)}:host ::ng-deep>.df-col-12{flex:0 0 calc(100% - var(--df-grid-gap) * 0);max-width:calc(100% - var(--df-grid-gap) * 0)}:host.df-gap-custom{--df-grid-gap: var(--row-custom-gap, 1rem)}@media(max-width:576px){:host.df-row-mobile-stack{flex-direction:column}:host.df-row-mobile-stack ::ng-deep>*{flex:0 0 100%!important;max-width:100%!important}}@media(min-width:577px)and (max-width:768px){:host.df-row-tablet-2col ::ng-deep>*{flex:0 0 calc(50% - var(--df-grid-gap) / 2);max-width:calc(50% - var(--df-grid-gap) / 2)}}@media(max-width:576px){:host{flex-direction:column}:host ::ng-deep>*{flex:0 0 100%!important;max-width:100%!important}:host.df-row-mobile-keep-cols{flex-direction:row}:host.df-row-mobile-keep-cols ::ng-deep>*:not([class*=df-col-]){flex:0 0 auto!important;max-width:none!important}}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6377
6585
|
}
|
|
6378
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
6586
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: RowFieldComponent, decorators: [{
|
|
6379
6587
|
type: Component,
|
|
6380
6588
|
args: [{ selector: 'div[row-field]', imports: [NgComponentOutlet], template: `
|
|
6381
6589
|
@for (field of resolvedFields(); track field.key) {
|
|
@@ -6411,7 +6619,7 @@ class GroupFieldComponent {
|
|
|
6411
6619
|
parentFieldSignalContext = inject(FIELD_SIGNAL_CONTEXT);
|
|
6412
6620
|
injector = inject(Injector);
|
|
6413
6621
|
eventBus = inject(EventBus);
|
|
6414
|
-
logger = inject(
|
|
6622
|
+
logger = inject(DynamicFormLogger);
|
|
6415
6623
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
6416
6624
|
// Memoized Functions
|
|
6417
6625
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -6490,7 +6698,7 @@ class GroupFieldComponent {
|
|
|
6490
6698
|
const groupKey = this.field().key;
|
|
6491
6699
|
const child = parentForm[groupKey];
|
|
6492
6700
|
if (!child) {
|
|
6493
|
-
throw new
|
|
6701
|
+
throw new DynamicFormError(`Group field "${groupKey}" not found in parent form. ` + `Ensure the parent form schema includes this group field.`);
|
|
6494
6702
|
}
|
|
6495
6703
|
return child;
|
|
6496
6704
|
}, ...(ngDevMode ? [{ debugName: "nestedFieldTree" }] : []));
|
|
@@ -6545,14 +6753,14 @@ class GroupFieldComponent {
|
|
|
6545
6753
|
}
|
|
6546
6754
|
});
|
|
6547
6755
|
}
|
|
6548
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
6549
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
6756
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: GroupFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6757
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: GroupFieldComponent, isStandalone: true, selector: "fieldset[group-field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted" }, host: { attributes: { "role": "group" }, properties: { "class.disabled": "disabled()", "id": "`${key()}`", "attr.data-testid": "key()" }, classAttribute: "df-field df-group" }, ngImport: i0, template: `
|
|
6550
6758
|
@for (field of resolvedFields(); track field.key) {
|
|
6551
6759
|
<ng-container *ngComponentOutlet="field.component; injector: field.injector; inputs: field.inputs()" />
|
|
6552
6760
|
}
|
|
6553
6761
|
`, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{border:none;margin:0;padding:0;min-width:0;display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}:host>*{grid-column:1/-1}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6554
6762
|
}
|
|
6555
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
6763
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: GroupFieldComponent, decorators: [{
|
|
6556
6764
|
type: Component,
|
|
6557
6765
|
args: [{ selector: 'fieldset[group-field]', imports: [NgComponentOutlet], template: `
|
|
6558
6766
|
@for (field of resolvedFields(); track field.key) {
|
|
@@ -6754,6 +6962,10 @@ function resolveArrayItem(options) {
|
|
|
6754
6962
|
if (destroyRef.destroyed) {
|
|
6755
6963
|
return undefined;
|
|
6756
6964
|
}
|
|
6965
|
+
// Componentless fields (e.g., hidden) return undefined - nothing to render
|
|
6966
|
+
if (!component) {
|
|
6967
|
+
return undefined;
|
|
6968
|
+
}
|
|
6757
6969
|
const itemId = generateArrayItemId();
|
|
6758
6970
|
const indexSignal = linkedSignal(() => {
|
|
6759
6971
|
const order = itemOrderSignal();
|
|
@@ -6769,16 +6981,19 @@ function resolveArrayItem(options) {
|
|
|
6769
6981
|
registry,
|
|
6770
6982
|
arrayField,
|
|
6771
6983
|
});
|
|
6984
|
+
// Array item templates should always have inputs (componentless fields are handled above)
|
|
6985
|
+
if (!inputs) {
|
|
6986
|
+
return undefined;
|
|
6987
|
+
}
|
|
6772
6988
|
return {
|
|
6773
6989
|
id: itemId,
|
|
6774
|
-
|
|
6775
|
-
component: component,
|
|
6990
|
+
component,
|
|
6776
6991
|
injector,
|
|
6777
6992
|
inputs,
|
|
6778
6993
|
};
|
|
6779
6994
|
}), catchError((error) => {
|
|
6780
6995
|
if (!destroyRef.destroyed) {
|
|
6781
|
-
const logger = parentInjector.get(
|
|
6996
|
+
const logger = parentInjector.get(DynamicFormLogger);
|
|
6782
6997
|
logger.error(`Failed to load component for field type '${template.type}' at index ${index} ` +
|
|
6783
6998
|
`within array '${arrayField.key}'. Ensure the field type is registered in your field registry.`, error);
|
|
6784
6999
|
}
|
|
@@ -6802,7 +7017,7 @@ class ArrayFieldComponent {
|
|
|
6802
7017
|
parentFieldSignalContext = inject(FIELD_SIGNAL_CONTEXT);
|
|
6803
7018
|
parentInjector = inject(Injector);
|
|
6804
7019
|
eventBus = inject(EventBus);
|
|
6805
|
-
logger = inject(
|
|
7020
|
+
logger = inject(DynamicFormLogger);
|
|
6806
7021
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
6807
7022
|
// Inputs
|
|
6808
7023
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -6978,14 +7193,14 @@ class ArrayFieldComponent {
|
|
|
6978
7193
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6979
7194
|
parentForm().value.set({ ...currentValue, [arrayKey]: newArray });
|
|
6980
7195
|
}
|
|
6981
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
6982
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
7196
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7197
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: ArrayFieldComponent, isStandalone: true, selector: "array-field", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "id": "`${key()}`", "attr.data-testid": "key()" }, classAttribute: "df-field df-array" }, ngImport: i0, template: `
|
|
6983
7198
|
@for (item of resolvedItems(); track item.id) {
|
|
6984
7199
|
<ng-container *ngComponentOutlet="item.component; injector: item.injector; inputs: item.inputs()" />
|
|
6985
7200
|
}
|
|
6986
7201
|
`, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}:host>*{grid-column:1/-1}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6987
7202
|
}
|
|
6988
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
7203
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ArrayFieldComponent, decorators: [{
|
|
6989
7204
|
type: Component,
|
|
6990
7205
|
args: [{ selector: 'array-field', imports: [NgComponentOutlet], template: `
|
|
6991
7206
|
@for (item of resolvedItems(); track item.id) {
|
|
@@ -7081,16 +7296,31 @@ function extractErrorParams(error) {
|
|
|
7081
7296
|
params.min = error.min;
|
|
7082
7297
|
if ('max' in error)
|
|
7083
7298
|
params.max = error.max;
|
|
7084
|
-
if ('requiredLength' in error)
|
|
7085
|
-
params.requiredLength = error.requiredLength;
|
|
7086
|
-
if ('actualLength' in error)
|
|
7087
|
-
params.actualLength = error.actualLength;
|
|
7088
7299
|
if ('pattern' in error)
|
|
7089
7300
|
params.pattern = error.pattern;
|
|
7090
7301
|
if ('actual' in error)
|
|
7091
7302
|
params.actual = error.actual;
|
|
7092
7303
|
if ('expected' in error)
|
|
7093
7304
|
params.expected = error.expected;
|
|
7305
|
+
// Handle length validation parameters
|
|
7306
|
+
// Angular Signal Forms uses 'minLength'/'maxLength' properties
|
|
7307
|
+
// Angular Classic Forms uses 'requiredLength'/'actualLength' properties
|
|
7308
|
+
// We support both formats and normalize to 'requiredLength' for message templates
|
|
7309
|
+
if ('requiredLength' in error) {
|
|
7310
|
+
params.requiredLength = error.requiredLength;
|
|
7311
|
+
}
|
|
7312
|
+
else if ('minLength' in error) {
|
|
7313
|
+
// Angular Signal Forms: minLength property maps to requiredLength for templates
|
|
7314
|
+
params.requiredLength = error.minLength;
|
|
7315
|
+
params.minLength = error.minLength;
|
|
7316
|
+
}
|
|
7317
|
+
else if ('maxLength' in error) {
|
|
7318
|
+
// Angular Signal Forms: maxLength property maps to requiredLength for templates
|
|
7319
|
+
params.requiredLength = error.maxLength;
|
|
7320
|
+
params.maxLength = error.maxLength;
|
|
7321
|
+
}
|
|
7322
|
+
if ('actualLength' in error)
|
|
7323
|
+
params.actualLength = error.actualLength;
|
|
7094
7324
|
// Include all custom properties from error object (for custom validators)
|
|
7095
7325
|
// Skip 'kind' as it's the error type identifier, not a parameter
|
|
7096
7326
|
Object.keys(error).forEach((key) => {
|
|
@@ -7132,5 +7362,5 @@ function extractErrorParams(error) {
|
|
|
7132
7362
|
* Generated bundle index. Do not edit.
|
|
7133
7363
|
*/
|
|
7134
7364
|
|
|
7135
|
-
export {
|
|
7136
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-
|
|
7365
|
+
export { BUILT_IN_FIELDS as $, AddArrayItemEvent as A, baseFieldMapper as B, ConsoleLogger as C, DynamicFormLogger as D, EventBus as E, FormClearEvent as F, buildBaseInputs as G, arrayFieldMapper as H, groupFieldMapper as I, pageFieldMapper as J, rowFieldMapper as K, textFieldMapper as L, FIELD_REGISTRY as M, NoopLogger as N, ARRAY_CONTEXT as O, PageChangeEvent as P, FIELD_SIGNAL_CONTEXT as Q, RemoveArrayItemEvent as R, SubmitEvent as S, dynamicTextToObservable as T, ArrayFieldComponent as U, GroupFieldComponent as V, RowFieldComponent as W, applyValidator as X, applyValidators as Y, getArrayLength as Z, resolveTokens as _, DynamicTextPipe as a, omit as a0, interpolateParams as a1, applyMetaToElement as a2, DynamicForm as b, createFieldResolutionPipe as c, derivedFromDeferred as d, emitComponentInitialized as e, isFormStateCondition as f, resolveNextButtonDisabled as g, isCheckedField as h, injectFieldRegistry as i, isValueField as j, isRowField as k, isArrayField as l, isContainerField as m, isDisplayOnlyField as n, isGroupField as o, provideDynamicForm as p, isLeafField as q, resolveSubmitButtonDisabled as r, isPageField as s, isValueBearingField as t, FormResetEvent as u, validatePageNesting as v, withLoggerConfig as w, NextPageEvent as x, PreviousPageEvent as y, DynamicFormError as z };
|
|
7366
|
+
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-CMSmISSp.mjs.map
|