@ngx-formbar/core 1.0.0 → 2.0.0-next.1

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 (202) hide show
  1. package/README.md +2 -34
  2. package/fesm2022/ngx-formbar-core.mjs +225 -1658
  3. package/fesm2022/ngx-formbar-core.mjs.map +1 -1
  4. package/package.json +5 -12
  5. package/types/ngx-formbar-core.d.ts +525 -0
  6. package/LICENSE +0 -21
  7. package/index.d.ts +0 -5
  8. package/lib/components/form/ngxfb-form.component.d.ts +0 -28
  9. package/lib/composables/computed-value.d.ts +0 -8
  10. package/lib/composables/disabled.state.d.ts +0 -36
  11. package/lib/composables/dynamic-label.d.ts +0 -9
  12. package/lib/composables/dynamic-title.d.ts +0 -9
  13. package/lib/composables/hidden.state.d.ts +0 -68
  14. package/lib/composables/readonly.state.d.ts +0 -19
  15. package/lib/composables/testId.d.ts +0 -16
  16. package/lib/composables/update-strategy.d.ts +0 -20
  17. package/lib/composables/validators.d.ts +0 -22
  18. package/lib/config/config.d.ts +0 -7
  19. package/lib/config/provide-formbar.d.ts +0 -38
  20. package/lib/directives/ngxfb-abstract-control.directive.d.ts +0 -53
  21. package/lib/directives/ngxfb-block.directive.d.ts +0 -124
  22. package/lib/directives/ngxfb-control.directive.d.ts +0 -203
  23. package/lib/directives/ngxfb-group.directive.d.ts +0 -253
  24. package/lib/helper/control-container-view-providers.d.ts +0 -33
  25. package/lib/index.d.ts +0 -23
  26. package/lib/services/component-registration.service.d.ts +0 -8
  27. package/lib/services/configuration.service.d.ts +0 -8
  28. package/lib/services/expression.service.d.ts +0 -148
  29. package/lib/services/form.service.d.ts +0 -10
  30. package/lib/services/validator-registration.service.d.ts +0 -10
  31. package/lib/tokens/component-registrations.d.ts +0 -2
  32. package/lib/tokens/component-resolver.d.ts +0 -3
  33. package/lib/tokens/default-update-strategy.d.ts +0 -3
  34. package/lib/tokens/global-config.d.ts +0 -5
  35. package/lib/tokens/validator-registrations.d.ts +0 -8
  36. package/lib/tokens/validator-resolver.d.ts +0 -3
  37. package/lib/types/component-resolver.type.d.ts +0 -4
  38. package/lib/types/content.type.d.ts +0 -137
  39. package/lib/types/expression.type.d.ts +0 -2
  40. package/lib/types/form.type.d.ts +0 -4
  41. package/lib/types/functions.type.d.ts +0 -4
  42. package/lib/types/global-configuration.type.d.ts +0 -4
  43. package/lib/types/provide.type.d.ts +0 -42
  44. package/lib/types/registration.type.d.ts +0 -18
  45. package/lib/types/validation.type.d.ts +0 -59
  46. package/lib/types/validator-resolver.type.d.ts +0 -6
  47. package/public-api.d.ts +0 -1
  48. package/schematics/block/files/__componentName@dasherize__.component.html.template +0 -1
  49. package/schematics/block/files/__componentName@dasherize__.component.ts.template +0 -29
  50. package/schematics/block/files/__interfaceName@dasherize__.type.ts.template +0 -6
  51. package/schematics/block/index.d.ts +0 -3
  52. package/schematics/block/index.js +0 -11
  53. package/schematics/block/index.js.map +0 -1
  54. package/schematics/block/schema.json +0 -62
  55. package/schematics/collection.json +0 -31
  56. package/schematics/control/files/__componentName@dasherize__.component.html.template +0 -0
  57. package/schematics/control/files/__componentName@dasherize__.component.ts.template +0 -29
  58. package/schematics/control/files/__interfaceName@dasherize__.type.ts.template +0 -6
  59. package/schematics/control/index.d.ts +0 -3
  60. package/schematics/control/index.js +0 -11
  61. package/schematics/control/index.js.map +0 -1
  62. package/schematics/control/schema.json +0 -61
  63. package/schematics/group/files/__componentName@dasherize__.component.html.template +0 -5
  64. package/schematics/group/files/__componentName@dasherize__.component.ts.template +0 -29
  65. package/schematics/group/files/__interfaceName@dasherize__.type.ts.template +0 -5
  66. package/schematics/group/index.d.ts +0 -3
  67. package/schematics/group/index.js +0 -11
  68. package/schematics/group/index.js.map +0 -1
  69. package/schematics/group/schema.json +0 -62
  70. package/schematics/ng-add/files/config-registrations/async-validator-registrations.ts.template +0 -4
  71. package/schematics/ng-add/files/config-registrations/component-registrations.ts.template +0 -4
  72. package/schematics/ng-add/files/config-registrations/index.ts.template +0 -3
  73. package/schematics/ng-add/files/config-registrations/validator-registrations.ts.template +0 -4
  74. package/schematics/ng-add/files/helper/block.host-directive.ts.template +0 -6
  75. package/schematics/ng-add/files/helper/control.host-directive.ts.template +0 -6
  76. package/schematics/ng-add/files/helper/group.host-directive.ts.template +0 -6
  77. package/schematics/ng-add/files/helper/index.ts.template +0 -4
  78. package/schematics/ng-add/files/helper/view-provider.ts.template +0 -9
  79. package/schematics/ng-add/files/provider-config/config/__providerConfigFileName__.ts.template +0 -9
  80. package/schematics/ng-add/files/provider-config/inline/__providerConfigFileName__.ts.template +0 -8
  81. package/schematics/ng-add/files/provider-config/token/__providerConfigFileName__.ts.template +0 -4
  82. package/schematics/ng-add/files/schematics-config/__schematicConfigFileName__.json.template +0 -1
  83. package/schematics/ng-add/files/token-registrations/async-validator-registrations.ts.template +0 -8
  84. package/schematics/ng-add/files/token-registrations/component-registrations.ts.template +0 -8
  85. package/schematics/ng-add/files/token-registrations/index.ts.template +0 -3
  86. package/schematics/ng-add/files/token-registrations/validator-registrations.ts.template +0 -8
  87. package/schematics/ng-add/helper.d.ts +0 -11
  88. package/schematics/ng-add/helper.js +0 -198
  89. package/schematics/ng-add/helper.js.map +0 -1
  90. package/schematics/ng-add/index.d.ts +0 -3
  91. package/schematics/ng-add/index.js +0 -68
  92. package/schematics/ng-add/index.js.map +0 -1
  93. package/schematics/ng-add/rules/create-config-registration-files.rule.d.ts +0 -3
  94. package/schematics/ng-add/rules/create-config-registration-files.rule.js +0 -32
  95. package/schematics/ng-add/rules/create-config-registration-files.rule.js.map +0 -1
  96. package/schematics/ng-add/rules/create-formbar-registration-config.rule.d.ts +0 -3
  97. package/schematics/ng-add/rules/create-formbar-registration-config.rule.js +0 -30
  98. package/schematics/ng-add/rules/create-formbar-registration-config.rule.js.map +0 -1
  99. package/schematics/ng-add/rules/create-helper-files.rule.d.ts +0 -6
  100. package/schematics/ng-add/rules/create-helper-files.rule.js +0 -22
  101. package/schematics/ng-add/rules/create-helper-files.rule.js.map +0 -1
  102. package/schematics/ng-add/rules/create-schematics-config.rule.d.ts +0 -3
  103. package/schematics/ng-add/rules/create-schematics-config.rule.js +0 -42
  104. package/schematics/ng-add/rules/create-schematics-config.rule.js.map +0 -1
  105. package/schematics/ng-add/rules/create-token-registration-files.rule.d.ts +0 -3
  106. package/schematics/ng-add/rules/create-token-registration-files.rule.js +0 -32
  107. package/schematics/ng-add/rules/create-token-registration-files.rule.js.map +0 -1
  108. package/schematics/ng-add/rules/include-templates.rule.d.ts +0 -3
  109. package/schematics/ng-add/rules/include-templates.rule.js +0 -11
  110. package/schematics/ng-add/rules/include-templates.rule.js.map +0 -1
  111. package/schematics/ng-add/rules/install-dependencies.rule.d.ts +0 -2
  112. package/schematics/ng-add/rules/install-dependencies.rule.js +0 -12
  113. package/schematics/ng-add/rules/install-dependencies.rule.js.map +0 -1
  114. package/schematics/ng-add/rules/update-app-config.rule.d.ts +0 -3
  115. package/schematics/ng-add/rules/update-app-config.rule.js +0 -49
  116. package/schematics/ng-add/rules/update-app-config.rule.js.map +0 -1
  117. package/schematics/ng-add/rules/update-schematics-config.rule.d.ts +0 -6
  118. package/schematics/ng-add/rules/update-schematics-config.rule.js +0 -28
  119. package/schematics/ng-add/rules/update-schematics-config.rule.js.map +0 -1
  120. package/schematics/ng-add/schema.d.ts +0 -23
  121. package/schematics/ng-add/schema.js +0 -3
  122. package/schematics/ng-add/schema.js.map +0 -1
  123. package/schematics/ng-add/schema.json +0 -81
  124. package/schematics/register/component-info.type.d.ts +0 -11
  125. package/schematics/register/component-info.type.js +0 -3
  126. package/schematics/register/component-info.type.js.map +0 -1
  127. package/schematics/register/discover-components.d.ts +0 -19
  128. package/schematics/register/discover-components.js +0 -267
  129. package/schematics/register/discover-components.js.map +0 -1
  130. package/schematics/register/index.d.ts +0 -3
  131. package/schematics/register/index.js +0 -49
  132. package/schematics/register/index.js.map +0 -1
  133. package/schematics/register/register-components.d.ts +0 -3
  134. package/schematics/register/register-components.js +0 -38
  135. package/schematics/register/register-components.js.map +0 -1
  136. package/schematics/register/schema.d.ts +0 -14
  137. package/schematics/register/schema.js +0 -3
  138. package/schematics/register/schema.js.map +0 -1
  139. package/schematics/register/schema.json +0 -44
  140. package/schematics/shared/ast/decorators.d.ts +0 -9
  141. package/schematics/shared/ast/decorators.js +0 -182
  142. package/schematics/shared/ast/decorators.js.map +0 -1
  143. package/schematics/shared/ast/imports.d.ts +0 -3
  144. package/schematics/shared/ast/imports.js +0 -93
  145. package/schematics/shared/ast/imports.js.map +0 -1
  146. package/schematics/shared/ast/parse.d.ts +0 -3
  147. package/schematics/shared/ast/parse.js +0 -17
  148. package/schematics/shared/ast/parse.js.map +0 -1
  149. package/schematics/shared/ast/registrations.d.ts +0 -22
  150. package/schematics/shared/ast/registrations.js +0 -654
  151. package/schematics/shared/ast/registrations.js.map +0 -1
  152. package/schematics/shared/ast/types.d.ts +0 -3
  153. package/schematics/shared/ast/types.js +0 -58
  154. package/schematics/shared/ast/types.js.map +0 -1
  155. package/schematics/shared/file.d.ts +0 -4
  156. package/schematics/shared/file.js +0 -60
  157. package/schematics/shared/file.js.map +0 -1
  158. package/schematics/shared/helper.d.ts +0 -2
  159. package/schematics/shared/helper.js +0 -29
  160. package/schematics/shared/helper.js.map +0 -1
  161. package/schematics/shared/rules/create-component.rule.d.ts +0 -3
  162. package/schematics/shared/rules/create-component.rule.js +0 -15
  163. package/schematics/shared/rules/create-component.rule.js.map +0 -1
  164. package/schematics/shared/rules/register-control.rule.d.ts +0 -3
  165. package/schematics/shared/rules/register-control.rule.js +0 -30
  166. package/schematics/shared/rules/register-control.rule.js.map +0 -1
  167. package/schematics/shared/rules/register-type-map.rule.d.ts +0 -3
  168. package/schematics/shared/rules/register-type-map.rule.js +0 -46
  169. package/schematics/shared/rules/register-type-map.rule.js.map +0 -1
  170. package/schematics/shared/rules/register-type-token.rule.d.ts +0 -3
  171. package/schematics/shared/rules/register-type-token.rule.js +0 -49
  172. package/schematics/shared/rules/register-type-token.rule.js.map +0 -1
  173. package/schematics/shared/rules/scaffold-and-register.rule.d.ts +0 -3
  174. package/schematics/shared/rules/scaffold-and-register.rule.js +0 -134
  175. package/schematics/shared/rules/scaffold-and-register.rule.js.map +0 -1
  176. package/schematics/shared/schema.d.ts +0 -32
  177. package/schematics/shared/schema.js +0 -3
  178. package/schematics/shared/schema.js.map +0 -1
  179. package/schematics/tests/generators.spec.d.ts +0 -1
  180. package/schematics/tests/generators.spec.js +0 -450
  181. package/schematics/tests/generators.spec.js.map +0 -1
  182. package/schematics/tests/helper.d.ts +0 -20
  183. package/schematics/tests/helper.js +0 -275
  184. package/schematics/tests/helper.js.map +0 -1
  185. package/schematics/tests/ng-add.spec.d.ts +0 -1
  186. package/schematics/tests/ng-add.spec.js +0 -380
  187. package/schematics/tests/ng-add.spec.js.map +0 -1
  188. package/schematics/tests/register.spec.d.ts +0 -1
  189. package/schematics/tests/register.spec.js +0 -340
  190. package/schematics/tests/register.spec.js.map +0 -1
  191. package/schematics/tests/workspace-setup.d.ts +0 -21
  192. package/schematics/tests/workspace-setup.js +0 -256
  193. package/schematics/tests/workspace-setup.js.map +0 -1
  194. package/shared/ast.d.ts +0 -10
  195. package/shared/ast.js +0 -93
  196. package/shared/ast.js.map +0 -1
  197. package/shared/constants.d.ts +0 -16
  198. package/shared/constants.js +0 -20
  199. package/shared/constants.js.map +0 -1
  200. package/shared/shared-config.type.d.ts +0 -20
  201. package/shared/shared-config.type.js +0 -3
  202. package/shared/shared-config.type.js.map +0 -1
