@ng-forge/dynamic-forms 0.1.3 → 0.2.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.
Files changed (19) hide show
  1. package/fesm2022/ng-forge-dynamic-forms-integration.mjs +48 -4
  2. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
  3. package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-B_676NHl.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-CZBdtTAU.mjs} +302 -301
  4. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CZBdtTAU.mjs.map +1 -0
  5. package/fesm2022/{ng-forge-dynamic-forms-page-field.component-YHFQcpCS.mjs → ng-forge-dynamic-forms-page-field.component-BM32StxL.mjs} +6 -6
  6. package/fesm2022/ng-forge-dynamic-forms-page-field.component-BM32StxL.mjs.map +1 -0
  7. package/fesm2022/{ng-forge-dynamic-forms-testing-test-button.harness-BenmCFcM.mjs → ng-forge-dynamic-forms-testing-test-button.harness-CbctXjtc.mjs} +4 -4
  8. package/fesm2022/{ng-forge-dynamic-forms-testing-test-button.harness-BenmCFcM.mjs.map → ng-forge-dynamic-forms-testing-test-button.harness-CbctXjtc.mjs.map} +1 -1
  9. package/fesm2022/ng-forge-dynamic-forms-testing.mjs +32 -29
  10. package/fesm2022/ng-forge-dynamic-forms-testing.mjs.map +1 -1
  11. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-CiYq73aF.mjs → ng-forge-dynamic-forms-text-field.component-C0I8jf9T.mjs} +5 -5
  12. package/fesm2022/{ng-forge-dynamic-forms-text-field.component-CiYq73aF.mjs.map → ng-forge-dynamic-forms-text-field.component-C0I8jf9T.mjs.map} +1 -1
  13. package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
  14. package/package.json +4 -4
  15. package/types/ng-forge-dynamic-forms-integration.d.ts +14 -2
  16. package/types/ng-forge-dynamic-forms-testing.d.ts +35 -34
  17. package/types/ng-forge-dynamic-forms.d.ts +66 -89
  18. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-B_676NHl.mjs.map +0 -1
  19. package/fesm2022/ng-forge-dynamic-forms-page-field.component-YHFQcpCS.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { isSignal, computed, runInInjectionContext, InjectionToken, inject, isDevMode, signal, Injectable, untracked, input, linkedSignal, ChangeDetectionStrategy, Component, DestroyRef, Injector, model, afterNextRender, makeEnvironmentProviders, Pipe } from '@angular/core';
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
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';
5
5
  import { toObservable, toSignal, takeUntilDestroyed, outputFromObservable } from '@angular/core/rxjs-interop';
