@ng-forge/dynamic-forms 0.9.0-next.4 → 0.9.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-DlnH8RvU.mjs → ng-forge-dynamic-forms-array-field.component-CltYGHg9.mjs} +3 -3
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-DlnH8RvU.mjs.map → ng-forge-dynamic-forms-array-field.component-CltYGHg9.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-BREzTxVg.mjs → ng-forge-dynamic-forms-container-field.component-D4Fpr2ri.mjs} +3 -3
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-BREzTxVg.mjs.map → ng-forge-dynamic-forms-container-field.component-D4Fpr2ri.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-container-utils-DhIj8ePz.mjs → ng-forge-dynamic-forms-container-utils-BTiqZ8Np.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-container-utils-DhIj8ePz.mjs.map → ng-forge-dynamic-forms-container-utils-BTiqZ8Np.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-D7z_KDTu.mjs → ng-forge-dynamic-forms-css-wrapper.component-mdfySFjX.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-D7z_KDTu.mjs.map → ng-forge-dynamic-forms-css-wrapper.component-mdfySFjX.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-ulrSEvoG.mjs → ng-forge-dynamic-forms-emit-initialization-Bt3kvlOY.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-ulrSEvoG.mjs.map → ng-forge-dynamic-forms-emit-initialization-Bt3kvlOY.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-CmGl1ojm.mjs → ng-forge-dynamic-forms-group-field.component-BxswrhOc.mjs} +3 -3
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-CmGl1ojm.mjs.map → ng-forge-dynamic-forms-group-field.component-BxswrhOc.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-BaV56Adz.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-QdV2vcCx.mjs} +414 -90
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-QdV2vcCx.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-CH2QPKXC.mjs → ng-forge-dynamic-forms-page-field.component-BHNVryIq.mjs} +3 -3
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-CH2QPKXC.mjs.map → ng-forge-dynamic-forms-page-field.component-BHNVryIq.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-CSJ_5_Kc.mjs → ng-forge-dynamic-forms-text-field.component-Cf1mEKVW.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-CSJ_5_Kc.mjs.map → ng-forge-dynamic-forms-text-field.component-Cf1mEKVW.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms.mjs +1 -1
- package/package.json +1 -1
- package/types/ng-forge-dynamic-forms.d.ts +104 -6
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-BaV56Adz.mjs.map +0 -1
|
@@ -4141,9 +4141,9 @@ class PageOrchestratorComponent {
|
|
|
4141
4141
|
}
|
|
4142
4142
|
}
|
|
4143
4143
|
}
|
|
4144
|
-
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-
|
|
4144
|
+
`, isInline: true, styles: [":host{display:block;position:relative;width:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [import('./ng-forge-dynamic-forms-page-field.component-BHNVryIq.mjs').then(m => m.default)], () => [import('./ng-forge-dynamic-forms-page-field.component-BHNVryIq.mjs').then(m => m.default)]] });
|
|
4145
4145
|
}
|
|
4146
|
-
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.8", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [import('./ng-forge-dynamic-forms-page-field.component-
|
|
4146
|
+
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.8", ngImport: i0, type: PageOrchestratorComponent, resolveDeferredDeps: () => [import('./ng-forge-dynamic-forms-page-field.component-BHNVryIq.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
4147
4147
|
type: Component,
|
|
4148
4148
|
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
4149
4149
|
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
@@ -6187,6 +6187,109 @@ function createSchemaFunction(schema) {
|
|
|
6187
6187
|
};
|
|
6188
6188
|
}
|
|
6189
6189
|
|
|
6190
|
+
/**
|
|
6191
|
+
* Combines the inherited "is any ancestor hidden" check into a single LogicFn.
|
|
6192
|
+
* Returns `undefined` when no ancestor has dynamic hidden logic.
|
|
6193
|
+
*
|
|
6194
|
+
* @internal
|
|
6195
|
+
*/
|
|
6196
|
+
function combineAncestorHiddenLogics(logics) {
|
|
6197
|
+
if (logics.length === 0)
|
|
6198
|
+
return undefined;
|
|
6199
|
+
if (logics.length === 1)
|
|
6200
|
+
return logics[0];
|
|
6201
|
+
return (ctx) => logics.some((fn) => fn(ctx));
|
|
6202
|
+
}
|
|
6203
|
+
/**
|
|
6204
|
+
* Returns the `FieldTreeMappingContext` to use **for the field itself** — i.e.
|
|
6205
|
+
* for gating its own validators. Only applies overrides that affect the field
|
|
6206
|
+
* (currently `validateWhenHidden`). The field's own hidden state is NOT folded
|
|
6207
|
+
* into `ancestorAlwaysHidden`/`ancestorHiddenLogics` here — Angular Signal
|
|
6208
|
+
* Forms already exposes that through `ctx.state.hidden()`, which the gate
|
|
6209
|
+
* checks separately.
|
|
6210
|
+
*
|
|
6211
|
+
* Returns the parent context unchanged when the field defines no overrides —
|
|
6212
|
+
* saves an allocation on every leaf in the common case.
|
|
6213
|
+
*
|
|
6214
|
+
* @internal
|
|
6215
|
+
*/
|
|
6216
|
+
function resolveFieldOwnContext(fieldDef, parentContext) {
|
|
6217
|
+
if (fieldDef.validateWhenHidden === undefined)
|
|
6218
|
+
return parentContext;
|
|
6219
|
+
if (fieldDef.validateWhenHidden === parentContext.validateWhenHidden)
|
|
6220
|
+
return parentContext;
|
|
6221
|
+
return {
|
|
6222
|
+
...parentContext,
|
|
6223
|
+
validateWhenHidden: fieldDef.validateWhenHidden,
|
|
6224
|
+
};
|
|
6225
|
+
}
|
|
6226
|
+
/**
|
|
6227
|
+
* Returns the `FieldTreeMappingContext` to use **for the field's descendants**.
|
|
6228
|
+
* Builds on top of `ownContext` by folding in the field's own hidden state so
|
|
6229
|
+
* children can correctly skip validation when this field hides them. Used for
|
|
6230
|
+
* groups, arrays, and layout containers that recurse into children.
|
|
6231
|
+
*
|
|
6232
|
+
* Behavior:
|
|
6233
|
+
* - `hidden: true` (static) → sets `ancestorAlwaysHidden`.
|
|
6234
|
+
* - `logic: [{type: 'hidden', condition: true}]` → sets `ancestorAlwaysHidden`.
|
|
6235
|
+
* - `logic: [{type: 'hidden', condition: false}]` → no effect.
|
|
6236
|
+
* - `logic: [{type: 'hidden', condition: <expr>}]` → appends a reactive
|
|
6237
|
+
* `LogicFn` to `ancestorHiddenLogics`.
|
|
6238
|
+
*
|
|
6239
|
+
* @internal
|
|
6240
|
+
*/
|
|
6241
|
+
function resolveDescendantContext(fieldDef, ownContext) {
|
|
6242
|
+
let ancestorAlwaysHidden = ownContext.ancestorAlwaysHidden;
|
|
6243
|
+
let ancestorHiddenLogics = ownContext.ancestorHiddenLogics;
|
|
6244
|
+
let mutated = false;
|
|
6245
|
+
if (fieldDef.hidden === true && !ancestorAlwaysHidden) {
|
|
6246
|
+
ancestorAlwaysHidden = true;
|
|
6247
|
+
mutated = true;
|
|
6248
|
+
}
|
|
6249
|
+
if (fieldDef.logic) {
|
|
6250
|
+
for (const logic of fieldDef.logic) {
|
|
6251
|
+
if (!isStateLogicConfig(logic) || logic.type !== 'hidden')
|
|
6252
|
+
continue;
|
|
6253
|
+
const condition = logic.condition;
|
|
6254
|
+
if (condition === true) {
|
|
6255
|
+
if (!ancestorAlwaysHidden) {
|
|
6256
|
+
ancestorAlwaysHidden = true;
|
|
6257
|
+
mutated = true;
|
|
6258
|
+
}
|
|
6259
|
+
continue;
|
|
6260
|
+
}
|
|
6261
|
+
if (condition === false)
|
|
6262
|
+
continue;
|
|
6263
|
+
// FormStateCondition strings ('formInvalid', etc.) are intended for buttons —
|
|
6264
|
+
// they don't make sense as a "field hidden" trigger, so we skip them here.
|
|
6265
|
+
if (typeof condition === 'string')
|
|
6266
|
+
continue;
|
|
6267
|
+
// Dynamic — we need a reactive logic function. createLogicFunction
|
|
6268
|
+
// requires an injection context, which the schema build provides.
|
|
6269
|
+
const fn = createLogicFunction(condition);
|
|
6270
|
+
ancestorHiddenLogics = ancestorHiddenLogics.length === 0 ? [fn] : [...ancestorHiddenLogics, fn];
|
|
6271
|
+
mutated = true;
|
|
6272
|
+
}
|
|
6273
|
+
}
|
|
6274
|
+
if (!mutated)
|
|
6275
|
+
return ownContext;
|
|
6276
|
+
return {
|
|
6277
|
+
validateWhenHidden: ownContext.validateWhenHidden,
|
|
6278
|
+
ancestorAlwaysHidden,
|
|
6279
|
+
ancestorHiddenLogics,
|
|
6280
|
+
};
|
|
6281
|
+
}
|
|
6282
|
+
|
|
6283
|
+
/**
|
|
6284
|
+
* Default {@link FieldTreeMappingContext} used when no inherited context is provided.
|
|
6285
|
+
* Mirrors the global token defaults so callers that bypass `createSchemaFromFields`
|
|
6286
|
+
* (e.g. nested group forms) still get sensible behavior.
|
|
6287
|
+
*/
|
|
6288
|
+
const DEFAULT_FIELD_TREE_MAPPING_CONTEXT = {
|
|
6289
|
+
validateWhenHidden: false,
|
|
6290
|
+
ancestorAlwaysHidden: false,
|
|
6291
|
+
ancestorHiddenLogics: [],
|
|
6292
|
+
};
|
|
6190
6293
|
/**
|
|
6191
6294
|
* Applies string-specific validators to a schema path.
|
|
6192
6295
|
* This function is properly typed - no casts needed internally.
|
|
@@ -6267,25 +6370,42 @@ function getNumberValidationConfig(fieldDef) {
|
|
|
6267
6370
|
* which uses RootFormRegistryService.
|
|
6268
6371
|
*
|
|
6269
6372
|
* Cross-field validators are skipped at field level and applied at form level via validateTree.
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6373
|
+
*
|
|
6374
|
+
* @param fieldDef Field definition to map
|
|
6375
|
+
* @param fieldPath Schema path for this field
|
|
6376
|
+
* @param inheritedContext Cascading mapping context from the parent. Field-level
|
|
6377
|
+
* overrides on `fieldDef` are merged on top and passed down to descendants. Defaults
|
|
6378
|
+
* to {@link DEFAULT_FIELD_TREE_MAPPING_CONTEXT} when omitted.
|
|
6379
|
+
*/
|
|
6380
|
+
function mapFieldToForm(fieldDef, fieldPath, inheritedContext = DEFAULT_FIELD_TREE_MAPPING_CONTEXT) {
|
|
6381
|
+
const ownContext = resolveFieldOwnContext(fieldDef, inheritedContext);
|
|
6382
|
+
// Layout containers (page, row, container) - flatten children to current level.
|
|
6383
|
+
// Layout containers have no schema path of their own, so any hidden state on them
|
|
6384
|
+
// must be propagated to descendants through the cascade.
|
|
6273
6385
|
if (isPageField(fieldDef) || isRowField(fieldDef) || isContainerTypedField(fieldDef)) {
|
|
6274
|
-
|
|
6386
|
+
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
6387
|
+
mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
|
|
6275
6388
|
return;
|
|
6276
6389
|
}
|
|
6277
|
-
// Group fields - map children under the group's path
|
|
6390
|
+
// Group fields - map children under the group's path. Groups have a schema path,
|
|
6391
|
+
// so Angular Signal Forms can propagate `hidden(path, ...)` calls to descendants —
|
|
6392
|
+
// but groups don't currently apply their own hidden state, so we still propagate
|
|
6393
|
+
// through the cascade as a belt-and-suspenders fallback.
|
|
6278
6394
|
if (isGroupField(fieldDef)) {
|
|
6279
|
-
|
|
6395
|
+
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
6396
|
+
mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
|
|
6280
6397
|
return;
|
|
6281
6398
|
}
|
|
6282
|
-
// Array fields - use applyEach for item schema
|
|
6399
|
+
// Array fields - use applyEach for item schema.
|
|
6283
6400
|
if (isArrayField(fieldDef)) {
|
|
6284
|
-
|
|
6401
|
+
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
6402
|
+
mapArrayFieldToForm(fieldDef, fieldPath, descendantContext);
|
|
6285
6403
|
return;
|
|
6286
6404
|
}
|
|
6287
|
-
// Leaf field - apply validation, logic, and configuration
|
|
6288
|
-
|
|
6405
|
+
// Leaf field - apply validation, logic, and configuration. Use the field's OWN
|
|
6406
|
+
// context (no descendant propagation) because the leaf's own hidden state is
|
|
6407
|
+
// already exposed via `ctx.state.hidden()`.
|
|
6408
|
+
mapLeafField(fieldDef, fieldPath, ownContext);
|
|
6289
6409
|
}
|
|
6290
6410
|
/**
|
|
6291
6411
|
* Maps children of a container field (page, row, group) to the form schema.
|
|
@@ -6296,50 +6416,100 @@ function mapFieldToForm(fieldDef, fieldPath) {
|
|
|
6296
6416
|
* leaf descendants. Group/array fields consume their key and look up their own
|
|
6297
6417
|
* child path on `parentPath`.
|
|
6298
6418
|
*/
|
|
6299
|
-
function mapContainerChildren(fields, parentPath) {
|
|
6419
|
+
function mapContainerChildren(fields, parentPath, inheritedContext) {
|
|
6300
6420
|
if (!fields)
|
|
6301
6421
|
return;
|
|
6302
6422
|
const pathRecord = parentPath;
|
|
6303
6423
|
for (const field of fields) {
|
|
6304
6424
|
if (isPageField(field) || isRowField(field) || isContainerTypedField(field)) {
|
|
6305
|
-
|
|
6425
|
+
// Layout containers can still carry their own cascading overrides that flow
|
|
6426
|
+
// down to their flattened descendants.
|
|
6427
|
+
const layoutOwn = resolveFieldOwnContext(field, inheritedContext);
|
|
6428
|
+
const layoutDescendant = resolveDescendantContext(field, layoutOwn);
|
|
6429
|
+
mapContainerChildren(field.fields, parentPath, layoutDescendant);
|
|
6306
6430
|
continue;
|
|
6307
6431
|
}
|
|
6308
6432
|
if (!field.key)
|
|
6309
6433
|
continue;
|
|
6310
6434
|
const childPath = pathRecord[field.key];
|
|
6311
6435
|
if (childPath) {
|
|
6312
|
-
mapFieldToForm(field, childPath);
|
|
6436
|
+
mapFieldToForm(field, childPath, inheritedContext);
|
|
6313
6437
|
}
|
|
6314
6438
|
}
|
|
6315
6439
|
}
|
|
6440
|
+
/**
|
|
6441
|
+
* Returns true for state logic that contributes validation (currently only `required`).
|
|
6442
|
+
* The other state logic types (`hidden`, `disabled`, `readonly`) must always apply,
|
|
6443
|
+
* even on hidden fields, so they're not gated by `validateWhenHidden`.
|
|
6444
|
+
*/
|
|
6445
|
+
function isValidationStateLogic(config) {
|
|
6446
|
+
return isStateLogicConfig(config) && config.type === 'required';
|
|
6447
|
+
}
|
|
6316
6448
|
/**
|
|
6317
6449
|
* Maps a leaf field (value-bearing field) to the form schema.
|
|
6450
|
+
*
|
|
6451
|
+
* @param context Resolved cascading context for this field. When
|
|
6452
|
+
* `context.validateWhenHidden` is `false`, the field's validation block is wrapped
|
|
6453
|
+
* in `applyWhen` keyed off `!ctx.state.hidden()`.
|
|
6318
6454
|
*/
|
|
6319
|
-
function mapLeafField(fieldDef, fieldPath) {
|
|
6455
|
+
function mapLeafField(fieldDef, fieldPath, context) {
|
|
6320
6456
|
const validationField = fieldDef;
|
|
6321
6457
|
const path = fieldPath;
|
|
6322
|
-
//
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
applyValidator(config, fieldPath);
|
|
6327
|
-
}
|
|
6328
|
-
}
|
|
6329
|
-
// Apply logic rules
|
|
6458
|
+
// Always-applied: state logic (hidden/disabled/readonly), schemas, and value derivations.
|
|
6459
|
+
// These must run regardless of hidden state — particularly the `hidden` logic itself,
|
|
6460
|
+
// which establishes the very signal `applyWhen` reads below.
|
|
6461
|
+
applyFieldState(fieldDef, path);
|
|
6330
6462
|
if (validationField.logic) {
|
|
6331
6463
|
for (const config of validationField.logic) {
|
|
6332
|
-
|
|
6464
|
+
if (!isValidationStateLogic(config)) {
|
|
6465
|
+
applyLogic(config, fieldPath);
|
|
6466
|
+
}
|
|
6333
6467
|
}
|
|
6334
6468
|
}
|
|
6335
|
-
// Apply schemas
|
|
6336
6469
|
if (validationField.schemas) {
|
|
6337
6470
|
for (const config of validationField.schemas) {
|
|
6338
6471
|
applySchema(config, fieldPath);
|
|
6339
6472
|
}
|
|
6340
6473
|
}
|
|
6341
|
-
//
|
|
6342
|
-
|
|
6474
|
+
// Validation block — gated by `validateWhenHidden` when hidden.
|
|
6475
|
+
const applyValidation = (validationPath) => {
|
|
6476
|
+
applySimpleValidationRules(validationField, validationPath);
|
|
6477
|
+
if (validationField.validators) {
|
|
6478
|
+
for (const config of validationField.validators) {
|
|
6479
|
+
applyValidator(config, validationPath);
|
|
6480
|
+
}
|
|
6481
|
+
}
|
|
6482
|
+
if (validationField.logic) {
|
|
6483
|
+
for (const config of validationField.logic) {
|
|
6484
|
+
if (isValidationStateLogic(config)) {
|
|
6485
|
+
applyLogic(config, validationPath);
|
|
6486
|
+
}
|
|
6487
|
+
}
|
|
6488
|
+
}
|
|
6489
|
+
};
|
|
6490
|
+
if (context.validateWhenHidden) {
|
|
6491
|
+
applyValidation(path);
|
|
6492
|
+
return;
|
|
6493
|
+
}
|
|
6494
|
+
// An ancestor is unconditionally hidden — there is no reactive case for the
|
|
6495
|
+
// validators to ever fire, so we simply do not apply them.
|
|
6496
|
+
if (context.ancestorAlwaysHidden) {
|
|
6497
|
+
return;
|
|
6498
|
+
}
|
|
6499
|
+
// applyWhen conditionally applies a sub-schema. The gate combines:
|
|
6500
|
+
// - the field's own `state.hidden()` (set by Angular Signal Forms whenever
|
|
6501
|
+
// `hidden(path, ...)` was called for this field or any ancestor with a
|
|
6502
|
+
// path); and
|
|
6503
|
+
// - any ancestor dynamic hidden logic that does NOT live on a schema path
|
|
6504
|
+
// (layout containers — see field-tree-mapping-context for details).
|
|
6505
|
+
const ancestorHiddenLogic = combineAncestorHiddenLogics(context.ancestorHiddenLogics);
|
|
6506
|
+
applyWhen(path, (ctx) => {
|
|
6507
|
+
if (ctx.state.hidden())
|
|
6508
|
+
return false;
|
|
6509
|
+
if (ancestorHiddenLogic && ancestorHiddenLogic(ctx))
|
|
6510
|
+
return false;
|
|
6511
|
+
return true;
|
|
6512
|
+
}, schema((subPath) => applyValidation(subPath)));
|
|
6343
6513
|
}
|
|
6344
6514
|
/**
|
|
6345
6515
|
* Applies simple validation rules from field properties.
|
|
@@ -6388,7 +6558,7 @@ function applyFieldState(fieldDef, path) {
|
|
|
6388
6558
|
* - Heterogeneous arrays - mixed primitives and objects
|
|
6389
6559
|
* - Container templates (row, group, page) that wrap children
|
|
6390
6560
|
*/
|
|
6391
|
-
function mapArrayFieldToForm(arrayField, fieldPath) {
|
|
6561
|
+
function mapArrayFieldToForm(arrayField, fieldPath, context) {
|
|
6392
6562
|
if (!isArrayField(arrayField)) {
|
|
6393
6563
|
return;
|
|
6394
6564
|
}
|
|
@@ -6459,20 +6629,24 @@ function mapArrayFieldToForm(arrayField, fieldPath) {
|
|
|
6459
6629
|
for (const templateField of allObjectFields) {
|
|
6460
6630
|
if (isRowField(templateField) || isPageField(templateField) || isContainerTypedField(templateField)) {
|
|
6461
6631
|
// Row/page/container templates flatten their children
|
|
6462
|
-
|
|
6632
|
+
const layoutOwn = resolveFieldOwnContext(templateField, context);
|
|
6633
|
+
const layoutDescendant = resolveDescendantContext(templateField, layoutOwn);
|
|
6634
|
+
mapContainerChildren(templateField.fields, itemPath, layoutDescendant);
|
|
6463
6635
|
}
|
|
6464
6636
|
else if (isGroupField(templateField)) {
|
|
6465
6637
|
// Group template - access group's path first
|
|
6466
6638
|
const groupKey = templateField.key;
|
|
6639
|
+
const groupOwn = resolveFieldOwnContext(templateField, context);
|
|
6640
|
+
const groupDescendant = resolveDescendantContext(templateField, groupOwn);
|
|
6467
6641
|
if (groupKey) {
|
|
6468
6642
|
const groupPath = pathRecord[groupKey];
|
|
6469
6643
|
if (groupPath) {
|
|
6470
|
-
mapContainerChildren(templateField.fields, groupPath);
|
|
6644
|
+
mapContainerChildren(templateField.fields, groupPath, groupDescendant);
|
|
6471
6645
|
}
|
|
6472
6646
|
}
|
|
6473
6647
|
else {
|
|
6474
6648
|
// No group key - apply children directly (edge case)
|
|
6475
|
-
mapContainerChildren(templateField.fields, itemPath);
|
|
6649
|
+
mapContainerChildren(templateField.fields, itemPath, groupDescendant);
|
|
6476
6650
|
}
|
|
6477
6651
|
}
|
|
6478
6652
|
else {
|
|
@@ -6481,7 +6655,7 @@ function mapArrayFieldToForm(arrayField, fieldPath) {
|
|
|
6481
6655
|
if (fieldKey) {
|
|
6482
6656
|
const fieldPathForKey = pathRecord[fieldKey];
|
|
6483
6657
|
if (fieldPathForKey) {
|
|
6484
|
-
mapFieldToForm(templateField, fieldPathForKey);
|
|
6658
|
+
mapFieldToForm(templateField, fieldPathForKey, context);
|
|
6485
6659
|
}
|
|
6486
6660
|
}
|
|
6487
6661
|
}
|
|
@@ -6902,12 +7076,21 @@ function mapValues(obj, fn) {
|
|
|
6902
7076
|
/**
|
|
6903
7077
|
* Deep-merges `value` into `defaults`, producing a new object that contains every
|
|
6904
7078
|
* key from `defaults` plus any overrides from `value`. Plain-object values at
|
|
6905
|
-
* matching keys are merged recursively;
|
|
6906
|
-
* replace the corresponding default wholesale.
|
|
7079
|
+
* matching keys are merged recursively; primitives, dates, and class instances
|
|
7080
|
+
* in `value` replace the corresponding default wholesale.
|
|
7081
|
+
*
|
|
7082
|
+
* Arrays whose lengths match between `defaults` and `value` are merged
|
|
7083
|
+
* positionally: when both `defaults[i]` and `value[i]` are plain objects, they
|
|
7084
|
+
* are deep-merged so partial array-item values don't drop sibling sub-field
|
|
7085
|
+
* keys. When the lengths differ — the runtime add/insert/remove case — the
|
|
7086
|
+
* value array is returned by reference so Signal Forms doesn't see a fresh
|
|
7087
|
+
* array of fresh inner objects on every form-value write (which would rebuild
|
|
7088
|
+
* item bindings and desync the rendered rows from their FieldTrees).
|
|
6907
7089
|
*
|
|
6908
7090
|
* Used by `FormStateManager.entity` so a partial input value (e.g. a nested
|
|
6909
|
-
* group object missing one of its declared sub-fields
|
|
6910
|
-
*
|
|
7091
|
+
* group object missing one of its declared sub-fields, or an array item missing
|
|
7092
|
+
* one of its declared sub-fields) does not orphan the absent sub-field in
|
|
7093
|
+
* Angular Signal Forms' validation graph.
|
|
6911
7094
|
*
|
|
6912
7095
|
* @example
|
|
6913
7096
|
* ```typescript
|
|
@@ -6916,6 +7099,12 @@ function mapValues(obj, fn) {
|
|
|
6916
7099
|
* { a: { line1: 'X', city: 'Y' }, b: 1 },
|
|
6917
7100
|
* );
|
|
6918
7101
|
* // { a: { line1: 'X', line2: '', city: 'Y' }, b: 1 }
|
|
7102
|
+
*
|
|
7103
|
+
* deepMergeDefaults(
|
|
7104
|
+
* { items: [{ checkboxA: false, checkboxB: false }] },
|
|
7105
|
+
* { items: [{ checkboxA: true }] },
|
|
7106
|
+
* );
|
|
7107
|
+
* // { items: [{ checkboxA: true, checkboxB: false }] }
|
|
6919
7108
|
* ```
|
|
6920
7109
|
*/
|
|
6921
7110
|
function deepMergeDefaults(defaults, value) {
|
|
@@ -6930,12 +7119,45 @@ function deepMergeDefaults(defaults, value) {
|
|
|
6930
7119
|
if (isPlainObject(existing) && isPlainObject(incoming)) {
|
|
6931
7120
|
result[key] = deepMergeDefaults(existing, incoming);
|
|
6932
7121
|
}
|
|
7122
|
+
else if (Array.isArray(existing) && Array.isArray(incoming)) {
|
|
7123
|
+
result[key] = mergeArrayDefaults(existing, incoming);
|
|
7124
|
+
}
|
|
6933
7125
|
else {
|
|
6934
7126
|
result[key] = incoming;
|
|
6935
7127
|
}
|
|
6936
7128
|
}
|
|
6937
7129
|
return result;
|
|
6938
7130
|
}
|
|
7131
|
+
/**
|
|
7132
|
+
* @internal
|
|
7133
|
+
* Merges a value array into a defaults array element-wise when their lengths
|
|
7134
|
+
* match. When lengths differ — the runtime case: prepend, insert, append,
|
|
7135
|
+
* remove — we return the value array reference as-is so we don't produce
|
|
7136
|
+
* fresh inner object references for items that already have every declared
|
|
7137
|
+
* sub-field key. Replacing references during the linkedSignal source
|
|
7138
|
+
* recompute that runs after Signal Forms wrote the array would otherwise
|
|
7139
|
+
* desync item FieldTrees from the rendered DOM rows.
|
|
7140
|
+
*
|
|
7141
|
+
* When lengths match (the declared/initial-value path the array fix targets)
|
|
7142
|
+
* each `defaults[i]`/`value[i]` pair is deep-merged when both are plain
|
|
7143
|
+
* objects so partial array-item values don't drop sibling sub-field keys.
|
|
7144
|
+
*/
|
|
7145
|
+
function mergeArrayDefaults(defaults, value) {
|
|
7146
|
+
if (defaults.length !== value.length)
|
|
7147
|
+
return value;
|
|
7148
|
+
// Short-circuit when no element pair would be deep-merged. Returning a fresh
|
|
7149
|
+
// .map() result for primitive arrays (e.g. tags: ['a', 'b']) hands Signal
|
|
7150
|
+
// Forms a new reference on every call and rebuilds item FieldTrees needlessly.
|
|
7151
|
+
if (!value.some((item, i) => isPlainObject(defaults[i]) && isPlainObject(item)))
|
|
7152
|
+
return value;
|
|
7153
|
+
return value.map((item, i) => {
|
|
7154
|
+
const defaultItem = defaults[i];
|
|
7155
|
+
if (isPlainObject(defaultItem) && isPlainObject(item)) {
|
|
7156
|
+
return deepMergeDefaults(defaultItem, item);
|
|
7157
|
+
}
|
|
7158
|
+
return item;
|
|
7159
|
+
});
|
|
7160
|
+
}
|
|
6939
7161
|
/**
|
|
6940
7162
|
* @internal
|
|
6941
7163
|
* Returns true for objects that should be deep-merged (plain objects produced
|
|
@@ -7137,6 +7359,27 @@ function createFormLevelSchema(formLevelSchema) {
|
|
|
7137
7359
|
});
|
|
7138
7360
|
}
|
|
7139
7361
|
|
|
7362
|
+
/**
|
|
7363
|
+
* Injection token for global validation execution defaults.
|
|
7364
|
+
*
|
|
7365
|
+
* Controls whether validators run on hidden fields. By default, validation is
|
|
7366
|
+
* skipped when a field is hidden (`validateWhenHidden: false`).
|
|
7367
|
+
*
|
|
7368
|
+
* Configured via `withValidationExecutionDefaults()`. Per-form overrides via
|
|
7369
|
+
* `FormOptions.validateWhenHidden`. Per-field overrides via `FieldDef.validateWhenHidden`,
|
|
7370
|
+
* which inherits down the field tree.
|
|
7371
|
+
*
|
|
7372
|
+
* **Resolution order:** Field (with parent inheritance) > Form > Global.
|
|
7373
|
+
*
|
|
7374
|
+
* @internal
|
|
7375
|
+
*/
|
|
7376
|
+
const VALIDATION_EXECUTION_DEFAULTS = new InjectionToken('VALIDATION_EXECUTION_DEFAULTS', {
|
|
7377
|
+
providedIn: 'root',
|
|
7378
|
+
factory: () => ({
|
|
7379
|
+
validateWhenHidden: false,
|
|
7380
|
+
}),
|
|
7381
|
+
});
|
|
7382
|
+
|
|
7140
7383
|
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
7141
7384
|
/**
|
|
7142
7385
|
* Gets a FieldTree by key, supporting nested paths with dot notation.
|
|
@@ -7181,11 +7424,17 @@ function createSchemaFromFields(fields, registry, optionsOrValidators) {
|
|
|
7181
7424
|
// These will be available because createSchemaFromFields is called within runInInjectionContext
|
|
7182
7425
|
const functionRegistry = inject(FunctionRegistryService);
|
|
7183
7426
|
const logger = inject(DynamicFormLogger);
|
|
7427
|
+
const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
|
|
7184
7428
|
// Normalize options - support both old array signature and new options object
|
|
7185
7429
|
const options = Array.isArray(optionsOrValidators)
|
|
7186
7430
|
? { crossFieldValidators: optionsOrValidators }
|
|
7187
7431
|
: (optionsOrValidators ?? {});
|
|
7188
7432
|
const { crossFieldValidators, formLevelSchema } = options;
|
|
7433
|
+
const rootContext = {
|
|
7434
|
+
validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,
|
|
7435
|
+
ancestorAlwaysHidden: false,
|
|
7436
|
+
ancestorHiddenLogics: [],
|
|
7437
|
+
};
|
|
7189
7438
|
return schema((path) => {
|
|
7190
7439
|
for (const fieldDef of fields) {
|
|
7191
7440
|
const valueHandling = getFieldValueHandling(fieldDef.type, registry);
|
|
@@ -7195,12 +7444,17 @@ function createSchemaFromFields(fields, registry, optionsOrValidators) {
|
|
|
7195
7444
|
continue;
|
|
7196
7445
|
}
|
|
7197
7446
|
if (valueHandling === 'flatten' && hasChildFields(fieldDef)) {
|
|
7447
|
+
// A flatten container can still set its own cascading overrides that flow
|
|
7448
|
+
// down to its children — including its own hidden state which has no
|
|
7449
|
+
// schema path of its own.
|
|
7450
|
+
const flattenedOwn = resolveFieldOwnContext(fieldDef, rootContext);
|
|
7451
|
+
const flattenedContext = resolveDescendantContext(fieldDef, flattenedOwn);
|
|
7198
7452
|
for (const childField of normalizeFieldsArray(fieldDef.fields)) {
|
|
7199
7453
|
if (!childField.key)
|
|
7200
7454
|
continue;
|
|
7201
7455
|
const childPath = path[childField.key];
|
|
7202
7456
|
if (childPath) {
|
|
7203
|
-
mapFieldToForm(childField, childPath);
|
|
7457
|
+
mapFieldToForm(childField, childPath, flattenedContext);
|
|
7204
7458
|
}
|
|
7205
7459
|
}
|
|
7206
7460
|
continue;
|
|
@@ -7213,7 +7467,7 @@ function createSchemaFromFields(fields, registry, optionsOrValidators) {
|
|
|
7213
7467
|
// Use the new modular form mapping function
|
|
7214
7468
|
// This will progressively apply validators, logic, and schemas
|
|
7215
7469
|
// Cross-field logic is handled automatically via RootFormRegistryService
|
|
7216
|
-
mapFieldToForm(fieldDef, fieldPath);
|
|
7470
|
+
mapFieldToForm(fieldDef, fieldPath, rootContext);
|
|
7217
7471
|
}
|
|
7218
7472
|
// Apply cross-field validators using validateTree
|
|
7219
7473
|
if (crossFieldValidators && crossFieldValidators.length > 0) {
|
|
@@ -7821,49 +8075,54 @@ function flattenFields(fields, registry, options = {}) {
|
|
|
7821
8075
|
* These functions are used by both `FormStateManager` and `GroupFieldComponent`
|
|
7822
8076
|
* to compute flattened fields, field lookups, and default values from field definitions.
|
|
7823
8077
|
*
|
|
8078
|
+
* Caches are keyed on the input array's reference identity (via WeakMap) rather than
|
|
8079
|
+
* a derived content key. Different array instances — even ones with the same field
|
|
8080
|
+
* keys/types — must produce fresh results so per-field content changes (e.g. updated
|
|
8081
|
+
* select options) propagate through the form pipeline.
|
|
8082
|
+
*
|
|
7824
8083
|
* @returns Object containing memoized processing functions
|
|
7825
8084
|
*/
|
|
7826
8085
|
function createContainerFieldProcessors() {
|
|
7827
|
-
const
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
8086
|
+
const flattenCache = new WeakMap();
|
|
8087
|
+
const memoizedFlattenFields = (fields, registry) => {
|
|
8088
|
+
const cached = flattenCache.get(fields);
|
|
8089
|
+
if (cached !== undefined) {
|
|
8090
|
+
return cached;
|
|
7831
8091
|
}
|
|
7832
|
-
|
|
8092
|
+
const result = flattenFields([...fields], registry);
|
|
8093
|
+
flattenCache.set(fields, result);
|
|
8094
|
+
return result;
|
|
8095
|
+
};
|
|
8096
|
+
const flattenForRenderingCache = new WeakMap();
|
|
8097
|
+
const memoizedFlattenFieldsForRendering = (fields, registry) => {
|
|
8098
|
+
const cached = flattenForRenderingCache.get(fields);
|
|
8099
|
+
if (cached !== undefined) {
|
|
8100
|
+
return cached;
|
|
8101
|
+
}
|
|
8102
|
+
const result = flattenFields([...fields], registry, { preserveRows: true });
|
|
8103
|
+
flattenForRenderingCache.set(fields, result);
|
|
8104
|
+
return result;
|
|
8105
|
+
};
|
|
8106
|
+
const keyByCache = new WeakMap();
|
|
8107
|
+
const memoizedKeyBy = (fields) => {
|
|
8108
|
+
const cached = keyByCache.get(fields);
|
|
8109
|
+
if (cached !== undefined) {
|
|
8110
|
+
return cached;
|
|
8111
|
+
}
|
|
8112
|
+
const result = keyBy(fields, 'key');
|
|
8113
|
+
keyByCache.set(fields, result);
|
|
8114
|
+
return result;
|
|
8115
|
+
};
|
|
8116
|
+
const defaultValuesCache = new WeakMap();
|
|
8117
|
+
const memoizedDefaultValues = (fieldsById, registry) => {
|
|
8118
|
+
const cached = defaultValuesCache.get(fieldsById);
|
|
8119
|
+
if (cached !== undefined) {
|
|
8120
|
+
return cached;
|
|
8121
|
+
}
|
|
8122
|
+
const result = mapValues(fieldsById, (field) => getFieldDefaultValue(field, registry));
|
|
8123
|
+
defaultValuesCache.set(fieldsById, result);
|
|
8124
|
+
return result;
|
|
7833
8125
|
};
|
|
7834
|
-
const memoizedFlattenFields = memoize((fields, registry) => flattenFields([...fields], registry), {
|
|
7835
|
-
// registry.size is a valid cache key proxy because the field registry is populated
|
|
7836
|
-
// once at bootstrap and never mutated at runtime. If the registry were mutable,
|
|
7837
|
-
// we would need a content-based hash instead.
|
|
7838
|
-
resolver: createFieldsResolver(),
|
|
7839
|
-
maxSize: 10,
|
|
7840
|
-
});
|
|
7841
|
-
const memoizedFlattenFieldsForRendering = memoize((fields, registry) => flattenFields([...fields], registry, { preserveRows: true }), {
|
|
7842
|
-
// registry.size is a valid cache key proxy because the field registry is populated
|
|
7843
|
-
// once at bootstrap and never mutated at runtime. If the registry were mutable,
|
|
7844
|
-
// we would need a content-based hash instead.
|
|
7845
|
-
resolver: createFieldsResolver(true),
|
|
7846
|
-
maxSize: 10,
|
|
7847
|
-
});
|
|
7848
|
-
const memoizedKeyBy = memoize((fields) => keyBy(fields, 'key'), {
|
|
7849
|
-
resolver: (fields) => {
|
|
7850
|
-
let key = '';
|
|
7851
|
-
for (const f of fields) {
|
|
7852
|
-
key += f.key + '|';
|
|
7853
|
-
}
|
|
7854
|
-
return key;
|
|
7855
|
-
},
|
|
7856
|
-
maxSize: 10,
|
|
7857
|
-
});
|
|
7858
|
-
const memoizedDefaultValues = memoize((fieldsById, registry) => mapValues(fieldsById, (field) => getFieldDefaultValue(field, registry)), {
|
|
7859
|
-
// registry.size is a valid cache key proxy because the field registry is populated
|
|
7860
|
-
// once at bootstrap and never mutated at runtime.
|
|
7861
|
-
resolver: (fieldsById, registry) => {
|
|
7862
|
-
const keys = Object.keys(fieldsById).sort();
|
|
7863
|
-
return keys.join('|') + '|' + registry.size;
|
|
7864
|
-
},
|
|
7865
|
-
maxSize: 10,
|
|
7866
|
-
});
|
|
7867
8126
|
return { memoizedFlattenFields, memoizedFlattenFieldsForRendering, memoizedKeyBy, memoizedDefaultValues };
|
|
7868
8127
|
}
|
|
7869
8128
|
/**
|
|
@@ -8472,6 +8731,11 @@ function resolveFieldSync(fieldDef, context) {
|
|
|
8472
8731
|
* Reconciles previous and current resolved fields to preserve injector instances
|
|
8473
8732
|
* for fields that haven't changed type, preventing unnecessary component recreation.
|
|
8474
8733
|
*
|
|
8734
|
+
* When a field's key, component, and injector all match the previous render but the
|
|
8735
|
+
* underlying `fieldDef` has changed (e.g. updated options/props), the existing
|
|
8736
|
+
* component/injector are preserved while the new `inputs` and `renderReady` signals
|
|
8737
|
+
* are adopted so prop updates flow through to the rendered component.
|
|
8738
|
+
*
|
|
8475
8739
|
* @param prev - Previous resolved fields array
|
|
8476
8740
|
* @param curr - Current resolved fields array
|
|
8477
8741
|
* @returns Reconciled fields with preserved injectors where applicable
|
|
@@ -8481,8 +8745,20 @@ function reconcileFields(prev, curr) {
|
|
|
8481
8745
|
return curr.map((field) => {
|
|
8482
8746
|
const existing = prevMap.get(field.key);
|
|
8483
8747
|
if (existing && existing.component === field.component && existing.injector === field.injector) {
|
|
8484
|
-
|
|
8485
|
-
|
|
8748
|
+
if (existing.fieldDef === field.fieldDef) {
|
|
8749
|
+
// Truly unchanged - preserve object identity for signal stability
|
|
8750
|
+
return existing;
|
|
8751
|
+
}
|
|
8752
|
+
// Same component instance + injector, but the field definition changed.
|
|
8753
|
+
// Preserve component identity (so ngComponentOutlet keeps the same instance)
|
|
8754
|
+
// while swapping in the freshly-mapped inputs/renderReady signals so prop
|
|
8755
|
+
// updates (e.g. select options) reach the component.
|
|
8756
|
+
return {
|
|
8757
|
+
...existing,
|
|
8758
|
+
fieldDef: field.fieldDef,
|
|
8759
|
+
inputs: field.inputs,
|
|
8760
|
+
renderReady: field.renderReady,
|
|
8761
|
+
};
|
|
8486
8762
|
}
|
|
8487
8763
|
// New field, type changed, or context changed (new injector) - use new field
|
|
8488
8764
|
return field;
|
|
@@ -9746,6 +10022,7 @@ class FormStateManager {
|
|
|
9746
10022
|
return createSchemaFromFields(setup.schemaFields, setup.registry, {
|
|
9747
10023
|
crossFieldValidators: crossFieldCollection.validators,
|
|
9748
10024
|
formLevelSchema: config.schema,
|
|
10025
|
+
validateWhenHidden: this.effectiveFormOptions().validateWhenHidden,
|
|
9749
10026
|
});
|
|
9750
10027
|
}
|
|
9751
10028
|
if (config.schema) {
|
|
@@ -9849,7 +10126,12 @@ class FormStateManager {
|
|
|
9849
10126
|
/**
|
|
9850
10127
|
* Phase-aware field source that coordinates component lifecycle with form updates.
|
|
9851
10128
|
* Teardown/Applying → intersection of old/new fields; Restoring → all new fields.
|
|
9852
|
-
*
|
|
10129
|
+
*
|
|
10130
|
+
* Uses per-field reference equality to suppress spurious emissions when the same
|
|
10131
|
+
* field definitions are returned (e.g. memoized intersections during transitions)
|
|
10132
|
+
* while still triggering re-resolution when the underlying field definition
|
|
10133
|
+
* references change (e.g. a config swap that updates options/props for a
|
|
10134
|
+
* same-keyed field).
|
|
9853
10135
|
*/
|
|
9854
10136
|
fieldsSource = computed(() => {
|
|
9855
10137
|
const state = this.machine.state();
|
|
@@ -9870,7 +10152,7 @@ class FormStateManager {
|
|
|
9870
10152
|
return true;
|
|
9871
10153
|
if (a.length !== b.length)
|
|
9872
10154
|
return false;
|
|
9873
|
-
return a.every((field, i) => field
|
|
10155
|
+
return a.every((field, i) => field === b[i]);
|
|
9874
10156
|
} });
|
|
9875
10157
|
/**
|
|
9876
10158
|
* Injector for field components with FIELD_SIGNAL_CONTEXT.
|
|
@@ -14810,31 +15092,31 @@ const BUILT_IN_FIELDS = [
|
|
|
14810
15092
|
// `row` is a virtual field type: it maps to ContainerFieldComponent, and
|
|
14811
15093
|
// the rowFieldMapper injects a `{ type: 'row' }` wrapper so the container
|
|
14812
15094
|
// renders the flex/grid layout via RowWrapperComponent.
|
|
14813
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
15095
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-D4Fpr2ri.mjs'),
|
|
14814
15096
|
mapper: rowFieldMapper,
|
|
14815
15097
|
valueHandling: 'flatten',
|
|
14816
15098
|
},
|
|
14817
15099
|
{
|
|
14818
15100
|
name: 'group',
|
|
14819
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-
|
|
15101
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-BxswrhOc.mjs'),
|
|
14820
15102
|
mapper: groupFieldMapper,
|
|
14821
15103
|
valueHandling: 'include',
|
|
14822
15104
|
},
|
|
14823
15105
|
{
|
|
14824
15106
|
name: 'array',
|
|
14825
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-
|
|
15107
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-CltYGHg9.mjs'),
|
|
14826
15108
|
mapper: arrayFieldMapper,
|
|
14827
15109
|
valueHandling: 'include',
|
|
14828
15110
|
},
|
|
14829
15111
|
{
|
|
14830
15112
|
name: 'page',
|
|
14831
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-
|
|
15113
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-BHNVryIq.mjs'),
|
|
14832
15114
|
mapper: pageFieldMapper,
|
|
14833
15115
|
valueHandling: 'flatten',
|
|
14834
15116
|
},
|
|
14835
15117
|
{
|
|
14836
15118
|
name: 'text',
|
|
14837
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-
|
|
15119
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-Cf1mEKVW.mjs'),
|
|
14838
15120
|
mapper: textFieldMapper,
|
|
14839
15121
|
valueHandling: 'exclude',
|
|
14840
15122
|
...DISPLAY_FIELD_TYPES_BASE,
|
|
@@ -14846,7 +15128,7 @@ const BUILT_IN_FIELDS = [
|
|
|
14846
15128
|
},
|
|
14847
15129
|
{
|
|
14848
15130
|
name: 'container',
|
|
14849
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
15131
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-D4Fpr2ri.mjs'),
|
|
14850
15132
|
mapper: containerFieldMapper,
|
|
14851
15133
|
valueHandling: 'flatten',
|
|
14852
15134
|
},
|
|
@@ -14854,7 +15136,7 @@ const BUILT_IN_FIELDS = [
|
|
|
14854
15136
|
const BUILT_IN_WRAPPERS = [
|
|
14855
15137
|
{
|
|
14856
15138
|
wrapperName: 'css',
|
|
14857
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-
|
|
15139
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-mdfySFjX.mjs'),
|
|
14858
15140
|
},
|
|
14859
15141
|
{
|
|
14860
15142
|
wrapperName: 'row',
|
|
@@ -15164,6 +15446,48 @@ function withValueExclusionDefaults(config) {
|
|
|
15164
15446
|
return createFeature('value-exclusion', [{ provide: VALUE_EXCLUSION_DEFAULTS, useValue: resolved }]);
|
|
15165
15447
|
}
|
|
15166
15448
|
|
|
15449
|
+
/**
|
|
15450
|
+
* Configures global validation execution defaults — when validators should run
|
|
15451
|
+
* relative to a field's reactive state.
|
|
15452
|
+
*
|
|
15453
|
+
* By default, validators are **skipped** for hidden fields (`validateWhenHidden: false`),
|
|
15454
|
+
* matching what most users expect: a hidden field is inert. Use this feature to
|
|
15455
|
+
* restore the legacy behavior where hidden fields still validate.
|
|
15456
|
+
*
|
|
15457
|
+
* @example Default behavior — no feature needed
|
|
15458
|
+
* ```typescript
|
|
15459
|
+
* provideDynamicForm(...withMaterialFields())
|
|
15460
|
+
* // hidden fields skip validation
|
|
15461
|
+
* ```
|
|
15462
|
+
*
|
|
15463
|
+
* @example Restore legacy validation-on-hidden behavior
|
|
15464
|
+
* ```typescript
|
|
15465
|
+
* provideDynamicForm(
|
|
15466
|
+
* ...withMaterialFields(),
|
|
15467
|
+
* withValidationExecutionDefaults({ validateWhenHidden: true })
|
|
15468
|
+
* )
|
|
15469
|
+
* ```
|
|
15470
|
+
*
|
|
15471
|
+
* @remarks
|
|
15472
|
+
* **Precedence rules:**
|
|
15473
|
+
* 1. Per-field `FieldDef.validateWhenHidden` — wins for that field; inherits down to descendants
|
|
15474
|
+
* that don't override.
|
|
15475
|
+
* 2. Per-form `FormOptions.validateWhenHidden` — root inherited value for the form.
|
|
15476
|
+
* 3. Global `withValidationExecutionDefaults()` — fallback when no form/field setting.
|
|
15477
|
+
* 4. No feature configured — uses token default (`validateWhenHidden: false`).
|
|
15478
|
+
*
|
|
15479
|
+
* @param config - Partial override. Unspecified properties keep their defaults.
|
|
15480
|
+
* @returns A `DynamicFormFeature` that configures validation execution defaults.
|
|
15481
|
+
*
|
|
15482
|
+
* @public
|
|
15483
|
+
*/
|
|
15484
|
+
function withValidationExecutionDefaults(config) {
|
|
15485
|
+
const resolved = {
|
|
15486
|
+
validateWhenHidden: config?.validateWhenHidden ?? false,
|
|
15487
|
+
};
|
|
15488
|
+
return createFeature('validation-execution', [{ provide: VALIDATION_EXECUTION_DEFAULTS, useValue: resolved }]);
|
|
15489
|
+
}
|
|
15490
|
+
|
|
15167
15491
|
/**
|
|
15168
15492
|
* Zero-cost type carrier used in wrapper registrations.
|
|
15169
15493
|
*
|
|
@@ -15886,5 +16210,5 @@ function extractErrorParams(error) {
|
|
|
15886
16210
|
* Generated bundle index. Do not edit.
|
|
15887
16211
|
*/
|
|
15888
16212
|
|
|
15889
|
-
export { SubmitEvent as $, ARRAY_CONTEXT as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DynamicFormLogger as D, EventBus as E, FIELD_SIGNAL_CONTEXT as F, DEFAULT_VALIDATION_MESSAGES as G, DynamicForm as H, DynamicFormError as I, EventDispatcher as J, FIELD_REGISTRY as K, FORM_OPTIONS as L, FormClearEvent as M, FormResetEvent as N, INITIALIZATION_TIMEOUT_MS as O, InsertArrayItemEvent as P, MoveArrayItemEvent as Q, RootFormRegistryService as R, NextPageEvent as S, NoopLogger as T, PageChangeEvent as U, PopArrayItemEvent as V, WRAPPER_AUTO_ASSOCIATIONS as W, PrependArrayItemEvent as X, PreviousPageEvent as Y, RemoveAtIndexEvent as Z, ShiftArrayItemEvent as _, DEFAULT_WRAPPERS as a, WRAPPER_REGISTRY as a0, applyMetaToElement as a1, applyValidator as a2, applyValidators as a3, arrayEvent as a4, arrayFieldMapper as a5, baseFieldMapper as a6, buildBaseInputs as a7, containerFieldMapper as a8, createField as a9, resolveNextButtonDisabled as aA, resolveNonFieldDisabled as aB, resolveNonFieldHidden as aC, resolveSubmitButtonDisabled as aD, resolveTokens as aE, rowFieldMapper as aF, textFieldMapper as aG, toReadonlyFieldTree as aH, withEventFormValue as aI, withLoggerConfig as aJ, withPreviousValue as aK,
|
|
15890
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-
|
|
16213
|
+
export { SubmitEvent as $, ARRAY_CONTEXT as A, BUILT_IN_FIELDS as B, CONTAINER_FIELD_PROCESSORS as C, DynamicFormLogger as D, EventBus as E, FIELD_SIGNAL_CONTEXT as F, DEFAULT_VALIDATION_MESSAGES as G, DynamicForm as H, DynamicFormError as I, EventDispatcher as J, FIELD_REGISTRY as K, FORM_OPTIONS as L, FormClearEvent as M, FormResetEvent as N, INITIALIZATION_TIMEOUT_MS as O, InsertArrayItemEvent as P, MoveArrayItemEvent as Q, RootFormRegistryService as R, NextPageEvent as S, NoopLogger as T, PageChangeEvent as U, PopArrayItemEvent as V, WRAPPER_AUTO_ASSOCIATIONS as W, PrependArrayItemEvent as X, PreviousPageEvent as Y, RemoveAtIndexEvent as Z, ShiftArrayItemEvent as _, DEFAULT_WRAPPERS as a, WRAPPER_REGISTRY as a0, applyMetaToElement as a1, applyValidator as a2, applyValidators as a3, arrayEvent as a4, arrayFieldMapper as a5, baseFieldMapper as a6, buildBaseInputs as a7, containerFieldMapper as a8, createField as a9, resolveNextButtonDisabled as aA, resolveNonFieldDisabled as aB, resolveNonFieldHidden as aC, resolveSubmitButtonDisabled as aD, resolveTokens as aE, rowFieldMapper as aF, textFieldMapper as aG, toReadonlyFieldTree as aH, withEventFormValue as aI, withLoggerConfig as aJ, withPreviousValue as aK, withValidationExecutionDefaults as aL, withValueExclusionDefaults as aM, wrapperProps as aN, createWrappers as aa, evaluateNonFieldDisabled as ab, evaluateNonFieldHidden as ac, field as ad, formConfig as ae, getArrayLength as af, groupFieldMapper as ag, hasFormValue as ah, interpolateParams as ai, isArrayField as aj, isCheckedField as ak, isContainerField as al, isContainerTypedField as am, isDisplayOnlyField as an, isFormStateCondition as ao, isLeafField as ap, isPageField as aq, isRowField as ar, isSimplifiedArrayField as as, isValueBearingField as at, isValueField as au, isWrapperTypeDefinition as av, isWrappersBundle as aw, omit as ax, pageFieldMapper as ay, provideDynamicForm as az, isSameWrapperChain as b, createFieldResolutionPipe as c, derivedFromDeferred as d, createWrapperChainController as e, DfFieldOutlet as f, isEqual as g, createSchemaFromFields as h, injectFieldRegistry as i, createRenderReadySignal as j, ARRAY_TEMPLATE_REGISTRY as k, ARRAY_ITEM_ID_GENERATOR as l, mapFieldToInputs as m, getNormalizedArrayMetadata as n, getFieldValueHandling as o, getFieldDefaultValue as p, isGroupField as q, resolveWrappers as r, createArrayItemIdGenerator as s, ComponentInitializedEvent as t, DynamicTextPipe as u, validatePageNesting as v, dynamicTextToObservable as w, AppendArrayItemEvent as x, ConsoleLogger as y, DEFAULT_PROPS as z };
|
|
16214
|
+
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-QdV2vcCx.mjs.map
|