@ng-forge/dynamic-forms 0.7.0 → 0.8.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/README.md +2 -0
- package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-CGNGJoqv.mjs +50 -0
- package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-CGNGJoqv.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-Sad9wa1d.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-BwoONrnj.mjs} +1498 -400
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-BwoONrnj.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-Cen4ArED.mjs → ng-forge-dynamic-forms-page-field.component-DV-EX9U0.mjs} +9 -14
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-DV-EX9U0.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-BIvEUoia.mjs +34 -0
- package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-BIvEUoia.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-cE1EUmYR.mjs → ng-forge-dynamic-forms-text-field.component-CHC8v87r.mjs} +5 -5
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-cE1EUmYR.mjs.map → ng-forge-dynamic-forms-text-field.component-CHC8v87r.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
- package/package.json +2 -2
- package/types/ng-forge-dynamic-forms-integration.d.ts +25 -23
- package/types/ng-forge-dynamic-forms.d.ts +3393 -2797
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-Sad9wa1d.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-Cen4ArED.mjs.map +0 -1
|
@@ -1,128 +1,58 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject,
|
|
3
|
-
import { NgComponentOutlet } from '@angular/common';
|
|
4
|
-
import { toObservable, toSignal, takeUntilDestroyed, rxResource, outputFromObservable } from '@angular/core/rxjs-interop';
|
|
5
|
-
import { submit, email, min, max, minLength, maxLength, pattern, required, validate, validateAsync, validateHttp, disabled, readonly, hidden, applyEach, applyWhenValue, applyWhen, apply, schema, validateStandardSchema, validateTree, form, provideSignalFormsConfig } from '@angular/forms/signals';
|
|
6
|
-
import { isObservable, firstValueFrom, exhaustMap, EMPTY, from, catchError, Subject, filter, TimeoutError, of, throwError, debounceTime, distinctUntilChanged, tap, take as take$1, startWith, pairwise, map as map$1, switchMap as switchMap$1, Observable, takeUntil, combineLatestWith, auditTime, scheduled, queueScheduler, merge, timer, defer, combineLatest, pipe, forkJoin, scan as scan$1, concatMap } from 'rxjs';
|
|
7
|
-
import { scan, map, filter as filter$1, take, switchMap, timeout, catchError as catchError$1, shareReplay } from 'rxjs/operators';
|
|
2
|
+
import { InjectionToken, reflectComponentType, Injector, inject, EnvironmentInjector, DestroyRef, computed, input, ViewContainerRef, signal, Directive, Injectable, isSignal, untracked, isWritableSignal, isDevMode, linkedSignal, ChangeDetectionStrategy, Component, resourceFromSnapshots, runInInjectionContext, afterNextRender, model, makeEnvironmentProviders, Pipe, viewChild, TemplateRef } from '@angular/core';
|
|
8
3
|
import { explicitEffect } from 'ngxtension/explicit-effect';
|
|
4
|
+
import { of, forkJoin, from, catchError, map, switchMap, isObservable, firstValueFrom, exhaustMap, EMPTY, Subject, filter, TimeoutError, throwError, debounceTime, distinctUntilChanged, tap, take as take$1, startWith, pairwise, Observable, takeUntil, combineLatestWith, auditTime, scheduled, queueScheduler, merge, timer, defer, combineLatest, pipe, scan as scan$1, concatMap } from 'rxjs';
|
|
5
|
+
import { takeUntilDestroyed, toObservable, toSignal, rxResource, outputFromObservable } from '@angular/core/rxjs-interop';
|
|
6
|
+
import { submit, email, min, max, minLength, maxLength, pattern, required, validate, validateAsync, validateHttp, disabled, readonly, hidden, applyEach, applyWhenValue, applyWhen, apply, schema, validateStandardSchema, validateTree, form, provideSignalFormsConfig } from '@angular/forms/signals';
|
|
7
|
+
import { scan, map as map$1, filter as filter$1, take, switchMap as switchMap$1, timeout, catchError as catchError$1, shareReplay } from 'rxjs/operators';
|
|
9
8
|
import { HttpClient } from '@angular/common/http';
|
|
10
9
|
import { isStandardSchemaMarker } from '@ng-forge/dynamic-forms/schema';
|
|
11
10
|
import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat';
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
15
|
-
* Converts Observables to Promises for compatibility with Angular Signal Forms' submit().
|
|
13
|
+
* Type guard for WrapperTypeDefinition.
|
|
16
14
|
*
|
|
17
|
-
*
|
|
18
|
-
* allowing the caller's catchError to log and keep the submission stream alive.
|
|
19
|
-
*
|
|
20
|
-
* @param action - The submission action function
|
|
21
|
-
* @returns A wrapped function that returns a Promise
|
|
15
|
+
* Discriminates via `wrapperName` — field types use `name`, wrapper types use `wrapperName`.
|
|
22
16
|
*/
|
|
23
|
-
function
|
|
24
|
-
return
|
|
25
|
-
const result = action(formTree);
|
|
26
|
-
// If the action returns an Observable, convert it to a Promise
|
|
27
|
-
if (isObservable(result)) {
|
|
28
|
-
await firstValueFrom(result);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
await Promise.resolve(result);
|
|
32
|
-
};
|
|
17
|
+
function isWrapperTypeDefinition(value) {
|
|
18
|
+
return typeof value === 'object' && value !== null && 'wrapperName' in value;
|
|
33
19
|
}
|
|
34
20
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* This utility encapsulates the submission handling logic:
|
|
38
|
-
* - Listens for submit events from the event bus
|
|
39
|
-
* - If a submission.action is configured, wraps it and uses Angular Signal Forms' submit()
|
|
40
|
-
* - Handles both Promise and Observable returns from the action
|
|
41
|
-
* - Uses exhaustMap to ignore new submissions while one is in-flight (first-submit-wins)
|
|
42
|
-
*
|
|
43
|
-
* The returned Observable should be subscribed to with takeUntilDestroyed() in the component.
|
|
44
|
-
*
|
|
45
|
-
* @param options - Configuration options for the submission handler
|
|
46
|
-
* @returns Observable that processes submissions (emits when submission completes)
|
|
21
|
+
* Injection token for the wrapper type registry.
|
|
47
22
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* createSubmissionHandler({
|
|
52
|
-
* eventBus: this.eventBus,
|
|
53
|
-
* configSignal: this.config,
|
|
54
|
-
* formSignal: this.form,
|
|
55
|
-
* })
|
|
56
|
-
* .pipe(takeUntilDestroyed())
|
|
57
|
-
* .subscribe();
|
|
58
|
-
* ```
|
|
23
|
+
* Provides access to the map of registered wrapper types. The registry is
|
|
24
|
+
* populated via `provideDynamicForm()` and used by `ContainerFieldComponent` to
|
|
25
|
+
* resolve wrapper types to their component implementations.
|
|
59
26
|
*/
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// switchMap would unsubscribe the Observable wrapper but cannot cancel the
|
|
65
|
-
// underlying Promise, causing both side effects to execute.
|
|
66
|
-
return eventBus.on('submit').pipe(exhaustMap(() => {
|
|
67
|
-
const submissionConfig = configSignal().submission;
|
|
68
|
-
// If no submission action is configured, let the submitted output handle it
|
|
69
|
-
// This maintains backward compatibility for users handling submission manually
|
|
70
|
-
if (!submissionConfig?.action) {
|
|
71
|
-
return EMPTY;
|
|
72
|
-
}
|
|
73
|
-
// Guard: match the (submitted) output's safety contract — reject submission
|
|
74
|
-
// when the form is invalid or has pending async validators.
|
|
75
|
-
if (!validSignal()) {
|
|
76
|
-
logger.debug('Submission action skipped: form is not valid (invalid or pending async validators)');
|
|
77
|
-
return EMPTY;
|
|
78
|
-
}
|
|
79
|
-
// Type assertion needed: submission.action accepts the form tree but its signature
|
|
80
|
-
// is defined broadly in FormConfig. The actual runtime type is FieldTree<TModel>.
|
|
81
|
-
const wrappedAction = wrapSubmissionAction(submissionConfig.action);
|
|
82
|
-
// Use Angular Signal Forms' native submit() function
|
|
83
|
-
// This automatically:
|
|
84
|
-
// - Sets form.submitting() to true during execution
|
|
85
|
-
// - Applies server errors to form fields on completion
|
|
86
|
-
// - Sets form.submitting() to false when done
|
|
87
|
-
// catchError keeps the exhaustMap stream alive after action failure —
|
|
88
|
-
// without it, an unhandled error would terminate all future submissions.
|
|
89
|
-
return from(submit(formSignal(), wrappedAction)).pipe(catchError((error) => {
|
|
90
|
-
logger.error('Submission action failed:', error);
|
|
91
|
-
return EMPTY;
|
|
92
|
-
}));
|
|
93
|
-
}));
|
|
94
|
-
}
|
|
95
|
-
|
|
27
|
+
const WRAPPER_REGISTRY = new InjectionToken('WRAPPER_REGISTRY', {
|
|
28
|
+
providedIn: 'root',
|
|
29
|
+
factory: () => new Map(),
|
|
30
|
+
});
|
|
96
31
|
/**
|
|
97
|
-
*
|
|
32
|
+
* Component cache for loaded wrapper components.
|
|
98
33
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* This token is configured via `withEventFormValue()` feature function.
|
|
103
|
-
* Per-form overrides can be set via `options.emitFormValueOnEvents` in the form config.
|
|
34
|
+
* Caches resolved wrapper component classes for instant re-resolution.
|
|
35
|
+
* SSR-safe because it's DI-scoped, not module-scoped.
|
|
104
36
|
*
|
|
105
|
-
*
|
|
37
|
+
* NOTE: The cache grows across the application lifetime. It's bounded by the
|
|
38
|
+
* number of wrapper types registered (typically small), so this is not a leak;
|
|
39
|
+
* the COMPONENT_CACHE for field types has the same shape.
|
|
106
40
|
*/
|
|
107
|
-
const
|
|
41
|
+
const WRAPPER_COMPONENT_CACHE = new InjectionToken('WRAPPER_COMPONENT_CACHE', {
|
|
108
42
|
providedIn: 'root',
|
|
109
|
-
factory: () =>
|
|
43
|
+
factory: () => new Map(),
|
|
110
44
|
});
|
|
111
|
-
|
|
112
45
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
46
|
+
* Pre-computed reverse index: `fieldType → WrapperConfig[]` for every
|
|
47
|
+
* registered `WrapperTypeDefinition.types` entry. Built once in the
|
|
48
|
+
* `provideDynamicForm(...)` factory so `resolveWrappers` can look up
|
|
49
|
+
* auto-associations in O(1) per field instead of scanning the full
|
|
50
|
+
* wrapper registry on every render.
|
|
117
51
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
this.formValue = formValue;
|
|
123
|
-
this.rootForm = rootForm;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
52
|
+
const WRAPPER_AUTO_ASSOCIATIONS = new InjectionToken('WRAPPER_AUTO_ASSOCIATIONS', {
|
|
53
|
+
providedIn: 'root',
|
|
54
|
+
factory: () => new Map(),
|
|
55
|
+
});
|
|
126
56
|
|
|
127
57
|
/**
|
|
128
58
|
* Base error class for all Dynamic Forms errors.
|
|
@@ -139,6 +69,18 @@ class DynamicFormError extends Error {
|
|
|
139
69
|
}
|
|
140
70
|
}
|
|
141
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Injection token for form-level default wrappers.
|
|
74
|
+
*
|
|
75
|
+
* Provides a Signal of the `defaultWrappers` array from FormConfig. Consumed by
|
|
76
|
+
* `DfFieldOutlet` / `ContainerFieldComponent` to merge into each field's
|
|
77
|
+
* effective wrapper chain (lowest priority after auto-associations, higher
|
|
78
|
+
* than field-level `wrappers`).
|
|
79
|
+
*
|
|
80
|
+
* Provided once at the DynamicForm level and inherited via Angular's
|
|
81
|
+
* hierarchical injector.
|
|
82
|
+
*/
|
|
83
|
+
const DEFAULT_WRAPPERS = new InjectionToken('DEFAULT_WRAPPERS');
|
|
142
84
|
/**
|
|
143
85
|
* Injection token for providing field signal context to mappers and components.
|
|
144
86
|
*
|
|
@@ -345,6 +287,201 @@ function createArrayItemIdGenerator() {
|
|
|
345
287
|
return () => `item-${counter++}`;
|
|
346
288
|
}
|
|
347
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Module-level cache keyed by component class. `reflectComponentType` returns
|
|
292
|
+
* immutable metadata per class, so we probe it once and reuse the resulting
|
|
293
|
+
* `Set<templateName>` for every `setInputIfDeclared` call afterwards.
|
|
294
|
+
*
|
|
295
|
+
* We cache `templateName` (the public input name, aka alias) rather than
|
|
296
|
+
* `propName` (the class field name) because `ComponentRef.setInput()` keys by
|
|
297
|
+
* the public name — that's also the name wrapper configs use. An aliased
|
|
298
|
+
* input declared as `input(..., { alias: 'header' })` with class field
|
|
299
|
+
* `headerText` must be addressed as `'header'`; caching `propName` would
|
|
300
|
+
* leave the alias out of the declared set and silently drop config values.
|
|
301
|
+
*
|
|
302
|
+
* SSR-safe: the WeakMap is keyed by the component class object — classes are
|
|
303
|
+
* created per Angular application bootstrap and GC'd with it, so this does not
|
|
304
|
+
* leak state between server renders.
|
|
305
|
+
*/
|
|
306
|
+
const inputNamesCache = new WeakMap();
|
|
307
|
+
function getDeclaredInputs(componentType) {
|
|
308
|
+
let inputs = inputNamesCache.get(componentType);
|
|
309
|
+
if (!inputs) {
|
|
310
|
+
const meta = reflectComponentType(componentType);
|
|
311
|
+
inputs = new Set(meta?.inputs.map((i) => i.templateName) ?? []);
|
|
312
|
+
inputNamesCache.set(componentType, inputs);
|
|
313
|
+
}
|
|
314
|
+
return inputs;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Set an input on a ComponentRef only when the target component actually declares it.
|
|
318
|
+
*
|
|
319
|
+
* Angular's `ComponentRef.setInput()` throws NG0303 when the input is missing.
|
|
320
|
+
* For config keys driven by user data (e.g. a wrapper config containing a prop
|
|
321
|
+
* the wrapper doesn't care about) that would surface as a noisy runtime error.
|
|
322
|
+
* We probe the component's metadata via `reflectComponentType` (public API),
|
|
323
|
+
* cached per component class — called once per input key per emission, so
|
|
324
|
+
* avoiding the reflection scan on each call is meaningful under heavy typing.
|
|
325
|
+
*/
|
|
326
|
+
function setInputIfDeclared(ref, inputName, value) {
|
|
327
|
+
const declared = getDeclaredInputs(ref.componentType);
|
|
328
|
+
if (declared.has(inputName)) {
|
|
329
|
+
ref.setInput(inputName, value);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Narrows an ES-module-with-default-export from a bare component class.
|
|
334
|
+
* Lazy component loaders may return either shape depending on how the user
|
|
335
|
+
* wrote the import expression (`import('./x')` vs `import('./x').then(m => m.Foo)`).
|
|
336
|
+
*/
|
|
337
|
+
function hasDefaultExport(value) {
|
|
338
|
+
return typeof value === 'object' && value !== null && 'default' in value && !!value.default;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Pick the component class out of whatever a lazy loader returned.
|
|
342
|
+
*/
|
|
343
|
+
function resolveDefaultExport(result) {
|
|
344
|
+
return hasDefaultExport(result) ? result.default : result;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Resolve a wrapper type name to its component class, with DI-scoped caching.
|
|
348
|
+
*/
|
|
349
|
+
async function loadWrapperComponent(type, registry, cache) {
|
|
350
|
+
const cached = cache.get(type);
|
|
351
|
+
if (cached)
|
|
352
|
+
return cached;
|
|
353
|
+
const definition = registry.get(type);
|
|
354
|
+
if (!definition)
|
|
355
|
+
return undefined;
|
|
356
|
+
const component = resolveDefaultExport(await definition.loadComponent());
|
|
357
|
+
if (component)
|
|
358
|
+
cache.set(type, component);
|
|
359
|
+
return component;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Load every wrapper for a chain, all-or-nothing. A single failed load
|
|
363
|
+
* aborts the whole chain (emits `[]`) so the field renders bare rather
|
|
364
|
+
* than in a partially-wrapped, visually-misleading state. Each failure
|
|
365
|
+
* is logged; field-type resolution still throws — this isn't silent.
|
|
366
|
+
*/
|
|
367
|
+
function loadWrapperComponents(configs, registry, cache, logger) {
|
|
368
|
+
if (configs.length === 0)
|
|
369
|
+
return of([]);
|
|
370
|
+
return forkJoin(configs.map((config) => from(loadWrapperComponent(config.type, registry, cache)).pipe(catchError(() => of(undefined)), map((component) => {
|
|
371
|
+
if (!component) {
|
|
372
|
+
logger.error(`Wrapper type '${config.type}' could not be loaded. Ensure it is registered via provideDynamicForm().`);
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
return { config, component };
|
|
376
|
+
})))).pipe(map((results) => (results.some((r) => r === null) ? [] : results)));
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Recursively create each wrapper component, threading the next one (or the
|
|
380
|
+
* innermost content) into its `#fieldComponent` slot.
|
|
381
|
+
*
|
|
382
|
+
* Each wrapper receives:
|
|
383
|
+
* - Each of its config properties (minus `type`) as an individual Angular input
|
|
384
|
+
* - `fieldInputs` as a single input, if provided
|
|
385
|
+
*
|
|
386
|
+
* Returns the list of wrapper `ComponentRef`s — callers retain them for later
|
|
387
|
+
* cleanup and for re-setting `fieldInputs` when the mapper signal emits.
|
|
388
|
+
*/
|
|
389
|
+
function renderWrapperChain(options) {
|
|
390
|
+
const refs = [];
|
|
391
|
+
renderStep(options.outerContainer, options.loadedWrappers, options, refs);
|
|
392
|
+
return refs;
|
|
393
|
+
}
|
|
394
|
+
function renderStep(slot, remaining, options, refs) {
|
|
395
|
+
if (remaining.length === 0) {
|
|
396
|
+
options.renderInnermost(slot);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const [wrapper, ...rest] = remaining;
|
|
400
|
+
// `slot.injector` is the element injector seen from inside the current slot —
|
|
401
|
+
// for nested slots that includes the outer wrapper's component, enabling
|
|
402
|
+
// `inject(OuterWrapper)`. When a field injector is provided we merge it in
|
|
403
|
+
// front so tokens like `ARRAY_CONTEXT` also resolve.
|
|
404
|
+
const wrapperInjector = options.fieldInjector ? createWrapperAwareInjector(options.fieldInjector, slot.injector) : slot.injector;
|
|
405
|
+
const ref = slot.createComponent(wrapper.component, {
|
|
406
|
+
environmentInjector: options.environmentInjector,
|
|
407
|
+
injector: wrapperInjector,
|
|
408
|
+
});
|
|
409
|
+
refs.push(ref);
|
|
410
|
+
for (const [key, value] of Object.entries(wrapper.config)) {
|
|
411
|
+
// `type` is the registry discriminant; `fieldInputs` is set explicitly
|
|
412
|
+
// below. Don't let a stray config key override either.
|
|
413
|
+
if (key === 'type' || key === 'fieldInputs')
|
|
414
|
+
continue;
|
|
415
|
+
setInputIfDeclared(ref, key, value);
|
|
416
|
+
}
|
|
417
|
+
if (options.fieldInputs !== undefined) {
|
|
418
|
+
setInputIfDeclared(ref, 'fieldInputs', options.fieldInputs);
|
|
419
|
+
}
|
|
420
|
+
// Required to resolve `viewChild.required('fieldComponent')` before we
|
|
421
|
+
// recurse into the slot. O(chain length) CD passes per rebuild — acceptable
|
|
422
|
+
// for typical chains (≤3); refactor to a template-based render if it bites.
|
|
423
|
+
ref.changeDetectorRef.detectChanges();
|
|
424
|
+
const inner = resolveInnerSlot(ref);
|
|
425
|
+
if (!inner) {
|
|
426
|
+
options.logger.error(`Wrapper component for type '${wrapper.config.type}' does not provide a 'fieldComponent' ViewContainerRef. ` +
|
|
427
|
+
`Ensure the wrapper component has a viewChild('fieldComponent', { read: ViewContainerRef }) query ` +
|
|
428
|
+
`and that #fieldComponent is not inside a conditional (@if, @defer).`);
|
|
429
|
+
// Unwind: destroy every wrapper built so far, including this one, so the
|
|
430
|
+
// caller doesn't end up with a partial chain left in the DOM.
|
|
431
|
+
while (refs.length)
|
|
432
|
+
refs.pop().destroy();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
renderStep(inner, rest, options, refs);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Read a wrapper's `#fieldComponent` ViewContainerRef. `viewChild.required`
|
|
439
|
+
* throws when the query can't resolve (missing template ref, or ref sitting
|
|
440
|
+
* inside `@if` / `@defer` / `@for`); any throw from this pure signal read
|
|
441
|
+
* means the slot is unusable, so we return undefined and let the caller log
|
|
442
|
+
* the actionable "wrapper missing fieldComponent" message.
|
|
443
|
+
*/
|
|
444
|
+
function resolveInnerSlot(ref) {
|
|
445
|
+
const contract = ref.instance;
|
|
446
|
+
try {
|
|
447
|
+
return contract.fieldComponent?.();
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Merged injector: check the field-level injector first (for tokens like
|
|
455
|
+
* `ARRAY_CONTEXT` / `FIELD_SIGNAL_CONTEXT`), fall back to the element-chain
|
|
456
|
+
* injector (so nested wrappers can still `inject(OuterWrapper)`).
|
|
457
|
+
*
|
|
458
|
+
* Precedence: a field-level token shadows a same-named token provided by an
|
|
459
|
+
* outer wrapper. That matches "more specific context wins" but is worth
|
|
460
|
+
* keeping in mind if a wrapper exports service tokens.
|
|
461
|
+
*/
|
|
462
|
+
function createWrapperAwareInjector(fieldInjector, elementInjector) {
|
|
463
|
+
if (fieldInjector === elementInjector)
|
|
464
|
+
return fieldInjector;
|
|
465
|
+
return new WrapperAwareInjector(fieldInjector, elementInjector);
|
|
466
|
+
}
|
|
467
|
+
/** Sentinel — distinguishes "token not found" from a legitimate `undefined` provider value. */
|
|
468
|
+
const WRAPPER_NOT_FOUND = {};
|
|
469
|
+
class WrapperAwareInjector extends Injector {
|
|
470
|
+
fieldInjector;
|
|
471
|
+
elementInjector;
|
|
472
|
+
constructor(fieldInjector, elementInjector) {
|
|
473
|
+
super();
|
|
474
|
+
this.fieldInjector = fieldInjector;
|
|
475
|
+
this.elementInjector = elementInjector;
|
|
476
|
+
}
|
|
477
|
+
get(token, notFoundValue, options) {
|
|
478
|
+
const field = this.fieldInjector.get(token, WRAPPER_NOT_FOUND, options);
|
|
479
|
+
if (field !== WRAPPER_NOT_FOUND)
|
|
480
|
+
return field;
|
|
481
|
+
return this.elementInjector.get(token, notFoundValue, options);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
348
485
|
/**
|
|
349
486
|
* Injection token for the dynamic forms logger.
|
|
350
487
|
*
|
|
@@ -353,6 +490,567 @@ function createArrayItemIdGenerator() {
|
|
|
353
490
|
*/
|
|
354
491
|
const DynamicFormLogger = new InjectionToken('DynamicFormLogger');
|
|
355
492
|
|
|
493
|
+
/**
|
|
494
|
+
* Shared empty chain — returned when no wrappers apply so callers get a
|
|
495
|
+
* stable reference. Freezing makes accidental mutation observable.
|
|
496
|
+
*/
|
|
497
|
+
const EMPTY_WRAPPERS = Object.freeze([]);
|
|
498
|
+
/**
|
|
499
|
+
* Element-wise identity (`===`) comparator for wrapper chains. Used as the
|
|
500
|
+
* `equal` option on signal memoisation so reconciled `FieldDef`s with the
|
|
501
|
+
* same chain don't churn downstream — relies on `WrapperConfig` objects
|
|
502
|
+
* being stable across ticks, which holds for configs declared in
|
|
503
|
+
* `FormConfig` / `createWrappers(...)`.
|
|
504
|
+
*/
|
|
505
|
+
function isSameWrapperChain(a, b) {
|
|
506
|
+
if (a === b)
|
|
507
|
+
return true;
|
|
508
|
+
if (a.length !== b.length)
|
|
509
|
+
return false;
|
|
510
|
+
for (let i = 0; i < a.length; i++) {
|
|
511
|
+
if (a[i] !== b[i])
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Resolves the wrapper chain for a field.
|
|
518
|
+
*
|
|
519
|
+
* Merge order (outermost → innermost): auto-associations, form defaults,
|
|
520
|
+
* field-level wrappers. `wrappers: null` skips all three; `skipAutoWrappers` /
|
|
521
|
+
* `skipDefaultWrappers` on the field opt out of individual layers. Fresh
|
|
522
|
+
* array per call — downstream memoisation is on the consuming computed.
|
|
523
|
+
*/
|
|
524
|
+
function resolveWrappers(field, defaultWrappers, autoAssociations) {
|
|
525
|
+
if (field.wrappers === null) {
|
|
526
|
+
return EMPTY_WRAPPERS;
|
|
527
|
+
}
|
|
528
|
+
const autoWrappers = field.skipAutoWrappers ? EMPTY_WRAPPERS : (autoAssociations.get(field.type) ?? EMPTY_WRAPPERS);
|
|
529
|
+
const defaults = field.skipDefaultWrappers ? EMPTY_WRAPPERS : (defaultWrappers ?? EMPTY_WRAPPERS);
|
|
530
|
+
const fieldLevel = field.wrappers ?? EMPTY_WRAPPERS;
|
|
531
|
+
if (autoWrappers.length === 0 && defaults.length === 0 && fieldLevel.length === 0) {
|
|
532
|
+
return EMPTY_WRAPPERS;
|
|
533
|
+
}
|
|
534
|
+
return [...autoWrappers, ...defaults, ...fieldLevel];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Owns the wrapper-chain lifecycle for a field or container: async component
|
|
539
|
+
* loading, switchMap-based cancellation of stale loads, flicker-tolerant
|
|
540
|
+
* rebuilds (only structural changes tear down), and `fieldInputs` push-through.
|
|
541
|
+
* Must be called from a DI injection context.
|
|
542
|
+
*/
|
|
543
|
+
function createWrapperChainController(opts) {
|
|
544
|
+
const deps = injectChainDeps();
|
|
545
|
+
const state = createStateSignal(opts);
|
|
546
|
+
const mounted = { value: null };
|
|
547
|
+
let refs = [];
|
|
548
|
+
buildEmissionStream(state, deps)
|
|
549
|
+
.pipe(takeUntilDestroyed(deps.destroyRef))
|
|
550
|
+
.subscribe((emission) => {
|
|
551
|
+
// Wrap the whole render path — a throw here (bad wrapper template,
|
|
552
|
+
// caller's renderInnermost blew up, etc.) would otherwise terminate
|
|
553
|
+
// the subscription and silently freeze subsequent chain updates.
|
|
554
|
+
try {
|
|
555
|
+
refs = applyEmission(emission, { opts, deps, mounted, refs });
|
|
556
|
+
}
|
|
557
|
+
catch (err) {
|
|
558
|
+
deps.logger.error('Wrapper chain render failed; tearing down partial state.', err);
|
|
559
|
+
opts.vcr().clear();
|
|
560
|
+
refs = [];
|
|
561
|
+
mounted.value = null;
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
pushFieldInputsOnChange(opts, () => refs);
|
|
565
|
+
deps.destroyRef.onDestroy(() => opts.vcr().clear());
|
|
566
|
+
}
|
|
567
|
+
function injectChainDeps() {
|
|
568
|
+
return {
|
|
569
|
+
registry: inject(WRAPPER_REGISTRY),
|
|
570
|
+
cache: inject(WRAPPER_COMPONENT_CACHE),
|
|
571
|
+
logger: inject(DynamicFormLogger),
|
|
572
|
+
destroyRef: inject(DestroyRef),
|
|
573
|
+
environmentInjector: inject(EnvironmentInjector),
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function createStateSignal(opts) {
|
|
577
|
+
return computed(() => ({
|
|
578
|
+
open: opts.gate?.() ?? true,
|
|
579
|
+
wrappers: opts.wrappers(),
|
|
580
|
+
rebuildKey: opts.rebuildKey?.(),
|
|
581
|
+
}), { equal: (a, b) => a.open === b.open && a.rebuildKey === b.rebuildKey && isSameWrapperChain(a.wrappers, b.wrappers) });
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Resolve a `ChainState` into a `ChainEmission`. `switchMap` cancellation
|
|
585
|
+
* guarantees in-flight async loads are discarded when a newer state arrives.
|
|
586
|
+
*/
|
|
587
|
+
function buildEmissionStream(state, deps) {
|
|
588
|
+
return toObservable(state).pipe(switchMap((s) => resolveLoadedWrappers(s, deps)));
|
|
589
|
+
}
|
|
590
|
+
function resolveLoadedWrappers(state, deps) {
|
|
591
|
+
if (!state.open) {
|
|
592
|
+
return of({ state, loaded: null });
|
|
593
|
+
}
|
|
594
|
+
if (state.wrappers.every((w) => deps.cache.has(w.type))) {
|
|
595
|
+
// Sync fast-path — the cache only holds SUCCESSFUL loads, so "every cached"
|
|
596
|
+
// means every wrapper was previously resolved cleanly. No need to re-log.
|
|
597
|
+
const loaded = state.wrappers.map((config) => ({ config, component: deps.cache.get(config.type) }));
|
|
598
|
+
return of({ state, loaded });
|
|
599
|
+
}
|
|
600
|
+
return loadWrapperComponents(state.wrappers, deps.registry, deps.cache, deps.logger).pipe(map((loaded) => ({ state, loaded })));
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Diff what's mounted against the next state and act:
|
|
604
|
+
* - Gate-only flicker → no-op (preserve the mounted chain)
|
|
605
|
+
* - Structural change → beforeRebuild → vcr.clear → render fresh
|
|
606
|
+
* - Idempotent re-emission → no-op
|
|
607
|
+
*
|
|
608
|
+
* Returns the (possibly new) wrapper refs so the caller can track them for
|
|
609
|
+
* `fieldInputs` push-through.
|
|
610
|
+
*/
|
|
611
|
+
function applyEmission({ state, loaded }, ctx) {
|
|
612
|
+
const { opts, deps, mounted, refs } = ctx;
|
|
613
|
+
const vcr = opts.vcr();
|
|
614
|
+
const structurallyChanged = isStructurallyDifferent(mounted.value, state);
|
|
615
|
+
// Gate-only flicker after first mount — keep the chain alive so the user's
|
|
616
|
+
// focus / caret / scroll survive. The caller's rawInputs effect continues
|
|
617
|
+
// to push live mapper outputs through the still-mounted innermost.
|
|
618
|
+
if (!structurallyChanged && !state.open)
|
|
619
|
+
return refs;
|
|
620
|
+
// Real structural change — tear down. Angular cascades destroy through
|
|
621
|
+
// every nested ComponentRef, so walking `refs` manually is redundant.
|
|
622
|
+
// `beforeRebuild` gives the caller a chance to detach views it wants to
|
|
623
|
+
// preserve (e.g. the innermost field when only wrappers changed).
|
|
624
|
+
if (structurallyChanged && mounted.value !== null) {
|
|
625
|
+
opts.beforeRebuild?.();
|
|
626
|
+
vcr.clear();
|
|
627
|
+
mounted.value = null;
|
|
628
|
+
}
|
|
629
|
+
if (!state.open || loaded === null)
|
|
630
|
+
return [];
|
|
631
|
+
// Same structure + already mounted — idempotent re-emission, nothing to do.
|
|
632
|
+
if (mounted.value !== null)
|
|
633
|
+
return refs;
|
|
634
|
+
const newRefs = renderWrapperChain({
|
|
635
|
+
outerContainer: vcr,
|
|
636
|
+
loadedWrappers: loaded,
|
|
637
|
+
environmentInjector: deps.environmentInjector,
|
|
638
|
+
logger: deps.logger,
|
|
639
|
+
fieldInputs: opts.fieldInputs?.(),
|
|
640
|
+
fieldInjector: opts.fieldInjector?.(),
|
|
641
|
+
renderInnermost: opts.renderInnermost,
|
|
642
|
+
});
|
|
643
|
+
mounted.value = { wrappers: state.wrappers, rebuildKey: state.rebuildKey };
|
|
644
|
+
return newRefs;
|
|
645
|
+
}
|
|
646
|
+
function isStructurallyDifferent(mounted, next) {
|
|
647
|
+
if (mounted === null)
|
|
648
|
+
return true;
|
|
649
|
+
if (mounted.rebuildKey !== next.rebuildKey)
|
|
650
|
+
return true;
|
|
651
|
+
return !isSameWrapperChain(mounted.wrappers, next.wrappers);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Push the latest `fieldInputs` bag to every mounted wrapper whenever it
|
|
655
|
+
* changes — without rebuilding the chain. No-op when the chain is empty
|
|
656
|
+
* (gated off, or nothing rendered yet).
|
|
657
|
+
*/
|
|
658
|
+
function pushFieldInputsOnChange(opts, getRefs) {
|
|
659
|
+
if (!opts.fieldInputs)
|
|
660
|
+
return;
|
|
661
|
+
explicitEffect([opts.fieldInputs], ([fi]) => {
|
|
662
|
+
for (const ref of getRefs())
|
|
663
|
+
setInputIfDeclared(ref, 'fieldInputs', fi);
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Get the length of an array FieldTree.
|
|
669
|
+
*/
|
|
670
|
+
function getArrayLength(arrayFieldTree) {
|
|
671
|
+
const value = arrayFieldTree().value();
|
|
672
|
+
return Array.isArray(value) ? value.length : 0;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Build a `ReadonlyFieldTree` by extracting the whitelisted read signals from a
|
|
676
|
+
* Signal Forms `FieldTree`. Returns a fresh plain object — no casting, no proxying,
|
|
677
|
+
* so consumers only see the narrow surface and Angular's `WritableSignal` capability
|
|
678
|
+
* on `value` is hidden.
|
|
679
|
+
*/
|
|
680
|
+
function toReadonlyFieldTree(field) {
|
|
681
|
+
const state = field();
|
|
682
|
+
return {
|
|
683
|
+
value: state.value,
|
|
684
|
+
valid: state.valid,
|
|
685
|
+
invalid: state.invalid,
|
|
686
|
+
touched: state.touched,
|
|
687
|
+
dirty: state.dirty,
|
|
688
|
+
required: state.required,
|
|
689
|
+
disabled: state.disabled,
|
|
690
|
+
hidden: state.hidden,
|
|
691
|
+
errors: state.errors,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* DI-scoped cache for `ReadonlyFieldTree` views, keyed on the source FieldTree
|
|
696
|
+
* identity. A fresh WeakMap per root injector keeps renders isolated under SSR —
|
|
697
|
+
* Angular creates a new root injector per request, so there's no shared state
|
|
698
|
+
* between server renders (same pattern as `COMPONENT_CACHE`).
|
|
699
|
+
*
|
|
700
|
+
* @internal
|
|
701
|
+
*/
|
|
702
|
+
const READONLY_FIELD_TREE_CACHE = new InjectionToken('READONLY_FIELD_TREE_CACHE', {
|
|
703
|
+
providedIn: 'root',
|
|
704
|
+
factory: () => new WeakMap(),
|
|
705
|
+
});
|
|
706
|
+
/**
|
|
707
|
+
* Cached variant of {@link toReadonlyFieldTree}. Each FieldTree instance is a
|
|
708
|
+
* stable reference for the lifetime of its form, so one `ReadonlyFieldTree`
|
|
709
|
+
* view per tree is enough. Callers on hot paths (`pushInputs` on every mapper
|
|
710
|
+
* emission) avoid re-allocating the nine-property view object.
|
|
711
|
+
*
|
|
712
|
+
* @internal
|
|
713
|
+
*/
|
|
714
|
+
function toReadonlyFieldTreeCached(cache, field) {
|
|
715
|
+
const key = field;
|
|
716
|
+
const cached = cache.get(key);
|
|
717
|
+
if (cached)
|
|
718
|
+
return cached;
|
|
719
|
+
const view = toReadonlyFieldTree(field);
|
|
720
|
+
cache.set(key, view);
|
|
721
|
+
return view;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Structural directive that renders a `ResolvedField` with its effective
|
|
726
|
+
* wrapper chain.
|
|
727
|
+
*
|
|
728
|
+
* Replaces `*ngComponentOutlet` in field-rendering templates. When the field
|
|
729
|
+
* has no wrappers, the component is created directly at the outlet position
|
|
730
|
+
* (no extra DOM nesting). When wrappers apply, they chain outermost-first and
|
|
731
|
+
* the field renders in the innermost slot.
|
|
732
|
+
*
|
|
733
|
+
* Effective wrappers are merged from (outermost → innermost):
|
|
734
|
+
* 1. `WrapperTypeDefinition.types` auto-associations for the field's `type`
|
|
735
|
+
* 2. `FormConfig.defaultWrappers`
|
|
736
|
+
* 3. Field-level `FieldDef.wrappers` (`null` = explicit opt-out; `[]` does not
|
|
737
|
+
* opt out — auto + defaults still apply)
|
|
738
|
+
*
|
|
739
|
+
* Wrapper config keys (minus `type`) are pushed as individual Angular inputs;
|
|
740
|
+
* every wrapper also receives `fieldInputs` — a `WrapperFieldInputs` bag that
|
|
741
|
+
* includes the field's mapper outputs and a `ReadonlyFieldTree` view of its
|
|
742
|
+
* form state.
|
|
743
|
+
*
|
|
744
|
+
* Rendering is gated by `field.renderReady()` — the directive waits until
|
|
745
|
+
* the mapper produces the required inputs before instantiating the component.
|
|
746
|
+
* Once rendered, a transient `renderReady → false` does *not* tear the chain
|
|
747
|
+
* down; the controller keeps the mounted components alive and ignores the
|
|
748
|
+
* flicker. Only a structural change (wrappers or component class) triggers
|
|
749
|
+
* a rebuild.
|
|
750
|
+
*
|
|
751
|
+
* @example
|
|
752
|
+
* ```html
|
|
753
|
+
* \@for (field of resolvedFields(); track field.key) {
|
|
754
|
+
* <ng-container *dfFieldOutlet="field; environmentInjector: envInjector" />
|
|
755
|
+
* }
|
|
756
|
+
* ```
|
|
757
|
+
*/
|
|
758
|
+
class DfFieldOutlet {
|
|
759
|
+
// Named to match the structural directive microsyntax directly
|
|
760
|
+
// (`*dfFieldOutlet="field; environmentInjector: env"`) so no aliasing is needed.
|
|
761
|
+
dfFieldOutlet = input.required(...(ngDevMode ? [{ debugName: "dfFieldOutlet" }] : /* istanbul ignore next */ []));
|
|
762
|
+
dfFieldOutletEnvironmentInjector = input(undefined, ...(ngDevMode ? [{ debugName: "dfFieldOutletEnvironmentInjector" }] : /* istanbul ignore next */ []));
|
|
763
|
+
vcrRef = inject(ViewContainerRef);
|
|
764
|
+
vcr = signal(this.vcrRef).asReadonly();
|
|
765
|
+
destroyRef = inject(DestroyRef);
|
|
766
|
+
wrapperAutoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
|
|
767
|
+
defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
|
|
768
|
+
readonlyFieldCache = inject(READONLY_FIELD_TREE_CACHE);
|
|
769
|
+
fieldRef;
|
|
770
|
+
/**
|
|
771
|
+
* VCR the `fieldRef`'s host view is currently inserted into (either the outer
|
|
772
|
+
* VCR when there are no wrappers, or the innermost wrapper's `#fieldComponent`
|
|
773
|
+
* slot). Tracked so `beforeRebuild` can detach the view before the outer VCR
|
|
774
|
+
* cascade-destroys it.
|
|
775
|
+
*/
|
|
776
|
+
fieldSlot;
|
|
777
|
+
/** Focus + caret captured during detach, replayed after re-insert. */
|
|
778
|
+
focusSnapshot;
|
|
779
|
+
/**
|
|
780
|
+
* Last rawInputs reference pushed to the innermost field. Used to dedupe the
|
|
781
|
+
* rawInputs effect when the same snapshot was already applied as part of the
|
|
782
|
+
* initial render — keeps per-keystroke input updates O(changed-keys) instead
|
|
783
|
+
* of re-walking the whole input bag.
|
|
784
|
+
*/
|
|
785
|
+
lastPushedInputs;
|
|
786
|
+
componentIdentity = computed(() => this.dfFieldOutlet().component, ...(ngDevMode ? [{ debugName: "componentIdentity" }] : /* istanbul ignore next */ []));
|
|
787
|
+
renderReady = computed(() => this.dfFieldOutlet().renderReady(), ...(ngDevMode ? [{ debugName: "renderReady" }] : /* istanbul ignore next */ []));
|
|
788
|
+
rawInputs = computed(() => this.dfFieldOutlet().inputs(), ...(ngDevMode ? [{ debugName: "rawInputs" }] : /* istanbul ignore next */ []));
|
|
789
|
+
/**
|
|
790
|
+
* Effective wrapper chain. Element-wise identity comparison keeps the signal
|
|
791
|
+
* stable across `ResolvedField` reference changes that don't actually change
|
|
792
|
+
* the chain — avoids rebuilds on reconciled fields.
|
|
793
|
+
*/
|
|
794
|
+
wrappers = computed(() => resolveWrappers(this.dfFieldOutlet().fieldDef, this.defaultWrappersSignal?.(), this.wrapperAutoAssociations), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
|
|
795
|
+
/**
|
|
796
|
+
* `fieldInputs` bag handed to every wrapper in the chain. Memoised on
|
|
797
|
+
* `rawInputs` identity so repeated emissions with the same underlying object
|
|
798
|
+
* return the same view and don't cascade OnPush re-evaluations.
|
|
799
|
+
*/
|
|
800
|
+
fieldInputs = computed(() => this.buildFieldInputs(this.rawInputs()), ...(ngDevMode ? [{ debugName: "fieldInputs" }] : /* istanbul ignore next */ []));
|
|
801
|
+
defaultEnvInjector = inject(EnvironmentInjector);
|
|
802
|
+
/** Environment injector for the innermost field component — `[environmentInjector]` input takes precedence over the directive's own DI. */
|
|
803
|
+
fieldEnvInjector = computed(() => this.dfFieldOutletEnvironmentInjector() ?? this.defaultEnvInjector, ...(ngDevMode ? [{ debugName: "fieldEnvInjector" }] : /* istanbul ignore next */ []));
|
|
804
|
+
/** Field-level injector (FIELD_SIGNAL_CONTEXT, ARRAY_CONTEXT, …). Threaded to the controller so wrappers can inject it too. */
|
|
805
|
+
fieldInjector = computed(() => this.dfFieldOutlet().injector, ...(ngDevMode ? [{ debugName: "fieldInjector" }] : /* istanbul ignore next */ []));
|
|
806
|
+
constructor() {
|
|
807
|
+
createWrapperChainController({
|
|
808
|
+
vcr: this.vcr,
|
|
809
|
+
wrappers: this.wrappers,
|
|
810
|
+
gate: this.renderReady,
|
|
811
|
+
rebuildKey: this.componentIdentity,
|
|
812
|
+
fieldInputs: this.fieldInputs,
|
|
813
|
+
fieldInjector: this.fieldInjector,
|
|
814
|
+
beforeRebuild: () => this.detachFieldRef(),
|
|
815
|
+
renderInnermost: (slot) => {
|
|
816
|
+
const resolved = this.dfFieldOutlet();
|
|
817
|
+
if (this.fieldRef && this.fieldRef.componentType === resolved.component) {
|
|
818
|
+
// Same component class — re-insert the preserved hostView. Browser
|
|
819
|
+
// loses focus on detach, so we replay the snapshot captured in
|
|
820
|
+
// detachFieldRef. Input value persists on the DOM node itself.
|
|
821
|
+
slot.insert(this.fieldRef.hostView);
|
|
822
|
+
this.fieldSlot = slot;
|
|
823
|
+
this.pushRawInputs(this.fieldRef, this.rawInputs());
|
|
824
|
+
this.restoreFocusSnapshot();
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
// Different component class — discard the old ref and create fresh.
|
|
828
|
+
// Merge field + element injectors so the field can inject ancestor
|
|
829
|
+
// wrappers AND form-context tokens (ARRAY_CONTEXT etc.).
|
|
830
|
+
// Clear state before createComponent — if it throws (bad provider,
|
|
831
|
+
// invalid class) we don't want to leak a reference to a destroyed ref
|
|
832
|
+
// that the same-component branch would later try to re-insert.
|
|
833
|
+
this.focusSnapshot = undefined;
|
|
834
|
+
this.lastPushedInputs = undefined;
|
|
835
|
+
const previousRef = this.fieldRef;
|
|
836
|
+
this.fieldRef = undefined;
|
|
837
|
+
this.fieldSlot = undefined;
|
|
838
|
+
previousRef?.destroy();
|
|
839
|
+
this.fieldRef = slot.createComponent(resolved.component, {
|
|
840
|
+
environmentInjector: this.fieldEnvInjector(),
|
|
841
|
+
injector: createWrapperAwareInjector(resolved.injector, slot.injector),
|
|
842
|
+
});
|
|
843
|
+
this.fieldSlot = slot;
|
|
844
|
+
this.pushRawInputs(this.fieldRef, this.rawInputs());
|
|
845
|
+
},
|
|
846
|
+
});
|
|
847
|
+
// Push rawInputs to the innermost field. Safe mid-rebuild — renderInnermost
|
|
848
|
+
// re-reads rawInputs synchronously on mount, so skipping here loses nothing.
|
|
849
|
+
explicitEffect([this.rawInputs], ([rawInputs]) => {
|
|
850
|
+
if (!this.fieldRef)
|
|
851
|
+
return;
|
|
852
|
+
if (this.lastPushedInputs === rawInputs)
|
|
853
|
+
return;
|
|
854
|
+
this.pushRawInputs(this.fieldRef, rawInputs);
|
|
855
|
+
});
|
|
856
|
+
this.destroyRef.onDestroy(() => {
|
|
857
|
+
// Only destroy when detached — vcr.clear() cascades otherwise.
|
|
858
|
+
if (this.fieldRef && !this.fieldSlot)
|
|
859
|
+
this.fieldRef.destroy();
|
|
860
|
+
this.fieldRef = undefined;
|
|
861
|
+
this.fieldSlot = undefined;
|
|
862
|
+
this.lastPushedInputs = undefined;
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
detachFieldRef() {
|
|
866
|
+
if (!this.fieldRef)
|
|
867
|
+
return;
|
|
868
|
+
if (!this.fieldSlot) {
|
|
869
|
+
// Stranded fieldRef — destroy to prevent a leak.
|
|
870
|
+
this.fieldRef.destroy();
|
|
871
|
+
this.fieldRef = undefined;
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
this.captureFocusSnapshot();
|
|
875
|
+
const idx = this.fieldSlot.indexOf(this.fieldRef.hostView);
|
|
876
|
+
if (idx >= 0)
|
|
877
|
+
this.fieldSlot.detach(idx);
|
|
878
|
+
this.fieldSlot = undefined;
|
|
879
|
+
}
|
|
880
|
+
/** Capture active element + selection before the hostView is detached from DOM. */
|
|
881
|
+
captureFocusSnapshot() {
|
|
882
|
+
if (!this.fieldRef)
|
|
883
|
+
return;
|
|
884
|
+
const hostEl = this.fieldRef.location.nativeElement;
|
|
885
|
+
const active = document.activeElement;
|
|
886
|
+
if (!(active instanceof HTMLElement) || !hostEl.contains(active))
|
|
887
|
+
return;
|
|
888
|
+
const sel = active;
|
|
889
|
+
this.focusSnapshot = {
|
|
890
|
+
element: active,
|
|
891
|
+
selectionStart: typeof sel.selectionStart === 'number' ? sel.selectionStart : null,
|
|
892
|
+
selectionEnd: typeof sel.selectionEnd === 'number' ? sel.selectionEnd : null,
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
/** Re-apply the pre-detach focus + caret after the hostView is back in the DOM. */
|
|
896
|
+
restoreFocusSnapshot() {
|
|
897
|
+
const snap = this.focusSnapshot;
|
|
898
|
+
this.focusSnapshot = undefined;
|
|
899
|
+
if (!snap || !snap.element.isConnected)
|
|
900
|
+
return;
|
|
901
|
+
snap.element.focus();
|
|
902
|
+
if (snap.selectionStart !== null && snap.selectionEnd !== null && 'setSelectionRange' in snap.element) {
|
|
903
|
+
snap.element.setSelectionRange(snap.selectionStart, snap.selectionEnd);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
pushRawInputs(ref, rawInputs) {
|
|
907
|
+
// Ref-identity dedupe per key — mappers must emit immutable snapshots.
|
|
908
|
+
const last = this.lastPushedInputs;
|
|
909
|
+
for (const [key, value] of Object.entries(rawInputs)) {
|
|
910
|
+
if (last && last[key] === value)
|
|
911
|
+
continue;
|
|
912
|
+
setInputIfDeclared(ref, key, value);
|
|
913
|
+
}
|
|
914
|
+
this.lastPushedInputs = rawInputs;
|
|
915
|
+
}
|
|
916
|
+
buildFieldInputs(rawInputs) {
|
|
917
|
+
const fieldTreeCandidate = rawInputs['field'];
|
|
918
|
+
// A FieldTree is a callable (() => FieldState). We expose it to wrappers via a narrow
|
|
919
|
+
// read-only view; raw FieldTree is still pushed to the innermost component for writes.
|
|
920
|
+
const readonlyField = fieldTreeCandidate && typeof fieldTreeCandidate === 'function'
|
|
921
|
+
? toReadonlyFieldTreeCached(this.readonlyFieldCache, fieldTreeCandidate)
|
|
922
|
+
: undefined;
|
|
923
|
+
// Shallow spread — relies on the mapper contract (see WrapperFieldInputs)
|
|
924
|
+
// that rawInputs are emitted as fresh snapshots, not mutated in place.
|
|
925
|
+
return {
|
|
926
|
+
...rawInputs,
|
|
927
|
+
field: readonlyField,
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DfFieldOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
931
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.8", type: DfFieldOutlet, isStandalone: true, selector: "[dfFieldOutlet]", inputs: { dfFieldOutlet: { classPropertyName: "dfFieldOutlet", publicName: "dfFieldOutlet", isSignal: true, isRequired: true, transformFunction: null }, dfFieldOutletEnvironmentInjector: { classPropertyName: "dfFieldOutletEnvironmentInjector", publicName: "dfFieldOutletEnvironmentInjector", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
932
|
+
}
|
|
933
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DfFieldOutlet, decorators: [{
|
|
934
|
+
type: Directive,
|
|
935
|
+
args: [{
|
|
936
|
+
selector: '[dfFieldOutlet]',
|
|
937
|
+
}]
|
|
938
|
+
}], ctorParameters: () => [], propDecorators: { dfFieldOutlet: [{ type: i0.Input, args: [{ isSignal: true, alias: "dfFieldOutlet", required: true }] }], dfFieldOutletEnvironmentInjector: [{ type: i0.Input, args: [{ isSignal: true, alias: "dfFieldOutletEnvironmentInjector", required: false }] }] } });
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Wraps a submission action to handle both Promise and Observable returns.
|
|
942
|
+
* Converts Observables to Promises for compatibility with Angular Signal Forms' submit().
|
|
943
|
+
*
|
|
944
|
+
* Errors are NOT caught here — they propagate so that submit() can reject its Promise,
|
|
945
|
+
* allowing the caller's catchError to log and keep the submission stream alive.
|
|
946
|
+
*
|
|
947
|
+
* @param action - The submission action function
|
|
948
|
+
* @returns A wrapped function that returns a Promise
|
|
949
|
+
*/
|
|
950
|
+
function wrapSubmissionAction(action) {
|
|
951
|
+
return async (formTree) => {
|
|
952
|
+
const result = action(formTree);
|
|
953
|
+
// If the action returns an Observable, convert it to a Promise
|
|
954
|
+
if (isObservable(result)) {
|
|
955
|
+
await firstValueFrom(result);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
await Promise.resolve(result);
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Creates an Observable that handles form submission with optional submission action.
|
|
963
|
+
*
|
|
964
|
+
* This utility encapsulates the submission handling logic:
|
|
965
|
+
* - Listens for submit events from the event bus
|
|
966
|
+
* - If a submission.action is configured, wraps it and uses Angular Signal Forms' submit()
|
|
967
|
+
* - Handles both Promise and Observable returns from the action
|
|
968
|
+
* - Uses exhaustMap to ignore new submissions while one is in-flight (first-submit-wins)
|
|
969
|
+
*
|
|
970
|
+
* The returned Observable should be subscribed to with takeUntilDestroyed() in the component.
|
|
971
|
+
*
|
|
972
|
+
* @param options - Configuration options for the submission handler
|
|
973
|
+
* @returns Observable that processes submissions (emits when submission completes)
|
|
974
|
+
*
|
|
975
|
+
* @example
|
|
976
|
+
* ```typescript
|
|
977
|
+
* // In component constructor
|
|
978
|
+
* createSubmissionHandler({
|
|
979
|
+
* eventBus: this.eventBus,
|
|
980
|
+
* configSignal: this.config,
|
|
981
|
+
* formSignal: this.form,
|
|
982
|
+
* })
|
|
983
|
+
* .pipe(takeUntilDestroyed())
|
|
984
|
+
* .subscribe();
|
|
985
|
+
* ```
|
|
986
|
+
*/
|
|
987
|
+
function createSubmissionHandler(options) {
|
|
988
|
+
const { eventBus, configSignal, formSignal, validSignal, logger } = options;
|
|
989
|
+
// exhaustMap ensures first-submit-wins: a second submit event while the first
|
|
990
|
+
// is in-flight is silently dropped rather than cancelling the running Promise.
|
|
991
|
+
// switchMap would unsubscribe the Observable wrapper but cannot cancel the
|
|
992
|
+
// underlying Promise, causing both side effects to execute.
|
|
993
|
+
return eventBus.on('submit').pipe(exhaustMap(() => {
|
|
994
|
+
const submissionConfig = configSignal().submission;
|
|
995
|
+
// If no submission action is configured, let the submitted output handle it
|
|
996
|
+
// This maintains backward compatibility for users handling submission manually
|
|
997
|
+
if (!submissionConfig?.action) {
|
|
998
|
+
return EMPTY;
|
|
999
|
+
}
|
|
1000
|
+
// Guard: match the (submitted) output's safety contract — reject submission
|
|
1001
|
+
// when the form is invalid or has pending async validators.
|
|
1002
|
+
if (!validSignal()) {
|
|
1003
|
+
logger.debug('Submission action skipped: form is not valid (invalid or pending async validators)');
|
|
1004
|
+
return EMPTY;
|
|
1005
|
+
}
|
|
1006
|
+
// Type assertion needed: submission.action accepts the form tree but its signature
|
|
1007
|
+
// is defined broadly in FormConfig. The actual runtime type is FieldTree<TModel>.
|
|
1008
|
+
const wrappedAction = wrapSubmissionAction(submissionConfig.action);
|
|
1009
|
+
// Use Angular Signal Forms' native submit() function
|
|
1010
|
+
// This automatically:
|
|
1011
|
+
// - Sets form.submitting() to true during execution
|
|
1012
|
+
// - Applies server errors to form fields on completion
|
|
1013
|
+
// - Sets form.submitting() to false when done
|
|
1014
|
+
// catchError keeps the exhaustMap stream alive after action failure —
|
|
1015
|
+
// without it, an unhandled error would terminate all future submissions.
|
|
1016
|
+
return from(submit(formSignal(), wrappedAction)).pipe(catchError((error) => {
|
|
1017
|
+
logger.error('Submission action failed:', error);
|
|
1018
|
+
return EMPTY;
|
|
1019
|
+
}));
|
|
1020
|
+
}));
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Injection token for globally enabling form value emission on events.
|
|
1025
|
+
*
|
|
1026
|
+
* When set to `true`, all events dispatched through the EventBus will include
|
|
1027
|
+
* the current form value in the `formValue` property.
|
|
1028
|
+
*
|
|
1029
|
+
* This token is configured via `withEventFormValue()` feature function.
|
|
1030
|
+
* Per-form overrides can be set via `options.emitFormValueOnEvents` in the form config.
|
|
1031
|
+
*
|
|
1032
|
+
* @internal
|
|
1033
|
+
*/
|
|
1034
|
+
const EMIT_FORM_VALUE_ON_EVENTS = new InjectionToken('EMIT_FORM_VALUE_ON_EVENTS', {
|
|
1035
|
+
providedIn: 'root',
|
|
1036
|
+
factory: () => false,
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Registry service that provides access to the root form and its values.
|
|
1041
|
+
*
|
|
1042
|
+
* Constructed via factory in `provideDynamicFormDI` with signals from
|
|
1043
|
+
* `FormStateManager` — no DI injection, no Maps, no lifecycle.
|
|
1044
|
+
*/
|
|
1045
|
+
class RootFormRegistryService {
|
|
1046
|
+
formValue;
|
|
1047
|
+
rootForm;
|
|
1048
|
+
constructor(formValue, rootForm) {
|
|
1049
|
+
this.formValue = formValue;
|
|
1050
|
+
this.rootForm = rootForm;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
356
1054
|
/**
|
|
357
1055
|
* No-operation logger implementation.
|
|
358
1056
|
* All methods are no-ops - used in production by default.
|
|
@@ -532,10 +1230,10 @@ class EventBus {
|
|
|
532
1230
|
}
|
|
533
1231
|
return this.pipeline$.pipe(filter((event) => event.type === eventType));
|
|
534
1232
|
}
|
|
535
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
536
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
1233
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EventBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1234
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EventBus });
|
|
537
1235
|
}
|
|
538
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1236
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EventBus, decorators: [{
|
|
539
1237
|
type: Injectable
|
|
540
1238
|
}] });
|
|
541
1239
|
|
|
@@ -588,7 +1286,7 @@ const INITIALIZATION_TIMEOUT_MS = new InjectionToken('INITIALIZATION_TIMEOUT_MS'
|
|
|
588
1286
|
* ```
|
|
589
1287
|
*/
|
|
590
1288
|
function createInitializationTracker(eventBus, expectedCount) {
|
|
591
|
-
return eventBus.on('component-initialized').pipe(scan((count) => count + 1, 0), map((currentCount) => currentCount >= expectedCount), filter$1((isComplete) => isComplete));
|
|
1289
|
+
return eventBus.on('component-initialized').pipe(scan((count) => count + 1, 0), map$1((currentCount) => currentCount >= expectedCount), filter$1((isComplete) => isComplete));
|
|
592
1290
|
}
|
|
593
1291
|
/**
|
|
594
1292
|
* Creates an observable that tracks component initialization progress with detailed status.
|
|
@@ -613,7 +1311,7 @@ function createInitializationTracker(eventBus, expectedCount) {
|
|
|
613
1311
|
* ```
|
|
614
1312
|
*/
|
|
615
1313
|
function createDetailedInitializationTracker(eventBus, expectedCount) {
|
|
616
|
-
return eventBus.on('component-initialized').pipe(scan((count) => count + 1, 0), map((currentCount) => ({
|
|
1314
|
+
return eventBus.on('component-initialized').pipe(scan((count) => count + 1, 0), map$1((currentCount) => ({
|
|
617
1315
|
currentCount,
|
|
618
1316
|
expectedCount,
|
|
619
1317
|
isComplete: currentCount >= expectedCount,
|
|
@@ -649,11 +1347,11 @@ function setupInitializationTracking(options) {
|
|
|
649
1347
|
const { eventBus, totalComponentsCount, injector, componentId } = options;
|
|
650
1348
|
const timeoutMs = injector.get(INITIALIZATION_TIMEOUT_MS);
|
|
651
1349
|
const logger = injector.get(DynamicFormLogger);
|
|
652
|
-
return toObservable(totalComponentsCount, { injector }).pipe(take(1), switchMap((count) => {
|
|
1350
|
+
return toObservable(totalComponentsCount, { injector }).pipe(take(1), switchMap$1((count) => {
|
|
653
1351
|
let tracking$;
|
|
654
1352
|
if (count === 1) {
|
|
655
1353
|
// Only dynamic-form component, emit immediately when it initializes
|
|
656
|
-
tracking$ = eventBus.on('component-initialized').pipe(filter$1((event) => event.componentType === 'dynamic-form' && event.componentId === componentId), map(() => true), take(1));
|
|
1354
|
+
tracking$ = eventBus.on('component-initialized').pipe(filter$1((event) => event.componentType === 'dynamic-form' && event.componentId === componentId), map$1(() => true), take(1));
|
|
657
1355
|
}
|
|
658
1356
|
else {
|
|
659
1357
|
tracking$ = createInitializationTracker(eventBus, count);
|
|
@@ -787,6 +1485,14 @@ function isSimplifiedArrayField(field) {
|
|
|
787
1485
|
return field.type === 'array' && 'template' in field;
|
|
788
1486
|
}
|
|
789
1487
|
|
|
1488
|
+
/**
|
|
1489
|
+
* Type guard for ContainerField with proper type narrowing
|
|
1490
|
+
*/
|
|
1491
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type guard must accept any field type
|
|
1492
|
+
function isContainerTypedField(field) {
|
|
1493
|
+
return field.type === 'container' && 'fields' in field && 'wrappers' in field;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
790
1496
|
/**
|
|
791
1497
|
* Type guard to check if a field definition has child fields.
|
|
792
1498
|
* This is a looser check that works with FieldDef<unknown> without requiring RegisteredFieldTypes.
|
|
@@ -795,7 +1501,7 @@ function hasChildFields(field) {
|
|
|
795
1501
|
return 'fields' in field && field.fields != null;
|
|
796
1502
|
}
|
|
797
1503
|
/** Container field type names */
|
|
798
|
-
const CONTAINER_TYPES$1 = new Set(['page', 'row', 'group', 'array']);
|
|
1504
|
+
const CONTAINER_TYPES$1 = new Set(['page', 'row', 'group', 'array', 'container']);
|
|
799
1505
|
function isContainerField(field) {
|
|
800
1506
|
return CONTAINER_TYPES$1.has(field.type);
|
|
801
1507
|
}
|
|
@@ -1709,10 +2415,10 @@ class FunctionRegistryService {
|
|
|
1709
2415
|
this.clearAsyncValidators();
|
|
1710
2416
|
this.clearHttpValidators();
|
|
1711
2417
|
}
|
|
1712
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1713
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
2418
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FunctionRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2419
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FunctionRegistryService });
|
|
1714
2420
|
}
|
|
1715
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2421
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FunctionRegistryService, decorators: [{
|
|
1716
2422
|
type: Injectable
|
|
1717
2423
|
}] });
|
|
1718
2424
|
|
|
@@ -1751,10 +2457,10 @@ class SchemaRegistryService {
|
|
|
1751
2457
|
}
|
|
1752
2458
|
return schema;
|
|
1753
2459
|
}
|
|
1754
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1755
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
2460
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SchemaRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2461
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SchemaRegistryService });
|
|
1756
2462
|
}
|
|
1757
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2463
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: SchemaRegistryService, decorators: [{
|
|
1758
2464
|
type: Injectable
|
|
1759
2465
|
}] });
|
|
1760
2466
|
|
|
@@ -2335,10 +3041,10 @@ class FieldContextRegistryService {
|
|
|
2335
3041
|
},
|
|
2336
3042
|
};
|
|
2337
3043
|
}
|
|
2338
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
2339
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
3044
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FieldContextRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3045
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FieldContextRegistryService });
|
|
2340
3046
|
}
|
|
2341
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
3047
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FieldContextRegistryService, decorators: [{
|
|
2342
3048
|
type: Injectable
|
|
2343
3049
|
}] });
|
|
2344
3050
|
|
|
@@ -5566,7 +6272,7 @@ function createHttpDerivationStream(entry, formValue$, context) {
|
|
|
5566
6272
|
// Capture after the guard — TS narrows this to `string` here
|
|
5567
6273
|
const responseExpression = entry.responseExpression;
|
|
5568
6274
|
const debounceMs = entry.debounceMs ?? DEFAULT_HTTP_DEBOUNCE_MS;
|
|
5569
|
-
return formValue$.pipe(startWith(null), pairwise(), map
|
|
6275
|
+
return formValue$.pipe(startWith(null), pairwise(), map(([previous, current]) => ({
|
|
5570
6276
|
current: current,
|
|
5571
6277
|
changedFields: getChangedKeys(previous, current),
|
|
5572
6278
|
})),
|
|
@@ -5575,7 +6281,7 @@ function createHttpDerivationStream(entry, formValue$, context) {
|
|
|
5575
6281
|
if (changedFields.size === 0)
|
|
5576
6282
|
return false;
|
|
5577
6283
|
return entry.dependsOn.some((dep) => changedFields.has(dep));
|
|
5578
|
-
}), debounceTime(debounceMs), switchMap
|
|
6284
|
+
}), debounceTime(debounceMs), switchMap(({ current, changedFields }) => {
|
|
5579
6285
|
return new Observable((subscriber) => {
|
|
5580
6286
|
const formAccessor = untracked(() => context.form());
|
|
5581
6287
|
// Check stopOnUserOverride — skip if the user has manually edited the target field
|
|
@@ -5760,7 +6466,7 @@ function createAsyncDerivationStream(entry, formValue$, context) {
|
|
|
5760
6466
|
// first emission (null vs actual form value), so every dependsOn field matches.
|
|
5761
6467
|
// This intentionally fires the async derivation on initial form load — same
|
|
5762
6468
|
// pattern as http-derivation-stream.ts.
|
|
5763
|
-
return formValue$.pipe(startWith(null), pairwise(), map
|
|
6469
|
+
return formValue$.pipe(startWith(null), pairwise(), map(([previous, current]) => ({
|
|
5764
6470
|
current: current,
|
|
5765
6471
|
changedFields: getChangedKeys(previous, current),
|
|
5766
6472
|
})),
|
|
@@ -5769,7 +6475,7 @@ function createAsyncDerivationStream(entry, formValue$, context) {
|
|
|
5769
6475
|
if (changedFields.size === 0)
|
|
5770
6476
|
return false;
|
|
5771
6477
|
return entry.dependsOn.some((dep) => changedFields.has(dep));
|
|
5772
|
-
}), debounceTime(debounceMs), switchMap
|
|
6478
|
+
}), debounceTime(debounceMs), switchMap(({ current, changedFields }) => {
|
|
5773
6479
|
return new Observable((subscriber) => {
|
|
5774
6480
|
const formAccessor = untracked(() => context.form());
|
|
5775
6481
|
// Check stopOnUserOverride — skip if the user has manually edited the target field
|
|
@@ -5971,7 +6677,7 @@ class DerivationOrchestrator {
|
|
|
5971
6677
|
auditTime(0),
|
|
5972
6678
|
// startWith + pairwise: Track previous and current values to detect changed fields.
|
|
5973
6679
|
// This enables reEngageOnDependencyChange for onChange derivations.
|
|
5974
|
-
startWith(null), pairwise(), filter((pair) => pair[1] !== null), map
|
|
6680
|
+
startWith(null), pairwise(), filter((pair) => pair[1] !== null), map(([previous, current]) => ({
|
|
5975
6681
|
collection: current[0],
|
|
5976
6682
|
formAccessor: current[2],
|
|
5977
6683
|
changedFields: getChangedKeys(previous?.[1] ?? null, current[1]),
|
|
@@ -5999,14 +6705,14 @@ class DerivationOrchestrator {
|
|
|
5999
6705
|
debounceTime(DEFAULT_DEBOUNCE_MS),
|
|
6000
6706
|
// startWith + pairwise: Track previous and current values to detect changes.
|
|
6001
6707
|
// startWith(null) ensures pairwise has an initial value to pair with.
|
|
6002
|
-
startWith(null), pairwise(), filter((pair) => pair[1] !== null), map
|
|
6708
|
+
startWith(null), pairwise(), filter((pair) => pair[1] !== null), map(([previous, current]) => ({
|
|
6003
6709
|
current,
|
|
6004
6710
|
changedFields: getChangedKeys(previous, current),
|
|
6005
6711
|
})), filter(({ changedFields }) => changedFields.size > 0),
|
|
6006
6712
|
// switchMap: For debounced derivations, it's OK to cancel pending work
|
|
6007
6713
|
// if new changes come in - we want the latest debounced values.
|
|
6008
6714
|
// (Unlike onChange which uses exhaustMap to prevent cancellation)
|
|
6009
|
-
switchMap
|
|
6715
|
+
switchMap(({ changedFields }) => {
|
|
6010
6716
|
const collection = untracked(() => this.derivationCollection());
|
|
6011
6717
|
const formAccessor = untracked(() => this.config.form());
|
|
6012
6718
|
if (!collection || !formAccessor)
|
|
@@ -6027,11 +6733,11 @@ class DerivationOrchestrator {
|
|
|
6027
6733
|
createPeriodStream(debounceMs, collection, formAccessor, changedFields) {
|
|
6028
6734
|
const additionalWait = Math.max(0, debounceMs - DEFAULT_DEBOUNCE_MS);
|
|
6029
6735
|
if (additionalWait === 0) {
|
|
6030
|
-
return of(null).pipe(map
|
|
6736
|
+
return of(null).pipe(map(() => {
|
|
6031
6737
|
this.applyDebouncedEntriesForPeriod(debounceMs, collection, formAccessor, changedFields);
|
|
6032
6738
|
}));
|
|
6033
6739
|
}
|
|
6034
|
-
return timer(additionalWait).pipe(map
|
|
6740
|
+
return timer(additionalWait).pipe(map(() => {
|
|
6035
6741
|
const currentCollection = untracked(() => this.derivationCollection());
|
|
6036
6742
|
const currentFormAccessor = untracked(() => this.config.form());
|
|
6037
6743
|
if (currentCollection && currentFormAccessor) {
|
|
@@ -6766,10 +7472,10 @@ class PropertyDerivationOrchestrator {
|
|
|
6766
7472
|
}
|
|
6767
7473
|
setupDebouncedStream() {
|
|
6768
7474
|
toObservable(this.config.formValue, { injector: this.injector })
|
|
6769
|
-
.pipe(debounceTime(DEFAULT_DEBOUNCE_MS), startWith(null), pairwise(), filter((pair) => pair[1] !== null), map
|
|
7475
|
+
.pipe(debounceTime(DEFAULT_DEBOUNCE_MS), startWith(null), pairwise(), filter((pair) => pair[1] !== null), map(([previous, current]) => ({
|
|
6770
7476
|
current,
|
|
6771
7477
|
changedFields: getChangedKeys(previous, current),
|
|
6772
|
-
})), filter(({ changedFields }) => changedFields.size > 0), switchMap
|
|
7478
|
+
})), filter(({ changedFields }) => changedFields.size > 0), switchMap(({ changedFields }) => {
|
|
6773
7479
|
const collection = untracked(() => this.propertyDerivationCollection());
|
|
6774
7480
|
if (!collection)
|
|
6775
7481
|
return of(null);
|
|
@@ -6785,7 +7491,7 @@ class PropertyDerivationOrchestrator {
|
|
|
6785
7491
|
}
|
|
6786
7492
|
createPeriodStream(debounceMs, collection, changedFields) {
|
|
6787
7493
|
const additionalWait = Math.max(0, debounceMs - DEFAULT_DEBOUNCE_MS);
|
|
6788
|
-
return timer(additionalWait).pipe(map
|
|
7494
|
+
return timer(additionalWait).pipe(map(() => {
|
|
6789
7495
|
// For periods beyond DEFAULT_DEBOUNCE_MS, re-read collection as it may have changed during the wait.
|
|
6790
7496
|
// For zero-wait periods, use the original collection directly since no time has elapsed.
|
|
6791
7497
|
const currentCollection = additionalWait > 0 ? (untracked(() => this.propertyDerivationCollection()) ?? collection) : collection;
|
|
@@ -7134,6 +7840,40 @@ class ShiftArrayItemEvent {
|
|
|
7134
7840
|
}
|
|
7135
7841
|
}
|
|
7136
7842
|
|
|
7843
|
+
/**
|
|
7844
|
+
* Event dispatched to move an existing item from one index to another within an array field.
|
|
7845
|
+
*
|
|
7846
|
+
* This is an atomic reorder operation — the item at `fromIndex` is removed and
|
|
7847
|
+
* reinserted at `toIndex`. No template is required because the existing item
|
|
7848
|
+
* (resolved component, form value, and stored template) is preserved.
|
|
7849
|
+
*
|
|
7850
|
+
* @example
|
|
7851
|
+
* ```typescript
|
|
7852
|
+
* // Use the builder API (recommended)
|
|
7853
|
+
* eventBus.dispatch(arrayEvent('contacts').move(0, 2));
|
|
7854
|
+
*
|
|
7855
|
+
* // Or instantiate directly
|
|
7856
|
+
* eventBus.dispatch(new MoveArrayItemEvent('contacts', 0, 2));
|
|
7857
|
+
* ```
|
|
7858
|
+
*/
|
|
7859
|
+
class MoveArrayItemEvent {
|
|
7860
|
+
arrayKey;
|
|
7861
|
+
fromIndex;
|
|
7862
|
+
toIndex;
|
|
7863
|
+
type = 'move-array-item';
|
|
7864
|
+
constructor(
|
|
7865
|
+
/** The key of the array field containing the item to move */
|
|
7866
|
+
arrayKey,
|
|
7867
|
+
/** The current index of the item to move */
|
|
7868
|
+
fromIndex,
|
|
7869
|
+
/** The target index to move the item to */
|
|
7870
|
+
toIndex) {
|
|
7871
|
+
this.arrayKey = arrayKey;
|
|
7872
|
+
this.fromIndex = fromIndex;
|
|
7873
|
+
this.toIndex = toIndex;
|
|
7874
|
+
}
|
|
7875
|
+
}
|
|
7876
|
+
|
|
7137
7877
|
/**
|
|
7138
7878
|
* Event dispatched to remove an item at a SPECIFIC INDEX from an array field.
|
|
7139
7879
|
*
|
|
@@ -7522,8 +8262,8 @@ class PageOrchestratorComponent {
|
|
|
7522
8262
|
}
|
|
7523
8263
|
return false;
|
|
7524
8264
|
}
|
|
7525
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
7526
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
8265
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: PageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8266
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: PageOrchestratorComponent, isStandalone: true, selector: "div[page-orchestrator]", inputs: { pageFields: { classPropertyName: "pageFields", publicName: "pageFields", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, fieldSignalContext: { classPropertyName: "fieldSignalContext", publicName: "fieldSignalContext", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-current-page": "state().currentPageIndex", "attr.data-total-pages": "state().totalPages" }, classAttribute: "df-page-orchestrator" }, providers: [
|
|
7527
8267
|
{
|
|
7528
8268
|
provide: FIELD_SIGNAL_CONTEXT,
|
|
7529
8269
|
useFactory: (orchestrator) => orchestrator.extendedFieldSignalContext(),
|
|
@@ -7553,9 +8293,9 @@ class PageOrchestratorComponent {
|
|
|
7553
8293
|
}
|
|
7554
8294
|
}
|
|
7555
8295
|
}
|
|
7556
|
-
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-
|
|
8296
|
+
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-DV-EX9U0.mjs').then(m => m.default)], () => [import('./ng-forge-dynamic-forms-page-field.component-DV-EX9U0.mjs').then(m => m.default)]] });
|
|
7557
8297
|
}
|
|
7558
|
-
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.
|
|
8298
|
+
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.8", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [import('./ng-forge-dynamic-forms-page-field.component-DV-EX9U0.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
7559
8299
|
type: Component,
|
|
7560
8300
|
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
7561
8301
|
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
@@ -7707,7 +8447,7 @@ function validateSimplifiedTemplate(field) {
|
|
|
7707
8447
|
* Expands a simplified array field into a full ArrayField + optional add button.
|
|
7708
8448
|
*/
|
|
7709
8449
|
function expandSimplifiedArray(field) {
|
|
7710
|
-
const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength } = field;
|
|
8450
|
+
const { template, value = [], addButton, removeButton, key, logic, minLength, maxLength, wrappers, skipAutoWrappers, skipDefaultWrappers, } = field;
|
|
7711
8451
|
const isObjectTemplate = Array.isArray(template);
|
|
7712
8452
|
const values = value;
|
|
7713
8453
|
// Validate template before expansion
|
|
@@ -7744,16 +8484,35 @@ function expandSimplifiedArray(field) {
|
|
|
7744
8484
|
if (maxLength !== undefined) {
|
|
7745
8485
|
arrayFieldObj['maxLength'] = maxLength;
|
|
7746
8486
|
}
|
|
8487
|
+
if (wrappers !== undefined) {
|
|
8488
|
+
arrayFieldObj['wrappers'] = wrappers;
|
|
8489
|
+
}
|
|
8490
|
+
if (skipAutoWrappers !== undefined) {
|
|
8491
|
+
arrayFieldObj['skipAutoWrappers'] = skipAutoWrappers;
|
|
8492
|
+
}
|
|
8493
|
+
if (skipDefaultWrappers !== undefined) {
|
|
8494
|
+
arrayFieldObj['skipDefaultWrappers'] = skipDefaultWrappers;
|
|
8495
|
+
}
|
|
7747
8496
|
// Safe cast: we're constructing a valid ArrayField shape with key, type, and fields
|
|
7748
8497
|
const arrayField = arrayFieldObj;
|
|
7749
|
-
// Store normalization metadata via Symbol property instead of a runtime property
|
|
8498
|
+
// Store normalization metadata via Symbol property instead of a runtime property.
|
|
8499
|
+
// `template` always gets populated so the array component can resolve untracked
|
|
8500
|
+
// items (e.g., from external form-value updates).
|
|
8501
|
+
//
|
|
8502
|
+
// For object templates, we must include the auto-generated remove button in the stored
|
|
8503
|
+
// template because object arrays embed the button inside each item's fields (via
|
|
8504
|
+
// buildObjectItem) rather than via the `autoRemoveButton` metadata. Without this,
|
|
8505
|
+
// items resolved from metadata would render without the remove button that
|
|
8506
|
+
// originally-declared items have. Primitive templates keep the button as separate
|
|
8507
|
+
// metadata and pass the raw template through — withAutoRemove() appends the button
|
|
8508
|
+
// at resolution time from autoRemoveButton.
|
|
7750
8509
|
const primitiveFieldKey = !isObjectTemplate ? template.key : undefined;
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
}
|
|
8510
|
+
const metadataTemplate = isObjectTemplate ? buildObjectItemTemplate(template, removeButton) : template;
|
|
8511
|
+
setNormalizedArrayMetadata(arrayFieldObj, {
|
|
8512
|
+
...(hasAutoRemove && { autoRemoveButton: buildRemoveButton(removeButton) }),
|
|
8513
|
+
...(primitiveFieldKey && { primitiveFieldKey }),
|
|
8514
|
+
template: metadataTemplate,
|
|
8515
|
+
});
|
|
7757
8516
|
// Construct the add button (sibling, placed after the array)
|
|
7758
8517
|
let addButtonField;
|
|
7759
8518
|
if (addButton !== false) {
|
|
@@ -7826,7 +8585,7 @@ function buildRemoveButton(config) {
|
|
|
7826
8585
|
* Type guard to check if a field is a container with fields
|
|
7827
8586
|
*/
|
|
7828
8587
|
function isContainerWithFields(field) {
|
|
7829
|
-
return ((field.type === 'row' || field.type === 'group' || field.type === 'page') &&
|
|
8588
|
+
return ((field.type === 'row' || field.type === 'group' || field.type === 'page' || field.type === 'container') &&
|
|
7830
8589
|
'fields' in field &&
|
|
7831
8590
|
Array.isArray(field.fields));
|
|
7832
8591
|
}
|
|
@@ -8239,7 +8998,7 @@ function createHttpConditionLogicFunction(condition) {
|
|
|
8239
8998
|
options['body'] = request.body;
|
|
8240
8999
|
if (request.headers)
|
|
8241
9000
|
options['headers'] = request.headers;
|
|
8242
|
-
return httpClient.request(method, request.url, options).pipe(map
|
|
9001
|
+
return httpClient.request(method, request.url, options).pipe(map((response) => extractBoolean(response, condition.responseExpression, pendingValue, logger)), tap((value) => {
|
|
8243
9002
|
const requestKey = stableStringify(request);
|
|
8244
9003
|
cache.set(requestKey, value, cacheDurationMs);
|
|
8245
9004
|
}), catchError((error) => {
|
|
@@ -8364,7 +9123,7 @@ function createAsyncConditionLogicFunction(condition) {
|
|
|
8364
9123
|
const evaluationContext = untracked(() => fieldContextRegistry.createReactiveEvaluationContext(capturedCtx, functionRegistry.getCustomFunctions()));
|
|
8365
9124
|
// asyncFn may return Promise<boolean> or Observable<boolean>.
|
|
8366
9125
|
// from() normalizes both to Observable.
|
|
8367
|
-
return from(asyncFn(evaluationContext)).pipe(map
|
|
9126
|
+
return from(asyncFn(evaluationContext)).pipe(map((result) => !!result), catchError((error) => {
|
|
8368
9127
|
logger.warn('Async Condition - function failed:', error);
|
|
8369
9128
|
return of(pendingValue);
|
|
8370
9129
|
}));
|
|
@@ -9147,8 +9906,8 @@ function getNumberValidationConfig(fieldDef) {
|
|
|
9147
9906
|
* Cross-field validators are skipped at field level and applied at form level via validateTree.
|
|
9148
9907
|
*/
|
|
9149
9908
|
function mapFieldToForm(fieldDef, fieldPath) {
|
|
9150
|
-
// Layout containers (page, row) - flatten children to current level
|
|
9151
|
-
if (isPageField(fieldDef) || isRowField(fieldDef)) {
|
|
9909
|
+
// Layout containers (page, row, container) - flatten children to current level
|
|
9910
|
+
if (isPageField(fieldDef) || isRowField(fieldDef) || isContainerTypedField(fieldDef)) {
|
|
9152
9911
|
mapContainerChildren(fieldDef.fields, fieldPath);
|
|
9153
9912
|
return;
|
|
9154
9913
|
}
|
|
@@ -9305,8 +10064,8 @@ function mapArrayFieldToForm(arrayField, fieldPath) {
|
|
|
9305
10064
|
const pathRecord = itemPath;
|
|
9306
10065
|
// Map ALL unique template fields from all object items
|
|
9307
10066
|
for (const templateField of allObjectFields) {
|
|
9308
|
-
if (isRowField(templateField) || isPageField(templateField)) {
|
|
9309
|
-
// Row/page templates flatten their children
|
|
10067
|
+
if (isRowField(templateField) || isPageField(templateField) || isContainerTypedField(templateField)) {
|
|
10068
|
+
// Row/page/container templates flatten their children
|
|
9310
10069
|
mapContainerChildren(templateField.fields, itemPath);
|
|
9311
10070
|
}
|
|
9312
10071
|
else if (isGroupField(templateField)) {
|
|
@@ -9345,8 +10104,8 @@ function mapArrayFieldToForm(arrayField, fieldPath) {
|
|
|
9345
10104
|
function collectFieldsFromObjectItem(itemFields, allFields) {
|
|
9346
10105
|
const seenKeys = new Set(allFields.map((f) => f.key).filter(Boolean));
|
|
9347
10106
|
for (const field of itemFields) {
|
|
9348
|
-
// For row/page fields, we need to collect their children's keys
|
|
9349
|
-
if (isRowField(field) || isPageField(field)) {
|
|
10107
|
+
// For row/page/container fields, we need to collect their children's keys
|
|
10108
|
+
if (isRowField(field) || isPageField(field) || isContainerTypedField(field)) {
|
|
9350
10109
|
// Use a synthetic key for container fields to dedupe them
|
|
9351
10110
|
const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;
|
|
9352
10111
|
if (!seenKeys.has(containerKey)) {
|
|
@@ -9477,17 +10236,24 @@ function getFieldDefaultValue(field, registry) {
|
|
|
9477
10236
|
}
|
|
9478
10237
|
return groupDefaults;
|
|
9479
10238
|
}
|
|
9480
|
-
//
|
|
10239
|
+
// Narrow once: `field` is FieldDef<unknown>, but value/nullable are defined on BaseValueField.
|
|
10240
|
+
// This cast avoids per-access `as` and documents what we're reading.
|
|
10241
|
+
const valueField = field;
|
|
10242
|
+
const isNullable = valueField.nullable === true;
|
|
10243
|
+
// Use explicit value if provided (including null when nullable)
|
|
9481
10244
|
if ('value' in field) {
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
return field.value;
|
|
10245
|
+
if (valueField.value !== null && valueField.value !== undefined) {
|
|
10246
|
+
return valueField.value;
|
|
9485
10247
|
}
|
|
9486
|
-
//
|
|
9487
|
-
if (
|
|
9488
|
-
return
|
|
10248
|
+
// Preserve explicit null for nullable fields
|
|
10249
|
+
if (valueField.value === null && isNullable) {
|
|
10250
|
+
return null;
|
|
9489
10251
|
}
|
|
9490
|
-
//
|
|
10252
|
+
// Non-nullable null and undefined both fall through to type-specific defaults below
|
|
10253
|
+
}
|
|
10254
|
+
// Nullable fields with no explicit value default to null
|
|
10255
|
+
if (isNullable) {
|
|
10256
|
+
return null;
|
|
9491
10257
|
}
|
|
9492
10258
|
// Type-specific defaults when no value is specified
|
|
9493
10259
|
if (field.type === 'checkbox') {
|
|
@@ -9519,8 +10285,8 @@ function getFieldDefaultValue(field, registry) {
|
|
|
9519
10285
|
// Groups wrap their value under the group key
|
|
9520
10286
|
itemValue[templateField.key] = fieldValue;
|
|
9521
10287
|
}
|
|
9522
|
-
else if (templateField.type === 'row') {
|
|
9523
|
-
// Rows flatten their fields directly
|
|
10288
|
+
else if (templateField.type === 'row' || templateField.type === 'container') {
|
|
10289
|
+
// Rows and containers flatten their fields directly
|
|
9524
10290
|
if (fieldValue && typeof fieldValue === 'object') {
|
|
9525
10291
|
itemValue = { ...itemValue, ...fieldValue };
|
|
9526
10292
|
}
|
|
@@ -10209,18 +10975,20 @@ function flattenFields(fields, registry, options = {}) {
|
|
|
10209
10975
|
// Step 1: Determine how this field type should handle its value in the form
|
|
10210
10976
|
// valueHandling can be: 'include', 'exclude', or 'flatten'
|
|
10211
10977
|
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
10212
|
-
// Step 2: Check if this is a row field that should be preserved for DOM rendering
|
|
10978
|
+
// Step 2: Check if this is a row or container field that should be preserved for DOM rendering
|
|
10213
10979
|
// Row fields need to render their container element for grid layouts to work
|
|
10214
|
-
|
|
10980
|
+
// Container fields need to render their container for the wrapper chain
|
|
10981
|
+
if (options.preserveRows && (isRowField(field) || isContainerTypedField(field))) {
|
|
10215
10982
|
if (field.fields) {
|
|
10216
10983
|
// Recursively flatten children while preserving row structure
|
|
10217
10984
|
const flattenedChildren = flattenFields(normalizeFieldsArray(field.fields), registry, options);
|
|
10218
|
-
// Keep the row field in the result with its flattened children
|
|
10219
|
-
// This allows the
|
|
10985
|
+
// Keep the row/container field in the result with its flattened children
|
|
10986
|
+
// This allows the container component to render while children are flattened
|
|
10987
|
+
const autoPrefix = isRowField(field) ? 'auto_row' : 'auto_container';
|
|
10220
10988
|
result.push({
|
|
10221
10989
|
...field,
|
|
10222
10990
|
fields: flattenedChildren,
|
|
10223
|
-
key: field.key ||
|
|
10991
|
+
key: field.key || `${autoPrefix}_${autoKeyCounter++}`,
|
|
10224
10992
|
});
|
|
10225
10993
|
}
|
|
10226
10994
|
}
|
|
@@ -10792,6 +11560,17 @@ function mapFieldToInputs(fieldDef, fieldRegistry) {
|
|
|
10792
11560
|
});
|
|
10793
11561
|
}
|
|
10794
11562
|
|
|
11563
|
+
function createRenderReadySignal(inputs, definition) {
|
|
11564
|
+
const explicitRenderReadyWhen = definition?.renderReadyWhen;
|
|
11565
|
+
const requiredInputs = definition?.mapper && explicitRenderReadyWhen === undefined ? ['field'] : (explicitRenderReadyWhen ?? []);
|
|
11566
|
+
if (requiredInputs.length === 0) {
|
|
11567
|
+
return computed(() => true);
|
|
11568
|
+
}
|
|
11569
|
+
return computed(() => {
|
|
11570
|
+
const currentInputs = inputs();
|
|
11571
|
+
return requiredInputs.every((inputName) => currentInputs[inputName] !== undefined);
|
|
11572
|
+
});
|
|
11573
|
+
}
|
|
10795
11574
|
/**
|
|
10796
11575
|
* Resolves a single field definition to a ResolvedField using RxJS.
|
|
10797
11576
|
* Loads the component asynchronously and maps inputs in the injection context.
|
|
@@ -10805,7 +11584,7 @@ function mapFieldToInputs(fieldDef, fieldRegistry) {
|
|
|
10805
11584
|
* @returns Observable that emits ResolvedField or undefined (for componentless fields or on error)
|
|
10806
11585
|
*/
|
|
10807
11586
|
function resolveField(fieldDef, context) {
|
|
10808
|
-
return from(context.loadTypeComponent(fieldDef.type)).pipe(map
|
|
11587
|
+
return from(context.loadTypeComponent(fieldDef.type)).pipe(map((component) => {
|
|
10809
11588
|
// Check if component is destroyed before proceeding
|
|
10810
11589
|
if (context.destroyRef.destroyed) {
|
|
10811
11590
|
return undefined;
|
|
@@ -10816,15 +11595,18 @@ function resolveField(fieldDef, context) {
|
|
|
10816
11595
|
}
|
|
10817
11596
|
// Run mapper in injection context
|
|
10818
11597
|
const inputs = runInInjectionContext(context.injector, () => mapFieldToInputs(fieldDef, context.registry));
|
|
11598
|
+
const definition = context.registry.get(fieldDef.type);
|
|
10819
11599
|
// Fields with components should always have inputs (componentless fields are handled above)
|
|
10820
11600
|
if (!inputs) {
|
|
10821
11601
|
return undefined;
|
|
10822
11602
|
}
|
|
10823
11603
|
return {
|
|
10824
11604
|
key: fieldDef.key,
|
|
11605
|
+
fieldDef,
|
|
10825
11606
|
component,
|
|
10826
11607
|
injector: context.injector,
|
|
10827
11608
|
inputs,
|
|
11609
|
+
renderReady: createRenderReadySignal(inputs, definition),
|
|
10828
11610
|
};
|
|
10829
11611
|
}), catchError((error) => {
|
|
10830
11612
|
// Only call onError if component is not destroyed to avoid accessing cleaned-up state
|
|
@@ -10850,14 +11632,17 @@ function resolveFieldSync(fieldDef, context) {
|
|
|
10850
11632
|
return undefined;
|
|
10851
11633
|
}
|
|
10852
11634
|
const inputs = runInInjectionContext(context.injector, () => mapFieldToInputs(fieldDef, context.registry));
|
|
11635
|
+
const definition = context.registry.get(fieldDef.type);
|
|
10853
11636
|
if (!inputs) {
|
|
10854
11637
|
return undefined;
|
|
10855
11638
|
}
|
|
10856
11639
|
return {
|
|
10857
11640
|
key: fieldDef.key,
|
|
11641
|
+
fieldDef,
|
|
10858
11642
|
component,
|
|
10859
11643
|
injector: context.injector,
|
|
10860
11644
|
inputs,
|
|
11645
|
+
renderReady: createRenderReadySignal(inputs, definition),
|
|
10861
11646
|
};
|
|
10862
11647
|
}
|
|
10863
11648
|
/**
|
|
@@ -10885,13 +11670,13 @@ function reconcileFields(prev, curr) {
|
|
|
10885
11670
|
* Used by container components (page, group) to resolve their child fields.
|
|
10886
11671
|
*/
|
|
10887
11672
|
function createFieldResolutionPipe(getContext) {
|
|
10888
|
-
return pipe(switchMap
|
|
11673
|
+
return pipe(switchMap((fields) => {
|
|
10889
11674
|
if (!fields || fields.length === 0) {
|
|
10890
11675
|
return of([]);
|
|
10891
11676
|
}
|
|
10892
11677
|
const context = getContext();
|
|
10893
11678
|
return forkJoin(fields.map((f) => resolveField(f, context)));
|
|
10894
|
-
}), map
|
|
11679
|
+
}), map((fields) => fields.filter((f) => f !== undefined)), scan$1(reconcileFields, []));
|
|
10895
11680
|
}
|
|
10896
11681
|
|
|
10897
11682
|
/**
|
|
@@ -11156,13 +11941,8 @@ function injectFieldRegistry() {
|
|
|
11156
11941
|
if (cached) {
|
|
11157
11942
|
return cached;
|
|
11158
11943
|
}
|
|
11159
|
-
// Handle async loading
|
|
11160
11944
|
try {
|
|
11161
|
-
const
|
|
11162
|
-
// Handle ES module imports - extract default export if needed
|
|
11163
|
-
const moduleResult = result;
|
|
11164
|
-
const component = (typeof moduleResult === 'object' && 'default' in moduleResult && moduleResult.default) || result;
|
|
11165
|
-
// Populate cache for future sync lookups
|
|
11945
|
+
const component = resolveDefaultExport(await fieldType.loadComponent());
|
|
11166
11946
|
if (component) {
|
|
11167
11947
|
componentCache.set(name, component);
|
|
11168
11948
|
}
|
|
@@ -12344,13 +13124,13 @@ class FormStateManager {
|
|
|
12344
13124
|
cleared$;
|
|
12345
13125
|
/** Emits once when the state machine reaches 'ready'. Useful for tests. */
|
|
12346
13126
|
get ready$() {
|
|
12347
|
-
return this.machine.state$.pipe(filter(isReadyState), take$1(1), map
|
|
13127
|
+
return this.machine.state$.pipe(filter(isReadyState), take$1(1), map(() => undefined));
|
|
12348
13128
|
}
|
|
12349
13129
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
12350
13130
|
// Constructor
|
|
12351
13131
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
12352
13132
|
constructor() {
|
|
12353
|
-
this.resolvedFields = derivedFromDeferred(this.fieldsSource, pipe(switchMap
|
|
13133
|
+
this.resolvedFields = derivedFromDeferred(this.fieldsSource, pipe(switchMap((fields) => {
|
|
12354
13134
|
if (!fields || fields.length === 0) {
|
|
12355
13135
|
return of([]);
|
|
12356
13136
|
}
|
|
@@ -12399,13 +13179,13 @@ class FormStateManager {
|
|
|
12399
13179
|
if (asyncObs.length === 0) {
|
|
12400
13180
|
return of(results);
|
|
12401
13181
|
}
|
|
12402
|
-
return forkJoin(asyncObs).pipe(map
|
|
13182
|
+
return forkJoin(asyncObs).pipe(map((asyncResults) => {
|
|
12403
13183
|
for (let j = 0; j < asyncResults.length; j++) {
|
|
12404
13184
|
results[asyncIndexes[j]] = asyncResults[j];
|
|
12405
13185
|
}
|
|
12406
13186
|
return results;
|
|
12407
13187
|
}));
|
|
12408
|
-
}), map
|
|
13188
|
+
}), map((fields) => fields.filter((f) => f !== undefined)), scan$1(reconcileFields, [])), { initialValue: [], injector: this.injector });
|
|
12409
13189
|
// Length comparison works because reconcileFields preserves ordering and
|
|
12410
13190
|
// fieldsSource changes atomically during transitions (lengths won't match until settled).
|
|
12411
13191
|
this.isFieldPipelineSettled = computed(() => {
|
|
@@ -12420,7 +13200,7 @@ class FormStateManager {
|
|
|
12420
13200
|
}),
|
|
12421
13201
|
// switchMap is intentional here: mapping is synchronous (reads signals, no async Promise).
|
|
12422
13202
|
// The exhaustMap concern in createSubmissionHandler does not apply to this path.
|
|
12423
|
-
switchMap
|
|
13203
|
+
switchMap(() => {
|
|
12424
13204
|
const config = this.activeConfig();
|
|
12425
13205
|
const submissionConfig = config?.submission;
|
|
12426
13206
|
if (submissionConfig?.action) {
|
|
@@ -12431,8 +13211,8 @@ class FormStateManager {
|
|
|
12431
13211
|
}
|
|
12432
13212
|
return of(this.filteredFormValue());
|
|
12433
13213
|
}), takeUntilDestroyed(this.destroyRef));
|
|
12434
|
-
this.reset$ = this.eventBus.on('form-reset').pipe(map
|
|
12435
|
-
this.cleared$ = this.eventBus.on('form-clear').pipe(map
|
|
13214
|
+
this.reset$ = this.eventBus.on('form-reset').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
|
|
13215
|
+
this.cleared$ = this.eventBus.on('form-clear').pipe(map(() => undefined), takeUntilDestroyed(this.destroyRef));
|
|
12436
13216
|
// Create the state machine eagerly
|
|
12437
13217
|
this.machine = createFormStateMachine({
|
|
12438
13218
|
injector: this.injector,
|
|
@@ -12602,10 +13382,10 @@ class FormStateManager {
|
|
|
12602
13382
|
ngOnDestroy() {
|
|
12603
13383
|
this.machine.dispatch({ type: Action.Destroy });
|
|
12604
13384
|
}
|
|
12605
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
12606
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
13385
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormStateManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
13386
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormStateManager });
|
|
12607
13387
|
}
|
|
12608
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
13388
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormStateManager, decorators: [{
|
|
12609
13389
|
type: Injectable
|
|
12610
13390
|
}], ctorParameters: () => [] });
|
|
12611
13391
|
|
|
@@ -12876,6 +13656,11 @@ function provideDynamicFormDI() {
|
|
|
12876
13656
|
useFactory: (stateManager) => computed(() => stateManager.activeConfig()?.defaultProps),
|
|
12877
13657
|
deps: [FormStateManager],
|
|
12878
13658
|
},
|
|
13659
|
+
{
|
|
13660
|
+
provide: DEFAULT_WRAPPERS,
|
|
13661
|
+
useFactory: (stateManager) => computed(() => stateManager.activeConfig()?.defaultWrappers),
|
|
13662
|
+
deps: [FormStateManager],
|
|
13663
|
+
},
|
|
12879
13664
|
{
|
|
12880
13665
|
provide: DEFAULT_VALIDATION_MESSAGES,
|
|
12881
13666
|
useFactory: (stateManager) => computed(() => stateManager.activeConfig()?.defaultValidationMessages),
|
|
@@ -13006,10 +13791,10 @@ class EventDispatcher {
|
|
|
13006
13791
|
disconnect() {
|
|
13007
13792
|
this.bus = undefined;
|
|
13008
13793
|
}
|
|
13009
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
13010
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
13794
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EventDispatcher, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
13795
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EventDispatcher });
|
|
13011
13796
|
}
|
|
13012
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
13797
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EventDispatcher, decorators: [{
|
|
13013
13798
|
type: Injectable
|
|
13014
13799
|
}] });
|
|
13015
13800
|
|
|
@@ -13219,8 +14004,8 @@ class DynamicForm {
|
|
|
13219
14004
|
error: (err) => this.logger.error('Submission handler error', err),
|
|
13220
14005
|
});
|
|
13221
14006
|
}
|
|
13222
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
13223
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
14007
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14008
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DynamicForm, isStandalone: true, selector: "form[dynamic-form]", inputs: { config: { classPropertyName: "config", publicName: "dynamic-form", isSignal: true, isRequired: true, transformFunction: null }, formOptions: { classPropertyName: "formOptions", publicName: "formOptions", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted", reset: "reset", cleared: "cleared", events: "events", initialized: "initialized", onPageChange: "onPageChange", onPageNavigationStateChange: "onPageNavigationStateChange" }, host: { attributes: { "novalidate": "" }, listeners: { "submit": "onNativeSubmit($event)" }, properties: { "class.disabled": "disabled()", "class.df-form-paged": "formModeDetection().mode === \"paged\"", "class.df-form-non-paged": "formModeDetection().mode === \"non-paged\"", "attr.data-form-mode": "formModeDetection().mode" }, classAttribute: "df-dynamic-form df-form" }, providers: [provideDynamicFormDI()], ngImport: i0, template: `
|
|
13224
14009
|
@if (shouldRender()) {
|
|
13225
14010
|
@switch (formModeDetection().mode) {
|
|
13226
14011
|
@case ('paged') {
|
|
@@ -13228,14 +14013,7 @@ class DynamicForm {
|
|
|
13228
14013
|
}
|
|
13229
14014
|
@case ('non-paged') {
|
|
13230
14015
|
@for (field of resolvedFields(); track field.key) {
|
|
13231
|
-
<ng-container
|
|
13232
|
-
*ngComponentOutlet="
|
|
13233
|
-
field.component;
|
|
13234
|
-
injector: field.injector;
|
|
13235
|
-
environmentInjector: environmentInjector;
|
|
13236
|
-
inputs: field.inputs()
|
|
13237
|
-
"
|
|
13238
|
-
/>
|
|
14016
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
13239
14017
|
}
|
|
13240
14018
|
}
|
|
13241
14019
|
@default {
|
|
@@ -13243,11 +14021,11 @@ class DynamicForm {
|
|
|
13243
14021
|
}
|
|
13244
14022
|
}
|
|
13245
14023
|
}
|
|
13246
|
-
`, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}:host>*{grid-column:1/-1}:host.df-form-spacing-custom{--df-grid-row-gap: var(--form-custom-spacing, 1.5rem)}:host.df-form-gap-none{--df-grid-row-gap: 0}:host.df-form-gap-xs{--df-grid-row-gap: .25rem}:host.df-form-gap-sm{--df-grid-row-gap: .5rem}:host.df-form-gap-md{--df-grid-row-gap: 1rem}:host.df-form-gap-lg{--df-grid-row-gap: 1.5rem}:host.df-form-gap-xl{--df-grid-row-gap: 2rem}:host.df-form-paged{--df-grid-row-gap: 0}\n"], dependencies: [{ kind: "directive", type:
|
|
14024
|
+
`, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}:host>*{grid-column:1/-1}:host.df-form-spacing-custom{--df-grid-row-gap: var(--form-custom-spacing, 1.5rem)}:host.df-form-gap-none{--df-grid-row-gap: 0}:host.df-form-gap-xs{--df-grid-row-gap: .25rem}:host.df-form-gap-sm{--df-grid-row-gap: .5rem}:host.df-form-gap-md{--df-grid-row-gap: 1rem}:host.df-form-gap-lg{--df-grid-row-gap: 1.5rem}:host.df-form-gap-xl{--df-grid-row-gap: 2rem}:host.df-form-paged{--df-grid-row-gap: 0}\n"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }, { kind: "component", type: PageOrchestratorComponent, selector: "div[page-orchestrator]", inputs: ["pageFields", "form", "fieldSignalContext"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13247
14025
|
}
|
|
13248
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
14026
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DynamicForm, decorators: [{
|
|
13249
14027
|
type: Component,
|
|
13250
|
-
args: [{ selector: 'form[dynamic-form]', imports: [
|
|
14028
|
+
args: [{ selector: 'form[dynamic-form]', imports: [DfFieldOutlet, PageOrchestratorComponent], template: `
|
|
13251
14029
|
@if (shouldRender()) {
|
|
13252
14030
|
@switch (formModeDetection().mode) {
|
|
13253
14031
|
@case ('paged') {
|
|
@@ -13255,14 +14033,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
|
|
|
13255
14033
|
}
|
|
13256
14034
|
@case ('non-paged') {
|
|
13257
14035
|
@for (field of resolvedFields(); track field.key) {
|
|
13258
|
-
<ng-container
|
|
13259
|
-
*ngComponentOutlet="
|
|
13260
|
-
field.component;
|
|
13261
|
-
injector: field.injector;
|
|
13262
|
-
environmentInjector: environmentInjector;
|
|
13263
|
-
inputs: field.inputs()
|
|
13264
|
-
"
|
|
13265
|
-
/>
|
|
14036
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
13266
14037
|
}
|
|
13267
14038
|
}
|
|
13268
14039
|
@default {
|
|
@@ -13281,7 +14052,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
|
|
|
13281
14052
|
}, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}:host>*{grid-column:1/-1}:host.df-form-spacing-custom{--df-grid-row-gap: var(--form-custom-spacing, 1.5rem)}:host.df-form-gap-none{--df-grid-row-gap: 0}:host.df-form-gap-xs{--df-grid-row-gap: .25rem}:host.df-form-gap-sm{--df-grid-row-gap: .5rem}:host.df-form-gap-md{--df-grid-row-gap: 1rem}:host.df-form-gap-lg{--df-grid-row-gap: 1.5rem}:host.df-form-gap-xl{--df-grid-row-gap: 2rem}:host.df-form-paged{--df-grid-row-gap: 0}\n"] }]
|
|
13282
14053
|
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "dynamic-form", required: true }] }], formOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "formOptions", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], validityChange: [{ type: i0.Output, args: ["validityChange"] }], dirtyChange: [{ type: i0.Output, args: ["dirtyChange"] }], submitted: [{ type: i0.Output, args: ["submitted"] }], reset: [{ type: i0.Output, args: ["reset"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], events: [{ type: i0.Output, args: ["events"] }], initialized: [{ type: i0.Output, args: ["initialized"] }], onPageChange: [{ type: i0.Output, args: ["onPageChange"] }], onPageNavigationStateChange: [{ type: i0.Output, args: ["onPageNavigationStateChange"] }] } });
|
|
13283
14054
|
/**
|
|
13284
|
-
* Recursively counts container fields (page, row, group, array) in a field tree.
|
|
14055
|
+
* Recursively counts container fields (page, row, group, array, wrapper) in a field tree.
|
|
13285
14056
|
* Descends into container children including array item templates to ensure
|
|
13286
14057
|
* nested containers are counted for accurate initialization tracking.
|
|
13287
14058
|
*/
|
|
@@ -13698,24 +14469,28 @@ function groupFieldMapper(fieldDef) {
|
|
|
13698
14469
|
});
|
|
13699
14470
|
}
|
|
13700
14471
|
|
|
14472
|
+
const ROW_WRAPPERS = [{ type: 'row' }];
|
|
13701
14473
|
/**
|
|
13702
|
-
* Maps a row field definition to component inputs.
|
|
14474
|
+
* Maps a row field definition to container component inputs.
|
|
13703
14475
|
*
|
|
13704
|
-
*
|
|
13705
|
-
*
|
|
14476
|
+
* `row` is a virtual field type: it resolves to `ContainerFieldComponent` with a
|
|
14477
|
+
* synthesized `{ type: 'row' }` wrapper, so the user-facing config stays
|
|
14478
|
+
* `{ type: 'row', fields: [...] }` while the runtime uses the container +
|
|
14479
|
+
* wrapper pipeline.
|
|
13706
14480
|
*
|
|
13707
14481
|
* Supports hidden state resolution via `logic` array or static `hidden` property.
|
|
13708
|
-
*
|
|
13709
|
-
* @param fieldDef The row field definition
|
|
13710
|
-
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
13711
14482
|
*/
|
|
13712
14483
|
function rowFieldMapper(fieldDef) {
|
|
13713
14484
|
const rootFormRegistry = inject(RootFormRegistryService);
|
|
13714
14485
|
const className = buildClassName(fieldDef);
|
|
13715
14486
|
return computed(() => {
|
|
14487
|
+
// Rebuilt each emission so logic-driven updates to fieldDef (e.g. `disabled`)
|
|
14488
|
+
// flow through. Shallow spread is safe while RowField has no nested
|
|
14489
|
+
// mutable props; add a deep-clone if that stops being true.
|
|
14490
|
+
const containerField = { ...fieldDef, wrappers: ROW_WRAPPERS };
|
|
13716
14491
|
const inputs = {
|
|
13717
14492
|
key: fieldDef.key,
|
|
13718
|
-
field:
|
|
14493
|
+
field: containerField,
|
|
13719
14494
|
...(className !== undefined && { className }),
|
|
13720
14495
|
};
|
|
13721
14496
|
applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
|
|
@@ -13774,6 +14549,38 @@ function textFieldMapper(fieldDef) {
|
|
|
13774
14549
|
});
|
|
13775
14550
|
}
|
|
13776
14551
|
|
|
14552
|
+
/**
|
|
14553
|
+
* Maps a container field definition to component inputs.
|
|
14554
|
+
*
|
|
14555
|
+
* Container components are layout containers that don't change the form shape.
|
|
14556
|
+
* The container component will inject FIELD_SIGNAL_CONTEXT directly.
|
|
14557
|
+
*
|
|
14558
|
+
* Supports hidden state resolution via `logic` array or static `hidden` property.
|
|
14559
|
+
*
|
|
14560
|
+
* @param fieldDef The container field definition
|
|
14561
|
+
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
14562
|
+
*/
|
|
14563
|
+
function containerFieldMapper(fieldDef) {
|
|
14564
|
+
const rootFormRegistry = inject(RootFormRegistryService);
|
|
14565
|
+
const className = buildClassName(fieldDef);
|
|
14566
|
+
return computed(() => {
|
|
14567
|
+
// Nullify wrapper-related props on the field passed to the container
|
|
14568
|
+
// component — DfFieldOutlet handles field-level wrappers at the outlet
|
|
14569
|
+
// level. Passing them here would cause double-wrapping since the
|
|
14570
|
+
// container's internal wrapper chain also resolves from `this.field()`.
|
|
14571
|
+
// We set wrappers to undefined (not stripped) so the object identity
|
|
14572
|
+
// stays close to the original fieldDef shape.
|
|
14573
|
+
const containerField = { ...fieldDef, wrappers: undefined, skipAutoWrappers: undefined, skipDefaultWrappers: undefined };
|
|
14574
|
+
const inputs = {
|
|
14575
|
+
key: fieldDef.key,
|
|
14576
|
+
field: containerField,
|
|
14577
|
+
...(className !== undefined && { className }),
|
|
14578
|
+
};
|
|
14579
|
+
applyHiddenLogic(inputs, fieldDef, rootFormRegistry);
|
|
14580
|
+
return inputs;
|
|
14581
|
+
});
|
|
14582
|
+
}
|
|
14583
|
+
|
|
13777
14584
|
/**
|
|
13778
14585
|
* Built-in field types provided by the dynamic form library.
|
|
13779
14586
|
*
|
|
@@ -13805,6 +14612,14 @@ function textFieldMapper(fieldDef) {
|
|
|
13805
14612
|
* { type: 'page', key: 'step1', fields: [...] }
|
|
13806
14613
|
* ```
|
|
13807
14614
|
*/
|
|
14615
|
+
/**
|
|
14616
|
+
* Base field type definitions for fields that don't use the `field` input.
|
|
14617
|
+
* Used by display-only fields like `text` that render immediately without
|
|
14618
|
+
* waiting for form value integration.
|
|
14619
|
+
*/
|
|
14620
|
+
const DISPLAY_FIELD_TYPES_BASE = {
|
|
14621
|
+
renderReadyWhen: [],
|
|
14622
|
+
};
|
|
13808
14623
|
/**
|
|
13809
14624
|
* Built-in field types provided by the dynamic form library.
|
|
13810
14625
|
*
|
|
@@ -13815,7 +14630,10 @@ function textFieldMapper(fieldDef) {
|
|
|
13815
14630
|
const BUILT_IN_FIELDS = [
|
|
13816
14631
|
{
|
|
13817
14632
|
name: 'row',
|
|
13818
|
-
|
|
14633
|
+
// `row` is a virtual field type: it maps to ContainerFieldComponent, and
|
|
14634
|
+
// the rowFieldMapper injects a `{ type: 'row' }` wrapper so the container
|
|
14635
|
+
// renders the flex/grid layout via RowWrapperComponent.
|
|
14636
|
+
loadComponent: () => Promise.resolve().then(function () { return containerField_component; }),
|
|
13819
14637
|
mapper: rowFieldMapper,
|
|
13820
14638
|
valueHandling: 'flatten',
|
|
13821
14639
|
},
|
|
@@ -13833,23 +14651,90 @@ const BUILT_IN_FIELDS = [
|
|
|
13833
14651
|
},
|
|
13834
14652
|
{
|
|
13835
14653
|
name: 'page',
|
|
13836
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-
|
|
14654
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-DV-EX9U0.mjs'),
|
|
13837
14655
|
mapper: pageFieldMapper,
|
|
13838
14656
|
valueHandling: 'flatten',
|
|
13839
14657
|
},
|
|
13840
14658
|
{
|
|
13841
14659
|
name: 'text',
|
|
13842
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-
|
|
14660
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-CHC8v87r.mjs'),
|
|
13843
14661
|
mapper: textFieldMapper,
|
|
13844
14662
|
valueHandling: 'exclude',
|
|
14663
|
+
...DISPLAY_FIELD_TYPES_BASE,
|
|
13845
14664
|
},
|
|
13846
14665
|
{
|
|
13847
14666
|
name: 'hidden',
|
|
13848
14667
|
// Componentless field - no loadComponent or mapper needed
|
|
13849
14668
|
valueHandling: 'include',
|
|
13850
14669
|
},
|
|
14670
|
+
{
|
|
14671
|
+
name: 'container',
|
|
14672
|
+
loadComponent: () => Promise.resolve().then(function () { return containerField_component; }),
|
|
14673
|
+
mapper: containerFieldMapper,
|
|
14674
|
+
valueHandling: 'flatten',
|
|
14675
|
+
},
|
|
14676
|
+
];
|
|
14677
|
+
const BUILT_IN_WRAPPERS = [
|
|
14678
|
+
{
|
|
14679
|
+
wrapperName: 'css',
|
|
14680
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-CGNGJoqv.mjs'),
|
|
14681
|
+
},
|
|
14682
|
+
{
|
|
14683
|
+
wrapperName: 'row',
|
|
14684
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-row-wrapper.component-BIvEUoia.mjs'),
|
|
14685
|
+
},
|
|
13851
14686
|
];
|
|
13852
14687
|
|
|
14688
|
+
/**
|
|
14689
|
+
* Bundle wrapper registrations into a single object that can be passed to
|
|
14690
|
+
* `provideDynamicForm(...)`.
|
|
14691
|
+
*
|
|
14692
|
+
* @example
|
|
14693
|
+
* ```typescript
|
|
14694
|
+
* const appWrappers = createWrappers(
|
|
14695
|
+
* {
|
|
14696
|
+
* wrapperName: 'section',
|
|
14697
|
+
* loadComponent: () => import('./section-wrapper'),
|
|
14698
|
+
* props: wrapperProps<SectionWrapper>(),
|
|
14699
|
+
* },
|
|
14700
|
+
* {
|
|
14701
|
+
* wrapperName: 'highlight',
|
|
14702
|
+
* loadComponent: () => import('./highlight-wrapper'),
|
|
14703
|
+
* types: ['input', 'select'],
|
|
14704
|
+
* props: wrapperProps<HighlightWrapper>(),
|
|
14705
|
+
* },
|
|
14706
|
+
* );
|
|
14707
|
+
*
|
|
14708
|
+
* declare module '@ng-forge/dynamic-forms' {
|
|
14709
|
+
* interface FieldRegistryWrappers extends InferWrapperRegistry<typeof appWrappers> {}
|
|
14710
|
+
* }
|
|
14711
|
+
*
|
|
14712
|
+
* bootstrapApplication(AppComponent, {
|
|
14713
|
+
* providers: [provideDynamicForm(appWrappers)],
|
|
14714
|
+
* });
|
|
14715
|
+
* ```
|
|
14716
|
+
*/
|
|
14717
|
+
function createWrappers(...registrations) {
|
|
14718
|
+
return {
|
|
14719
|
+
ɵkind: 'wrappers',
|
|
14720
|
+
ɵregistrations: registrations,
|
|
14721
|
+
ɵdefinitions: registrations.map(({ wrapperName, loadComponent, types }) => ({
|
|
14722
|
+
wrapperName,
|
|
14723
|
+
loadComponent,
|
|
14724
|
+
types,
|
|
14725
|
+
})),
|
|
14726
|
+
};
|
|
14727
|
+
}
|
|
14728
|
+
/** Type guard for a `WrappersBundle`. */
|
|
14729
|
+
function isWrappersBundle(value) {
|
|
14730
|
+
return (typeof value === 'object' &&
|
|
14731
|
+
value !== null &&
|
|
14732
|
+
'ɵkind' in value &&
|
|
14733
|
+
value.ɵkind === 'wrappers' &&
|
|
14734
|
+
'ɵdefinitions' in value &&
|
|
14735
|
+
Array.isArray(value.ɵdefinitions));
|
|
14736
|
+
}
|
|
14737
|
+
|
|
13853
14738
|
/**
|
|
13854
14739
|
* Provider function to configure the dynamic form system with field types and options.
|
|
13855
14740
|
*
|
|
@@ -13899,19 +14784,14 @@ const BUILT_IN_FIELDS = [
|
|
|
13899
14784
|
* @public
|
|
13900
14785
|
*/
|
|
13901
14786
|
function provideDynamicForm(...items) {
|
|
13902
|
-
// Separate field types
|
|
13903
|
-
const fieldTypes = items.filter((item) => !isDynamicFormFeature(item));
|
|
14787
|
+
// Separate field types, wrapper types, wrapper bundles, and features
|
|
14788
|
+
const fieldTypes = items.filter((item) => !isDynamicFormFeature(item) && !isWrapperTypeDefinition(item) && !isWrappersBundle(item));
|
|
14789
|
+
const wrapperTypes = items.filter(isWrapperTypeDefinition);
|
|
14790
|
+
const wrapperBundles = items.filter(isWrappersBundle);
|
|
13904
14791
|
const features = items.filter(isDynamicFormFeature);
|
|
13905
14792
|
const fields = [...BUILT_IN_FIELDS, ...fieldTypes];
|
|
13906
|
-
|
|
13907
|
-
|
|
13908
|
-
fieldTypes.forEach((fieldTypeArray) => {
|
|
13909
|
-
const fieldTypeWithConfig = fieldTypeArray;
|
|
13910
|
-
if (fieldTypeWithConfig.__configProviders) {
|
|
13911
|
-
configProviders.push(...fieldTypeWithConfig.__configProviders);
|
|
13912
|
-
}
|
|
13913
|
-
});
|
|
13914
|
-
// Extract providers from features
|
|
14793
|
+
const wrappers = [...BUILT_IN_WRAPPERS, ...wrapperTypes, ...wrapperBundles.flatMap((bundle) => bundle.ɵdefinitions)];
|
|
14794
|
+
// Extract providers from features (includes config features like material-config, bootstrap-config, etc.)
|
|
13915
14795
|
const featureProviders = [];
|
|
13916
14796
|
const hasLoggerFeature = features.some((feature) => feature.ɵkind === 'logger');
|
|
13917
14797
|
features.forEach((feature) => {
|
|
@@ -13938,7 +14818,41 @@ function provideDynamicForm(...items) {
|
|
|
13938
14818
|
return registry;
|
|
13939
14819
|
},
|
|
13940
14820
|
},
|
|
13941
|
-
|
|
14821
|
+
// Always provide default Wrapper classes
|
|
14822
|
+
{
|
|
14823
|
+
provide: WRAPPER_REGISTRY,
|
|
14824
|
+
useFactory: () => {
|
|
14825
|
+
const logger = inject(DynamicFormLogger);
|
|
14826
|
+
const registry = new Map();
|
|
14827
|
+
// Add custom wrapper types
|
|
14828
|
+
wrappers.forEach((wrapperType) => {
|
|
14829
|
+
if (registry.has(wrapperType.wrapperName)) {
|
|
14830
|
+
logger.warn(`Wrapper type "${wrapperType.wrapperName}" is already registered. Overwriting.`);
|
|
14831
|
+
}
|
|
14832
|
+
registry.set(wrapperType.wrapperName, wrapperType);
|
|
14833
|
+
});
|
|
14834
|
+
return registry;
|
|
14835
|
+
},
|
|
14836
|
+
},
|
|
14837
|
+
// Pre-computed reverse index for auto-association lookup — built once so
|
|
14838
|
+
// resolveWrappers is O(1) per field render instead of scanning every
|
|
14839
|
+
// registered wrapper.
|
|
14840
|
+
{
|
|
14841
|
+
provide: WRAPPER_AUTO_ASSOCIATIONS,
|
|
14842
|
+
useFactory: () => {
|
|
14843
|
+
const autoMap = new Map();
|
|
14844
|
+
for (const wrapperType of wrappers) {
|
|
14845
|
+
if (!wrapperType.types)
|
|
14846
|
+
continue;
|
|
14847
|
+
for (const fieldType of wrapperType.types) {
|
|
14848
|
+
const existing = autoMap.get(fieldType) ?? [];
|
|
14849
|
+
existing.push({ type: wrapperType.wrapperName });
|
|
14850
|
+
autoMap.set(fieldType, existing);
|
|
14851
|
+
}
|
|
14852
|
+
}
|
|
14853
|
+
return autoMap;
|
|
14854
|
+
},
|
|
14855
|
+
},
|
|
13942
14856
|
...featureProviders,
|
|
13943
14857
|
]);
|
|
13944
14858
|
}
|
|
@@ -14073,8 +14987,38 @@ function withValueExclusionDefaults(config) {
|
|
|
14073
14987
|
return createFeature('value-exclusion', [{ provide: VALUE_EXCLUSION_DEFAULTS, useValue: resolved }]);
|
|
14074
14988
|
}
|
|
14075
14989
|
|
|
14990
|
+
/**
|
|
14991
|
+
* Zero-cost type carrier used in wrapper registrations.
|
|
14992
|
+
*
|
|
14993
|
+
* Returns `undefined` at runtime but is typed as `T`, so TypeScript can
|
|
14994
|
+
* thread a wrapper's config type through `createWrappers(...)` into the
|
|
14995
|
+
* `InferWrapperRegistry<typeof ...>` utility without requiring users to
|
|
14996
|
+
* hand-write the augmentation shape.
|
|
14997
|
+
*
|
|
14998
|
+
* @example
|
|
14999
|
+
* ```typescript
|
|
15000
|
+
* const wrappers = createWrappers(
|
|
15001
|
+
* {
|
|
15002
|
+
* wrapperName: 'section',
|
|
15003
|
+
* loadComponent: () => import('./section-wrapper'),
|
|
15004
|
+
* props: wrapperProps<SectionWrapper>(),
|
|
15005
|
+
* },
|
|
15006
|
+
* );
|
|
15007
|
+
*
|
|
15008
|
+
* declare module '@ng-forge/dynamic-forms' {
|
|
15009
|
+
* interface FieldRegistryWrappers extends InferWrapperRegistry<typeof wrappers> {}
|
|
15010
|
+
* }
|
|
15011
|
+
* ```
|
|
15012
|
+
*/
|
|
15013
|
+
function wrapperProps() {
|
|
15014
|
+
return undefined;
|
|
15015
|
+
}
|
|
15016
|
+
|
|
14076
15017
|
function isValueField(field) {
|
|
14077
|
-
|
|
15018
|
+
// `nullable: true` without an explicit `value` is still a value-bearing declaration:
|
|
15019
|
+
// the field resolves to `null` at runtime. Treating it as a value field keeps
|
|
15020
|
+
// downstream narrowing consistent with getFieldDefaultValue's behavior.
|
|
15021
|
+
return 'value' in field || 'nullable' in field;
|
|
14078
15022
|
}
|
|
14079
15023
|
|
|
14080
15024
|
function isCheckedField(field) {
|
|
@@ -14112,10 +15056,13 @@ function isCheckedField(field) {
|
|
|
14112
15056
|
* eventBus.dispatch(arrayEvent('contacts').pop()); // Remove last
|
|
14113
15057
|
* eventBus.dispatch(arrayEvent('contacts').shift()); // Remove first
|
|
14114
15058
|
* eventBus.dispatch(arrayEvent('contacts').removeAt(2)); // Remove at index
|
|
15059
|
+
*
|
|
15060
|
+
* // Reordering items (no template needed, preserves item identity)
|
|
15061
|
+
* eventBus.dispatch(arrayEvent('contacts').move(0, 2)); // Move first to third
|
|
14115
15062
|
* ```
|
|
14116
15063
|
*
|
|
14117
15064
|
* @param arrayKey - The key of the array field to operate on
|
|
14118
|
-
* @returns An object with methods for all
|
|
15065
|
+
* @returns An object with methods for all 7 array operations
|
|
14119
15066
|
*/
|
|
14120
15067
|
function arrayEvent(arrayKey) {
|
|
14121
15068
|
return {
|
|
@@ -14226,6 +15173,22 @@ function arrayEvent(arrayKey) {
|
|
|
14226
15173
|
* ```
|
|
14227
15174
|
*/
|
|
14228
15175
|
removeAt: (index) => new RemoveAtIndexEvent(arrayKey, index),
|
|
15176
|
+
/**
|
|
15177
|
+
* Move an existing item from one position to another within the array.
|
|
15178
|
+
* This is an atomic reorder — the item is NOT destroyed and recreated.
|
|
15179
|
+
* The resolved component, form value, and stored template are preserved.
|
|
15180
|
+
*
|
|
15181
|
+
* @param from - The current index of the item to move
|
|
15182
|
+
* @param to - The target index to move the item to
|
|
15183
|
+
* @returns A MoveArrayItemEvent to dispatch
|
|
15184
|
+
*
|
|
15185
|
+
* @example
|
|
15186
|
+
* ```typescript
|
|
15187
|
+
* // Move item from index 0 to index 2
|
|
15188
|
+
* eventBus.dispatch(arrayEvent('contacts').move(0, 2));
|
|
15189
|
+
* ```
|
|
15190
|
+
*/
|
|
15191
|
+
move: (from, to) => new MoveArrayItemEvent(arrayKey, from, to),
|
|
14229
15192
|
};
|
|
14230
15193
|
}
|
|
14231
15194
|
|
|
@@ -14341,11 +15304,11 @@ function formConfig(config) {
|
|
|
14341
15304
|
/**
|
|
14342
15305
|
* Container field types that do NOT support labels
|
|
14343
15306
|
*/
|
|
14344
|
-
const CONTAINER_TYPES = ['group', 'row', 'array'];
|
|
15307
|
+
const CONTAINER_TYPES = ['group', 'row', 'array', 'container'];
|
|
14345
15308
|
/**
|
|
14346
15309
|
* Container and page field types that only support 'hidden' logic
|
|
14347
15310
|
*/
|
|
14348
|
-
const HIDDEN_ONLY_LOGIC_TYPES = ['group', 'row', 'array', 'page'];
|
|
15311
|
+
const HIDDEN_ONLY_LOGIC_TYPES = ['group', 'row', 'array', 'container', 'page'];
|
|
14349
15312
|
/**
|
|
14350
15313
|
* Field types that support options at the field level
|
|
14351
15314
|
*/
|
|
@@ -14605,10 +15568,10 @@ class DynamicTextPipe {
|
|
|
14605
15568
|
}
|
|
14606
15569
|
return of(value || '');
|
|
14607
15570
|
}
|
|
14608
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
14609
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.
|
|
15571
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DynamicTextPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
15572
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.8", ngImport: i0, type: DynamicTextPipe, isStandalone: true, name: "dynamicText" });
|
|
14610
15573
|
}
|
|
14611
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
15574
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DynamicTextPipe, decorators: [{
|
|
14612
15575
|
type: Pipe,
|
|
14613
15576
|
args: [{
|
|
14614
15577
|
name: 'dynamicText',
|
|
@@ -14658,111 +15621,30 @@ function computeContainerHostClasses(containerType, className) {
|
|
|
14658
15621
|
* When resolved fields become non-empty, emits a ComponentInitializedEvent
|
|
14659
15622
|
* via afterNextRender to signal the container is ready.
|
|
14660
15623
|
*
|
|
15624
|
+
* `componentType` may be a static value or a lazy getter — the latter lets
|
|
15625
|
+
* virtual field types (e.g. `row`, which renders via ContainerFieldComponent)
|
|
15626
|
+
* preserve their original type on the emitted event instead of collapsing to
|
|
15627
|
+
* the host component's type.
|
|
15628
|
+
*
|
|
14661
15629
|
* @param resolvedFields - Signal of resolved fields
|
|
14662
15630
|
* @param eventBus - EventBus for dispatching initialization events
|
|
14663
|
-
* @param componentType -
|
|
15631
|
+
* @param componentType - Static type, or a getter evaluated at emission time
|
|
14664
15632
|
* @param fieldKeyFn - Function returning the field's key
|
|
14665
15633
|
* @param injector - Injector for afterNextRender scheduling
|
|
14666
15634
|
*/
|
|
14667
15635
|
function setupContainerInitEffect(resolvedFields, eventBus, componentType, fieldKeyFn, injector) {
|
|
14668
|
-
|
|
14669
|
-
|
|
14670
|
-
|
|
15636
|
+
const allReady = computed(() => {
|
|
15637
|
+
const fields = resolvedFields();
|
|
15638
|
+
return fields.length > 0 && fields.every((field) => field.renderReady());
|
|
15639
|
+
}, ...(ngDevMode ? [{ debugName: "allReady" }] : /* istanbul ignore next */ []));
|
|
15640
|
+
explicitEffect([allReady], ([ready]) => {
|
|
15641
|
+
if (ready) {
|
|
15642
|
+
const type = typeof componentType === 'function' ? componentType() : componentType;
|
|
15643
|
+
emitComponentInitialized(eventBus, type, fieldKeyFn(), injector);
|
|
14671
15644
|
}
|
|
14672
15645
|
});
|
|
14673
15646
|
}
|
|
14674
15647
|
|
|
14675
|
-
/**
|
|
14676
|
-
* Layout container for horizontal field arrangement.
|
|
14677
|
-
*
|
|
14678
|
-
* Does not create a new form context - fields share the parent's context.
|
|
14679
|
-
* Field values are flattened into the parent form (no nesting under row key).
|
|
14680
|
-
* Purely a visual/layout container with no impact on form structure.
|
|
14681
|
-
*/
|
|
14682
|
-
class RowFieldComponent {
|
|
14683
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14684
|
-
// Dependencies
|
|
14685
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14686
|
-
destroyRef = inject(DestroyRef);
|
|
14687
|
-
fieldRegistry = injectFieldRegistry();
|
|
14688
|
-
injector = inject(Injector);
|
|
14689
|
-
environmentInjector = inject(EnvironmentInjector);
|
|
14690
|
-
eventBus = inject(EventBus);
|
|
14691
|
-
logger = inject(DynamicFormLogger);
|
|
14692
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14693
|
-
// Inputs
|
|
14694
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14695
|
-
field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
|
|
14696
|
-
key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
|
|
14697
|
-
className = input(...(ngDevMode ? [undefined, { debugName: "className" }] : /* istanbul ignore next */ []));
|
|
14698
|
-
hidden = input(false, ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
|
|
14699
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14700
|
-
// Computed Signals
|
|
14701
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14702
|
-
hostClasses = computed(() => computeContainerHostClasses('row', this.className()), ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
|
|
14703
|
-
disabled = computed(() => this.field().disabled || false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
14704
|
-
rawFieldRegistry = computed(() => this.fieldRegistry.raw, ...(ngDevMode ? [{ debugName: "rawFieldRegistry" }] : /* istanbul ignore next */ []));
|
|
14705
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14706
|
-
// Field Resolution
|
|
14707
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14708
|
-
fieldsSource = computed(() => (this.field().fields || []), ...(ngDevMode ? [{ debugName: "fieldsSource" }] : /* istanbul ignore next */ []));
|
|
14709
|
-
resolvedFields = derivedFromDeferred(this.fieldsSource, createFieldResolutionPipe(() => ({
|
|
14710
|
-
loadTypeComponent: (type) => this.fieldRegistry.loadTypeComponent(type),
|
|
14711
|
-
registry: this.rawFieldRegistry(),
|
|
14712
|
-
injector: this.injector,
|
|
14713
|
-
destroyRef: this.destroyRef,
|
|
14714
|
-
onError: (fieldDef, error) => {
|
|
14715
|
-
const fieldKey = fieldDef.key || '<no key>';
|
|
14716
|
-
const rowKey = this.field().key || '<no key>';
|
|
14717
|
-
this.logger.error(`Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +
|
|
14718
|
-
`within row '${rowKey}'. Ensure the field type is registered in your field registry.`, error);
|
|
14719
|
-
},
|
|
14720
|
-
})), { initialValue: [], injector: this.injector });
|
|
14721
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14722
|
-
// Constructor
|
|
14723
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14724
|
-
constructor() {
|
|
14725
|
-
this.setupEffects();
|
|
14726
|
-
}
|
|
14727
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14728
|
-
// Private Methods
|
|
14729
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
14730
|
-
setupEffects() {
|
|
14731
|
-
setupContainerInitEffect(this.resolvedFields, this.eventBus, 'row', () => this.field().key, this.injector);
|
|
14732
|
-
}
|
|
14733
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: RowFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14734
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: RowFieldComponent, isStandalone: true, selector: "div[row-field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "class.disabled": "disabled()", "class.df-container-hidden": "hidden()", "attr.aria-hidden": "hidden() || null", "id": "`${key()}`", "attr.data-testid": "key()" } }, ngImport: i0, template: `
|
|
14735
|
-
@for (field of resolvedFields(); track field.key) {
|
|
14736
|
-
<ng-container
|
|
14737
|
-
*ngComponentOutlet="field.component; injector: field.injector; environmentInjector: environmentInjector; inputs: field.inputs()"
|
|
14738
|
-
/>
|
|
14739
|
-
}
|
|
14740
|
-
`, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:flex;flex-wrap:wrap;gap:var(--df-grid-gap);align-items:flex-start;width:100%}:host ::ng-deep>*:not([class*=df-col-]){flex:0 0 auto}:host ::ng-deep>.df-col-1{flex:0 0 calc(8.3333333333% - var(--df-grid-gap) * .9166666667);max-width:calc(8.3333333333% - var(--df-grid-gap) * .9166666667)}:host ::ng-deep>.df-col-2{flex:0 0 calc(16.6666666667% - var(--df-grid-gap) * .8333333333);max-width:calc(16.6666666667% - var(--df-grid-gap) * .8333333333)}:host ::ng-deep>.df-col-3{flex:0 0 calc(25% - var(--df-grid-gap) * .75);max-width:calc(25% - var(--df-grid-gap) * .75)}:host ::ng-deep>.df-col-4{flex:0 0 calc(33.3333333333% - var(--df-grid-gap) * .6666666667);max-width:calc(33.3333333333% - var(--df-grid-gap) * .6666666667)}:host ::ng-deep>.df-col-5{flex:0 0 calc(41.6666666667% - var(--df-grid-gap) * .5833333333);max-width:calc(41.6666666667% - var(--df-grid-gap) * .5833333333)}:host ::ng-deep>.df-col-6{flex:0 0 calc(50% - var(--df-grid-gap) * .5);max-width:calc(50% - var(--df-grid-gap) * .5)}:host ::ng-deep>.df-col-7{flex:0 0 calc(58.3333333333% - var(--df-grid-gap) * .4166666667);max-width:calc(58.3333333333% - var(--df-grid-gap) * .4166666667)}:host ::ng-deep>.df-col-8{flex:0 0 calc(66.6666666667% - var(--df-grid-gap) * .3333333333);max-width:calc(66.6666666667% - var(--df-grid-gap) * .3333333333)}:host ::ng-deep>.df-col-9{flex:0 0 calc(75% - var(--df-grid-gap) * .25);max-width:calc(75% - var(--df-grid-gap) * .25)}:host ::ng-deep>.df-col-10{flex:0 0 calc(83.3333333333% - var(--df-grid-gap) * .1666666667);max-width:calc(83.3333333333% - var(--df-grid-gap) * .1666666667)}:host ::ng-deep>.df-col-11{flex:0 0 calc(91.6666666667% - var(--df-grid-gap) * .0833333333);max-width:calc(91.6666666667% - var(--df-grid-gap) * .0833333333)}:host ::ng-deep>.df-col-12{flex:0 0 calc(100% - var(--df-grid-gap) * 0);max-width:calc(100% - var(--df-grid-gap) * 0)}:host.df-gap-custom{--df-grid-gap: var(--row-custom-gap, 1rem)}@media(max-width:576px){:host.df-row-mobile-stack ::ng-deep>*{flex:0 0 100%!important;max-width:100%!important}}@media(min-width:577px)and (max-width:768px){:host.df-row-tablet-2col ::ng-deep>*{flex:0 0 calc(50% - var(--df-grid-gap) / 2);max-width:calc(50% - var(--df-grid-gap) / 2)}}:host.df-container-hidden{display:none}@media(max-width:576px){:host ::ng-deep>*{flex:0 0 100%!important;max-width:100%!important}:host.df-row-mobile-keep-cols{flex-direction:row}:host.df-row-mobile-keep-cols ::ng-deep>*:not([class*=df-col-]){flex:0 0 auto!important;max-width:none!important}}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14741
|
-
}
|
|
14742
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: RowFieldComponent, decorators: [{
|
|
14743
|
-
type: Component,
|
|
14744
|
-
args: [{ selector: 'div[row-field]', imports: [NgComponentOutlet], template: `
|
|
14745
|
-
@for (field of resolvedFields(); track field.key) {
|
|
14746
|
-
<ng-container
|
|
14747
|
-
*ngComponentOutlet="field.component; injector: field.injector; environmentInjector: environmentInjector; inputs: field.inputs()"
|
|
14748
|
-
/>
|
|
14749
|
-
}
|
|
14750
|
-
`, host: {
|
|
14751
|
-
'[class]': 'hostClasses()',
|
|
14752
|
-
'[class.disabled]': 'disabled()',
|
|
14753
|
-
'[class.df-container-hidden]': 'hidden()',
|
|
14754
|
-
'[attr.aria-hidden]': 'hidden() || null',
|
|
14755
|
-
'[id]': '`${key()}`',
|
|
14756
|
-
'[attr.data-testid]': 'key()',
|
|
14757
|
-
}, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:flex;flex-wrap:wrap;gap:var(--df-grid-gap);align-items:flex-start;width:100%}:host ::ng-deep>*:not([class*=df-col-]){flex:0 0 auto}:host ::ng-deep>.df-col-1{flex:0 0 calc(8.3333333333% - var(--df-grid-gap) * .9166666667);max-width:calc(8.3333333333% - var(--df-grid-gap) * .9166666667)}:host ::ng-deep>.df-col-2{flex:0 0 calc(16.6666666667% - var(--df-grid-gap) * .8333333333);max-width:calc(16.6666666667% - var(--df-grid-gap) * .8333333333)}:host ::ng-deep>.df-col-3{flex:0 0 calc(25% - var(--df-grid-gap) * .75);max-width:calc(25% - var(--df-grid-gap) * .75)}:host ::ng-deep>.df-col-4{flex:0 0 calc(33.3333333333% - var(--df-grid-gap) * .6666666667);max-width:calc(33.3333333333% - var(--df-grid-gap) * .6666666667)}:host ::ng-deep>.df-col-5{flex:0 0 calc(41.6666666667% - var(--df-grid-gap) * .5833333333);max-width:calc(41.6666666667% - var(--df-grid-gap) * .5833333333)}:host ::ng-deep>.df-col-6{flex:0 0 calc(50% - var(--df-grid-gap) * .5);max-width:calc(50% - var(--df-grid-gap) * .5)}:host ::ng-deep>.df-col-7{flex:0 0 calc(58.3333333333% - var(--df-grid-gap) * .4166666667);max-width:calc(58.3333333333% - var(--df-grid-gap) * .4166666667)}:host ::ng-deep>.df-col-8{flex:0 0 calc(66.6666666667% - var(--df-grid-gap) * .3333333333);max-width:calc(66.6666666667% - var(--df-grid-gap) * .3333333333)}:host ::ng-deep>.df-col-9{flex:0 0 calc(75% - var(--df-grid-gap) * .25);max-width:calc(75% - var(--df-grid-gap) * .25)}:host ::ng-deep>.df-col-10{flex:0 0 calc(83.3333333333% - var(--df-grid-gap) * .1666666667);max-width:calc(83.3333333333% - var(--df-grid-gap) * .1666666667)}:host ::ng-deep>.df-col-11{flex:0 0 calc(91.6666666667% - var(--df-grid-gap) * .0833333333);max-width:calc(91.6666666667% - var(--df-grid-gap) * .0833333333)}:host ::ng-deep>.df-col-12{flex:0 0 calc(100% - var(--df-grid-gap) * 0);max-width:calc(100% - var(--df-grid-gap) * 0)}:host.df-gap-custom{--df-grid-gap: var(--row-custom-gap, 1rem)}@media(max-width:576px){:host.df-row-mobile-stack ::ng-deep>*{flex:0 0 100%!important;max-width:100%!important}}@media(min-width:577px)and (max-width:768px){:host.df-row-tablet-2col ::ng-deep>*{flex:0 0 calc(50% - var(--df-grid-gap) / 2);max-width:calc(50% - var(--df-grid-gap) / 2)}}:host.df-container-hidden{display:none}@media(max-width:576px){:host ::ng-deep>*{flex:0 0 100%!important;max-width:100%!important}:host.df-row-mobile-keep-cols{flex-direction:row}:host.df-row-mobile-keep-cols ::ng-deep>*:not([class*=df-col-]){flex:0 0 auto!important;max-width:none!important}}\n"] }]
|
|
14758
|
-
}], 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 }] }] } });
|
|
14759
|
-
|
|
14760
|
-
var rowField_component = /*#__PURE__*/Object.freeze({
|
|
14761
|
-
__proto__: null,
|
|
14762
|
-
RowFieldComponent: RowFieldComponent,
|
|
14763
|
-
default: RowFieldComponent
|
|
14764
|
-
});
|
|
14765
|
-
|
|
14766
15648
|
/**
|
|
14767
15649
|
* Container component for rendering nested form groups.
|
|
14768
15650
|
*
|
|
@@ -14915,22 +15797,18 @@ class GroupFieldComponent {
|
|
|
14915
15797
|
}
|
|
14916
15798
|
}, { defer: true });
|
|
14917
15799
|
}
|
|
14918
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
14919
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
15800
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: GroupFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15801
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: GroupFieldComponent, isStandalone: true, selector: "fieldset[group-field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { validityChange: "validityChange", dirtyChange: "dirtyChange", submitted: "submitted" }, host: { attributes: { "role": "group" }, properties: { "class": "hostClasses()", "class.disabled": "disabled()", "class.df-container-hidden": "hidden()", "attr.aria-hidden": "hidden() || null", "id": "`${key()}`", "attr.data-testid": "key()" } }, ngImport: i0, template: `
|
|
14920
15802
|
@for (field of resolvedFields(); track field.key) {
|
|
14921
|
-
<ng-container
|
|
14922
|
-
*ngComponentOutlet="field.component; injector: field.injector; environmentInjector: environmentInjector; inputs: field.inputs()"
|
|
14923
|
-
/>
|
|
15803
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
14924
15804
|
}
|
|
14925
|
-
`, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{border:none;margin:0;padding:0;min-width:0;display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}:host>*{grid-column:1/-1}:host.df-container-hidden{display:none}\n"], dependencies: [{ kind: "directive", type:
|
|
15805
|
+
`, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{border:none;margin:0;padding:0;min-width:0;display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}:host>*{grid-column:1/-1}:host.df-container-hidden{display:none}\n"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14926
15806
|
}
|
|
14927
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
15807
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: GroupFieldComponent, decorators: [{
|
|
14928
15808
|
type: Component,
|
|
14929
|
-
args: [{ selector: 'fieldset[group-field]', imports: [
|
|
15809
|
+
args: [{ selector: 'fieldset[group-field]', imports: [DfFieldOutlet], template: `
|
|
14930
15810
|
@for (field of resolvedFields(); track field.key) {
|
|
14931
|
-
<ng-container
|
|
14932
|
-
*ngComponentOutlet="field.component; injector: field.injector; environmentInjector: environmentInjector; inputs: field.inputs()"
|
|
14933
|
-
/>
|
|
15811
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
14934
15812
|
}
|
|
14935
15813
|
`, host: {
|
|
14936
15814
|
'[class]': 'hostClasses()',
|
|
@@ -15140,7 +16018,7 @@ function resolveArrayItem(options) {
|
|
|
15140
16018
|
return positionMap.get(itemId) ?? index;
|
|
15141
16019
|
}, ...(ngDevMode ? [{ debugName: "indexSignal" }] : /* istanbul ignore next */ []));
|
|
15142
16020
|
// Resolve all templates in parallel
|
|
15143
|
-
const fieldObservables = templates.map((template) => from(loadTypeComponent(template.type)).pipe(map
|
|
16021
|
+
const fieldObservables = templates.map((template) => from(loadTypeComponent(template.type)).pipe(map((component) => {
|
|
15144
16022
|
if (destroyRef.destroyed) {
|
|
15145
16023
|
return undefined;
|
|
15146
16024
|
}
|
|
@@ -15162,9 +16040,12 @@ function resolveArrayItem(options) {
|
|
|
15162
16040
|
return undefined;
|
|
15163
16041
|
}
|
|
15164
16042
|
return {
|
|
16043
|
+
key: template.key,
|
|
16044
|
+
fieldDef: template,
|
|
15165
16045
|
component,
|
|
15166
16046
|
injector,
|
|
15167
16047
|
inputs,
|
|
16048
|
+
renderReady: createRenderReadySignal(inputs, registry.get(template.type)),
|
|
15168
16049
|
};
|
|
15169
16050
|
}), catchError((error) => {
|
|
15170
16051
|
if (!destroyRef.destroyed) {
|
|
@@ -15174,7 +16055,7 @@ function resolveArrayItem(options) {
|
|
|
15174
16055
|
}
|
|
15175
16056
|
return of(undefined);
|
|
15176
16057
|
})));
|
|
15177
|
-
return forkJoin(fieldObservables).pipe(map
|
|
16058
|
+
return forkJoin(fieldObservables).pipe(map((fields) => {
|
|
15178
16059
|
const validFields = fields.filter((f) => f !== undefined);
|
|
15179
16060
|
// If no fields resolved, return undefined
|
|
15180
16061
|
if (validFields.length === 0) {
|
|
@@ -15194,6 +16075,7 @@ const ARRAY_EVENT_TYPES = [
|
|
|
15194
16075
|
'append-array-item',
|
|
15195
16076
|
'prepend-array-item',
|
|
15196
16077
|
'insert-array-item',
|
|
16078
|
+
'move-array-item',
|
|
15197
16079
|
'pop-array-item',
|
|
15198
16080
|
'shift-array-item',
|
|
15199
16081
|
'remove-at-index',
|
|
@@ -15210,6 +16092,8 @@ function toArrayAction(event) {
|
|
|
15210
16092
|
return { action: 'add', template: event.template, index: 0 };
|
|
15211
16093
|
case 'insert-array-item':
|
|
15212
16094
|
return { action: 'add', template: event.template, index: event.index };
|
|
16095
|
+
case 'move-array-item':
|
|
16096
|
+
return { action: 'move', fromIndex: event.fromIndex, toIndex: event.toIndex };
|
|
15213
16097
|
case 'pop-array-item':
|
|
15214
16098
|
return { action: 'remove' };
|
|
15215
16099
|
case 'shift-array-item':
|
|
@@ -15239,13 +16123,13 @@ function toArrayAction(event) {
|
|
|
15239
16123
|
* ```
|
|
15240
16124
|
*/
|
|
15241
16125
|
function observeArrayActions(eventBus, arrayKey) {
|
|
15242
|
-
return eventBus.on(ARRAY_EVENT_TYPES).pipe(filter((event) => event.arrayKey === arrayKey()), map
|
|
16126
|
+
return eventBus.on(ARRAY_EVENT_TYPES).pipe(filter((event) => event.arrayKey === arrayKey()), map(toArrayAction));
|
|
15243
16127
|
}
|
|
15244
16128
|
|
|
15245
16129
|
/**
|
|
15246
16130
|
* Container component for rendering dynamic arrays of fields.
|
|
15247
16131
|
*
|
|
15248
|
-
* Supports add/remove operations via the arrayEvent() builder API.
|
|
16132
|
+
* Supports add/remove/move operations via the arrayEvent() builder API.
|
|
15249
16133
|
* Uses differential updates to optimize rendering - only recreates items when necessary.
|
|
15250
16134
|
* Each item gets a scoped injector with ARRAY_CONTEXT for position-aware operations.
|
|
15251
16135
|
* Supports multiple sibling fields per array item (e.g., name + email without a wrapper).
|
|
@@ -15315,6 +16199,38 @@ class ArrayFieldComponent {
|
|
|
15315
16199
|
}
|
|
15316
16200
|
return undefined;
|
|
15317
16201
|
}, ...(ngDevMode ? [{ debugName: "primitiveFieldKey" }] : /* istanbul ignore next */ []));
|
|
16202
|
+
/**
|
|
16203
|
+
* Normalized item templates WITHOUT auto-remove button appended.
|
|
16204
|
+
* Each element is normalized to an array: single FieldDef → [FieldDef], array stays as-is.
|
|
16205
|
+
*
|
|
16206
|
+
* Used by moveItem() to stash raw templates into the templateRegistry, preserving
|
|
16207
|
+
* the invariant that registry entries are pre-synthesis (withAutoRemove() adds the
|
|
16208
|
+
* button during resolution).
|
|
16209
|
+
*/
|
|
16210
|
+
rawItemTemplates = computed(() => {
|
|
16211
|
+
const arrayField = this.field();
|
|
16212
|
+
const definitions = arrayField.fields || [];
|
|
16213
|
+
return definitions.map((def) => {
|
|
16214
|
+
return (Array.isArray(def) ? def : [def]);
|
|
16215
|
+
});
|
|
16216
|
+
}, ...(ngDevMode ? [{ debugName: "rawItemTemplates" }] : /* istanbul ignore next */ []));
|
|
16217
|
+
/**
|
|
16218
|
+
* Resolves the effective fallback template for this array — used when the form
|
|
16219
|
+
* value contains an item with no registered template (i.e., items that were
|
|
16220
|
+
* neither added via event handlers nor covered by a positional entry in `fields`).
|
|
16221
|
+
*
|
|
16222
|
+
* Populated from `SimplifiedArrayField.template` via normalization metadata, so every
|
|
16223
|
+
* simplified array gets an automatic default. Returns undefined for full-API arrays;
|
|
16224
|
+
* `createResolveItemObservable` then falls back to warn-and-drop.
|
|
16225
|
+
*
|
|
16226
|
+
* Homogeneous arrays only — all fallback items receive the same template.
|
|
16227
|
+
*/
|
|
16228
|
+
fallbackTemplate = computed(() => {
|
|
16229
|
+
const raw = getNormalizedArrayMetadata(this.field())?.template;
|
|
16230
|
+
if (!raw)
|
|
16231
|
+
return undefined;
|
|
16232
|
+
return (Array.isArray(raw) ? [...raw] : [raw]);
|
|
16233
|
+
}, ...(ngDevMode ? [{ debugName: "fallbackTemplate" }] : /* istanbul ignore next */ []));
|
|
15318
16234
|
/**
|
|
15319
16235
|
* Gets the item templates (field definitions) for the array.
|
|
15320
16236
|
* Each element can be either:
|
|
@@ -15328,17 +16244,12 @@ class ArrayFieldComponent {
|
|
|
15328
16244
|
* Returns normalized templates where all items are arrays for consistent handling.
|
|
15329
16245
|
*/
|
|
15330
16246
|
itemTemplates = computed(() => {
|
|
15331
|
-
const
|
|
15332
|
-
const definitions = arrayField.fields || [];
|
|
16247
|
+
const raw = this.rawItemTemplates();
|
|
15333
16248
|
const removeButton = this.autoRemoveButton();
|
|
15334
|
-
|
|
15335
|
-
|
|
15336
|
-
return
|
|
15337
|
-
|
|
15338
|
-
if (removeButton) {
|
|
15339
|
-
return [...normalized, removeButton];
|
|
15340
|
-
}
|
|
15341
|
-
return normalized;
|
|
16249
|
+
if (!removeButton)
|
|
16250
|
+
return raw;
|
|
16251
|
+
return raw.map((normalized) => {
|
|
16252
|
+
return [...normalized, removeButton];
|
|
15342
16253
|
});
|
|
15343
16254
|
}, ...(ngDevMode ? [{ debugName: "itemTemplates" }] : /* istanbul ignore next */ []));
|
|
15344
16255
|
arrayFieldTrees = computed(() => {
|
|
@@ -15364,6 +16275,8 @@ class ArrayFieldComponent {
|
|
|
15364
16275
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
15365
16276
|
resolvedItemsSignal = signal([], ...(ngDevMode ? [{ debugName: "resolvedItemsSignal" }] : /* istanbul ignore next */ []));
|
|
15366
16277
|
updateVersion = signal(0, ...(ngDevMode ? [{ debugName: "updateVersion" }] : /* istanbul ignore next */ []));
|
|
16278
|
+
pendingInitializationCycle = signal(null, ...(ngDevMode ? [{ debugName: "pendingInitializationCycle" }] : /* istanbul ignore next */ []));
|
|
16279
|
+
settledInitializationCycle = signal(null, ...(ngDevMode ? [{ debugName: "settledInitializationCycle" }] : /* istanbul ignore next */ []));
|
|
15367
16280
|
/**
|
|
15368
16281
|
* Map of item IDs to their current positions. O(1) lookup vs O(n) indexOf().
|
|
15369
16282
|
* Used by child linkedSignals to reactively track their position in the array.
|
|
@@ -15374,6 +16287,7 @@ class ArrayFieldComponent {
|
|
|
15374
16287
|
}, ...(ngDevMode ? [{ debugName: "itemPositionMap" }] : /* istanbul ignore next */ []));
|
|
15375
16288
|
/** Read-only view of resolved items for template consumption. */
|
|
15376
16289
|
resolvedItems = computed(() => this.resolvedItemsSignal(), ...(ngDevMode ? [{ debugName: "resolvedItems" }] : /* istanbul ignore next */ []));
|
|
16290
|
+
allResolvedFieldsRenderReady = computed(() => this.resolvedItems().every((item) => item.fields.every((field) => field.renderReady())), ...(ngDevMode ? [{ debugName: "allResolvedFieldsRenderReady" }] : /* istanbul ignore next */ []));
|
|
15377
16291
|
/**
|
|
15378
16292
|
* Whether the array has reached its configured maxLength.
|
|
15379
16293
|
* Exposed as a public signal so add-button components can bind [disabled]="atMaxLength()".
|
|
@@ -15412,6 +16326,12 @@ class ArrayFieldComponent {
|
|
|
15412
16326
|
explicitEffect([this.arrayFieldTrees], ([fieldTrees]) => {
|
|
15413
16327
|
this.performDifferentialUpdate(fieldTrees);
|
|
15414
16328
|
});
|
|
16329
|
+
explicitEffect([this.pendingInitializationCycle, this.settledInitializationCycle, this.allResolvedFieldsRenderReady], ([pending, settled, allReady]) => {
|
|
16330
|
+
if (pending !== null && settled === pending && allReady) {
|
|
16331
|
+
emitComponentInitialized(this.eventBus, 'array', this.field().key, this.parentInjector);
|
|
16332
|
+
this.pendingInitializationCycle.set(null);
|
|
16333
|
+
}
|
|
16334
|
+
});
|
|
15415
16335
|
}
|
|
15416
16336
|
setupEventHandlers() {
|
|
15417
16337
|
observeArrayActions(this.eventBus, () => this.key())
|
|
@@ -15428,6 +16348,9 @@ class ArrayFieldComponent {
|
|
|
15428
16348
|
}
|
|
15429
16349
|
void this.handleAddFromEvent(action.template, action.index);
|
|
15430
16350
|
}
|
|
16351
|
+
else if (action.action === 'move') {
|
|
16352
|
+
this.moveItem(action.fromIndex, action.toIndex);
|
|
16353
|
+
}
|
|
15431
16354
|
else {
|
|
15432
16355
|
this.removeItem(action.index);
|
|
15433
16356
|
}
|
|
@@ -15442,9 +16365,13 @@ class ArrayFieldComponent {
|
|
|
15442
16365
|
* Supports both primitive (single FieldDef) and object (FieldDef[]) templates.
|
|
15443
16366
|
*/
|
|
15444
16367
|
async handleAddFromEvent(template, index) {
|
|
15445
|
-
// Normalize template to mutable array for consistent handling
|
|
16368
|
+
// Normalize template to mutable array for consistent handling.
|
|
16369
|
+
// A single FieldDef with valueHandling: 'flatten' (container, row) is an object item
|
|
16370
|
+
// whose children should be flattened — NOT a primitive item. Only true leaf fields
|
|
16371
|
+
// (input, checkbox, etc.) are primitive when passed as a single FieldDef.
|
|
15446
16372
|
const templates = Array.isArray(template) ? [...template] : [template];
|
|
15447
|
-
const
|
|
16373
|
+
const isSingleField = !Array.isArray(template);
|
|
16374
|
+
const isPrimitiveItem = isSingleField && getFieldValueHandling(templates[0].type, this.rawFieldRegistry()) !== 'flatten';
|
|
15448
16375
|
// Track primitive field key for full-API arrays that start empty.
|
|
15449
16376
|
// Simplified arrays already have this info from normalization metadata.
|
|
15450
16377
|
if (isPrimitiveItem && templates[0].key && !this.primitiveFieldKey()) {
|
|
@@ -15472,14 +16399,14 @@ class ArrayFieldComponent {
|
|
|
15472
16399
|
for (const templateField of templates) {
|
|
15473
16400
|
const rawValue = getFieldDefaultValue(templateField, this.rawFieldRegistry());
|
|
15474
16401
|
const valueHandling = getFieldValueHandling(templateField.type, this.rawFieldRegistry());
|
|
15475
|
-
const isContainer = templateField.type === 'group' || templateField.type === 'row';
|
|
16402
|
+
const isContainer = templateField.type === 'group' || templateField.type === 'row' || templateField.type === 'container';
|
|
15476
16403
|
if (isContainer) {
|
|
15477
16404
|
if (isGroupField(templateField)) {
|
|
15478
16405
|
// Groups wrap their fields under the group key
|
|
15479
16406
|
value = { ...value, [templateField.key]: rawValue };
|
|
15480
16407
|
}
|
|
15481
16408
|
else {
|
|
15482
|
-
// Rows flatten their fields directly
|
|
16409
|
+
// Rows and containers flatten their fields directly
|
|
15483
16410
|
value = { ...value, ...rawValue };
|
|
15484
16411
|
}
|
|
15485
16412
|
}
|
|
@@ -15544,26 +16471,31 @@ class ArrayFieldComponent {
|
|
|
15544
16471
|
// Clean up template registry when all items are removed
|
|
15545
16472
|
this.templateRegistry.clear();
|
|
15546
16473
|
this.resolvedItemsSignal.set([]);
|
|
16474
|
+
if (resolvedItems.length === 0 && this.pendingInitializationCycle() === null && this.settledInitializationCycle() === null) {
|
|
16475
|
+
this.pendingInitializationCycle.set(currentVersion);
|
|
16476
|
+
this.settledInitializationCycle.set(currentVersion);
|
|
16477
|
+
}
|
|
15547
16478
|
break;
|
|
15548
16479
|
case 'initial':
|
|
16480
|
+
this.pendingInitializationCycle.set(currentVersion);
|
|
16481
|
+
this.settledInitializationCycle.set(null);
|
|
15549
16482
|
void this.resolveAllItems(fieldTrees, currentVersion);
|
|
15550
16483
|
break;
|
|
15551
16484
|
case 'append':
|
|
15552
16485
|
void this.appendItems(fieldTrees, operation.startIndex, operation.endIndex, currentVersion);
|
|
15553
16486
|
break;
|
|
15554
16487
|
case 'recreate': {
|
|
15555
|
-
//
|
|
15556
|
-
//
|
|
15557
|
-
|
|
15558
|
-
const positionalTemplates = resolvedItems.map((item
|
|
15559
|
-
//
|
|
15560
|
-
|
|
15561
|
-
return undefined; // Will use itemTemplates[idx] during resolve
|
|
15562
|
-
}
|
|
15563
|
-
// For dynamically added items, look up their stored template
|
|
16488
|
+
// Capture templates by item ID so each item is recreated with its original template,
|
|
16489
|
+
// even after move operations that change positions. Registry entries (from dynamic adds
|
|
16490
|
+
// or moves) take priority over positional itemTemplates lookup.
|
|
16491
|
+
const positionalTemplates = resolvedItems.map((item) => {
|
|
16492
|
+
// Registry entries (from dynamic adds or moves) take priority;
|
|
16493
|
+
// unmoved initial items return undefined → falls through to itemTemplates[idx] during resolve.
|
|
15564
16494
|
return this.templateRegistry.get(item.id);
|
|
15565
16495
|
});
|
|
15566
16496
|
this.resolvedItemsSignal.set([]);
|
|
16497
|
+
this.pendingInitializationCycle.set(currentVersion);
|
|
16498
|
+
this.settledInitializationCycle.set(null);
|
|
15567
16499
|
void this.resolveAllItems(fieldTrees, currentVersion, positionalTemplates);
|
|
15568
16500
|
break;
|
|
15569
16501
|
}
|
|
@@ -15580,7 +16512,7 @@ class ArrayFieldComponent {
|
|
|
15580
16512
|
return of(undefined);
|
|
15581
16513
|
})));
|
|
15582
16514
|
try {
|
|
15583
|
-
const items = await firstValueFrom(forkJoin(safeItemObservables).pipe(map
|
|
16515
|
+
const items = await firstValueFrom(forkJoin(safeItemObservables).pipe(map((items) => items.filter((item) => item !== undefined))));
|
|
15584
16516
|
if (updateId === this.updateVersion()) {
|
|
15585
16517
|
// Update template registry with new item IDs for dynamically added items
|
|
15586
16518
|
if (positionalTemplates) {
|
|
@@ -15599,7 +16531,9 @@ class ArrayFieldComponent {
|
|
|
15599
16531
|
});
|
|
15600
16532
|
}
|
|
15601
16533
|
this.resolvedItemsSignal.set(items);
|
|
15602
|
-
|
|
16534
|
+
if (this.pendingInitializationCycle() === updateId) {
|
|
16535
|
+
this.settledInitializationCycle.set(updateId);
|
|
16536
|
+
}
|
|
15603
16537
|
}
|
|
15604
16538
|
}
|
|
15605
16539
|
catch (err) {
|
|
@@ -15620,7 +16554,7 @@ class ArrayFieldComponent {
|
|
|
15620
16554
|
}));
|
|
15621
16555
|
});
|
|
15622
16556
|
try {
|
|
15623
|
-
const newItems = await firstValueFrom(forkJoin(safeItemObservables).pipe(map
|
|
16557
|
+
const newItems = await firstValueFrom(forkJoin(safeItemObservables).pipe(map((items) => items.filter((item) => item !== undefined))));
|
|
15624
16558
|
if (updateId === this.updateVersion()) {
|
|
15625
16559
|
this.resolvedItemsSignal.update((current) => [...current, ...newItems]);
|
|
15626
16560
|
}
|
|
@@ -15635,7 +16569,12 @@ class ArrayFieldComponent {
|
|
|
15635
16569
|
* Template resolution order:
|
|
15636
16570
|
* 1. Use overrideTemplate if provided (from recreate with stored templates)
|
|
15637
16571
|
* 2. Use itemTemplates[index] if within defined templates range
|
|
15638
|
-
* 3.
|
|
16572
|
+
* 3. Use the simplified-array fallback template (from normalization metadata) for
|
|
16573
|
+
* untracked items present in the form value (e.g., external `value.set`, parent
|
|
16574
|
+
* two-way binding, initial values beyond what was declared via simplified `value`).
|
|
16575
|
+
* Registers the resolved item in templateRegistry so subsequent recreates use
|
|
16576
|
+
* Priority 1.
|
|
16577
|
+
* 4. Return undefined (item cannot be resolved without a template)
|
|
15639
16578
|
*/
|
|
15640
16579
|
createResolveItemObservable(index, overrideTemplate) {
|
|
15641
16580
|
const itemTemplates = this.itemTemplates();
|
|
@@ -15673,10 +16612,35 @@ class ArrayFieldComponent {
|
|
|
15673
16612
|
primitiveFieldKey: primitiveKey,
|
|
15674
16613
|
});
|
|
15675
16614
|
}
|
|
15676
|
-
//
|
|
15677
|
-
//
|
|
15678
|
-
|
|
15679
|
-
|
|
16615
|
+
// Priority 3: Use the metadata fallback template for untracked items in the form value.
|
|
16616
|
+
// Homogeneous only — every fallback item receives the same template regardless of value shape.
|
|
16617
|
+
const fallback = this.fallbackTemplate();
|
|
16618
|
+
if (fallback && fallback.length > 0) {
|
|
16619
|
+
return resolveArrayItem({
|
|
16620
|
+
index,
|
|
16621
|
+
templates: this.withAutoRemove(fallback),
|
|
16622
|
+
arrayField: this.field(),
|
|
16623
|
+
itemPositionMap: this.itemPositionMap,
|
|
16624
|
+
parentFieldSignalContext: this.parentFieldSignalContext,
|
|
16625
|
+
parentInjector: this.parentInjector,
|
|
16626
|
+
registry: this.rawFieldRegistry(),
|
|
16627
|
+
destroyRef: this.destroyRef,
|
|
16628
|
+
loadTypeComponent: (type) => this.fieldRegistry.loadTypeComponent(type),
|
|
16629
|
+
generateItemId: this.generateItemId,
|
|
16630
|
+
primitiveFieldKey: primitiveKey,
|
|
16631
|
+
}).pipe(tap((item) => {
|
|
16632
|
+
// Register the fallback template against the generated item id so subsequent
|
|
16633
|
+
// recreates hit Priority 1 instead of re-resolving via this branch.
|
|
16634
|
+
if (item)
|
|
16635
|
+
this.templateRegistry.set(item.id, fallback);
|
|
16636
|
+
}));
|
|
16637
|
+
}
|
|
16638
|
+
// Priority 4: no template available. Full-API arrays are positional by design —
|
|
16639
|
+
// `fields` declares one template per item, so values extending past `fields.length`
|
|
16640
|
+
// (e.g., from external `value.set`, parent two-way binding, or initial values on a
|
|
16641
|
+
// `fields: []` array) have no template to render. Use the simplified array API
|
|
16642
|
+
// (`template` + `value`) when you need homogeneous arrays with value-driven items.
|
|
16643
|
+
this.logger.warn(`No template found for array item at index ${index}. This likely occured for a Full-API array element that was created from the DynamicForm's value being set directly, which is currently not supported. Consider using the simplified array API.`);
|
|
15680
16644
|
return of(undefined);
|
|
15681
16645
|
}
|
|
15682
16646
|
/**
|
|
@@ -15701,6 +16665,61 @@ class ArrayFieldComponent {
|
|
|
15701
16665
|
this.autoRemoveCache.set(templates, cached);
|
|
15702
16666
|
return cached;
|
|
15703
16667
|
}
|
|
16668
|
+
/**
|
|
16669
|
+
* Handles move operations — reorders an existing item without destroying it.
|
|
16670
|
+
* Updates resolvedItems and form value atomically. Since the array length
|
|
16671
|
+
* doesn't change, `determineDifferentialOperation` returns 'none' and no
|
|
16672
|
+
* recreate is triggered. The `@for` loop tracks by `item.id`, so Angular
|
|
16673
|
+
* moves the DOM node instead of destroying/recreating. The `itemPositionMap`
|
|
16674
|
+
* computed auto-recomputes, propagating new indices to child linkedSignals.
|
|
16675
|
+
*/
|
|
16676
|
+
moveItem(fromIndex, toIndex) {
|
|
16677
|
+
const arrayKey = this.field().key;
|
|
16678
|
+
const parentForm = this.parentFieldSignalContext.form;
|
|
16679
|
+
const currentValue = parentForm().value();
|
|
16680
|
+
const currentArray = getArrayValue(currentValue, arrayKey);
|
|
16681
|
+
const length = currentArray.length;
|
|
16682
|
+
if (fromIndex < 0 || fromIndex >= length) {
|
|
16683
|
+
this.logger.warn(`moveArrayItem fromIndex ${fromIndex} is out of bounds for array '${arrayKey}' with length ${length}. Operation skipped.`);
|
|
16684
|
+
return;
|
|
16685
|
+
}
|
|
16686
|
+
if (toIndex < 0 || toIndex >= length) {
|
|
16687
|
+
this.logger.warn(`moveArrayItem toIndex ${toIndex} is out of bounds for array '${arrayKey}' with length ${length}. Operation skipped.`);
|
|
16688
|
+
return;
|
|
16689
|
+
}
|
|
16690
|
+
if (fromIndex === toIndex)
|
|
16691
|
+
return;
|
|
16692
|
+
// Register raw (pre-auto-remove) templates for all items whose position will change.
|
|
16693
|
+
// Initial items use itemTemplates[currentIndex] during recreate, but after
|
|
16694
|
+
// a move their position no longer matches their original template. Stashing
|
|
16695
|
+
// the template by item ID lets the recreate path resolve the correct one.
|
|
16696
|
+
// We use rawItemTemplates (without auto-remove button) because the recreate path
|
|
16697
|
+
// calls withAutoRemove() during resolution — storing the synthesized version would
|
|
16698
|
+
// cause duplicate remove buttons.
|
|
16699
|
+
const rawTemplates = this.rawItemTemplates();
|
|
16700
|
+
const currentItems = this.resolvedItemsSignal();
|
|
16701
|
+
const lo = Math.min(fromIndex, toIndex);
|
|
16702
|
+
const hi = Math.max(fromIndex, toIndex);
|
|
16703
|
+
for (let i = lo; i <= hi; i++) {
|
|
16704
|
+
const item = currentItems[i];
|
|
16705
|
+
if (item && !this.templateRegistry.has(item.id) && i < rawTemplates.length) {
|
|
16706
|
+
this.templateRegistry.set(item.id, rawTemplates[i]);
|
|
16707
|
+
}
|
|
16708
|
+
}
|
|
16709
|
+
// Reorder resolvedItems — splice preserves object identity (no destroy/recreate)
|
|
16710
|
+
this.resolvedItemsSignal.update((current) => {
|
|
16711
|
+
const newItems = [...current];
|
|
16712
|
+
const [moved] = newItems.splice(fromIndex, 1);
|
|
16713
|
+
newItems.splice(toIndex, 0, moved);
|
|
16714
|
+
return newItems;
|
|
16715
|
+
});
|
|
16716
|
+
// Reorder form value array the same way
|
|
16717
|
+
const newArray = [...currentArray];
|
|
16718
|
+
const [movedValue] = newArray.splice(fromIndex, 1);
|
|
16719
|
+
newArray.splice(toIndex, 0, movedValue);
|
|
16720
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16721
|
+
parentForm().value.set({ ...currentValue, [arrayKey]: newArray });
|
|
16722
|
+
}
|
|
15704
16723
|
/**
|
|
15705
16724
|
* Handles remove operations from events (pop, shift, removeAt).
|
|
15706
16725
|
* Updates resolvedItems FIRST, then form value - this ensures differential
|
|
@@ -15751,8 +16770,8 @@ class ArrayFieldComponent {
|
|
|
15751
16770
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15752
16771
|
parentForm().value.set({ ...currentValue, [arrayKey]: newArray });
|
|
15753
16772
|
}
|
|
15754
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
15755
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
16773
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
16774
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ArrayFieldComponent, isStandalone: true, selector: "array-field", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "class.df-container-hidden": "hidden()", "attr.aria-hidden": "hidden() || null", "id": "`${key()}`", "attr.data-testid": "key()" } }, providers: [
|
|
15756
16775
|
// Each array gets its own template registry to track templates used for dynamically added items
|
|
15757
16776
|
{ provide: ARRAY_TEMPLATE_REGISTRY, useFactory: () => new Map() },
|
|
15758
16777
|
// Each array gets its own ID generator for SSR hydration compatibility
|
|
@@ -15767,17 +16786,15 @@ class ArrayFieldComponent {
|
|
|
15767
16786
|
[attr.data-array-item-index]="i"
|
|
15768
16787
|
>
|
|
15769
16788
|
@for (field of item.fields; track $index) {
|
|
15770
|
-
<ng-container
|
|
15771
|
-
*ngComponentOutlet="field.component; injector: field.injector; environmentInjector: environmentInjector; inputs: field.inputs()"
|
|
15772
|
-
/>
|
|
16789
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
15773
16790
|
}
|
|
15774
16791
|
</div>
|
|
15775
16792
|
}
|
|
15776
|
-
`, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-array-item-gap, var(--df-grid-row-gap, .5rem));width:100%}:host>*{grid-column:1/-1}:host.df-container-hidden{display:none}.df-array-item{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}.df-array-item>*{grid-column:1/-1}\n"], dependencies: [{ kind: "directive", type:
|
|
16793
|
+
`, isInline: true, styles: [":host,.df-form{--df-grid-columns: 12;--df-grid-gap: .5rem;--df-grid-row-gap: .5rem;--df-breakpoint-sm: 576px;--df-breakpoint-md: 768px;--df-breakpoint-lg: 992px;--df-breakpoint-xl: 1200px;--df-grid-gap-sm: .5rem;--df-grid-gap-md: .5rem;--df-grid-gap-lg: .5rem;--df-grid-gap-xl: .5rem;--df-grid-row-gap-sm: .5rem;--df-grid-row-gap-md: .5rem;--df-grid-row-gap-lg: .5rem;--df-grid-row-gap-xl: .5rem;--df-array-item-gap: var(--df-grid-row-gap);--df-group-gap: var(--df-grid-gap);--df-group-padding: var(--df-grid-gap)}.df-form{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap);width:100%}.df-form>*{grid-column:1/-1}.df-row{display:grid;grid-template-columns:repeat(var(--df-grid-columns, 12),1fr);gap:var(--df-grid-gap);align-items:start;width:100%}.df-row>*:not([class*=df-col-]){grid-column:1/-1}.df-col-1{grid-column:span 1}.df-col-2{grid-column:span 2}.df-col-3{grid-column:span 3}.df-col-4{grid-column:span 4}.df-col-5{grid-column:span 5}.df-col-6{grid-column:span 6}.df-col-7{grid-column:span 7}.df-col-8{grid-column:span 8}.df-col-9{grid-column:span 9}.df-col-10{grid-column:span 10}.df-col-11{grid-column:span 11}.df-col-12{grid-column:span 12}.df-col-auto{grid-column:span auto;width:auto}.df-col-full{grid-column:1/-1}.df-col-start-1{grid-column-start:1}.df-col-start-2{grid-column-start:2}.df-col-start-3{grid-column-start:3}.df-col-start-4{grid-column-start:4}.df-col-start-5{grid-column-start:5}.df-col-start-6{grid-column-start:6}.df-col-start-7{grid-column-start:7}.df-col-start-8{grid-column-start:8}.df-col-start-9{grid-column-start:9}.df-col-start-10{grid-column-start:10}.df-col-start-11{grid-column-start:11}.df-col-start-12{grid-column-start:12}.df-col-end-1{grid-column-end:1}.df-col-end-2{grid-column-end:2}.df-col-end-3{grid-column-end:3}.df-col-end-4{grid-column-end:4}.df-col-end-5{grid-column-end:5}.df-col-end-6{grid-column-end:6}.df-col-end-7{grid-column-end:7}.df-col-end-8{grid-column-end:8}.df-col-end-9{grid-column-end:9}.df-col-end-10{grid-column-end:10}.df-col-end-11{grid-column-end:11}.df-col-end-12{grid-column-end:12}.df-col-end-13{grid-column-end:13}@media(max-width:576px){.df-form{--df-grid-gap: var(--df-grid-gap-sm);--df-grid-row-gap: var(--df-grid-row-gap-sm)}.df-row{grid-template-columns:1fr}.df-row>*{grid-column:1/-1!important}.df-row.df-row-mobile-keep-cols{grid-template-columns:repeat(var(--df-grid-columns),1fr)}.df-row.df-row-mobile-keep-cols>*{grid-column:revert!important}}@media(min-width:577px)and (max-width:768px){.df-form{--df-grid-gap: var(--df-grid-gap-md);--df-grid-row-gap: var(--df-grid-row-gap-md)}.df-row{--df-grid-columns: 6}.df-col-sm-1{grid-column:span 1}.df-col-sm-2{grid-column:span 2}.df-col-sm-3{grid-column:span 3}.df-col-sm-4{grid-column:span 4}.df-col-sm-5{grid-column:span 5}.df-col-sm-6{grid-column:span 6}.df-col-sm-full{grid-column:1/-1}}@media(min-width:769px)and (max-width:992px){.df-form{--df-grid-gap: var(--df-grid-gap-lg);--df-grid-row-gap: var(--df-grid-row-gap-lg)}.df-col-md-1{grid-column:span 1}.df-col-md-2{grid-column:span 2}.df-col-md-3{grid-column:span 3}.df-col-md-4{grid-column:span 4}.df-col-md-5{grid-column:span 5}.df-col-md-6{grid-column:span 6}.df-col-md-7{grid-column:span 7}.df-col-md-8{grid-column:span 8}.df-col-md-9{grid-column:span 9}.df-col-md-10{grid-column:span 10}.df-col-md-11{grid-column:span 11}.df-col-md-12{grid-column:span 12}.df-col-md-full{grid-column:1/-1}}@media(min-width:993px){.df-form{--df-grid-gap: var(--df-grid-gap-xl);--df-grid-row-gap: var(--df-grid-row-gap-xl)}.df-col-lg-1{grid-column:span 1}.df-col-lg-2{grid-column:span 2}.df-col-lg-3{grid-column:span 3}.df-col-lg-4{grid-column:span 4}.df-col-lg-5{grid-column:span 5}.df-col-lg-6{grid-column:span 6}.df-col-lg-7{grid-column:span 7}.df-col-lg-8{grid-column:span 8}.df-col-lg-9{grid-column:span 9}.df-col-lg-10{grid-column:span 10}.df-col-lg-11{grid-column:span 11}.df-col-lg-12{grid-column:span 12}.df-col-lg-full{grid-column:1/-1}}.df-gap-none{--df-grid-gap: 0}.df-gap-xs{--df-grid-gap: .25rem}.df-gap-sm{--df-grid-gap: .5rem}.df-gap-md{--df-grid-gap: 1rem}.df-gap-lg{--df-grid-gap: 1.5rem}.df-gap-xl{--df-grid-gap: 2rem}.df-row-gap-none{--df-grid-row-gap: 0}.df-row-gap-xs{--df-grid-row-gap: .25rem}.df-row-gap-sm{--df-grid-row-gap: .5rem}.df-row-gap-md{--df-grid-row-gap: 1rem}.df-row-gap-lg{--df-grid-row-gap: 1.5rem}.df-row-gap-xl{--df-grid-row-gap: 2rem}.df-field{display:block;width:100%;min-width:0;overflow:hidden;margin:0}.df-group,.df-page{display:block;width:100%}.df-form.disabled,.df-row.disabled,.df-field.disabled{opacity:.6;pointer-events:none}.df-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-sr-only-focusable:focus,.df-sr-only-focusable:active{position:static;width:auto;height:auto;padding:inherit;margin:inherit;overflow:visible;clip:auto;white-space:normal}.df-live-region{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.df-form :focus-visible{outline:2px solid var(--df-focus-color, #005fcc);outline-offset:2px}.df-group:focus-within{outline:1px dashed var(--df-focus-color, #005fcc);outline-offset:4px}:host{display:grid;grid-template-columns:1fr;gap:var(--df-array-item-gap, var(--df-grid-row-gap, .5rem));width:100%}:host>*{grid-column:1/-1}:host.df-container-hidden{display:none}.df-array-item{display:grid;grid-template-columns:1fr;gap:var(--df-grid-row-gap, .5rem);width:100%}.df-array-item>*{grid-column:1/-1}\n"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15777
16794
|
}
|
|
15778
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
16795
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ArrayFieldComponent, decorators: [{
|
|
15779
16796
|
type: Component,
|
|
15780
|
-
args: [{ selector: 'array-field', imports: [
|
|
16797
|
+
args: [{ selector: 'array-field', imports: [DfFieldOutlet], template: `
|
|
15781
16798
|
@for (item of resolvedItems(); track item.id; let i = $index) {
|
|
15782
16799
|
<div
|
|
15783
16800
|
class="df-array-item"
|
|
@@ -15787,9 +16804,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
|
|
|
15787
16804
|
[attr.data-array-item-index]="i"
|
|
15788
16805
|
>
|
|
15789
16806
|
@for (field of item.fields; track $index) {
|
|
15790
|
-
<ng-container
|
|
15791
|
-
*ngComponentOutlet="field.component; injector: field.injector; environmentInjector: environmentInjector; inputs: field.inputs()"
|
|
15792
|
-
/>
|
|
16807
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
15793
16808
|
}
|
|
15794
16809
|
</div>
|
|
15795
16810
|
}
|
|
@@ -15814,12 +16829,95 @@ var arrayField_component = /*#__PURE__*/Object.freeze({
|
|
|
15814
16829
|
});
|
|
15815
16830
|
|
|
15816
16831
|
/**
|
|
15817
|
-
*
|
|
16832
|
+
* Layout container that wraps child fields with UI chrome.
|
|
16833
|
+
*
|
|
16834
|
+
* Resolves children like a row, then chains wrapper components around them
|
|
16835
|
+
* using imperative `ViewContainerRef.createComponent()` — each wrapper's
|
|
16836
|
+
* `#fieldComponent` slot hosts the next wrapper or the children template.
|
|
16837
|
+
*
|
|
16838
|
+
* Does not create a new form context - fields share the parent's context.
|
|
16839
|
+
* Field values are flattened into the parent form (no nesting under container key).
|
|
16840
|
+
* Purely a visual/layout container with no impact on form structure.
|
|
15818
16841
|
*/
|
|
15819
|
-
|
|
15820
|
-
|
|
15821
|
-
|
|
16842
|
+
class ContainerFieldComponent {
|
|
16843
|
+
destroyRef = inject(DestroyRef);
|
|
16844
|
+
fieldRegistry = injectFieldRegistry();
|
|
16845
|
+
injector = inject(Injector);
|
|
16846
|
+
environmentInjector = inject(EnvironmentInjector);
|
|
16847
|
+
eventBus = inject(EventBus);
|
|
16848
|
+
logger = inject(DynamicFormLogger);
|
|
16849
|
+
wrapperAutoAssociations = inject(WRAPPER_AUTO_ASSOCIATIONS);
|
|
16850
|
+
defaultWrappersSignal = inject(DEFAULT_WRAPPERS, { optional: true });
|
|
16851
|
+
childrenTpl = viewChild.required('childrenTpl', { read: TemplateRef });
|
|
16852
|
+
wrapperContainer = viewChild.required('wrapperContainer', { read: ViewContainerRef });
|
|
16853
|
+
field = input.required(...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
|
|
16854
|
+
key = input.required(...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
|
|
16855
|
+
className = input(...(ngDevMode ? [undefined, { debugName: "className" }] : /* istanbul ignore next */ []));
|
|
16856
|
+
hidden = input(false, ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
|
|
16857
|
+
hostClasses = computed(() => computeContainerHostClasses('container', this.className()), ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
|
|
16858
|
+
disabled = computed(() => this.field().disabled || false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
16859
|
+
rawFieldRegistry = computed(() => this.fieldRegistry.raw, ...(ngDevMode ? [{ debugName: "rawFieldRegistry" }] : /* istanbul ignore next */ []));
|
|
16860
|
+
fieldsSource = computed(() => (this.field().fields || []), ...(ngDevMode ? [{ debugName: "fieldsSource" }] : /* istanbul ignore next */ []));
|
|
16861
|
+
resolvedFields = derivedFromDeferred(this.fieldsSource, createFieldResolutionPipe(() => ({
|
|
16862
|
+
loadTypeComponent: (type) => this.fieldRegistry.loadTypeComponent(type),
|
|
16863
|
+
registry: this.rawFieldRegistry(),
|
|
16864
|
+
injector: this.injector,
|
|
16865
|
+
destroyRef: this.destroyRef,
|
|
16866
|
+
onError: (fieldDef, error) => {
|
|
16867
|
+
const fieldKey = fieldDef.key || '<no key>';
|
|
16868
|
+
const containerKey = this.field().key || '<no key>';
|
|
16869
|
+
this.logger.error(`Failed to load component for field type '${fieldDef.type}' (key: ${fieldKey}) ` +
|
|
16870
|
+
`within container '${containerKey}'. Ensure the field type is registered in your field registry.`, error);
|
|
16871
|
+
},
|
|
16872
|
+
})), { initialValue: [], injector: this.injector });
|
|
16873
|
+
wrappers = computed(() => resolveWrappers(this.field(), this.defaultWrappersSignal?.(), this.wrapperAutoAssociations), { ...(ngDevMode ? { debugName: "wrappers" } : /* istanbul ignore next */ {}), equal: isSameWrapperChain });
|
|
16874
|
+
constructor() {
|
|
16875
|
+
// `row` is a virtual field type backed by ContainerFieldComponent — at
|
|
16876
|
+
// runtime the spread in rowFieldMapper preserves `type: 'row'` on the
|
|
16877
|
+
// field input, even though the declared type is `ContainerField`. Widen
|
|
16878
|
+
// here so `(events)` consumers filtering by `componentType === 'row'`
|
|
16879
|
+
// keep receiving emissions.
|
|
16880
|
+
setupContainerInitEffect(this.resolvedFields, this.eventBus, () => (this.field().type === 'row' ? 'row' : 'container'), () => this.field().key, this.injector);
|
|
16881
|
+
createWrapperChainController({
|
|
16882
|
+
vcr: this.wrapperContainer,
|
|
16883
|
+
wrappers: this.wrappers,
|
|
16884
|
+
renderInnermost: (slot) => slot.createEmbeddedView(this.childrenTpl()),
|
|
16885
|
+
});
|
|
16886
|
+
}
|
|
16887
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ContainerFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
16888
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ContainerFieldComponent, isStandalone: true, selector: "div[container-field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "class.disabled": "disabled()", "class.df-container-hidden": "hidden()", "attr.aria-hidden": "hidden() || null", "id": "`${key()}`", "attr.data-testid": "key()" } }, viewQueries: [{ propertyName: "childrenTpl", first: true, predicate: ["childrenTpl"], descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "wrapperContainer", first: true, predicate: ["wrapperContainer"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: `
|
|
16889
|
+
<ng-template #childrenTpl>
|
|
16890
|
+
@for (field of resolvedFields(); track field.key) {
|
|
16891
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
16892
|
+
}
|
|
16893
|
+
</ng-template>
|
|
16894
|
+
<ng-container #wrapperContainer></ng-container>
|
|
16895
|
+
`, isInline: true, styles: [":host{display:block;width:100%}:host.df-container-hidden{display:none}\n"], dependencies: [{ kind: "directive", type: DfFieldOutlet, selector: "[dfFieldOutlet]", inputs: ["dfFieldOutlet", "dfFieldOutletEnvironmentInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15822
16896
|
}
|
|
16897
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ContainerFieldComponent, decorators: [{
|
|
16898
|
+
type: Component,
|
|
16899
|
+
args: [{ selector: 'div[container-field]', imports: [DfFieldOutlet], template: `
|
|
16900
|
+
<ng-template #childrenTpl>
|
|
16901
|
+
@for (field of resolvedFields(); track field.key) {
|
|
16902
|
+
<ng-container *dfFieldOutlet="field; environmentInjector: environmentInjector" />
|
|
16903
|
+
}
|
|
16904
|
+
</ng-template>
|
|
16905
|
+
<ng-container #wrapperContainer></ng-container>
|
|
16906
|
+
`, host: {
|
|
16907
|
+
'[class]': 'hostClasses()',
|
|
16908
|
+
'[class.disabled]': 'disabled()',
|
|
16909
|
+
'[class.df-container-hidden]': 'hidden()',
|
|
16910
|
+
'[attr.aria-hidden]': 'hidden() || null',
|
|
16911
|
+
'[id]': '`${key()}`',
|
|
16912
|
+
'[attr.data-testid]': 'key()',
|
|
16913
|
+
}, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;width:100%}:host.df-container-hidden{display:none}\n"] }]
|
|
16914
|
+
}], 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 }] }] } });
|
|
16915
|
+
|
|
16916
|
+
var containerField_component = /*#__PURE__*/Object.freeze({
|
|
16917
|
+
__proto__: null,
|
|
16918
|
+
ContainerFieldComponent: ContainerFieldComponent,
|
|
16919
|
+
default: ContainerFieldComponent
|
|
16920
|
+
});
|
|
15823
16921
|
|
|
15824
16922
|
/**
|
|
15825
16923
|
* Interpolates {{param}} placeholders in message with error values
|
|
@@ -15952,5 +17050,5 @@ function extractErrorParams(error) {
|
|
|
15952
17050
|
* Generated bundle index. Do not edit.
|
|
15953
17051
|
*/
|
|
15954
17052
|
|
|
15955
|
-
export {
|
|
15956
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-
|
|
17053
|
+
export { evaluateNonFieldDisabled as $, ARRAY_CONTEXT as A, BUILT_IN_FIELDS as B, ConsoleLogger as C, DynamicFormLogger as D, EventBus as E, FIELD_REGISTRY as F, GroupFieldComponent as G, PreviousPageEvent as H, INITIALIZATION_TIMEOUT_MS as I, RootFormRegistryService as J, SubmitEvent as K, applyMetaToElement as L, MoveArrayItemEvent as M, NextPageEvent as N, applyValidator as O, PageChangeEvent as P, applyValidators as Q, RemoveAtIndexEvent as R, ShiftArrayItemEvent as S, arrayEvent as T, arrayFieldMapper as U, baseFieldMapper as V, WRAPPER_REGISTRY as W, buildBaseInputs as X, containerFieldMapper as Y, createField as Z, createWrappers as _, createFieldResolutionPipe as a, evaluateNonFieldHidden as a0, field as a1, formConfig as a2, getArrayLength as a3, groupFieldMapper as a4, hasFormValue as a5, interpolateParams as a6, isArrayField as a7, isCheckedField as a8, isContainerField as a9, withPreviousValue as aA, withValueExclusionDefaults as aB, wrapperProps as aC, isContainerTypedField as aa, isDisplayOnlyField as ab, isEqual as ac, isFormStateCondition as ad, isGroupField as ae, isLeafField as af, isPageField as ag, isRowField as ah, isSimplifiedArrayField as ai, isValueBearingField as aj, isValueField as ak, isWrapperTypeDefinition as al, isWrappersBundle as am, omit as an, pageFieldMapper as ao, provideDynamicForm as ap, resolveNextButtonDisabled as aq, resolveNonFieldDisabled as ar, resolveNonFieldHidden as as, resolveSubmitButtonDisabled as at, resolveTokens as au, rowFieldMapper as av, textFieldMapper as aw, toReadonlyFieldTree as ax, withEventFormValue as ay, withLoggerConfig as az, DfFieldOutlet as b, computeContainerHostClasses as c, derivedFromDeferred as d, DynamicTextPipe as e, dynamicTextToObservable as f, AppendArrayItemEvent as g, ArrayFieldComponent as h, injectFieldRegistry as i, ContainerFieldComponent as j, DEFAULT_PROPS as k, DEFAULT_VALIDATION_MESSAGES as l, DEFAULT_WRAPPERS as m, DynamicForm as n, DynamicFormError as o, EventDispatcher as p, FIELD_SIGNAL_CONTEXT as q, FORM_OPTIONS as r, setupContainerInitEffect as s, FormClearEvent as t, FormResetEvent as u, validatePageNesting as v, InsertArrayItemEvent as w, NoopLogger as x, PopArrayItemEvent as y, PrependArrayItemEvent as z };
|
|
17054
|
+
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-BwoONrnj.mjs.map
|