@@ -73,27 +73,12 @@ function derivedFromDeferred(source, operator, options) {
73
73
  * // Returns: ''
74
74
  * ```
75
75
  *
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
76
  * @public
93
77
  */
94
78
  function getGridClassString(fieldDef) {
95
- if (typeof fieldDef.col === 'number' && Number.isInteger(fieldDef.col) && fieldDef.col > 0 && fieldDef.col <= 12) {
96
- return `df-col-${fieldDef.col}`;
79
+ const col = fieldDef.col;
80
+ if (typeof col === 'number' && Number.isInteger(col) && col > 0 && col <= 12) {
81
+ return `df-col-${col}`;
97
82
  }
98
83
  return '';
99
84
  }
@@ -248,7 +233,7 @@ function createFieldResolutionPipe(getContext) {
248
233
  * @param action - The submission action function
249
234
  * @returns A wrapped function that always returns a Promise
250
235
  */
251
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
236
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Form tree type is dynamic, action signatures vary
252
237
  function wrapSubmissionAction(action) {
253
238
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
239
  return (formTree) => {
@@ -399,11 +384,11 @@ function memoize(fn, resolverOrOptions) {
399
384
  const cache = new Map();
400
385
  return ((...args) => {
401
386
  const key = resolver ? resolver(...args) : JSON.stringify(args);
402
- if (cache.has(key)) {
403
- const value = cache.get(key);
387
+ const cachedValue = cache.get(key);
388
+ if (cachedValue !== undefined) {
404
389
  cache.delete(key);
405
- cache.set(key, value);
406
- return value;
390
+ cache.set(key, cachedValue);
391
+ return cachedValue;
407
392
  }
408
393
  const result = fn(...args);
409
394
  if (cache.size >= maxSize) {
@@ -534,6 +519,21 @@ const FIELD_SIGNAL_CONTEXT = new InjectionToken('FIELD_SIGNAL_CONTEXT', {
534
519
  */
535
520
  const ARRAY_CONTEXT = new InjectionToken('ARRAY_CONTEXT');
536
521
 
522
+ /**
523
+ * Base error class for all Dynamic Forms errors.
524
+ *
525
+ * This class centralizes the `[Dynamic Forms]` prefix, ensuring consistent
526
+ * error messaging across the library without requiring each error site to
527
+ * manually include the prefix.
528
+ */
529
+ class DynamicFormError extends Error {
530
+ static PREFIX = '[Dynamic Forms]';
531
+ constructor(message) {
532
+ super(`${DynamicFormError.PREFIX} ${message}`);
533
+ this.name = 'DynamicFormError';
534
+ }
535
+ }
536
+
537
537
  /**
538
538
  * Injection token for the global field type registry.
539
539
  *
@@ -663,20 +663,21 @@ function injectFieldRegistry() {
663
663
  async loadTypeComponent(name) {
664
664
  const fieldType = registry.get(name);
665
665
  if (!fieldType) {
666
- throw new Error(`[Dynamic Forms] Field type "${name}" is not registered`);
666
+ throw new DynamicFormError(`Field type "${name}" is not registered`);
667
667
  }
668
668
  // Handle async loading
669
669
  if (fieldType.loadComponent) {
670
670
  try {
671
671
  const result = await fieldType.loadComponent();
672
672
  // Handle ES module imports - extract default export if needed
673
- return result?.default || result;
673
+ const moduleResult = result;
674
+ return (typeof moduleResult === 'object' && 'default' in moduleResult && moduleResult.default) || result;
674
675
  }
675
676
  catch (error) {
676
- throw new Error(`[Dynamic Forms] Failed to load component for field type "${name}": ${error}`);
677
+ throw new DynamicFormError(`Failed to load component for field type "${name}": ${error}`);
677
678
  }
678
679
  }
679
- throw new Error(`[Dynamic Forms] Field type "${name}" has no component or loadComponent function`);
680
+ throw new DynamicFormError(`Field type "${name}" has no component or loadComponent function`);
680
681
  },
681
682
  /**
682
683
  * Gets all registered field type definitions.
@@ -735,100 +736,13 @@ function injectFieldRegistry() {
735
736
  };
736
737
  }
737
738
 
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
739
  /**
814
740
  * Injection token for the dynamic forms logger.
815
741
  *
816
- * Default behavior:
817
- * - Development mode: ConsoleLogger with LogLevel.Warn
818
- * - Production mode: NoopLogger (silent)
819
- *
820
- * Override by providing a custom logger via withLogger() feature
821
- * or by providing directly to this token.
742
+ * Provided by provideDynamicForm() with ConsoleLogger as default.
743
+ * Override with withLoggerConfig(false) to disable logging.
822
744
  */
823
- const DYNAMIC_FORM_LOGGER = new InjectionToken('DYNAMIC_FORM_LOGGER', {
824
- providedIn: 'root',
825
- factory: () => {
826
- if (isDevMode()) {
827
- return new ConsoleLogger(LogLevel.Warn);
828
- }
829
- return new NoopLogger();
830
- },
831
- });
745
+ const DynamicFormLogger = new InjectionToken('DynamicFormLogger');
832
746
 
833
747
  /**
834
748
  * Registry service that maintains references to root form fields.
@@ -987,10 +901,10 @@ class RootFormRegistryService {
987
901
  this._formValueSignals.set(new Map());
988
902
  this.formContexts.clear();
989
903
  }
990
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: RootFormRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
991
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: RootFormRegistryService });
904
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RootFormRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
905
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RootFormRegistryService });
992
906
  }
993
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: RootFormRegistryService, decorators: [{
907
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RootFormRegistryService, decorators: [{
994
908
  type: Injectable
995
909
  }] });
996
910
 
@@ -1006,7 +920,7 @@ function isChildFieldContext(context) {
1006
920
  */
1007
921
  class FieldContextRegistryService {
1008
922
  rootFormRegistry = inject(RootFormRegistryService);
1009
- logger = inject(DYNAMIC_FORM_LOGGER);
923
+ logger = inject(DynamicFormLogger);
1010
924
  /**
1011
925
  * Creates an evaluation context for a field by combining:
1012
926
  * - The field's current value
@@ -1114,10 +1028,10 @@ class FieldContextRegistryService {
1114
1028
  logger: this.logger,
1115
1029
  };
1116
1030
  }
1117
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: FieldContextRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1118
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: FieldContextRegistryService });
1031
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FieldContextRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1032
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FieldContextRegistryService });
1119
1033
  }
1120
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: FieldContextRegistryService, decorators: [{
1034
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FieldContextRegistryService, decorators: [{
1121
1035
  type: Injectable
1122
1036
  }] });
1123
1037
 
@@ -1457,10 +1371,10 @@ class FunctionRegistryService {
1457
1371
  this.clearAsyncValidators();
1458
1372
  this.clearHttpValidators();
1459
1373
  }
1460
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: FunctionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1461
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: FunctionRegistryService });
1374
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FunctionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1375
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FunctionRegistryService });
1462
1376
  }
1463
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: FunctionRegistryService, decorators: [{
1377
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FunctionRegistryService, decorators: [{
1464
1378
  type: Injectable
1465
1379
  }] });
1466
1380
 
@@ -1607,7 +1521,7 @@ var TokenType;
1607
1521
  /**
1608
1522
  * Parser error with position information
1609
1523
  */
1610
- class ExpressionParserError extends Error {
1524
+ class ExpressionParserError extends DynamicFormError {
1611
1525
  position;
1612
1526
  expression;
1613
1527
  constructor(message, position, expression) {
@@ -1705,7 +1619,7 @@ class Tokenizer {
1705
1619
  this.position++;
1706
1620
  }
1707
1621
  }
1708
- throw new ExpressionParserError('[Dynamic Forms] Unterminated string literal', start, this.expression);
1622
+ throw new ExpressionParserError('Unterminated string literal', start, this.expression);
1709
1623
  }
1710
1624
  readNumber() {
1711
1625
  const start = this.position;
@@ -1810,7 +1724,7 @@ class Tokenizer {
1810
1724
  case ']':
1811
1725
  return { type: TokenType.RBRACKET, value: ']', position: start };
1812
1726
  default:
1813
- throw new ExpressionParserError(`[Dynamic Forms] Unexpected character: ${char}`, start, this.expression);
1727
+ throw new ExpressionParserError(`Unexpected character: ${char}`, start, this.expression);
1814
1728
  }
1815
1729
  }
1816
1730
  }
@@ -1834,7 +1748,7 @@ class Parser {
1834
1748
  this.tokens = tokenizer.tokenize();
1835
1749
  this.current = 0;
1836
1750
  if (this.isAtEnd()) {
1837
- throw new ExpressionParserError('[Dynamic Forms] Empty expression', 0, this.expression);
1751
+ throw new ExpressionParserError('Empty expression', 0, this.expression);
1838
1752
  }
1839
1753
  return this.parseExpression();
1840
1754
  }
@@ -1909,7 +1823,7 @@ class Parser {
1909
1823
  if (this.match(TokenType.DOT)) {
1910
1824
  // Member access: obj.property
1911
1825
  if (!this.check(TokenType.IDENTIFIER)) {
1912
- throw new ExpressionParserError('[Dynamic Forms] Expected property name after "."', this.peek().position, this.expression);
1826
+ throw new ExpressionParserError('Expected property name after "."', this.peek().position, this.expression);
1913
1827
  }
1914
1828
  const property = this.advance().value;
1915
1829
  node = { type: 'MemberAccess', object: node, property };
@@ -1962,7 +1876,7 @@ class Parser {
1962
1876
  this.consume(TokenType.RPAREN, 'Expected ")" after expression');
1963
1877
  return expr;
1964
1878
  }
1965
- throw new ExpressionParserError(`[Dynamic Forms] Unexpected token: ${this.peek().value}`, this.peek().position, this.expression);
1879
+ throw new ExpressionParserError(`Unexpected token: ${this.peek().value}`, this.peek().position, this.expression);
1966
1880
  }
1967
1881
  parseArgumentList() {
1968
1882
  const args = [];
@@ -2013,7 +1927,7 @@ class Parser {
2013
1927
  consume(type, message) {
2014
1928
  if (this.check(type))
2015
1929
  return this.advance();
2016
- throw new ExpressionParserError(`[Dynamic Forms] ${message}`, this.peek().position, this.expression);
1930
+ throw new ExpressionParserError(message, this.peek().position, this.expression);
2017
1931
  }
2018
1932
  }
2019
1933
 
@@ -2165,7 +2079,7 @@ class Evaluator {
2165
2079
  case 'ArrayLiteral':
2166
2080
  return this.evaluateArrayLiteral(node);
2167
2081
  default:
2168
- throw new ExpressionParserError(`[Dynamic Forms] Unknown node type: ${node.type}`, 0, this.expression);
2082
+ throw new ExpressionParserError(`Unknown node type: ${node.type}`, 0, this.expression);
2169
2083
  }
2170
2084
  }
2171
2085
  evaluateIdentifier(name) {
@@ -2181,7 +2095,7 @@ class Evaluator {
2181
2095
  }
2182
2096
  // Block access to dangerous properties that could leak information or enable attacks
2183
2097
  if (BLOCKED_PROPERTIES.has(node.property)) {
2184
- throw new ExpressionParserError(`[Dynamic Forms] Property "${node.property}" is not accessible for security reasons`, 0, this.expression);
2098
+ throw new ExpressionParserError(`Property "${node.property}" is not accessible for security reasons`, 0, this.expression);
2185
2099
  }
2186
2100
  // Allow property access on plain objects and primitives
2187
2101
  if (typeof obj === 'object' || typeof obj === 'string' || typeof obj === 'number') {
@@ -2236,7 +2150,7 @@ class Evaluator {
2236
2150
  case '<=':
2237
2151
  return left <= right;
2238
2152
  default:
2239
- throw new ExpressionParserError(`[Dynamic Forms] Unknown binary operator: ${node.operator}`, 0, this.expression);
2153
+ throw new ExpressionParserError(`Unknown binary operator: ${node.operator}`, 0, this.expression);
2240
2154
  }
2241
2155
  }
2242
2156
  evaluateUnaryOp(node) {
@@ -2249,23 +2163,23 @@ class Evaluator {
2249
2163
  case '+':
2250
2164
  return +operand;
2251
2165
  default:
2252
- throw new ExpressionParserError(`[Dynamic Forms] Unknown unary operator: ${node.operator}`, 0, this.expression);
2166
+ throw new ExpressionParserError(`Unknown unary operator: ${node.operator}`, 0, this.expression);
2253
2167
  }
2254
2168
  }
2255
2169
  evaluateCallExpression(node) {
2256
2170
  // Only allow method calls (member access), not arbitrary function calls
2257
2171
  if (node.callee.type !== 'MemberAccess') {
2258
- throw new ExpressionParserError('[Dynamic Forms] Only method calls are allowed, not arbitrary function calls', 0, this.expression);
2172
+ throw new ExpressionParserError('Only method calls are allowed, not arbitrary function calls', 0, this.expression);
2259
2173
  }
2260
2174
  const obj = this.evaluate(node.callee.object);
2261
2175
  const methodName = node.callee.property;
2262
2176
  // Block access to dangerous properties (must check before method call)
2263
2177
  if (BLOCKED_PROPERTIES.has(methodName)) {
2264
- throw new ExpressionParserError(`[Dynamic Forms] Property "${methodName}" is not accessible for security reasons`, 0, this.expression);
2178
+ throw new ExpressionParserError(`Property "${methodName}" is not accessible for security reasons`, 0, this.expression);
2265
2179
  }
2266
2180
  // Check if the method is in the instance method whitelist
2267
2181
  if (!this.isMethodSafe(obj, methodName)) {
2268
- throw new ExpressionParserError(`[Dynamic Forms] Method "${methodName}" is not allowed for security reasons`, 0, this.expression);
2182
+ throw new ExpressionParserError(`Method "${methodName}" is not allowed for security reasons`, 0, this.expression);
2269
2183
  }
2270
2184
  // Evaluate arguments
2271
2185
  const args = node.arguments.map((arg) => this.evaluate(arg));
@@ -2383,7 +2297,7 @@ class ExpressionParser {
2383
2297
  throw error;
2384
2298
  }
2385
2299
  // Wrap other errors for consistency
2386
- throw new ExpressionParserError(`[Dynamic Forms] Error evaluating expression: ${error instanceof Error ? error.message : String(error)}`, 0, expression);
2300
+ throw new ExpressionParserError(`Error evaluating expression: ${error instanceof Error ? error.message : String(error)}`, 0, expression);
2387
2301
  }
2388
2302
  }
2389
2303
  /**
@@ -2478,6 +2392,56 @@ function evaluateOrCondition(expression, context) {
2478
2392
  return expression.conditions.some((condition) => evaluateCondition(condition, context));
2479
2393
  }
2480
2394
 
2395
+ /**
2396
+ * Cache for memoized logic functions, keyed by injection context.
2397
+ * Uses WeakMap with FunctionRegistryService as outer key to automatically clean up.
2398
+ * Inner structure: Map<FieldContextRegistryService, Map<expressionKey, LogicFn>>
2399
+ */
2400
+ const logicFunctionCache = new WeakMap();
2401
+ /**
2402
+ * Serializes a value to a deterministic string for cache key generation.
2403
+ * Unlike JSON.stringify, this sorts object keys to ensure consistent output
2404
+ * regardless of property insertion order.
2405
+ */
2406
+ function stableStringify(value) {
2407
+ if (value === null || value === undefined) {
2408
+ return String(value);
2409
+ }
2410
+ if (typeof value !== 'object') {
2411
+ return JSON.stringify(value);
2412
+ }
2413
+ if (Array.isArray(value)) {
2414
+ return '[' + value.map(stableStringify).join(',') + ']';
2415
+ }
2416
+ const obj = value;
2417
+ const sortedKeys = Object.keys(obj).sort();
2418
+ const pairs = sortedKeys.map((key) => JSON.stringify(key) + ':' + stableStringify(obj[key]));
2419
+ return '{' + pairs.join(',') + '}';
2420
+ }
2421
+ /**
2422
+ * Serializes a conditional expression to a deterministic string key for caching.
2423
+ * Uses stable stringification to ensure equivalent objects produce the same key
2424
+ * regardless of property order.
2425
+ */
2426
+ function serializeExpression(expression) {
2427
+ return stableStringify(expression);
2428
+ }
2429
+ /**
2430
+ * Gets or creates the cache map for a specific injection context.
2431
+ */
2432
+ function getContextCache(ctx) {
2433
+ let outerCache = logicFunctionCache.get(ctx.functionRegistry);
2434
+ if (!outerCache) {
2435
+ outerCache = new WeakMap();
2436
+ logicFunctionCache.set(ctx.functionRegistry, outerCache);
2437
+ }
2438
+ let innerCache = outerCache.get(ctx.fieldContextRegistry);
2439
+ if (!innerCache) {
2440
+ innerCache = new Map();
2441
+ outerCache.set(ctx.fieldContextRegistry, innerCache);
2442
+ }
2443
+ return innerCache;
2444
+ }
2481
2445
  /**
2482
2446
  * Create a logic function from a conditional expression.
2483
2447
  *
@@ -2489,17 +2453,32 @@ function evaluateOrCondition(expression, context) {
2489
2453
  * NOTE: For validators, use createEvaluationContext directly (with untracked) to prevent
2490
2454
  * infinite reactive loops. Validators with cross-field dependencies should be hoisted
2491
2455
  * to form-level using validateTree.
2456
+ *
2457
+ * @param expression The conditional expression to evaluate
2458
+ * @returns A LogicFn that evaluates the condition in the context of a field
2492
2459
  */
2493
2460
  function createLogicFunction(expression) {
2494
2461
  // Inject services during factory creation, not during execution
2495
2462
  const functionRegistry = inject(FunctionRegistryService);
2496
2463
  const fieldContextRegistry = inject(FieldContextRegistryService);
2497
- return (ctx) => {
2464
+ // Get cache for this injection context
2465
+ const contextCache = getContextCache({ functionRegistry, fieldContextRegistry });
2466
+ // Generate cache key from serialized expression
2467
+ const cacheKey = serializeExpression(expression);
2468
+ // Check cache first
2469
+ const cached = contextCache.get(cacheKey);
2470
+ if (cached) {
2471
+ return cached;
2472
+ }
2473
+ const fn = (ctx) => {
2498
2474
  // Create REACTIVE evaluation context for logic functions
2499
2475
  // This allows logic to re-evaluate when dependent fields change
2500
2476
  const evaluationContext = fieldContextRegistry.createReactiveEvaluationContext(ctx, functionRegistry.getCustomFunctions());
2501
2477
  return evaluateCondition(expression, evaluationContext);
2502
2478
  };
2479
+ // Cache the function
2480
+ contextCache.set(cacheKey, fn);
2481
+ return fn;
2503
2482
  }
2504
2483
 
2505
2484
  /**
@@ -2512,11 +2491,6 @@ const dynamicValueFunctionCache = new WeakMap();
2512
2491
  * Create a dynamic value function from an expression string.
2513
2492
  * Uses secure AST-based parsing instead of eval() or new Function().
2514
2493
  *
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
2494
  * @param expression The expression string to evaluate
2521
2495
  * @returns A LogicFn that evaluates the expression in the context of a field
2522
2496
  */
@@ -2524,7 +2498,7 @@ function createDynamicValueFunction(expression) {
2524
2498
  // Inject services during factory creation, not during execution
2525
2499
  // This captures the service instance in the closure
2526
2500
  const fieldContextRegistry = inject(FieldContextRegistryService);
2527
- const logger = inject(DYNAMIC_FORM_LOGGER);
2501
+ const logger = inject(DynamicFormLogger);
2528
2502
  // Get or create cache for this injection context
2529
2503
  let contextCache = dynamicValueFunctionCache.get(fieldContextRegistry);
2530
2504
  if (!contextCache) {
@@ -2891,7 +2865,7 @@ function applyCustomValidator(config, fieldPath) {
2891
2865
  validatorFn = createFunctionValidator(config);
2892
2866
  }
2893
2867
  else {
2894
- const logger = inject(DYNAMIC_FORM_LOGGER);
2868
+ const logger = inject(DynamicFormLogger);
2895
2869
  logger.warn('Custom validator must have either "expression" or "functionName"');
2896
2870
  return;
2897
2871
  }
@@ -2903,7 +2877,7 @@ function applyCustomValidator(config, fieldPath) {
2903
2877
  });
2904
2878
  }
2905
2879
  function createFunctionValidator(config) {
2906
- const logger = inject(DYNAMIC_FORM_LOGGER);
2880
+ const logger = inject(DynamicFormLogger);
2907
2881
  const functionName = config.functionName;
2908
2882
  if (!functionName) {
2909
2883
  logger.warn('Custom validator missing functionName');
@@ -2919,7 +2893,7 @@ function createFunctionValidator(config) {
2919
2893
  return (ctx) => validatorFn(ctx, config.params);
2920
2894
  }
2921
2895
  function createExpressionValidator(config) {
2922
- const logger = inject(DYNAMIC_FORM_LOGGER);
2896
+ const logger = inject(DynamicFormLogger);
2923
2897
  const expression = config.expression;
2924
2898
  if (!expression) {
2925
2899
  logger.warn('Custom validator missing expression');
@@ -2930,7 +2904,7 @@ function createExpressionValidator(config) {
2930
2904
  return (ctx) => {
2931
2905
  try {
2932
2906
  const evaluationContext = fieldContextRegistry.createEvaluationContext(ctx, functionRegistry.getCustomFunctions());
2933
- const result = ExpressionParser.evaluate(config.expression, evaluationContext);
2907
+ const result = ExpressionParser.evaluate(expression, evaluationContext);
2934
2908
  if (result) {
2935
2909
  return null;
2936
2910
  }
@@ -2966,7 +2940,7 @@ function createExpressionValidator(config) {
2966
2940
  * - onError: Optional handler for resource errors
2967
2941
  */
2968
2942
  function applyAsyncValidator(config, fieldPath) {
2969
- const logger = inject(DYNAMIC_FORM_LOGGER);
2943
+ const logger = inject(DynamicFormLogger);
2970
2944
  const registry = inject(FunctionRegistryService);
2971
2945
  const validatorConfig = registry.getAsyncValidator(config.functionName);
2972
2946
  if (!validatorConfig) {
@@ -2996,7 +2970,7 @@ function applyAsyncValidator(config, fieldPath) {
2996
2970
  * - onError: Optional handler for HTTP errors
2997
2971
  */
2998
2972
  function applyHttpValidator(config, fieldPath) {
2999
- const logger = inject(DYNAMIC_FORM_LOGGER);
2973
+ const logger = inject(DynamicFormLogger);
3000
2974
  const registry = inject(FunctionRegistryService);
3001
2975
  const httpValidatorConfig = registry.getHttpValidator(config.functionName);
3002
2976
  if (!httpValidatorConfig) {
@@ -3085,10 +3059,10 @@ class SchemaRegistryService {
3085
3059
  }
3086
3060
  return schema;
3087
3061
  }
3088
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3089
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: SchemaRegistryService });
3062
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3063
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaRegistryService });
3090
3064
  }
3091
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: SchemaRegistryService, decorators: [{
3065
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaRegistryService, decorators: [{
3092
3066
  type: Injectable
3093
3067
  }] });
3094
3068
 
@@ -3135,7 +3109,7 @@ function createTypePredicateFunction(predicate, logger) {
3135
3109
  * 3. The actual schema application happens at runtime via the schema function
3136
3110
  */
3137
3111
  function applySchema(config, fieldPath) {
3138
- const logger = inject(DYNAMIC_FORM_LOGGER);
3112
+ const logger = inject(DynamicFormLogger);
3139
3113
  const schemaRegistry = inject(SchemaRegistryService);
3140
3114
  const schema = schemaRegistry.resolveSchema(config.schema);
3141
3115
  if (!schema) {
@@ -3196,6 +3170,7 @@ function createSchemaFunction(schema) {
3196
3170
  /**
3197
3171
  * Type guard for GroupField with proper type narrowing
3198
3172
  */
3173
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type guard must accept any field type
3199
3174
  function isGroupField(field) {
3200
3175
  return field.type === 'group' && 'fields' in field;
3201
3176
  }
@@ -3203,6 +3178,7 @@ function isGroupField(field) {
3203
3178
  /**
3204
3179
  * Type guard for ArrayField with proper type narrowing
3205
3180
  */
3181
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type guard must accept any field type
3206
3182
  function isArrayField(field) {
3207
3183
  return field.type === 'array' && 'fields' in field;
3208
3184
  }
@@ -3210,6 +3186,7 @@ function isArrayField(field) {
3210
3186
  /**
3211
3187
  * Type guard for RowField with proper type narrowing
3212
3188
  */
3189
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type guard must accept any field type
3213
3190
  function isRowField(field) {
3214
3191
  return field.type === 'row' && 'fields' in field && Array.isArray(field.fields);
3215
3192
  }
@@ -3610,74 +3587,9 @@ function isDisplayOnlyField(field) {
3610
3587
  return field.type === 'text' || isContainerField(field);
3611
3588
  }
3612
3589
 
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
3590
  const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3679
3591
  function getFieldTreeByKey(ctx, key) {
3680
- return ctx.field[key];
3592
+ return ctx.fieldTree[key];
3681
3593
  }
3682
3594
  /**
3683
3595
  * Creates an Angular signal forms schema from field definitions
@@ -3697,7 +3609,7 @@ function createSchemaFromFields(fields, registry, crossFieldValidators) {
3697
3609
  // Inject services for cross-field validation
3698
3610
  // These will be available because createSchemaFromFields is called within runInInjectionContext
3699
3611
  const functionRegistry = inject(FunctionRegistryService);
3700
- const logger = inject(DYNAMIC_FORM_LOGGER);
3612
+ const logger = inject(DynamicFormLogger);
3701
3613
  return schema((path) => {
3702
3614
  for (const fieldDef of fields) {
3703
3615
  const valueHandling = getFieldValueHandling(fieldDef.type, registry);
@@ -3739,7 +3651,7 @@ function createSchemaFromFields(fields, registry, crossFieldValidators) {
3739
3651
  * This is the key integration point that routes cross-field validation errors
3740
3652
  * to the appropriate target fields via Angular's form state system.
3741
3653
  *
3742
- * The validateTree function allows returning errors with a `field` property
3654
+ * The validateTree function allows returning errors with a `fieldTree` property
3743
3655
  * that targets specific fields, which Angular automatically routes to those
3744
3656
  * fields' errors() signal.
3745
3657
  *
@@ -3765,7 +3677,7 @@ function applyCrossFieldTreeValidator(rootPath, validators, functionRegistry, lo
3765
3677
  for (const entry of validators) {
3766
3678
  const { sourceFieldKey, config } = entry;
3767
3679
  try {
3768
- const error = evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, customFunctions);
3680
+ const error = evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, customFunctions, logger);
3769
3681
  if (error) {
3770
3682
  errors.push(error);
3771
3683
  }
@@ -3778,7 +3690,7 @@ function applyCrossFieldTreeValidator(rootPath, validators, functionRegistry, lo
3778
3690
  const customConfig = config;
3779
3691
  errors.push({
3780
3692
  kind: customConfig.kind || config.type || 'custom',
3781
- field: targetField,
3693
+ fieldTree: targetField,
3782
3694
  });
3783
3695
  }
3784
3696
  }
@@ -3789,7 +3701,7 @@ function applyCrossFieldTreeValidator(rootPath, validators, functionRegistry, lo
3789
3701
  /**
3790
3702
  * Evaluates a single cross-field validator entry and returns an error if validation fails.
3791
3703
  */
3792
- function evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, customFunctions) {
3704
+ function evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, customFunctions, logger) {
3793
3705
  const { config } = entry;
3794
3706
  const fieldValue = formValue[sourceFieldKey];
3795
3707
  // Create evaluation context for condition/expression evaluation
@@ -3798,6 +3710,7 @@ function evaluateCrossFieldValidator(entry, formValue, sourceFieldKey, ctx, cust
3798
3710
  formValue,
3799
3711
  fieldPath: sourceFieldKey,
3800
3712
  customFunctions,
3713
+ logger,
3801
3714
  };
3802
3715
  // Check if this is a custom validator (with expression) or a built-in validator (with when condition)
3803
3716
  if (config.type === 'custom') {
@@ -3815,7 +3728,7 @@ function evaluateCustomCrossFieldValidator(config, evaluationContext, sourceFiel
3815
3728
  if (!config.expression) {
3816
3729
  return null;
3817
3730
  }
3818
- const { fieldValue, formValue } = evaluationContext;
3731
+ const { fieldValue, formValue, logger, customFunctions } = evaluationContext;
3819
3732
  // First, evaluate the when condition if present
3820
3733
  // If the condition is false, the validator doesn't apply (validation passes)
3821
3734
  if (config.when) {
@@ -3823,8 +3736,8 @@ function evaluateCustomCrossFieldValidator(config, evaluationContext, sourceFiel
3823
3736
  fieldValue,
3824
3737
  formValue,
3825
3738
  fieldPath: sourceFieldKey,
3826
- customFunctions: evaluationContext.customFunctions || {},
3827
- logger: evaluationContext.logger,
3739
+ customFunctions: customFunctions || {},
3740
+ logger,
3828
3741
  });
3829
3742
  if (!conditionMet) {
3830
3743
  return null; // Condition not met, skip validation
@@ -3843,7 +3756,7 @@ function evaluateCustomCrossFieldValidator(config, evaluationContext, sourceFiel
3843
3756
  }
3844
3757
  const errorObj = {
3845
3758
  kind: config.kind || 'custom',
3846
- field: targetField,
3759
+ fieldTree: targetField,
3847
3760
  };
3848
3761
  // Evaluate and include errorParams for message interpolation
3849
3762
  if (config.errorParams) {
@@ -3863,7 +3776,7 @@ function evaluateCustomCrossFieldValidator(config, evaluationContext, sourceFiel
3863
3776
  * First checks the when condition, then applies the built-in validation logic.
3864
3777
  */
3865
3778
  function evaluateBuiltInCrossFieldValidator(config, evaluationContext, sourceFieldKey, ctx) {
3866
- const { fieldValue, formValue } = evaluationContext;
3779
+ const { fieldValue, formValue, logger, customFunctions } = evaluationContext;
3867
3780
  // First, evaluate the when condition
3868
3781
  // If the condition is false, the validator doesn't apply (validation passes)
3869
3782
  if (config.when) {
@@ -3871,8 +3784,8 @@ function evaluateBuiltInCrossFieldValidator(config, evaluationContext, sourceFie
3871
3784
  fieldValue,
3872
3785
  formValue,
3873
3786
  fieldPath: sourceFieldKey,
3874
- customFunctions: evaluationContext.customFunctions || {},
3875
- logger: evaluationContext.logger,
3787
+ customFunctions: customFunctions || {},
3788
+ logger,
3876
3789
  });
3877
3790
  if (!conditionMet) {
3878
3791
  return null; // Condition not met, skip validation
@@ -3890,7 +3803,7 @@ function evaluateBuiltInCrossFieldValidator(config, evaluationContext, sourceFie
3890
3803
  }
3891
3804
  return {
3892
3805
  kind: config.type,
3893
- field: targetField,
3806
+ fieldTree: targetField,
3894
3807
  };
3895
3808
  }
3896
3809
  /**
@@ -4038,6 +3951,7 @@ class EventBus {
4038
3951
  * eventBus.dispatch(CustomFormEvent);
4039
3952
  * ```
4040
3953
  */
3954
+ // TypeScript limitation: Must use ConstructorParameters which relies on `any` in FormEventConstructor
4041
3955
  dispatch(eventConstructor, ...args) {
4042
3956
  this.pipeline$.next(new eventConstructor(...args));
4043
3957
  }
@@ -4079,10 +3993,10 @@ class EventBus {
4079
3993
  }
4080
3994
  return this.pipeline$.pipe(filter((event) => event.type === eventType));
4081
3995
  }
