@ng-forge/dynamic-forms 1.0.0-rc.1 → 1.0.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-By12r5Cj.mjs → ng-forge-dynamic-forms-array-field.component-D5qCzJsL.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-By12r5Cj.mjs.map → ng-forge-dynamic-forms-array-field.component-D5qCzJsL.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-QJzwEYSj.mjs → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-D6ntLtW9.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-QJzwEYSj.mjs.map → ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-D6ntLtW9.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-BGFguOa9.mjs → ng-forge-dynamic-forms-container-field.component-nB6KFqVs.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-BGFguOa9.mjs.map → ng-forge-dynamic-forms-container-field.component-nB6KFqVs.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-Bz4GD8Hy.mjs → ng-forge-dynamic-forms-group-field.component-Bq6Sgd6Z.mjs} +2 -2
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-Bq6Sgd6Z.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs +6 -6
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs +281 -42
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-DTkMZcnb.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-C59pvc26.mjs} +86 -407
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-C59pvc26.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-CjCd5Byo.mjs → ng-forge-dynamic-forms-page-field.component-DRaxbiqA.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-CjCd5Byo.mjs.map → ng-forge-dynamic-forms-page-field.component-DRaxbiqA.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms.mjs +2 -2
- package/package.json +3 -3
- package/types/ng-forge-dynamic-forms-integration.d.ts +6 -10
- package/types/ng-forge-dynamic-forms-internal.d.ts +1951 -1750
- package/types/ng-forge-dynamic-forms.d.ts +61 -270
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-Bz4GD8Hy.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-DTkMZcnb.mjs.map +0 -1
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import * as rxjs from 'rxjs';
|
|
2
2
|
import * as _angular_core from '@angular/core';
|
|
3
|
-
import { Type, Injector, Signal, EnvironmentInjector, WritableSignal, Provider, EnvironmentProviders, TemplateRef
|
|
3
|
+
import { Type, Injector, Signal, EnvironmentInjector, WritableSignal, Provider, EnvironmentProviders, TemplateRef } from '@angular/core';
|
|
4
4
|
import * as _ng_forge_dynamic_forms_internal from '@ng-forge/dynamic-forms/internal';
|
|
5
|
-
import { FieldDef, FormEvent, RegisteredFieldTypes,
|
|
6
|
-
export {
|
|
5
|
+
import { FieldDef, FormEvent, RegisteredFieldTypes, InferFormModel, FormConfig, FormOptions, EventBus, LazyComponentLoader, WrapperTypeDefinition, FieldTypeDefinition, InferFormValue as InferFormValue$1, DerivationLogLevel, Logger, ValueExclusionConfig, ValidationExecutionConfig, TextAddon, WrapperFieldInputs, TemplateAddon, ComponentAddon, BaseAddon, AddonTypeDefinition, AddonActionHandler, ArrayItemDefinition } from '@ng-forge/dynamic-forms/internal';
|
|
6
|
+
export { AddonActionContext, AddonActionPreset, AddonSlot, AddonTypeDefinition, AnyAddon, ArrayAllowedChildren, ArrayButtonConfig, ArrayField, ArrayItemTemplate, AsyncCondition, AsyncConditionFunction, AsyncCustomValidator, AsyncDerivationFunction, AsyncValidatorConfig, AvailableFieldTypes, BaseAddon, BaseCheckedField, BaseValidatorConfig, BaseValueField, BuiltInValidatorConfig, CheckedFieldComponent, CommonAddonActionPreset, CommonAddonSlot, ComponentAddon, ConditionalExpression, ContainerAllowedChildren, ContainerField, ContainerFieldTypes, CssWrapper, CustomFnConfig, CustomFunction, CustomValidator, CustomValidatorConfig, DF_FIELD_TEMPLATES, DeclarativeHttpValidatorConfig, DynamicFormActionRegistry, DynamicFormAddonActionPresetRegistry, DynamicFormAddonRegistry, DynamicFormAddonSlotRegistry, DynamicFormError, DynamicFormFieldRegistry, DynamicFormLogger, DynamicText, DynamicValue, EvaluationContext, ExtractField, FieldBoundAddonActionContext, FieldComponent, FieldDef, FieldMeta, FieldOption, FieldPathAccess, FieldRegistryContainers, FieldRegistryLeaves, FieldRegistryWrappers, FieldWithValidation, FieldWrapper, FormConfig, FormEvent, FormEventConstructor, FormMode, FormModeDetectionResult, FormOptions, FormStateCondition, GroupAllowedChildren, GroupField, HiddenField, HiddenScalar, HiddenValue, HttpCondition, HttpCustomValidator, HttpRequestConfig, HttpValidationResponseMapping, InferFormValue, LeafFieldTypes, Logger, LogicConfig, NarrowField, NarrowFields, NextButtonOptions, NoopLogger, OrphanAddonActionContext, PageAllowedChildren, PageField, RegisteredActionRef, RegisteredFieldTypes, ResolvedValidationExecutionConfig, ResolvedValueExclusionConfig, RowAllowedChildren, RowField, SchemaApplicationConfig, SchemaDefinition, SimplifiedArrayField, StateLogicConfig, SubmissionActionResult, SubmissionConfig, SubmitButtonOptions, TemplateAddon, TextAddon, TextElementType, TextField, TextProps, TokenContext, ValidationError, ValidationExecutionConfig, ValidationMessages, ValidatorConfig, ValueExclusionConfig, ValueFieldComponent, ValueType, WithInputSignals, WrapperConfig, WrapperTypeDefinition, hasFormValue, isArrayField, isContainerField, isDisplayOnlyField, isFieldBoundContext, isFormStateCondition, isGroupField, isHiddenField, isLeafField, isPageField, isRowField, isSimplifiedArrayField, isValueBearingField, isWrapperTypeDefinition, resolveDynamicValue } from '@ng-forge/dynamic-forms/internal';
|
|
7
7
|
import * as _angular_forms_signals from '@angular/forms/signals';
|
|
8
8
|
import { FieldTree } from '@angular/forms/signals';
|
|
9
9
|
import * as _angular_forms from '@angular/forms';
|
|
10
10
|
import * as _ng_forge_dynamic_forms from '@ng-forge/dynamic-forms';
|
|
11
|
-
import { FormSchema } from '@ng-forge/dynamic-forms/schema';
|
|
12
11
|
|
|
13
12
|
/** Resolved field ready for rendering with ngComponentOutlet / DfFieldOutlet. */
|
|
14
13
|
interface ResolvedField {
|
|
@@ -47,6 +46,7 @@ declare class FormResetEvent implements FormEvent {
|
|
|
47
46
|
readonly type: "form-reset";
|
|
48
47
|
}
|
|
49
48
|
|
|
49
|
+
/** Event emitted when the active page of a paged form changes. */
|
|
50
50
|
declare class PageChangeEvent implements FormEvent {
|
|
51
51
|
/** The current page index (0-based) */
|
|
52
52
|
readonly currentPageIndex: number;
|
|
@@ -85,19 +85,45 @@ declare class PageNavigationStateChangeEvent implements FormEvent {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
88
|
+
* Renders a form from a {@link FormConfig}. Attribute component on a native
|
|
89
|
+
* `<form>` element (selector `form[dynamic-form]`); state management is
|
|
90
|
+
* delegated to `FormStateManager`.
|
|
91
|
+
*
|
|
92
|
+
* Provide a UI adapter once via {@link provideDynamicForm} (for example
|
|
93
|
+
* `provideDynamicForm(...withMaterialFields())`), then bind a config and
|
|
94
|
+
* listen for `submitted`. Field-value types are inferred from the config when
|
|
95
|
+
* it is authored with `as const satisfies FormConfig`.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* @Component({
|
|
100
|
+
* imports: [DynamicForm],
|
|
101
|
+
* template: `<form [dynamic-form]="config" (submitted)="onSubmit($event)"></form>`,
|
|
102
|
+
* })
|
|
103
|
+
* export class SignInComponent {
|
|
104
|
+
* config = {
|
|
105
|
+
* fields: [
|
|
106
|
+
* { key: 'email', type: 'input', value: '', label: 'Email', required: true, email: true },
|
|
107
|
+
* { type: 'submit', key: 'submit', label: 'Sign in' },
|
|
108
|
+
* ],
|
|
109
|
+
* } as const satisfies FormConfig;
|
|
110
|
+
*
|
|
111
|
+
* onSubmit(value: InferFormValue<typeof this.config.fields>) {
|
|
112
|
+
* console.log(value); // inferred: { email: string }
|
|
113
|
+
* }
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
90
116
|
*/
|
|
91
|
-
declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TModel extends Record<string, unknown> =
|
|
117
|
+
declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFieldTypes[], TModel extends Record<string, unknown> = InferFormModel<TFields>> {
|
|
92
118
|
/** Form configuration defining the structure, validation, and behavior. */
|
|
93
|
-
config: _angular_core.InputSignal<FormConfig<TFields, InferFormValue
|
|
119
|
+
config: _angular_core.InputSignal<FormConfig<TFields, _ng_forge_dynamic_forms.InferFormValue<TFields extends readonly RegisteredFieldTypes[] ? TFields : RegisteredFieldTypes[]>, Record<string, unknown>, unknown>>;
|
|
94
120
|
/** Runtime form options that override config options when provided. */
|
|
95
121
|
formOptions: _angular_core.InputSignal<FormOptions | undefined>;
|
|
96
122
|
/** Form values for two-way data binding. */
|
|
97
123
|
value: _angular_core.ModelSignal<Partial<TModel> | undefined>;
|
|
98
124
|
/**
|
|
99
125
|
* How lenient the addon validator is on `config.fields[...].addons`.
|
|
100
|
-
* `'inline'` (default) keeps everything; `'json'` drops code-only
|
|
126
|
+
* `'inline'` (default) keeps everything; `'json'` drops code-only types
|
|
101
127
|
* with a warning. Read at form init and on every config swap — change
|
|
102
128
|
* `source` AND the config to force re-validation mid-flight.
|
|
103
129
|
*/
|
|
@@ -122,7 +148,7 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
|
|
|
122
148
|
protected readonly idPrefix: Signal<string>;
|
|
123
149
|
private componentId;
|
|
124
150
|
/** The currently active config used for form rendering */
|
|
125
|
-
activeConfig: Signal<FormConfig<TFields, InferFormValue
|
|
151
|
+
activeConfig: Signal<FormConfig<TFields, _ng_forge_dynamic_forms.InferFormValue<TFields extends readonly RegisteredFieldTypes[] ? TFields : RegisteredFieldTypes[]>, Record<string, unknown>, unknown> | undefined>;
|
|
126
152
|
/** Current render phase: 'render' = showing form, 'teardown' = hiding old components */
|
|
127
153
|
renderPhase: Signal<"teardown" | "render">;
|
|
128
154
|
/** Computed form mode detection with validation */
|
|
@@ -181,8 +207,8 @@ declare class DynamicForm<TFields extends RegisteredFieldTypes[] = RegisteredFie
|
|
|
181
207
|
validityChange: _angular_core.OutputRef<boolean>;
|
|
182
208
|
/** Emits when form dirty state changes. */
|
|
183
209
|
dirtyChange: _angular_core.OutputRef<boolean>;
|
|
184
|
-
/** Emits form values when submitted (via
|
|
185
|
-
submitted: _angular_core.OutputRef<
|
|
210
|
+
/** Emits form values when submitted (via FormSubmitEvent) and form is valid. */
|
|
211
|
+
submitted: _angular_core.OutputRef<TModel>;
|
|
186
212
|
/** Emits when form is reset to default values. */
|
|
187
213
|
reset: _angular_core.OutputRef<FormResetEvent>;
|
|
188
214
|
/** Emits when form is cleared to empty state. */
|
|
@@ -452,77 +478,6 @@ declare function withLegacyStatusClasses(): DynamicFormFeature<'legacy-status-cl
|
|
|
452
478
|
/** Zero-cost type carrier used in wrapper registrations. */
|
|
453
479
|
declare function wrapperProps<T>(): T;
|
|
454
480
|
|
|
455
|
-
/**
|
|
456
|
-
* Dispatcher component that renders an addon by looking up its `kind` in
|
|
457
|
-
* `ADDON_KIND_REGISTRY` and instantiating the registered component.
|
|
458
|
-
*/
|
|
459
|
-
declare class DfAddonSlot {
|
|
460
|
-
private readonly registry;
|
|
461
|
-
private readonly logger;
|
|
462
|
-
private readonly hostInjector;
|
|
463
|
-
readonly addon: _angular_core.InputSignal<AnyAddon>;
|
|
464
|
-
/**
|
|
465
|
-
* Optional read-only view of the host field's mapped inputs — same shape
|
|
466
|
-
* wrappers receive (`field?: ReadonlyFieldTree`, `key`, `props`, etc.).
|
|
467
|
-
* Forwarded to kind components so they can read field state without
|
|
468
|
-
* re-injecting `FIELD_SIGNAL_CONTEXT` and rebuilding context themselves.
|
|
469
|
-
*/
|
|
470
|
-
readonly fieldInputs: _angular_core.InputSignal<WrapperFieldInputs | undefined>;
|
|
471
|
-
/**
|
|
472
|
-
* Pre-resolved `hidden` signal supplied by the host field. When provided,
|
|
473
|
-
* the dispatcher reads this instead of re-resolving `addon.hidden` —
|
|
474
|
-
* lets the host (e.g., `prime-input`) share a single subscription per
|
|
475
|
-
* Observable-typed `hidden` between its own visibility filter and the
|
|
476
|
-
* slot's `[style.display]` binding.
|
|
477
|
-
*/
|
|
478
|
-
readonly hidden: _angular_core.InputSignal<Signal<boolean> | undefined>;
|
|
479
|
-
/** `slot` HTML attribute forwarded to host — needed for Ionic shadow projection. */
|
|
480
|
-
protected readonly slotAttr: Signal<_ng_forge_dynamic_forms.AddonSlot>;
|
|
481
|
-
protected readonly className: Signal<string | null>;
|
|
482
|
-
/**
|
|
483
|
-
* Locally-resolved `hidden` signal — always reflects `addon.hidden`
|
|
484
|
-
* regardless of whether the host supplied a pre-resolved signal.
|
|
485
|
-
* Populated via `explicitEffect` so `resolveDynamicValue` (which may call
|
|
486
|
-
* `toSignal` for Observable-typed values) does NOT run inside a reactive
|
|
487
|
-
* context (NG0602). The WeakMap caches per `hidden`-value identity, so
|
|
488
|
-
* swapping `addon` inputs that share the same Observable reuses the
|
|
489
|
-
* subscription instead of leaking a fresh one per swap.
|
|
490
|
-
*/
|
|
491
|
-
private readonly _hiddenByValue;
|
|
492
|
-
private readonly _resolvedHidden;
|
|
493
|
-
/**
|
|
494
|
-
* `hidden` resolved from `DynamicValue<boolean>` to a flat `Signal<boolean>`.
|
|
495
|
-
* Prefers the host-supplied signal when present (avoids duplicate `toSignal`
|
|
496
|
-
* subscriptions for Observable-typed `hidden`); falls back to the locally-
|
|
497
|
-
* resolved signal, which is always populated by the addon-tracking effect.
|
|
498
|
-
*/
|
|
499
|
-
protected readonly isHidden: Signal<boolean>;
|
|
500
|
-
/**
|
|
501
|
-
* Latest async-loaded `{ kind, component }` pair. Tagging the result with
|
|
502
|
-
* its kind lets `resolvedComponent` filter stale loads structurally — no
|
|
503
|
-
* monotonic token needed. When `addon.kind` changes mid-load, the prior
|
|
504
|
-
* load still completes and writes here, but the computed ignores it
|
|
505
|
-
* because the recorded kind no longer matches the current one.
|
|
506
|
-
*/
|
|
507
|
-
private readonly asyncLoaded;
|
|
508
|
-
/**
|
|
509
|
-
* Resolved kind component — composed declaratively from two sources of
|
|
510
|
-
* truth: the registry's synchronous cache (warm hits, e.g., a second
|
|
511
|
-
* `<df-addon-slot>` for an already-loaded kind) and the kind-tagged async
|
|
512
|
-
* loader result. Reading `addon().kind` first makes the computed react to
|
|
513
|
-
* addon swaps and naturally invalidate stale async-load results.
|
|
514
|
-
*/
|
|
515
|
-
protected readonly resolvedComponent: Signal<Type<unknown> | undefined>;
|
|
516
|
-
/** Inputs passed to the kind component — addon plus the wrapper-style host bag. */
|
|
517
|
-
protected readonly kindInputs: Signal<{
|
|
518
|
-
addon: AnyAddon;
|
|
519
|
-
fieldInputs: WrapperFieldInputs | undefined;
|
|
520
|
-
}>;
|
|
521
|
-
constructor();
|
|
522
|
-
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DfAddonSlot, never>;
|
|
523
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<DfAddonSlot, "df-addon-slot", never, { "addon": { "alias": "addon"; "required": true; "isSignal": true; }; "fieldInputs": { "alias": "fieldInputs"; "required": false; "isSignal": true; }; "hidden": { "alias": "hidden"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
481
|
/**
|
|
527
482
|
* Marks an `<ng-template>` projected into `<df-dynamic-form>` content as
|
|
528
483
|
* available to template addons by name.
|
|
@@ -536,7 +491,7 @@ declare class DfTemplate {
|
|
|
536
491
|
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<DfTemplate, "ng-template[dfTemplate]", ["dfTemplate"], { "dfTemplate": { "alias": "dfTemplate"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
537
492
|
}
|
|
538
493
|
|
|
539
|
-
/** Renderer for the universal `text` addon
|
|
494
|
+
/** Renderer for the universal `text` addon type. */
|
|
540
495
|
declare class TextAddonComponent {
|
|
541
496
|
readonly addon: _angular_core.InputSignal<TextAddon>;
|
|
542
497
|
/** Accepted for contract uniformity — `NgComponentOutlet` setInput is strict; every kind must declare it. */
|
|
@@ -545,7 +500,7 @@ declare class TextAddonComponent {
|
|
|
545
500
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TextAddonComponent, "df-text-addon", never, { "addon": { "alias": "addon"; "required": true; "isSignal": true; }; "fieldInputs": { "alias": "fieldInputs"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
546
501
|
}
|
|
547
502
|
|
|
548
|
-
/** Renderer for the universal `template` addon
|
|
503
|
+
/** Renderer for the universal `template` addon type. */
|
|
549
504
|
declare class TemplateAddonComponent {
|
|
550
505
|
private readonly templates;
|
|
551
506
|
private readonly logger;
|
|
@@ -563,7 +518,7 @@ declare class TemplateAddonComponent {
|
|
|
563
518
|
}
|
|
564
519
|
|
|
565
520
|
/**
|
|
566
|
-
* Renderer for the universal `component` addon
|
|
521
|
+
* Renderer for the universal `component` addon type.
|
|
567
522
|
*
|
|
568
523
|
* @codeOnly The `component` loader is a function; this kind is dropped in
|
|
569
524
|
* lenient validation when the config originated from JSON.
|
|
@@ -589,107 +544,11 @@ declare class ComponentAddonComponent {
|
|
|
589
544
|
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ComponentAddonComponent, "df-component-addon", never, { "addon": { "alias": "addon"; "required": true; "isSignal": true; }; "fieldInputs": { "alias": "fieldInputs"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
590
545
|
}
|
|
591
546
|
|
|
592
|
-
/**
|
|
593
|
-
|
|
594
|
-
* by the button kind component which has access to its own DI scope.
|
|
595
|
-
*/
|
|
596
|
-
interface PresetCollaborators {
|
|
597
|
-
/** Per-field input-type override signal — populated only inside an input field. */
|
|
598
|
-
readonly typeOverride?: WritableSignal<string | undefined>;
|
|
599
|
-
/** Writer that pushes a value into the host field's tree. */
|
|
600
|
-
readonly fieldValueSetter?: (next: unknown) => void;
|
|
601
|
-
/**
|
|
602
|
-
* Read the host field's configured default value (from the form's
|
|
603
|
-
* `defaultValues` map keyed by `field.key`). Used by the `'reset'`
|
|
604
|
-
* preset; absent when the addon is rendered outside an active field
|
|
605
|
-
* context, in which case `'reset'` falls back to clearing.
|
|
606
|
-
*/
|
|
607
|
-
readonly fieldDefaultValueGetter?: () => unknown;
|
|
608
|
-
/**
|
|
609
|
-
* Baseline configured input type (from `props().type`), read at preset-run
|
|
610
|
-
* time. Used by `toggle-password-visibility` to refuse to corrupt
|
|
611
|
-
* `'email'`/`'number'`/etc. inputs into `'password'` when no override is
|
|
612
|
-
* yet set.
|
|
613
|
-
*/
|
|
614
|
-
readonly baselineType?: () => string | undefined;
|
|
615
|
-
/** Logger for warnings about presets that can't be fulfilled in this context. */
|
|
616
|
-
readonly logger: Logger;
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* Apply a built-in preset action against the addon's host field. Shared
|
|
620
|
-
* across all adapters — each adapter's `ADDON_PRESET_HANDLER` provider
|
|
621
|
-
* delegates here.
|
|
622
|
-
*
|
|
623
|
-
* @param adapterLabel Used in warnings — `'Material'` / `'Bootstrap'` / etc.
|
|
624
|
-
* @param fieldLabel Used in warnings — `'mat-input'` / `'bs-input'` / etc.
|
|
625
|
-
*/
|
|
626
|
-
declare function runPresetAction(preset: AddonActionPreset, ctx: AddonActionContext, collaborators: PresetCollaborators, adapterLabel: string, fieldLabel: string): Promise<void>;
|
|
627
|
-
|
|
628
|
-
/**
|
|
629
|
-
* Cache for resolved addon-kind components.
|
|
630
|
-
*
|
|
631
|
-
* @internal
|
|
632
|
-
*/
|
|
633
|
-
declare const ADDON_KIND_COMPONENT_CACHE: InjectionToken<Map<string, Type<unknown>>>;
|
|
634
|
-
/**
|
|
635
|
-
* Public shape returned by {@link injectAddonKindRegistry}. Pinned explicitly
|
|
636
|
-
* so the API extractor doesn't churn between versions when the inner factory
|
|
637
|
-
* gets refactored.
|
|
638
|
-
*/
|
|
639
|
-
interface AddonKindRegistryRef {
|
|
640
|
-
getKind(kind: string): AddonKindDefinition | undefined;
|
|
641
|
-
hasKind(kind: string): boolean;
|
|
642
|
-
getKindNames(): string[];
|
|
643
|
-
loadKindComponent(kind: string): Promise<Type<unknown>>;
|
|
644
|
-
getLoadedKindComponent(kind: string): Type<unknown> | undefined;
|
|
645
|
-
readonly raw: ReadonlyMap<string, AddonKindDefinition>;
|
|
646
|
-
}
|
|
647
|
-
/** Injection function for accessing the addon kind registry. */
|
|
648
|
-
declare function injectAddonKindRegistry(): AddonKindRegistryRef;
|
|
649
|
-
|
|
650
|
-
/** One entry per field type that declares addon support. */
|
|
651
|
-
interface FieldAddonSupportEntry {
|
|
652
|
-
readonly name: string;
|
|
653
|
-
readonly slots: FieldAddonSupport['slots'];
|
|
654
|
-
readonly allowedKinds: FieldAddonSupport['allowedKinds'];
|
|
655
|
-
}
|
|
656
|
-
/**
|
|
657
|
-
* Walk `FIELD_REGISTRY` and return every field type that opted into addons
|
|
658
|
-
* (i.e., declared `addons` on its `FieldTypeDefinition`).
|
|
659
|
-
*
|
|
660
|
-
* @Component({ ... })
|
|
661
|
-
* class FormBuilder {
|
|
662
|
-
* protected readonly addonable = injectFieldsSupportingAddons();
|
|
663
|
-
*
|
|
664
|
-
* constructor() {
|
|
665
|
-
* // -> [{ name: 'prime-input', slots: ['prefix', 'suffix'], allowedKinds: [...] }, ...]
|
|
666
|
-
* }
|
|
667
|
-
* }
|
|
668
|
-
* ```
|
|
669
|
-
*/
|
|
670
|
-
declare function injectFieldsSupportingAddons(): FieldAddonSupportEntry[];
|
|
671
|
-
|
|
672
|
-
/** Register a custom addon kind with the form. */
|
|
673
|
-
declare function withCustomAddon<T extends BaseAddon>(definition: AddonKindDefinition<T>): DynamicFormFeature<'addons'>;
|
|
547
|
+
/** Register a custom addon type with the form. */
|
|
548
|
+
declare function withCustomAddon<T extends BaseAddon>(definition: AddonTypeDefinition<T>): DynamicFormFeature<'addons'>;
|
|
674
549
|
|
|
675
|
-
/** Handler signature for user-registered addon actions. */
|
|
676
|
-
type AddonActionHandler<TValue = unknown> = (ctx: AddonActionContext<TValue>) => void;
|
|
677
|
-
/**
|
|
678
|
-
* Multi-provider token populated by `provideAddonActions({...})` calls.
|
|
679
|
-
* `ADDON_ACTION_REGISTRY` aggregates these into a single name → handler map.
|
|
680
|
-
*
|
|
681
|
-
* @internal
|
|
682
|
-
*/
|
|
683
|
-
declare const ADDON_ACTION_HANDLERS: InjectionToken<readonly Record<string, AddonActionHandler<unknown>>[]>;
|
|
684
550
|
/**
|
|
685
|
-
*
|
|
686
|
-
* time for `actionRef: 'name'` lookups. Provided at form scope (not root)
|
|
687
|
-
* because `ADDON_ACTION_HANDLERS` is form-scoped via `provideAddonActions`.
|
|
688
|
-
*/
|
|
689
|
-
declare const ADDON_ACTION_REGISTRY: InjectionToken<ReadonlyMap<string, AddonActionHandler<unknown>>>;
|
|
690
|
-
|
|
691
|
-
/**
|
|
692
|
-
* Typed feature returned by {@link provideAddonActions}. Carries the
|
|
551
|
+
* Typed feature returned by {@link withAddonActions}. Carries the
|
|
693
552
|
* registered handler keys as a phantom type so consumers can derive the
|
|
694
553
|
* `DynamicFormActionRegistry` augmentation in one line instead of hand-typing
|
|
695
554
|
* every key.
|
|
@@ -699,16 +558,7 @@ type AddonActionsFeature<K extends string = string> = DynamicFormFeature<'addon-
|
|
|
699
558
|
readonly __handlerKeys?: K;
|
|
700
559
|
};
|
|
701
560
|
/** Register named action handlers reachable via JSON-driven addon configs. */
|
|
702
|
-
declare function
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* Multi-provider token used by `withCustomAddon(...)` features to contribute
|
|
706
|
-
* addon kind definitions. `provideDynamicForm` aggregates all values into
|
|
707
|
-
* the form-scoped `ADDON_KIND_REGISTRY` map.
|
|
708
|
-
*
|
|
709
|
-
* @internal
|
|
710
|
-
*/
|
|
711
|
-
declare const ADDON_KIND_DEFINITIONS: InjectionToken<readonly AddonKindDefinition<_ng_forge_dynamic_forms.BaseAddon<_ng_forge_dynamic_forms.AddonSlot>>[]>;
|
|
561
|
+
declare function withAddonActions<const H extends Record<string, AddonActionHandler>>(handlers: H): AddonActionsFeature<keyof H & string>;
|
|
712
562
|
|
|
713
563
|
/** Reason an addon was dropped during validation. */
|
|
714
564
|
type AddonWarning = {
|
|
@@ -726,55 +576,30 @@ type AddonWarning = {
|
|
|
726
576
|
slot: string;
|
|
727
577
|
allowedSlots: readonly string[];
|
|
728
578
|
} | {
|
|
729
|
-
type: 'unknown-
|
|
579
|
+
type: 'unknown-type';
|
|
730
580
|
fieldKey: string;
|
|
731
|
-
|
|
732
|
-
|
|
581
|
+
addonType: string;
|
|
582
|
+
registeredTypes: readonly string[];
|
|
733
583
|
} | {
|
|
734
|
-
type: '
|
|
584
|
+
type: 'type-not-allowed';
|
|
735
585
|
fieldKey: string;
|
|
736
586
|
fieldType: string;
|
|
737
|
-
|
|
738
|
-
|
|
587
|
+
addonType: string;
|
|
588
|
+
allowedTypes: readonly string[];
|
|
739
589
|
} | {
|
|
740
590
|
type: 'shape-violation';
|
|
741
591
|
fieldKey: string;
|
|
742
|
-
|
|
592
|
+
addonType: string;
|
|
743
593
|
reason: string;
|
|
744
594
|
} | {
|
|
745
|
-
type: 'code-only-
|
|
595
|
+
type: 'code-only-type-in-json';
|
|
746
596
|
fieldKey: string;
|
|
747
|
-
|
|
597
|
+
addonType: string;
|
|
748
598
|
} | {
|
|
749
599
|
type: 'code-only-action-in-json';
|
|
750
600
|
fieldKey: string;
|
|
751
601
|
reason: string;
|
|
752
602
|
};
|
|
753
|
-
/** Render an `AddonWarning` to a developer-friendly message. */
|
|
754
|
-
declare function formatAddonWarning(w: AddonWarning): string;
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* Walk a field's addons; return the survivors plus a list of warnings for
|
|
758
|
-
* anything dropped.
|
|
759
|
-
*
|
|
760
|
-
* @param field The field to validate.
|
|
761
|
-
* @param fieldRegistry Snapshot of `FIELD_REGISTRY` map.
|
|
762
|
-
* @param kindRegistry Snapshot of `ADDON_KIND_REGISTRY` map.
|
|
763
|
-
* @param source Whether the config originated from JSON (lenient drop
|
|
764
|
-
* of code-only kinds) or inline TypeScript.
|
|
765
|
-
*/
|
|
766
|
-
declare function validateFieldAddons(field: FieldDef<unknown>, fieldRegistry: ReadonlyMap<string, FieldTypeDefinition>, kindRegistry: ReadonlyMap<string, AddonKindDefinition>, source: 'json' | 'inline'): {
|
|
767
|
-
addons: AnyAddon[];
|
|
768
|
-
warnings: AddonWarning[];
|
|
769
|
-
};
|
|
770
|
-
/**
|
|
771
|
-
* Recursively walk a field tree, validating each field's addons. Returns a
|
|
772
|
-
* sanitized copy of the input field array plus accumulated warnings.
|
|
773
|
-
*/
|
|
774
|
-
declare function walkAndValidateAddons(fields: readonly FieldDef<unknown>[], fieldRegistry: ReadonlyMap<string, FieldTypeDefinition>, kindRegistry: ReadonlyMap<string, AddonKindDefinition>, source: 'json' | 'inline'): {
|
|
775
|
-
fields: FieldDef<unknown>[];
|
|
776
|
-
warnings: AddonWarning[];
|
|
777
|
-
};
|
|
778
603
|
|
|
779
604
|
/** Options accepted by {@link sanitizeFormConfig}. */
|
|
780
605
|
interface SanitizeFormConfigOptions {
|
|
@@ -793,7 +618,7 @@ interface SanitizedFormConfig {
|
|
|
793
618
|
}
|
|
794
619
|
/**
|
|
795
620
|
* Walk a `FormConfig`, validate every field's addons against the active
|
|
796
|
-
* `FIELD_REGISTRY` and `
|
|
621
|
+
* `FIELD_REGISTRY` and `ADDON_TYPE_REGISTRY`, and return a sanitized copy
|
|
797
622
|
* plus a list of dropped-addon warnings.
|
|
798
623
|
*
|
|
799
624
|
* @Component({ ... })
|
|
@@ -809,28 +634,18 @@ interface SanitizedFormConfig {
|
|
|
809
634
|
* ```
|
|
810
635
|
*/
|
|
811
636
|
declare function sanitizeFormConfig(config: FormConfig, options?: SanitizeFormConfigOptions): SanitizedFormConfig;
|
|
812
|
-
/**
|
|
813
|
-
* Default warning sink. Pass a `DynamicFormLogger` to route through the
|
|
814
|
-
* form's configured logger (respects `NoopLogger` opt-out); omit it to
|
|
815
|
-
* fall back to `console.warn` for build-time / admin-UI use cases that
|
|
816
|
-
* run outside an Angular injection context.
|
|
817
|
-
*/
|
|
818
|
-
declare function logAddonWarnings(warnings: readonly AddonWarning[], logger?: Logger): void;
|
|
819
|
-
/**
|
|
820
|
-
* DI-free variant of {@link sanitizeFormConfig} for tooling that lives
|
|
821
|
-
* outside Angular's injector — build-time linters, MCP-side checks, CLI
|
|
822
|
-
* scripts.
|
|
823
|
-
*/
|
|
824
|
-
declare function sanitizeFormConfigPure(config: FormConfig, fieldRegistry: ReadonlyMap<string, _ng_forge_dynamic_forms_internal.FieldTypeDefinition>, kindRegistry: ReadonlyMap<string, _ng_forge_dynamic_forms_internal.AddonKindDefinition>, options?: SanitizeFormConfigOptions): SanitizedFormConfig;
|
|
825
637
|
|
|
826
|
-
|
|
638
|
+
/** Event dispatched to submit the form (the action behind a `submit` button). */
|
|
639
|
+
declare class FormSubmitEvent implements FormEvent {
|
|
827
640
|
readonly type: "submit";
|
|
828
641
|
}
|
|
829
642
|
|
|
643
|
+
/** Event dispatched to advance a paged form to the next page. */
|
|
830
644
|
declare class NextPageEvent implements FormEvent {
|
|
831
645
|
readonly type: "next-page";
|
|
832
646
|
}
|
|
833
647
|
|
|
648
|
+
/** Event dispatched to return a paged form to the previous page. */
|
|
834
649
|
declare class PreviousPageEvent implements FormEvent {
|
|
835
650
|
readonly type: "previous-page";
|
|
836
651
|
}
|
|
@@ -1063,29 +878,5 @@ interface ArrayItemContext {
|
|
|
1063
878
|
formValue?: unknown;
|
|
1064
879
|
}
|
|
1065
880
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
*
|
|
1069
|
-
* @typeParam TFields - The field definitions array (narrowed via `as const`)
|
|
1070
|
-
* @typeParam TProps - Optional form-level default props type
|
|
1071
|
-
* @param config - The form configuration object
|
|
1072
|
-
* @returns The same configuration with proper type inference
|
|
1073
|
-
*/
|
|
1074
|
-
declare function formConfig<const TFields extends NarrowFields, TProps extends object = Record<string, unknown>>(config: Omit<FormConfig<TFields, InferFormValue$1<TFields>, TProps>, 'schema'> & {
|
|
1075
|
-
schema?: FormSchema<InferFormValue$1<TFields>>;
|
|
1076
|
-
}): FormConfig<TFields, InferFormValue$1<TFields>, TProps>;
|
|
1077
|
-
|
|
1078
|
-
/**
|
|
1079
|
-
* Creates a typed field configuration with helpful error messages for common mistakes.
|
|
1080
|
-
*
|
|
1081
|
-
* @param type - The field type (e.g., 'input', 'select', 'group')
|
|
1082
|
-
* @param config - The field configuration (excluding the 'type' property)
|
|
1083
|
-
* @returns A properly typed field configuration
|
|
1084
|
-
* @throws DynamicFormError if the configuration contains common mistakes
|
|
1085
|
-
*/
|
|
1086
|
-
declare function createField<T extends AvailableFieldTypes>(type: T, config: Omit<ExtractField<T>, 'type'>): ExtractField<T>;
|
|
1087
|
-
/** Shorthand alias for createField */
|
|
1088
|
-
declare const field: typeof createField;
|
|
1089
|
-
|
|
1090
|
-
export { ADDON_ACTION_HANDLERS, ADDON_ACTION_REGISTRY, ADDON_KIND_COMPONENT_CACHE, ADDON_KIND_DEFINITIONS, AppendArrayItemEvent, BUILT_IN_FIELDS, ComponentAddonComponent, ConsoleLogger, DfAddonSlot, DfTemplate, DynamicForm, EventDispatcher, FormClearEvent, FormResetEvent, InsertArrayItemEvent, MoveArrayItemEvent, NextPageEvent, PageChangeEvent, PopArrayItemEvent, PrependArrayItemEvent, PreviousPageEvent, RemoveAtIndexEvent, ShiftArrayItemEvent, SubmitEvent, TemplateAddonComponent, TextAddonComponent, arrayEvent, createField, createWrappers, field, formConfig, formatAddonWarning, injectAddonKindRegistry, injectFieldsSupportingAddons, isWrappersBundle, logAddonWarnings, provideAddonActions, provideDynamicForm, runPresetAction, sanitizeFormConfig, sanitizeFormConfigPure, validateFieldAddons, walkAndValidateAddons, withCustomAddon, withEventFormValue, withLegacyStatusClasses, withLoggerConfig, withValidationExecutionDefaults, withValueExclusionDefaults, wrapperProps };
|
|
1091
|
-
export type { AddonActionHandler, AddonActionsFeature, AddonWarning, ArrayItemContext, ArrayItemDefinitionTemplate, ExtractFieldDefs, ExtractFormValue, FieldAddonSupportEntry, InferWrapperRegistry, PresetCollaborators, SanitizeFormConfigOptions, SanitizedFormConfig, WrapperRegistration, WrappersBundle };
|
|
881
|
+
export { AppendArrayItemEvent, BUILT_IN_FIELDS, ComponentAddonComponent, ConsoleLogger, DfTemplate, DynamicForm, EventDispatcher, FormClearEvent, FormResetEvent, FormSubmitEvent, InsertArrayItemEvent, MoveArrayItemEvent, NextPageEvent, PageChangeEvent, PopArrayItemEvent, PrependArrayItemEvent, PreviousPageEvent, RemoveAtIndexEvent, ShiftArrayItemEvent, TemplateAddonComponent, TextAddonComponent, arrayEvent, createWrappers, isWrappersBundle, provideDynamicForm, sanitizeFormConfig, withAddonActions, withCustomAddon, withEventFormValue, withLegacyStatusClasses, withLoggerConfig, withValidationExecutionDefaults, withValueExclusionDefaults, wrapperProps };
|
|
882
|
+
export type { AddonActionsFeature, AddonWarning, ArrayItemContext, ArrayItemDefinitionTemplate, ExtractFieldDefs, ExtractFormValue, InferWrapperRegistry, SanitizeFormConfigOptions, SanitizedFormConfig, WrapperRegistration, WrappersBundle };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ng-forge-dynamic-forms-group-field.component-Bz4GD8Hy.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 { SubmitEvent } 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<SubmitEvent>('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"]}
|