@@ -1,244 +1,56 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, InjectionToken, ViewContainerRef, input, computed, effect, Directive, Component, signal, makeEnvironmentProviders, untracked } from '@angular/core';
3
- import { ControlContainer, Validators, FormGroup, FormControl } from '@angular/forms';
4
- import { toSignal } from '@angular/core/rxjs-interop';
2
+ import { InjectionToken, inject, Injectable, signal, computed, effect, untracked } from '@angular/core';
5
3
  import * as acorn from 'acorn';
6
4
 
7
- class FormService {
8
- controlContainer = inject(ControlContainer);
9
- formGroup = this.controlContainer.control;
10
- formValue = toSignal(this.formGroup.valueChanges);
11
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FormService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
12
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FormService });
13
- }
14
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FormService, decorators: [{
15
- type: Injectable
16
- }] });
17
-
18
- /**
19
- * Provides the parent ControlContainer to child components
20
- *
21
- * This provider configuration allows child form components to access their parent's
22
- * ControlContainer through dependency injection. This is particularly useful for
23
- * creating nested form components that inherit the parent form context without
24
- * explicitly passing FormGroup instances.
25
- *
26
- * @remarks
27
- * The `skipSelf` option ensures the provider looks for the ControlContainer in parent
28
- * components rather than trying to resolve it from the component where this is used.
29
- *
30
- * @example
31
- * ```typescript
32
- * @Component({
33
- * selector: 'app-child-form',
34
- * template: '...',
35
- * viewProviders: [controlContainerViewProviders]
36
- * })
37
- * export class ChildFormComponent {
38
- * // Now this component can access the parent form
39
- * private controlContainer = inject(ControlContainer);
40
- *
41
- * get parentFormGroup() {
42
- * return this.parentContainer.control as FormGroup | null;
43
- * }
44
- * }
45
- */
46
- const controlContainerViewProviders = [
47
- {
48
- provide: ControlContainer,
49
- useFactory: () => inject(ControlContainer, { skipSelf: true }),
50
- },
51
- ];
52
-
53
- const NGX_FW_COMPONENT_RESOLVER = new InjectionToken('NGX_FW_COMPONENT_RESOLVER');
54
-
55
- /**
56
- * Structural directive that renders the appropriate component based on the control's type.
57
- *
58
- * This directive acts as a dynamic renderer for form controls, blocks, and groups.
59
- * It works by:
60
- * 1. Receiving a content configuration and name
61
- * 2. Looking up the registered component for the content's type
62
- * 3. Creating an instance of that component and binding the content and name to it
63
- *
64
- * This allows forms to be composed declaratively through configuration objects
65
- * rather than explicit templates.
66
- *
67
- * @example
68
- * ```html
69
- * <!-- Used with ngFor to render a list of controls -->
70
- * @for (control of controls(); track control[0]) {
71
- * <ng-template *ngxfbAbstractControl="control" />
72
- * }
73
- *
74
- * <!-- Used directly with a specific control -->
75
- * <ng-template *ngxfbAbstractControl="['name', nameControlConfig]" />
76
- * ```
77
- */
78
- class NgxfbAbstractControlDirective {
79
- viewContainerRef = inject(ViewContainerRef);
80
- /**
81
- * Service for component registration
82
- * Provides access to component type mappings
83
- */
84
- contentRegistrationService = inject(NGX_FW_COMPONENT_RESOLVER);
85
- /**
86
- * Required input for control configuration
87
- * Defines properties like type, validation, and other control-specific settings
88
- */
89
- content = input.required({
90
- alias: 'ngxfbAbstractControl',
91
- });
92
- controlName = computed(() => this.content()[0]);
93
- controlConfig = computed(() => this.content()[1]);
94
- /**
95
- * Registration map of component types
96
- * Maps control types to component implementations
97
- */
98
- registrations = this.contentRegistrationService.registrations;
99
- /**
100
- * Computed component type based on content.type
101
- * Looks up the component implementation from registrations map
102
- */
103
- component = computed(() => {
104
- const registrations = this.registrations();
105
- const content = this.controlConfig();
106
- const component = registrations.get(content.type);
107
- return component ?? null;
108
- });
109
- constructor() {
110
- effect(() => {
111
- const component = this.component();
112
- this.viewContainerRef.clear();
113
- if (component) {
114
- const componentRef = this.viewContainerRef.createComponent(component);
115
- componentRef.setInput('content', this.controlConfig());
116
- componentRef.setInput('name', this.controlName());
117
- }
118
- });
119
- }
120
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxfbAbstractControlDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
121
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.15", type: NgxfbAbstractControlDirective, isStandalone: true, selector: "[ngxfbAbstractControl]", inputs: { content: { classPropertyName: "content", publicName: "ngxfbAbstractControl", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
122
- }
123
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxfbAbstractControlDirective, decorators: [{
124
- type: Directive,
125
- args: [{
126
- selector: '[ngxfbAbstractControl]',
127
- }]
128
- }], ctorParameters: () => [] });
129
-
130
- /**
131
- * Ngx Formbar Form Component
132
- *
133
- * This component serves as the main container for Ngx Formbar forms:
134
- * - Takes a form configuration
135
- * - Establishes the form context through FormService provider
136
- * - Renders each content item using NgxfbAbstractControlDirective
137
- * - Handles component registration and dependency injection
138
- *
139
- * The component acts as the root element for declarative form creation,
140
- * processing the form content configuration and rendering the appropriate
141
- * components for each control defined in the configuration.
142
- */
143
- class NgxfbFormComponent {
144
- /**
145
- * Required input containing form configuration
146
- */
147
- formConfig = input.required();
148
- /**
149
- * Computed value containing form content
150
- */
151
- formContent = computed(() => Object.entries(this.formConfig().content));
152
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxfbFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
153
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: NgxfbFormComponent, isStandalone: true, selector: "ngxfb-form", inputs: { formConfig: { classPropertyName: "formConfig", publicName: "formConfig", isSignal: true, isRequired: true, transformFunction: null } }, providers: [FormService], ngImport: i0, template: "@for (content of formContent(); track content[0]) {\n <ng-template *ngxfbAbstractControl=\"content\" />\n}\n", dependencies: [{ kind: "directive", type: NgxfbAbstractControlDirective, selector: "[ngxfbAbstractControl]", inputs: ["ngxfbAbstractControl"] }], viewProviders: [controlContainerViewProviders] });
154
- }
155
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxfbFormComponent, decorators: [{
156
- type: Component,
157
- args: [{ selector: 'ngxfb-form', imports: [NgxfbAbstractControlDirective], providers: [FormService], viewProviders: [controlContainerViewProviders], template: "@for (content of formContent(); track content[0]) {\n <ng-template *ngxfbAbstractControl=\"content\" />\n}\n" }]
158
- }] });
159
-
160
- /**
161
- * Type helper to make it easier to use formbar.config.ts
162
- * accepts a direct {@link FormbarConfig} object
163
- */
164
- function defineFormbarConfig(config) {
165
- return config;
166
- }
167
-
168
5
  const NGX_FW_COMPONENT_REGISTRATIONS = new InjectionToken('NGX_FW_COMPONENT_REGISTRATIONS', {
169
6
  providedIn: 'root',
170
7
  factory: () => new Map(),
171
8
  });
172
9
 
173
- class ComponentRegistrationService {
174
- _registrations = signal(inject(NGX_FW_COMPONENT_REGISTRATIONS));
175
- registrations = this._registrations.asReadonly();
176
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ComponentRegistrationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
177
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ComponentRegistrationService, providedIn: 'root' });
178
- }
179
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ComponentRegistrationService, decorators: [{
180
- type: Injectable,
181
- args: [{
182
- providedIn: 'root',
183
- }]
184
- }] });
10
+ const NGX_FW_COMPONENT_RESOLVER = new InjectionToken('NGX_FW_COMPONENT_RESOLVER');
185
11
 
