@ng-forge/dynamic-forms 1.0.0 → 1.1.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-CIheZh11.mjs → ng-forge-dynamic-forms-array-field.component-CftX-bCB.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-CIheZh11.mjs.map → ng-forge-dynamic-forms-array-field.component-CftX-bCB.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-C9aVachF.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CFSJLZPZ.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-C9aVachF.mjs.map → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CFSJLZPZ.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-DNafCSO8.mjs → ng-forge-dynamic-forms-container-field.component-B_bQ-f2g.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-DNafCSO8.mjs.map → ng-forge-dynamic-forms-container-field.component-B_bQ-f2g.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-BmHGlmES.mjs → ng-forge-dynamic-forms-css-wrapper.component-D_yRuLpu.mjs} +4 -3
- package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-D_yRuLpu.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-CKVIw2K5.mjs → ng-forge-dynamic-forms-group-field.component-Bl9WRPvF.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-CKVIw2K5.mjs.map → ng-forge-dynamic-forms-group-field.component-Bl9WRPvF.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs +48 -18
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs +77 -35
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-CBawLbtp.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-fMB33l2f.mjs} +22 -14
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-fMB33l2f.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-BRixkgyS.mjs → ng-forge-dynamic-forms-page-field.component-C9bQkVjQ.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-BRixkgyS.mjs.map → ng-forge-dynamic-forms-page-field.component-C9bQkVjQ.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
- package/package.json +1 -1
- package/types/ng-forge-dynamic-forms-integration.d.ts +28 -2
- package/types/ng-forge-dynamic-forms-internal.d.ts +35 -3
- package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-BmHGlmES.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CBawLbtp.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, DestroyRef, Injector, EnvironmentInjector, viewChild, TemplateRef, ViewContainerRef, input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
-
import { i as injectFieldRegistry, c as createFieldResolutionPipe, r as resolveWrappers, a as isSameWrapperChain, b as createWrapperChainController, D as DfFieldOutlet } from './ng-forge-dynamic-forms-ng-forge-dynamic-forms-
|
|
3
|
+
import { i as injectFieldRegistry, c as createFieldResolutionPipe, r as resolveWrappers, a as isSameWrapperChain, b as createWrapperChainController, D as DfFieldOutlet } from './ng-forge-dynamic-forms-ng-forge-dynamic-forms-fMB33l2f.mjs';
|
|
4
4
|
import { EventBus, DynamicFormLogger, WRAPPER_AUTO_ASSOCIATIONS, DEFAULT_WRAPPERS, derivedFromDeferred } from '@ng-forge/dynamic-forms/internal';
|
|
5
5
|
import { c as computeContainerHostClasses, s as setupContainerInitEffect } from './ng-forge-dynamic-forms-container-utils-BZFz6ztF.mjs';
|
|
6
6
|
|
|
@@ -86,4 +86,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
86
86
|
}], ctorParameters: () => [], propDecorators: { childrenTpl: [{ type: i0.ViewChild, args: ['childrenTpl', { ...{ read: TemplateRef }, isSignal: true }] }], wrapperContainer: [{ type: i0.ViewChild, args: ['wrapperContainer', { ...{ read: ViewContainerRef }, isSignal: true }] }], field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }] } });
|
|
87
87
|
|
|
88
88
|
export { ContainerFieldComponent, ContainerFieldComponent as default };
|
|
89
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-container-field.component-
|
|
89
|
+
//# sourceMappingURL=ng-forge-dynamic-forms-container-field.component-B_bQ-f2g.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ng-forge-dynamic-forms-container-field.component-
|
|
1
|
+
{"version":3,"file":"ng-forge-dynamic-forms-container-field.component-B_bQ-f2g.mjs","mappings":";;;;;;AAoDc;AACK;;AAEA;AACE;AACF;AACA;AACA;;AAGA;AACA;;;;;AAIjB;;;;AAGS;;AACA;;;;AAGQ;;AAEE;AAGf;AACA;;;AAGA;AACE;;;AAKI;;AAIP;AAIc;AAIjB;;AAGE;;;AAGE;AACD;;;AAxDgB;;;;;;;;;AAZlB;;;;AAXS;;;;;;;;;;AAcR;AACA;AACA;AACA;AACA;AACA;;AAcgD","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms/src/lib/fields/container/container-field.component.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n EnvironmentInjector,\n inject,\n Injector,\n input,\n TemplateRef,\n viewChild,\n ViewContainerRef,\n} from '@angular/core';\nimport { DfFieldOutlet } from '../../directives/df-field-outlet/df-field-outlet.directive';\nimport { derivedFromDeferred } from '@ng-forge/dynamic-forms/internal';\nimport { createFieldResolutionPipe, ResolvedField } from '../../utils/resolve-field/resolve-field';\nimport { computeContainerHostClasses, setupContainerInitEffect } from '../../utils/container-utils/container-utils';\nimport { ContainerField } from '@ng-forge/dynamic-forms/internal';\nimport { injectFieldRegistry } from '../../utils/inject-field-registry/inject-field-registry';\nimport { EventBus } from '@ng-forge/dynamic-forms/internal';\nimport { FieldDef } from '@ng-forge/dynamic-forms/internal';\nimport { DynamicFormLogger } from '@ng-forge/dynamic-forms/internal';\nimport { WRAPPER_AUTO_ASSOCIATIONS } from '@ng-forge/dynamic-forms/internal';\nimport { DEFAULT_WRAPPERS } from '@ng-forge/dynamic-forms/internal';\nimport { isSameWrapperChain, resolveWrappers } from '../../utils/resolve-wrappers/resolve-wrappers';\nimport { createWrapperChainController } from '../../utils/wrapper-chain/wrapper-chain-controller';\n\n/** Layout container that wraps child fields with UI chrome. */\n@Component({\n selector: 'div[container-field]',\n imports: [DfFieldOutlet],\n template: `\n <ng-template #childrenTpl>\n @for (field of resolvedFields(); track field.key) {\n @if (!field.hidden()) {\n <ng-container *dfFieldOutlet=\"field; environmentInjector: environmentInjector\" />\n }\n }\n </ng-template>\n <ng-container #wrapperContainer></ng-container>\n `,\n styleUrl: './container-field.component.scss',\n host: {\n '[class]': 'hostClasses()',\n '[class.disabled]': 'disabled()',\n '[class.df-container-hidden]': 'hidden()',\n '[attr.aria-hidden]': 'hidden() || null',\n '[id]': '`${key()}`',\n '[attr.data-testid]': 'key()',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class ContainerFieldComponent {\n private readonly destroyRef = inject(DestroyRef);\n private readonly fieldRegistry = injectFieldRegistry();\n private readonly injector = inject(Injector);\n protected readonly environmentInjector = inject(EnvironmentInjector);\n private readonly eventBus = inject(EventBus);\n private readonly logger = inject(DynamicFormLogger);\n private readonly wrapperAutoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);\n private readonly defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });\n\n private readonly childrenTpl = viewChild.required('childrenTpl', { read: TemplateRef });\n private readonly wrapperContainer = viewChild.required('wrapperContainer', { read: ViewContainerRef });\n\n field = input.required<ContainerField>();\n key = input.required<string>();\n className = input<string>();\n hidden = input(false);\n\n readonly hostClasses = computed(() => computeContainerHostClasses('container', this.className()));\n readonly disabled = computed(() => this.field().disabled || false);\n\n private readonly rawFieldRegistry = computed(() => this.fieldRegistry.raw);\n private readonly fieldsSource = computed(() => (this.field().fields || []) as FieldDef<unknown>[]);\n\n protected readonly resolvedFields = derivedFromDeferred(\n this.fieldsSource,\n createFieldResolutionPipe(() => ({\n loadTypeComponent: (type: string) => this.fieldRegistry.loadTypeComponent(type),\n registry: this.rawFieldRegistry(),\n injector: this.injector,\n destroyRef: this.destroyRef,\n onError: (fieldDef: FieldDef<unknown>, error: unknown) => {\n const fieldKey = fieldDef.key || '<no key>';\n\n const containerKey = this.field().key || '<no key>';\n this.logger.error(\n `Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +\n `within container '${containerKey}'. Ensure the field type is registered in your field registry.`,\n error,\n );\n },\n })),\n { initialValue: [] as ResolvedField[], injector: this.injector },\n );\n\n private readonly wrappers = computed(() => resolveWrappers(this.field(), this.defaultWrappersSignal?.(), this.wrapperAutoAssociations), {\n equal: isSameWrapperChain,\n });\n\n constructor() {\n setupContainerInitEffect(this.resolvedFields, this.eventBus, 'container', () => this.field().key, this.injector);\n\n createWrapperChainController({\n vcr: this.wrapperContainer,\n wrappers: this.wrappers,\n renderInnermost: (slot) => slot.createEmbeddedView(this.childrenTpl()),\n });\n }\n}\n\nexport { ContainerFieldComponent };\n"]}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { viewChild, ViewContainerRef, input, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
import { inject, Injector, viewChild, ViewContainerRef, input, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import { pipe, switchMap } from 'rxjs';
|
|
4
4
|
import { derivedFrom } from 'ngxtension/derived-from';
|
|
5
5
|
import { dynamicTextToObservable } from '@ng-forge/dynamic-forms/internal';
|
|
6
6
|
|
|
7
7
|
class CssWrapperComponent {
|
|
8
|
+
injector = inject(Injector);
|
|
8
9
|
fieldComponent = viewChild.required('fieldComponent', { read: ViewContainerRef });
|
|
9
10
|
cssClasses = input(/* @ts-ignore */
|
|
10
11
|
...(ngDevMode ? [undefined, { debugName: "cssClasses" }] : /* istanbul ignore next */ []));
|
|
11
12
|
fieldInputs = input(/* @ts-ignore */
|
|
12
13
|
...(ngDevMode ? [undefined, { debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
|
|
13
|
-
resolvedClasses = derivedFrom([this.cssClasses], pipe(switchMap(([value]) => dynamicTextToObservable(value))), {
|
|
14
|
+
resolvedClasses = derivedFrom([this.cssClasses], pipe(switchMap(([value]) => dynamicTextToObservable(value, this.injector))), {
|
|
14
15
|
initialValue: '',
|
|
15
16
|
});
|
|
16
17
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: CssWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -29,4 +30,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
29
30
|
}], propDecorators: { fieldComponent: [{ type: i0.ViewChild, args: ['fieldComponent', { ...{ read: ViewContainerRef }, isSignal: true }] }], cssClasses: [{ type: i0.Input, args: [{ isSignal: true, alias: "cssClasses", required: false }] }], fieldInputs: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldInputs", required: false }] }] } });
|
|
30
31
|
|
|
31
32
|
export { CssWrapperComponent, CssWrapperComponent as default };
|
|
32
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-css-wrapper.component-
|
|
33
|
+
//# sourceMappingURL=ng-forge-dynamic-forms-css-wrapper.component-D_yRuLpu.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ng-forge-dynamic-forms-css-wrapper.component-D_yRuLpu.mjs","mappings":";;;;;;AAiBc;AACK;AAER;AAEA;;AACA;;AAEA;AAIL;AACD;;;;;;AArBM;AACT;AACA;;AAEA;AACE;AACD;AACF;AAI8C","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms/src/lib/wrappers/css/css-wrapper.component.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, Injector, input, Signal, ViewContainerRef, viewChild } from '@angular/core';\nimport { pipe, switchMap } from 'rxjs';\nimport { derivedFrom } from 'ngxtension/derived-from';\nimport { FieldWrapper } from '@ng-forge/dynamic-forms/internal';\nimport { DynamicText } from '@ng-forge/dynamic-forms/internal';\nimport { dynamicTextToObservable } from '@ng-forge/dynamic-forms/internal';\nimport { WrapperFieldInputs } from '@ng-forge/dynamic-forms/internal';\n\n/** Built-in CSS wrapper component. */\n@Component({\n selector: 'df-css-wrapper',\n template: `<ng-container #fieldComponent></ng-container>`,\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'resolvedClasses()',\n },\n})\nexport default class CssWrapperComponent implements FieldWrapper {\n private readonly injector = inject(Injector);\n\n readonly fieldComponent = viewChild.required('fieldComponent', { read: ViewContainerRef });\n\n readonly cssClasses = input<DynamicText>();\n readonly fieldInputs = input<WrapperFieldInputs>();\n\n readonly resolvedClasses: Signal<string> = derivedFrom(\n [this.cssClasses],\n pipe(switchMap(([value]) => dynamicTextToObservable(value, this.injector))),\n {\n initialValue: '',\n },\n );\n}\n\nexport { CssWrapperComponent };\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, DestroyRef, Injector, EnvironmentInjector, input, computed, linkedSignal, runInInjectionContext, untracked, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
-
import { i as injectFieldRegistry, C as CONTAINER_FIELD_PROCESSORS, d as createSchemaFromFields, c as createFieldResolutionPipe, D as DfFieldOutlet } from './ng-forge-dynamic-forms-ng-forge-dynamic-forms-
|
|
3
|
+
import { i as injectFieldRegistry, C as CONTAINER_FIELD_PROCESSORS, d as createSchemaFromFields, c as createFieldResolutionPipe, D as DfFieldOutlet } from './ng-forge-dynamic-forms-ng-forge-dynamic-forms-fMB33l2f.mjs';
|
|
4
4
|
import { outputFromObservable, toObservable } from '@angular/core/rxjs-interop';
|
|
5
5
|
import { explicitEffect } from 'ngxtension/explicit-effect';
|
|
6
6
|
import { injectFieldSignalContext, GROUP_CONTEXT, EventBus, DynamicFormLogger, isEqual, FIELD_SIGNAL_CONTEXT, derivedFromDeferred } from '@ng-forge/dynamic-forms/internal';
|
|
@@ -178,4 +178,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
178
178
|
}], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], validityChange: [{ type: i0.Output, args: ["validityChange"] }], dirtyChange: [{ type: i0.Output, args: ["dirtyChange"] }], submitted: [{ type: i0.Output, args: ["submitted"] }] } });
|
|
179
179
|
|
|
180
180
|
export { GroupFieldComponent, GroupFieldComponent as default };
|
|
181
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-group-field.component-
|
|
181
|
+
//# sourceMappingURL=ng-forge-dynamic-forms-group-field.component-Bl9WRPvF.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ng-forge-dynamic-forms-group-field.component-CKVIw2K5.mjs","mappings":";;;;;;;;;AA0Dc;YAKK;;;;AAIA;AACE;AACF;AACA;YAMA;;;;;AAQjB;;;;YAOS;;;;AAIQ;AACf;AACA;AAEA;AACE;AACA;;AAEA;;AAGE;AACA;;;;;;;AAQF;AACA;AACA;AACA;;;;;;;AAOJ,IAKiB;;;AAIb;;AAEA;AACF;AAEE;AACA;AACD;AAGc;AACf;AACE;;AAGE;AACA;;AAGF;AACF;;;;;AAQO;;AACA;;AACA;;AACA;;AACA;;AACA;;AAEQ;AACf;;AAEA;;;;AAIA;;;AAGE;;AAEA;AACE;;;;AAKN,IAMiB;AACf;;;AAGE;AACF;;AAGe;;AAEf;;;AAGC;AACF;;;AAQQ;;;AAQU;AAGf;AACA;;;AAGA;AACE;;;;AAOH;YAQH;;;;;AAWE;;AAIM;;AAIJ;;;AA/Me;;;;;;AAhBlB;;;;AARS;;;;;;;AAWR;AACA;AACA;AACA;AACA;wEAIA;AACA","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms/src/lib/fields/group/group-field.component.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n EnvironmentInjector,\n inject,\n Injector,\n input,\n linkedSignal,\n runInInjectionContext,\n untracked,\n} from '@angular/core';\nimport { DfFieldOutlet } from '../../directives/df-field-outlet/df-field-outlet.directive';\nimport { outputFromObservable, toObservable } from '@angular/core/rxjs-interop';\nimport { explicitEffect } from 'ngxtension/explicit-effect';\nimport { derivedFromDeferred } from '@ng-forge/dynamic-forms/internal';\nimport { createFieldResolutionPipe, ResolvedField } from '../../utils/resolve-field/resolve-field';\nimport { computeContainerHostClasses, setupContainerInitEffect } from '../../utils/container-utils/container-utils';\nimport { CONTAINER_FIELD_PROCESSORS } from '../../utils/container-utils/container-field-processors';\nimport { isEqual } from '@ng-forge/dynamic-forms/internal';\nimport { DynamicFormLogger } from '@ng-forge/dynamic-forms/internal';\nimport { GroupField } from '@ng-forge/dynamic-forms/internal';\nimport { injectFieldRegistry } from '../../utils/inject-field-registry/inject-field-registry';\nimport { FieldTree, form } from '@angular/forms/signals';\nimport { FieldDef } from '@ng-forge/dynamic-forms/internal';\nimport { FieldSignalContext, GroupContext } from '@ng-forge/dynamic-forms/internal';\nimport { FIELD_SIGNAL_CONTEXT, GROUP_CONTEXT, injectFieldSignalContext } from '@ng-forge/dynamic-forms/internal';\nimport { createSchemaFromFields } from '../../core/schema-builder';\nimport { EventBus } from '@ng-forge/dynamic-forms/internal';\nimport { FormSubmitEvent } from '../../events/constants/submit.event';\n\n/** Container component for rendering nested form groups. */\n@Component({\n selector: 'fieldset[group-field]',\n imports: [DfFieldOutlet],\n template: `\n @for (field of resolvedFields(); track field.key) {\n @if (!field.hidden()) {\n <ng-container *dfFieldOutlet=\"field; environmentInjector: environmentInjector\" />\n }\n }\n `,\n styleUrl: './group-field.component.scss',\n host: {\n '[class]': 'hostClasses()',\n role: 'group',\n '[class.disabled]': 'disabled()',\n '[class.df-container-hidden]': 'hidden()',\n '[attr.aria-hidden]': 'hidden() || null',\n // The group element's own id is the plain `key()` — only its DESCENDANTS\n // compose scoped ids via GROUP_CONTEXT (e.g. children of an `address`\n // group render as `address_street`). The group selector is `#address`.\n '[id]': '`${key()}`',\n '[attr.data-testid]': 'key()',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class GroupFieldComponent<TModel extends Record<string, unknown> = Record<string, unknown>> {\n // ─────────────────────────────────────────────────────────────────────────────\n // Dependencies\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly fieldRegistry = injectFieldRegistry();\n private readonly parentFieldSignalContext = injectFieldSignalContext<TModel>();\n private readonly parentGroupContext = inject(GROUP_CONTEXT, { optional: true });\n private readonly injector = inject(Injector);\n protected readonly environmentInjector = inject(EnvironmentInjector);\n private readonly eventBus = inject(EventBus);\n private readonly logger = inject(DynamicFormLogger);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Memoized Functions\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly fieldProcessors = inject(CONTAINER_FIELD_PROCESSORS);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Inputs\n // ─────────────────────────────────────────────────────────────────────────────\n\n field = input.required<GroupField>();\n key = input.required<string>();\n className = input<string>();\n hidden = input(false);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Computed Signals\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly hostClasses = computed(() => computeContainerHostClasses('group', this.className()));\n\n private readonly rawFieldRegistry = computed(() => this.fieldRegistry.raw);\n\n private readonly formSetup = computed(() => {\n const groupField = this.field();\n const registry = this.rawFieldRegistry();\n\n if (groupField.fields && groupField.fields.length > 0) {\n const flattenedFields = this.fieldProcessors.memoizedFlattenFields(groupField.fields, registry);\n const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(groupField.fields, registry);\n const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);\n const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);\n\n return {\n fields: flattenedFieldsForRendering,\n schemaFields: flattenedFields,\n originalFields: groupField.fields,\n defaultValues,\n registry,\n };\n }\n\n return {\n fields: [],\n schemaFields: [],\n originalFields: [],\n defaultValues: {},\n registry,\n };\n });\n\n readonly defaultValues = linkedSignal(() => this.formSetup().defaultValues);\n\n /**\n * Entity computed from parent value, group key, and defaults.\n * Uses deep equality check to prevent unnecessary updates when\n * object spread creates new references with identical values.\n */\n private readonly entity = linkedSignal(\n () => {\n const parentValue = this.parentFieldSignalContext.value();\n const groupKey = this.field().key;\n const defaults = this.defaultValues();\n const groupValue = (parentValue as Record<string, unknown>)?.[groupKey] || {};\n return { ...defaults, ...groupValue };\n },\n {\n debugName: 'GroupFieldComponent.entity',\n equal: isEqual,\n },\n );\n\n private readonly form = computed(() => {\n return runInInjectionContext(this.injector, () => {\n const setup = this.formSetup();\n\n if (setup.schemaFields.length > 0) {\n const schema = createSchemaFromFields(setup.schemaFields, setup.registry);\n return untracked(() => form(this.entity, schema));\n }\n\n return untracked(() => form(this.entity));\n });\n });\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Public State Signals\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly formValue = computed(() => this.entity());\n readonly valid = computed(() => this.form()().valid());\n readonly invalid = computed(() => this.form()().invalid());\n readonly dirty = computed(() => this.form()().dirty());\n readonly touched = computed(() => this.form()().touched());\n readonly errors = computed(() => this.form()().errors());\n readonly disabled = computed(() => this.form()().disabled());\n\n private readonly nestedFieldTree = computed((): FieldTree<Record<string, unknown>> | undefined => {\n const parentForm = this.parentFieldSignalContext.form as Record<string, FieldTree<Record<string, unknown>>>;\n const groupKey = this.field().key;\n return parentForm[groupKey];\n });\n\n private readonly groupFieldSignalContext: FieldSignalContext<Record<string, unknown>> = (() => {\n const nestedFieldTree = this.nestedFieldTree;\n return {\n injector: this.injector,\n value: this.parentFieldSignalContext.value,\n defaultValues: this.defaultValues,\n get form() {\n return nestedFieldTree() ?? ({} as FieldTree<Record<string, unknown>>);\n },\n };\n })();\n\n /**\n * Group context propagated to descendants — carries the dot-separated path of\n * group ancestors so child fields can scope their DOM IDs (issue #401).\n * Reactive via `computed()`: a nested group composes its path from the parent\n * context signal + its own key, so the chain stays consistent if either changes.\n */\n private readonly groupContext: GroupContext = {\n groupPath: computed(() => {\n const ownKey = this.field().key;\n const parentPath = this.parentGroupContext?.groupPath();\n return parentPath ? `${parentPath}.${ownKey}` : ownKey;\n }),\n };\n\n private readonly groupInjector = Injector.create({\n parent: this.injector,\n providers: [\n { provide: FIELD_SIGNAL_CONTEXT, useValue: this.groupFieldSignalContext },\n { provide: GROUP_CONTEXT, useValue: this.groupContext },\n ],\n });\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Outputs\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly validityChange = outputFromObservable(toObservable(this.valid));\n readonly dirtyChange = outputFromObservable(toObservable(this.dirty));\n readonly submitted = outputFromObservable(this.eventBus.on<FormSubmitEvent>('submit'));\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Field Resolution\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly fieldsSource = computed(() => this.formSetup().fields);\n\n protected readonly resolvedFields = derivedFromDeferred(\n this.fieldsSource,\n createFieldResolutionPipe(() => ({\n loadTypeComponent: (type: string) => this.fieldRegistry.loadTypeComponent(type),\n registry: this.rawFieldRegistry(),\n injector: this.groupInjector,\n destroyRef: this.destroyRef,\n onError: (fieldDef: FieldDef<unknown>, error: unknown) => {\n const fieldKey = fieldDef.key || '<no key>';\n this.logger.error(\n `Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +\n `within group '${this.field().key}'. Ensure the field type is registered in your field registry.`,\n error,\n );\n },\n })),\n { initialValue: [] as ResolvedField[], injector: this.injector },\n );\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Constructor\n // ─────────────────────────────────────────────────────────────────────────────\n\n constructor() {\n this.setupEffects();\n }\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Private Methods\n // ─────────────────────────────────────────────────────────────────────────────\n\n private setupEffects(): void {\n setupContainerInitEffect(this.resolvedFields, this.eventBus, 'group', () => this.field().key, this.injector);\n\n explicitEffect(\n [this.nestedFieldTree, this.field],\n ([tree, field]) => {\n if (!tree) {\n this.logger.warn(\n `Group field \"${field.key}\" not found in parent form. ` + `Ensure the parent form schema includes this group field.`,\n );\n }\n },\n { defer: true },\n );\n }\n}\n\nexport { GroupFieldComponent };\n"]}
|
|
1
|
+
{"version":3,"file":"ng-forge-dynamic-forms-group-field.component-Bl9WRPvF.mjs","mappings":";;;;;;;;;AA0Dc;YAKK;;;;AAIA;AACE;AACF;AACA;YAMA;;;;;AAQjB;;;;YAOS;;;;AAIQ;AACf;AACA;AAEA;AACE;AACA;;AAEA;;AAGE;AACA;;;;;;;AAQF;AACA;AACA;AACA;;;;;;;AAOJ,IAKiB;;;AAIb;;AAEA;AACF;AAEE;AACA;AACD;AAGc;AACf;AACE;;AAGE;AACA;;AAGF;AACF;;;;;AAQO;;AACA;;AACA;;AACA;;AACA;;AACA;;AAEQ;AACf;;AAEA;;;;AAIA;;;AAGE;;AAEA;AACE;;;;AAKN,IAMiB;AACf;;;AAGE;AACF;;AAGe;;AAEf;;;AAGC;AACF;;;AAQQ;;;AAQU;AAGf;AACA;;;AAGA;AACE;;;;AAOH;YAQH;;;;;AAWE;;AAIM;;AAIJ;;;AA/Me;;;;;;AAhBlB;;;;AARS;;;;;;;AAWR;AACA;AACA;AACA;AACA;wEAIA;AACA","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms/src/lib/fields/group/group-field.component.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n EnvironmentInjector,\n inject,\n Injector,\n input,\n linkedSignal,\n runInInjectionContext,\n untracked,\n} from '@angular/core';\nimport { DfFieldOutlet } from '../../directives/df-field-outlet/df-field-outlet.directive';\nimport { outputFromObservable, toObservable } from '@angular/core/rxjs-interop';\nimport { explicitEffect } from 'ngxtension/explicit-effect';\nimport { derivedFromDeferred } from '@ng-forge/dynamic-forms/internal';\nimport { createFieldResolutionPipe, ResolvedField } from '../../utils/resolve-field/resolve-field';\nimport { computeContainerHostClasses, setupContainerInitEffect } from '../../utils/container-utils/container-utils';\nimport { CONTAINER_FIELD_PROCESSORS } from '../../utils/container-utils/container-field-processors';\nimport { isEqual } from '@ng-forge/dynamic-forms/internal';\nimport { DynamicFormLogger } from '@ng-forge/dynamic-forms/internal';\nimport { GroupField } from '@ng-forge/dynamic-forms/internal';\nimport { injectFieldRegistry } from '../../utils/inject-field-registry/inject-field-registry';\nimport { FieldTree, form } from '@angular/forms/signals';\nimport { FieldDef } from '@ng-forge/dynamic-forms/internal';\nimport { FieldSignalContext, GroupContext } from '@ng-forge/dynamic-forms/internal';\nimport { FIELD_SIGNAL_CONTEXT, GROUP_CONTEXT, injectFieldSignalContext } from '@ng-forge/dynamic-forms/internal';\nimport { createSchemaFromFields } from '../../core/schema-builder';\nimport { EventBus } from '@ng-forge/dynamic-forms/internal';\nimport { FormSubmitEvent } from '../../events/constants/submit.event';\n\n/** Container component for rendering nested form groups. */\n@Component({\n selector: 'fieldset[group-field]',\n imports: [DfFieldOutlet],\n template: `\n @for (field of resolvedFields(); track field.key) {\n @if (!field.hidden()) {\n <ng-container *dfFieldOutlet=\"field; environmentInjector: environmentInjector\" />\n }\n }\n `,\n styleUrl: './group-field.component.scss',\n host: {\n '[class]': 'hostClasses()',\n role: 'group',\n '[class.disabled]': 'disabled()',\n '[class.df-container-hidden]': 'hidden()',\n '[attr.aria-hidden]': 'hidden() || null',\n // The group element's own id is the plain `key()` — only its DESCENDANTS\n // compose scoped ids via GROUP_CONTEXT (e.g. children of an `address`\n // group render as `address_street`). The group selector is `#address`.\n '[id]': '`${key()}`',\n '[attr.data-testid]': 'key()',\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport default class GroupFieldComponent<TModel extends Record<string, unknown> = Record<string, unknown>> {\n // ─────────────────────────────────────────────────────────────────────────────\n // Dependencies\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly fieldRegistry = injectFieldRegistry();\n private readonly parentFieldSignalContext = injectFieldSignalContext<TModel>();\n private readonly parentGroupContext = inject(GROUP_CONTEXT, { optional: true });\n private readonly injector = inject(Injector);\n protected readonly environmentInjector = inject(EnvironmentInjector);\n private readonly eventBus = inject(EventBus);\n private readonly logger = inject(DynamicFormLogger);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Memoized Functions\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly fieldProcessors = inject(CONTAINER_FIELD_PROCESSORS);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Inputs\n // ─────────────────────────────────────────────────────────────────────────────\n\n field = input.required<GroupField>();\n key = input.required<string>();\n className = input<string>();\n hidden = input(false);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Computed Signals\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly hostClasses = computed(() => computeContainerHostClasses('group', this.className()));\n\n private readonly rawFieldRegistry = computed(() => this.fieldRegistry.raw);\n\n private readonly formSetup = computed(() => {\n const groupField = this.field();\n const registry = this.rawFieldRegistry();\n\n if (groupField.fields && groupField.fields.length > 0) {\n const flattenedFields = this.fieldProcessors.memoizedFlattenFields(groupField.fields, registry);\n const flattenedFieldsForRendering = this.fieldProcessors.memoizedFlattenFieldsForRendering(groupField.fields, registry);\n const fieldsById = this.fieldProcessors.memoizedKeyBy(flattenedFields);\n const defaultValues = this.fieldProcessors.memoizedDefaultValues(fieldsById, registry);\n\n return {\n fields: flattenedFieldsForRendering,\n schemaFields: flattenedFields,\n originalFields: groupField.fields,\n defaultValues,\n registry,\n };\n }\n\n return {\n fields: [],\n schemaFields: [],\n originalFields: [],\n defaultValues: {},\n registry,\n };\n });\n\n readonly defaultValues = linkedSignal(() => this.formSetup().defaultValues);\n\n /**\n * Entity computed from parent value, group key, and defaults.\n * Uses deep equality check to prevent unnecessary updates when\n * object spread creates new references with identical values.\n */\n private readonly entity = linkedSignal(\n () => {\n const parentValue = this.parentFieldSignalContext.value();\n const groupKey = this.field().key;\n const defaults = this.defaultValues();\n const groupValue = (parentValue as Record<string, unknown>)?.[groupKey] || {};\n return { ...defaults, ...groupValue };\n },\n {\n debugName: 'GroupFieldComponent.entity',\n equal: isEqual,\n },\n );\n\n private readonly form = computed(() => {\n return runInInjectionContext(this.injector, () => {\n const setup = this.formSetup();\n\n if (setup.schemaFields.length > 0) {\n const schema = createSchemaFromFields(setup.schemaFields, setup.registry);\n return untracked(() => form(this.entity, schema));\n }\n\n return untracked(() => form(this.entity));\n });\n });\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Public State Signals\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly formValue = computed(() => this.entity());\n readonly valid = computed(() => this.form()().valid());\n readonly invalid = computed(() => this.form()().invalid());\n readonly dirty = computed(() => this.form()().dirty());\n readonly touched = computed(() => this.form()().touched());\n readonly errors = computed(() => this.form()().errors());\n readonly disabled = computed(() => this.form()().disabled());\n\n private readonly nestedFieldTree = computed((): FieldTree<Record<string, unknown>> | undefined => {\n const parentForm = this.parentFieldSignalContext.form as Record<string, FieldTree<Record<string, unknown>>>;\n const groupKey = this.field().key;\n return parentForm[groupKey];\n });\n\n private readonly groupFieldSignalContext: FieldSignalContext<Record<string, unknown>> = (() => {\n const nestedFieldTree = this.nestedFieldTree;\n return {\n injector: this.injector,\n value: this.parentFieldSignalContext.value,\n defaultValues: this.defaultValues,\n get form() {\n return nestedFieldTree() ?? ({} as FieldTree<Record<string, unknown>>);\n },\n };\n })();\n\n /**\n * Group context propagated to descendants — carries the dot-separated path of\n * group ancestors so child fields can scope their DOM IDs (issue #401).\n * Reactive via `computed()`: a nested group composes its path from the parent\n * context signal + its own key, so the chain stays consistent if either changes.\n */\n private readonly groupContext: GroupContext = {\n groupPath: computed(() => {\n const ownKey = this.field().key;\n const parentPath = this.parentGroupContext?.groupPath();\n return parentPath ? `${parentPath}.${ownKey}` : ownKey;\n }),\n };\n\n private readonly groupInjector = Injector.create({\n parent: this.injector,\n providers: [\n { provide: FIELD_SIGNAL_CONTEXT, useValue: this.groupFieldSignalContext },\n { provide: GROUP_CONTEXT, useValue: this.groupContext },\n ],\n });\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Outputs\n // ─────────────────────────────────────────────────────────────────────────────\n\n readonly validityChange = outputFromObservable(toObservable(this.valid));\n readonly dirtyChange = outputFromObservable(toObservable(this.dirty));\n readonly submitted = outputFromObservable(this.eventBus.on<FormSubmitEvent>('submit'));\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Field Resolution\n // ─────────────────────────────────────────────────────────────────────────────\n\n private readonly fieldsSource = computed(() => this.formSetup().fields);\n\n protected readonly resolvedFields = derivedFromDeferred(\n this.fieldsSource,\n createFieldResolutionPipe(() => ({\n loadTypeComponent: (type: string) => this.fieldRegistry.loadTypeComponent(type),\n registry: this.rawFieldRegistry(),\n injector: this.groupInjector,\n destroyRef: this.destroyRef,\n onError: (fieldDef: FieldDef<unknown>, error: unknown) => {\n const fieldKey = fieldDef.key || '<no key>';\n this.logger.error(\n `Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +\n `within group '${this.field().key}'. Ensure the field type is registered in your field registry.`,\n error,\n );\n },\n })),\n { initialValue: [] as ResolvedField[], injector: this.injector },\n );\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Constructor\n // ─────────────────────────────────────────────────────────────────────────────\n\n constructor() {\n this.setupEffects();\n }\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Private Methods\n // ─────────────────────────────────────────────────────────────────────────────\n\n private setupEffects(): void {\n setupContainerInitEffect(this.resolvedFields, this.eventBus, 'group', () => this.field().key, this.injector);\n\n explicitEffect(\n [this.nestedFieldTree, this.field],\n ([tree, field]) => {\n if (!tree) {\n this.logger.warn(\n `Group field \"${field.key}\" not found in parent form. ` + `Ensure the parent form schema includes this group field.`,\n );\n }\n },\n { defer: true },\n );\n }\n}\n\nexport { GroupFieldComponent };\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, computed, signal, Injector, isDevMode, afterRenderEffect, input, Directive, untracked, InjectionToken, ElementRef, EnvironmentInjector, runInInjectionContext } from '@angular/core';
|
|
3
|
-
import { injectFieldSignalContext, omit, buildBaseInputs, DEFAULT_PROPS, resolveNonFieldHidden, resolveNonFieldDisabled, RootFormRegistryService, ARRAY_CONTEXT, FORM_OPTIONS, resolveSubmitButtonDisabled, resolveNextButtonDisabled, dynamicTextToObservable, interpolateParams, isEqual, applyMetaToElement, DEFAULT_VALIDATION_MESSAGES, EventBus, resolveTokens, resolveDynamicValue, ADDON_ACTION_REGISTRY, writeToFieldValue } from '@ng-forge/dynamic-forms/internal';
|
|
3
|
+
import { injectFieldSignalContext, omit, buildBaseInputs, DEFAULT_PROPS, resolveNonFieldHidden, resolveNonFieldDisabled, RootFormRegistryService, injectNonFieldEvaluationContext, ARRAY_CONTEXT, FORM_OPTIONS, resolveSubmitButtonDisabled, resolveNextButtonDisabled, dynamicTextToObservable, interpolateParams, isEqual, applyMetaToElement, DEFAULT_VALIDATION_MESSAGES, EventBus, resolveTokens, resolveDynamicValue, ADDON_ACTION_REGISTRY, writeToFieldValue } from '@ng-forge/dynamic-forms/internal';
|
|
4
4
|
export { ADDON_ACTION_REGISTRY, ADDON_TYPE_COMPONENT_CACHE, ADDON_TYPE_DEFINITIONS, ADDON_TYPE_REGISTRY, ARRAY_CONTEXT, DEFAULT_PROPS, DEFAULT_VALIDATION_MESSAGES, DEFAULT_WRAPPERS, DfAddonSlot, DynamicTextPipe, EventBus, FIELD_REGISTRY, FIELD_SIGNAL_CONTEXT, FORM_OPTIONS, GROUP_CONTEXT, INITIALIZATION_TIMEOUT_MS, RootFormRegistryService, applyMetaToElement, applyValidator, applyValidators, arrayFieldMapper, baseFieldMapper, buildBaseInputs, containerFieldMapper, dynamicTextToObservable, getArrayLength, groupFieldMapper, injectAddonTypeRegistry, injectFieldSignalContext, injectFieldsSupportingAddons, interpolateParams, isCheckedField, isEqual, isValueField, pageFieldMapper, resolveDynamicValue, resolveNonFieldDisabled, resolveNonFieldHidden, resolveTokens, rowFieldMapper, runPresetAction, textFieldMapper, toReadonlyFieldTree, withPreviousValue, writeToFieldValue } from '@ng-forge/dynamic-forms/internal';
|
|
5
5
|
import { DynamicFormLogger, AppendArrayItemEvent, PrependArrayItemEvent, InsertArrayItemEvent, RemoveAtIndexEvent, PopArrayItemEvent, ShiftArrayItemEvent, NextPageEvent, PreviousPageEvent } from '@ng-forge/dynamic-forms';
|
|
6
6
|
export { BUILT_IN_FIELDS, createWrappers, isWrappersBundle, wrapperProps } from '@ng-forge/dynamic-forms';
|
|
@@ -8,7 +8,7 @@ import { toObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
|
8
8
|
import { combineLatest, of } from 'rxjs';
|
|
9
9
|
import { switchMap, map } from 'rxjs/operators';
|
|
10
10
|
import { explicitEffect } from 'ngxtension/explicit-effect';
|
|
11
|
-
import { TestBed } from '@angular/core/testing';
|
|
11
|
+
import { getTestBed, TestBed } from '@angular/core/testing';
|
|
12
12
|
import { form, schema } from '@angular/forms/signals';
|
|
13
13
|
|
|
14
14
|
function resolveValueFieldContext() {
|
|
@@ -125,7 +125,7 @@ function sliderFieldMapper(fieldDef) {
|
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
function applyNonFieldLogic(rootFormRegistry, fieldDef) {
|
|
128
|
+
function applyNonFieldLogic(rootFormRegistry, fieldDef, evaluationContext) {
|
|
129
129
|
const result = {};
|
|
130
130
|
const rootForm = rootFormRegistry.rootForm();
|
|
131
131
|
if (rootForm) {
|
|
@@ -134,6 +134,7 @@ function applyNonFieldLogic(rootFormRegistry, fieldDef) {
|
|
|
134
134
|
form: rootForm,
|
|
135
135
|
fieldLogic: fieldDef.logic,
|
|
136
136
|
explicitValue: fieldDef.hidden,
|
|
137
|
+
evaluationContext,
|
|
137
138
|
});
|
|
138
139
|
result.hidden = hiddenSignal();
|
|
139
140
|
}
|
|
@@ -142,6 +143,7 @@ function applyNonFieldLogic(rootFormRegistry, fieldDef) {
|
|
|
142
143
|
form: rootForm,
|
|
143
144
|
fieldLogic: fieldDef.logic,
|
|
144
145
|
explicitValue: fieldDef.disabled,
|
|
146
|
+
evaluationContext,
|
|
145
147
|
});
|
|
146
148
|
result.disabled = disabledSignal();
|
|
147
149
|
}
|
|
@@ -156,7 +158,7 @@ function applyNonFieldLogic(rootFormRegistry, fieldDef) {
|
|
|
156
158
|
}
|
|
157
159
|
return result;
|
|
158
160
|
}
|
|
159
|
-
function resolveHiddenValue(rootForm, fieldDef) {
|
|
161
|
+
function resolveHiddenValue(rootForm, fieldDef, evaluationContext) {
|
|
160
162
|
if (!rootForm) {
|
|
161
163
|
return fieldDef.hidden;
|
|
162
164
|
}
|
|
@@ -165,23 +167,29 @@ function resolveHiddenValue(rootForm, fieldDef) {
|
|
|
165
167
|
form: rootForm,
|
|
166
168
|
fieldLogic: fieldDef.logic,
|
|
167
169
|
explicitValue: fieldDef.hidden,
|
|
170
|
+
evaluationContext,
|
|
168
171
|
})();
|
|
169
172
|
}
|
|
170
173
|
return undefined;
|
|
171
174
|
}
|
|
175
|
+
function injectNonFieldLogicResolver(fieldDef) {
|
|
176
|
+
const rootFormRegistry = inject(RootFormRegistryService);
|
|
177
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef);
|
|
178
|
+
return () => applyNonFieldLogic(rootFormRegistry, fieldDef, evaluationContext);
|
|
179
|
+
}
|
|
172
180
|
|
|
173
181
|
function buttonFieldMapper(fieldDef) {
|
|
174
182
|
const defaultProps = inject(DEFAULT_PROPS);
|
|
175
|
-
const
|
|
183
|
+
const resolveLogic = injectNonFieldLogicResolver(fieldDef);
|
|
176
184
|
return computed(() => {
|
|
177
185
|
const inputs = buildBaseInputs(fieldDef, defaultProps());
|
|
178
|
-
|
|
186
|
+
inputs['event'] = fieldDef.event;
|
|
179
187
|
if (fieldDef.eventArgs !== undefined) {
|
|
180
188
|
inputs['eventArgs'] = fieldDef.eventArgs;
|
|
181
189
|
}
|
|
182
|
-
|
|
190
|
+
return {
|
|
183
191
|
...inputs,
|
|
184
|
-
...
|
|
192
|
+
...resolveLogic(),
|
|
185
193
|
};
|
|
186
194
|
});
|
|
187
195
|
}
|
|
@@ -225,6 +233,7 @@ function resolveArrayButtonEventArgs(fieldEventArgs, defaultEventArgs = ['$array
|
|
|
225
233
|
function addArrayItemButtonMapper(fieldDef) {
|
|
226
234
|
const defaultProps = inject(DEFAULT_PROPS);
|
|
227
235
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
236
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef);
|
|
228
237
|
const ctx = resolveArrayButtonContext(fieldDef.key, 'addArrayItem', fieldDef.arrayKey);
|
|
229
238
|
return computed(() => {
|
|
230
239
|
const baseInputs = buildBaseInputs(fieldDef, defaultProps?.());
|
|
@@ -233,13 +242,14 @@ function addArrayItemButtonMapper(fieldDef) {
|
|
|
233
242
|
event: AppendArrayItemEvent,
|
|
234
243
|
eventArgs: resolveArrayButtonEventArgs(fieldDef.eventArgs, ['$arrayKey', '$template']),
|
|
235
244
|
eventContext: buildArrayButtonEventContext(fieldDef.key, ctx, fieldDef.template),
|
|
236
|
-
...applyNonFieldLogic(rootFormRegistry, fieldDef),
|
|
245
|
+
...applyNonFieldLogic(rootFormRegistry, fieldDef, evaluationContext),
|
|
237
246
|
};
|
|
238
247
|
});
|
|
239
248
|
}
|
|
240
249
|
function prependArrayItemButtonMapper(fieldDef) {
|
|
241
250
|
const defaultProps = inject(DEFAULT_PROPS);
|
|
242
251
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
252
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef);
|
|
243
253
|
const ctx = resolveArrayButtonContext(fieldDef.key, 'prependArrayItem', fieldDef.arrayKey);
|
|
244
254
|
return computed(() => {
|
|
245
255
|
const baseInputs = buildBaseInputs(fieldDef, defaultProps?.());
|
|
@@ -248,13 +258,14 @@ function prependArrayItemButtonMapper(fieldDef) {
|
|
|
248
258
|
event: PrependArrayItemEvent,
|
|
249
259
|
eventArgs: resolveArrayButtonEventArgs(fieldDef.eventArgs, ['$arrayKey', '$template']),
|
|
250
260
|
eventContext: buildArrayButtonEventContext(fieldDef.key, ctx, fieldDef.template),
|
|
251
|
-
...applyNonFieldLogic(rootFormRegistry, fieldDef),
|
|
261
|
+
...applyNonFieldLogic(rootFormRegistry, fieldDef, evaluationContext),
|
|
252
262
|
};
|
|
253
263
|
});
|
|
254
264
|
}
|
|
255
265
|
function insertArrayItemButtonMapper(fieldDef) {
|
|
256
266
|
const defaultProps = inject(DEFAULT_PROPS);
|
|
257
267
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
268
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef);
|
|
258
269
|
const ctx = resolveArrayButtonContext(fieldDef.key, 'insertArrayItem', fieldDef.arrayKey);
|
|
259
270
|
return computed(() => {
|
|
260
271
|
const baseInputs = buildBaseInputs(fieldDef, defaultProps?.());
|
|
@@ -263,13 +274,14 @@ function insertArrayItemButtonMapper(fieldDef) {
|
|
|
263
274
|
event: InsertArrayItemEvent,
|
|
264
275
|
eventArgs: resolveArrayButtonEventArgs(fieldDef.eventArgs, ['$arrayKey', fieldDef.index, '$template']),
|
|
265
276
|
eventContext: buildArrayButtonEventContext(fieldDef.key, ctx, fieldDef.template),
|
|
266
|
-
...applyNonFieldLogic(rootFormRegistry, fieldDef),
|
|
277
|
+
...applyNonFieldLogic(rootFormRegistry, fieldDef, evaluationContext),
|
|
267
278
|
};
|
|
268
279
|
});
|
|
269
280
|
}
|
|
270
281
|
function removeArrayItemButtonMapper(fieldDef) {
|
|
271
282
|
const defaultProps = inject(DEFAULT_PROPS);
|
|
272
283
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
284
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef);
|
|
273
285
|
const ctx = resolveArrayButtonContext(fieldDef.key, 'removeArrayItem', fieldDef.arrayKey);
|
|
274
286
|
const eventType = ctx.isInsideArray ? RemoveAtIndexEvent : PopArrayItemEvent;
|
|
275
287
|
const defaultEventArgs = ctx.isInsideArray ? ['$arrayKey', '$index'] : ['$arrayKey'];
|
|
@@ -280,13 +292,14 @@ function removeArrayItemButtonMapper(fieldDef) {
|
|
|
280
292
|
event: eventType,
|
|
281
293
|
eventArgs: resolveArrayButtonEventArgs(fieldDef.eventArgs, defaultEventArgs),
|
|
282
294
|
eventContext: buildArrayButtonEventContext(fieldDef.key, ctx),
|
|
283
|
-
...applyNonFieldLogic(rootFormRegistry, fieldDef),
|
|
295
|
+
...applyNonFieldLogic(rootFormRegistry, fieldDef, evaluationContext),
|
|
284
296
|
};
|
|
285
297
|
});
|
|
286
298
|
}
|
|
287
299
|
function popArrayItemButtonMapper(fieldDef) {
|
|
288
300
|
const defaultProps = inject(DEFAULT_PROPS);
|
|
289
301
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
302
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef);
|
|
290
303
|
const ctx = resolveArrayButtonContext(fieldDef.key, 'popArrayItem', fieldDef.arrayKey);
|
|
291
304
|
return computed(() => {
|
|
292
305
|
const baseInputs = buildBaseInputs(fieldDef, defaultProps?.());
|
|
@@ -295,13 +308,14 @@ function popArrayItemButtonMapper(fieldDef) {
|
|
|
295
308
|
event: PopArrayItemEvent,
|
|
296
309
|
eventArgs: resolveArrayButtonEventArgs(fieldDef.eventArgs, ['$arrayKey']),
|
|
297
310
|
eventContext: buildArrayButtonEventContext(fieldDef.key, ctx),
|
|
298
|
-
...applyNonFieldLogic(rootFormRegistry, fieldDef),
|
|
311
|
+
...applyNonFieldLogic(rootFormRegistry, fieldDef, evaluationContext),
|
|
299
312
|
};
|
|
300
313
|
});
|
|
301
314
|
}
|
|
302
315
|
function shiftArrayItemButtonMapper(fieldDef) {
|
|
303
316
|
const defaultProps = inject(DEFAULT_PROPS);
|
|
304
317
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
318
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef);
|
|
305
319
|
const ctx = resolveArrayButtonContext(fieldDef.key, 'shiftArrayItem', fieldDef.arrayKey);
|
|
306
320
|
return computed(() => {
|
|
307
321
|
const baseInputs = buildBaseInputs(fieldDef, defaultProps?.());
|
|
@@ -310,7 +324,7 @@ function shiftArrayItemButtonMapper(fieldDef) {
|
|
|
310
324
|
event: ShiftArrayItemEvent,
|
|
311
325
|
eventArgs: resolveArrayButtonEventArgs(fieldDef.eventArgs, ['$arrayKey']),
|
|
312
326
|
eventContext: buildArrayButtonEventContext(fieldDef.key, ctx),
|
|
313
|
-
...applyNonFieldLogic(rootFormRegistry, fieldDef),
|
|
327
|
+
...applyNonFieldLogic(rootFormRegistry, fieldDef, evaluationContext),
|
|
314
328
|
};
|
|
315
329
|
});
|
|
316
330
|
}
|
|
@@ -319,6 +333,7 @@ function submitButtonFieldMapper(fieldDef) {
|
|
|
319
333
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
320
334
|
const defaultProps = inject(DEFAULT_PROPS);
|
|
321
335
|
const formOptions = inject(FORM_OPTIONS);
|
|
336
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef);
|
|
322
337
|
const fieldWithLogic = fieldDef;
|
|
323
338
|
return computed(() => {
|
|
324
339
|
const baseInputs = buildBaseInputs(fieldDef, defaultProps());
|
|
@@ -329,6 +344,7 @@ function submitButtonFieldMapper(fieldDef) {
|
|
|
329
344
|
formOptions: formOptions(),
|
|
330
345
|
fieldLogic: fieldWithLogic.logic,
|
|
331
346
|
explicitlyDisabled: fieldDef.disabled,
|
|
347
|
+
evaluationContext,
|
|
332
348
|
})()
|
|
333
349
|
: (fieldDef.disabled ?? false);
|
|
334
350
|
const inputs = {
|
|
@@ -336,7 +352,7 @@ function submitButtonFieldMapper(fieldDef) {
|
|
|
336
352
|
props: { ...fieldDef.props, type: 'submit' },
|
|
337
353
|
disabled,
|
|
338
354
|
};
|
|
339
|
-
const hidden = resolveHiddenValue(rootForm, fieldWithLogic);
|
|
355
|
+
const hidden = resolveHiddenValue(rootForm, fieldWithLogic, evaluationContext);
|
|
340
356
|
if (hidden !== undefined) {
|
|
341
357
|
inputs['hidden'] = hidden;
|
|
342
358
|
}
|
|
@@ -348,6 +364,7 @@ function nextButtonFieldMapper(fieldDef) {
|
|
|
348
364
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
349
365
|
const defaultProps = inject(DEFAULT_PROPS);
|
|
350
366
|
const formOptions = inject(FORM_OPTIONS);
|
|
367
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef);
|
|
351
368
|
const fieldWithLogic = fieldDef;
|
|
352
369
|
return computed(() => {
|
|
353
370
|
const baseInputs = buildBaseInputs(fieldDef, defaultProps());
|
|
@@ -357,13 +374,14 @@ function nextButtonFieldMapper(fieldDef) {
|
|
|
357
374
|
formOptions: formOptions(),
|
|
358
375
|
fieldLogic: fieldWithLogic.logic,
|
|
359
376
|
currentPageValid: fieldSignalContext.currentPageValid,
|
|
377
|
+
evaluationContext,
|
|
360
378
|
})();
|
|
361
379
|
const inputs = {
|
|
362
380
|
...baseInputs,
|
|
363
381
|
event: NextPageEvent,
|
|
364
382
|
disabled,
|
|
365
383
|
};
|
|
366
|
-
const hidden = resolveHiddenValue(rootForm, fieldWithLogic);
|
|
384
|
+
const hidden = resolveHiddenValue(rootForm, fieldWithLogic, evaluationContext);
|
|
367
385
|
if (hidden !== undefined) {
|
|
368
386
|
inputs['hidden'] = hidden;
|
|
369
387
|
}
|
|
@@ -373,6 +391,7 @@ function nextButtonFieldMapper(fieldDef) {
|
|
|
373
391
|
function previousButtonFieldMapper(fieldDef) {
|
|
374
392
|
const defaultProps = inject(DEFAULT_PROPS);
|
|
375
393
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
394
|
+
const evaluationContext = injectNonFieldEvaluationContext(fieldDef);
|
|
376
395
|
const fieldWithLogic = fieldDef;
|
|
377
396
|
return computed(() => {
|
|
378
397
|
const baseInputs = buildBaseInputs(fieldDef, defaultProps());
|
|
@@ -384,7 +403,7 @@ function previousButtonFieldMapper(fieldDef) {
|
|
|
384
403
|
if (fieldDef.disabled !== undefined) {
|
|
385
404
|
inputs['disabled'] = fieldDef.disabled;
|
|
386
405
|
}
|
|
387
|
-
const hidden = resolveHiddenValue(rootForm, fieldWithLogic);
|
|
406
|
+
const hidden = resolveHiddenValue(rootForm, fieldWithLogic, evaluationContext);
|
|
388
407
|
if (hidden !== undefined) {
|
|
389
408
|
inputs['hidden'] = hidden;
|
|
390
409
|
}
|
|
@@ -926,7 +945,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
926
945
|
}]
|
|
927
946
|
}] });
|
|
928
947
|
|
|
948
|
+
function assertTestEnvironmentInitialized() {
|
|
949
|
+
if (getTestBed().platform)
|
|
950
|
+
return;
|
|
951
|
+
throw new Error('[Dynamic Forms] The Angular test environment was never initialized for the @angular/core/testing instance this fixture resolved. ' +
|
|
952
|
+
'Under Vitest this typically means your specs are inlined but @ng-forge/dynamic-forms is externalized, creating a second testing instance. ' +
|
|
953
|
+
"Fix: add the package to Vitest's server.deps.inline:\n" +
|
|
954
|
+
"test: { server: { deps: { inline: ['@ng-forge/dynamic-forms'] } } }\n" +
|
|
955
|
+
'On Jest or Karma this instead means initTestEnvironment was never called; add it to your test setup file (e.g. getTestBed().initTestEnvironment(...)).');
|
|
956
|
+
}
|
|
929
957
|
function createNgForgeFieldFixture(component, options) {
|
|
958
|
+
assertTestEnvironmentInitialized();
|
|
930
959
|
TestBed.configureTestingModule({ imports: [component], providers: [...(options.providers ?? [])] });
|
|
931
960
|
const key = options.key;
|
|
932
961
|
const rootValue = signal({ [key]: options.value }, /* @ts-ignore */
|
|
@@ -954,6 +983,7 @@ function createNgForgeFieldFixture(component, options) {
|
|
|
954
983
|
return { fixture, field, rootValue };
|
|
955
984
|
}
|
|
956
985
|
function createNgForgeActionFixture(component, options) {
|
|
986
|
+
assertTestEnvironmentInitialized();
|
|
957
987
|
TestBed.configureTestingModule({ imports: [component], providers: [EventBus, ...(options.providers ?? [])] });
|
|
958
988
|
const fixture = TestBed.createComponent(component);
|
|
959
989
|
fixture.componentRef.setInput('key', options.key);
|
|
@@ -979,5 +1009,5 @@ function provideTestValidationMessages(messages) {
|
|
|
979
1009
|
|
|
980
1010
|
|
|
981
1011
|
|
|
982
|
-
export { ADDON_PRESET_HANDLER, NG_FORGE_ACTION_INPUTS, NG_FORGE_ADDONS_INPUTS, NG_FORGE_ADDON_ACTION_INPUTS, NG_FORGE_FIELD_SHELL_INPUTS, NG_FORGE_VALUE_FIELD_INPUTS, NgForgeAction, NgForgeActionHost, NgForgeAddonAction, NgForgeAddonActionBase, NgForgeAddons, NgForgeAddonsBase, NgForgeControl, NgForgeField, NgForgeFieldHost, NgForgeFieldShell, NgForgeHostControl, addArrayItemButtonMapper, buildArrayButtonEventContext, buildValueFieldInputs, buttonFieldMapper, checkboxFieldMapper, createAriaDescribedBySignal, createNgForgeActionFixture, createNgForgeFieldFixture, createResolvedErrorsSignal, datepickerFieldMapper, injectNgForgeAction, injectNgForgeAddonAction, injectNgForgeAddons, injectNgForgeField, insertArrayItemButtonMapper, nextButtonFieldMapper, optionsFieldMapper, popArrayItemButtonMapper, prependArrayItemButtonMapper, previousButtonFieldMapper, provideTestValidationMessages, removeArrayItemButtonMapper, resolveArrayButtonContext, resolveArrayButtonEventArgs, resolveValueFieldContext, setupMetaTracking, shiftArrayItemButtonMapper, shouldShowErrors, submitButtonFieldMapper, valueFieldMapper };
|
|
1012
|
+
export { ADDON_PRESET_HANDLER, NG_FORGE_ACTION_INPUTS, NG_FORGE_ADDONS_INPUTS, NG_FORGE_ADDON_ACTION_INPUTS, NG_FORGE_FIELD_SHELL_INPUTS, NG_FORGE_VALUE_FIELD_INPUTS, NgForgeAction, NgForgeActionHost, NgForgeAddonAction, NgForgeAddonActionBase, NgForgeAddons, NgForgeAddonsBase, NgForgeControl, NgForgeField, NgForgeFieldHost, NgForgeFieldShell, NgForgeHostControl, addArrayItemButtonMapper, buildArrayButtonEventContext, buildValueFieldInputs, buttonFieldMapper, checkboxFieldMapper, createAriaDescribedBySignal, createNgForgeActionFixture, createNgForgeFieldFixture, createResolvedErrorsSignal, datepickerFieldMapper, injectNgForgeAction, injectNgForgeAddonAction, injectNgForgeAddons, injectNgForgeField, injectNonFieldLogicResolver, insertArrayItemButtonMapper, nextButtonFieldMapper, optionsFieldMapper, popArrayItemButtonMapper, prependArrayItemButtonMapper, previousButtonFieldMapper, provideTestValidationMessages, removeArrayItemButtonMapper, resolveArrayButtonContext, resolveArrayButtonEventArgs, resolveValueFieldContext, setupMetaTracking, shiftArrayItemButtonMapper, shouldShowErrors, submitButtonFieldMapper, valueFieldMapper };
|
|
983
1013
|
//# sourceMappingURL=ng-forge-dynamic-forms-integration.mjs.map
|