4082
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: EventBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4083
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: EventBus });
3996
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: EventBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3997
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: EventBus });
4084
3998
  }
4085
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: EventBus, decorators: [{
3999
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: EventBus, decorators: [{
4086
4000
  type: Injectable
4087
4001
  }] });
4088
4002
 
@@ -4126,7 +4040,7 @@ class ComponentInitializedEvent {
4126
4040
  * ```
4127
4041
  */
4128
4042
  function createInitializationTracker(eventBus, expectedCount) {
4129
- return eventBus.on('component-initialized').pipe(scan$1((count, _event) => count + 1, 0), map$1((currentCount) => currentCount >= expectedCount), filter$1((isComplete) => isComplete));
4043
+ return eventBus.on('component-initialized').pipe(scan$1((count) => count + 1, 0), map$1((currentCount) => currentCount >= expectedCount), filter$1((isComplete) => isComplete));
4130
4044
  }
4131
4045
  /**
4132
4046
  * Creates an observable that tracks component initialization progress with detailed status.
@@ -4151,7 +4065,7 @@ function createInitializationTracker(eventBus, expectedCount) {
4151
4065
  * ```
4152
4066
  */
4153
4067
  function createDetailedInitializationTracker(eventBus, expectedCount) {
4154
- return eventBus.on('component-initialized').pipe(scan$1((count, _event) => count + 1, 0), map$1((currentCount) => ({
4068
+ return eventBus.on('component-initialized').pipe(scan$1((count) => count + 1, 0), map$1((currentCount) => ({
4155
4069
  currentCount,
4156
4070
  expectedCount,
4157
4071
  isComplete: currentCount >= expectedCount,
@@ -4584,6 +4498,9 @@ function tryCreateSchemaEntry(fieldKey, config) {
4584
4498
  /** Converts a built-in validator with cross-field expression to a custom validator. */
4585
4499
  function convertBuiltInToCustomValidator(config) {
4586
4500
  const expression = config.expression;
4501
+ if (!expression) {
4502
+ throw new Error(`Built-in validator ${config.type} missing required expression for cross-field conversion`);
4503
+ }
4587
4504
  let validationExpression;
4588
4505
  switch (config.type) {
4589
4506
  case 'min':
@@ -4659,11 +4576,8 @@ class FormModeValidator {
4659
4576
  static validateFormConfigurationOrThrow(fields) {
4660
4577
  const result = this.validateFormConfiguration(fields);
4661
4578
  if (!result.isValid) {
4662
- const errorMessage = [
4663
- `[Dynamic Forms] Invalid form configuration (${result.mode} mode):`,
4664
- ...result.errors.map((error) => ` - ${error}`),
4665
- ].join('\n');
4666
- throw new Error(errorMessage);
4579
+ const errorMessage = [`Invalid form configuration (${result.mode} mode):`, ...result.errors.map((error) => ` - ${error}`)].join('\n');
4580
+ throw new DynamicFormError(errorMessage);
4667
4581
  }
4668
4582
  }
4669
4583
  /**
@@ -4885,7 +4799,7 @@ class PageOrchestratorComponent {
4885
4799
  eventBus = inject(EventBus);
4886
4800
  fieldContextRegistry = inject(FieldContextRegistryService);
4887
4801
  functionRegistry = inject(FunctionRegistryService);
4888
- logger = inject(DYNAMIC_FORM_LOGGER);
4802
+ logger = inject(DynamicFormLogger);
4889
4803
  /**
4890
4804
  * Array of page field definitions to render
4891
4805
  */
@@ -4898,6 +4812,7 @@ class PageOrchestratorComponent {
4898
4812
  /**
4899
4813
  * Field signal context for child fields
4900
4814
  */
4815
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- FieldSignalContext is contravariant in TModel, using any allows accepting any form type
4901
4816
  fieldSignalContext = input.required(...(ngDevMode ? [{ debugName: "fieldSignalContext" }] : []));
4902
4817
  /**
4903
4818
  * Computed signal that tracks which pages are hidden.
@@ -4978,7 +4893,7 @@ class PageOrchestratorComponent {
4978
4893
  const fieldKey = fieldDef.key;
4979
4894
  if (!fieldKey)
4980
4895
  continue;
4981
- // Access the field from the form using dynamic key access
4896
+ // Access the field from the form using bracket notation
4982
4897
  const field = form[fieldKey];
4983
4898
  if (field && typeof field === 'function') {
4984
4899
  const fieldState = field();
@@ -5143,7 +5058,7 @@ class PageOrchestratorComponent {
5143
5058
  }
5144
5059
  // Check each hidden logic - if ANY condition is true, the page is hidden
5145
5060
  for (const logic of hiddenLogic) {
5146
- // Handle static boolean conditions
5061
+ // Handle static boolean conditions (fast path)
5147
5062
  if (typeof logic.condition === 'boolean') {
5148
5063
  if (logic.condition) {
5149
5064
  return true;
@@ -5159,8 +5074,8 @@ class PageOrchestratorComponent {
5159
5074
  }
5160
5075
  return false;
5161
5076
  }
5162
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5163
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.5", 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: [
5077
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5078
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", 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
5079
  {
5165
5080
  provide: FIELD_SIGNAL_CONTEXT,
5166
5081
  useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
@@ -5190,9 +5105,9 @@ class PageOrchestratorComponent {
5190
5105
  }
5191
5106
  }
5192
5107
  }
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-YHFQcpCS.mjs').then(m => m.default)], () => [import('./ng-forge-dynamic-forms-page-field.component-YHFQcpCS.mjs').then(m => m.default)]] });
5108
+ `, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-BM32StxL.mjs').then(m => m.default)], () => [import('./ng-forge-dynamic-forms-page-field.component-BM32StxL.mjs').then(m => m.default)]] });
5194
5109
  }
5195
- i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.0.5", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [import('./ng-forge-dynamic-forms-page-field.component-YHFQcpCS.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
5110
+ i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.0.6", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [import('./ng-forge-dynamic-forms-page-field.component-BM32StxL.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
5196
5111
  type: Component,
5197
5112
  args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
5198
5113
  @for (pageField of pageFields(); track pageField.key; let i = $index) {
@@ -5261,7 +5176,7 @@ class DynamicForm {
5261
5176
  rootFormRegistry = inject(RootFormRegistryService);
5262
5177
  functionRegistry = inject(FunctionRegistryService);
5263
5178
  schemaRegistry = inject(SchemaRegistryService);
5264
- logger = inject(DYNAMIC_FORM_LOGGER);
5179
+ logger = inject(DynamicFormLogger);
5265
5180
  // ─────────────────────────────────────────────────────────────────────────────
5266
5181
  // Inputs
5267
5182
  // ─────────────────────────────────────────────────────────────────────────────
@@ -5629,13 +5544,13 @@ class DynamicForm {
5629
5544
  }
5630
5545
  onFormReset() {
5631
5546
  const defaults = this.defaultValues();
5632
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5547
+ // Type assertion is necessary: Angular Signal Forms expects exact form value type
5633
5548
  this.form()().value.set(defaults);
5634
5549
  this.value.set(defaults);
5635
5550
  }
5636
5551
  onFormClear() {
5637
5552
  const emptyValue = {};
5638
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5553
+ // Type assertion is necessary: Angular Signal Forms expects exact form value type
5639
5554
  this.form()().value.set(emptyValue);
5640
5555
  this.value.set(emptyValue);
5641
5556
  }
@@ -5645,8 +5560,8 @@ class DynamicForm {
5645
5560
  ngOnDestroy() {
5646
5561
  this.rootFormRegistry.unregisterForm();
5647
5562
  }
5648
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
5649
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.5", 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: { 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: `
5563
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
5564
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", 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: { 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
5565
  @if (formModeDetection().mode === 'paged') {
5651
5566
  <div page-orchestrator [pageFields]="pageFieldDefinitions()" [form]="form()" [fieldSignalContext]="fieldSignalContext()"></div>
5652
5567
  } @else {
@@ -5656,7 +5571,7 @@ class DynamicForm {
5656
5571
  }
5657
5572
  `, 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
5573
  }
5659
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: DynamicForm, decorators: [{
5574
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DynamicForm, decorators: [{
5660
5575
  type: Component,
5661
5576
  args: [{ selector: 'form[dynamic-form]', imports: [NgComponentOutlet, PageOrchestratorComponent], template: `
5662
5577
  @if (formModeDetection().mode === 'paged') {
@@ -5759,7 +5674,6 @@ function pageFieldMapper(fieldDef) {
5759
5674
  function textFieldMapper(fieldDef) {
5760
5675
  const fieldContextRegistry = inject(FieldContextRegistryService);
5761
5676
  const functionRegistry = inject(FunctionRegistryService);
5762
- const logger = inject(DYNAMIC_FORM_LOGGER);
5763
5677
  // Build base inputs (static, from field definition)
5764
5678
  const baseInputs = buildBaseInputs(fieldDef);
5765
5679
  // Create computed signal for hidden state based on logic configuration
@@ -5814,6 +5728,13 @@ function textFieldMapper(fieldDef) {
5814
5728
  * { type: 'page', key: 'step1', fields: [...] }
5815
5729
  * ```
5816
5730
  */
5731
+ /**
5732
+ * Built-in field types provided by the dynamic form library.
5733
+ *
5734
+ * Each field type is validated at compile time using satisfies, ensuring
5735
+ * type safety of the mapper function while allowing the array to be typed
5736
+ * as FieldTypeDefinition[] for consumer flexibility.
5737
+ */
5817
5738
  const BUILT_IN_FIELDS = [
5818
5739
  {
5819
5740
  name: 'row',
@@ -5835,25 +5756,65 @@ const BUILT_IN_FIELDS = [
5835
5756
  },
5836
5757
  {
5837
5758
  name: 'page',
5838
- loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-YHFQcpCS.mjs'),
5759
+ loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-BM32StxL.mjs'),
5839
5760
  mapper: pageFieldMapper,
5840
5761
  valueHandling: 'flatten',
5841
5762
  },
5842
5763
  {
5843
5764
  name: 'text',
5844
- loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-CiYq73aF.mjs'),
5765
+ loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-C0I8jf9T.mjs'),
5845
5766
  mapper: textFieldMapper,
5846
5767
  valueHandling: 'exclude',
5847
5768
  },
5848
5769
  ];
5849
5770
 
5771
+ /**
5772
+ * Type guard to check if a value is a DynamicFormFeature
5773
+ */
5774
+ function isDynamicFormFeature(value) {
5775
+ return (typeof value === 'object' &&
5776
+ value !== null &&
5777
+ 'ɵkind' in value &&
5778
+ typeof value.ɵkind === 'string' &&
5779
+ 'ɵproviders' in value &&
5780
+ Array.isArray(value.ɵproviders));
5781
+ }
5782
+ /**
5783
+ * Helper to create a feature with proper typing
5784
+ */
5785
+ function createFeature(kind, providers) {
5786
+ return {
5787
+ ɵkind: kind,
5788
+ ɵproviders: providers,
5789
+ };
5790
+ }
5791
+
5792
+ /**
5793
+ * Console-based logger implementation.
5794
+ */
5795
+ class ConsoleLogger {
5796
+ prefix = '[Dynamic Forms]';
5797
+ debug(message, ...args) {
5798
+ console.debug(this.prefix, message, ...args);
5799
+ }
5800
+ info(message, ...args) {
5801
+ console.info(this.prefix, message, ...args);
5802
+ }
5803
+ warn(message, ...args) {
5804
+ console.warn(this.prefix, message, ...args);
5805
+ }
5806
+ error(message, ...args) {
5807
+ console.error(this.prefix, message, ...args);
5808
+ }
5809
+ }
5810
+
5850
5811
  /**
5851
5812
  * Provider function to configure the dynamic form system with field types and options.
5852
5813
  *
5853
5814
  * This function creates environment providers that can be used at application or route level
5854
5815
  * to register field types. It provides type-safe field registration with automatic type inference.
5855
5816
  *
5856
- * @param items - Field type definitions and/or features (like withLogger)
5817
+ * @param items - Field type definitions and/or features (like withLoggerConfig)
5857
5818
  * @returns Environment providers for dependency injection with type inference
5858
5819
  *
5859
5820
  * @example
@@ -5868,12 +5829,12 @@ const BUILT_IN_FIELDS = [
5868
5829
  *
5869
5830
  * @example
5870
5831
  * ```typescript
5871
- * // With logging configuration
5832
+ * // Disable logging
5872
5833
  * bootstrapApplication(AppComponent, {
5873
5834
  * providers: [
5874
5835
  * provideDynamicForm(
5875
5836
  * ...withMaterialFields(),
5876
- * withLogger({ level: LogLevel.Debug })
5837
+ * withLoggerConfig(false)
5877
5838
  * )
5878
5839
  * ]
5879
5840
  * });
@@ -5881,18 +5842,6 @@ const BUILT_IN_FIELDS = [
5881
5842
  *
5882
5843
  * @example
5883
5844
  * ```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
5845
  * // Custom field types with type inference
5897
5846
  * import { CustomFieldType, AnotherFieldType } from './custom-fields';
5898
5847
  *
@@ -5922,14 +5871,18 @@ function provideDynamicForm(...items) {
5922
5871
  });
5923
5872
  // Extract providers from features
5924
5873
  const featureProviders = [];
5874
+ const hasLoggerFeature = features.some((feature) => feature.ɵkind === 'logger');
5925
5875
  features.forEach((feature) => {
5926
5876
  featureProviders.push(...feature.ɵproviders);
5927
5877
  });
5878
+ // Default logger provider (ConsoleLogger) if no logger feature was provided
5879
+ const defaultLoggerProvider = hasLoggerFeature ? [] : [{ provide: DynamicFormLogger, useValue: new ConsoleLogger() }];
5928
5880
  return makeEnvironmentProviders([
5881
+ ...defaultLoggerProvider,
5929
5882
  {
5930
5883
  provide: FIELD_REGISTRY,
5931
5884
  useFactory: () => {
5932
- const logger = inject(DYNAMIC_FORM_LOGGER);
5885
+ const logger = inject(DynamicFormLogger);
5933
5886
  const registry = new Map();
5934
5887
  // Add custom field types
5935
5888
  fields.forEach((fieldType) => {
@@ -5946,6 +5899,55 @@ function provideDynamicForm(...items) {
5946
5899
  ]);
5947
5900
  }
5948
5901
 
5902
+ /**
5903
+ * No-operation logger implementation.
5904
+ * All methods are no-ops - used in production by default.
5905
+ */
5906
+ class NoopLogger {
5907
+ debug() {
5908
+ // Intentionally empty
5909
+ }
5910
+ info() {
5911
+ // Intentionally empty
5912
+ }
5913
+ warn() {
5914
+ // Intentionally empty
5915
+ }
5916
+ error() {
5917
+ // Intentionally empty
5918
+ }
5919
+ }
5920
+
5921
+ /**
5922
+ * Configure logging for dynamic forms.
5923
+ *
5924
+ * By default, logging is enabled (ConsoleLogger). Use this feature to disable logging
5925
+ * or configure it conditionally.
5926
+ *
5927
+ * @example
5928
+ * ```typescript
5929
+ * // Disable logging
5930
+ * provideDynamicForm(
5931
+ * ...withMaterialFields(),
5932
+ * withLoggerConfig(false)
5933
+ * )
5934
+ *
5935
+ * // Conditional logging (e.g., only in dev mode)
5936
+ * provideDynamicForm(
5937
+ * ...withMaterialFields(),
5938
+ * withLoggerConfig(() => isDevMode())
5939
+ * )
5940
+ * ```
5941
+ *
5942
+ * @param enabled - Whether logging is enabled. Can be a boolean or a function that returns a boolean. Defaults to true.
5943
+ * @returns A DynamicFormFeature that configures logging
5944
+ */
5945
+ function withLoggerConfig(enabled = true) {
5946
+ const isEnabled = typeof enabled === 'function' ? enabled() : enabled;
5947
+ const logger = isEnabled ? new ConsoleLogger() : new NoopLogger();
5948
+ return createFeature('logger', [{ provide: DynamicFormLogger, useValue: logger }]);
5949
+ }
5950
+
5949
5951
  /**
5950
5952
  * No-op logger for when no logger is provided.
5951
5953
  * Used as fallback in button logic evaluation to avoid breaking downstream packages.
@@ -6262,10 +6264,10 @@ class DynamicTextPipe {
6262
6264
  }
6263
6265
  return of(value || '');
6264
6266
  }
6265
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: DynamicTextPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
6266
- static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.5", ngImport: i0, type: DynamicTextPipe, isStandalone: true, name: "dynamicText" });
6267
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DynamicTextPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
6268
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: DynamicTextPipe, isStandalone: true, name: "dynamicText" });
6267
6269
  }
6268
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: DynamicTextPipe, decorators: [{
6270
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DynamicTextPipe, decorators: [{
6269
6271
  type: Pipe,
6270
6272
  args: [{
6271
6273
  name: 'dynamicText',
@@ -6311,7 +6313,7 @@ class RowFieldComponent {
6311
6313
  fieldRegistry = injectFieldRegistry();
6312
6314
  injector = inject(Injector);
6313
6315
  eventBus = inject(EventBus);
6314
- logger = inject(DYNAMIC_FORM_LOGGER);
6316
+ logger = inject(DynamicFormLogger);
6315
6317
  // ─────────────────────────────────────────────────────────────────────────────
6316
6318
  // Inputs
6317
6319
  // ─────────────────────────────────────────────────────────────────────────────
@@ -6368,14 +6370,14 @@ class RowFieldComponent {
6368
6370
  }
6369
6371
  });
6370
6372
  }
6371
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: RowFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6372
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.5", 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
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RowFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6374
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", 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
6375
  @for (field of resolvedFields(); track field.key) {
6374
6376
  <ng-container *ngComponentOutlet="field.component; injector: field.injector; inputs: field.inputs()" />
6375
6377
  }
6376
6378
  `, 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
6379
  }
6378
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: RowFieldComponent, decorators: [{
6380
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RowFieldComponent, decorators: [{
6379
6381
  type: Component,
6380
6382
  args: [{ selector: 'div[row-field]', imports: [NgComponentOutlet], template: `
6381
6383
  @for (field of resolvedFields(); track field.key) {
@@ -6411,7 +6413,7 @@ class GroupFieldComponent {
6411
6413
  parentFieldSignalContext = inject(FIELD_SIGNAL_CONTEXT);
6412
6414
  injector = inject(Injector);
6413
6415
  eventBus = inject(EventBus);
6414
- logger = inject(DYNAMIC_FORM_LOGGER);
6416
+ logger = inject(DynamicFormLogger);
6415
6417
  // ─────────────────────────────────────────────────────────────────────────────
6416
6418
  // Memoized Functions
6417
6419
  // ─────────────────────────────────────────────────────────────────────────────
@@ -6490,7 +6492,7 @@ class GroupFieldComponent {
6490
6492
  const groupKey = this.field().key;
6491
6493
  const child = parentForm[groupKey];
6492
6494
  if (!child) {
6493
- throw new Error(`[Dynamic Forms] Group field "${groupKey}" not found in parent form. ` + `Ensure the parent form schema includes this group field.`);
6495
+ throw new DynamicFormError(`Group field "${groupKey}" not found in parent form. ` + `Ensure the parent form schema includes this group field.`);
6494
6496
  }
6495
6497
  return child;
6496
6498
  }, ...(ngDevMode ? [{ debugName: "nestedFieldTree" }] : []));
@@ -6545,14 +6547,14 @@ class GroupFieldComponent {
6545
6547
  }
6546
6548
  });
6547
6549
  }
6548
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: GroupFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6549
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.5", 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
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: GroupFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6551
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", 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
6552
  @for (field of resolvedFields(); track field.key) {
6551
6553
  <ng-container *ngComponentOutlet="field.component; injector: field.injector; inputs: field.inputs()" />
6552
6554
  }
6553
6555
  `, 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
6556
  }
6555
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: GroupFieldComponent, decorators: [{
6557
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: GroupFieldComponent, decorators: [{
6556
6558
  type: Component,
6557
6559
  args: [{ selector: 'fieldset[group-field]', imports: [NgComponentOutlet], template: `
6558
6560
  @for (field of resolvedFields(); track field.key) {
@@ -6771,14 +6773,13 @@ function resolveArrayItem(options) {
6771
6773
  });
6772
6774
  return {
6773
6775
  id: itemId,
6774
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
6775
6776
  component: component,
6776
6777
  injector,
6777
6778
  inputs,
6778
6779
  };
6779
6780
  }), catchError((error) => {
6780
6781
  if (!destroyRef.destroyed) {
6781
- const logger = parentInjector.get(DYNAMIC_FORM_LOGGER);
6782
+ const logger = parentInjector.get(DynamicFormLogger);
6782
6783
  logger.error(`Failed to load component for field type '${template.type}' at index ${index} ` +
6783
6784
  `within array '${arrayField.key}'. Ensure the field type is registered in your field registry.`, error);
6784
6785
  }
@@ -6802,7 +6803,7 @@ class ArrayFieldComponent {
6802
6803
  parentFieldSignalContext = inject(FIELD_SIGNAL_CONTEXT);
6803
6804
  parentInjector = inject(Injector);
6804
6805
  eventBus = inject(EventBus);
6805
- logger = inject(DYNAMIC_FORM_LOGGER);
6806
+ logger = inject(DynamicFormLogger);
6806
6807
  // ─────────────────────────────────────────────────────────────────────────────
6807
6808
  // Inputs
6808
6809
  // ─────────────────────────────────────────────────────────────────────────────
@@ -6978,14 +6979,14 @@ class ArrayFieldComponent {
6978
6979
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
6979
6980
  parentForm().value.set({ ...currentValue, [arrayKey]: newArray });
6980
6981
  }
6981
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6982
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.5", 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: `
6982
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6983
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", 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
6984
  @for (item of resolvedItems(); track item.id) {
6984
6985
  <ng-container *ngComponentOutlet="item.component; injector: item.injector; inputs: item.inputs()" />
6985
6986
  }
6986
6987
  `, 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
6988
  }
6988
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImport: i0, type: ArrayFieldComponent, decorators: [{
6989
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArrayFieldComponent, decorators: [{
6989
6990
  type: Component,
6990
6991
  args: [{ selector: 'array-field', imports: [NgComponentOutlet], template: `
6991
6992
  @for (item of resolvedItems(); track item.id) {
@@ -7132,5 +7133,5 @@ function extractErrorParams(error) {
7132
7133
  * Generated bundle index. Do not edit.
7133
7134
  */
7134
7135
 
7135
- export { omit as $, AddArrayItemEvent as A, buildBaseInputs as B, arrayFieldMapper as C, DYNAMIC_FORM_LOGGER as D, EventBus as E, FormClearEvent as F, groupFieldMapper as G, pageFieldMapper as H, rowFieldMapper as I, textFieldMapper as J, FIELD_REGISTRY as K, LogLevel as L, ARRAY_CONTEXT as M, NoopLogger as N, FIELD_SIGNAL_CONTEXT as O, PageChangeEvent as P, dynamicTextToObservable as Q, RemoveArrayItemEvent as R, SubmitEvent as S, ArrayFieldComponent as T, GroupFieldComponent as U, RowFieldComponent as V, applyValidator as W, applyValidators as X, getArrayLength as Y, resolveTokens as Z, BUILT_IN_FIELDS as _, DynamicTextPipe as a, interpolateParams as a0, 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, withLogger as w, NextPageEvent as x, PreviousPageEvent as y, baseFieldMapper as z };
7136
- //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-B_676NHl.mjs.map
7136
+ 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, 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 };
7137
+ //# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-CZBdtTAU.mjs.map