186
- const NGX_FW_DEFAULT_VALIDATOR_REGISTRATIONS = new InjectionToken('NgxFbDefaultValidatorRegistrations', {
187
- providedIn: 'root',
188
- factory: () => new Map([
189
- ['required', [Validators.required]],
190
- ['requiredTrue', [Validators.requiredTrue]],
191
- ['email', [Validators.email]],
192
- ['nullValidator', [Validators.nullValidator]],
193
- ]),
194
- });
195
- const NGX_FW_DEFAULT_ASYNC_VALIDATOR_REGISTRATIONS = new InjectionToken('NGX_FW_DEFAULT_ASYNC_VALIDATOR_REGISTRATIONS', {
12
+ const NGX_FW_DEFAULT_CONFIG = new InjectionToken('NGX_FW_DEFAULT_CONFIG', {
196
13
  providedIn: 'root',
197
- factory: () => new Map(),
14
+ factory: () => ({}),
198
15
  });
199
- const NGX_FW_VALIDATOR_REGISTRATIONS = new InjectionToken('NGX_FW_VALIDATOR_REGISTRATIONS');
200
- const NGX_FW_ASYNC_VALIDATOR_REGISTRATIONS = new InjectionToken('NGX_FW_ASYNC_VALIDATOR_REGISTRATIONS');
201
- const NGX_FW_VALIDATOR_REGISTRATIONS_RESOLVED = new InjectionToken('NGX_FW_VALIDATOR_REGISTRATIONS_RESOLVED', {
16
+ const NGX_FW_CONFIG = new InjectionToken('NGX_FW_CONFIG', {
202
17
  providedIn: 'root',
203
- factory: () => {
204
- const base = inject(NGX_FW_DEFAULT_VALIDATOR_REGISTRATIONS);
205
- const extras = inject(NGX_FW_VALIDATOR_REGISTRATIONS, { optional: true }) ?? [];
206
- return mergeMapsLastWins(base, ...extras);
207
- },
18
+ factory: () => [],
208
19
  });
209
- const NGX_FW_ASYNC_VALIDATOR_REGISTRATIONS_RESOLVED = new InjectionToken('NGX_FW_ASYNC_VALIDATOR_REGISTRATIONS_RESOLVED', {
20
+ const NGX_FW_CONFIG_RESOLVED = new InjectionToken('NGX_FW_CONFIG_RESOLVED', {
210
21
  providedIn: 'root',
211
22
  factory: () => {
212
- const base = inject(NGX_FW_DEFAULT_ASYNC_VALIDATOR_REGISTRATIONS);
213
- const extras = inject(NGX_FW_ASYNC_VALIDATOR_REGISTRATIONS, { optional: true }) ?? [];
214
- return mergeMapsLastWins(base, ...extras);
23
+ const base = inject(NGX_FW_DEFAULT_CONFIG);
24
+ const extras = inject(NGX_FW_CONFIG, { optional: true }) ?? [];
25
+ return mergeDeep(base, ...extras);
215
26
  },
216
27
  });
217
- /** Utility: merge maps left->right, later maps override earlier keys */
218
- function mergeMapsLastWins(...maps) {
219
- const out = new Map();
220
- for (const m of maps) {
221
- for (const [k, v] of m) {
222
- out.set(k, v);
28
+ function mergeDeep(base, ...partials) {
29
+ const out = { ...base };
30
+ for (const p of partials) {
31
+ for (const k of Object.keys(p)) {
32
+ const src = p[k];
33
+ const dst = out[k];
34
+ const bothObjects = typeof dst === 'object' &&
35
+ dst !== null &&
36
+ typeof src === 'object' &&
37
+ src !== null &&
38
+ !Array.isArray(dst) &&
39
+ !Array.isArray(src);
40
+ if (bothObjects) {
41
+ out[k] = mergeDeep(dst, src);
42
+ continue;
43
+ }
44
+ out[k] = src;
223
45
  }
224
46
  }
225
47
  return out;
226
48
  }
227
49
 
228
- class ValidatorRegistrationService {
229
- _registrations = signal(inject(NGX_FW_VALIDATOR_REGISTRATIONS_RESOLVED));
230
- registrations = this._registrations.asReadonly();
231
- _asyncRegistrations = signal(inject(NGX_FW_ASYNC_VALIDATOR_REGISTRATIONS_RESOLVED));
232
- asyncRegistrations = this._asyncRegistrations.asReadonly();
233
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ValidatorRegistrationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
234
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ValidatorRegistrationService, providedIn: 'root' });
235
- }
236
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ValidatorRegistrationService, decorators: [{
237
- type: Injectable,
238
- args: [{
239
- providedIn: 'root',
240
- }]
241
- }] });
50
+ const NGX_FW_DEFAULT_UPDATE_STRATEGY = new InjectionToken('NGX_FW_DEFAULT_UPDATE_STRATEGY', {
51
+ providedIn: 'root',
52
+ factory: () => 'change',
53
+ });
242
54
 
243
55
  /**
244
56
  * Set of node types that are not supported in expressions for security or complexity reasons
@@ -876,505 +688,249 @@ class ExpressionService {
876
688
  throw new TypeError('Block-bodied arrow functions are not supported');
877
689
  };
878
690
  }
879
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ExpressionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
880
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ExpressionService, providedIn: 'root' });
691
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ExpressionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
692
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ExpressionService, providedIn: 'root' });
881
693
  }
882
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ExpressionService, decorators: [{
694
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ExpressionService, decorators: [{
883
695
  type: Injectable,
884
696
  args: [{
885
697
  providedIn: 'root',
886
698
  }]
887
699
  }] });
888
700
 
889
- const NGX_FW_DEFAULT_UPDATE_STRATEGY = new InjectionToken('NGX_FW_DEFAULT_UPDATE_STRATEGY', {
890
- providedIn: 'root',
891
- factory: () => 'change',
892
- });
893
-
894
- const NGX_VALIDATOR_RESOLVER = new InjectionToken('NGX_VALIDATOR_RESOLVER');
701
+ class ComponentRegistrationService {
702
+ _registrations = signal(inject(NGX_FW_COMPONENT_REGISTRATIONS), ...(ngDevMode ? [{ debugName: "_registrations" }] : []));
703
+ registrations = this._registrations.asReadonly();
704
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ComponentRegistrationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
705
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ComponentRegistrationService, providedIn: 'root' });
706
+ }
707
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ComponentRegistrationService, decorators: [{
708
+ type: Injectable,
709
+ args: [{
710
+ providedIn: 'root',
711
+ }]
712
+ }] });
895
713
 
896
- const NGX_FW_DEFAULT_CONFIG = new InjectionToken('NGX_FW_DEFAULT_CONFIG', {
897
- providedIn: 'root',
898
- factory: () => ({}),
899
- });
900
- const NGX_FW_CONFIG = new InjectionToken('NGX_FW_CONFIG', {
901
- providedIn: 'root',
902
- factory: () => [],
903
- });
904
- const NGX_FW_CONFIG_RESOLVED = new InjectionToken('NGX_FW_CONFIG_RESOLVED', {
905
- providedIn: 'root',
906
- factory: () => {
907
- const base = inject(NGX_FW_DEFAULT_CONFIG);
908
- const extras = inject(NGX_FW_CONFIG, { optional: true }) ?? [];
909
- return mergeDeep(base, ...extras);
910
- },
911
- });
912
- function mergeDeep(base, ...partials) {
913
- const out = { ...base };
914
- for (const p of partials) {
915
- for (const k of Object.keys(p)) {
916
- const src = p[k];
917
- const dst = out[k];
918
- const bothObjects = typeof dst === 'object' &&
919
- dst !== null &&
920
- typeof src === 'object' &&
921
- src !== null &&
922
- !Array.isArray(dst) &&
923
- !Array.isArray(src);
924
- if (bothObjects) {
925
- out[k] = mergeDeep(dst, src);
926
- continue;
927
- }
928
- out[k] = src;
929
- }
714
+ class NgxFbConfigurationService {
715
+ _config = inject(NGX_FW_CONFIG_RESOLVED);
716
+ get testIdBuilder() {
717
+ return this._config.testIdBuilderFn;
930
718
  }
931
- return out;
719
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: NgxFbConfigurationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
720
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: NgxFbConfigurationService, providedIn: 'root' });
932
721
  }
722
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: NgxFbConfigurationService, decorators: [{
723
+ type: Injectable,
724
+ args: [{
725
+ providedIn: 'root',
726
+ }]
727
+ }] });
933
728
 
934
729
  /**
935
- * Configures and provides ngx-formbar to your application.
936
- *
937
- * This function is used in app.config.ts to register form components, validators,
938
- * and set global configuration for the formbar library.
939
- *
940
- * @param config Configuration object for ngx-formbar:
941
- * - componentRegistrations: Optional mapping of control types to component implementations
942
- * - validatorRegistrations: Optional mapping of validator names to validator functions
943
- * (Angular's required, requiredTrue, email, and nullValidator are registered by default)
944
- * - asyncValidatorRegistrations: Optional mapping of async validator names to async validator functions
945
- * - updateOn: Optional default update strategy for all form controls
946
- * - globalConfig: Optional global configuration settings
947
- *
948
- * @returns Angular environment providers to be included in application configuration
730
+ * Creates a static component registration entry.
731
+ * Use for eagerly imported components that don't need lazy loading.
949
732
  *
950
733
  * @example
951
734
  * ```ts
952
- * // app.config.ts
953
- * export const appConfig: ApplicationConfig = {
954
- * providers: [
955
- * provideFormbar({
956
- * componentRegistrations: {
957
- * text: TextInputComponent,
958
- * select: SelectComponent,
959
- * },
960
- * validatorRegistrations: {
961
- * customValidator: [myCustomValidator]
962
- * }
963
- * })
964
- * ]
735
+ * import { staticComponent } from '@ngx-formbar/core';
736
+ * import { TextComponent } from './text.component';
737
+ *
738
+ * const registrations = {
739
+ * text: staticComponent(TextComponent),
965
740
  * };
966
741
  * ```
967
742
  */
968
- function provideFormbar(config) {
969
- config ??= {};
970
- const { componentRegistrations, validatorRegistrations, asyncValidatorRegistrations, updateOn, globalConfig, } = config;
971
- const providers = [
972
- {
973
- provide: NGX_FW_COMPONENT_RESOLVER,
974
- useClass: ComponentRegistrationService,
975
- },
976
- {
977
- provide: NGX_VALIDATOR_RESOLVER,
978
- useClass: ValidatorRegistrationService,
979
- },
980
- ExpressionService,
981
- ];
982
- if (componentRegistrations !== undefined) {
983
- providers.push({
984
- provide: NGX_FW_COMPONENT_REGISTRATIONS,
985
- useValue: toComponentRegistrationMap(componentRegistrations),
986
- });
987
- }
988
- if (validatorRegistrations !== undefined) {
989
- providers.push({
990
- provide: NGX_FW_VALIDATOR_REGISTRATIONS,
991
- useFactory: () => toValidatorRegistrationMap(validatorRegistrations),
992
- multi: true,
993
- });
994
- }
995
- if (asyncValidatorRegistrations !== undefined) {
996
- providers.push({
997
- provide: NGX_FW_ASYNC_VALIDATOR_REGISTRATIONS,
998
- useFactory: () => toAsyncValidatorRegistrationMap(asyncValidatorRegistrations),
999
- multi: true,
1000
- });
1001
- }
1002
- if (updateOn !== undefined) {
1003
- providers.push({
1004
- provide: NGX_FW_DEFAULT_UPDATE_STRATEGY,
1005
- useValue: updateOn,
1006
- });
1007
- }
1008
- if (globalConfig !== undefined) {
1009
- providers.push({
1010
- provide: NGX_FW_CONFIG,
1011
- useValue: globalConfig,
1012
- multi: true,
1013
- });
1014
- }
1015
- return makeEnvironmentProviders(providers);
1016
- }
1017
- /**
1018
- * Converts component registration array to a lookup map
1019
- *
1020
- * @param componentRegistrations Array of component registration configurations
1021
- * @returns Map of component types to component implementations
1022
- */
1023
- function toComponentRegistrationMap(componentRegistrations) {
1024
- return new Map(Object.entries(componentRegistrations));
1025
- }
1026
- /**
1027
- * Converts validator configuration to a map of validator functions
1028
- * Resolves validator keys to actual validator functions
1029
- *
1030
- * @param config Configuration object for validators
1031
- * @returns Map of validator keys to validator function arrays
1032
- */
1033
- function toValidatorRegistrationMap(config) {
1034
- return toValidatorMap(config);
743
+ function staticComponent(type) {
744
+ return { component: type };
1035
745
  }
1036
746
  /**
1037
- * Converts async validator configuration to a map of async validator functions
747
+ * Creates a lazy component registration entry.
748
+ * Use for components that should be loaded on demand.
1038
749
  *
1039
- * @param config Configuration object for async validators
1040
- * @returns Map of validator keys to async validator function arrays
1041
- */
1042
- function toAsyncValidatorRegistrationMap(config) {
1043
- return toValidatorMap(config);
1044
- }
1045
- /**
1046
- * Resolves validator references to actual validator functions
1047
- * Supports recursive validator references and memoization
750
+ * @example
751
+ * ```ts
752
+ * import { loadComponent } from '@ngx-formbar/core';
1048
753
  *
1049
- * @param validators Array of validator functions or validator keys
1050
- * @param registrations Map of all registered validators
1051
- * @param memo Memoization cache to avoid circular references
1052
- * @param visiting
1053
- * @returns Flattened array of validator functions
754
+ * const registrations = {
755
+ * text: loadComponent(() => import('./text.component').then(m => m.TextComponent)),
756
+ * };
757
+ * ```
1054
758
  */
1055
- function toValidatorFn(validators, registrations, memo = new Map(), visiting = new Set()) {
1056
- return validators.flatMap((v) => {
1057
- switch (typeof v) {
1058
- case 'string': {
1059
- const cached = memo.get(v);
1060
- if (cached)
1061
- return cached;
1062
- if (visiting.has(v))
1063
- throw new Error(`Cyclic validator reference: ${[...visiting, v].join(' -> ')}`);
1064
- if (!Object.prototype.hasOwnProperty.call(registrations, v))
1065
- throw new Error(`Unknown validator key: "${v}"`);
1066
- const spec = registrations[v];
1067
- visiting.add(v);
1068
- const resolved = toValidatorFn(spec, registrations, memo, visiting);
1069
- visiting.delete(v);
1070
- memo.set(v, resolved);
1071
- return resolved;
1072
- }
1073
- default:
1074
- return v;
1075
- }
1076
- });
759
+ function loadComponent(load) {
760
+ return { loadComponent: load };
1077
761
  }
1078
- function toValidatorMap(source) {
1079
- const out = new Map();
1080
- const memo = new Map();
1081
- for (const key in source) {
1082
- if (!Object.prototype.hasOwnProperty.call(source, key)) {
1083
- continue;
1084
- }
1085
- const spec = source[key];
1086
- const cached = memo.get(key);
1087
- if (cached) {
1088
- out.set(key, cached);
1089
- continue;
1090
- }
1091
- const resolved = toValidatorFn(spec, source, memo);
1092
- memo.set(key, resolved);
1093
- out.set(key, resolved);
762
+
763
+ function toSafeString(value) {
764
+ if (value === null) {
765
+ return 'null';
766
+ }
767
+ switch (typeof value) {
768
+ case 'undefined':
769
+ return 'undefined';
770
+ case 'string':
771
+ return value;
772
+ case 'boolean':
773
+ return value ? 'true' : 'false';
774
+ case 'number':
775
+ case 'symbol':
776
+ case 'bigint':
777
+ case 'function':
778
+ return value.toString();
779
+ default:
780
+ return JSON.stringify(value);
1094
781
  }
1095
- return out;
1096
782
  }
1097
783
 
784
+ function isExpressionFn(value) {
785
+ return typeof value === 'function';
786
+ }
1098
787
  /**
1099
- * Computes a reactive readonly state based on control content
788
+ * Resolves an expression option into a computed signal value
1100
789
  *
1101
- * The readonly state is determined using the following priority:
1102
- * 1. If content.readonly is a boolean, that value is used directly
1103
- * 2. If content.readonly is an expression string, it's parsed to AST and evaluated
1104
- * against the current form values
1105
- * 3. If no readonly property is defined, the control inherits the readonly state
1106
- * from its parent group
790
+ * Handles three expression types:
791
+ * - **string**: Parsed to AST and evaluated against the form context
792
+ * - **function**: Called with the form context as argument
793
+ * - **static value / undefined**: Returned as-is
1107
794
  *
1108
- * This hierarchical inheritance ensures that child controls are automatically
1109
- * set to readonly when their parent group is readonly, unless explicitly overridden.
795
+ * Internally caches the parsed AST in a separate computed signal
796
+ * so it is only recalculated when the option itself changes.
1110
797
  *
1111
- * @param content Signal containing control configuration with potential readonly property
1112
- * @returns Computed signal that resolves to boolean readonly state
798
+ * @param option Signal containing the expression option (string, function, static value, or undefined)
799
+ * @param formContext Signal providing the current form context for expression evaluation
800
+ * @param expressionService Service used to parse and evaluate string expressions
801
+ * @returns Computed signal that resolves to the evaluated value or undefined
1113
802
  */
1114
- function withReadonlyState(content) {
1115
- const formService = inject(FormService);
1116
- const expressionService = inject(ExpressionService);
1117
- const parentGroupDirective = inject((NgxfbGroupDirective), {
1118
- optional: true,
1119
- skipSelf: true,
1120
- });
1121
- const parentGroupIsReadonly = computed(() => {
1122
- const parentGroup = parentGroupDirective;
1123
- if (!parentGroup) {
1124
- return false;
1125
- }
1126
- return parentGroup.readonly();
1127
- });
1128
- const readonlyAst = computed(() => {
1129
- const readonlyOption = content().readonly;
1130
- if (typeof readonlyOption === 'boolean' ||
1131
- typeof readonlyOption === 'function') {
1132
- return null;
1133
- }
1134
- return expressionService.parseExpressionToAst(readonlyOption);
1135
- });
1136
- const readonlyBool = computed(() => {
1137
- const readonlyOption = content().readonly;
1138
- if (typeof readonlyOption !== 'boolean') {
1139
- return null;
1140
- }
1141
- return readonlyOption;
1142
- });
1143
- const readonlyFunction = computed(() => {
1144
- const readonlyOption = content().readonly;
1145
- if (typeof readonlyOption !== 'function') {
803
+ function resolveExpression(option, formContext, expressionService) {
804
+ const ast = computed(() => {
805
+ const value = option();
806
+ if (typeof value !== 'string') {
1146
807
  return null;
1147
808
  }
1148
- return readonlyOption;
1149
- });
809
+ return expressionService.parseExpressionToAst(value);
810
+ }, ...(ngDevMode ? [{ debugName: "ast" }] : []));
1150
811
  return computed(() => {
1151
- const readonlyStatic = readonlyBool();
1152
- if (readonlyStatic !== null) {
1153
- return readonlyStatic;
812
+ const value = option();
813
+ if (value === undefined) {
814
+ return undefined;
815
+ }
816
+ if (isExpressionFn(value)) {
817
+ return value(formContext());
1154
818
  }
1155
- const reactiveFormValues = formService.formValue();
1156
- const currentSynchronousFormValues = formService.formGroup
1157
- .value;
1158
- const evaluationContext = reactiveFormValues ?? currentSynchronousFormValues;
1159
- const readonlyFn = readonlyFunction();
1160
- if (readonlyFn) {
1161
- return readonlyFn(evaluationContext);
819
+ if (typeof value !== 'string') {
820
+ return value;
1162
821
  }
1163
- const ast = readonlyAst();
1164
- if (!ast) {
1165
- return parentGroupIsReadonly();
822
+ const parsedAst = ast();
823
+ if (!parsedAst) {
824
+ return undefined;
1166
825
  }
1167
- const readonly = expressionService.evaluateExpression(ast, evaluationContext) ?? false;
1168
- return readonly;
826
+ return expressionService.evaluateExpression(parsedAst, formContext());
1169
827
  });
1170
828
  }
1171
829
 
1172
830
  /**
1173
- * Computes a reactive hidden state based on control content
831
+ * Resolves an inheritable boolean expression with parent state fallback
1174
832
  *
1175
- * The hidden state is determined using the following priority:
1176
- * 1. If content.hidden is an expression string, it's parsed to AST and evaluated
1177
- * against the current form values
1178
- * 2. If no hidden expression is defined, the control inherits the hidden state
1179
- * from its parent group
1180
- * 3. Both conditions can be combined - a control is hidden if either its own
1181
- * condition evaluates to true OR its parent group is hidden
833
+ * Used for states like disabled and readonly that follow a common pattern:
834
+ * 1. If the option is undefined, inherit the parent state
835
+ * 2. Otherwise, resolve the expression and default to false
1182
836
  *
1183
- * @param content Signal containing control configuration with potential hidden expression
1184
- * @returns Computed signal that resolves to boolean hidden state
837
+ * @param option Signal containing the expression option (boolean, string expression, function, or undefined)
838
+ * @param formContext Signal providing the current form context for expression evaluation
839
+ * @param expressionService Service used to parse and evaluate string expressions
840
+ * @param parentState Signal providing the parent group's state to inherit from
841
+ * @returns Computed signal that resolves to a boolean state
1185
842
  */
1186
- function withHiddenState(content) {
1187
- const formService = inject(FormService);
1188
- const expressionService = inject(ExpressionService);
1189
- const parentGroupDirective = inject((NgxfbGroupDirective), {
1190
- optional: true,
1191
- skipSelf: true,
1192
- });
1193
- const parentGroupIsHidden = computed(() => {
1194
- const parentGroup = parentGroupDirective;
1195
- if (!parentGroup) {
1196
- return false;
1197
- }
1198
- return parentGroup.isHidden();
1199
- });
1200
- const visibilityAst = computed(() => {
1201
- const hiddenOption = content().hidden;
1202
- if (typeof hiddenOption !== 'string') {
1203
- return null;
1204
- }
1205
- return expressionService.parseExpressionToAst(hiddenOption);
1206
- });
1207
- const visibilityFunction = computed(() => {
1208
- const visibilityOption = content().hidden;
1209
- if (typeof visibilityOption !== 'function') {
1210
- return null;
1211
- }
1212
- return visibilityOption;
1213
- });
843
+ function resolveInheritableExpression(option, formContext, expressionService, parentState) {
844
+ const resolved = resolveExpression(option, formContext, expressionService);
1214
845
  return computed(() => {
1215
- const reactiveFormValues = formService.formValue();
1216
- const currentSynchronousFormValues = formService.formGroup
1217
- .value;
1218
- const evaluationContext = reactiveFormValues ?? currentSynchronousFormValues;
1219
- const visibilityFn = visibilityFunction();
1220
- if (visibilityFn) {
1221
- return visibilityFn(evaluationContext);
1222
- }
1223
- const ast = visibilityAst();
1224
- if (!ast) {
1225
- return parentGroupIsHidden();
846
+ if (option() === undefined) {
847
+ return parentState();
1226
848
  }
1227
- const isHidden = expressionService.evaluateExpression(ast, evaluationContext) ?? false;
1228
- return isHidden || parentGroupIsHidden();
849
+ return resolved() ?? false;
1229
850
  });
1230
851
  }
852
+
1231
853
  /**
1232
- * Creates a computed attribute value for hidden DOM elements
1233
- *
1234
- * When visibilityHandling is set to 'auto', this returns a boolean attribute value
1235
- * that can be used with Angular's [attr.hidden] binding. When set to 'manual',
1236
- * it returns null so the attribute is not applied.
1237
- *
1238
- * @param options Configuration object for hidden attribute
1239
- * @param options.hiddenSignal Signal that indicates if the control should be hidden
1240
- * @param options.hiddenHandlingSignal Signal that determines how visibility is managed
1241
- * @returns Computed signal that resolves to attribute value (true or null)
854
+ * Resolves the hidden state with parent combination logic
855
+ *
856
+ * The hidden state differs from disabled/readonly inheritance:
857
+ * 1. If the option is undefined, inherit the parent hidden state
858
+ * 2. If the option is a function expression, use only the resolved value
859
+ * 3. If the option is a string expression, OR with the parent hidden state
860
+ * (a child is hidden if either its own condition or its parent is hidden)
861
+ *
862
+ * @param option Signal containing the hidden expression option
863
+ * @param formContext Signal providing the current form context for expression evaluation
864
+ * @param expressionService Service used to parse and evaluate string expressions
865
+ * @param parentHiddenState Signal providing the parent group's hidden state
866
+ * @returns Computed signal that resolves to a boolean hidden state
1242
867
  */
1243
- function withHiddenAttribute(options) {
868
+ function resolveHiddenState(option, formContext, expressionService, parentHiddenState) {
869
+ const resolved = resolveExpression(option, formContext, expressionService);
1244
870
  return computed(() => {
1245
- const isHidden = options.hiddenSignal();
1246
- const visibilityHandling = options.hiddenHandlingSignal();
1247
- if (visibilityHandling !== 'auto') {
1248
- return null;
871
+ const hiddenOption = option();
872
+ if (hiddenOption === undefined) {
873
+ return parentHiddenState();
1249
874
  }
1250
- return isHidden ? true : null;
875
+ const isHidden = resolved() ?? false;
876
+ if (typeof hiddenOption === 'function') {
877
+ return isHidden;
878
+ }
879
+ return isHidden || parentHiddenState();
1251
880
  });
1252
881
  }
882
+
1253
883
  /**
1254
- * Creates an effect that manages control visibility in forms
884
+ * Creates an effect that manages disabled state transitions
1255
885
  *
1256
- * Based on visibility state and hide strategy, this effect:
1257
- * 1. Attaches the control to the form when visible
1258
- * 2. Detaches the control from the form when hidden and strategy is 'remove'
1259
- * 3. Manages control values based on the specified valueStrategy when visibility changes
886
+ * When disabledHandling is 'auto', calls the appropriate enable/disable
887
+ * function based on the disabled signal. When 'manual', does nothing.
1260
888
  *
1261
- * @param options Configuration object for hidden effect
1262
- * @param options.content Signal containing control configuration
1263
- * @param options.name Signal containing the name of the control
1264
- * @param options.controlInstance Signal with the form control instance
1265
- * @param options.hiddenSignal Signal that indicates if the control should be hidden
1266
- * @param options.hideStrategySignal Signal with the strategy for handling hidden controls
1267
- * @param options.valueStrategySignal Signal with the strategy for handling control values
1268
- * @param options.parentValueStrategySignal Signal with the parent's value strategy
1269
- * @param options.attachFunction Function to call when control should be attached
1270
- * @param options.detachFunction Function to call when control should be detached
1271
- * @param options.valueHandleFunction Function to handle control value based on strategy
889
+ * @param options Configuration object for disabled effect
890
+ * @param options.disabledSignal Signal that indicates if the component should be disabled
891
+ * @param options.disabledHandlingSignal Signal that determines how disabled state changes should be handled
892
+ * @param options.enableFunction Function to call when component should be enabled
893
+ * @param options.disableFunction Function to call when component should be disabled
1272
894
  */
1273
- function hiddenEffect(options) {
1274
- const parentContainer = inject(ControlContainer);
1275
- const parentFormGroup = parentContainer.control;
895
+ function resolveDisabledEffect(options) {
1276
896
  effect(() => {
1277
- options.controlInstance();
1278
- const isHidden = options.hiddenSignal();
1279
- const hideStrategy = options.hideStrategySignal();
1280
- const valueStrategy = options.valueStrategySignal() ?? options.parentValueStrategySignal();
1281
- const formControl = untracked(() => parentFormGroup?.get(options.name()));
1282
- // Re-attach control
1283
- // On initial render the form control will not be attached, but we need it for the hide strategy "keep"
1284
- if (!formControl && (!isHidden || hideStrategy === 'keep')) {
1285
- untracked(() => {
1286
- options.attachFunction();
1287
- });
1288
- return;
1289
- }
1290
- // Control is already detached
1291
- if (hideStrategy === 'remove' && !formControl) {
897
+ const disabled = options.disabledSignal();
898
+ const disabledHandling = options.disabledHandlingSignal();
899
+ if (disabledHandling === 'manual') {
1292
900
  return;
1293
901
  }
1294
- // Remove control
1295
- if (hideStrategy === 'remove' && isHidden) {
902
+ if (!disabled) {
1296
903
  untracked(() => {
1297
- options.detachFunction();
904
+ options.enableFunction();
1298
905
  });
906
+ return;
1299
907
  }
1300
- // Only thing left to check is value strategy
1301
908
  untracked(() => {
1302
- options.valueHandleFunction(valueStrategy);
909
+ options.disableFunction();
1303
910
  });
1304
911
  });
1305
912
  }
1306
913
 
1307
914
  /**
1308
- * Computes a reactive array of validators based on control content
1309
- *
1310
- * Extracts validator keys from content and maps them to actual validator functions
1311
- * by looking them up in the validator registration service.
1312
- *
1313
- * @param content Signal containing control configuration with validator keys
1314
- * @returns Computed signal that resolves to an array of validator functions
915
+ * Resolves a test ID for a form element
916
+ *
917
+ * The test ID is determined using the following priority:
918
+ * 1. A local testIdBuilder function if provided
919
+ * 2. A global testIdBuilder function if provided
920
+ * 3. Default: joins parentTestId and name with '-', or just name if no parent
921
+ *
922
+ * @param content Signal containing the base content configuration
923
+ * @param name Signal containing the control/group name
924
+ * @param testIdBuilder Signal holding an optional local testIdBuilder function
925
+ * @param globalTestIdBuilder Optional global testIdBuilder function from configuration
926
+ * @param parentTestId Signal providing the parent group's test ID
927
+ * @returns Computed signal that resolves to the test ID string
1315
928
  */
1316
- function withValidators(content) {
1317
- const validatorRegistrations = inject(NGX_VALIDATOR_RESOLVER).registrations;
1318
- return computed(() => {
1319
- const validatorKeys = content().validators ?? [];
1320
- return validatorKeys.flatMap((key) => validatorRegistrations().get(key) ?? []);
1321
- });
1322
- }
1323
- /**
1324
- * Computes a reactive array of async validators based on control content
1325
- *
1326
- * Extracts async validator keys from content and maps them to actual async validator
1327
- * functions by looking them up in the validator registration service.
1328
- *
1329
- * @param content Signal containing control configuration with async validator keys
1330
- * @returns Computed signal that resolves to an array of async validator functions
1331
- */
1332
- function withAsyncValidators(content) {
1333
- const asyncValidatorRegistrations = inject(NGX_VALIDATOR_RESOLVER).asyncRegistrations;
1334
- return computed(() => {
1335
- const validatorKeys = content().asyncValidators ?? [];
1336
- return validatorKeys.flatMap((key) => asyncValidatorRegistrations().get(key) ?? []);
1337
- });
1338
- }
1339
-
1340
- class NgxFbConfigurationService {
1341
- _config = inject(NGX_FW_CONFIG_RESOLVED);
1342
- get testIdBuilder() {
1343
- return this._config.testIdBuilderFn;
1344
- }
1345
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxFbConfigurationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1346
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxFbConfigurationService, providedIn: 'root' });
1347
- }
1348
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxFbConfigurationService, decorators: [{
1349
- type: Injectable,
1350
- args: [{
1351
- providedIn: 'root',
1352
- }]
1353
- }] });
1354
-
1355
- /**
1356
- * Creates a computed signal that extracts the ID for testing purposes
1357
- *
1358
- * This utility function derives a test identifier from a form control's content,
1359
- * which can be used for targeting elements in automated tests.
1360
- *
1361
- * @template T - Type extending NgxFbBaseContent
1362
- * @param content - Signal containing the control or group content configuration
1363
- * @param name - Signal containing the name of the control
1364
- * @param testIdBuilder - Signal holding a testIdBuilder function
1365
- * @returns Computed signal that resolves to the element's ID for testing
1366
- */
1367
- function withTestId(content, name, testIdBuilder) {
1368
- const parentGroupDirective = inject((NgxfbGroupDirective), {
1369
- optional: true,
1370
- skipSelf: true,
1371
- });
1372
- const globalConfig = inject(NgxFbConfigurationService);
1373
- const globalTestIdBuilder = globalConfig.testIdBuilder;
929
+ function resolveTestId(content, name, testIdBuilder, globalTestIdBuilder, parentTestId) {
1374
930
  return computed(() => {
1375
931
  const contentValue = content();
1376
932
  const id = name();
1377
- const parentGroupTestId = parentGroupDirective?.testId();
933
+ const parentGroupTestId = parentTestId();
1378
934
  const builderFn = testIdBuilder();
1379
935
  if (builderFn) {
1380
936
  return builderFn(contentValue, id, parentGroupTestId);
@@ -1390,1040 +946,51 @@ function withTestId(content, name, testIdBuilder) {
1390
946
  }
1391
947
 
1392
948
  /**
1393
- * Creates a computed signal for the control's update strategy
949
+ * Resolves the hidden attribute value for DOM binding
1394
950
  *
1395
- * This function determines when form controls should update:
1396
- * - Uses the control's specified updateOn strategy if defined
1397
- * - Falls back to parent group's strategy when not defined in the control
1398
- * - Handles inheritance of update strategies through the form hierarchy
1399
- * - Uses the application's default strategy as final fallback
951
+ * When visibilityHandling is 'auto', returns `true` (attribute present)
952
+ * or `null` (attribute absent) based on the hidden state.
953
+ * When visibilityHandling is 'manual', always returns `null` so the
954
+ * component can handle visibility itself.
1400
955
  *
1401
- * Update strategies control when form values and validation happen:
1402
- * - 'change': Update on every change event (default Angular behavior)
1403
- * - 'blur': Update when the control loses focus
1404
- * - 'submit': Update only when the form is submitted
1405
- *
1406
- * @param content Signal containing the NgxFbAbstractControl with possible updateOn configuration
1407
- * @returns Computed signal providing the resolved update strategy
956
+ * @param options Configuration object
957
+ * @param options.hiddenSignal Signal indicating whether the element should be hidden
958
+ * @param options.hiddenHandlingSignal Signal for the visibility handling strategy
959
+ * @returns Computed signal resolving to `true` (hidden) or `null` (visible)
1408
960
  */
1409
- function withUpdateStrategy(content) {
1410
- const parentGroupDirective = inject((NgxfbGroupDirective), {
1411
- optional: true,
1412
- skipSelf: true,
1413
- });
1414
- const defaultUpdateStrategy = inject(NGX_FW_DEFAULT_UPDATE_STRATEGY);
1415
- const parentGroupUpdateStrategy = computed(() => {
1416
- return parentGroupDirective?.updateStrategy();
1417
- });
961
+ function resolveHiddenAttribute(options) {
1418
962
  return computed(() => {
1419
- return (content().updateOn ?? parentGroupUpdateStrategy() ?? defaultUpdateStrategy);
1420
- });
1421
- }
1422
-
1423
- /**
1424
- * Computes a dynamic title for a form control based on expression evaluation
1425
- *
1426
- * @param content Signal containing control configuration with dynamicTitle property
1427
- * @returns Computed signal that resolves to the evaluated dynamic title string or undefined
1428
- */
1429
- function withDynamicTitle(content) {
1430
- const formService = inject(FormService);
1431
- const expressionService = inject(ExpressionService);
1432
- const dynamicTitleAst = computed(() => {
1433
- const dynamicTitleOption = content().dynamicTitle;
1434
- if (typeof dynamicTitleOption !== 'string') {
1435
- return null;
1436
- }
1437
- return expressionService.parseExpressionToAst(dynamicTitleOption);
1438
- });
1439
- const dynamicTitleFunction = computed(() => {
1440
- const dynamicTitleOption = content().dynamicTitle;
1441
- if (typeof dynamicTitleOption !== 'function') {
963
+ const isHidden = options.hiddenSignal();
964
+ const visibilityHandling = options.hiddenHandlingSignal();
965
+ if (visibilityHandling !== 'auto') {
1442
966
  return null;
1443
967
  }
1444
- return dynamicTitleOption;
1445
- });
1446
- return computed(() => {
1447
- const reactiveFormValues = formService.formValue();
1448
- const currentSynchronousFormValues = formService.formGroup
1449
- .value;
1450
- const evaluationContext = reactiveFormValues ?? currentSynchronousFormValues;
1451
- const dynamicTitleFn = dynamicTitleFunction();
1452
- if (dynamicTitleFn) {
1453
- return dynamicTitleFn(evaluationContext);
1454
- }
1455
- const ast = dynamicTitleAst();
1456
- if (!ast) {
1457
- return undefined;
1458
- }
1459
- const title = expressionService.evaluateExpression(ast, evaluationContext);
1460
- return title;
1461
- });
1462
- }
1463
-
1464
- /**
1465
- * Core directive for creating form groups in ngx-formbar.
1466
- *
1467
- * This directive handles the integration between Angular's reactive forms and
1468
- * ngx-formbar's declarative configuration for FormGroups. It manages:
1469
- *
1470
- * - Group registration and lifecycle within parent forms
1471
- * - State management (hidden, disabled, readonly)
1472
- * - Validation setup
1473
- * - Test ID generation
1474
- * - Dynamic title support
1475
- * - Child control management
1476
- *
1477
- * Use this directive with hostDirectives in your custom group components:
1478
- *
1479
- * ```typescript
1480
- * @Component({
1481
- * hostDirectives: [
1482
- * {
1483
- * directive: NgxfbGroupDirective,
1484
- * inputs: ['content', 'name'],
1485
- * }
1486
- * ],
1487
- * })
1488
- * export class GroupComponent {
1489
- * private readonly control = inject(NgxfbGroupDirective<Group>);
1490
- * readonly content = this.control.content;
1491
- * readonly controls = this.control.controls;
1492
- * }
1493
- * ```
1494
- *
1495
- * @template T Type of the group configuration, must extend NgxFbFormGroup
1496
- */
1497
- class NgxfbGroupDirective {
1498
- parentContainer = inject(ControlContainer);
1499
- parentGroupDirective = inject((NgxfbGroupDirective), {
1500
- optional: true,
1501
- skipSelf: true,
1502
- });
1503
- /**
1504
- * Required input containing the group configuration
1505
- * Defines properties like type, controls, validation, and state expressions
1506
- */
1507
- content = input.required();
1508
- /**
1509
- * Required input for the group's name
1510
- * Used as the key in the parent FormGroup
1511
- */
1512
- name = input.required();
1513
- /**
1514
- * Signal for managing the visibility handling strategy ('auto' or 'manual')
1515
- * - 'auto': directive handles visibility via hidden attribute
1516
- * - 'manual': component handles visibility in its own template
1517
- */
1518
- visibilityHandling = signal('auto');
1519
- /**
1520
- * Signal for managing the disabled state handling strategy ('auto' or 'manual')
1521
- * - 'auto': directive handles disabled state via FormGroup methods
1522
- * - 'manual': component handles disabled state in its own template
1523
- */
1524
- disabledHandling = signal('auto');
1525
- /**
1526
- * Signal for the test ID builder function
1527
- * Used to customize how test IDs are generated
1528
- */
1529
- testIdBuilder = signal(undefined);
1530
- /**
1531
- * Computed test ID derived from the group's name
1532
- * Used for automated testing identification
1533
- *
1534
- * Access this in your component template:
1535
- * ```html
1536
- * <div [attr.data-testid]="testId()">...</div>
1537
- * ```
1538
- */
1539
- testId = withTestId(this.content, this.name, this.testIdBuilder);
1540
- /**
1541
- * Computed signal for the group's hide strategy
1542
- * Determines how the group behaves when hidden (keep or remove from form)
1543
- */
1544
- hideStrategy = computed(() => this.content().hideStrategy);
1545
- /**
1546
- * Computed signal for the group's value strategy
1547
- * Determines how the group's values are managed when visibility changes:
1548
- * - 'last': preserves last values
1549
- * - 'default': reverts to default values
1550
- * - 'reset': clears values
1551
- */
1552
- valueStrategy = computed(() => this.content().valueStrategy ?? this.parentValueStrategy());
1553
- /**
1554
- * Computed signal for the parent's value strategy
1555
- * Used when group doesn't define its own strategy
1556
- */
1557
- parentValueStrategy = computed(() => this.parentGroupDirective?.valueStrategy());
1558
- /**
1559
- * Computed signal for the hidden state
1560
- * True when the group should be hidden based on 'hidden' expression
1561
- *
1562
- * Use this in your component when implementing custom visibility handling:
1563
- * ```typescript
1564
- * readonly isHidden = this.control.isHidden;
1565
- * ```
1566
- */
1567
- isHidden = withHiddenState(this.content);
1568
- /**
1569
- * Computed signal for the hidden attribute
1570
- * Used in DOM binding to show/hide the group element
1571
- */
1572
- hiddenAttribute = withHiddenAttribute({
1573
- hiddenSignal: this.isHidden,
1574
- hiddenHandlingSignal: this.visibilityHandling,
1575
- });
1576
- /**
1577
- * Computed signal for the disabled state
1578
- * True when the group should be disabled based on 'disabled' expression
1579
- *
1580
- * Use this in your component for custom disabled state handling:
1581
- * ```typescript
1582
- * readonly disabled = this.control.disabled;
1583
- * ```
1584
- */
1585
- disabled = withDisabledState(this.content);
1586
- /**
1587
- * Computed signal for the readonly state
1588
- * True when the group should be readonly based on 'readonly' expression
1589
- *
1590
- * Use this in your component to implement readonly behavior:
1591
- * ```typescript
1592
- * readonly readonly = this.control.readonly;
1593
- * ```
1594
- */
1595
- readonly = withReadonlyState(this.content);
1596
- /**
1597
- * Computed signal for the update strategy
1598
- * Determines when form values are updated ('change', 'blur', or 'submit')
1599
- */
1600
- updateStrategy = withUpdateStrategy(this.content);
1601
- /**
1602
- * Computed signal for the dynamic title
1603
- * Contains the evaluated result of the dynamicTitle expression
1604
- *
1605
- * Use this in your component to display dynamic titles:
1606
- * ```typescript
1607
- * readonly displayTitle = computed(() => {
1608
- * const dynamic = this.control.dynamicTitle();
1609
- * return dynamic || this.content().title || '';
1610
- * });
1611
- * ```
1612
- */
1613
- dynamicTitle = withDynamicTitle(this.content);
1614
- /**
1615
- * Computed signal for the validators
1616
- * Contains validator functions derived from configuration keys
1617
- */
1618
- validators = withValidators(this.content);
1619
- /**
1620
- * Computed signal for the async validators
1621
- * Contains async validator functions derived from configuration keys
1622
- */
1623
- asyncValidators = withAsyncValidators(this.content);
1624
- /**
1625
- * Computed signal for the form group instance
1626
- * Creates a new FormGroup with appropriate validators and configuration
1627
- */
1628
- groupInstance = computed(() => {
1629
- const validators = this.validators();
1630
- const asyncValidators = this.asyncValidators();
1631
- const updateOn = this.updateStrategy();
1632
- return new FormGroup({}, {
1633
- validators,
1634
- asyncValidators,
1635
- updateOn,
1636
- });
968
+ return isHidden ? true : null;
1637
969
  });
1638
- /**
1639
- * Computed signal for the title of the group
1640
- * Returns the static title from the configuration
1641
- */
1642
- title = computed(() => this.content().title);
1643
- /**
1644
- * Computed signal for the child controls of the group
1645
- * Returns an array of [name, control] pairs for rendering
1646
- */
1647
- controls = computed(() => Object.entries(this.content().controls));
1648
- /**
1649
- * Access to the parent FormGroup containing this group
1650
- */
1651
- get parentFormGroup() {
1652
- return this.parentContainer.control;
1653
- }
1654
- /**
1655
- * Access to this group's FormGroup instance
1656
- * Use this to access validation state, errors, and other FormGroup methods
1657
- */
1658
- get formGroup() {
1659
- return this.parentFormGroup?.get(this.name());
1660
- }
1661
- constructor() {
1662
- hiddenEffect({
1663
- content: this.content,
1664
- name: this.name,
1665
- controlInstance: this.groupInstance,
1666
- hiddenSignal: this.isHidden,
1667
- hideStrategySignal: this.hideStrategy,
1668
- valueStrategySignal: this.valueStrategy,
1669
- parentValueStrategySignal: this.parentValueStrategy,
1670
- attachFunction: this.setGroup.bind(this),
1671
- detachFunction: this.removeGroup.bind(this),
1672
- valueHandleFunction: this.handleValue.bind(this),
1673
- });
1674
- disabledEffect({
1675
- disabledSignal: this.disabled,
1676
- disabledHandlingSignal: this.disabledHandling,
1677
- enableFunction: this.enableGroup.bind(this),
1678
- disableFunction: this.disableGroup.bind(this),
1679
- });
1680
- }
1681
- /**
1682
- * Sets the visibility handling strategy
1683
- * Determines if visibility should be managed by the component (manual) or by Formbar (auto)
1684
- *
1685
- * Use 'manual' when implementing custom visibility handling in your component:
1686
- * ```typescript
1687
- * constructor() {
1688
- * this.control.setVisibilityHandling('manual');
1689
- * }
1690
- * ```
1691
- *
1692
- * @param visibilityHandling Strategy for handling visibility ('auto' or 'manual')
1693
- */
1694
- setVisibilityHandling(visibilityHandling) {
1695
- this.visibilityHandling.set(visibilityHandling);
1696
- }
1697
- /**
1698
- * Sets the disabled handling strategy
1699
- * Determines if disabled state should be managed by the component (manual) or by Formbar (auto)
1700
- *
1701
- * Use 'manual' when implementing custom disabled state handling in your component:
1702
- * ```typescript
1703
- * constructor() {
1704
- * this.control.setDisabledHandling('manual');
1705
- * }
1706
- * ```
1707
- *
1708
- * @param disabledHandling Strategy for handling disabled state ('auto' or 'manual')
1709
- */
1710
- setDisabledHandling(disabledHandling) {
1711
- this.disabledHandling.set(disabledHandling);
1712
- }
1713
- /**
1714
- * Sets the function to use for building a test id.
1715
- * This allows custom test ID generation strategies to be used.
1716
- *
1717
- * @param builderFn Function that returns the test id
1718
- */
1719
- setTestIdBuilderFn(builderFn) {
1720
- this.testIdBuilder.set(builderFn);
1721
- }
1722
- /**
1723
- * Registers this group with the parent FormGroup
1724
- * @private
1725
- */
1726
- setGroup() {
1727
- this.parentFormGroup?.setControl(this.name(), this.groupInstance(), {
1728
- emitEvent: false,
1729
- });
1730
- }
1731
- /**
1732
- * Removes this group from the parent FormGroup
1733
- * @private
1734
- */
1735
- removeGroup() {
1736
- const id = this.name();
1737
- const formGroup = this.formGroup;
1738
- // Check if control exists immediately before attempting removal
1739
- if (formGroup) {
1740
- this.parentFormGroup?.removeControl(id, { emitEvent: false });
1741
- }
1742
- }
1743
- /**
1744
- * Enables the form group
1745
- * @private
1746
- */
1747
- enableGroup() {
1748
- const formGroup = this.groupInstance();
1749
- formGroup.enable({ emitEvent: false });
1750
- }
1751
- /**
1752
- * Disables the form group
1753
- * @private
1754
- */
1755
- disableGroup() {
1756
- const formGroup = this.groupInstance();
1757
- formGroup.disable({ emitEvent: false });
1758
- }
1759
- /**
1760
- * Handles value changes when visibility changes
1761
- * @param valueStrategy Strategy for handling values
1762
- * @private
1763
- */
1764
- handleValue(valueStrategy) {
1765
- switch (valueStrategy) {
1766
- case 'last':
1767
- break;
1768
- case 'default':
1769
- break;
1770
- default:
1771
- // Instead of resetting the group, we need to reset the controls individually
1772
- // to allow them to overwrite the value strategy
1773
- // If a control doesn't have a value strategy, we reset it
1774
- Object.entries(this.content().controls).forEach(([name, control]) => {
1775
- if (!('valueStrategy' in control)) {
1776
- return;
1777
- }
1778
- if (control.valueStrategy) {
1779
- return;
1780
- }
1781
- const formControl = this.formGroup?.get(name);
1782
- if (formControl) {
1783
- formControl.reset(undefined, { emitEvent: false });
1784
- }
1785
- });
1786
- break;
1787
- }
1788
- }
1789
- /**
1790
- * Removes the group when the directive is destroyed
1791
- */
1792
- ngOnDestroy() {
1793
- this.removeGroup();
1794
- }
1795
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxfbGroupDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1796
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.15", type: NgxfbGroupDirective, isStandalone: true, selector: "[ngxfbGroup]", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: true, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.hidden": "hiddenAttribute()" } }, ngImport: i0 });
1797
970
  }
1798
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxfbGroupDirective, decorators: [{
1799
- type: Directive,
1800
- args: [{
1801
- selector: '[ngxfbGroup]',
1802
- host: {
1803
- '[attr.hidden]': 'hiddenAttribute()',
1804
- },
1805
- }]
1806
- }], ctorParameters: () => [] });
1807
971
 
1808
972
  /**
1809
- * Computes a reactive disabled state based on control content
973
+ * Resolves the update strategy for a form control or group
1810
974
  *
1811
- * The disabled state is determined using the following priority:
1812
- * 1. If content.disabled is a boolean, that value is used directly
1813
- * 2. If content.disabled is an expression string, it's parsed to AST and evaluated
1814
- * against the current form values
1815
- * 3. If no disabled property is defined, the control inherits the disabled state
1816
- * from its parent group
975
+ * The strategy is determined using the following priority:
976
+ * 1. The control's own updateOn value if defined
977
+ * 2. The parent group's strategy if defined
978
+ * 3. The application-wide default strategy
1817
979
  *
1818
- * This hierarchical inheritance ensures that child controls are automatically
1819
- * disabled when their parent group is disabled, unless explicitly overridden.
1820
- *
1821
- * @param content Signal containing control configuration with potential disabled property
1822
- * @returns Computed signal that resolves to boolean disabled state
1823
- */
1824
- function withDisabledState(content) {
1825
- const formService = inject(FormService);
1826
- const expressionService = inject(ExpressionService);
1827
- const parentGroupDirective = inject((NgxfbGroupDirective), {
1828
- optional: true,
1829
- skipSelf: true,
1830
- });
1831
- const parentGroupIsDisabled = computed(() => {
1832
- const parentGroup = parentGroupDirective;
1833
- if (!parentGroup) {
1834
- return false;
1835
- }
1836
- return parentGroup.disabled();
1837
- });
1838
- const disabledAst = computed(() => {
1839
- const disabledOption = content().disabled;
1840
- if (typeof disabledOption !== 'string') {
1841
- return null;
1842
- }
1843
- return expressionService.parseExpressionToAst(disabledOption);
1844
- });
1845
- const disabledBool = computed(() => {
1846
- const disabledOption = content().disabled;
1847
- if (typeof disabledOption !== 'boolean') {
1848
- return null;
1849
- }
1850
- return disabledOption;
1851
- });
1852
- const disabledFunction = computed(() => {
1853
- const disabledOption = content().disabled;
1854
- if (typeof disabledOption !== 'function') {
1855
- return null;
1856
- }
1857
- return disabledOption;
1858
- });
1859
- return computed(() => {
1860
- const disabledStatic = disabledBool();
1861
- if (disabledStatic !== null) {
1862
- return disabledStatic;
1863
- }
1864
- const reactiveFormValues = formService.formValue();
1865
- const currentSynchronousFormValues = formService.formGroup
1866
- .value;
1867
- const evaluationContext = reactiveFormValues ?? currentSynchronousFormValues;
1868
- const disableFn = disabledFunction();
1869
- if (disableFn) {
1870
- return disableFn(evaluationContext);
1871
- }
1872
- const ast = disabledAst();
1873
- if (!ast) {
1874
- return parentGroupIsDisabled();
1875
- }
1876
- const disabled = expressionService.evaluateExpression(ast, evaluationContext) ?? false;
1877
- return disabled;
1878
- });
1879
- }
1880
- /**
1881
- * Creates an effect that manages control/group disabled state
1882
- *
1883
- * @param options Configuration object for disabled effect
1884
- * @param options.disabledSignal Signal that indicates if the component should be disabled
1885
- * @param options.disabledHandlingSignal Signal that determines how disabled state changes should be handled
1886
- * @param options.enableFunction Function to call when component should be enabled
1887
- * @param options.disableFunction Function to call when component should be disabled
1888
- */
1889
- function disabledEffect(options) {
1890
- effect(() => {
1891
- const disabled = options.disabledSignal();
1892
- const disabledHandling = options.disabledHandlingSignal();
1893
- if (disabledHandling === 'manual') {
1894
- return;
1895
- }
1896
- if (!disabled) {
1897
- untracked(() => {
1898
- options.enableFunction();
1899
- });
1900
- return;
1901
- }
1902
- untracked(() => {
1903
- options.disableFunction();
1904
- });
1905
- });
1906
- }
1907
-
1908
- function withComputedValue(content) {
1909
- const formService = inject(FormService);
1910
- const expressionService = inject(ExpressionService);
1911
- const parentContainer = inject(ControlContainer);
1912
- const computedValueAst = computed(() => {
1913
- const computedValueOption = content().computedValue;
1914
- if (typeof computedValueOption !== 'string') {
1915
- return null;
1916
- }
1917
- return expressionService.parseExpressionToAst(computedValueOption);
1918
- });
1919
- const computedValueFunction = computed(() => {
1920
- const computedValueOption = content().computedValue;
1921
- if (typeof computedValueOption !== 'function') {
1922
- return null;
1923
- }
1924
- return computedValueOption;
1925
- });
1926
- return computed(() => {
1927
- const value = formService.formValue() ?? parentContainer.value;
1928
- const computedValueFn = computedValueFunction();
1929
- if (computedValueFn) {
1930
- return computedValueFn(value);
1931
- }
1932
- const ast = computedValueAst();
1933
- if (!ast) {
1934
- return undefined;
1935
- }
1936
- return expressionService.evaluateExpression(ast, value);
1937
- });
1938
- }
1939
- function setComputedValueEffect(options) {
1940
- const parentContainer = inject(ControlContainer);
1941
- effect(() => {
1942
- const control = options.controlInstance();
1943
- const value = options.computeValueSignal();
1944
- if (!value && parentContainer.pristine) {
1945
- return;
1946
- }
1947
- control.setValue(value);
1948
- });
1949
- }
1950
-
1951
- /**
1952
- * Computes a dynamic label for a form control based on expression evaluation
1953
- *
1954
- * @param content Signal containing control configuration with dynamicLabel property
1955
- * @returns Computed signal that resolves to the evaluated dynamic label string or undefined
980
+ * @param controlUpdateOn Signal containing the control's updateOn configuration
981
+ * @param parentStrategy Signal providing the parent group's update strategy
982
+ * @param defaultStrategy The application-wide default update strategy
983
+ * @returns Computed signal providing the resolved update strategy
1956
984
  */
1957
- function withDynamicLabel(content) {
1958
- const formService = inject(FormService);
1959
- const expressionService = inject(ExpressionService);
1960
- const dynamicLabelAst = computed(() => {
1961
- const dynamicLabelOption = content().dynamicLabel;
1962
- if (typeof dynamicLabelOption !== 'string') {
1963
- return null;
1964
- }
1965
- return expressionService.parseExpressionToAst(dynamicLabelOption);
1966
- });
1967
- const dynamicLabelFunction = computed(() => {
1968
- const dynamicLabelOption = content().dynamicLabel;
1969
- if (typeof dynamicLabelOption !== 'function') {
1970
- return null;
1971
- }
1972
- return dynamicLabelOption;
1973
- });
985
+ function resolveUpdateStrategy(controlUpdateOn, parentStrategy, defaultStrategy) {
1974
986
  return computed(() => {
1975
- const reactiveFormValues = formService.formValue();
1976
- const currentSynchronousFormValues = formService.formGroup
1977
- .value;
1978
- const evaluationContext = reactiveFormValues ?? currentSynchronousFormValues;
1979
- const dynamicLabelFn = dynamicLabelFunction();
1980
- if (dynamicLabelFn) {
1981
- return dynamicLabelFn(evaluationContext);
1982
- }
1983
- const ast = dynamicLabelAst();
1984
- if (!ast) {
1985
- return undefined;
1986
- }
1987
- const label = expressionService.evaluateExpression(ast, evaluationContext);
1988
- return label;
987
+ return controlUpdateOn() ?? parentStrategy() ?? defaultStrategy;
1989
988
  });
1990
989
  }
1991
990
 
1992
- /**
1993
- * Core directive for creating form controls in ngx-formbar.
1994
- *
1995
- * This directive handles the integration between Angular's reactive forms and
1996
- * ngx-formbar's declarative configuration. It manages:
1997
- *
1998
- * - Control registration and lifecycle within parent form groups
1999
- * - State management (hidden, disabled, readonly)
2000
- * - Validation setup
2001
- * - Dynamic value computation
2002
- * - Test ID generation
2003
- * - Dynamic label support
2004
- *
2005
- * Use this directive with hostDirectives in your custom control components:
2006
- *
2007
- * ```typescript
2008
- * @Component({
2009
- * hostDirectives: [
2010
- * {
2011
- * directive: NgxfbControlDirective,
2012
- * inputs: ['content', 'name'],
2013
- * }
2014
- * ],
2015
- * })
2016
- * export class TextControlComponent {
2017
- * private readonly control = inject(NgxfbControlDirective<TextControl>);
2018
- * readonly content = this.control.content;
2019
- * readonly name = this.control.name;
2020
- * }
2021
- * ```
2022
- *
2023
- * @template T Type of the control configuration, must extend NgxFbControl
2024
- */
2025
- class NgxfbControlDirective {
2026
- parentContainer = inject(ControlContainer);
2027
- parentGroupDirective = inject((NgxfbGroupDirective), {
2028
- optional: true,
2029
- });
2030
- /**
2031
- * Required input containing the control configuration
2032
- * Defines properties like ID, default value, validation, and state expressions
2033
- */
2034
- content = input.required();
2035
- /**
2036
- * Required input for the controls name
2037
- */
2038
- name = input.required();
2039
- visibilityHandling = signal('auto');
2040
- disabledHandling = signal('auto');
2041
- testIdBuilder = signal(undefined);
2042
- /**
2043
- * Computed test ID derived from the control's ID
2044
- * Used for automated testing identification
2045
- *
2046
- * Access this in your component template:
2047
- * ```html
2048
- * <input [attr.data-testid]="testId()" ... />
2049
- * ```
2050
- */
2051
- testId = withTestId(this.content, this.name, this.testIdBuilder);
2052
- /**
2053
- * Computed signal for the control's hide strategy
2054
- * Determines how the control behaves when hidden (keep or remove from form)
2055
- */
2056
- hideStrategy = computed(() => this.content().hideStrategy);
2057
- /**
2058
- * Computed signal for the control's value strategy
2059
- * Determines how the control's value is managed when visibility changes
2060
- */
2061
- valueStrategy = computed(() => this.content().valueStrategy ?? this.parentValueStrategy());
2062
- /**
2063
- * Computed signal for the parent's value strategy
2064
- * Used when control doesn't define its own strategy
2065
- */
2066
- parentValueStrategy = computed(() => this.parentGroupDirective?.valueStrategy());
2067
- /**
2068
- * Computed signal for the hidden state
2069
- * True when the control should be hidden based on 'hidden' expression
2070
- *
2071
- * Use this in your component when implementing custom visibility handling:
2072
- * ```typescript
2073
- * readonly isHidden = this.control.isHidden;
2074
- * ```
2075
- */
2076
- isHidden = withHiddenState(this.content);
2077
- /**
2078
- * Computed signal for the hidden attribute
2079
- * Used in DOM binding to show/hide the control element
2080
- */
2081
- hiddenAttribute = withHiddenAttribute({
2082
- hiddenSignal: this.isHidden,
2083
- hiddenHandlingSignal: this.visibilityHandling,
2084
- });
2085
- /**
2086
- * Computed signal for the disabled state
2087
- * True when the control should be disabled based on 'disabled' expression
2088
- *
2089
- * Use this in your component for custom disabled state handling:
2090
- * ```typescript
2091
- * readonly disabled = this.control.disabled;
2092
- * ```
2093
- */
2094
- disabled = withDisabledState(this.content);
2095
- /**
2096
- * Computed signal for the readonly state
2097
- * True when the control should be readonly based on 'readonly' expression
2098
- *
2099
- * Use this in your component to implement readonly behavior:
2100
- * ```html
2101
- * <input [attr.readonly]="readonly() || null" ... />
2102
- * ```
2103
- */
2104
- readonly = withReadonlyState(this.content);
2105
- /**
2106
- * Computed signal for the update strategy
2107
- * Determines when form values are updated ('change', 'blur', or 'submit')
2108
- */
2109
- updateStrategy = withUpdateStrategy(this.content);
2110
- /**
2111
- * Computed signal for the dynamic label
2112
- * Contains the evaluated result of the dynamicLabel expression
2113
- *
2114
- * Use this in your component to display dynamic labels:
2115
- * ```typescript
2116
- * readonly displayLabel = computed(() => {
2117
- * const dynamic = this.control.dynamicLabel();
2118
- * return dynamic || this.content().label;
2119
- * });
2120
- * ```
2121
- */
2122
- dynamicLabel = withDynamicLabel(this.content);
2123
- /**
2124
- * Computed signal for the validators
2125
- * Contains validator functions derived from configuration keys
2126
- */
2127
- validators = withValidators(this.content);
2128
- /**
2129
- * Computed signal for the async validators
2130
- * Contains async validator functions derived from configuration keys
2131
- */
2132
- asyncValidators = withAsyncValidators(this.content);
2133
- /**
2134
- * Computed signal for the computed value
2135
- * Contains the evaluated result of computedValue expressions
2136
- */
2137
- computedValue = withComputedValue(this.content);
2138
- /**
2139
- * Computed signal for the form control instance
2140
- * Creates a new FormControl with appropriate validators and configuration
2141
- */
2142
- controlInstance = computed(() => {
2143
- const content = this.content();
2144
- const validators = this.validators();
2145
- const asyncValidators = this.asyncValidators();
2146
- const updateOn = this.updateStrategy();
2147
- return new FormControl(content.defaultValue, {
2148
- nonNullable: content.nonNullable,
2149
- validators,
2150
- asyncValidators,
2151
- updateOn,
2152
- });
2153
- });
2154
- /**
2155
- * Access to the parent FormGroup containing this control
2156
- */
2157
- get parentFormGroup() {
2158
- return this.parentContainer.control;
2159
- }
2160
- /**
2161
- * Access to this control's FormControl instance
2162
- * Use this to access validation state, errors, and other FormControl methods
2163
- *
2164
- * Example:
2165
- * ```typescript
2166
- * get hasError() {
2167
- * return this.control.formControl?.hasError('required');
2168
- * }
2169
- * ```
2170
- */
2171
- get formControl() {
2172
- const id = this.name();
2173
- if (!this.parentFormGroup?.contains(id)) {
2174
- return null;
2175
- }
2176
- return this.parentFormGroup.get(id);
2177
- }
2178
- constructor() {
2179
- hiddenEffect({
2180
- content: this.content,
2181
- name: this.name,
2182
- controlInstance: this.controlInstance,
2183
- hiddenSignal: this.isHidden,
2184
- hideStrategySignal: this.hideStrategy,
2185
- valueStrategySignal: this.valueStrategy,
2186
- parentValueStrategySignal: this.parentValueStrategy,
2187
- attachFunction: this.setControl.bind(this),
2188
- detachFunction: this.removeControl.bind(this),
2189
- valueHandleFunction: this.handleValue.bind(this),
2190
- });
2191
- disabledEffect({
2192
- disabledSignal: this.disabled,
2193
- disabledHandlingSignal: this.disabledHandling,
2194
- enableFunction: this.enableControl.bind(this),
2195
- disableFunction: this.disableControl.bind(this),
2196
- });
2197
- setComputedValueEffect({
2198
- controlInstance: this.controlInstance,
2199
- computeValueSignal: this.computedValue,
2200
- });
2201
- }
2202
- /**
2203
- * Sets the visibility handling strategy
2204
- * Determines if visibility should be managed by the component (manual) or by Formbar (auto)
2205
- *
2206
- * Use 'manual' when implementing custom visibility handling in your component:
2207
- * ```typescript
2208
- * constructor() {
2209
- * this.control.setVisibilityHandling('manual');
2210
- * }
2211
- * @param visibilityHandling Strategy for handling visibility ('auto' or 'manual')
2212
- */
2213
- setVisibilityHandling(visibilityHandling) {
2214
- this.visibilityHandling.set(visibilityHandling);
2215
- }
2216
- /**
2217
- * Sets the disabled handling strategy
2218
- * Determines if disabled state should be managed by the component (manual) or by Formbar (auto)
2219
- *
2220
- * @param disabledHandling Strategy for handling disabled state ('auto' or 'manual')
2221
- */
2222
- setDisabledHandling(disabledHandling) {
2223
- this.disabledHandling.set(disabledHandling);
2224
- }
2225
- /**
2226
- * Sets the function to use for building a test id.
2227
- *
2228
- * @param builderFn Function that returns the test id
2229
- */
2230
- setTestIdBuilderFn(builderFn) {
2231
- this.testIdBuilder.set(builderFn);
2232
- }
2233
- setControl() {
2234
- this.parentFormGroup?.setControl(this.name(), this.controlInstance(), {
2235
- emitEvent: false,
2236
- });
2237
- }
2238
- removeControl() {
2239
- const id = this.name();
2240
- const formControl = this.formControl;
2241
- // Check if control exists immediately before attempting removal
2242
- if (formControl) {
2243
- this.parentFormGroup?.removeControl(id, { emitEvent: false });
2244
- }
2245
- }
2246
- enableControl() {
2247
- const formControl = this.controlInstance();
2248
- formControl.enable({ emitEvent: false });
2249
- }
2250
- disableControl() {
2251
- const formControl = this.controlInstance();
2252
- formControl.disable({ emitEvent: false });
2253
- }
2254
- handleValue(valueStrategy) {
2255
- switch (valueStrategy) {
2256
- case 'last':
2257
- break;
2258
- case 'reset':
2259
- this.controlInstance().reset(undefined, { emitEvent: false });
2260
- break;
2261
- default:
2262
- this.controlInstance().setValue(this.content().defaultValue);
2263
- break;
2264
- }
2265
- }
2266
- ngOnDestroy() {
2267
- this.removeControl();
2268
- }
2269
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxfbControlDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2270
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.15", type: NgxfbControlDirective, isStandalone: true, selector: "[ngxfbControl]", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: true, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.hidden": "hiddenAttribute()" } }, ngImport: i0 });
2271
- }
2272
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxfbControlDirective, decorators: [{
2273
- type: Directive,
2274
- args: [{
2275
- selector: '[ngxfbControl]',
2276
- host: {
2277
- '[attr.hidden]': 'hiddenAttribute()',
2278
- },
2279
- }]
2280
- }], ctorParameters: () => [] });
2281
-
2282
- /**
2283
- * Core directive for non-form elements that appear in forms.
2284
- *
2285
- * Block elements represent UI components that don't contribute to the form's value
2286
- * but provide information or functionality within forms, such as:
2287
- * - Information blocks
2288
- * - Images
2289
- * - Dividers
2290
- * - Help text
2291
- * - Custom UI elements
2292
- *
2293
- * This directive handles visibility conditions and test ID generation for block elements.
2294
- *
2295
- * Use this directive with hostDirectives in your custom block components:
2296
- *
2297
- * ```typescript
2298
- * @Component({
2299
- * hostDirectives: [
2300
- * {
2301
- * directive: NgxfbBlockDirective,
2302
- * inputs: ['content', 'name'],
2303
- * }
2304
- * ],
2305
- * })
2306
- * export class InfoBlockComponent {
2307
- * private readonly blockDirective = inject(NgxfbBlockDirective<InfoBlock>);
2308
- * readonly content = this.blockDirective.content;
2309
- * readonly message = computed(() => this.content().message);
2310
- * }
2311
- * ```
2312
- *
2313
- * @template T Type of the block configuration, must extend NgxFbBaseContent
2314
- */
2315
- class NgxfbBlockDirective {
2316
- /**
2317
- * Reference to the parent form container.
2318
- * Provides access to the form that contains this block.
2319
- */
2320
- parentContainer = inject(ControlContainer);
2321
- /**
2322
- * Required input containing the block configuration.
2323
- * Defines properties like type, hidden condition, and custom properties.
2324
- */
2325
- content = input.required();
2326
- /**
2327
- * Required input for the block's name.
2328
- * Used as an identifier within the form.
2329
- */
2330
- name = input.required();
2331
- /**
2332
- * Signal for managing the visibility handling strategy ('auto' or 'manual').
2333
- * - 'auto': directive handles visibility via hidden attribute
2334
- * - 'manual': component handles visibility in its own template
2335
- */
2336
- visibilityHandling = signal('auto');
2337
- /**
2338
- * Signal for the test ID builder function.
2339
- * Used to customize how test IDs are generated.
2340
- */
2341
- testIdBuilder = signal(undefined);
2342
- /**
2343
- * Computed test ID derived from the block's name.
2344
- * Used for automated testing identification.
2345
- *
2346
- * Access this in your component template:
2347
- * ```html
2348
- * <div [attr.data-testid]="testId()">...</div>
2349
- * ```
2350
- */
2351
- testId = withTestId(this.content, this.name, this.testIdBuilder);
2352
- /**
2353
- * Computed signal for the hidden state.
2354
- * True when the block should be hidden based on 'hidden' expression.
2355
- *
2356
- * Use this in your component when implementing custom visibility handling:
2357
- * ```typescript
2358
- * readonly isHidden = this.blockDirective.isHidden;
2359
- * ```
2360
- */
2361
- isHidden = withHiddenState(this.content);
2362
- /**
2363
- * Computed signal for the hidden attribute.
2364
- * Used in DOM binding to show/hide the block element.
2365
- */
2366
- hiddenAttribute = withHiddenAttribute({
2367
- hiddenSignal: this.isHidden,
2368
- hiddenHandlingSignal: this.visibilityHandling,
2369
- });
2370
- /**
2371
- * Returns the parent form container.
2372
- * Provides access to the form instance that contains this block.
2373
- *
2374
- * Use this to access form data or methods:
2375
- * ```typescript
2376
- * const formData = this.blockDirective.rootForm.control.value;
2377
- * ```
2378
- */
2379
- get rootForm() {
2380
- return this.parentContainer;
2381
- }
2382
- /**
2383
- * Sets the visibility handling strategy.
2384
- * Determines if visibility should be managed by the component (manual) or by Formbar (auto).
2385
- *
2386
- * Use 'manual' when implementing custom visibility handling in your component:
2387
- * ```typescript
2388
- * constructor() {
2389
- * this.blockDirective.setVisibilityHandling('manual');
2390
- * }
2391
- * ```
2392
- *
2393
- * @param visibilityHandling Strategy for handling visibility ('auto' or 'manual')
2394
- */
2395
- setVisibilityHandling(visibilityHandling) {
2396
- this.visibilityHandling.set(visibilityHandling);
2397
- }
2398
- /**
2399
- * Sets the function to use for building a test id.
2400
- * This allows custom test ID generation strategies to be used.
2401
- *
2402
- * @param builderFn Function that returns the test id
2403
- */
2404
- setTestIdBuilderFn(builderFn) {
2405
- this.testIdBuilder.set(builderFn);
2406
- }
2407
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxfbBlockDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2408
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.15", type: NgxfbBlockDirective, isStandalone: true, selector: "[ngxfbBlock]", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: true, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.hidden": "hiddenAttribute()" } }, ngImport: i0 });
2409
- }
2410
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxfbBlockDirective, decorators: [{
2411
- type: Directive,
2412
- args: [{
2413
- selector: '[ngxfbBlock]',
2414
- host: {
2415
- '[attr.hidden]': 'hiddenAttribute()',
2416
- },
2417
- }]
2418
- }] });
2419
-
2420
- /*
2421
- * Public API Surface of core
2422
- */
2423
-
2424
991
  /**
2425
992
  * Generated bundle index. Do not edit.
2426
993
  */
2427
994
 
2428
- export { NGX_FW_ASYNC_VALIDATOR_REGISTRATIONS, NGX_FW_ASYNC_VALIDATOR_REGISTRATIONS_RESOLVED, NGX_FW_COMPONENT_REGISTRATIONS, NGX_FW_COMPONENT_RESOLVER, NGX_FW_CONFIG, NGX_FW_CONFIG_RESOLVED, NGX_FW_DEFAULT_ASYNC_VALIDATOR_REGISTRATIONS, NGX_FW_DEFAULT_CONFIG, NGX_FW_DEFAULT_UPDATE_STRATEGY, NGX_FW_DEFAULT_VALIDATOR_REGISTRATIONS, NGX_FW_VALIDATOR_REGISTRATIONS, NGX_FW_VALIDATOR_REGISTRATIONS_RESOLVED, NGX_VALIDATOR_RESOLVER, NgxfbAbstractControlDirective, NgxfbBlockDirective, NgxfbControlDirective, NgxfbFormComponent, NgxfbGroupDirective, defineFormbarConfig, provideFormbar };
995
+ export { ComponentRegistrationService, ExpressionService, NGX_FW_COMPONENT_REGISTRATIONS, NGX_FW_COMPONENT_RESOLVER, NGX_FW_CONFIG, NGX_FW_CONFIG_RESOLVED, NGX_FW_DEFAULT_CONFIG, NGX_FW_DEFAULT_UPDATE_STRATEGY, NgxFbConfigurationService, loadComponent, resolveDisabledEffect, resolveExpression, resolveHiddenAttribute, resolveHiddenState, resolveInheritableExpression, resolveTestId, resolveUpdateStrategy, staticComponent, toSafeString };
2429
996
  //# sourceMappingURL=ngx-formbar-core.mjs.map