@ng-forge/dynamic-forms 1.3.0-next.2 → 1.3.0-next.4
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-active-page-initialized.event-bnrwNw7Y.mjs +156 -0
- package/fesm2022/ng-forge-dynamic-forms-active-page-initialized.event-bnrwNw7Y.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-Dv9mPnJ3.mjs +770 -0
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-Dv9mPnJ3.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-array-item-registry.service-D-AxVLdk.mjs +83 -0
- package/fesm2022/ng-forge-dynamic-forms-array-item-registry.service-D-AxVLdk.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-B6WH0Etk.mjs +1887 -0
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-B6WH0Etk.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-VjJn-JeD.mjs +954 -0
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-VjJn-JeD.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-container-field-processors-C_JRvaVt.mjs +117 -0
- package/fesm2022/ng-forge-dynamic-forms-container-field-processors-C_JRvaVt.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-Bg7Win9n.mjs +228 -0
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-Bg7Win9n.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-R2yc-H0T.mjs +102 -0
- package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-R2yc-H0T.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-debounce-CG3uP5B-.mjs +4 -0
- package/fesm2022/{ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs.map → ng-forge-dynamic-forms-debounce-CG3uP5B-.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms-default-value-D5M4MNE-.mjs +77 -0
- package/fesm2022/ng-forge-dynamic-forms-default-value-D5M4MNE-.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-dev-mode-4rbYBBo_.mjs +4 -0
- package/fesm2022/ng-forge-dynamic-forms-dev-mode-4rbYBBo_.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-df-field-outlet.directive-CQsDMV-s.mjs +644 -0
- package/fesm2022/ng-forge-dynamic-forms-df-field-outlet.directive-CQsDMV-s.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-DKgkpIpG.mjs +56 -0
- package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-DKgkpIpG.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-form-schema.service-Dd-gHJ_7.mjs +38 -0
- package/fesm2022/{ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs.map → ng-forge-dynamic-forms-form-schema.service-Dd-gHJ_7.mjs.map} +1 -1
- package/fesm2022/ng-forge-dynamic-forms-form-state-manager-BJKBX4FL.mjs +1545 -0
- package/fesm2022/ng-forge-dynamic-forms-form-state-manager-BJKBX4FL.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-BjZi0bhq.mjs +292 -0
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-BjZi0bhq.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs +1318 -843
- package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs +3421 -3641
- package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-Bsd8YpzL.mjs +327 -0
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-Bsd8YpzL.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-resolve-field-ByGa5cpo.mjs +329 -0
- package/fesm2022/ng-forge-dynamic-forms-resolve-field-ByGa5cpo.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-CuFycQb-.mjs +81 -0
- package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-CuFycQb-.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-C62afItJ.mjs +400 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-C62afItJ.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-registry.service-axBV9FBH.mjs +45 -0
- package/fesm2022/ng-forge-dynamic-forms-schema-registry.service-axBV9FBH.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-schema.mjs +8 -14
- package/fesm2022/ng-forge-dynamic-forms-schema.mjs.map +1 -1
- package/fesm2022/ng-forge-dynamic-forms-testing.mjs +68 -73
- package/fesm2022/ng-forge-dynamic-forms-testing.mjs.map +1 -1
- package/fesm2022/ng-forge-dynamic-forms-text-field.component--MAh_h4Q.mjs +257 -0
- package/fesm2022/ng-forge-dynamic-forms-text-field.component--MAh_h4Q.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-validation-execution.token-BFyySKtp.mjs +8 -0
- package/fesm2022/ng-forge-dynamic-forms-validation-execution.token-BFyySKtp.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-with-logger-config-BAATqYY0.mjs +94 -0
- package/fesm2022/ng-forge-dynamic-forms-with-logger-config-BAATqYY0.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms-wrapper-chain-BnntfNU1.mjs +115 -0
- package/fesm2022/ng-forge-dynamic-forms-wrapper-chain-BnntfNU1.mjs.map +1 -0
- package/fesm2022/ng-forge-dynamic-forms.mjs +2195 -3
- package/fesm2022/ng-forge-dynamic-forms.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ng-forge-dynamic-forms-integration.d.ts +543 -415
- package/types/ng-forge-dynamic-forms-internal.d.ts +2329 -2437
- package/types/ng-forge-dynamic-forms-schema.d.ts +13 -16
- package/types/ng-forge-dynamic-forms-testing.d.ts +34 -37
- package/types/ng-forge-dynamic-forms.d.ts +796 -722
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs +0 -750
- package/fesm2022/ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs +0 -2394
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-BtjqeJOj.mjs +0 -1054
- package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-BtjqeJOj.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs +0 -86
- package/fesm2022/ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.mjs +0 -33
- package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs +0 -17
- package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-CIKbWqYA.mjs +0 -25
- package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-CIKbWqYA.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs +0 -24
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs +0 -177
- package/fesm2022/ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs +0 -5133
- package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs +0 -176
- package/fesm2022/ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-DMEeKZny.mjs +0 -19
- package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-DMEeKZny.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-BvDTzfdM.mjs +0 -570
- package/fesm2022/ng-forge-dynamic-forms-schema-builder-BvDTzfdM.mjs.map +0 -1
- package/fesm2022/ng-forge-dynamic-forms-text-field.component-BB-jutBq.mjs +0 -126
- package/fesm2022/ng-forge-dynamic-forms-text-field.component-BB-jutBq.mjs.map +0 -1
|
@@ -1,570 +0,0 @@
|
|
|
1
|
-
import { inject } from '@angular/core';
|
|
2
|
-
import { validateStandardSchema, schema, required, disabled, readonly, hidden, applyEach, applyWhenValue, applyWhen, apply, email, minLength, maxLength, pattern, min, max } from '@angular/forms/signals';
|
|
3
|
-
import { isStateLogicConfig, isDerivationLogicConfig, isFormStateCondition, DynamicFormError, createFormStateLogicFunction, createDebouncedLogicFunction, createLogicFunction, ExpressionParser, DynamicFormLogger, applyValidator, isPageField, isRowField, isGenericContainerField, isGroupField, isArrayField, getFieldValueHandling, hasChildFields, normalizeFieldsArray } from '@ng-forge/dynamic-forms/internal';
|
|
4
|
-
import { q as DEV_MODE, S as SchemaRegistryService, h as getNormalizedArrayMetadata, V as VALIDATION_EXECUTION_DEFAULTS, k as getFieldDefaultValue } from './ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs';
|
|
5
|
-
import { D as DEFAULT_DEBOUNCE_MS } from './ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs';
|
|
6
|
-
import { isStandardSchemaMarker } from '@ng-forge/dynamic-forms/schema';
|
|
7
|
-
|
|
8
|
-
function applyFormLevelSchema(path, formLevelSchema) {
|
|
9
|
-
if (isStandardSchemaMarker(formLevelSchema)) {
|
|
10
|
-
validateStandardSchema(path, formLevelSchema.schema);
|
|
11
|
-
}
|
|
12
|
-
else if (typeof formLevelSchema === 'function') {
|
|
13
|
-
formLevelSchema(path);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function createFormLevelSchema(formLevelSchema) {
|
|
17
|
-
return schema((path) => {
|
|
18
|
-
applyFormLevelSchema(path, formLevelSchema);
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function getConfigTrigger(config) {
|
|
23
|
-
if (!isStateLogicConfig(config)) {
|
|
24
|
-
return 'onChange';
|
|
25
|
-
}
|
|
26
|
-
return config.trigger ?? 'onChange';
|
|
27
|
-
}
|
|
28
|
-
function getConfigDebounceMs(config) {
|
|
29
|
-
if (!isStateLogicConfig(config)) {
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
if (config.trigger === 'debounced') {
|
|
33
|
-
return config.debounceMs;
|
|
34
|
-
}
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
|
-
function applyLogic(config, fieldPath) {
|
|
38
|
-
if (isDerivationLogicConfig(config))
|
|
39
|
-
return;
|
|
40
|
-
if (!isStateLogicConfig(config)) {
|
|
41
|
-
if (DEV_MODE) {
|
|
42
|
-
console.warn(`[Dynamic Forms] Unrecognized logic config type '${config.type}' in applyLogic. ` +
|
|
43
|
-
'This config will be ignored. If this is a new logic type, ensure it is handled explicitly.');
|
|
44
|
-
}
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const path = fieldPath;
|
|
48
|
-
const trigger = getConfigTrigger(config);
|
|
49
|
-
const debounceMs = getConfigDebounceMs(config) ?? DEFAULT_DEBOUNCE_MS;
|
|
50
|
-
if (typeof config.condition === 'boolean') {
|
|
51
|
-
applyLogicFn(config.type, path, () => config.condition);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (isFormStateCondition(config.condition)) {
|
|
55
|
-
if (config.condition === 'formInvalid' || config.condition === 'pageInvalid') {
|
|
56
|
-
const reason = config.condition === 'formInvalid'
|
|
57
|
-
? `it would create a validation cycle against the form's own validity`
|
|
58
|
-
: `a value field has no page context for it to resolve against`;
|
|
59
|
-
throw new DynamicFormError(`The '${config.condition}' form-state condition is not supported on value-field logic because ${reason}. ` +
|
|
60
|
-
`Use 'formSubmitting', or apply this condition to a button or container instead.`);
|
|
61
|
-
}
|
|
62
|
-
applyLogicFn(config.type, path, createFormStateLogicFunction(config.condition));
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const logicFn = trigger === 'debounced'
|
|
66
|
-
? createDebouncedLogicFunction(config.condition, debounceMs)
|
|
67
|
-
: createLogicFunction(config.condition);
|
|
68
|
-
applyLogicFn(config.type, path, logicFn);
|
|
69
|
-
}
|
|
70
|
-
function applyLogicFn(type, path, logicFn) {
|
|
71
|
-
switch (type) {
|
|
72
|
-
case 'hidden':
|
|
73
|
-
hidden(path, logicFn);
|
|
74
|
-
break;
|
|
75
|
-
case 'readonly':
|
|
76
|
-
readonly(path, logicFn);
|
|
77
|
-
break;
|
|
78
|
-
case 'disabled':
|
|
79
|
-
disabled(path, logicFn);
|
|
80
|
-
break;
|
|
81
|
-
case 'required':
|
|
82
|
-
required(path, { when: logicFn });
|
|
83
|
-
break;
|
|
84
|
-
default: {
|
|
85
|
-
const _exhaustive = type;
|
|
86
|
-
throw new DynamicFormError(`Unhandled state logic type: ${_exhaustive}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
function applyMultipleLogic(configs, fieldPath) {
|
|
91
|
-
configs.forEach((config) => applyLogic(config, fieldPath));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function createTypePredicateFunction(predicate, logger) {
|
|
95
|
-
return (value) => {
|
|
96
|
-
try {
|
|
97
|
-
const result = ExpressionParser.evaluate(predicate, { value });
|
|
98
|
-
return Boolean(result);
|
|
99
|
-
}
|
|
100
|
-
catch (error) {
|
|
101
|
-
logger.error('Error evaluating type predicate:', predicate, error);
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function applySchema(config, fieldPath) {
|
|
108
|
-
const logger = inject(DynamicFormLogger);
|
|
109
|
-
const schemaRegistry = inject(SchemaRegistryService);
|
|
110
|
-
const schema = schemaRegistry.resolveSchema(config.schema);
|
|
111
|
-
if (!schema) {
|
|
112
|
-
const availableSchemas = Array.from(schemaRegistry.getAllSchemas().keys()).join(', ') || '<none>';
|
|
113
|
-
logger.error(`Schema not found: '${config.schema}'. ` +
|
|
114
|
-
`Available schemas: ${availableSchemas}. ` +
|
|
115
|
-
`Ensure the schema is registered in your schema registry.`);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const schemaFn = createSchemaFunction(schema, logger);
|
|
119
|
-
const path = fieldPath;
|
|
120
|
-
switch (config.type) {
|
|
121
|
-
case 'apply':
|
|
122
|
-
apply(path, schemaFn);
|
|
123
|
-
break;
|
|
124
|
-
case 'applyWhen':
|
|
125
|
-
if (config.condition) {
|
|
126
|
-
const conditionFn = createLogicFunction(config.condition);
|
|
127
|
-
applyWhen(path, conditionFn, schemaFn);
|
|
128
|
-
}
|
|
129
|
-
break;
|
|
130
|
-
case 'applyWhenValue':
|
|
131
|
-
if (config.typePredicate) {
|
|
132
|
-
const predicate = createTypePredicateFunction(config.typePredicate, logger);
|
|
133
|
-
applyWhenValue(path, predicate, schemaFn);
|
|
134
|
-
}
|
|
135
|
-
break;
|
|
136
|
-
case 'applyEach':
|
|
137
|
-
applyEach(path, schemaFn);
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function createSchemaFunction(schema, logger) {
|
|
142
|
-
return (path) => {
|
|
143
|
-
schema.validators?.forEach((validatorConfig) => {
|
|
144
|
-
applyValidator(validatorConfig, path);
|
|
145
|
-
});
|
|
146
|
-
schema.logic?.forEach((logicConfig) => {
|
|
147
|
-
applyLogic(logicConfig, path);
|
|
148
|
-
});
|
|
149
|
-
schema.subSchemas?.forEach((subSchemaConfig) => {
|
|
150
|
-
applySchema(subSchemaConfig, path);
|
|
151
|
-
});
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function combineAncestorHiddenLogics(logics) {
|
|
156
|
-
if (logics.length === 0)
|
|
157
|
-
return undefined;
|
|
158
|
-
if (logics.length === 1)
|
|
159
|
-
return logics[0];
|
|
160
|
-
return (ctx) => logics.some((fn) => fn(ctx));
|
|
161
|
-
}
|
|
162
|
-
function resolveFieldOwnContext(fieldDef, parentContext) {
|
|
163
|
-
if (fieldDef.validateWhenHidden === undefined)
|
|
164
|
-
return parentContext;
|
|
165
|
-
if (fieldDef.validateWhenHidden === parentContext.validateWhenHidden)
|
|
166
|
-
return parentContext;
|
|
167
|
-
return {
|
|
168
|
-
...parentContext,
|
|
169
|
-
validateWhenHidden: fieldDef.validateWhenHidden,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
function resolveDescendantContext(fieldDef, ownContext) {
|
|
173
|
-
let ancestorAlwaysHidden = ownContext.ancestorAlwaysHidden;
|
|
174
|
-
let ancestorHiddenLogics = ownContext.ancestorHiddenLogics;
|
|
175
|
-
let mutated = false;
|
|
176
|
-
let ancestorRequired = ownContext.ancestorRequired;
|
|
177
|
-
if (fieldDef.required !== undefined && fieldDef.required !== ancestorRequired) {
|
|
178
|
-
ancestorRequired = fieldDef.required;
|
|
179
|
-
mutated = true;
|
|
180
|
-
}
|
|
181
|
-
if (fieldDef.hidden === true && !ancestorAlwaysHidden) {
|
|
182
|
-
ancestorAlwaysHidden = true;
|
|
183
|
-
mutated = true;
|
|
184
|
-
}
|
|
185
|
-
if (fieldDef.logic) {
|
|
186
|
-
for (const logic of fieldDef.logic) {
|
|
187
|
-
if (!isStateLogicConfig(logic) || logic.type !== 'hidden')
|
|
188
|
-
continue;
|
|
189
|
-
const condition = logic.condition;
|
|
190
|
-
if (condition === true) {
|
|
191
|
-
if (!ancestorAlwaysHidden) {
|
|
192
|
-
ancestorAlwaysHidden = true;
|
|
193
|
-
mutated = true;
|
|
194
|
-
}
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (condition === false)
|
|
198
|
-
continue;
|
|
199
|
-
if (typeof condition === 'string')
|
|
200
|
-
continue;
|
|
201
|
-
const fn = createLogicFunction(condition);
|
|
202
|
-
ancestorHiddenLogics = ancestorHiddenLogics.length === 0 ? [fn] : [...ancestorHiddenLogics, fn];
|
|
203
|
-
mutated = true;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (!mutated)
|
|
207
|
-
return ownContext;
|
|
208
|
-
return {
|
|
209
|
-
validateWhenHidden: ownContext.validateWhenHidden,
|
|
210
|
-
ancestorAlwaysHidden,
|
|
211
|
-
ancestorHiddenLogics,
|
|
212
|
-
ancestorRequired,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const DEFAULT_FIELD_TREE_MAPPING_CONTEXT = {
|
|
217
|
-
validateWhenHidden: false,
|
|
218
|
-
ancestorAlwaysHidden: false,
|
|
219
|
-
ancestorHiddenLogics: [],
|
|
220
|
-
};
|
|
221
|
-
function applyStringValidators(path, config) {
|
|
222
|
-
if (config.email) {
|
|
223
|
-
email(path);
|
|
224
|
-
}
|
|
225
|
-
if (config.minLength !== undefined) {
|
|
226
|
-
minLength(path, config.minLength);
|
|
227
|
-
}
|
|
228
|
-
if (config.maxLength !== undefined) {
|
|
229
|
-
maxLength(path, config.maxLength);
|
|
230
|
-
}
|
|
231
|
-
if (config.pattern) {
|
|
232
|
-
let regex;
|
|
233
|
-
if (typeof config.pattern === 'string') {
|
|
234
|
-
try {
|
|
235
|
-
regex = new RegExp(config.pattern);
|
|
236
|
-
}
|
|
237
|
-
catch (e) {
|
|
238
|
-
throw new DynamicFormError(`Invalid regex pattern: '${config.pattern}' — ${e instanceof Error ? e.message : String(e)}`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
regex = config.pattern;
|
|
243
|
-
}
|
|
244
|
-
pattern(path, regex);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
function applyNumberValidators(path, config) {
|
|
248
|
-
if (config.min !== undefined) {
|
|
249
|
-
min(path, config.min);
|
|
250
|
-
}
|
|
251
|
-
if (config.max !== undefined) {
|
|
252
|
-
max(path, config.max);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
function getStringValidationConfig(fieldDef) {
|
|
256
|
-
if (!fieldDef.email && fieldDef.minLength === undefined && fieldDef.maxLength === undefined && !fieldDef.pattern) {
|
|
257
|
-
return undefined;
|
|
258
|
-
}
|
|
259
|
-
return {
|
|
260
|
-
email: fieldDef.email,
|
|
261
|
-
minLength: fieldDef.minLength,
|
|
262
|
-
maxLength: fieldDef.maxLength,
|
|
263
|
-
pattern: fieldDef.pattern,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
function getNumberValidationConfig(fieldDef) {
|
|
267
|
-
const minVal = fieldDef.min ?? fieldDef.minValue;
|
|
268
|
-
const maxVal = fieldDef.max ?? fieldDef.maxValue;
|
|
269
|
-
if (minVal === undefined && maxVal === undefined) {
|
|
270
|
-
return undefined;
|
|
271
|
-
}
|
|
272
|
-
return { min: minVal, max: maxVal };
|
|
273
|
-
}
|
|
274
|
-
function hasValidationRules(fieldDef, context) {
|
|
275
|
-
const declaresRequiredLogic = fieldDef.logic?.some(isValidationStateLogic) ?? false;
|
|
276
|
-
const required = fieldDef.required ?? (declaresRequiredLogic ? false : context.ancestorRequired);
|
|
277
|
-
return (required === true ||
|
|
278
|
-
declaresRequiredLogic ||
|
|
279
|
-
getStringValidationConfig(fieldDef) !== undefined ||
|
|
280
|
-
getNumberValidationConfig(fieldDef) !== undefined ||
|
|
281
|
-
(fieldDef.validators?.length ?? 0) > 0);
|
|
282
|
-
}
|
|
283
|
-
function mapFieldToForm(fieldDef, fieldPath, inheritedContext = DEFAULT_FIELD_TREE_MAPPING_CONTEXT) {
|
|
284
|
-
const ownContext = resolveFieldOwnContext(fieldDef, inheritedContext);
|
|
285
|
-
if (isPageField(fieldDef) || isRowField(fieldDef) || isGenericContainerField(fieldDef)) {
|
|
286
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
287
|
-
mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
if (isGroupField(fieldDef)) {
|
|
291
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
292
|
-
applyContainerValidators(fieldDef, fieldPath, descendantContext);
|
|
293
|
-
mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
if (isArrayField(fieldDef)) {
|
|
297
|
-
const descendantContext = resolveDescendantContext(fieldDef, ownContext);
|
|
298
|
-
mapArrayFieldToForm(fieldDef, fieldPath, descendantContext);
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
mapLeafField(fieldDef, fieldPath, ownContext);
|
|
302
|
-
}
|
|
303
|
-
function mapContainerChildren(fields, parentPath, inheritedContext) {
|
|
304
|
-
if (!fields)
|
|
305
|
-
return;
|
|
306
|
-
const pathRecord = parentPath;
|
|
307
|
-
for (const field of fields) {
|
|
308
|
-
if (isPageField(field) || isRowField(field) || isGenericContainerField(field)) {
|
|
309
|
-
const layoutOwn = resolveFieldOwnContext(field, inheritedContext);
|
|
310
|
-
const layoutDescendant = resolveDescendantContext(field, layoutOwn);
|
|
311
|
-
mapContainerChildren(field.fields, parentPath, layoutDescendant);
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
if (!field.key)
|
|
315
|
-
continue;
|
|
316
|
-
const childPath = pathRecord[field.key];
|
|
317
|
-
if (childPath) {
|
|
318
|
-
mapFieldToForm(field, childPath, inheritedContext);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
function isValidationStateLogic(config) {
|
|
323
|
-
return isStateLogicConfig(config) && config.type === 'required';
|
|
324
|
-
}
|
|
325
|
-
function mapLeafField(fieldDef, fieldPath, context) {
|
|
326
|
-
const validationField = fieldDef;
|
|
327
|
-
const path = fieldPath;
|
|
328
|
-
applyFieldState(fieldDef, path);
|
|
329
|
-
if (validationField.logic) {
|
|
330
|
-
for (const config of validationField.logic) {
|
|
331
|
-
if (!isValidationStateLogic(config)) {
|
|
332
|
-
applyLogic(config, fieldPath);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
if (validationField.schemas) {
|
|
337
|
-
for (const config of validationField.schemas) {
|
|
338
|
-
applySchema(config, fieldPath);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
if (!hasValidationRules(validationField, context)) {
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
const applyValidation = (validationPath) => {
|
|
345
|
-
applySimpleValidationRules(validationField, validationPath, context);
|
|
346
|
-
if (validationField.validators) {
|
|
347
|
-
for (const config of validationField.validators) {
|
|
348
|
-
applyValidator(config, validationPath);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
if (validationField.logic) {
|
|
352
|
-
for (const config of validationField.logic) {
|
|
353
|
-
if (isValidationStateLogic(config)) {
|
|
354
|
-
applyLogic(config, validationPath);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
applyHiddenGatedValidation(path, context, applyValidation);
|
|
360
|
-
}
|
|
361
|
-
function applyHiddenGatedValidation(path, context, apply) {
|
|
362
|
-
if (context.validateWhenHidden) {
|
|
363
|
-
apply(path);
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
if (context.ancestorAlwaysHidden) {
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
const ancestorHiddenLogic = combineAncestorHiddenLogics(context.ancestorHiddenLogics);
|
|
370
|
-
applyWhen(path, (ctx) => {
|
|
371
|
-
if (ctx.state.hidden())
|
|
372
|
-
return false;
|
|
373
|
-
if (ancestorHiddenLogic && ancestorHiddenLogic(ctx))
|
|
374
|
-
return false;
|
|
375
|
-
return true;
|
|
376
|
-
}, schema((subPath) => apply(subPath)));
|
|
377
|
-
}
|
|
378
|
-
function applyContainerValidators(fieldDef, fieldPath, context) {
|
|
379
|
-
const validators = fieldDef.validators;
|
|
380
|
-
if (!validators || validators.length === 0)
|
|
381
|
-
return;
|
|
382
|
-
applyHiddenGatedValidation(fieldPath, context, (validationPath) => {
|
|
383
|
-
for (const config of validators) {
|
|
384
|
-
applyValidator(config, validationPath, 'tree');
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
function applySimpleValidationRules(fieldDef, path, context) {
|
|
389
|
-
const declaresRequiredLogic = fieldDef.logic?.some(isValidationStateLogic) ?? false;
|
|
390
|
-
if (fieldDef.required ?? (declaresRequiredLogic ? false : context.ancestorRequired)) {
|
|
391
|
-
required(path);
|
|
392
|
-
}
|
|
393
|
-
const stringConfig = getStringValidationConfig(fieldDef);
|
|
394
|
-
if (stringConfig) {
|
|
395
|
-
applyStringValidators(path, stringConfig);
|
|
396
|
-
}
|
|
397
|
-
const numberConfig = getNumberValidationConfig(fieldDef);
|
|
398
|
-
if (numberConfig) {
|
|
399
|
-
applyNumberValidators(path, numberConfig);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
function applyFieldState(fieldDef, path) {
|
|
403
|
-
if (fieldDef.disabled) {
|
|
404
|
-
disabled(path);
|
|
405
|
-
}
|
|
406
|
-
if (fieldDef.readonly) {
|
|
407
|
-
readonly(path);
|
|
408
|
-
}
|
|
409
|
-
if (fieldDef.hidden) {
|
|
410
|
-
hidden(path, () => true);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
function mapArrayFieldToForm(arrayField, fieldPath, context) {
|
|
414
|
-
if (!isArrayField(arrayField)) {
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
if (arrayField.minLength !== undefined) {
|
|
418
|
-
minLength(fieldPath, arrayField.minLength);
|
|
419
|
-
}
|
|
420
|
-
if (arrayField.maxLength !== undefined) {
|
|
421
|
-
maxLength(fieldPath, arrayField.maxLength);
|
|
422
|
-
}
|
|
423
|
-
applyContainerValidators(arrayField, fieldPath, context);
|
|
424
|
-
let itemDefinitions = arrayField.fields;
|
|
425
|
-
if (!itemDefinitions || itemDefinitions.length === 0) {
|
|
426
|
-
const metadataTemplate = getNormalizedArrayMetadata(arrayField)?.template;
|
|
427
|
-
if (metadataTemplate) {
|
|
428
|
-
itemDefinitions = [
|
|
429
|
-
Array.isArray(metadataTemplate) ? [...metadataTemplate] : metadataTemplate,
|
|
430
|
-
];
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
if (!itemDefinitions || itemDefinitions.length === 0) {
|
|
434
|
-
const emptyItemSchema = schema(() => {
|
|
435
|
-
});
|
|
436
|
-
applyEach(fieldPath, emptyItemSchema);
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
let hasPrimitiveItems = false;
|
|
440
|
-
const allObjectFields = [];
|
|
441
|
-
for (const itemDef of itemDefinitions) {
|
|
442
|
-
if (Array.isArray(itemDef)) {
|
|
443
|
-
collectFieldsFromObjectItem(itemDef, allObjectFields);
|
|
444
|
-
}
|
|
445
|
-
else if (isPageField(itemDef) ||
|
|
446
|
-
isRowField(itemDef) ||
|
|
447
|
-
isGenericContainerField(itemDef)) {
|
|
448
|
-
collectFieldsFromObjectItem([itemDef], allObjectFields);
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
hasPrimitiveItems = true;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
if (hasPrimitiveItems && allObjectFields.length === 0) {
|
|
455
|
-
const primitiveItemSchema = schema(() => {
|
|
456
|
-
});
|
|
457
|
-
applyEach(fieldPath, primitiveItemSchema);
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
const itemSchema = schema((itemPath) => {
|
|
461
|
-
const pathRecord = itemPath;
|
|
462
|
-
for (const templateField of allObjectFields) {
|
|
463
|
-
if (isRowField(templateField) || isPageField(templateField) || isGenericContainerField(templateField)) {
|
|
464
|
-
const layoutOwn = resolveFieldOwnContext(templateField, context);
|
|
465
|
-
const layoutDescendant = resolveDescendantContext(templateField, layoutOwn);
|
|
466
|
-
mapContainerChildren(templateField.fields, itemPath, layoutDescendant);
|
|
467
|
-
}
|
|
468
|
-
else if (isGroupField(templateField)) {
|
|
469
|
-
const groupKey = templateField.key;
|
|
470
|
-
if (groupKey) {
|
|
471
|
-
const groupPath = pathRecord[groupKey];
|
|
472
|
-
if (groupPath) {
|
|
473
|
-
mapFieldToForm(templateField, groupPath, context);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
else {
|
|
477
|
-
const groupOwn = resolveFieldOwnContext(templateField, context);
|
|
478
|
-
mapContainerChildren(templateField.fields, itemPath, resolveDescendantContext(templateField, groupOwn));
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
482
|
-
const fieldKey = templateField.key;
|
|
483
|
-
if (fieldKey) {
|
|
484
|
-
const fieldPathForKey = pathRecord[fieldKey];
|
|
485
|
-
if (fieldPathForKey) {
|
|
486
|
-
mapFieldToForm(templateField, fieldPathForKey, context);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
applyEach(fieldPath, itemSchema);
|
|
493
|
-
}
|
|
494
|
-
function collectFieldsFromObjectItem(itemFields, allFields) {
|
|
495
|
-
const seenKeys = new Set(allFields.map((f) => f.key).filter(Boolean));
|
|
496
|
-
for (const field of itemFields) {
|
|
497
|
-
if (isRowField(field) || isPageField(field) || isGenericContainerField(field)) {
|
|
498
|
-
const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;
|
|
499
|
-
if (!seenKeys.has(containerKey)) {
|
|
500
|
-
seenKeys.add(containerKey);
|
|
501
|
-
allFields.push(field);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
else {
|
|
505
|
-
const key = field.key;
|
|
506
|
-
if (key && !seenKeys.has(key)) {
|
|
507
|
-
seenKeys.add(key);
|
|
508
|
-
allFields.push(field);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function createSchemaFromFields(fields, registry, options = {}) {
|
|
515
|
-
const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);
|
|
516
|
-
const { formLevelSchema } = options;
|
|
517
|
-
const rootContext = {
|
|
518
|
-
validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,
|
|
519
|
-
ancestorAlwaysHidden: false,
|
|
520
|
-
ancestorHiddenLogics: [],
|
|
521
|
-
};
|
|
522
|
-
return schema((path) => {
|
|
523
|
-
for (const fieldDef of fields) {
|
|
524
|
-
const valueHandling = getFieldValueHandling(fieldDef.type, registry);
|
|
525
|
-
if (valueHandling === 'exclude') {
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
528
|
-
if (valueHandling === 'flatten' && hasChildFields(fieldDef)) {
|
|
529
|
-
const flattenedOwn = resolveFieldOwnContext(fieldDef, rootContext);
|
|
530
|
-
const flattenedContext = resolveDescendantContext(fieldDef, flattenedOwn);
|
|
531
|
-
for (const childField of normalizeFieldsArray(fieldDef.fields)) {
|
|
532
|
-
if (!childField.key)
|
|
533
|
-
continue;
|
|
534
|
-
const childPath = path[childField.key];
|
|
535
|
-
if (childPath) {
|
|
536
|
-
mapFieldToForm(childField, childPath, flattenedContext);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
const fieldPath = path[fieldDef.key];
|
|
542
|
-
if (!fieldPath) {
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
mapFieldToForm(fieldDef, fieldPath, rootContext);
|
|
546
|
-
}
|
|
547
|
-
if (formLevelSchema) {
|
|
548
|
-
applyFormLevelSchema(path, formLevelSchema);
|
|
549
|
-
}
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
function fieldsToDefaultValues(fields, registry) {
|
|
553
|
-
const defaultValues = {};
|
|
554
|
-
for (const field of fields) {
|
|
555
|
-
if (!field.key)
|
|
556
|
-
continue;
|
|
557
|
-
const valueHandling = getFieldValueHandling(field.type, registry);
|
|
558
|
-
if (valueHandling === 'flatten') {
|
|
559
|
-
continue;
|
|
560
|
-
}
|
|
561
|
-
const value = getFieldDefaultValue(field, registry);
|
|
562
|
-
if (value !== undefined) {
|
|
563
|
-
defaultValues[field.key] = value;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
return defaultValues;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
export { createFormLevelSchema as a, createSchemaFromFields as c };
|
|
570
|
-
//# sourceMappingURL=ng-forge-dynamic-forms-schema-builder-BvDTzfdM.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ng-forge-dynamic-forms-schema-builder-BvDTzfdM.mjs","mappings":";;;;;;;AAmBM;AACJ;wBAIG;;AACI;;;AAMT;AASM;AACJ;AACE;AACF;AACF;;ACzBA;AACE;AACE;;AAEF;AACF;AAGA;AACE;AACE;;IAGF;;;AAGA;AACF;AAEM;;;IAKJ;;AAEI;AAEI;;;;;AAOR;;AAGA;AACE;;;IAKF;gBAGE;AACE;AAEI;;;AAIA;;AAGN;;;IAKF;;AAGI;;AAGN;AAEA;;AAEI;AACE;;AAEF;AACE;;AAEF;AACE;;AAEF;;;;;AAKE;;;AAGN;AAEM;AACJ;AACF;;ACtGM;;AAEF;YAEE;AACA;;;;AAGA;;AAEJ;AACF;;ACNM;AACJ;AACA;;;;AAKE;AAEI;AACA;;;;;AAUN;AACE;AACE;;AAGF;AACE;;AAEE;;;AAIJ;AACE;;AAEE;;;AAIJ;AACE;;;AAGN;AAGM;;;AAIA;AACF;;AAIE;AACF;;AAIE;AACF;AACF;AACF;;AC3BM;AACJ;AAAyB;AACzB;AAAyB;AACzB;AACF;AAYM;AAIJ;AAA+C;AAC/C;AAAsE;;AAEpE;;;AAGJ;AAUM;AAIJ;AACA;;YAMA;AACA;AACE;;;;;;;AASF;AACE;;;AAGE;AAEA;;;;;;;;;;;wBAgBA;;;;;AAMJ;AAAc;;;;;;;AAQhB;;ACvGA;AACE;AACA;AACA;;AA0BF;AACE;;;AAGA;AACE;;AAEF;AACE;;AAEF;AACE;AACA;AACE;;;;;;;;AAMA;;AAEF;;AAEJ;AAMA;AACE;AACE;;AAEF;AACE;;AAEJ;AAMA;;AAEI;;;;;;;;AAQJ;AAOA;;;;AAKI;;;AAGJ;AAGM;AACJ;AACA;;;AAKE;AACA;;AAGJ;AAWM;;YAUJ;;;;;gBAUA;;AAEE;;;;IAMF;;AAEE;;;YAOF;AACF;AAGA;AAKE;;;AAIA;AACE;;;;;;;;;;AAaE;;;AAGN;AAOA;;AAEA;AASA;;;YAOE;AAEA;AACE;AACE;AACE;;;;AAKN;AACE;AACE;;;;;;IASJ;AACE;AAEA;AACE;AACE;;;AAIJ;AACE;AACE;AACE;;;;AAIR;AAEA;AACF;AAGA;AAKE;;;;QAOA;;;;AAWA;AAGI;AAAwB;AACxB;AAAqD;AACrD;AACF;AAGJ;AAMA;AACE;AACA;;;AAGE;AACE;;AAEJ;AACF;AAMA;YAIE;AACA;;;IAKA;;AAEE;;IAIF;;AAEE;;AAEJ;AAGA;AACE;;;AAIA;;;AAIA;;;AAGF;AAGA;AACE;;;IAKA;AACE;;AAEF;AACE;;AAGF;IAGA;;;;AASI;AACE;;;;;AAQJ;YAEA;AACA;;;;;AAQF;AACE;YAEE;;;;AAIA;oCAKA;;;;;;;AASF;YAEA;AACA;;;QAMF;;QAIE;AACE;;;;;AAKO;AACL;;oBAGE;;AAEE;;;;;AAKF;;;;gBAIF;;AAEE;;AAEE;;;;;AAKV;AAEA;AACF;AAOA;;AAGE;QAEE;YAEE;;AAEE;AACA;;;;AAGF;;AAEE;AACA;;;;AAIR;;AChfM;QAOJ;AAEA;AACA;AACE;AACA;AACA;;AAGF;AACE;;YAIE;;;;;;;;;;;AAgBM;;;;;;;;;oCAgBN;;;AAKA;;AAEJ;AACF;AAGM;;AAGJ;;;;AAKE;;;;AAKA;AACE;;;AAIJ;AACF","names":[],"ignoreList":[],"sources":["../../../../packages/dynamic-forms/src/lib/core/form-schema-merger.ts","../../../../packages/dynamic-forms/src/lib/core/logic/logic-applicator.ts","../../../../packages/dynamic-forms/src/lib/core/values/type-predicate-factory.ts","../../../../packages/dynamic-forms/src/lib/core/schema-application.ts","../../../../packages/dynamic-forms/src/lib/core/field-tree-mapping-context.ts","../../../../packages/dynamic-forms/src/lib/core/form-mapping.ts","../../../../packages/dynamic-forms/src/lib/core/schema-builder.ts"],"sourcesContent":["import { schema, Schema, SchemaPathTree, validateStandardSchema } from '@angular/forms/signals';\n// Import from secondary entry point - this is correct for ng-packagr's entry point architecture\n// (different from importing internal barrel files which should be avoided)\nimport { isStandardSchemaMarker, type FormSchema } from '@ng-forge/dynamic-forms/schema';\n\n/**\n * Applies a form-level schema validation to a schema path.\n * Supports both Standard Schema (Zod, Valibot, ArkType) and raw Angular schema callbacks.\n *\n * @typeParam TModel - The form value type\n * @param path - The schema path to validate (from Angular's schema callback)\n * @param formLevelSchema - Form-level schema (Standard Schema marker or Angular callback)\n * @remarks\n * Type assertions are required due to Angular's complex generic constraints:\n * - `validateStandardSchema` uses `IgnoreUnknownProperties<TSchema>` to accommodate Zod's strict types\n * - `SchemaPathTree` includes `SchemaPath` via conditional types that TypeScript can't narrow\n * These assertions are safe because the path originates from Angular's schema() function.\n * @internal\n */\nexport function applyFormLevelSchema<TModel>(path: SchemaPathTree<TModel>, formLevelSchema: FormSchema<TModel>): void {\n if (isStandardSchemaMarker(formLevelSchema)) {\n // Standard Schema (Zod, Valibot, ArkType, etc.)\n // Cast via Parameters: validateStandardSchema's IgnoreUnknownProperties<TSchema> creates type incompatibility\n // that TypeScript cannot resolve. The cast is safe as path comes from Angular's schema() callback.\n (validateStandardSchema as (...args: unknown[]) => void)(path, formLevelSchema.schema);\n } else if (typeof formLevelSchema === 'function') {\n // Angular schema callback - execute directly with the path\n // Cast via Function: AngularSchemaCallback expects intersection type SchemaPath & SchemaPathTree.\n // SchemaPathTree structurally includes SchemaPath, so this is safe at runtime.\n (formLevelSchema as (path: unknown) => void)(path);\n }\n}\n\n/**\n * Creates a schema from only form-level schema (when no field-level schema exists).\n *\n * @typeParam TModel - The form value type\n * @param formLevelSchema - Form-level Standard Schema\n * @returns Schema that applies form-level validation\n */\nexport function createFormLevelSchema<TModel>(formLevelSchema: FormSchema<TModel>): Schema<TModel> {\n return schema<TModel>((path) => {\n applyFormLevelSchema(path, formLevelSchema);\n });\n}\n","import { disabled, hidden, readonly, required, LogicFn } from '@angular/forms/signals';\nimport { DEV_MODE } from '../../utils/dev-mode';\nimport type { SchemaPath, SchemaPathTree } from '@angular/forms/signals';\nimport {\n LogicConfig,\n isStateLogicConfig,\n isDerivationLogicConfig,\n isFormStateCondition,\n LogicTrigger,\n type StateLogicType,\n} from '@ng-forge/dynamic-forms/internal';\nimport { ConditionalExpression } from '@ng-forge/dynamic-forms/internal';\nimport { createLogicFunction, createDebouncedLogicFunction, createFormStateLogicFunction } from '@ng-forge/dynamic-forms/internal';\nimport { DEFAULT_DEBOUNCE_MS } from '../../utils/debounce/debounce';\nimport { DynamicFormError } from '@ng-forge/dynamic-forms/internal';\n\ntype AnyLogicFn<TValue> = LogicFn<TValue, boolean> | (() => boolean);\n\n/** Extracts the trigger from a StateLogicConfig, handling the discriminated union. */\nfunction getConfigTrigger(config: LogicConfig): LogicTrigger {\n if (!isStateLogicConfig(config)) {\n return 'onChange';\n }\n return config.trigger ?? 'onChange';\n}\n\n/** Extracts debounceMs from a StateLogicConfig if trigger is 'debounced'. */\nfunction getConfigDebounceMs(config: LogicConfig): number | undefined {\n if (!isStateLogicConfig(config)) {\n return undefined;\n }\n // Type narrowing: debounceMs only exists when trigger is 'debounced'\n if (config.trigger === 'debounced') {\n return config.debounceMs;\n }\n return undefined;\n}\n\nexport function applyLogic<TValue>(config: LogicConfig, fieldPath: SchemaPath<TValue> | SchemaPathTree<TValue>): void {\n // Value derivations (including property derivations via targetProperty) are handled by their orchestrators — skip them.\n if (isDerivationLogicConfig(config)) return;\n\n // Guard against unrecognized logic types that may be added in the future.\n if (!isStateLogicConfig(config)) {\n if (DEV_MODE) {\n console.warn(\n `[Dynamic Forms] Unrecognized logic config type '${(config as { type: string }).type}' in applyLogic. ` +\n 'This config will be ignored. If this is a new logic type, ensure it is handled explicitly.',\n );\n }\n return;\n }\n\n const path = fieldPath as SchemaPath<TValue>;\n const trigger = getConfigTrigger(config);\n const debounceMs = getConfigDebounceMs(config) ?? DEFAULT_DEBOUNCE_MS;\n\n if (typeof config.condition === 'boolean') {\n applyLogicFn(config.type, path, () => config.condition as boolean);\n return;\n }\n\n // Form-state conditions read root form state, not the expression evaluator.\n if (isFormStateCondition(config.condition)) {\n // formInvalid cycles against the field's own validity; pageInvalid has no leaf page context.\n // Only formSubmitting (an independent flag) is cycle-free on value fields.\n if (config.condition === 'formInvalid' || config.condition === 'pageInvalid') {\n const reason =\n config.condition === 'formInvalid'\n ? `it would create a validation cycle against the form's own validity`\n : `a value field has no page context for it to resolve against`;\n throw new DynamicFormError(\n `The '${config.condition}' form-state condition is not supported on value-field logic because ${reason}. ` +\n `Use 'formSubmitting', or apply this condition to a button or container instead.`,\n );\n }\n applyLogicFn(config.type, path, createFormStateLogicFunction(config.condition));\n return;\n }\n\n // Create appropriate logic function based on trigger\n const logicFn =\n trigger === 'debounced'\n ? createDebouncedLogicFunction(config.condition as ConditionalExpression, debounceMs)\n : createLogicFunction(config.condition as ConditionalExpression);\n\n applyLogicFn(config.type, path, logicFn);\n}\n\nfunction applyLogicFn<TValue>(type: StateLogicType, path: SchemaPath<TValue>, logicFn: AnyLogicFn<TValue>): void {\n switch (type) {\n case 'hidden':\n hidden(path, logicFn);\n break;\n case 'readonly':\n readonly(path, logicFn);\n break;\n case 'disabled':\n disabled(path, logicFn);\n break;\n case 'required':\n required(path, { when: logicFn });\n break;\n default: {\n const _exhaustive: never = type;\n throw new DynamicFormError(`Unhandled state logic type: ${_exhaustive}`);\n }\n }\n}\n\nexport function applyMultipleLogic<TValue>(configs: LogicConfig[], fieldPath: SchemaPath<TValue> | SchemaPathTree<TValue>): void {\n configs.forEach((config) => applyLogic(config, fieldPath));\n}\n","import type { Logger } from '@ng-forge/dynamic-forms/internal';\nimport { ExpressionParser } from '@ng-forge/dynamic-forms/internal';\n\n/**\n * Create a type predicate function from a predicate string.\n *\n * @param predicate - A JavaScript expression that evaluates to boolean. The value to check is available as `value`.\n * @param logger - Logger for error reporting\n * @returns A type predicate function\n */\nexport function createTypePredicateFunction<T = unknown>(predicate: string, logger: Logger): (value: unknown) => value is T {\n return (value: unknown): value is T => {\n try {\n // Evaluate using secure AST-based parser with value in scope\n const result = ExpressionParser.evaluate(predicate, { value });\n return Boolean(result);\n } catch (error) {\n logger.error('Error evaluating type predicate:', predicate, error);\n return false;\n }\n };\n}\n","import { inject } from '@angular/core';\nimport { apply, applyEach, applyWhen, applyWhenValue, SchemaOrSchemaFn } from '@angular/forms/signals';\nimport type { SchemaPath, SchemaPathTree } from '@angular/forms/signals';\nimport { SchemaApplicationConfig, SchemaDefinition } from '@ng-forge/dynamic-forms/internal';\nimport { SchemaRegistryService } from './registry/schema-registry.service';\nimport { createLogicFunction } from '@ng-forge/dynamic-forms/internal';\nimport { createTypePredicateFunction } from './values/type-predicate-factory';\nimport { applyValidator } from '@ng-forge/dynamic-forms/internal';\nimport { applyLogic } from './logic/logic-applicator';\nimport { DynamicFormLogger, type Logger } from '@ng-forge/dynamic-forms/internal';\n\n/**\n * Apply schema configuration.\n * Accepts both SchemaPath and SchemaPathTree for flexibility.\n */\nexport function applySchema(config: SchemaApplicationConfig, fieldPath: SchemaPath<unknown> | SchemaPathTree<unknown>): void {\n const logger = inject(DynamicFormLogger);\n const schemaRegistry = inject(SchemaRegistryService);\n const schema = schemaRegistry.resolveSchema(config.schema);\n\n if (!schema) {\n const availableSchemas = Array.from(schemaRegistry.getAllSchemas().keys()).join(', ') || '<none>';\n logger.error(\n `Schema not found: '${config.schema}'. ` +\n `Available schemas: ${availableSchemas}. ` +\n `Ensure the schema is registered in your schema registry.`,\n );\n return;\n }\n\n const schemaFn = createSchemaFunction(schema, logger);\n\n // Cast to suppress union type errors - safe because we only use signal forms (see function docs)\n const path = fieldPath as SchemaPath<unknown>;\n\n switch (config.type) {\n case 'apply':\n apply(path, schemaFn);\n break;\n\n case 'applyWhen':\n if (config.condition) {\n const conditionFn = createLogicFunction(config.condition);\n applyWhen(path, conditionFn, schemaFn);\n }\n break;\n\n case 'applyWhenValue':\n if (config.typePredicate) {\n const predicate = createTypePredicateFunction(config.typePredicate, logger);\n applyWhenValue(path, predicate, schemaFn);\n }\n break;\n\n case 'applyEach':\n applyEach(path as SchemaPath<unknown[]>, schemaFn);\n break;\n }\n}\n\n/** Create a schema function from schema definition. */\nexport function createSchemaFunction(schema: SchemaDefinition, logger?: Logger): SchemaOrSchemaFn<unknown> {\n return (path: SchemaPathTree<unknown>) => {\n // Apply validators - path is SchemaPathTree which is accepted by applyValidator\n schema.validators?.forEach((validatorConfig) => {\n applyValidator(validatorConfig, path);\n });\n\n // Apply logic - path is SchemaPathTree which is accepted by applyLogic\n schema.logic?.forEach((logicConfig) => {\n applyLogic(logicConfig, path);\n });\n\n // Apply sub-schemas - path is SchemaPathTree which is accepted by applySchema\n schema.subSchemas?.forEach((subSchemaConfig) => {\n applySchema(subSchemaConfig, path);\n });\n };\n}\n","import { FieldContext, LogicFn } from '@angular/forms/signals';\nimport { FieldDef } from '@ng-forge/dynamic-forms/internal';\nimport { FieldWithValidation } from '@ng-forge/dynamic-forms/internal';\nimport { isStateLogicConfig } from '@ng-forge/dynamic-forms/internal';\nimport { createLogicFunction } from '@ng-forge/dynamic-forms/internal';\n\n/**\n * Cascading configuration that flows from parent to child during schema mapping.\n *\n * @internal\n */\nexport interface FieldTreeMappingContext {\n /**\n * Whether validators on this field run while the field (or any ancestor) is\n * hidden. Defaults to `false` at the global level.\n */\n readonly validateWhenHidden: boolean;\n\n /**\n * `true` when an ancestor is unconditionally hidden (`hidden: true` or\n * `logic: [{type: 'hidden', condition: true}]`). When set together with\n * `validateWhenHidden=false`, the descendant skips validator application\n * entirely — there's no reactive case to gate on.\n */\n readonly ancestorAlwaysHidden: boolean;\n\n /**\n * Reactive logic functions for ancestor-level dynamic hidden conditions\n * (`logic: [{type: 'hidden', condition: ...}]`). Layout containers do not\n * have a schema path, so Angular Signal Forms cannot propagate their hidden\n * state to descendants — this list does it manually. Each function evaluates\n * against the current form value through the field context.\n */\n readonly ancestorHiddenLogics: readonly LogicFn<unknown, boolean>[];\n\n /**\n * `required` inherited from the nearest ancestor container that declared it\n * (see `ContainerValidation.required`). `undefined` when no ancestor did.\n *\n * A field's own `required` always wins — `fieldDef.required ?? ancestorRequired`\n * — so an explicit `required: false` opts back out of an inherited `true`.\n */\n readonly ancestorRequired?: boolean;\n}\n\n/**\n * Combines the inherited \"is any ancestor hidden\" check into a single LogicFn.\n * Returns `undefined` when no ancestor has dynamic hidden logic.\n *\n * @internal\n */\nexport function combineAncestorHiddenLogics(logics: readonly LogicFn<unknown, boolean>[]): LogicFn<unknown, boolean> | undefined {\n if (logics.length === 0) return undefined;\n if (logics.length === 1) return logics[0];\n return (ctx: FieldContext<unknown>) => logics.some((fn) => fn(ctx));\n}\n\n/**\n * Returns the `FieldTreeMappingContext` to use **for the field itself** — i.e.\n * for gating its own validators. Only applies overrides that affect the field\n * (currently `validateWhenHidden`). The field's own hidden state is NOT folded\n * into `ancestorAlwaysHidden`/`ancestorHiddenLogics` here — Angular Signal\n * Forms already exposes that through `ctx.state.hidden()`, which the gate\n * checks separately.\n *\n * @internal\n */\nexport function resolveFieldOwnContext(\n fieldDef: Pick<FieldDef<unknown>, 'validateWhenHidden'>,\n parentContext: FieldTreeMappingContext,\n): FieldTreeMappingContext {\n if (fieldDef.validateWhenHidden === undefined) return parentContext;\n if (fieldDef.validateWhenHidden === parentContext.validateWhenHidden) return parentContext;\n return {\n ...parentContext,\n validateWhenHidden: fieldDef.validateWhenHidden,\n };\n}\n\n/**\n * Returns the `FieldTreeMappingContext` to use **for the field's descendants**.\n * Builds on top of `ownContext` by folding in the field's own hidden state so\n * children can correctly skip validation when this field hides them. Used for\n * groups, arrays, and layout containers that recurse into children.\n *\n * @internal\n */\nexport function resolveDescendantContext(\n fieldDef: FieldDef<unknown> & Partial<Pick<FieldWithValidation, 'logic' | 'required'>>,\n ownContext: FieldTreeMappingContext,\n): FieldTreeMappingContext {\n let ancestorAlwaysHidden = ownContext.ancestorAlwaysHidden;\n let ancestorHiddenLogics = ownContext.ancestorHiddenLogics;\n let mutated = false;\n\n // A container that declares `required` becomes the new inherited default for\n // its subtree. Only `group` / `array` can express this (see ContainerValidation);\n // layout containers have no such property in their types.\n let ancestorRequired = ownContext.ancestorRequired;\n if (fieldDef.required !== undefined && fieldDef.required !== ancestorRequired) {\n ancestorRequired = fieldDef.required;\n mutated = true;\n }\n\n if (fieldDef.hidden === true && !ancestorAlwaysHidden) {\n ancestorAlwaysHidden = true;\n mutated = true;\n }\n\n if (fieldDef.logic) {\n for (const logic of fieldDef.logic) {\n if (!isStateLogicConfig(logic) || logic.type !== 'hidden') continue;\n\n const condition = logic.condition;\n\n if (condition === true) {\n if (!ancestorAlwaysHidden) {\n ancestorAlwaysHidden = true;\n mutated = true;\n }\n continue;\n }\n\n if (condition === false) continue;\n\n // FormStateCondition strings ('formInvalid', etc.) are intended for buttons —\n // they don't make sense as a \"field hidden\" trigger, so we skip them here.\n if (typeof condition === 'string') continue;\n\n // Dynamic — we need a reactive logic function. createLogicFunction\n // requires an injection context, which the schema build provides.\n const fn = createLogicFunction(condition) as LogicFn<unknown, boolean>;\n ancestorHiddenLogics = ancestorHiddenLogics.length === 0 ? [fn] : [...ancestorHiddenLogics, fn];\n mutated = true;\n }\n }\n\n if (!mutated) return ownContext;\n\n return {\n validateWhenHidden: ownContext.validateWhenHidden,\n ancestorAlwaysHidden,\n ancestorHiddenLogics,\n ancestorRequired,\n };\n}\n","import {\n applyEach,\n applyWhen,\n disabled,\n email,\n hidden,\n max,\n maxLength,\n min,\n minLength,\n pattern,\n readonly,\n required,\n schema,\n} from '@angular/forms/signals';\nimport type { FieldContext, SchemaPath, SchemaPathTree } from '@angular/forms/signals';\nimport { FieldDef } from '@ng-forge/dynamic-forms/internal';\nimport { FieldWithValidation } from '@ng-forge/dynamic-forms/internal';\nimport { ContainerValidation } from '@ng-forge/dynamic-forms/internal';\nimport { applyValidator } from '@ng-forge/dynamic-forms/internal';\nimport { applyLogic } from './logic/logic-applicator';\nimport { applySchema } from './schema-application';\nimport { isGroupField } from '@ng-forge/dynamic-forms/internal';\nimport { isArrayField } from '@ng-forge/dynamic-forms/internal';\nimport { isPageField } from '@ng-forge/dynamic-forms/internal';\nimport { isRowField } from '@ng-forge/dynamic-forms/internal';\nimport { isGenericContainerField } from '@ng-forge/dynamic-forms/internal';\nimport { getNormalizedArrayMetadata } from '../utils/array-field/normalized-array-metadata';\nimport { DynamicFormError } from '@ng-forge/dynamic-forms/internal';\nimport { isStateLogicConfig, LogicConfig } from '@ng-forge/dynamic-forms/internal';\nimport {\n combineAncestorHiddenLogics,\n FieldTreeMappingContext,\n resolveDescendantContext,\n resolveFieldOwnContext,\n} from './field-tree-mapping-context';\n\n/**\n * Default {@link FieldTreeMappingContext} used when no inherited context is provided.\n * Mirrors the global token defaults so callers that bypass `createSchemaFromFields`\n * (e.g. nested group forms) still get sensible behavior.\n */\nconst DEFAULT_FIELD_TREE_MAPPING_CONTEXT: FieldTreeMappingContext = {\n validateWhenHidden: false,\n ancestorAlwaysHidden: false,\n ancestorHiddenLogics: [],\n};\n\n// Type alias for schema path parameters\ntype AnySchemaPath = SchemaPath<unknown> | SchemaPathTree<unknown>;\n\n// ============================================================================\n// Type-safe validator application functions\n// ============================================================================\n\ninterface StringValidationConfig {\n email?: boolean;\n minLength?: number;\n maxLength?: number;\n pattern?: string | RegExp;\n}\n\ninterface NumberValidationConfig {\n min?: number;\n max?: number;\n}\n\n/**\n * Applies string-specific validators to a schema path.\n * This function is properly typed - no casts needed internally.\n */\nfunction applyStringValidators(path: SchemaPath<string>, config: StringValidationConfig): void {\n if (config.email) {\n email(path);\n }\n if (config.minLength !== undefined) {\n minLength(path, config.minLength);\n }\n if (config.maxLength !== undefined) {\n maxLength(path, config.maxLength);\n }\n if (config.pattern) {\n let regex: RegExp;\n if (typeof config.pattern === 'string') {\n try {\n regex = new RegExp(config.pattern);\n } catch (e) {\n throw new DynamicFormError(`Invalid regex pattern: '${config.pattern}' — ${e instanceof Error ? e.message : String(e)}`);\n }\n } else {\n regex = config.pattern;\n }\n pattern(path, regex);\n }\n}\n\n/**\n * Applies number-specific validators to a schema path.\n * This function is properly typed - no casts needed internally.\n */\nfunction applyNumberValidators(path: SchemaPath<number>, config: NumberValidationConfig): void {\n if (config.min !== undefined) {\n min(path, config.min);\n }\n if (config.max !== undefined) {\n max(path, config.max);\n }\n}\n\n/**\n * Extracts string validation config from a field definition.\n * Returns undefined if no string validators are configured.\n */\nfunction getStringValidationConfig(fieldDef: FieldWithValidation): StringValidationConfig | undefined {\n if (!fieldDef.email && fieldDef.minLength === undefined && fieldDef.maxLength === undefined && !fieldDef.pattern) {\n return undefined;\n }\n return {\n email: fieldDef.email,\n minLength: fieldDef.minLength,\n maxLength: fieldDef.maxLength,\n pattern: fieldDef.pattern,\n };\n}\n\n/**\n * Extracts number validation config from a field definition.\n * Handles both 'min'/'max' (standard) and 'minValue'/'maxValue' (SliderField).\n * Returns undefined if no number validators are configured.\n */\nfunction getNumberValidationConfig(fieldDef: FieldWithValidation): NumberValidationConfig | undefined {\n const minVal = fieldDef.min ?? (fieldDef as { minValue?: number }).minValue;\n const maxVal = fieldDef.max ?? (fieldDef as { maxValue?: number }).maxValue;\n\n if (minVal === undefined && maxVal === undefined) {\n return undefined;\n }\n return { min: minVal, max: maxVal };\n}\n\n/** Returns whether mapping this leaf needs a validation schema branch. */\nexport function hasValidationRules(fieldDef: FieldDef<unknown> & FieldWithValidation, context: FieldTreeMappingContext): boolean {\n const declaresRequiredLogic = fieldDef.logic?.some(isValidationStateLogic) ?? false;\n const required = fieldDef.required ?? (declaresRequiredLogic ? false : context.ancestorRequired);\n\n return (\n required === true ||\n declaresRequiredLogic ||\n getStringValidationConfig(fieldDef) !== undefined ||\n getNumberValidationConfig(fieldDef) !== undefined ||\n (fieldDef.validators?.length ?? 0) > 0\n );\n}\n\n/**\n * Maps a field definition to the Angular Signal Forms schema.\n *\n * @param fieldDef Field definition to map\n * @param fieldPath Schema path for this field\n * @param inheritedContext Cascading mapping context from the parent. Field-level\n * overrides on `fieldDef` are merged on top and passed down to descendants. Defaults\n * to {@link DEFAULT_FIELD_TREE_MAPPING_CONTEXT} when omitted.\n */\nexport function mapFieldToForm(\n fieldDef: FieldDef<unknown>,\n fieldPath: AnySchemaPath,\n inheritedContext: FieldTreeMappingContext = DEFAULT_FIELD_TREE_MAPPING_CONTEXT,\n): void {\n const ownContext = resolveFieldOwnContext(fieldDef, inheritedContext);\n\n // Layout containers (page, row, container) - flatten children to current level.\n // Layout containers have no schema path of their own, so any hidden state on them\n // must be propagated to descendants through the cascade.\n if (isPageField(fieldDef) || isRowField(fieldDef) || isGenericContainerField(fieldDef)) {\n const descendantContext = resolveDescendantContext(fieldDef, ownContext);\n mapContainerChildren(fieldDef.fields as FieldDef<unknown>[] | undefined, fieldPath, descendantContext);\n return;\n }\n\n // Group fields - map children under the group's path. Groups have a schema path,\n // so Angular Signal Forms can propagate `hidden(path, ...)` calls to descendants —\n // but groups don't currently apply their own hidden state, so we still propagate\n // through the cascade as a belt-and-suspenders fallback.\n if (isGroupField(fieldDef)) {\n const descendantContext = resolveDescendantContext(fieldDef, ownContext);\n applyContainerValidators(fieldDef, fieldPath, descendantContext);\n mapContainerChildren(fieldDef.fields, fieldPath, descendantContext);\n return;\n }\n\n // Array fields - use applyEach for item schema.\n if (isArrayField(fieldDef)) {\n const descendantContext = resolveDescendantContext(fieldDef, ownContext);\n mapArrayFieldToForm(fieldDef, fieldPath, descendantContext);\n return;\n }\n\n // Leaf field - apply validation, logic, and configuration. Use the field's OWN\n // context (no descendant propagation) because the leaf's own hidden state is\n // already exposed via `ctx.state.hidden()`.\n mapLeafField(fieldDef, fieldPath, ownContext);\n}\n\n/** Maps children of a container field (page, row, group) to the form schema. */\nfunction mapContainerChildren(\n fields: readonly FieldDef<unknown>[] | undefined,\n parentPath: AnySchemaPath,\n inheritedContext: FieldTreeMappingContext,\n): void {\n if (!fields) return;\n\n const pathRecord = parentPath as Record<string, AnySchemaPath>;\n\n for (const field of fields) {\n if (isPageField(field) || isRowField(field) || isGenericContainerField(field)) {\n // Layout containers can still carry their own cascading overrides that flow\n // down to their flattened descendants.\n const layoutOwn = resolveFieldOwnContext(field, inheritedContext);\n const layoutDescendant = resolveDescendantContext(field, layoutOwn);\n mapContainerChildren(field.fields as readonly FieldDef<unknown>[] | undefined, parentPath, layoutDescendant);\n continue;\n }\n\n if (!field.key) continue;\n\n const childPath = pathRecord[field.key];\n if (childPath) {\n mapFieldToForm(field, childPath, inheritedContext);\n }\n }\n}\n\n/**\n * Returns true for state logic that contributes validation (currently only `required`).\n * The other state logic types (`hidden`, `disabled`, `readonly`) must always apply,\n * even on hidden fields, so they're not gated by `validateWhenHidden`.\n */\nfunction isValidationStateLogic(config: LogicConfig): boolean {\n return isStateLogicConfig(config) && config.type === 'required';\n}\n\n/**\n * Maps a leaf field (value-bearing field) to the form schema.\n *\n * @param context Resolved cascading context for this field. When\n * `context.validateWhenHidden` is `false`, the field's validation block is wrapped\n * in `applyWhen` keyed off `!ctx.state.hidden()`.\n */\nfunction mapLeafField(fieldDef: FieldDef<unknown>, fieldPath: AnySchemaPath, context: FieldTreeMappingContext): void {\n const validationField = fieldDef as FieldDef<unknown> & FieldWithValidation;\n const path = fieldPath as SchemaPath<unknown>;\n\n // Always-applied: state logic (hidden/disabled/readonly), schemas, and value derivations.\n // These must run regardless of hidden state — particularly the `hidden` logic itself,\n // which establishes the very signal `applyWhen` reads below.\n applyFieldState(fieldDef, path);\n\n if (validationField.logic) {\n for (const config of validationField.logic) {\n if (!isValidationStateLogic(config)) {\n applyLogic(config, fieldPath);\n }\n }\n }\n\n if (validationField.schemas) {\n for (const config of validationField.schemas) {\n applySchema(config, fieldPath);\n }\n }\n\n if (!hasValidationRules(validationField, context)) {\n return;\n }\n\n // Validation block — gated by `validateWhenHidden` when hidden.\n const applyValidation = (validationPath: SchemaPath<unknown>): void => {\n applySimpleValidationRules(validationField, validationPath, context);\n\n if (validationField.validators) {\n for (const config of validationField.validators) {\n applyValidator(config, validationPath);\n }\n }\n\n if (validationField.logic) {\n for (const config of validationField.logic) {\n if (isValidationStateLogic(config)) {\n applyLogic(config, validationPath);\n }\n }\n }\n };\n\n applyHiddenGatedValidation(path, context, applyValidation);\n}\n\n/** Applies a validation block through the `validateWhenHidden` gate. Shared by leaves and containers. */\nfunction applyHiddenGatedValidation(\n path: SchemaPath<unknown>,\n context: FieldTreeMappingContext,\n apply: (validationPath: SchemaPath<unknown>) => void,\n): void {\n if (context.validateWhenHidden) {\n apply(path);\n return;\n }\n\n // An ancestor is unconditionally hidden — there is no reactive case for the\n // validators to ever fire, so we simply do not apply them.\n if (context.ancestorAlwaysHidden) {\n return;\n }\n\n // applyWhen conditionally applies a sub-schema. The gate combines:\n // - the field's own `state.hidden()` (set by Angular Signal Forms whenever\n // `hidden(path, ...)` was called for this field or any ancestor with a\n // path); and\n // - any ancestor dynamic hidden logic that does NOT live on a schema path\n // (layout containers — see field-tree-mapping-context for details).\n const ancestorHiddenLogic = combineAncestorHiddenLogics(context.ancestorHiddenLogics);\n applyWhen(\n path,\n (ctx: FieldContext<unknown>) => {\n if (ctx.state.hidden()) return false;\n if (ancestorHiddenLogic && ancestorHiddenLogic(ctx)) return false;\n return true;\n },\n schema<unknown>((subPath) => apply(subPath as SchemaPath<unknown>)),\n );\n}\n\n/**\n * Applies a container's own `validators` to its schema path (#568).\n * `context` must be the DESCENDANT context — a group never calls `hidden()` on its own path.\n */\nfunction applyContainerValidators(fieldDef: FieldDef<unknown>, fieldPath: AnySchemaPath, context: FieldTreeMappingContext): void {\n const validators = (fieldDef as FieldDef<unknown> & ContainerValidation).validators;\n if (!validators || validators.length === 0) return;\n\n applyHiddenGatedValidation(fieldPath as SchemaPath<unknown>, context, (validationPath) => {\n for (const config of validators) {\n applyValidator(config, validationPath, 'tree');\n }\n });\n}\n\n/**\n * Applies simple validation rules from field properties.\n * Casts are isolated to the boundary between untyped field definitions and typed validator functions.\n */\nfunction applySimpleValidationRules(fieldDef: FieldWithValidation, path: SchemaPath<unknown>, context: FieldTreeMappingContext): void {\n // Own `required` wins over an inherited one, so `required: false` opts out. A child\n // whose requiredness is conditional (`logic: [{ type: 'required', when }]`) also opts\n // out — an unconditional cascade on top would make that condition unreachable.\n const declaresRequiredLogic = fieldDef.logic?.some(isValidationStateLogic) ?? false;\n if (fieldDef.required ?? (declaresRequiredLogic ? false : context.ancestorRequired)) {\n required(path);\n }\n\n // String validators - single cast at boundary\n const stringConfig = getStringValidationConfig(fieldDef);\n if (stringConfig) {\n applyStringValidators(path as SchemaPath<string>, stringConfig);\n }\n\n // Number validators - single cast at boundary\n const numberConfig = getNumberValidationConfig(fieldDef);\n if (numberConfig) {\n applyNumberValidators(path as SchemaPath<number>, numberConfig);\n }\n}\n\n/** Applies field state configuration (disabled, readonly, hidden). */\nfunction applyFieldState(fieldDef: FieldDef<unknown>, path: SchemaPath<unknown>): void {\n if (fieldDef.disabled) {\n disabled(path);\n }\n\n if (fieldDef.readonly) {\n readonly(path);\n }\n\n if (fieldDef.hidden) {\n hidden(path, () => true);\n }\n}\n\n/** Maps an array field to the form schema using applyEach. */\nfunction mapArrayFieldToForm(arrayField: FieldDef<unknown>, fieldPath: AnySchemaPath, context: FieldTreeMappingContext): void {\n if (!isArrayField(arrayField)) {\n return;\n }\n\n // Apply array-level length validation\n if (arrayField.minLength !== undefined) {\n minLength(fieldPath as SchemaPath<unknown[]>, arrayField.minLength);\n }\n if (arrayField.maxLength !== undefined) {\n maxLength(fieldPath as SchemaPath<unknown[]>, arrayField.maxLength);\n }\n\n applyContainerValidators(arrayField, fieldPath, context);\n\n // Fields can be either FieldDef (primitive) or FieldDef[] (object)\n let itemDefinitions = arrayField.fields as readonly (FieldDef<unknown> | readonly FieldDef<unknown>[])[];\n\n // Simplified arrays initialized without `value` have empty `fields`; their\n // item shape only lives in Symbol metadata. Fall back to that template so\n // validators and logic declared inside it still apply when items are\n // materialized from a programmatically-set form value.\n if (!itemDefinitions || itemDefinitions.length === 0) {\n const metadataTemplate = getNormalizedArrayMetadata(arrayField)?.template;\n if (metadataTemplate) {\n itemDefinitions = [\n Array.isArray(metadataTemplate) ? [...(metadataTemplate as readonly FieldDef<unknown>[])] : (metadataTemplate as FieldDef<unknown>),\n ];\n }\n }\n\n // Empty array with no template metadata - items will be added via buttons with\n // their own templates. Create a minimal schema that just accepts any value.\n if (!itemDefinitions || itemDefinitions.length === 0) {\n const emptyItemSchema = schema<unknown>(() => {\n // No fields to map - items will be added dynamically via buttons\n });\n applyEach(fieldPath as SchemaPath<unknown[]>, emptyItemSchema);\n return;\n }\n\n // Analyze item definitions to determine schema type\n let hasPrimitiveItems = false;\n const allObjectFields: FieldDef<unknown>[] = [];\n\n for (const itemDef of itemDefinitions) {\n if (Array.isArray(itemDef)) {\n // Object item: collect fields for superset schema\n collectFieldsFromObjectItem(itemDef as readonly FieldDef<unknown>[], allObjectFields);\n } else if (\n isPageField(itemDef as FieldDef<unknown>) ||\n isRowField(itemDef as FieldDef<unknown>) ||\n isGenericContainerField(itemDef as FieldDef<unknown>)\n ) {\n // Layout container as a single-template item: its descendants describe\n // the item's object shape. Hand it to the object-item collector wrapped\n // as a 1-element array so the structured-item mapping path runs.\n collectFieldsFromObjectItem([itemDef as FieldDef<unknown>], allObjectFields);\n } else {\n // Primitive item: single FieldDef\n hasPrimitiveItems = true;\n }\n }\n\n // For pure primitive arrays, use a simple schema\n if (hasPrimitiveItems && allObjectFields.length === 0) {\n const primitiveItemSchema = schema<unknown>(() => {\n // Primitive items don't need field mapping - just accept any value\n });\n applyEach(fieldPath as SchemaPath<unknown[]>, primitiveItemSchema);\n return;\n }\n\n // For object or mixed arrays, use object schema with optional fields\n // Mixed arrays use the superset of all object fields (primitive items are just values)\n const itemSchema = schema<Record<string, unknown>>((itemPath) => {\n const pathRecord = itemPath as Record<string, AnySchemaPath>;\n\n // Map ALL unique template fields from all object items\n for (const templateField of allObjectFields) {\n if (isRowField(templateField) || isPageField(templateField) || isGenericContainerField(templateField)) {\n // Row/page/container templates flatten their children\n const layoutOwn = resolveFieldOwnContext(templateField, context);\n const layoutDescendant = resolveDescendantContext(templateField, layoutOwn);\n mapContainerChildren(templateField.fields as FieldDef<unknown>[] | undefined, itemPath, layoutDescendant);\n } else if (isGroupField(templateField)) {\n const groupKey = templateField.key;\n if (groupKey) {\n // Via mapFieldToForm so the group's own validators apply, not just its children.\n const groupPath = pathRecord[groupKey];\n if (groupPath) {\n mapFieldToForm(templateField, groupPath, context);\n }\n } else {\n // No group key - apply children directly (edge case)\n const groupOwn = resolveFieldOwnContext(templateField, context);\n mapContainerChildren(templateField.fields, itemPath, resolveDescendantContext(templateField, groupOwn));\n }\n } else {\n // Simple field template - get the specific field's path\n const fieldKey = templateField.key;\n if (fieldKey) {\n const fieldPathForKey = pathRecord[fieldKey];\n if (fieldPathForKey) {\n mapFieldToForm(templateField, fieldPathForKey, context);\n }\n }\n }\n }\n });\n\n applyEach(fieldPath as SchemaPath<Record<string, unknown>[]>, itemSchema);\n}\n\n/**\n * Collects unique field definitions from an object item template.\n * Uses field key as the uniqueness identifier.\n * When the same key appears in multiple templates, uses the first occurrence.\n */\nfunction collectFieldsFromObjectItem(itemFields: readonly FieldDef<unknown>[], allFields: FieldDef<unknown>[]): void {\n const seenKeys = new Set(allFields.map((f) => f.key).filter(Boolean));\n\n for (const field of itemFields) {\n // For row/page/container fields, we need to collect their children's keys\n if (isRowField(field) || isPageField(field) || isGenericContainerField(field)) {\n // Use a synthetic key for container fields to dedupe them\n const containerKey = `__container_${field.type}_${JSON.stringify(field.fields?.map((f) => f.key))}`;\n if (!seenKeys.has(containerKey)) {\n seenKeys.add(containerKey);\n allFields.push(field);\n }\n } else {\n const key = field.key;\n if (key && !seenKeys.has(key)) {\n seenKeys.add(key);\n allFields.push(field);\n }\n }\n }\n}\n","import { inject } from '@angular/core';\nimport { Schema, schema } from '@angular/forms/signals';\nimport type { SchemaPath, SchemaPathTree } from '@angular/forms/signals';\nimport { FieldDef } from '@ng-forge/dynamic-forms/internal';\nimport { mapFieldToForm } from './form-mapping';\nimport { FieldTypeDefinition, getFieldValueHandling } from '@ng-forge/dynamic-forms/internal';\nimport { getFieldDefaultValue } from '../utils/default-value/default-value';\nimport { hasChildFields } from '@ng-forge/dynamic-forms/internal';\nimport { normalizeFieldsArray } from '@ng-forge/dynamic-forms/internal';\nimport { applyFormLevelSchema } from './form-schema-merger';\nimport type { FormSchema } from '@ng-forge/dynamic-forms/schema';\nimport { VALIDATION_EXECUTION_DEFAULTS } from '../providers/features/validation-execution/validation-execution.token';\nimport { FieldTreeMappingContext, resolveDescendantContext, resolveFieldOwnContext } from './field-tree-mapping-context';\n\n/** Options for creating a schema from field definitions. */\nexport interface CreateSchemaOptions<TModel = unknown> {\n /** Optional form-level Standard Schema for additional validation. */\n formLevelSchema?: FormSchema<TModel>;\n\n /** Form-level `validateWhenHidden` setting (from `FormConfig.options.validateWhenHidden`). */\n validateWhenHidden?: boolean;\n}\n\n/**\n * Creates an Angular signal forms schema from field definitions\n * This is the single entry point at dynamic form level that replaces createSchemaFromFields\n * Uses the new modular signal forms adapter structure\n *\n * @param fields Field definitions to create schema from\n * @param registry Field type registry\n * @param options Optional configuration object\n */\nexport function createSchemaFromFields<TModel = unknown>(\n fields: FieldDef<unknown>[],\n registry: Map<string, FieldTypeDefinition>,\n options: CreateSchemaOptions<TModel> = {},\n): Schema<TModel> {\n // Inject services for form-level schema construction\n // These will be available because createSchemaFromFields is called within runInInjectionContext\n const validationExecutionDefaults = inject(VALIDATION_EXECUTION_DEFAULTS);\n\n const { formLevelSchema } = options;\n const rootContext: FieldTreeMappingContext = {\n validateWhenHidden: options.validateWhenHidden ?? validationExecutionDefaults.validateWhenHidden,\n ancestorAlwaysHidden: false,\n ancestorHiddenLogics: [],\n };\n\n return schema<TModel>((path) => {\n for (const fieldDef of fields) {\n const valueHandling = getFieldValueHandling(fieldDef.type, registry);\n\n // Handle different value handling modes\n if (valueHandling === 'exclude') {\n // Skip fields that don't contribute to form values\n continue;\n }\n\n if (valueHandling === 'flatten' && hasChildFields(fieldDef)) {\n // A flatten container can still set its own cascading overrides that flow\n // down to its children — including its own hidden state which has no\n // schema path of its own.\n const flattenedOwn = resolveFieldOwnContext(fieldDef, rootContext);\n const flattenedContext = resolveDescendantContext(fieldDef, flattenedOwn);\n for (const childField of normalizeFieldsArray(fieldDef.fields)) {\n if (!childField.key) continue;\n\n const childPath = (path as Record<string, SchemaPath<unknown>>)[childField.key];\n if (childPath) {\n mapFieldToForm(childField, childPath, flattenedContext);\n }\n }\n continue;\n }\n\n // Regular field processing for 'include' fields\n const fieldPath = (path as Record<string, SchemaPath<unknown>>)[fieldDef.key];\n\n if (!fieldPath) {\n continue;\n }\n\n // Use the new modular form mapping function\n // This will progressively apply validators, logic, and schemas\n // Cross-field logic is handled automatically via RootFormRegistryService\n mapFieldToForm(fieldDef, fieldPath, rootContext);\n }\n\n // Apply form-level Standard Schema validation\n if (formLevelSchema) {\n applyFormLevelSchema(path as SchemaPathTree<TModel>, formLevelSchema);\n }\n });\n}\n\n/** Utility to convert field definitions to default values object */\nexport function fieldsToDefaultValues<TModel = unknown>(fields: FieldDef<unknown>[], registry: Map<string, FieldTypeDefinition>): TModel {\n const defaultValues: Record<string, unknown> = {};\n\n for (const field of fields) {\n if (!field.key) continue;\n\n // Skip flatten fields (row/page) at top level - they are presentational containers\n const valueHandling = getFieldValueHandling(field.type, registry);\n if (valueHandling === 'flatten') {\n continue;\n }\n\n const value = getFieldDefaultValue(field, registry);\n if (value !== undefined) {\n defaultValues[field.key] = value;\n }\n }\n\n return defaultValues as TModel;\n}\n"]}
|