ngx-dynamic-entity 1.14.0 → 2.0.1
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/ngx-dynamic-entity.mjs +718 -318
- package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
- package/package.json +2 -2
- package/styles.css +2 -12
- package/types/ngx-dynamic-entity.d.ts +242 -45
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ngx-dynamic-entity",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Angular standalone UI library for rendering dynamic entity forms.",
|
|
5
5
|
"//typings": "Local workspace resolution only: ng-packagr 21 writes types/ngx-dynamic-entity.d.ts into dist/package.json. Do NOT add a 'main' field here — ng-packagr does not generate one, so it would be copied verbatim and resolve wrongly in the published package.",
|
|
6
6
|
"typings": "types/ngx-dynamic-entity.d.ts",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"@angular/common": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
9
9
|
"@angular/core": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
10
10
|
"@angular/forms": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
11
|
-
"@dynamic-entity/core": "^
|
|
11
|
+
"@dynamic-entity/core": "^2.0.1",
|
|
12
12
|
"rxjs": "^7.0.0"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
package/styles.css
CHANGED
|
@@ -224,6 +224,7 @@
|
|
|
224
224
|
flex-direction: column;
|
|
225
225
|
gap: var(--ngx-field-gap);
|
|
226
226
|
min-width: 0;
|
|
227
|
+
width: 100%;
|
|
227
228
|
}
|
|
228
229
|
|
|
229
230
|
.ngx-field__label {
|
|
@@ -1057,18 +1058,7 @@ textarea.ngx-field__input:focus {
|
|
|
1057
1058
|
}
|
|
1058
1059
|
|
|
1059
1060
|
/*
|
|
1060
|
-
*
|
|
1061
|
-
*
|
|
1062
|
-
* With every field at `span 12` the record view spent a full screen on six values — and no
|
|
1063
|
-
* control is being sized here, so the width a text input needs is not the width a value
|
|
1064
|
-
* needs. `auto-fit` packs them, and a long note still takes its own row when the content
|
|
1065
|
-
* demands one.
|
|
1066
|
-
*/
|
|
1067
|
-
/*
|
|
1068
|
-
* Nothing is being asked of the reader, so nothing is required of them.
|
|
1069
|
-
*
|
|
1070
|
-
* The matching `.ngx-form--readonly` *grid* rule lives in the component's own styles, next to
|
|
1071
|
-
* the grid it overrides — see the Layout note above.
|
|
1061
|
+
* In read-only mode, required markers are suppressed since no user input is expected.
|
|
1072
1062
|
*/
|
|
1073
1063
|
.ngx-form--readonly .ngx-field__req {
|
|
1074
1064
|
display: none;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, Type, EnvironmentProviders, WritableSignal, OnInit, OnChanges, OnDestroy, EventEmitter, SimpleChanges, Provider } from '@angular/core';
|
|
3
|
-
import { ReferenceOption, LocalizedText, EntityFormConfig, FormRule, ImportLookups, MappingPlan, ImportResult, TemplateSpec, SheetData, CommonModuleEntry, EntityReferenceLoader, LookupListSource, RecordMigration, FileUploadHandler, FieldValidators, NestedFieldConfig, ReferenceLoaderContext, DropdownOption, LookupListValue, NestedTabConfig, RuleEvaluationResult, ImportColumn, UnsupportedColumn, ImportRowError } from '@dynamic-entity/core';
|
|
3
|
+
import { ReferenceOption, LocalizedText, EntityFormConfig, FormRule, ImportLookups, MappingPlan, ImportResult, TemplateSpec, SheetData, CommonModuleEntry, EntityReferenceLoader, LookupListSource, RecordMigration, FileUploadHandler, FieldValidators, NestedFieldConfig, ReferenceLoaderContext, DropdownOption, LookupListValue, NestedTabConfig, RuleEvaluationResult, FieldScopeEntry, EvaluateFormRulesOptions, ImportColumn, UnsupportedColumn, ImportRowError } from '@dynamic-entity/core';
|
|
4
4
|
export { AutoPatchConfig, CommonModuleEntry, ComponentClass, DropdownOption, EntityFormConfig, EntityPermissions, EntityReferenceConfig, EntityReferenceLoader, FieldTableConfig, FieldValidators, FileRef, FormRule, LocalizedText, NestedFieldConfig, NestedTabConfig, PatchOnTrueMapping, RbacContext, ReferenceLoaderContext, ReferenceOption, RichFieldType, RuleAction, RuleCondition, RuleEvaluationResult, VersionedRecord, formatDisplayValue, normalizeConfig, normalizeOption, resolveLabel, resolveOptionLabel, resolveOptionValue, valuesMatch } from '@dynamic-entity/core';
|
|
5
|
-
import { ValidatorFn, AsyncValidatorFn, ValidationErrors, FormGroup, FormArray
|
|
5
|
+
import { ValidatorFn, AsyncValidatorFn, AbstractControl, ValidationErrors, FormGroup, FormArray } from '@angular/forms';
|
|
6
6
|
import * as rxjs from 'rxjs';
|
|
7
7
|
import * as ngx_dynamic_entity from 'ngx-dynamic-entity';
|
|
8
8
|
|
|
@@ -618,7 +618,7 @@ declare const provideNgxDynamicEntity: (config?: NgxDynamicEntityConfig) => Envi
|
|
|
618
618
|
*
|
|
619
619
|
* `FieldRegistryService` imports no components. Whatever you register here is what gets
|
|
620
620
|
* bundled, so an app that only uses text/number/dropdown pays for three components and
|
|
621
|
-
* their imports, not
|
|
621
|
+
* their imports, not twenty-one.
|
|
622
622
|
*
|
|
623
623
|
* ```ts
|
|
624
624
|
* // Everything (convenient; bundles all built-ins):
|
|
@@ -634,7 +634,7 @@ declare const provideNgxDynamicEntity: (config?: NgxDynamicEntityConfig) => Envi
|
|
|
634
634
|
declare function builtInFieldTypes(): Record<string, Type<any>>;
|
|
635
635
|
/** Register a specific set of field types. Only the components you name are bundled. */
|
|
636
636
|
declare function provideFieldTypes(types: Record<string, Type<any>>): EnvironmentProviders;
|
|
637
|
-
/** Register all
|
|
637
|
+
/** Register all 21 built-in field type keys. Convenience for apps that use most of them. */
|
|
638
638
|
declare function provideBuiltInFieldTypes(): EnvironmentProviders;
|
|
639
639
|
|
|
640
640
|
/**
|
|
@@ -642,7 +642,7 @@ declare function provideBuiltInFieldTypes(): EnvironmentProviders;
|
|
|
642
642
|
*
|
|
643
643
|
* **Tree-shaking contract:** this service holds *no* static imports of field components.
|
|
644
644
|
* Nothing is bundled until something registers it, which is what makes the built-ins
|
|
645
|
-
* opt-in. Register them with `provideBuiltInFieldTypes()` (all
|
|
645
|
+
* opt-in. Register them with `provideBuiltInFieldTypes()` (all 21 keys) or a narrower
|
|
646
646
|
* subset via `provideFieldTypes({ ... })` — see `providers/provide-field-types.ts`.
|
|
647
647
|
*
|
|
648
648
|
* Resolution order: consumer token registry → runtime registrations → nothing.
|
|
@@ -706,6 +706,22 @@ declare class ValidatorRegistryService {
|
|
|
706
706
|
* An unknown name is skipped rather than throwing, matching the synchronous behaviour.
|
|
707
707
|
*/
|
|
708
708
|
resolveAsyncFromConfig(config?: FieldValidators | string[]): AsyncValidatorFn[];
|
|
709
|
+
/**
|
|
710
|
+
* Say when a named validator resolved to nothing.
|
|
711
|
+
*
|
|
712
|
+
* It used to be dropped in silence, which is the worst failure mode this library has: the
|
|
713
|
+
* schema says the field is checked, the field is not checked, the form saves, and nothing
|
|
714
|
+
* anywhere said so. For `customAsync` that is typically a uniqueness check, so the duplicate
|
|
715
|
+
* lands in the database.
|
|
716
|
+
*
|
|
717
|
+
* Dev only, once per name — this runs while controls are built, which happens on every
|
|
718
|
+
* config change. `validateConfig` with `knownValidators` is the build-time version of the
|
|
719
|
+
* same check, and it fails CI rather than a console.
|
|
720
|
+
*/
|
|
721
|
+
private warnUnresolved;
|
|
722
|
+
/** Per service instance, which is per injector — so a test gets a clean slate. */
|
|
723
|
+
private readonly warned;
|
|
724
|
+
private warnOnce;
|
|
709
725
|
static ɵfac: i0.ɵɵFactoryDeclaration<ValidatorRegistryService, never>;
|
|
710
726
|
static ɵprov: i0.ɵɵInjectableDeclaration<ValidatorRegistryService>;
|
|
711
727
|
}
|
|
@@ -963,18 +979,88 @@ declare class RbacService {
|
|
|
963
979
|
static ɵprov: i0.ɵɵInjectableDeclaration<RbacService>;
|
|
964
980
|
}
|
|
965
981
|
|
|
982
|
+
/** What `syncHiddenFieldState` needs from the form it is operating on. */
|
|
983
|
+
interface RuleSyncContext {
|
|
984
|
+
result: RuleEvaluationResult;
|
|
985
|
+
/** The values `showWhen` is evaluated against — the flat map the rules engine reads. */
|
|
986
|
+
values: Record<string, unknown>;
|
|
987
|
+
/** Every name a field answers to: its bare id, and its bracketed path. */
|
|
988
|
+
namesOf(field: NestedFieldConfig): readonly string[];
|
|
989
|
+
/**
|
|
990
|
+
* The field's dotted address, unbracketed — `personal.address.city`.
|
|
991
|
+
*
|
|
992
|
+
* Separate from `namesOf` because it is compared as a *prefix*: a container hides its
|
|
993
|
+
* descendants, and `[personal.address]` is not a prefix of `[personal.address.city]` once
|
|
994
|
+
* the brackets are in the way.
|
|
995
|
+
*/
|
|
996
|
+
addressOf(field: NestedFieldConfig): string;
|
|
997
|
+
/** The control a scope entry addresses, or `null` when it has no static path. */
|
|
998
|
+
controlFor(entry: FieldScopeEntry): AbstractControl | null;
|
|
999
|
+
}
|
|
966
1000
|
/**
|
|
967
|
-
* RulesEvaluationService — evaluates
|
|
1001
|
+
* RulesEvaluationService — evaluates rules, and owns what a result *means*.
|
|
1002
|
+
*
|
|
1003
|
+
* It used to be two passthrough methods, with the interpretation spread across the component:
|
|
1004
|
+
* the render filter decided one way what a rule result did to a field, and the validity sync
|
|
1005
|
+
* decided again forty lines further down. They disagreed about what "every field" meant, and
|
|
1006
|
+
* the consequence was a required field inside a `group` that a rule had hidden holding
|
|
1007
|
+
* `form.invalid` true forever with nothing on screen to explain it.
|
|
1008
|
+
*
|
|
1009
|
+
* Precedence lives here now, in one method that both callers go through. Still stateless —
|
|
1010
|
+
* every method takes the result it is interpreting, so there is nothing to go stale against a
|
|
1011
|
+
* rebuilt form and nothing to reset between configs.
|
|
968
1012
|
*/
|
|
969
1013
|
declare class RulesEvaluationService {
|
|
970
1014
|
/**
|
|
971
1015
|
* Evaluate rules against form values and optional original baseline values.
|
|
972
1016
|
*/
|
|
973
|
-
evaluate(rules: FormRule[] | undefined | null, values: Record<string, unknown>, originalValues?: Record<string, unknown
|
|
1017
|
+
evaluate(rules: FormRule[] | undefined | null, values: Record<string, unknown>, originalValues?: Record<string, unknown>, options?: EvaluateFormRulesOptions): RuleEvaluationResult;
|
|
974
1018
|
/**
|
|
975
1019
|
* Filter rules pertinent to a single tab.
|
|
976
1020
|
*/
|
|
977
1021
|
filterForTab(rules: FormRule[] | undefined | null, tabId: string, config: EntityFormConfig): FormRule[];
|
|
1022
|
+
/**
|
|
1023
|
+
* Whether a field renders, under the precedence documented on `RuleEvaluationResult`.
|
|
1024
|
+
*
|
|
1025
|
+
* An explicit hide beats everything. A show beats static hiding — `visibility: false`, or a
|
|
1026
|
+
* `showWhen` that does not currently hold — because a rule that could only hide what the
|
|
1027
|
+
* config already hides would have nothing to say, and `{ visibility: true }` was a
|
|
1028
|
+
* documented no-op until it did. Otherwise the config's own visibility decides.
|
|
1029
|
+
*/
|
|
1030
|
+
isFieldVisible(result: RuleEvaluationResult, field: NestedFieldConfig, names: readonly string[], values: Record<string, unknown>): boolean;
|
|
1031
|
+
/** The same precedence for a tab. */
|
|
1032
|
+
isTabVisible(result: RuleEvaluationResult, tab: NestedTabConfig): boolean;
|
|
1033
|
+
/**
|
|
1034
|
+
* Keep a hidden field's control out of form validity.
|
|
1035
|
+
*
|
|
1036
|
+
* A field hidden by a rule or a `showWhen` is filtered out of the render, but its
|
|
1037
|
+
* validators stay attached — so a hidden *required* field holds `form.invalid` true
|
|
1038
|
+
* forever, permanently disabling Save with nothing on screen to explain why.
|
|
1039
|
+
*
|
|
1040
|
+
* Disabling is the fix rather than stripping validators: Angular excludes disabled controls
|
|
1041
|
+
* from validity, and the field's own validators survive intact for when it comes back.
|
|
1042
|
+
* Values are untouched — a disabled control still carries its value, so rule evaluation and
|
|
1043
|
+
* the emitted record see exactly what they saw before. That also keeps this from feeding
|
|
1044
|
+
* back on itself: hiding a field cannot change the values the rules are evaluated over.
|
|
1045
|
+
*
|
|
1046
|
+
* The predicate is `isFieldVisible`, the same one the render filter uses, so what is on
|
|
1047
|
+
* screen and what counts toward validity cannot drift apart.
|
|
1048
|
+
*
|
|
1049
|
+
* Entries must arrive parent-first, which is what `collectFieldScopes` yields: a container's
|
|
1050
|
+
* children are skipped once the container itself is hidden, because Angular's `disable()`
|
|
1051
|
+
* cascades to descendants and a child's `enable()` walks back up recalculating ancestors.
|
|
1052
|
+
* Toggling a child inside a hidden group would fight its parent.
|
|
1053
|
+
*/
|
|
1054
|
+
syncHiddenFieldState(entries: readonly FieldScopeEntry[], ctx: RuleSyncContext): void;
|
|
1055
|
+
/**
|
|
1056
|
+
* The hidden and shown names as sets, memoised against the result they came from.
|
|
1057
|
+
*
|
|
1058
|
+
* `isFieldVisible` runs once per field per change-detection pass; building two sets from
|
|
1059
|
+
* two arrays each time turns a rule result into allocation proportional to the form. A
|
|
1060
|
+
* `WeakMap` keyed by the result means it is built once per evaluation and collected with it.
|
|
1061
|
+
*/
|
|
1062
|
+
private nameSets;
|
|
1063
|
+
private readonly nameSetCache;
|
|
978
1064
|
static ɵfac: i0.ɵɵFactoryDeclaration<RulesEvaluationService, never>;
|
|
979
1065
|
static ɵprov: i0.ɵɵInjectableDeclaration<RulesEvaluationService>;
|
|
980
1066
|
}
|
|
@@ -1146,6 +1232,8 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1146
1232
|
private readonly hookRegistry;
|
|
1147
1233
|
private readonly rbacService;
|
|
1148
1234
|
private readonly rulesEvaluation;
|
|
1235
|
+
/** config ⇄ FormGroup: where a value lives. See `FormStructureService`. */
|
|
1236
|
+
private readonly structure;
|
|
1149
1237
|
/** Resolves what is wrong with a field, for the error summary — see `resolveForField`. */
|
|
1150
1238
|
private readonly messages;
|
|
1151
1239
|
private readonly entityRefSelection;
|
|
@@ -1176,6 +1264,9 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1176
1264
|
private previousValues;
|
|
1177
1265
|
handleKeyboardEvent(event: KeyboardEvent): void;
|
|
1178
1266
|
readonly ruleResult: i0.Signal<ngx_dynamic_entity.RuleEvaluationResult>;
|
|
1267
|
+
/** Warned-about rule problems, per component instance — see `warnedAmbiguousIds`. */
|
|
1268
|
+
private readonly warnedProblems;
|
|
1269
|
+
private warnOnce;
|
|
1179
1270
|
/**
|
|
1180
1271
|
* Critical fields whose value differs from the session baseline.
|
|
1181
1272
|
* These drive the deferred `VALUE_CHANGED` banner — a critical edit is announced once,
|
|
@@ -1184,6 +1275,7 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1184
1275
|
readonly changedCriticalFields: i0.Signal<NestedFieldConfig[]>;
|
|
1185
1276
|
get tabs(): NestedTabConfig[];
|
|
1186
1277
|
get visibleTabs(): NestedTabConfig[];
|
|
1278
|
+
private isTabVisible;
|
|
1187
1279
|
get activeTabConfig(): NestedTabConfig | null;
|
|
1188
1280
|
get visibleSubTabs(): NestedTabConfig[];
|
|
1189
1281
|
get activeSubTabConfig(): NestedTabConfig | null;
|
|
@@ -1203,6 +1295,12 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1203
1295
|
private readonly warnedSelectorModules;
|
|
1204
1296
|
private warnSelectorModule;
|
|
1205
1297
|
get fieldsForActiveTab(): NestedFieldConfig[];
|
|
1298
|
+
/**
|
|
1299
|
+
* Whether a field renders. Precedence lives in `RulesEvaluationService`, which is also
|
|
1300
|
+
* what `syncHiddenFieldState` asks — so what is on screen and what counts toward validity
|
|
1301
|
+
* cannot drift apart.
|
|
1302
|
+
*/
|
|
1303
|
+
private isFieldVisible;
|
|
1206
1304
|
/**
|
|
1207
1305
|
* Entity-level permissions for the current roles.
|
|
1208
1306
|
*
|
|
@@ -1344,6 +1442,14 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1344
1442
|
* library never reaches for the global `document`.
|
|
1345
1443
|
*/
|
|
1346
1444
|
jumpToField(fieldId: string): void;
|
|
1445
|
+
/**
|
|
1446
|
+
* Which tab (and sub-tab) holds a field.
|
|
1447
|
+
*
|
|
1448
|
+
* Descends into `group`/`array` children: this compared top-level fields only, so "jump to
|
|
1449
|
+
* the first invalid field" found nothing for a nested one and silently did nothing. Only
|
|
1450
|
+
* sub-tabs one level deep are named, because that is what the tab strip can select —
|
|
1451
|
+
* anything deeper resolves to the ancestor it renders under.
|
|
1452
|
+
*/
|
|
1347
1453
|
private locateField;
|
|
1348
1454
|
/** A critical field stays read-only until the user deliberately unlocks it. */
|
|
1349
1455
|
isFieldLocked(field: NestedFieldConfig): boolean;
|
|
@@ -1370,8 +1476,6 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1370
1476
|
*/
|
|
1371
1477
|
changedCriticalFieldLabels(): string;
|
|
1372
1478
|
private buildForm;
|
|
1373
|
-
private buildFieldControl;
|
|
1374
|
-
/** One row of an `array` field: a FormGroup when the field declares columns, else a bare control. */
|
|
1375
1479
|
/**
|
|
1376
1480
|
* The `FormArray` behind an `array` field, for callers that manage rows themselves —
|
|
1377
1481
|
* the record editor's inline row drawer.
|
|
@@ -1408,9 +1512,8 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1408
1512
|
* stay empty — with no error and no clue. That silent miss is the most expensive way to
|
|
1409
1513
|
* lose an hour with this library, so name it in dev builds.
|
|
1410
1514
|
*
|
|
1411
|
-
*
|
|
1412
|
-
*
|
|
1413
|
-
* our business.
|
|
1515
|
+
* Which keys were missed is `FormStructureService`'s answer; warning once per key per form
|
|
1516
|
+
* is this component's, because it is the thing that lives as long as the form does.
|
|
1414
1517
|
*/
|
|
1415
1518
|
private warnUnconsumedInitialData;
|
|
1416
1519
|
/** Warn once per key per form instance — patchForm re-runs on every initialData change. */
|
|
@@ -1429,7 +1532,16 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1429
1532
|
* consumers that never call the validator still get a signal in development.
|
|
1430
1533
|
*/
|
|
1431
1534
|
private warnAmbiguousRuleReferences;
|
|
1432
|
-
|
|
1535
|
+
/**
|
|
1536
|
+
* Ids already warned about, per component instance.
|
|
1537
|
+
*
|
|
1538
|
+
* This was `private static`, so it was module-global mutable state that nothing ever
|
|
1539
|
+
* cleared: under SSR every render of every form in the process accumulated into one set
|
|
1540
|
+
* that lived as long as the server did, and the first request to render a config
|
|
1541
|
+
* suppressed the warning for every request after it. Per instance, the warning is once per
|
|
1542
|
+
* form — which is what "warn once" was meant to mean.
|
|
1543
|
+
*/
|
|
1544
|
+
private readonly warnedAmbiguousIds;
|
|
1433
1545
|
/**
|
|
1434
1546
|
* The value map rules and `showWhen` are evaluated against.
|
|
1435
1547
|
*
|
|
@@ -1443,17 +1555,53 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1443
1555
|
* unknown>` and needs no knowledge of refs at all — the extra keys simply resolve.
|
|
1444
1556
|
*/
|
|
1445
1557
|
/**
|
|
1446
|
-
*
|
|
1558
|
+
* Every name a rule may address this field by.
|
|
1447
1559
|
*
|
|
1448
1560
|
* A rule target is either a bare field id — how every config so far addresses a field — or
|
|
1449
|
-
* a bracketed path, which is the only way to name one of two fields that share an id.
|
|
1450
|
-
*
|
|
1561
|
+
* a bracketed path, which is the only way to name one of two fields that share an id. Both
|
|
1562
|
+
* have to match, or a rule written either way would fail to hide what it targeted.
|
|
1563
|
+
*
|
|
1564
|
+
* The path comes last, and `syncHiddenFieldState` relies on that: it uses the last entry
|
|
1565
|
+
* as the field's address when deciding whether a hidden container already covers it.
|
|
1451
1566
|
*/
|
|
1452
|
-
private
|
|
1567
|
+
private namesOf;
|
|
1568
|
+
/**
|
|
1569
|
+
* The field's dotted address, unbracketed.
|
|
1570
|
+
*
|
|
1571
|
+
* The positional path rather than an authored `refererField`: this is compared as a prefix
|
|
1572
|
+
* to decide whether a hidden container already covers a field, and containment is a fact
|
|
1573
|
+
* about where the field *is*. A `refererField` is a binding override — it says where the
|
|
1574
|
+
* value goes, not which group renders it.
|
|
1575
|
+
*/
|
|
1576
|
+
private addressOf;
|
|
1453
1577
|
/** The field's declared path, or the one its position implies when it carries none. */
|
|
1454
1578
|
private pathFor;
|
|
1579
|
+
/**
|
|
1580
|
+
* Every field in the config with the scope its value is stored under, cached per config.
|
|
1581
|
+
*
|
|
1582
|
+
* `collectFieldScopes` walks the whole tree. It was called once per field from `pathFor`,
|
|
1583
|
+
* which `namesField` calls, which the render filter calls for every field on the active
|
|
1584
|
+
* tab — a quadratic walk on every change-detection pass. Keyed by config identity, so a
|
|
1585
|
+
* new config invalidates it without an explicit lifecycle hook to forget.
|
|
1586
|
+
*/
|
|
1587
|
+
private fieldScopes;
|
|
1588
|
+
private scopeEntryFor;
|
|
1589
|
+
private scopeCache?;
|
|
1590
|
+
/**
|
|
1591
|
+
* The control a scope entry addresses, named by path rather than by bare id.
|
|
1592
|
+
*
|
|
1593
|
+
* `getControl(field.id)` with no tab falls through to a first-match recursive search of the
|
|
1594
|
+
* whole form, so with `address` on two tabs, hiding one disabled the other. The control
|
|
1595
|
+
* tree mirrors the scope path exactly — `buildTabControls` nests by tab id and
|
|
1596
|
+
* `buildFieldControl` nests a `group` under its own id — so a `[path]` ref resolves through
|
|
1597
|
+
* `form.get()` on `getControl`'s first branch and names exactly one control.
|
|
1598
|
+
*
|
|
1599
|
+
* A field inside an `array` has no static path: its controls live in `FormArray` rows built
|
|
1600
|
+
* per row. `form.get()` returns null for those and they are left alone, which is correct —
|
|
1601
|
+
* a row's controls are created and destroyed with the row.
|
|
1602
|
+
*/
|
|
1603
|
+
private controlForEntry;
|
|
1455
1604
|
private flattenFormValues;
|
|
1456
|
-
private getTabGroup;
|
|
1457
1605
|
/**
|
|
1458
1606
|
* `autoPatch` — copy mapped fields out of the record the user just selected in an
|
|
1459
1607
|
* entity-ref field into the configured target tab's controls.
|
|
@@ -1464,37 +1612,44 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1464
1612
|
* copy `from` → `to` within the current record.
|
|
1465
1613
|
*/
|
|
1466
1614
|
private runPatchOnTrue;
|
|
1467
|
-
/** Prefer a control declared on the configured target tab; fall back to a top-level control. */
|
|
1468
|
-
private resolveTargetControl;
|
|
1469
1615
|
/**
|
|
1470
|
-
*
|
|
1471
|
-
*
|
|
1472
|
-
*
|
|
1616
|
+
* Prefer a control declared on the configured target tab; fall back to a top-level control.
|
|
1617
|
+
*
|
|
1618
|
+
* "Declared on the tab" now means anywhere under it, sub-tabs and `group` children
|
|
1619
|
+
* included. It meant the tab's own top-level fields only, so an `autoPatch` mapping onto a
|
|
1620
|
+
* field inside a group resolved to `null` and copied nothing, silently.
|
|
1473
1621
|
*/
|
|
1474
|
-
private
|
|
1622
|
+
private resolveTargetControl;
|
|
1475
1623
|
/**
|
|
1476
|
-
* Keep a hidden field's control out of form validity
|
|
1477
|
-
*
|
|
1478
|
-
* A field hidden by a rule or a `showWhen` condition is filtered out of the render, but
|
|
1479
|
-
* its validators stay attached — so a hidden *required* field holds `form.invalid` true
|
|
1480
|
-
* forever, permanently disabling Save with nothing on screen to explain why.
|
|
1481
|
-
*
|
|
1482
|
-
* Disabling is the fix rather than stripping validators: Angular excludes disabled
|
|
1483
|
-
* controls from validity, and the field's own validators survive intact for when it
|
|
1484
|
-
* comes back. Values are untouched — `flattenFormValues` and `extractRecord` both read
|
|
1485
|
-
* `control.value`, which a disabled control still carries, so rule evaluation and the
|
|
1486
|
-
* emitted record see exactly what they saw before. That also keeps this from feeding
|
|
1487
|
-
* back on itself: hiding a field cannot change the values the rules are evaluated over.
|
|
1624
|
+
* Keep a hidden field's control out of form validity — see
|
|
1625
|
+
* `RulesEvaluationService.syncHiddenFieldState`, which owns the rule and the reasoning.
|
|
1488
1626
|
*
|
|
1489
|
-
*
|
|
1490
|
-
*
|
|
1627
|
+
* This supplies the three things it cannot know: which fields exist and in what order
|
|
1628
|
+
* (`collectFieldScopes`, parent-first), what each one is called, and which control each
|
|
1629
|
+
* one addresses.
|
|
1491
1630
|
*/
|
|
1492
1631
|
private syncHiddenFieldState;
|
|
1493
1632
|
/** Every field in the config, including `group`/`array` children. */
|
|
1494
1633
|
private allFields;
|
|
1495
1634
|
/** An empty `originalData` counts as "not supplied" — fall back to the captured baseline. */
|
|
1496
1635
|
private baseline;
|
|
1636
|
+
/**
|
|
1637
|
+
* Whether a critical field still holds what it held when the session started.
|
|
1638
|
+
*
|
|
1639
|
+
* The same question `VALUE_CHANGED` asks, so it is answered by the same comparator rather
|
|
1640
|
+
* than by a third one. The local version compared objects through `JSON.stringify`, which
|
|
1641
|
+
* is key-order sensitive — two serialisers writing the same option in a different key
|
|
1642
|
+
* order made the field read as edited — and had its own idea of which values count as
|
|
1643
|
+
* empty. `valuesEqual` is order-independent and treats `null` and `undefined` as the same
|
|
1644
|
+
* absence; the empty-string case is kept here, because a cleared text input is not an edit.
|
|
1645
|
+
*/
|
|
1497
1646
|
private sameValue;
|
|
1647
|
+
/**
|
|
1648
|
+
* Find a control by a bracketed path or a bare field id.
|
|
1649
|
+
*
|
|
1650
|
+
* Public API, so it stays on the component and delegates — see `FormStructureService`,
|
|
1651
|
+
* which owns the resolution rule and is where the behaviour is documented.
|
|
1652
|
+
*/
|
|
1498
1653
|
getControl(fieldId: string, currentTabId?: string): AbstractControl | null;
|
|
1499
1654
|
/**
|
|
1500
1655
|
* Move focus into the newly shown panel.
|
|
@@ -2300,7 +2455,7 @@ declare function fieldDomId(field: {
|
|
|
2300
2455
|
* is read first. `null` rather than an empty string when there is nothing to describe — an
|
|
2301
2456
|
* empty `aria-describedby` is a dangling reference, not an absent one.
|
|
2302
2457
|
*
|
|
2303
|
-
* Shared rather than written into each of the
|
|
2458
|
+
* Shared rather than written into each of the twenty-one field components, because the rule is
|
|
2304
2459
|
* the same everywhere and getting it wrong is invisible until somebody uses a screen reader.
|
|
2305
2460
|
*/
|
|
2306
2461
|
declare function fieldDescribedBy(domId: (suffix?: string) => string, parts: {
|
|
@@ -2431,7 +2586,14 @@ declare class MarkdownFieldComponent implements OnChanges {
|
|
|
2431
2586
|
* A renderer is consumer code and may throw on input it does not like. Letting that
|
|
2432
2587
|
* escape would take down the whole form over one malformed field, so it falls back to the
|
|
2433
2588
|
* source text — which is what the field would have shown with no renderer at all.
|
|
2589
|
+
*
|
|
2590
|
+
* Memoised on the source, because this is called from the template: without it the
|
|
2591
|
+
* consumer's markdown parser ran on every change-detection pass, for every markdown field
|
|
2592
|
+
* on screen, over the whole document — which on a form holding a few long ones is a parse
|
|
2593
|
+
* per keystroke *elsewhere* in the form. The cache is keyed by the exact source string,
|
|
2594
|
+
* so a value that comes back to something already rendered is free too.
|
|
2434
2595
|
*/
|
|
2596
|
+
private cached?;
|
|
2435
2597
|
protected rendered(): string | null;
|
|
2436
2598
|
/**
|
|
2437
2599
|
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
@@ -2867,11 +3029,20 @@ declare class DropdownFieldComponent {
|
|
|
2867
3029
|
readonly: boolean;
|
|
2868
3030
|
masked: boolean;
|
|
2869
3031
|
/** Inline `options`, or the field's named list resolved through the registry (§6.3). */
|
|
2870
|
-
readonly options: i0.WritableSignal<
|
|
3032
|
+
readonly options: i0.WritableSignal<DropdownOption[]>;
|
|
2871
3033
|
readonly compareFn: (o1: unknown, o2: unknown) => boolean;
|
|
2872
3034
|
isObjectVal(option: DropdownOption): boolean;
|
|
2873
3035
|
get label(): string;
|
|
2874
3036
|
get placeholder(): string;
|
|
3037
|
+
/**
|
|
3038
|
+
* The value this option stores — the option object itself.
|
|
3039
|
+
*
|
|
3040
|
+
* Kept as a method rather than binding `option` straight into the template, because it is
|
|
3041
|
+
* the one place that says *why* the two are the same thing. It used to delegate to
|
|
3042
|
+
* `getOptionStoredValue`, an exported identity function whose two branches returned their
|
|
3043
|
+
* argument unchanged; naming a transformation that does not happen is worse than naming
|
|
3044
|
+
* nothing, because a reader has to go and check.
|
|
3045
|
+
*/
|
|
2875
3046
|
getOptStoredVal(option: DropdownOption): unknown;
|
|
2876
3047
|
getOptLabel(option: DropdownOption): string;
|
|
2877
3048
|
/**
|
|
@@ -2890,7 +3061,7 @@ declare class DropdownFieldComponent {
|
|
|
2890
3061
|
}
|
|
2891
3062
|
|
|
2892
3063
|
/** Radio field: a group of radio buttons built from field.options. */
|
|
2893
|
-
declare class RadioFieldComponent {
|
|
3064
|
+
declare class RadioFieldComponent implements OnDestroy {
|
|
2894
3065
|
/**
|
|
2895
3066
|
* Unique to this component instance: an `array` renders the same field once per row, and a
|
|
2896
3067
|
* DOM id may not repeat. See `field-dom-id.ts`.
|
|
@@ -2901,19 +3072,34 @@ declare class RadioFieldComponent {
|
|
|
2901
3072
|
protected readonly maskedText: string;
|
|
2902
3073
|
private readonly messages;
|
|
2903
3074
|
private readonly lookups;
|
|
3075
|
+
private readonly cdr;
|
|
2904
3076
|
private _field;
|
|
2905
3077
|
private _language;
|
|
3078
|
+
private _control;
|
|
3079
|
+
private _controlSub?;
|
|
3080
|
+
ngOnDestroy(): void;
|
|
2906
3081
|
/** Setter-based so options resolve however the input is set — see `refreshChoiceOptions`. */
|
|
2907
3082
|
set field(value: NestedFieldConfig);
|
|
2908
3083
|
get field(): NestedFieldConfig;
|
|
2909
3084
|
set language(value: string);
|
|
2910
3085
|
get language(): string;
|
|
2911
|
-
control: AbstractControl;
|
|
3086
|
+
set control(value: AbstractControl);
|
|
3087
|
+
get control(): AbstractControl;
|
|
2912
3088
|
readonly: boolean;
|
|
2913
3089
|
masked: boolean;
|
|
2914
3090
|
/** Inline `options`, or the field's named list resolved through the registry (§6.3). */
|
|
2915
|
-
readonly options: i0.WritableSignal<
|
|
3091
|
+
readonly options: i0.WritableSignal<DropdownOption[]>;
|
|
3092
|
+
onSelect(option: DropdownOption): void;
|
|
2916
3093
|
get label(): string;
|
|
3094
|
+
/**
|
|
3095
|
+
* The value this option stores — the option object itself.
|
|
3096
|
+
*
|
|
3097
|
+
* Kept as a method rather than binding `option` straight into the template, because it is
|
|
3098
|
+
* the one place that says *why* the two are the same thing. It used to delegate to
|
|
3099
|
+
* `getOptionStoredValue`, an exported identity function whose two branches returned their
|
|
3100
|
+
* argument unchanged; naming a transformation that does not happen is worse than naming
|
|
3101
|
+
* nothing, because a reader has to go and check.
|
|
3102
|
+
*/
|
|
2917
3103
|
getOptStoredVal(option: DropdownOption): unknown;
|
|
2918
3104
|
/**
|
|
2919
3105
|
* Stable DOM id for one radio, so the <label for> association works.
|
|
@@ -2931,6 +3117,8 @@ declare class RadioFieldComponent {
|
|
|
2931
3117
|
optionSlug(option: DropdownOption): string;
|
|
2932
3118
|
getOptVal(option: DropdownOption): string | number | boolean;
|
|
2933
3119
|
getOptLabel(option: DropdownOption): string;
|
|
3120
|
+
/** Whether the given option matches the control's current value. */
|
|
3121
|
+
isChecked(option: DropdownOption): boolean;
|
|
2934
3122
|
/** Read-only display — synchronous, per §6.2. See `DropdownFieldComponent.getLabel`. */
|
|
2935
3123
|
getSelectedLabel(): string;
|
|
2936
3124
|
/**
|
|
@@ -2970,10 +3158,19 @@ declare class MultiSelectFieldComponent {
|
|
|
2970
3158
|
readonly: boolean;
|
|
2971
3159
|
masked: boolean;
|
|
2972
3160
|
/** Inline `options`, or the field's named list resolved through the registry (§6.3). */
|
|
2973
|
-
readonly options: i0.WritableSignal<
|
|
3161
|
+
readonly options: i0.WritableSignal<DropdownOption[]>;
|
|
2974
3162
|
readonly compareFn: (o1: unknown, o2: unknown) => boolean;
|
|
2975
3163
|
isObjectVal(option: DropdownOption): boolean;
|
|
2976
3164
|
get label(): string;
|
|
3165
|
+
/**
|
|
3166
|
+
* The value this option stores — the option object itself.
|
|
3167
|
+
*
|
|
3168
|
+
* Kept as a method rather than binding `option` straight into the template, because it is
|
|
3169
|
+
* the one place that says *why* the two are the same thing. It used to delegate to
|
|
3170
|
+
* `getOptionStoredValue`, an exported identity function whose two branches returned their
|
|
3171
|
+
* argument unchanged; naming a transformation that does not happen is worse than naming
|
|
3172
|
+
* nothing, because a reader has to go and check.
|
|
3173
|
+
*/
|
|
2977
3174
|
getOptStoredVal(option: DropdownOption): unknown;
|
|
2978
3175
|
getOptLabel(option: DropdownOption): string;
|
|
2979
3176
|
/** Read-only display — synchronous, per §6.2. See `DropdownFieldComponent.getLabel`. */
|
|
@@ -3197,4 +3394,4 @@ declare class FileFieldComponent {
|
|
|
3197
3394
|
}
|
|
3198
3395
|
|
|
3199
3396
|
export { ASYNC_VALIDATOR_REGISTRY, ArrayFieldComponent, BooleanFieldComponent, COMMON_MODULES_REGISTRY, CONFIG_SOURCE, CascadeDataService, CheckboxFieldComponent, ConfigSourceService, CurrencyFieldComponent, DEFAULT_UI_TEXT, DEFAULT_VALIDATION_MESSAGES, DateFieldComponent, DateTimeFieldComponent, DropdownFieldComponent, DynamicFieldComponent, DynamicFormComponent, DynamicRecordFormComponent, ENTITY_REF_CACHE_STORE, ENTITY_REF_REGISTRY, EmailFieldComponent, EntityImportComponent, EntityRefFieldComponent, EntityRefRegistryService, EntityRefSelectionService, EntityReferenceService, FIELD_TYPE_REGISTRY, FIELD_TYPE_SETS, FieldRegistryService, FileFieldComponent, GroupFieldComponent, HOOK_REGISTRY, HookRegistryService, HttpImportTransport, IMPORT_TRANSPORT, ImageFieldComponent, ImportErrorsComponent, ImportMapperComponent, ImportPreviewComponent, ImportTemplateComponent, InMemoryEntityRefCacheStore, LOOKUP_REGISTRY, LocalImportTransport, LookupRegistryService, MARKDOWN_RENDERER, MASKED_PLACEHOLDER, MASKED_ROLES, MarkdownFieldComponent, MonthYearFieldComponent, MultiSelectFieldComponent, NumberFieldComponent, PasswordFieldComponent, RECORD_MIGRATIONS, RadioFieldComponent, RbacService, RulesEvaluationService, SHEET_PARSER, SYSTEM_DEFAULT_CAN_EDIT, TextFieldComponent, TextareaFieldComponent, TimeFieldComponent, UI_TEXT, UPLOAD_HANDLER, UiTextService, VALIDATION_MESSAGES, VALIDATOR_REGISTRY, ValidationMessagesService, ValidatorRegistryService, builtInFieldTypes, defaultSheetParser, fieldDescribedBy, fieldDomId, nextFieldInstanceId, provideBuiltInFieldTypes, provideFieldTypes, provideHttpImportTransport, provideNgxDynamicEntity, refreshChoiceOptions, resolveUiText };
|
|
3200
|
-
export type { ChoiceOptionHost, ConfigSourceHandler, DynamicFieldComponentContract, EntityRefCacheStore, EntityRefSelection, HookFn, HttpImportTransportOptions, ImportContext, ImportPreview, ImportTransport, InvalidField, MessageOrderEntry, NgxDynamicEntityConfig, SheetParser, TemplateFormat, UiTextKey, UiTextOverrides, UiTextOverridesFor, UiTextParams, UiTextResolver, UiTextResolverFor, UiTextValue, ValidationMessage };
|
|
3397
|
+
export type { ChoiceOptionHost, ConfigSourceHandler, DynamicFieldComponentContract, EntityRefCacheStore, EntityRefSelection, HookFn, HttpImportTransportOptions, ImportContext, ImportPreview, ImportTransport, InvalidField, MessageOrderEntry, NgxDynamicEntityConfig, RuleSyncContext, SheetParser, TemplateFormat, UiTextKey, UiTextOverrides, UiTextOverridesFor, UiTextParams, UiTextResolver, UiTextResolverFor, UiTextValue, ValidationMessage };
|