@ng-forge/dynamic-forms 0.8.0-next.4 → 0.8.0
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-array-field.component-CJ_OclbR.mjs → ng-forge-dynamic-forms-array-field.component-CWxCWVdz.mjs} +3 -3
- package/fesm2022/{ng-forge-dynamic-forms-array-field.component-CJ_OclbR.mjs.map → ng-forge-dynamic-forms-array-field.component-CWxCWVdz.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-container-field.component-DQ_OQLtx.mjs → ng-forge-dynamic-forms-container-field.component-PozYj2JC.mjs} +4 -9
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-PozYj2JC.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-container-utils-zQSTe7Jk.mjs → ng-forge-dynamic-forms-container-utils-DW_P7w-b.mjs} +4 -5
- package/fesm2022/ng-forge-dynamic-forms-container-utils-DW_P7w-b.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-2fdmbtL7.mjs → ng-forge-dynamic-forms-css-wrapper.component-DrtHSaVV.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-css-wrapper.component-2fdmbtL7.mjs.map → ng-forge-dynamic-forms-css-wrapper.component-DrtHSaVV.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-emit-initialization-pCNwBRRm.mjs → ng-forge-dynamic-forms-emit-initialization-CSx0gwFc.mjs} +3 -3
- package/fesm2022/ng-forge-dynamic-forms-emit-initialization-CSx0gwFc.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-BdLdSEHj.mjs → ng-forge-dynamic-forms-group-field.component-2yRmHAUp.mjs} +3 -3
- package/fesm2022/{ng-forge-dynamic-forms-group-field.component-BdLdSEHj.mjs.map → ng-forge-dynamic-forms-group-field.component-2yRmHAUp.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs +3 -39
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-ng-forge-dynamic-forms-BdMxsqmk.mjs → ng-forge-dynamic-forms-ng-forge-dynamic-forms-Ll4W2Z_n.mjs} +517 -396
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-Ll4W2Z_n.mjs.map +1 -0
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-C5rUo80O.mjs → ng-forge-dynamic-forms-page-field.component-B_CxX0T-.mjs} +3 -3
- package/fesm2022/{ng-forge-dynamic-forms-page-field.component-C5rUo80O.mjs.map → ng-forge-dynamic-forms-page-field.component-B_CxX0T-.mjs.map} +1 -1
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-lvuyTM1r.mjs → ng-forge-dynamic-forms-text-field.component-rJMtKX2X.mjs} +2 -2
- package/fesm2022/{ng-forge-dynamic-forms-text-field.component-lvuyTM1r.mjs.map → ng-forge-dynamic-forms-text-field.component-rJMtKX2X.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 +3 -4
- package/types/ng-forge-dynamic-forms.d.ts +68 -27
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-DQ_OQLtx.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-container-utils-zQSTe7Jk.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-emit-initialization-pCNwBRRm.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-BdMxsqmk.mjs.map +0 -1
|
@@ -1318,13 +1318,11 @@ function setupInitializationTracking(options) {
|
|
|
1318
1318
|
}
|
|
1319
1319
|
|
|
1320
1320
|
/**
|
|
1321
|
-
* Type guard
|
|
1322
|
-
*
|
|
1323
|
-
* @param field - The field to check
|
|
1324
|
-
* @returns True if the field is a HiddenField
|
|
1321
|
+
* Type guard for RowField with proper type narrowing
|
|
1325
1322
|
*/
|
|
1326
|
-
|
|
1327
|
-
|
|
1323
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type guard must accept any field type
|
|
1324
|
+
function isRowField(field) {
|
|
1325
|
+
return field.type === 'row' && 'fields' in field && Array.isArray(field.fields);
|
|
1328
1326
|
}
|
|
1329
1327
|
|
|
1330
1328
|
/**
|
|
@@ -1336,41 +1334,20 @@ function isGroupField(field) {
|
|
|
1336
1334
|
}
|
|
1337
1335
|
|
|
1338
1336
|
/**
|
|
1339
|
-
* Type guard for
|
|
1340
|
-
*/
|
|
1341
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type guard must accept any field type
|
|
1342
|
-
function isRowField(field) {
|
|
1343
|
-
return field.type === 'row' && 'fields' in field && Array.isArray(field.fields);
|
|
1344
|
-
}
|
|
1345
|
-
/**
|
|
1346
|
-
* Validates that a row field doesn't contain hidden fields.
|
|
1347
|
-
* Hidden fields are not allowed in rows because rows are for horizontal layouts,
|
|
1348
|
-
* and hidden fields don't render anything.
|
|
1337
|
+
* Type guard for ContainerField with proper type narrowing.
|
|
1349
1338
|
*
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1339
|
+
* `wrappers` is part of the type but optional in practice — many configs use
|
|
1340
|
+
* containers purely as flex/layout wrappers without any wrapper components.
|
|
1341
|
+
* Match on `type === 'container'` and the `fields` shape only, mirroring
|
|
1342
|
+
* `isPageField` / `isRowField`.
|
|
1352
1343
|
*/
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
*/
|
|
1361
|
-
function hasHiddenFields(fields) {
|
|
1362
|
-
for (const field of fields) {
|
|
1363
|
-
if (isHiddenField(field)) {
|
|
1364
|
-
return true;
|
|
1365
|
-
}
|
|
1366
|
-
// Check group fields for nested hidden fields
|
|
1367
|
-
if (isGroupField(field)) {
|
|
1368
|
-
if (hasHiddenFields(field.fields)) {
|
|
1369
|
-
return true;
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
return false;
|
|
1344
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type guard must accept any field type
|
|
1345
|
+
function isContainerTypedField(field) {
|
|
1346
|
+
return (field.type === 'container' &&
|
|
1347
|
+
'wrappers' in field &&
|
|
1348
|
+
'fields' in field &&
|
|
1349
|
+
Array.isArray(field.wrappers) &&
|
|
1350
|
+
Array.isArray(field.fields));
|
|
1374
1351
|
}
|
|
1375
1352
|
|
|
1376
1353
|
/**
|
|
@@ -1391,7 +1368,9 @@ function validatePageNesting(pageField) {
|
|
|
1391
1368
|
* Type guard to check if a field is a container with fields property
|
|
1392
1369
|
*/
|
|
1393
1370
|
function isContainerWithFields$1(field) {
|
|
1394
|
-
return (isRowField(field) || isGroupField(field)
|
|
1371
|
+
return ((isRowField(field) || isGroupField(field) || isContainerTypedField(field)) &&
|
|
1372
|
+
'fields' in field &&
|
|
1373
|
+
Array.isArray(field.fields));
|
|
1395
1374
|
}
|
|
1396
1375
|
/**
|
|
1397
1376
|
* Recursively checks if fields contain any nested page fields
|
|
@@ -1431,14 +1410,6 @@ function isSimplifiedArrayField(field) {
|
|
|
1431
1410
|
return field.type === 'array' && 'template' in field;
|
|
1432
1411
|
}
|
|
1433
1412
|
|
|
1434
|
-
/**
|
|
1435
|
-
* Type guard for ContainerField with proper type narrowing
|
|
1436
|
-
*/
|
|
1437
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Type guard must accept any field type
|
|
1438
|
-
function isContainerTypedField(field) {
|
|
1439
|
-
return field.type === 'container' && 'fields' in field && 'wrappers' in field;
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
1413
|
/**
|
|
1443
1414
|
* Type guard to check if a field definition has child fields.
|
|
1444
1415
|
* This is a looser check that works with FieldDef<unknown> without requiring RegisteredFieldTypes.
|
|
@@ -4170,9 +4141,9 @@ class PageOrchestratorComponent {
|
|
|
4170
4141
|
}
|
|
4171
4142
|
}
|
|
4172
4143
|
}
|
|
4173
|
-
`, 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-B_CxX0T-.mjs').then(m => m.default)], () => [import('./ng-forge-dynamic-forms-page-field.component-B_CxX0T-.mjs').then(m => m.default)]] });
|
|
4174
4145
|
}
|
|
4175
|
-
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-B_CxX0T-.mjs').then(m => m.default)], resolveMetadata: PageFieldComponent => ({ decorators: [{
|
|
4176
4147
|
type: Component,
|
|
4177
4148
|
args: [{ selector: 'div[page-orchestrator]', imports: [PageFieldComponent], template: `
|
|
4178
4149
|
@for (pageField of pageFields(); track pageField.key; let i = $index) {
|
|
@@ -4558,7 +4529,7 @@ function hasAnyPageFields(fields) {
|
|
|
4558
4529
|
return true;
|
|
4559
4530
|
}
|
|
4560
4531
|
// Check nested fields in container types
|
|
4561
|
-
if (
|
|
4532
|
+
if (!isPageField(field) && isContainerWithFields(field)) {
|
|
4562
4533
|
if (hasAnyPageFields(field.fields)) {
|
|
4563
4534
|
return true;
|
|
4564
4535
|
}
|
|
@@ -4581,7 +4552,7 @@ function hasNestedPageFields(fields) {
|
|
|
4581
4552
|
}
|
|
4582
4553
|
}
|
|
4583
4554
|
// Check other container types for nested pages
|
|
4584
|
-
if (
|
|
4555
|
+
if (!isPageField(field) && isContainerWithFields(field)) {
|
|
4585
4556
|
if (hasNestedPageFields(field.fields)) {
|
|
4586
4557
|
return true;
|
|
4587
4558
|
}
|
|
@@ -6318,12 +6289,22 @@ function mapFieldToForm(fieldDef, fieldPath) {
|
|
|
6318
6289
|
}
|
|
6319
6290
|
/**
|
|
6320
6291
|
* Maps children of a container field (page, row, group) to the form schema.
|
|
6292
|
+
*
|
|
6293
|
+
* Layout containers (page/row/container) flatten into `parentPath` and are
|
|
6294
|
+
* recursed through without consuming a key — same contract as `mapFieldToForm`'s
|
|
6295
|
+
* top-level layout branch, so any depth of layout-container nesting reaches its
|
|
6296
|
+
* leaf descendants. Group/array fields consume their key and look up their own
|
|
6297
|
+
* child path on `parentPath`.
|
|
6321
6298
|
*/
|
|
6322
6299
|
function mapContainerChildren(fields, parentPath) {
|
|
6323
6300
|
if (!fields)
|
|
6324
6301
|
return;
|
|
6325
6302
|
const pathRecord = parentPath;
|
|
6326
6303
|
for (const field of fields) {
|
|
6304
|
+
if (isPageField(field) || isRowField(field) || isContainerTypedField(field)) {
|
|
6305
|
+
mapContainerChildren(field.fields, parentPath);
|
|
6306
|
+
continue;
|
|
6307
|
+
}
|
|
6327
6308
|
if (!field.key)
|
|
6328
6309
|
continue;
|
|
6329
6310
|
const childPath = pathRecord[field.key];
|
|
@@ -6419,9 +6400,21 @@ function mapArrayFieldToForm(arrayField, fieldPath) {
|
|
|
6419
6400
|
maxLength(fieldPath, arrayField.maxLength);
|
|
6420
6401
|
}
|
|
6421
6402
|
// Fields can be either FieldDef (primitive) or FieldDef[] (object)
|
|
6422
|
-
|
|
6423
|
-
//
|
|
6424
|
-
//
|
|
6403
|
+
let itemDefinitions = arrayField.fields;
|
|
6404
|
+
// Simplified arrays initialized without `value` have empty `fields`; their
|
|
6405
|
+
// item shape only lives in Symbol metadata. Fall back to that template so
|
|
6406
|
+
// validators and logic declared inside it still apply when items are
|
|
6407
|
+
// materialized from a programmatically-set form value.
|
|
6408
|
+
if (!itemDefinitions || itemDefinitions.length === 0) {
|
|
6409
|
+
const metadataTemplate = getNormalizedArrayMetadata(arrayField)?.template;
|
|
6410
|
+
if (metadataTemplate) {
|
|
6411
|
+
itemDefinitions = [
|
|
6412
|
+
Array.isArray(metadataTemplate) ? [...metadataTemplate] : metadataTemplate,
|
|
6413
|
+
];
|
|
6414
|
+
}
|
|
6415
|
+
}
|
|
6416
|
+
// Empty array with no template metadata - items will be added via buttons with
|
|
6417
|
+
// their own templates. Create a minimal schema that just accepts any value.
|
|
6425
6418
|
if (!itemDefinitions || itemDefinitions.length === 0) {
|
|
6426
6419
|
const emptyItemSchema = schema(() => {
|
|
6427
6420
|
// No fields to map - items will be added dynamically via buttons
|
|
@@ -6433,14 +6426,22 @@ function mapArrayFieldToForm(arrayField, fieldPath) {
|
|
|
6433
6426
|
let hasPrimitiveItems = false;
|
|
6434
6427
|
const allObjectFields = [];
|
|
6435
6428
|
for (const itemDef of itemDefinitions) {
|
|
6436
|
-
if (
|
|
6437
|
-
// Primitive item: single FieldDef
|
|
6438
|
-
hasPrimitiveItems = true;
|
|
6439
|
-
}
|
|
6440
|
-
else {
|
|
6429
|
+
if (Array.isArray(itemDef)) {
|
|
6441
6430
|
// Object item: collect fields for superset schema
|
|
6442
6431
|
collectFieldsFromObjectItem(itemDef, allObjectFields);
|
|
6443
6432
|
}
|
|
6433
|
+
else if (isPageField(itemDef) ||
|
|
6434
|
+
isRowField(itemDef) ||
|
|
6435
|
+
isContainerTypedField(itemDef)) {
|
|
6436
|
+
// Layout container as a single-template item: its descendants describe
|
|
6437
|
+
// the item's object shape. Hand it to the object-item collector wrapped
|
|
6438
|
+
// as a 1-element array so the structured-item mapping path runs.
|
|
6439
|
+
collectFieldsFromObjectItem([itemDef], allObjectFields);
|
|
6440
|
+
}
|
|
6441
|
+
else {
|
|
6442
|
+
// Primitive item: single FieldDef
|
|
6443
|
+
hasPrimitiveItems = true;
|
|
6444
|
+
}
|
|
6444
6445
|
}
|
|
6445
6446
|
// For pure primitive arrays, use a simple schema
|
|
6446
6447
|
if (hasPrimitiveItems && allObjectFields.length === 0) {
|
|
@@ -6580,7 +6581,7 @@ function getFieldDefaultValue(field, registry) {
|
|
|
6580
6581
|
if (valueHandling === 'exclude') {
|
|
6581
6582
|
return undefined;
|
|
6582
6583
|
}
|
|
6583
|
-
// Flatten fields (row/page) return flattened object of children's values
|
|
6584
|
+
// Flatten fields (row/page/container) return flattened object of children's values
|
|
6584
6585
|
// This is used by array fields to get the template structure
|
|
6585
6586
|
// Note: fieldsToDefaultValues will skip these at top-level since they have keys and return objects
|
|
6586
6587
|
if (valueHandling === 'flatten' && 'fields' in field && field.fields) {
|
|
@@ -6588,11 +6589,18 @@ function getFieldDefaultValue(field, registry) {
|
|
|
6588
6589
|
// Collect only fields that contribute values (exclude buttons, text, etc.)
|
|
6589
6590
|
const flattenedValues = {};
|
|
6590
6591
|
for (const childField of childFields) {
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6592
|
+
const childValueHandling = getFieldValueHandling(childField.type, registry);
|
|
6593
|
+
if (childValueHandling === 'exclude')
|
|
6594
|
+
continue;
|
|
6595
|
+
const childValue = getFieldDefaultValue(childField, registry);
|
|
6596
|
+
if (childValue === undefined)
|
|
6597
|
+
continue;
|
|
6598
|
+
if (childValueHandling === 'flatten' && childValue && typeof childValue === 'object' && !Array.isArray(childValue)) {
|
|
6599
|
+
// Nested flatten field — its value is already flat; spread it up.
|
|
6600
|
+
Object.assign(flattenedValues, childValue);
|
|
6601
|
+
}
|
|
6602
|
+
else if ('key' in childField && childField.key) {
|
|
6603
|
+
flattenedValues[childField.key] = childValue;
|
|
6596
6604
|
}
|
|
6597
6605
|
}
|
|
6598
6606
|
// Return flattened object if there are any values, otherwise undefined
|
|
@@ -6607,20 +6615,17 @@ function getFieldDefaultValue(field, registry) {
|
|
|
6607
6615
|
const groupDefaults = {};
|
|
6608
6616
|
for (const childField of fields) {
|
|
6609
6617
|
const childValueHandling = getFieldValueHandling(childField.type, registry);
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
}
|
|
6619
|
-
}
|
|
6618
|
+
if (childValueHandling === 'exclude')
|
|
6619
|
+
continue;
|
|
6620
|
+
const childValue = getFieldDefaultValue(childField, registry);
|
|
6621
|
+
if (childValueHandling === 'flatten') {
|
|
6622
|
+
// Flatten container/row/page values into the group (they are presentational containers).
|
|
6623
|
+
// The recursive call already returns a flat object — spread it directly.
|
|
6624
|
+
if (childValue && typeof childValue === 'object' && !Array.isArray(childValue)) {
|
|
6625
|
+
Object.assign(groupDefaults, childValue);
|
|
6620
6626
|
}
|
|
6621
6627
|
}
|
|
6622
6628
|
else if ('key' in childField && childField.key) {
|
|
6623
|
-
const childValue = getFieldDefaultValue(childField, registry);
|
|
6624
6629
|
if (childValue !== undefined) {
|
|
6625
6630
|
groupDefaults[childField.key] = childValue;
|
|
6626
6631
|
}
|
|
@@ -7438,10 +7443,10 @@ function createEmptyCollection() {
|
|
|
7438
7443
|
/** Traverses field definitions and collects cross-field entries (validators, logic, schemas). */
|
|
7439
7444
|
function collectCrossFieldEntries(fields) {
|
|
7440
7445
|
const collection = createEmptyCollection();
|
|
7441
|
-
traverseFields
|
|
7446
|
+
traverseFields(fields, collection, '');
|
|
7442
7447
|
return collection;
|
|
7443
7448
|
}
|
|
7444
|
-
function traverseFields
|
|
7449
|
+
function traverseFields(fields, collection, pathPrefix) {
|
|
7445
7450
|
for (const field of fields) {
|
|
7446
7451
|
collectFromField$2(field, collection, pathPrefix);
|
|
7447
7452
|
// Recursively process container fields (page, row, group, array)
|
|
@@ -7453,7 +7458,7 @@ function traverseFields$2(fields, collection, pathPrefix) {
|
|
|
7453
7458
|
// be built for them.
|
|
7454
7459
|
const groupKey = isGroupField(field) ? field.key : undefined;
|
|
7455
7460
|
const childPrefix = groupKey ? (pathPrefix ? `${pathPrefix}.${groupKey}` : groupKey) : pathPrefix;
|
|
7456
|
-
traverseFields
|
|
7461
|
+
traverseFields(normalizeFieldsArray(field.fields), collection, childPrefix);
|
|
7457
7462
|
}
|
|
7458
7463
|
}
|
|
7459
7464
|
}
|
|
@@ -8168,6 +8173,29 @@ function buildPropertyOverrideKey(arrayKey, index, fieldKey) {
|
|
|
8168
8173
|
return fieldKey;
|
|
8169
8174
|
}
|
|
8170
8175
|
|
|
8176
|
+
/**
|
|
8177
|
+
* Returns true if the field has any property-derivation logic entries
|
|
8178
|
+
* (type: 'derivation' with a non-empty targetProperty). Mirrors the inclusion
|
|
8179
|
+
* rule used by `collectPropertyDerivations`, so the mapper and orchestrator
|
|
8180
|
+
* agree on which fields participate in property overrides — without depending
|
|
8181
|
+
* on whether the orchestrator's registration effect has run yet (which it
|
|
8182
|
+
* hasn't on the resolveFieldSync path with a warm COMPONENT_CACHE).
|
|
8183
|
+
*/
|
|
8184
|
+
function fieldHasPropertyDerivations(fieldDef) {
|
|
8185
|
+
// Match the collector's keyless guard (property-derivation-collector.ts):
|
|
8186
|
+
// keyless fields are never registered in the store, so the mapper must skip
|
|
8187
|
+
// them too — otherwise getOverrides() would be called with an undefined key.
|
|
8188
|
+
if (!fieldDef.key)
|
|
8189
|
+
return false;
|
|
8190
|
+
const logic = fieldDef.logic;
|
|
8191
|
+
if (!logic || logic.length === 0)
|
|
8192
|
+
return false;
|
|
8193
|
+
for (const entry of logic) {
|
|
8194
|
+
if (isDerivationLogicConfig(entry) && hasTargetProperty(entry))
|
|
8195
|
+
return true;
|
|
8196
|
+
}
|
|
8197
|
+
return false;
|
|
8198
|
+
}
|
|
8171
8199
|
/**
|
|
8172
8200
|
* Merges forwarded props into meta, with meta taking precedence.
|
|
8173
8201
|
*
|
|
@@ -8269,11 +8297,13 @@ function mapFieldToInputs(fieldDef, fieldRegistry) {
|
|
|
8269
8297
|
// Check that arrayContext exists and has a valid index signal (guards against mock injectors)
|
|
8270
8298
|
const indexSignal = arrayContext?.index;
|
|
8271
8299
|
const hasArrayContext = indexSignal !== undefined && isSignal(indexSignal);
|
|
8272
|
-
// Fast-path check for property overrides: only fields
|
|
8273
|
-
// enter the computed() wrapper
|
|
8274
|
-
//
|
|
8275
|
-
//
|
|
8276
|
-
|
|
8300
|
+
// Fast-path check for property overrides: only fields whose definition declares
|
|
8301
|
+
// a property-derivation enter the computed() wrapper. We inspect the FieldDef
|
|
8302
|
+
// directly rather than asking the store, because the store's registration
|
|
8303
|
+
// effect may not have fired yet when the mapper runs in the resolveFieldSync
|
|
8304
|
+
// path (warm COMPONENT_CACHE). Both the mapper and the orchestrator's
|
|
8305
|
+
// collector apply the same inclusion rule, so the answers always agree.
|
|
8306
|
+
const hasOverrides = fieldHasPropertyDerivations(fieldDef);
|
|
8277
8307
|
// Fast path: no transformations needed
|
|
8278
8308
|
if (!hasPropsForwarding && !hasArrayContext && !hasOverrides) {
|
|
8279
8309
|
return mapperResult;
|
|
@@ -8427,8 +8457,7 @@ function createFieldResolutionPipe(getContext) {
|
|
|
8427
8457
|
* Comprehensive form configuration validator that checks:
|
|
8428
8458
|
* 1. Form mode consistency (paged vs non-paged)
|
|
8429
8459
|
* 2. Page nesting rules
|
|
8430
|
-
* 3.
|
|
8431
|
-
* 4. Field placement constraints
|
|
8460
|
+
* 3. Field placement constraints
|
|
8432
8461
|
*/
|
|
8433
8462
|
class FormModeValidator {
|
|
8434
8463
|
/**
|
|
@@ -8495,57 +8524,6 @@ class FormModeValidator {
|
|
|
8495
8524
|
}
|
|
8496
8525
|
}
|
|
8497
8526
|
}
|
|
8498
|
-
// Warn about hidden fields in rows (they work but don't render, which may be confusing)
|
|
8499
|
-
const rowWarnings = this.collectRowHiddenFieldWarnings(fields);
|
|
8500
|
-
warnings.push(...rowWarnings);
|
|
8501
|
-
return warnings;
|
|
8502
|
-
}
|
|
8503
|
-
/**
|
|
8504
|
-
* Recursively collects warnings for hidden fields inside rows
|
|
8505
|
-
* @param fields The form field definitions to check
|
|
8506
|
-
* @param path Current path for warning messages
|
|
8507
|
-
* @returns Array of warning messages
|
|
8508
|
-
*/
|
|
8509
|
-
static collectRowHiddenFieldWarnings(fields, path = '') {
|
|
8510
|
-
if (!fields) {
|
|
8511
|
-
return [];
|
|
8512
|
-
}
|
|
8513
|
-
const warnings = [];
|
|
8514
|
-
for (let i = 0; i < fields.length; i++) {
|
|
8515
|
-
const field = fields[i];
|
|
8516
|
-
const fieldPath = path ? `${path}.fields[${i}]` : `fields[${i}]`;
|
|
8517
|
-
const fieldKey = field.key || 'unknown';
|
|
8518
|
-
if (isRowField(field)) {
|
|
8519
|
-
if (!validateRowNesting(field)) {
|
|
8520
|
-
warnings.push(`Row field at ${fieldPath} (key: "${fieldKey}") contains hidden fields. ` +
|
|
8521
|
-
`Hidden fields in rows don't render anything - consider placing them outside the row.`);
|
|
8522
|
-
}
|
|
8523
|
-
// Continue checking nested rows within the row's children
|
|
8524
|
-
warnings.push(...this.collectRowHiddenFieldWarnings(field.fields, fieldPath));
|
|
8525
|
-
}
|
|
8526
|
-
else if (isPageField(field)) {
|
|
8527
|
-
// Check rows within pages
|
|
8528
|
-
warnings.push(...this.collectRowHiddenFieldWarnings(field.fields, fieldPath));
|
|
8529
|
-
}
|
|
8530
|
-
else if (isGroupField(field)) {
|
|
8531
|
-
// Check rows within groups
|
|
8532
|
-
warnings.push(...this.collectRowHiddenFieldWarnings(field.fields, fieldPath));
|
|
8533
|
-
}
|
|
8534
|
-
else if (isSimplifiedArrayField(field)) {
|
|
8535
|
-
// Simplified arrays are normalized later; check template fields if present
|
|
8536
|
-
const templateFields = Array.isArray(field.template) ? [...field.template] : [field.template];
|
|
8537
|
-
warnings.push(...this.collectRowHiddenFieldWarnings(templateFields, fieldPath));
|
|
8538
|
-
}
|
|
8539
|
-
else if (isArrayField(field)) {
|
|
8540
|
-
// Check rows within array templates — items may be single FieldDef (primitive) or FieldDef[] (object)
|
|
8541
|
-
const itemTemplates = field.fields;
|
|
8542
|
-
for (let j = 0; j < itemTemplates.length; j++) {
|
|
8543
|
-
const itemFields = itemTemplates[j];
|
|
8544
|
-
const fieldsArray = Array.isArray(itemFields) ? [...itemFields] : [itemFields];
|
|
8545
|
-
warnings.push(...this.collectRowHiddenFieldWarnings(fieldsArray, `${fieldPath}.fields[${j}]`));
|
|
8546
|
-
}
|
|
8547
|
-
}
|
|
8548
|
-
}
|
|
8549
8527
|
return warnings;
|
|
8550
8528
|
}
|
|
8551
8529
|
}
|
|
@@ -10532,6 +10510,71 @@ function parseDerivationKey(key) {
|
|
|
10532
10510
|
return { fieldKey: key };
|
|
10533
10511
|
}
|
|
10534
10512
|
|
|
10513
|
+
/**
|
|
10514
|
+
* Single dispatch over `value` / `expression` / `functionName` shared by both
|
|
10515
|
+
* the value-derivation and property-derivation applicators.
|
|
10516
|
+
*
|
|
10517
|
+
* Throws {@link DynamicFormError} on missing function or missing value source
|
|
10518
|
+
* so both pipelines surface the library's standard error type.
|
|
10519
|
+
*
|
|
10520
|
+
* @internal
|
|
10521
|
+
*/
|
|
10522
|
+
function computeValueFromEntry(entry, evalContext, options) {
|
|
10523
|
+
if (entry.value !== undefined) {
|
|
10524
|
+
return entry.value;
|
|
10525
|
+
}
|
|
10526
|
+
if (entry.expression) {
|
|
10527
|
+
return ExpressionParser.evaluate(entry.expression, evalContext);
|
|
10528
|
+
}
|
|
10529
|
+
if (entry.functionName) {
|
|
10530
|
+
const fn = options.derivationFunctions?.[entry.functionName];
|
|
10531
|
+
if (!fn) {
|
|
10532
|
+
const kind = options.functionKind ?? 'Derivation function';
|
|
10533
|
+
throw new DynamicFormError(`${kind} '${entry.functionName}' not found in customFnConfig.derivations`);
|
|
10534
|
+
}
|
|
10535
|
+
return fn(evalContext);
|
|
10536
|
+
}
|
|
10537
|
+
throw new DynamicFormError(`Derivation for ${options.subject} has no value source. Specify 'value', 'expression', or 'functionName'.`);
|
|
10538
|
+
}
|
|
10539
|
+
|
|
10540
|
+
/**
|
|
10541
|
+
* Returns the path to the parent of the given field path. Drops the last
|
|
10542
|
+
* `.`-delimited segment. Returns `undefined` when the field has no parent
|
|
10543
|
+
* in scope (single-segment root key).
|
|
10544
|
+
*
|
|
10545
|
+
* - `'address.state'` → `'address'`
|
|
10546
|
+
* - `'org.address.state'` → `'org.address'`
|
|
10547
|
+
* - `'state'` → `undefined`
|
|
10548
|
+
*
|
|
10549
|
+
* Used by the non-array evaluation context to populate `groupValue` from
|
|
10550
|
+
* the field's parent group on `formValue`.
|
|
10551
|
+
*
|
|
10552
|
+
* @internal
|
|
10553
|
+
*/
|
|
10554
|
+
function getParentPathInScope(fieldKey) {
|
|
10555
|
+
const lastDot = fieldKey.lastIndexOf('.');
|
|
10556
|
+
if (lastDot <= 0)
|
|
10557
|
+
return undefined;
|
|
10558
|
+
return fieldKey.slice(0, lastDot);
|
|
10559
|
+
}
|
|
10560
|
+
/**
|
|
10561
|
+
* Resolves the per-array-item scope shared by both derivation applicators.
|
|
10562
|
+
*
|
|
10563
|
+
* Both `derivation-applicator.ts` and `property-derivation-applicator.ts`
|
|
10564
|
+
* need the same fan-out from `(pathInfo, arrayItem)` to `fieldValue`,
|
|
10565
|
+
* `groupValue`, and `relativePath` when building an evaluation context for
|
|
10566
|
+
* an array item. Centralising here keeps the two pipelines from drifting.
|
|
10567
|
+
*
|
|
10568
|
+
* @internal
|
|
10569
|
+
*/
|
|
10570
|
+
function resolveArrayItemScope(pathInfo, arrayItem) {
|
|
10571
|
+
const relativePath = pathInfo.isArrayPath ? pathInfo.relativePath : '';
|
|
10572
|
+
const innerParentPath = relativePath.includes('.') ? relativePath.slice(0, relativePath.lastIndexOf('.')) : '';
|
|
10573
|
+
const groupValue = innerParentPath ? getNestedValue(arrayItem, innerParentPath) : arrayItem;
|
|
10574
|
+
const fieldValue = relativePath ? getNestedValue(arrayItem, relativePath) : arrayItem;
|
|
10575
|
+
return { relativePath, groupValue, fieldValue };
|
|
10576
|
+
}
|
|
10577
|
+
|
|
10535
10578
|
/**
|
|
10536
10579
|
* Centralized constants for the derivation system.
|
|
10537
10580
|
*
|
|
@@ -11101,10 +11144,15 @@ function createEvaluationContext$1(entry, formValue, context, chainContext) {
|
|
|
11101
11144
|
// Create field state snapshot for this specific field (non-reactive)
|
|
11102
11145
|
const fieldAccessor = context.rootForm[entry.fieldKey];
|
|
11103
11146
|
const fieldState = fieldAccessor ? readFieldStateInfo(fieldAccessor, false) : undefined;
|
|
11147
|
+
// Parent group value: the absolute path with the trailing field segment
|
|
11148
|
+
// dropped. `undefined` when the field is at form root.
|
|
11149
|
+
const parentPath = getParentPathInScope(entry.fieldKey);
|
|
11150
|
+
const groupValue = parentPath ? getNestedValue(formValue, parentPath) : undefined;
|
|
11104
11151
|
return {
|
|
11105
11152
|
fieldValue,
|
|
11106
11153
|
formValue,
|
|
11107
11154
|
fieldPath: entry.fieldKey,
|
|
11155
|
+
groupValue,
|
|
11108
11156
|
customFunctions: context.customFunctions,
|
|
11109
11157
|
externalData: context.externalData,
|
|
11110
11158
|
logger: context.logger,
|
|
@@ -11143,10 +11191,12 @@ function createArrayItemEvaluationContext$1(entry, arrayItem, rootFormValue, ite
|
|
|
11143
11191
|
}
|
|
11144
11192
|
}
|
|
11145
11193
|
}
|
|
11194
|
+
const { relativePath, groupValue, fieldValue } = resolveArrayItemScope(pathInfo, arrayItem);
|
|
11146
11195
|
return {
|
|
11147
|
-
fieldValue
|
|
11196
|
+
fieldValue,
|
|
11148
11197
|
formValue: arrayItem,
|
|
11149
|
-
fieldPath: `${arrayPath}.${itemIndex}`,
|
|
11198
|
+
fieldPath: relativePath ? `${arrayPath}.${itemIndex}.${relativePath}` : `${arrayPath}.${itemIndex}`,
|
|
11199
|
+
groupValue,
|
|
11150
11200
|
customFunctions: context.customFunctions,
|
|
11151
11201
|
externalData: context.externalData,
|
|
11152
11202
|
logger: context.logger,
|
|
@@ -11175,24 +11225,10 @@ function evaluateDerivationCondition(condition, context) {
|
|
|
11175
11225
|
* @internal
|
|
11176
11226
|
*/
|
|
11177
11227
|
function computeDerivedValue(entry, evalContext, applicatorContext) {
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
}
|
|
11182
|
-
// Expression
|
|
11183
|
-
if (entry.expression) {
|
|
11184
|
-
return ExpressionParser.evaluate(entry.expression, evalContext);
|
|
11185
|
-
}
|
|
11186
|
-
// Custom function
|
|
11187
|
-
if (entry.functionName) {
|
|
11188
|
-
const fn = applicatorContext.derivationFunctions?.[entry.functionName];
|
|
11189
|
-
if (!fn) {
|
|
11190
|
-
throw new Error(`Derivation function '${entry.functionName}' not found in customFnConfig.derivations`);
|
|
11191
|
-
}
|
|
11192
|
-
return fn(evalContext);
|
|
11193
|
-
}
|
|
11194
|
-
// No value source specified
|
|
11195
|
-
throw new Error(`Derivation for ${entry.fieldKey} has no value source. ` + `Specify 'value', 'expression', or 'functionName'.`);
|
|
11228
|
+
return computeValueFromEntry(entry, evalContext, {
|
|
11229
|
+
derivationFunctions: applicatorContext.derivationFunctions,
|
|
11230
|
+
subject: entry.fieldKey,
|
|
11231
|
+
});
|
|
11196
11232
|
}
|
|
11197
11233
|
/**
|
|
11198
11234
|
* Error message prefix for derivation-related errors.
|
|
@@ -11442,6 +11478,134 @@ function topologicalSort(entries) {
|
|
|
11442
11478
|
return sorted;
|
|
11443
11479
|
}
|
|
11444
11480
|
|
|
11481
|
+
/**
|
|
11482
|
+
* Extracts the union of field dependencies declared by a derivation config.
|
|
11483
|
+
*
|
|
11484
|
+
* Combines:
|
|
11485
|
+
* - Explicit `dependsOn` (when provided, it takes precedence over auto-detection)
|
|
11486
|
+
* - Expression-detected dependencies (only used when `dependsOn` is empty)
|
|
11487
|
+
* - `'*'` wildcard for `functionName` (only when `dependsOn` is empty)
|
|
11488
|
+
* - Condition expression dependencies (always included)
|
|
11489
|
+
*
|
|
11490
|
+
* @internal
|
|
11491
|
+
*/
|
|
11492
|
+
function extractDependenciesFromConfig(config) {
|
|
11493
|
+
const deps = new Set();
|
|
11494
|
+
if (config.dependsOn && config.dependsOn.length > 0) {
|
|
11495
|
+
config.dependsOn.forEach((dep) => deps.add(dep));
|
|
11496
|
+
}
|
|
11497
|
+
else {
|
|
11498
|
+
if (config.expression) {
|
|
11499
|
+
extractStringDependencies(config.expression).forEach((dep) => deps.add(dep));
|
|
11500
|
+
}
|
|
11501
|
+
if (config.functionName) {
|
|
11502
|
+
deps.add('*');
|
|
11503
|
+
}
|
|
11504
|
+
}
|
|
11505
|
+
if (config.condition && config.condition !== true) {
|
|
11506
|
+
extractExpressionDependencies(config.condition).forEach((dep) => deps.add(dep));
|
|
11507
|
+
}
|
|
11508
|
+
return Array.from(deps);
|
|
11509
|
+
}
|
|
11510
|
+
|
|
11511
|
+
/**
|
|
11512
|
+
* Recursively walks a field-definition tree, invoking `visitor` for each field.
|
|
11513
|
+
*
|
|
11514
|
+
* Owns the subtleties shared across derivation collectors:
|
|
11515
|
+
* - Detects container fields via `hasChildFields`.
|
|
11516
|
+
* - Falls back to the simplified-array Symbol-metadata template when an array
|
|
11517
|
+
* field has empty `fields` (the case for arrays initialized without a
|
|
11518
|
+
* starting `value` — only the metadata template carries their item shape).
|
|
11519
|
+
* - Flattens primitive (FieldDef) and object (FieldDef[]) array items into a
|
|
11520
|
+
* single child list before recursing.
|
|
11521
|
+
*
|
|
11522
|
+
* Callers customize per-boundary context via {@link FieldTraversalHooks}: e.g.,
|
|
11523
|
+
* the value-derivation collector tracks an array path AND a group path (with
|
|
11524
|
+
* the group path reset at array boundaries), while the property-derivation
|
|
11525
|
+
* collector tracks only the array path.
|
|
11526
|
+
*
|
|
11527
|
+
* @internal
|
|
11528
|
+
*/
|
|
11529
|
+
function traverseFieldsWithContext(fields, context, visitor, hooks) {
|
|
11530
|
+
for (const field of fields) {
|
|
11531
|
+
visitor(field, context);
|
|
11532
|
+
if (!hasChildFields(field))
|
|
11533
|
+
continue;
|
|
11534
|
+
if (field.type === 'array') {
|
|
11535
|
+
const childContext = mergeContext(context, hooks?.onArrayChild?.(context, field));
|
|
11536
|
+
const arrayChildren = collectArrayChildren(field);
|
|
11537
|
+
traverseFieldsWithContext(arrayChildren, childContext, visitor, hooks);
|
|
11538
|
+
continue;
|
|
11539
|
+
}
|
|
11540
|
+
const overrides = field.type === 'group' ? hooks?.onGroupChild?.(context, field) : hooks?.onLayoutChild?.(context, field);
|
|
11541
|
+
const childContext = mergeContext(context, overrides);
|
|
11542
|
+
traverseFieldsWithContext(normalizeFieldsArray(field.fields), childContext, visitor, hooks);
|
|
11543
|
+
}
|
|
11544
|
+
}
|
|
11545
|
+
/**
|
|
11546
|
+
* Returns the flattened list of children for an array field, falling back
|
|
11547
|
+
* to the Symbol metadata template when `fields` is empty.
|
|
11548
|
+
*
|
|
11549
|
+
* Caller must ensure `field` is a container (passes `hasChildFields`); this
|
|
11550
|
+
* function reads `field.fields` directly and would error otherwise.
|
|
11551
|
+
*
|
|
11552
|
+
* @internal
|
|
11553
|
+
*/
|
|
11554
|
+
function collectArrayChildren(field) {
|
|
11555
|
+
let arrayItems = normalizeFieldsArray(field.fields);
|
|
11556
|
+
if (arrayItems.length === 0) {
|
|
11557
|
+
const metadataTemplate = getNormalizedArrayMetadata(field)?.template;
|
|
11558
|
+
if (metadataTemplate) {
|
|
11559
|
+
arrayItems = [
|
|
11560
|
+
Array.isArray(metadataTemplate) ? [...metadataTemplate] : metadataTemplate,
|
|
11561
|
+
];
|
|
11562
|
+
}
|
|
11563
|
+
}
|
|
11564
|
+
const flattened = [];
|
|
11565
|
+
for (const item of arrayItems) {
|
|
11566
|
+
if (Array.isArray(item)) {
|
|
11567
|
+
flattened.push(...item);
|
|
11568
|
+
}
|
|
11569
|
+
else {
|
|
11570
|
+
flattened.push(item);
|
|
11571
|
+
}
|
|
11572
|
+
}
|
|
11573
|
+
return flattened;
|
|
11574
|
+
}
|
|
11575
|
+
/** @internal */
|
|
11576
|
+
function mergeContext(base, overrides) {
|
|
11577
|
+
return overrides ? { ...base, ...overrides } : { ...base };
|
|
11578
|
+
}
|
|
11579
|
+
|
|
11580
|
+
/**
|
|
11581
|
+
* Token in `dependsOn` arrays that resolves to the current field's own
|
|
11582
|
+
* absolute path at collection time. Useful for self-derivations on fields
|
|
11583
|
+
* nested under groups, where the absolute path depends on ancestor keys
|
|
11584
|
+
* the config author may not know (e.g. factory-built field shapes).
|
|
11585
|
+
*
|
|
11586
|
+
* @public
|
|
11587
|
+
*/
|
|
11588
|
+
const SELF_DEPENDENCY_TOKEN = '$self';
|
|
11589
|
+
/**
|
|
11590
|
+
* Token in `dependsOn` arrays that resolves to the absolute path of the
|
|
11591
|
+
* field's nearest parent container (group or array) at collection time.
|
|
11592
|
+
*
|
|
11593
|
+
* Useful when a derivation should fire whenever any sibling under the
|
|
11594
|
+
* same parent container changes (e.g. "compute when anything in the
|
|
11595
|
+
* address group is edited") without enumerating each sibling explicitly.
|
|
11596
|
+
*
|
|
11597
|
+
* Resolution rules:
|
|
11598
|
+
* - Inside group(s) only: resolves to the dotted ancestor group path
|
|
11599
|
+
* (e.g. `'address'` or `'org.address'`).
|
|
11600
|
+
* - Inside an array (no group between): resolves to the array key
|
|
11601
|
+
* (e.g. `'items'`). Fires whenever any item changes.
|
|
11602
|
+
* - Inside group(s) inside an array: resolves to the array placeholder
|
|
11603
|
+
* form including the inner groups (e.g. `'items.$.address'`).
|
|
11604
|
+
* - At form root: throws `DynamicFormError` (no parent to target).
|
|
11605
|
+
*
|
|
11606
|
+
* @public
|
|
11607
|
+
*/
|
|
11608
|
+
const GROUP_DEPENDENCY_TOKEN = '$group';
|
|
11445
11609
|
/**
|
|
11446
11610
|
* Collects all derivation entries from field definitions.
|
|
11447
11611
|
*
|
|
@@ -11479,8 +11643,21 @@ function topologicalSort(entries) {
|
|
|
11479
11643
|
*/
|
|
11480
11644
|
function collectDerivations(fields) {
|
|
11481
11645
|
const entries = [];
|
|
11482
|
-
|
|
11483
|
-
|
|
11646
|
+
traverseFieldsWithContext(fields, {}, (field, context) => collectFromField$1(field, entries, context), {
|
|
11647
|
+
// Array boundary: the array placeholder path takes over and any
|
|
11648
|
+
// ancestor groupPath is reset for descendants (entries inside
|
|
11649
|
+
// array items use `arrayPath.$.fieldKey` form).
|
|
11650
|
+
onArrayChild: (_, field) => ({ arrayPath: field.key, groupPath: undefined }),
|
|
11651
|
+
onGroupChild: (parent, field) => {
|
|
11652
|
+
// Group establishes a model boundary; descend with the field's
|
|
11653
|
+
// key appended to the ancestor groupPath. Groups without a key
|
|
11654
|
+
// (rare) act like layout containers — leave context unchanged.
|
|
11655
|
+
if (!field.key)
|
|
11656
|
+
return {};
|
|
11657
|
+
return { groupPath: parent.groupPath ? `${parent.groupPath}.${field.key}` : field.key };
|
|
11658
|
+
},
|
|
11659
|
+
// Layout containers (page, row, container) leave context unchanged.
|
|
11660
|
+
});
|
|
11484
11661
|
// Sort entries in topological order for efficient processing.
|
|
11485
11662
|
// This ensures derivations are processed in dependency order,
|
|
11486
11663
|
// reducing the number of iterations needed in the applicator.
|
|
@@ -11488,40 +11665,83 @@ function collectDerivations(fields) {
|
|
|
11488
11665
|
return { entries: sortedEntries };
|
|
11489
11666
|
}
|
|
11490
11667
|
/**
|
|
11491
|
-
*
|
|
11668
|
+
* Builds the absolute field path for a derivation entry from the field's
|
|
11669
|
+
* key and the current collection context.
|
|
11670
|
+
*
|
|
11671
|
+
* - Inside an array AND inside one or more groups (groups nested under
|
|
11672
|
+
* the array item): combines both as `${arrayPath}.$.${groupPath}.${fieldKey}`.
|
|
11673
|
+
* - Inside an array only: returns `${arrayPath}.$.${fieldKey}`.
|
|
11674
|
+
* - Inside one or more groups: returns `${groupPath}.${fieldKey}`.
|
|
11675
|
+
* - Otherwise returns `fieldKey` unchanged.
|
|
11676
|
+
*
|
|
11677
|
+
* `groupPath` is reset when entering an array (each array item is its own
|
|
11678
|
+
* model root); ancestor groups OUTSIDE the array are not part of the
|
|
11679
|
+
* entry path. Groups INSIDE the array are tracked via `groupPath` and
|
|
11680
|
+
* combined here.
|
|
11492
11681
|
*
|
|
11493
11682
|
* @internal
|
|
11494
11683
|
*/
|
|
11495
|
-
function
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
|
|
11512
|
-
|
|
11513
|
-
|
|
11514
|
-
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
11684
|
+
function buildEffectiveFieldKey(fieldKey, context) {
|
|
11685
|
+
if (context.arrayPath && context.groupPath) {
|
|
11686
|
+
return `${context.arrayPath}.$.${context.groupPath}.${fieldKey}`;
|
|
11687
|
+
}
|
|
11688
|
+
if (context.arrayPath) {
|
|
11689
|
+
return `${context.arrayPath}.$.${fieldKey}`;
|
|
11690
|
+
}
|
|
11691
|
+
if (context.groupPath) {
|
|
11692
|
+
return `${context.groupPath}.${fieldKey}`;
|
|
11693
|
+
}
|
|
11694
|
+
return fieldKey;
|
|
11695
|
+
}
|
|
11696
|
+
/**
|
|
11697
|
+
* Resolves the absolute path of the field's nearest parent container
|
|
11698
|
+
* (group or array). Returns `undefined` when the field is at the form
|
|
11699
|
+
* root with no parent container.
|
|
11700
|
+
*
|
|
11701
|
+
* @internal
|
|
11702
|
+
*/
|
|
11703
|
+
function buildNearestGroupPath(context) {
|
|
11704
|
+
if (context.arrayPath && context.groupPath) {
|
|
11705
|
+
return `${context.arrayPath}.$.${context.groupPath}`;
|
|
11706
|
+
}
|
|
11707
|
+
return context.arrayPath ?? context.groupPath;
|
|
11708
|
+
}
|
|
11709
|
+
/**
|
|
11710
|
+
* Substitutes special tokens in a `dependsOn` array with their resolved
|
|
11711
|
+
* absolute paths. Currently supports:
|
|
11712
|
+
*
|
|
11713
|
+
* - `SELF_DEPENDENCY_TOKEN` ('$self') → the field's own absolute path
|
|
11714
|
+
* - `'$self.X'` → the field's own path joined with `.X` (e.g. for object-valued
|
|
11715
|
+
* fields where you want to depend on a sub-property)
|
|
11716
|
+
* - `GROUP_DEPENDENCY_TOKEN` ('$group') → the field's nearest parent
|
|
11717
|
+
* container path; throws if the field has no parent group/array.
|
|
11718
|
+
* - `'$group.sibling'` → the parent path joined with `.sibling`, useful to
|
|
11719
|
+
* target a specific sibling without naming the parent group key.
|
|
11720
|
+
*
|
|
11721
|
+
* Non-token strings are passed through unchanged.
|
|
11722
|
+
*
|
|
11723
|
+
* @internal
|
|
11724
|
+
*/
|
|
11725
|
+
function resolveTokenDependencies(deps, effectiveFieldKey, context) {
|
|
11726
|
+
return deps.map((dep) => {
|
|
11727
|
+
if (dep === SELF_DEPENDENCY_TOKEN) {
|
|
11728
|
+
return effectiveFieldKey;
|
|
11729
|
+
}
|
|
11730
|
+
if (dep.startsWith(`${SELF_DEPENDENCY_TOKEN}.`)) {
|
|
11731
|
+
return effectiveFieldKey + dep.slice(SELF_DEPENDENCY_TOKEN.length);
|
|
11732
|
+
}
|
|
11733
|
+
if (dep === GROUP_DEPENDENCY_TOKEN || dep.startsWith(`${GROUP_DEPENDENCY_TOKEN}.`)) {
|
|
11734
|
+
const groupPath = buildNearestGroupPath(context);
|
|
11735
|
+
if (!groupPath) {
|
|
11736
|
+
throw new DynamicFormError(`Derivation for '${effectiveFieldKey}' uses '${dep}' but the field has no parent group or array. ` +
|
|
11737
|
+
`'${GROUP_DEPENDENCY_TOKEN}' is only valid for fields nested inside a group or array container.`);
|
|
11522
11738
|
}
|
|
11739
|
+
if (dep === GROUP_DEPENDENCY_TOKEN)
|
|
11740
|
+
return groupPath;
|
|
11741
|
+
return groupPath + dep.slice(GROUP_DEPENDENCY_TOKEN.length);
|
|
11523
11742
|
}
|
|
11524
|
-
|
|
11743
|
+
return dep;
|
|
11744
|
+
});
|
|
11525
11745
|
}
|
|
11526
11746
|
/**
|
|
11527
11747
|
* Collects derivation entries from a single field.
|
|
@@ -11561,8 +11781,7 @@ function collectFromField$1(field, entries, context) {
|
|
|
11561
11781
|
* @internal
|
|
11562
11782
|
*/
|
|
11563
11783
|
function createShorthandEntry(fieldKey, expression, context) {
|
|
11564
|
-
|
|
11565
|
-
const effectiveFieldKey = context.arrayPath ? `${context.arrayPath}.$.${fieldKey}` : fieldKey;
|
|
11784
|
+
const effectiveFieldKey = buildEffectiveFieldKey(fieldKey, context);
|
|
11566
11785
|
return {
|
|
11567
11786
|
fieldKey: effectiveFieldKey,
|
|
11568
11787
|
dependsOn: extractStringDependencies(expression),
|
|
@@ -11588,11 +11807,16 @@ function createShorthandEntry(fieldKey, expression, context) {
|
|
|
11588
11807
|
* @internal
|
|
11589
11808
|
*/
|
|
11590
11809
|
function createLogicEntry(fieldKey, config, context) {
|
|
11591
|
-
|
|
11592
|
-
const effectiveFieldKey = context.arrayPath ? `${context.arrayPath}.$.${fieldKey}` : fieldKey;
|
|
11810
|
+
const effectiveFieldKey = buildEffectiveFieldKey(fieldKey, context);
|
|
11593
11811
|
// Runtime guards for HTTP and async derivations.
|
|
11594
11812
|
// Mutual exclusivity and required fields are now enforced by TypeScript (via `source` discriminant).
|
|
11595
11813
|
// The wildcard and empty-array checks below are not expressible in the type system.
|
|
11814
|
+
//
|
|
11815
|
+
// Note: `$group` / `$group.X` tokens are intentionally allowed for HTTP and async sources even
|
|
11816
|
+
// though they resolve to the parent path and therefore fire on any sibling change inside that
|
|
11817
|
+
// group/array. Unlike `*`, the token is structurally scoped (collection throws if the field has
|
|
11818
|
+
// no parent container) and an explicit opt-in. Consumers that want to bound request frequency
|
|
11819
|
+
// can use `trigger: 'debounced'` here, or rely on debouncing inside their async function.
|
|
11596
11820
|
if (config.source === 'http') {
|
|
11597
11821
|
if (config.dependsOn.length === 0) {
|
|
11598
11822
|
throw new DynamicFormError(`HTTP derivation for '${effectiveFieldKey}' requires explicit 'dependsOn'. ` +
|
|
@@ -11613,7 +11837,7 @@ function createLogicEntry(fieldKey, config, context) {
|
|
|
11613
11837
|
`Wildcards would trigger async functions on every form change. Specify explicit field dependencies instead.`);
|
|
11614
11838
|
}
|
|
11615
11839
|
}
|
|
11616
|
-
const dependsOn =
|
|
11840
|
+
const dependsOn = resolveTokenDependencies(extractDependenciesFromConfig(config), effectiveFieldKey, context);
|
|
11617
11841
|
const condition = config.condition ?? true;
|
|
11618
11842
|
const trigger = config.trigger ?? 'onChange';
|
|
11619
11843
|
// Extract debounceMs from debounced configs
|
|
@@ -11638,43 +11862,6 @@ function createLogicEntry(fieldKey, config, context) {
|
|
|
11638
11862
|
reEngageOnDependencyChange: config.reEngageOnDependencyChange,
|
|
11639
11863
|
};
|
|
11640
11864
|
}
|
|
11641
|
-
/**
|
|
11642
|
-
* Extracts all field dependencies from a derivation config.
|
|
11643
|
-
*
|
|
11644
|
-
* Combines dependencies from:
|
|
11645
|
-
* - Explicit `dependsOn` array (if provided, takes precedence)
|
|
11646
|
-
* - Condition expression
|
|
11647
|
-
* - Value expression
|
|
11648
|
-
* - Function name (defaults to '*' if no explicit dependsOn)
|
|
11649
|
-
*
|
|
11650
|
-
* @internal
|
|
11651
|
-
*/
|
|
11652
|
-
function extractDependencies$1(config) {
|
|
11653
|
-
const deps = new Set();
|
|
11654
|
-
// If explicit dependsOn is provided, use it as the primary source
|
|
11655
|
-
// This allows users to override automatic detection and control
|
|
11656
|
-
// when derivations are triggered
|
|
11657
|
-
if (config.dependsOn && config.dependsOn.length > 0) {
|
|
11658
|
-
config.dependsOn.forEach((dep) => deps.add(dep));
|
|
11659
|
-
}
|
|
11660
|
-
else {
|
|
11661
|
-
// Extract from expression (automatic dependency detection)
|
|
11662
|
-
if (config.expression) {
|
|
11663
|
-
const exprDeps = extractStringDependencies(config.expression);
|
|
11664
|
-
exprDeps.forEach((dep) => deps.add(dep));
|
|
11665
|
-
}
|
|
11666
|
-
// Custom functions assume full form access if no explicit dependsOn
|
|
11667
|
-
if (config.functionName) {
|
|
11668
|
-
deps.add('*');
|
|
11669
|
-
}
|
|
11670
|
-
}
|
|
11671
|
-
// Always extract from condition (these are additional runtime guards)
|
|
11672
|
-
if (config.condition && config.condition !== true) {
|
|
11673
|
-
const conditionDeps = extractExpressionDependencies(config.condition);
|
|
11674
|
-
conditionDeps.forEach((dep) => deps.add(dep));
|
|
11675
|
-
}
|
|
11676
|
-
return Array.from(deps);
|
|
11677
|
-
}
|
|
11678
11865
|
|
|
11679
11866
|
/**
|
|
11680
11867
|
* Detects cycles in the derivation dependency graph.
|
|
@@ -12361,6 +12548,58 @@ function createAsyncDerivationStream(entry, formValue$, context) {
|
|
|
12361
12548
|
}));
|
|
12362
12549
|
}
|
|
12363
12550
|
|
|
12551
|
+
/**
|
|
12552
|
+
* Returns the unique set of debounce periods declared by `entries` whose
|
|
12553
|
+
* `trigger === 'debounced'`. Entries without an explicit `debounceMs` use
|
|
12554
|
+
* {@link DEFAULT_DEBOUNCE_MS}.
|
|
12555
|
+
*
|
|
12556
|
+
* @internal
|
|
12557
|
+
*/
|
|
12558
|
+
function getDebouncePeriods(entries) {
|
|
12559
|
+
const periods = new Set();
|
|
12560
|
+
for (const entry of entries) {
|
|
12561
|
+
if (entry.trigger === 'debounced') {
|
|
12562
|
+
periods.add(entry.debounceMs ?? DEFAULT_DEBOUNCE_MS);
|
|
12563
|
+
}
|
|
12564
|
+
}
|
|
12565
|
+
return Array.from(periods);
|
|
12566
|
+
}
|
|
12567
|
+
/**
|
|
12568
|
+
* Filters `entries` down to those whose `trigger === 'debounced'` and whose
|
|
12569
|
+
* effective `debounceMs` (defaulting to {@link DEFAULT_DEBOUNCE_MS}) equals `period`.
|
|
12570
|
+
*
|
|
12571
|
+
* @internal
|
|
12572
|
+
*/
|
|
12573
|
+
function filterEntriesByDebouncePeriod(entries, period) {
|
|
12574
|
+
return entries.filter((entry) => entry.trigger === 'debounced' && (entry.debounceMs ?? DEFAULT_DEBOUNCE_MS) === period);
|
|
12575
|
+
}
|
|
12576
|
+
|
|
12577
|
+
/**
|
|
12578
|
+
* Resolves a record of external-data signals to their current values without
|
|
12579
|
+
* establishing reactive dependencies.
|
|
12580
|
+
*
|
|
12581
|
+
* The orchestrators read external data per derivation cycle; using `untracked`
|
|
12582
|
+
* here ensures the orchestrator's own computed/effect graph isn't tied to the
|
|
12583
|
+
* external-data signal identities.
|
|
12584
|
+
*
|
|
12585
|
+
* @internal
|
|
12586
|
+
*/
|
|
12587
|
+
function resolveExternalData(externalDataSignal) {
|
|
12588
|
+
const externalDataRecord = untracked(() => externalDataSignal?.());
|
|
12589
|
+
if (!externalDataRecord)
|
|
12590
|
+
return undefined;
|
|
12591
|
+
const resolved = {};
|
|
12592
|
+
for (const [key, value] of Object.entries(externalDataRecord)) {
|
|
12593
|
+
if (isSignal(value)) {
|
|
12594
|
+
resolved[key] = untracked(() => value());
|
|
12595
|
+
}
|
|
12596
|
+
else {
|
|
12597
|
+
resolved[key] = value;
|
|
12598
|
+
}
|
|
12599
|
+
}
|
|
12600
|
+
return resolved;
|
|
12601
|
+
}
|
|
12602
|
+
|
|
12364
12603
|
/**
|
|
12365
12604
|
* Orchestrates derivation processing for a dynamic form.
|
|
12366
12605
|
*
|
|
@@ -12477,7 +12716,7 @@ class DerivationOrchestrator {
|
|
|
12477
12716
|
if (!collection || !formAccessor)
|
|
12478
12717
|
return of(null);
|
|
12479
12718
|
// Get unique debounce periods from entries with trigger 'debounced'
|
|
12480
|
-
const debouncePeriods =
|
|
12719
|
+
const debouncePeriods = getDebouncePeriods(collection.entries);
|
|
12481
12720
|
if (debouncePeriods.length === 0)
|
|
12482
12721
|
return of(null);
|
|
12483
12722
|
// merge: Process multiple debounce periods concurrently.
|
|
@@ -12510,7 +12749,7 @@ class DerivationOrchestrator {
|
|
|
12510
12749
|
rootForm: formAccessor,
|
|
12511
12750
|
derivationFunctions: this.functionRegistry.getDerivationFunctions(),
|
|
12512
12751
|
customFunctions: this.functionRegistry.getCustomFunctions(),
|
|
12513
|
-
externalData: this.
|
|
12752
|
+
externalData: resolveExternalData(this.config.externalData),
|
|
12514
12753
|
logger: this.logger,
|
|
12515
12754
|
warningTracker: this.warningTracker,
|
|
12516
12755
|
derivationLogger: untracked(() => this.config.derivationLogger()),
|
|
@@ -12538,7 +12777,7 @@ class DerivationOrchestrator {
|
|
|
12538
12777
|
rootForm: formAccessor,
|
|
12539
12778
|
derivationFunctions: this.functionRegistry.getDerivationFunctions(),
|
|
12540
12779
|
customFunctions: this.functionRegistry.getCustomFunctions(),
|
|
12541
|
-
externalData: this.
|
|
12780
|
+
externalData: resolveExternalData(this.config.externalData),
|
|
12542
12781
|
logger: this.logger,
|
|
12543
12782
|
warningTracker: this.warningTracker,
|
|
12544
12783
|
derivationLogger: untracked(() => this.config.derivationLogger()),
|
|
@@ -12588,7 +12827,7 @@ class DerivationOrchestrator {
|
|
|
12588
12827
|
logger: this.logger,
|
|
12589
12828
|
derivationLogger: this.config.derivationLogger,
|
|
12590
12829
|
customFunctions: () => this.functionRegistry.getCustomFunctions(),
|
|
12591
|
-
externalData: () => this.
|
|
12830
|
+
externalData: () => resolveExternalData(this.config.externalData),
|
|
12592
12831
|
warningTracker: this.warningTracker,
|
|
12593
12832
|
guard$: this.streamGuard.guard$,
|
|
12594
12833
|
};
|
|
@@ -12662,7 +12901,7 @@ class DerivationOrchestrator {
|
|
|
12662
12901
|
derivationLogger: this.config.derivationLogger,
|
|
12663
12902
|
customFunctions: () => this.functionRegistry.getCustomFunctions(),
|
|
12664
12903
|
asyncDerivationFunctions: () => this.functionRegistry.getAsyncDerivationFunctions(),
|
|
12665
|
-
externalData: () => this.
|
|
12904
|
+
externalData: () => resolveExternalData(this.config.externalData),
|
|
12666
12905
|
warningTracker: this.warningTracker,
|
|
12667
12906
|
};
|
|
12668
12907
|
const stream = createAsyncDerivationStream(entry, formValue$, context).pipe(takeUntilDestroyed(this.destroyRef));
|
|
@@ -12697,42 +12936,6 @@ class DerivationOrchestrator {
|
|
|
12697
12936
|
return `${entry.fieldKey}:${JSON.stringify(config, Object.keys(config).sort())}`;
|
|
12698
12937
|
}));
|
|
12699
12938
|
}
|
|
12700
|
-
/**
|
|
12701
|
-
* Gets all unique debounce periods from entries with trigger 'debounced'.
|
|
12702
|
-
*/
|
|
12703
|
-
getDebouncePeriods(entries) {
|
|
12704
|
-
const periods = new Set();
|
|
12705
|
-
for (const entry of entries) {
|
|
12706
|
-
if (entry.trigger === 'debounced') {
|
|
12707
|
-
periods.add(entry.debounceMs ?? DEFAULT_DEBOUNCE_MS);
|
|
12708
|
-
}
|
|
12709
|
-
}
|
|
12710
|
-
return Array.from(periods);
|
|
12711
|
-
}
|
|
12712
|
-
/**
|
|
12713
|
-
* Resolves external data signals to their current values without creating dependencies.
|
|
12714
|
-
*
|
|
12715
|
-
* Uses `untracked()` to read signals without establishing reactive dependencies,
|
|
12716
|
-
* which is important for derivations that shouldn't re-trigger on every external data change.
|
|
12717
|
-
*
|
|
12718
|
-
* @returns Record of resolved external data values, or undefined if no external data.
|
|
12719
|
-
*/
|
|
12720
|
-
resolveExternalData() {
|
|
12721
|
-
const externalDataRecord = untracked(() => this.config.externalData?.());
|
|
12722
|
-
if (!externalDataRecord) {
|
|
12723
|
-
return undefined;
|
|
12724
|
-
}
|
|
12725
|
-
const resolved = {};
|
|
12726
|
-
for (const [key, signal] of Object.entries(externalDataRecord)) {
|
|
12727
|
-
if (isSignal(signal)) {
|
|
12728
|
-
resolved[key] = untracked(() => signal());
|
|
12729
|
-
}
|
|
12730
|
-
else {
|
|
12731
|
-
resolved[key] = signal;
|
|
12732
|
-
}
|
|
12733
|
-
}
|
|
12734
|
-
return resolved;
|
|
12735
|
-
}
|
|
12736
12939
|
}
|
|
12737
12940
|
/**
|
|
12738
12941
|
* Creates a DerivationOrchestrator for a dynamic form.
|
|
@@ -13171,14 +13374,26 @@ function tryApplyArrayPropertyDerivation(entry, context) {
|
|
|
13171
13374
|
/**
|
|
13172
13375
|
* Creates an evaluation context for property derivation processing.
|
|
13173
13376
|
*
|
|
13377
|
+
* Note: `fieldState`/`formFieldState` are intentionally not populated here.
|
|
13378
|
+
* Property derivations run against the form value signal and write to a
|
|
13379
|
+
* separate override store; the applicator does not have access to the live
|
|
13380
|
+
* `FieldTree` (no `rootForm` on `PropertyDerivationApplicatorContext`), so
|
|
13381
|
+
* field-state snapshots cannot be read in this pipeline. Functions that need
|
|
13382
|
+
* field state (e.g. `dirty()`, `touched()`) must use a regular derivation.
|
|
13383
|
+
*
|
|
13174
13384
|
* @internal
|
|
13175
13385
|
*/
|
|
13176
13386
|
function createEvaluationContext(entry, formValue, context) {
|
|
13177
13387
|
const fieldValue = getNestedValue(formValue, entry.fieldKey);
|
|
13388
|
+
// Parent group value: the absolute path with the trailing field segment
|
|
13389
|
+
// dropped. `undefined` when the field is at form root.
|
|
13390
|
+
const parentPath = getParentPathInScope(entry.fieldKey);
|
|
13391
|
+
const groupValue = parentPath ? getNestedValue(formValue, parentPath) : undefined;
|
|
13178
13392
|
return {
|
|
13179
13393
|
fieldValue,
|
|
13180
13394
|
formValue,
|
|
13181
13395
|
fieldPath: entry.fieldKey,
|
|
13396
|
+
groupValue,
|
|
13182
13397
|
customFunctions: context.customFunctions,
|
|
13183
13398
|
externalData: context.externalData,
|
|
13184
13399
|
logger: context.logger,
|
|
@@ -13190,10 +13405,13 @@ function createEvaluationContext(entry, formValue, context) {
|
|
|
13190
13405
|
* @internal
|
|
13191
13406
|
*/
|
|
13192
13407
|
function createArrayItemEvaluationContext(entry, arrayItem, rootFormValue, itemIndex, arrayPath, context) {
|
|
13408
|
+
const pathInfo = parseArrayPath(entry.fieldKey);
|
|
13409
|
+
const { relativePath, groupValue, fieldValue } = resolveArrayItemScope(pathInfo, arrayItem);
|
|
13193
13410
|
return {
|
|
13194
|
-
fieldValue
|
|
13411
|
+
fieldValue,
|
|
13195
13412
|
formValue: arrayItem,
|
|
13196
|
-
fieldPath: `${arrayPath}.${itemIndex}`,
|
|
13413
|
+
fieldPath: relativePath ? `${arrayPath}.${itemIndex}.${relativePath}` : `${arrayPath}.${itemIndex}`,
|
|
13414
|
+
groupValue,
|
|
13197
13415
|
customFunctions: context.customFunctions,
|
|
13198
13416
|
externalData: context.externalData,
|
|
13199
13417
|
logger: context.logger,
|
|
@@ -13219,24 +13437,11 @@ function evaluatePropertyCondition(condition, context) {
|
|
|
13219
13437
|
* @internal
|
|
13220
13438
|
*/
|
|
13221
13439
|
function computePropertyValue(entry, evalContext, applicatorContext) {
|
|
13222
|
-
|
|
13223
|
-
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
if (entry.expression) {
|
|
13228
|
-
return ExpressionParser.evaluate(entry.expression, evalContext);
|
|
13229
|
-
}
|
|
13230
|
-
// Custom function
|
|
13231
|
-
if (entry.functionName) {
|
|
13232
|
-
const fn = applicatorContext.derivationFunctions?.[entry.functionName];
|
|
13233
|
-
if (!fn) {
|
|
13234
|
-
throw new DynamicFormError(`Property derivation function '${entry.functionName}' not found in customFnConfig.derivations`);
|
|
13235
|
-
}
|
|
13236
|
-
return fn(evalContext);
|
|
13237
|
-
}
|
|
13238
|
-
throw new DynamicFormError(`Property derivation for ${entry.fieldKey}.${entry.targetProperty} has no value source. ` +
|
|
13239
|
-
`Specify 'value', 'expression', or 'functionName'.`);
|
|
13440
|
+
return computeValueFromEntry(entry, evalContext, {
|
|
13441
|
+
derivationFunctions: applicatorContext.derivationFunctions,
|
|
13442
|
+
subject: `${entry.fieldKey}.${entry.targetProperty}`,
|
|
13443
|
+
functionKind: 'Property derivation function',
|
|
13444
|
+
});
|
|
13240
13445
|
}
|
|
13241
13446
|
|
|
13242
13447
|
/**
|
|
@@ -13256,40 +13461,12 @@ function computePropertyValue(entry, evalContext, applicatorContext) {
|
|
|
13256
13461
|
function collectPropertyDerivations(fields, logger, tracker) {
|
|
13257
13462
|
const entries = [];
|
|
13258
13463
|
const context = { logger, tracker };
|
|
13259
|
-
|
|
13464
|
+
traverseFieldsWithContext(fields, context, (field, ctx) => collectFromField(field, entries, ctx), {
|
|
13465
|
+
onArrayChild: (_, field) => ({ arrayPath: field.key }),
|
|
13466
|
+
// Group and layout containers do not affect property-derivation paths.
|
|
13467
|
+
});
|
|
13260
13468
|
return { entries };
|
|
13261
13469
|
}
|
|
13262
|
-
/**
|
|
13263
|
-
* Recursively traverses field definitions to collect property derivations.
|
|
13264
|
-
*
|
|
13265
|
-
* @internal
|
|
13266
|
-
*/
|
|
13267
|
-
function traverseFields(fields, entries, context) {
|
|
13268
|
-
for (const field of fields) {
|
|
13269
|
-
collectFromField(field, entries, context);
|
|
13270
|
-
// Recursively process container fields (page, row, group, array)
|
|
13271
|
-
if (hasChildFields(field)) {
|
|
13272
|
-
const childContext = { ...context };
|
|
13273
|
-
if (field.type === 'array') {
|
|
13274
|
-
childContext.arrayPath = field.key;
|
|
13275
|
-
const arrayItems = normalizeFieldsArray(field.fields);
|
|
13276
|
-
const normalizedChildren = [];
|
|
13277
|
-
for (const item of arrayItems) {
|
|
13278
|
-
if (Array.isArray(item)) {
|
|
13279
|
-
normalizedChildren.push(...item);
|
|
13280
|
-
}
|
|
13281
|
-
else {
|
|
13282
|
-
normalizedChildren.push(item);
|
|
13283
|
-
}
|
|
13284
|
-
}
|
|
13285
|
-
traverseFields(normalizedChildren, entries, childContext);
|
|
13286
|
-
}
|
|
13287
|
-
else {
|
|
13288
|
-
traverseFields(normalizeFieldsArray(field.fields), entries, childContext);
|
|
13289
|
-
}
|
|
13290
|
-
}
|
|
13291
|
-
}
|
|
13292
|
-
}
|
|
13293
13470
|
/**
|
|
13294
13471
|
* Collects property derivation entries from a single field.
|
|
13295
13472
|
*
|
|
@@ -13319,7 +13496,7 @@ function collectFromField(field, entries, context) {
|
|
|
13319
13496
|
*/
|
|
13320
13497
|
function createPropertyDerivationEntryFromDerivation(fieldKey, config, context) {
|
|
13321
13498
|
const effectiveFieldKey = buildPropertyOverrideKey(context.arrayPath, context.arrayPath ? PLACEHOLDER_INDEX : undefined, fieldKey);
|
|
13322
|
-
const dependsOn =
|
|
13499
|
+
const dependsOn = extractDependenciesFromConfig(config);
|
|
13323
13500
|
const condition = config.condition ?? true;
|
|
13324
13501
|
const trigger = config.trigger ?? 'onChange';
|
|
13325
13502
|
const debounceMs = trigger === 'debounced' ? config.debounceMs : undefined;
|
|
@@ -13337,32 +13514,6 @@ function createPropertyDerivationEntryFromDerivation(fieldKey, config, context)
|
|
|
13337
13514
|
originalConfig: config,
|
|
13338
13515
|
};
|
|
13339
13516
|
}
|
|
13340
|
-
/**
|
|
13341
|
-
* Extracts all field dependencies from a property derivation config.
|
|
13342
|
-
*
|
|
13343
|
-
* @internal
|
|
13344
|
-
*/
|
|
13345
|
-
function extractDependencies(config) {
|
|
13346
|
-
const deps = new Set();
|
|
13347
|
-
if (config.dependsOn && config.dependsOn.length > 0) {
|
|
13348
|
-
config.dependsOn.forEach((dep) => deps.add(dep));
|
|
13349
|
-
}
|
|
13350
|
-
else {
|
|
13351
|
-
if (config.expression) {
|
|
13352
|
-
const exprDeps = extractStringDependencies(config.expression);
|
|
13353
|
-
exprDeps.forEach((dep) => deps.add(dep));
|
|
13354
|
-
}
|
|
13355
|
-
if (config.functionName) {
|
|
13356
|
-
deps.add('*');
|
|
13357
|
-
}
|
|
13358
|
-
}
|
|
13359
|
-
// Always extract from condition
|
|
13360
|
-
if (config.condition && config.condition !== true) {
|
|
13361
|
-
const conditionDeps = extractExpressionDependencies(config.condition);
|
|
13362
|
-
conditionDeps.forEach((dep) => deps.add(dep));
|
|
13363
|
-
}
|
|
13364
|
-
return Array.from(deps);
|
|
13365
|
-
}
|
|
13366
13517
|
|
|
13367
13518
|
/**
|
|
13368
13519
|
* Orchestrates property derivation processing for a dynamic form.
|
|
@@ -13440,7 +13591,7 @@ class PropertyDerivationOrchestrator {
|
|
|
13440
13591
|
const collection = untracked(() => this.propertyDerivationCollection());
|
|
13441
13592
|
if (!collection)
|
|
13442
13593
|
return of(null);
|
|
13443
|
-
const debouncePeriods =
|
|
13594
|
+
const debouncePeriods = getDebouncePeriods(collection.entries);
|
|
13444
13595
|
if (debouncePeriods.length === 0)
|
|
13445
13596
|
return of(null);
|
|
13446
13597
|
const periodStreams = debouncePeriods.map((debounceMs) => this.createPeriodStream(debounceMs, collection, changedFields));
|
|
@@ -13465,7 +13616,7 @@ class PropertyDerivationOrchestrator {
|
|
|
13465
13616
|
store: this.config.store,
|
|
13466
13617
|
derivationFunctions: this.functionRegistry.getDerivationFunctions(),
|
|
13467
13618
|
customFunctions: this.functionRegistry.getCustomFunctions(),
|
|
13468
|
-
externalData: this.
|
|
13619
|
+
externalData: resolveExternalData(this.config.externalData),
|
|
13469
13620
|
logger: this.logger,
|
|
13470
13621
|
warningTracker: this.warningTracker,
|
|
13471
13622
|
};
|
|
@@ -13481,45 +13632,12 @@ class PropertyDerivationOrchestrator {
|
|
|
13481
13632
|
store: this.config.store,
|
|
13482
13633
|
derivationFunctions: this.functionRegistry.getDerivationFunctions(),
|
|
13483
13634
|
customFunctions: this.functionRegistry.getCustomFunctions(),
|
|
13484
|
-
externalData: this.
|
|
13635
|
+
externalData: resolveExternalData(this.config.externalData),
|
|
13485
13636
|
logger: this.logger,
|
|
13486
13637
|
warningTracker: this.warningTracker,
|
|
13487
13638
|
};
|
|
13488
13639
|
applyPropertyDerivationsForTrigger(filteredCollection, 'debounced', applicatorContext, changedFields);
|
|
13489
13640
|
}
|
|
13490
|
-
getDebouncePeriods(entries) {
|
|
13491
|
-
const periods = new Set();
|
|
13492
|
-
for (const entry of entries) {
|
|
13493
|
-
if (entry.trigger === 'debounced') {
|
|
13494
|
-
periods.add(entry.debounceMs ?? DEFAULT_DEBOUNCE_MS);
|
|
13495
|
-
}
|
|
13496
|
-
}
|
|
13497
|
-
return Array.from(periods);
|
|
13498
|
-
}
|
|
13499
|
-
/**
|
|
13500
|
-
* Resolves external data signals to their current values.
|
|
13501
|
-
*
|
|
13502
|
-
* Called on every onChange and debounced application. Each call iterates all
|
|
13503
|
-
* external data entries and resolves signals via untracked(). For forms with
|
|
13504
|
-
* many external data entries, this could be optimized by caching the resolved
|
|
13505
|
-
* record and only invalidating when external data signal values change.
|
|
13506
|
-
* In practice, external data entries are few, so the linear scan is acceptable.
|
|
13507
|
-
*/
|
|
13508
|
-
resolveExternalData() {
|
|
13509
|
-
const externalDataRecord = untracked(() => this.config.externalData?.());
|
|
13510
|
-
if (!externalDataRecord)
|
|
13511
|
-
return undefined;
|
|
13512
|
-
const resolved = {};
|
|
13513
|
-
for (const [key, signalValue] of Object.entries(externalDataRecord)) {
|
|
13514
|
-
if (isSignal(signalValue)) {
|
|
13515
|
-
resolved[key] = untracked(() => signalValue());
|
|
13516
|
-
}
|
|
13517
|
-
else {
|
|
13518
|
-
resolved[key] = signalValue;
|
|
13519
|
-
}
|
|
13520
|
-
}
|
|
13521
|
-
return resolved;
|
|
13522
|
-
}
|
|
13523
13641
|
}
|
|
13524
13642
|
/**
|
|
13525
13643
|
* Creates a PropertyDerivationOrchestrator for a dynamic form.
|
|
@@ -14491,7 +14609,10 @@ function rowFieldMapper(fieldDef) {
|
|
|
14491
14609
|
// Rebuilt each emission so logic-driven updates to fieldDef (e.g. `disabled`)
|
|
14492
14610
|
// flow through. Shallow spread is safe while RowField has no nested
|
|
14493
14611
|
// mutable props; add a deep-clone if that stops being true.
|
|
14494
|
-
|
|
14612
|
+
// `type` is rewritten to 'container' because `row` is a synthetic field
|
|
14613
|
+
// type — the runtime renders it as a ContainerField with a synthesized
|
|
14614
|
+
// `{ type: 'row' }` wrapper.
|
|
14615
|
+
const containerField = { ...fieldDef, type: 'container', wrappers: ROW_WRAPPERS };
|
|
14495
14616
|
const inputs = {
|
|
14496
14617
|
key: fieldDef.key,
|
|
14497
14618
|
field: containerField,
|
|
@@ -14637,31 +14758,31 @@ const BUILT_IN_FIELDS = [
|
|
|
14637
14758
|
// `row` is a virtual field type: it maps to ContainerFieldComponent, and
|
|
14638
14759
|
// the rowFieldMapper injects a `{ type: 'row' }` wrapper so the container
|
|
14639
14760
|
// renders the flex/grid layout via RowWrapperComponent.
|
|
14640
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
14761
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-PozYj2JC.mjs'),
|
|
14641
14762
|
mapper: rowFieldMapper,
|
|
14642
14763
|
valueHandling: 'flatten',
|
|
14643
14764
|
},
|
|
14644
14765
|
{
|
|
14645
14766
|
name: 'group',
|
|
14646
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-
|
|
14767
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-group-field.component-2yRmHAUp.mjs'),
|
|
14647
14768
|
mapper: groupFieldMapper,
|
|
14648
14769
|
valueHandling: 'include',
|
|
14649
14770
|
},
|
|
14650
14771
|
{
|
|
14651
14772
|
name: 'array',
|
|
14652
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-
|
|
14773
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-array-field.component-CWxCWVdz.mjs'),
|
|
14653
14774
|
mapper: arrayFieldMapper,
|
|
14654
14775
|
valueHandling: 'include',
|
|
14655
14776
|
},
|
|
14656
14777
|
{
|
|
14657
14778
|
name: 'page',
|
|
14658
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-
|
|
14779
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-page-field.component-B_CxX0T-.mjs'),
|
|
14659
14780
|
mapper: pageFieldMapper,
|
|
14660
14781
|
valueHandling: 'flatten',
|
|
14661
14782
|
},
|
|
14662
14783
|
{
|
|
14663
14784
|
name: 'text',
|
|
14664
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-
|
|
14785
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-text-field.component-rJMtKX2X.mjs'),
|
|
14665
14786
|
mapper: textFieldMapper,
|
|
14666
14787
|
valueHandling: 'exclude',
|
|
14667
14788
|
...DISPLAY_FIELD_TYPES_BASE,
|
|
@@ -14673,7 +14794,7 @@ const BUILT_IN_FIELDS = [
|
|
|
14673
14794
|
},
|
|
14674
14795
|
{
|
|
14675
14796
|
name: 'container',
|
|
14676
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-
|
|
14797
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-container-field.component-PozYj2JC.mjs'),
|
|
14677
14798
|
mapper: containerFieldMapper,
|
|
14678
14799
|
valueHandling: 'flatten',
|
|
14679
14800
|
},
|
|
@@ -14681,7 +14802,7 @@ const BUILT_IN_FIELDS = [
|
|
|
14681
14802
|
const BUILT_IN_WRAPPERS = [
|
|
14682
14803
|
{
|
|
14683
14804
|
wrapperName: 'css',
|
|
14684
|
-
loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-
|
|
14805
|
+
loadComponent: () => import('./ng-forge-dynamic-forms-css-wrapper.component-DrtHSaVV.mjs'),
|
|
14685
14806
|
},
|
|
14686
14807
|
{
|
|
14687
14808
|
wrapperName: 'row',
|
|
@@ -15714,4 +15835,4 @@ function extractErrorParams(error) {
|
|
|
15714
15835
|
*/
|
|
15715
15836
|
|
|
15716
15837
|
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, withValueExclusionDefaults as aL, wrapperProps as aM, 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 };
|
|
15717
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-
|
|
15838
|
+
//# sourceMappingURL=ng-forge-dynamic-forms-ng-forge-dynamic-forms-Ll4W2Z_n.mjs.map
|