ngx-dynamic-entity 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +15 -1
  2. package/fesm2022/ngx-dynamic-entity.mjs +512 -161
  3. package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
  4. package/package.json +6 -7
  5. package/types/ngx-dynamic-entity.d.ts +1719 -0
  6. package/esm2022/lib/field-types/array-field.component.mjs +0 -168
  7. package/esm2022/lib/field-types/boolean-field.component.mjs +0 -101
  8. package/esm2022/lib/field-types/checkbox-field.component.mjs +0 -82
  9. package/esm2022/lib/field-types/currency-field.component.mjs +0 -102
  10. package/esm2022/lib/field-types/date-field.component.mjs +0 -91
  11. package/esm2022/lib/field-types/dropdown-field.component.mjs +0 -176
  12. package/esm2022/lib/field-types/email-field.component.mjs +0 -102
  13. package/esm2022/lib/field-types/entity-ref-field.component.mjs +0 -195
  14. package/esm2022/lib/field-types/field-component.contract.mjs +0 -2
  15. package/esm2022/lib/field-types/file-field.component.mjs +0 -157
  16. package/esm2022/lib/field-types/group-field.component.mjs +0 -89
  17. package/esm2022/lib/field-types/image-field.component.mjs +0 -169
  18. package/esm2022/lib/field-types/month-year-field.component.mjs +0 -152
  19. package/esm2022/lib/field-types/multi-select-field.component.mjs +0 -146
  20. package/esm2022/lib/field-types/number-field.component.mjs +0 -112
  21. package/esm2022/lib/field-types/password-field.component.mjs +0 -110
  22. package/esm2022/lib/field-types/radio-field.component.mjs +0 -153
  23. package/esm2022/lib/field-types/text-field.component.mjs +0 -121
  24. package/esm2022/lib/field-types/textarea-field.component.mjs +0 -85
  25. package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +0 -151
  26. package/esm2022/lib/form/dynamic-form.component.mjs +0 -890
  27. package/esm2022/lib/form/dynamic-record-form.component.mjs +0 -494
  28. package/esm2022/lib/providers/provide-field-types.mjs +0 -72
  29. package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +0 -27
  30. package/esm2022/lib/services/cascade-data.service.mjs +0 -129
  31. package/esm2022/lib/services/config-source.service.mjs +0 -67
  32. package/esm2022/lib/services/entity-ref-cache.mjs +0 -20
  33. package/esm2022/lib/services/entity-ref-registry.service.mjs +0 -31
  34. package/esm2022/lib/services/entity-ref-selection.service.mjs +0 -27
  35. package/esm2022/lib/services/entity-reference.service.mjs +0 -168
  36. package/esm2022/lib/services/field-registry.service.mjs +0 -55
  37. package/esm2022/lib/services/file-upload.service.mjs +0 -89
  38. package/esm2022/lib/services/hook-registry.service.mjs +0 -26
  39. package/esm2022/lib/services/lookup-registry.service.mjs +0 -246
  40. package/esm2022/lib/services/rbac.service.mjs +0 -46
  41. package/esm2022/lib/services/rules-evaluation.service.mjs +0 -27
  42. package/esm2022/lib/services/validation-messages.service.mjs +0 -56
  43. package/esm2022/lib/services/validator-registry.service.mjs +0 -99
  44. package/esm2022/lib/tokens/injection-tokens.mjs +0 -126
  45. package/esm2022/ngx-dynamic-entity.mjs +0 -5
  46. package/esm2022/public-api.mjs +0 -56
  47. package/index.d.ts +0 -5
  48. package/lib/field-types/array-field.component.d.ts +0 -28
  49. package/lib/field-types/boolean-field.component.d.ts +0 -18
  50. package/lib/field-types/checkbox-field.component.d.ts +0 -13
  51. package/lib/field-types/currency-field.component.d.ts +0 -17
  52. package/lib/field-types/date-field.component.d.ts +0 -15
  53. package/lib/field-types/dropdown-field.component.d.ts +0 -34
  54. package/lib/field-types/email-field.component.d.ts +0 -15
  55. package/lib/field-types/entity-ref-field.component.d.ts +0 -49
  56. package/lib/field-types/field-component.contract.d.ts +0 -49
  57. package/lib/field-types/file-field.component.d.ts +0 -25
  58. package/lib/field-types/group-field.component.d.ts +0 -17
  59. package/lib/field-types/image-field.component.d.ts +0 -30
  60. package/lib/field-types/month-year-field.component.d.ts +0 -28
  61. package/lib/field-types/multi-select-field.component.d.ts +0 -27
  62. package/lib/field-types/number-field.component.d.ts +0 -16
  63. package/lib/field-types/password-field.component.d.ts +0 -17
  64. package/lib/field-types/radio-field.component.d.ts +0 -33
  65. package/lib/field-types/text-field.component.d.ts +0 -23
  66. package/lib/field-types/textarea-field.component.d.ts +0 -14
  67. package/lib/form/dynamic-field/dynamic-field.component.d.ts +0 -78
  68. package/lib/form/dynamic-form.component.d.ts +0 -291
  69. package/lib/form/dynamic-record-form.component.d.ts +0 -180
  70. package/lib/providers/provide-field-types.d.ts +0 -24
  71. package/lib/providers/provide-ngx-dynamic-entity.d.ts +0 -56
  72. package/lib/services/cascade-data.service.d.ts +0 -45
  73. package/lib/services/config-source.service.d.ts +0 -14
  74. package/lib/services/entity-ref-cache.d.ts +0 -27
  75. package/lib/services/entity-ref-registry.service.d.ts +0 -21
  76. package/lib/services/entity-ref-selection.service.d.ts +0 -24
  77. package/lib/services/entity-reference.service.d.ts +0 -49
  78. package/lib/services/field-registry.service.d.ts +0 -32
  79. package/lib/services/file-upload.service.d.ts +0 -27
  80. package/lib/services/hook-registry.service.d.ts +0 -12
  81. package/lib/services/lookup-registry.service.d.ts +0 -94
  82. package/lib/services/rbac.service.d.ts +0 -27
  83. package/lib/services/rules-evaluation.service.d.ts +0 -17
  84. package/lib/services/validation-messages.service.d.ts +0 -43
  85. package/lib/services/validator-registry.service.d.ts +0 -32
  86. package/lib/tokens/injection-tokens.d.ts +0 -134
  87. package/public-api.d.ts +0 -40
@@ -1,16 +0,0 @@
1
- import { AbstractControl } from '@angular/forms';
2
- import type { NestedFieldConfig } from '@dynamic-entity/core';
3
- import * as i0 from "@angular/core";
4
- export declare class NumberFieldComponent {
5
- private readonly messages;
6
- field: NestedFieldConfig;
7
- control: AbstractControl;
8
- language: string;
9
- readonly: boolean;
10
- masked: boolean;
11
- get label(): string;
12
- get placeholder(): string;
13
- get errorMessage(): string;
14
- static ɵfac: i0.ɵɵFactoryDeclaration<NumberFieldComponent, never>;
15
- static ɵcmp: i0.ɵɵComponentDeclaration<NumberFieldComponent, "ngx-number-field", never, { "field": { "alias": "field"; "required": false; }; "control": { "alias": "control"; "required": false; }; "language": { "alias": "language"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "masked": { "alias": "masked"; "required": false; }; }, {}, never, never, true, never>;
16
- }
@@ -1,17 +0,0 @@
1
- import { AbstractControl } from '@angular/forms';
2
- import type { NestedFieldConfig } from '@dynamic-entity/core';
3
- import * as i0 from "@angular/core";
4
- /** Password field: masked input with show/hide eye toggle. */
5
- export declare class PasswordFieldComponent {
6
- field: NestedFieldConfig;
7
- control: AbstractControl;
8
- language: string;
9
- readonly: boolean;
10
- masked: boolean;
11
- readonly visible: import("@angular/core").WritableSignal<boolean>;
12
- get label(): string;
13
- get placeholder(): string;
14
- toggleVisibility(): void;
15
- static ɵfac: i0.ɵɵFactoryDeclaration<PasswordFieldComponent, never>;
16
- static ɵcmp: i0.ɵɵComponentDeclaration<PasswordFieldComponent, "ngx-password-field", never, { "field": { "alias": "field"; "required": false; }; "control": { "alias": "control"; "required": false; }; "language": { "alias": "language"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "masked": { "alias": "masked"; "required": false; }; }, {}, never, never, true, never>;
17
- }
@@ -1,33 +0,0 @@
1
- import { AbstractControl } from '@angular/forms';
2
- import type { NestedFieldConfig } from '@dynamic-entity/core';
3
- import * as i0 from "@angular/core";
4
- /** Radio field: a group of radio buttons built from field.options. */
5
- export declare class RadioFieldComponent {
6
- private readonly lookups;
7
- private _field;
8
- private _language;
9
- /** Setter-based so options resolve however the input is set — see `refreshChoiceOptions`. */
10
- set field(value: NestedFieldConfig);
11
- get field(): NestedFieldConfig;
12
- set language(value: string);
13
- get language(): string;
14
- control: AbstractControl;
15
- readonly: boolean;
16
- masked: boolean;
17
- /** Inline `options`, or the field's named list resolved through the registry (§6.3). */
18
- readonly options: import("@angular/core").WritableSignal<DropdownOption[]>;
19
- get label(): string;
20
- getOptStoredVal(option: any): any;
21
- /**
22
- * Stable DOM id for one radio, so the <label for> association works.
23
- * Slugified because the option text is the value now — "On Leave" must not put a space
24
- * into an id attribute.
25
- */
26
- getRadioId(option: any): string;
27
- getOptVal(option: any): any;
28
- getOptLabel(option: any): string;
29
- /** Read-only display — synchronous, per §6.2. See `DropdownFieldComponent.getLabel`. */
30
- getSelectedLabel(): string;
31
- static ɵfac: i0.ɵɵFactoryDeclaration<RadioFieldComponent, never>;
32
- static ɵcmp: i0.ɵɵComponentDeclaration<RadioFieldComponent, "ngx-radio-field", never, { "field": { "alias": "field"; "required": false; }; "language": { "alias": "language"; "required": false; }; "control": { "alias": "control"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "masked": { "alias": "masked"; "required": false; }; }, {}, never, never, true, never>;
33
- }
@@ -1,23 +0,0 @@
1
- import { OnDestroy } from '@angular/core';
2
- import { AbstractControl } from '@angular/forms';
3
- import type { NestedFieldConfig } from '@dynamic-entity/core';
4
- import * as i0 from "@angular/core";
5
- export declare class TextFieldComponent implements OnDestroy {
6
- private readonly messages;
7
- private displaySub?;
8
- field: NestedFieldConfig;
9
- set control(value: AbstractControl);
10
- get control(): AbstractControl;
11
- private _control;
12
- language: string;
13
- readonly: boolean;
14
- masked: boolean;
15
- /** Readonly markup reads this signal so an external patch (autoPatch) is visible under OnPush. */
16
- readonly displayValue: import("@angular/core").WritableSignal<unknown>;
17
- ngOnDestroy(): void;
18
- get label(): string;
19
- get placeholder(): string;
20
- get errorMessage(): string;
21
- static ɵfac: i0.ɵɵFactoryDeclaration<TextFieldComponent, never>;
22
- static ɵcmp: i0.ɵɵComponentDeclaration<TextFieldComponent, "ngx-text-field", never, { "field": { "alias": "field"; "required": false; }; "control": { "alias": "control"; "required": false; }; "language": { "alias": "language"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "masked": { "alias": "masked"; "required": false; }; }, {}, never, never, true, never>;
23
- }
@@ -1,14 +0,0 @@
1
- import { AbstractControl } from '@angular/forms';
2
- import type { NestedFieldConfig } from '@dynamic-entity/core';
3
- import * as i0 from "@angular/core";
4
- export declare class TextareaFieldComponent {
5
- field: NestedFieldConfig;
6
- control: AbstractControl;
7
- language: string;
8
- readonly: boolean;
9
- masked: boolean;
10
- get label(): string;
11
- get placeholder(): string;
12
- static ɵfac: i0.ɵɵFactoryDeclaration<TextareaFieldComponent, never>;
13
- static ɵcmp: i0.ɵɵComponentDeclaration<TextareaFieldComponent, "ngx-textarea-field", never, { "field": { "alias": "field"; "required": false; }; "control": { "alias": "control"; "required": false; }; "language": { "alias": "language"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "masked": { "alias": "masked"; "required": false; }; }, {}, never, never, true, never>;
14
- }
@@ -1,78 +0,0 @@
1
- import { OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
2
- import { AbstractControl } from '@angular/forms';
3
- import type { NestedFieldConfig, EntityFormConfig } from '@dynamic-entity/core';
4
- import * as i0 from "@angular/core";
5
- /**
6
- * DynamicFieldComponent — mounts the correct field component for a given NestedFieldConfig.
7
- * Uses ViewContainerRef.createComponent() and passes inputs via setInput(); see
8
- * DynamicFieldComponentContract for the five inputs every field component receives.
9
- * Never add field-type-specific logic here — this component must remain generic.
10
- *
11
- * The 5 inputs (field, control, language, readonly, masked) are passed to every mounted
12
- * component uniformly — no special-casing per type.
13
- */
14
- export declare class DynamicFieldComponent implements OnChanges, OnDestroy {
15
- field: NestedFieldConfig;
16
- control: AbstractControl;
17
- config: EntityFormConfig;
18
- currentTabId?: string;
19
- language: string;
20
- readonly: boolean;
21
- userRoles: string[];
22
- private readonly fieldHost;
23
- private readonly fieldRegistry;
24
- private readonly rbacService;
25
- /**
26
- * Passed into `createComponent` so a hosted field sees this host's injector — including
27
- * `DynamicFormComponent`'s scoped `EntityRefSelectionService`. Without it, a standalone
28
- * field resolves the root bus and `autoPatch` never runs.
29
- */
30
- private readonly injector;
31
- private componentRef;
32
- ngOnChanges(_changes: SimpleChanges): void;
33
- private mountField;
34
- /**
35
- * Field types are opt-in (see `provideFieldTypes` / `provideBuiltInFieldTypes`), so an
36
- * unregistered type renders nothing. Say so once per type in dev builds rather than
37
- * leaving a silently blank slot.
38
- */
39
- private warnUnresolved;
40
- private static readonly warnedTypes;
41
- /**
42
- * Re-check the hosted field component.
43
- *
44
- * The field components are OnPush, so they re-render on an input change, an event from
45
- * their own template, or a signal they read. `form.markAllAsTouched()` is none of those —
46
- * it flips `touched` from outside, and every field's error message depends on it. Without
47
- * this, a blocked submit would mark the form touched and no error would appear.
48
- */
49
- refresh(): void;
50
- /**
51
- * Re-check the hosted component whenever its control changes from outside.
52
- *
53
- * The field components are OnPush, so they re-render on an input change or an event from
54
- * their own template. A control mutated externally is neither — and that is not an edge
55
- * case: patchForm, reset, autoPatch and patchOnTrue all do exactly that. Without this, a
56
- * field patched from a record would keep rendering its previous value.
57
- *
58
- * `detectChanges()` rather than `markForCheck()`: a selection that arrives from a native
59
- * `change` event (or Playwright) may not be followed by another Angular tick, so a dirty
60
- * flag would never be flushed. Running CD on the hosted component now is the copy showing up.
61
- *
62
- * Done here rather than in each of the eighteen field components: the host owns the
63
- * component reference, so one subscription covers every type including custom ones.
64
- */
65
- private watchControl;
66
- private controlSub?;
67
- ngOnDestroy(): void;
68
- /**
69
- * Pass all 5 contract inputs via ComponentRef.setInput() — uniform for all types.
70
- * The public shape is DynamicFieldComponentContract.
71
- * Must use setInput(), not property assignment: the field components declare inputs with
72
- * definite assignment and no initializer, and the lib compiles with
73
- * useDefineForClassFields:false, so `'field' in instance` is false until first set.
74
- */
75
- private setInputs;
76
- static ɵfac: i0.ɵɵFactoryDeclaration<DynamicFieldComponent, never>;
77
- static ɵcmp: i0.ɵɵComponentDeclaration<DynamicFieldComponent, "ngx-dynamic-field", never, { "field": { "alias": "field"; "required": false; }; "control": { "alias": "control"; "required": false; }; "config": { "alias": "config"; "required": false; }; "currentTabId": { "alias": "currentTabId"; "required": false; }; "language": { "alias": "language"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "userRoles": { "alias": "userRoles"; "required": false; }; }, {}, never, never, true, never>;
78
- }
@@ -1,291 +0,0 @@
1
- import { EventEmitter, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
2
- import { AbstractControl, FormArray, FormGroup } from '@angular/forms';
3
- import type { EntityFormConfig, FormRule, NestedFieldConfig, NestedTabConfig } from '@dynamic-entity/core';
4
- import { RbacService } from '../services/rbac.service';
5
- import * as i0 from "@angular/core";
6
- /**
7
- * DynamicFormComponent — the main form component.
8
- * Renders a reactive form from EntityFormConfig with tab support, responsive 12-col grid,
9
- * rules evaluation, conditional visibility, autoPatch/patchOnTrue, criticalField locking,
10
- * keyboard shortcuts (Ctrl+S, Esc), and RBAC-gated submission.
11
- */
12
- export declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
13
- /**
14
- * The schema to render.
15
- *
16
- * An accessor pair rather than a plain field: `ngOnChanges` normalises the option shape,
17
- * and that result is kept here instead of being written back over the input. Reassigning
18
- * an `@Input` works, but it mutates what the parent handed us — and a parent using OnPush
19
- * or signals would never observe the replacement anyway.
20
- *
21
- * Reads of `this.config` get the normalised copy once it exists, so every internal caller
22
- * is unaffected.
23
- */
24
- set config(value: EntityFormConfig);
25
- get config(): EntityFormConfig;
26
- private rawConfig;
27
- private normalizedConfig?;
28
- rules?: FormRule[];
29
- initialData?: Record<string, any>;
30
- /** Session-original values for `VALUE_CHANGED` rules. Captured from the first build when omitted. */
31
- originalData?: Record<string, any>;
32
- userRoles: string[];
33
- language: string;
34
- readonly: boolean;
35
- /** Field ids forced read-only while the rest of the form stays editable. */
36
- readOnlyFields: string[];
37
- /**
38
- * Render rule `info` banners inline. The record editor sets this false and renders its
39
- * own dismissible versions, so the two do not double up.
40
- */
41
- showInfoBanners: boolean;
42
- loading: boolean;
43
- error: string | null;
44
- /**
45
- * Milliseconds to debounce `formChange`. The reference debounces at 300ms so a consumer
46
- * re-rendering on every value change is not driven per keystroke. Zero emits
47
- * synchronously, which the record editor relies on for its own bookkeeping.
48
- */
49
- changeDebounceMs: number;
50
- /**
51
- * Preview mode: seed one empty row per `array` field so the structure is visible, then
52
- * disable the form. Used by the builder's live preview.
53
- */
54
- preview: boolean;
55
- formSubmit: EventEmitter<Record<string, any>>;
56
- formChange: EventEmitter<Record<string, any>>;
57
- formReset: EventEmitter<void>;
58
- /** The visible tab changed. Lets a host track it without reaching into this component. */
59
- activeTabChange: EventEmitter<string>;
60
- /**
61
- * Emitted when a `beforeSave` hook aborted the save — by returning false or throwing.
62
- * `formSubmit` does not fire in that case.
63
- */
64
- saveRejected: EventEmitter<{
65
- reason: string;
66
- error?: unknown;
67
- }>;
68
- private readonly fb;
69
- private readonly validatorRegistry;
70
- private readonly hookRegistry;
71
- private readonly rbacService;
72
- private readonly rulesEvaluation;
73
- private readonly entityRefSelection;
74
- private readonly commonModulesRegistry;
75
- private readonly migrations;
76
- protected readonly Object: ObjectConstructor;
77
- readonly activeTab: import("@angular/core").WritableSignal<string>;
78
- readonly activeSubTab: import("@angular/core").WritableSignal<string>;
79
- readonly isSaving: import("@angular/core").WritableSignal<boolean>;
80
- readonly formValues: import("@angular/core").WritableSignal<Record<string, any>>;
81
- /** Baseline captured at first build, used when `originalData` is not supplied. */
82
- readonly sessionBaseline: import("@angular/core").WritableSignal<Record<string, any>>;
83
- /** Critical fields the user has explicitly unlocked for editing. */
84
- readonly unlockedFields: import("@angular/core").WritableSignal<ReadonlySet<string>>;
85
- /** The active tab's field panel, focused after a tab switch. */
86
- private formPanel?;
87
- /** The hosted field components, so an external control-state change can reach them. */
88
- private fieldHosts?;
89
- form: FormGroup;
90
- private valueSub?;
91
- private selectionSub?;
92
- /** Previous values, for detecting `patchOnTrue` false→true transitions. */
93
- private previousValues;
94
- handleKeyboardEvent(event: KeyboardEvent): void;
95
- readonly ruleResult: import("@angular/core").Signal<RuleEvaluationResult>;
96
- /**
97
- * Critical fields whose value differs from the session baseline.
98
- * These drive the deferred `VALUE_CHANGED` banner — a critical edit is announced once,
99
- * against the value the record had when the session started, not per keystroke.
100
- */
101
- readonly changedCriticalFields: import("@angular/core").Signal<NestedFieldConfig[]>;
102
- get tabs(): NestedTabConfig[];
103
- get visibleTabs(): NestedTabConfig[];
104
- get activeTabConfig(): NestedTabConfig | null;
105
- get visibleSubTabs(): NestedTabConfig[];
106
- get activeSubTabConfig(): NestedTabConfig | null;
107
- get activeTabModuleComponent(): any | null;
108
- get fieldsForActiveTab(): NestedFieldConfig[];
109
- /**
110
- * Entity-level permissions for the current roles.
111
- *
112
- * Cached rather than recomputed: this is read from the template several times per change
113
- * detection pass, and a fresh object each time is both wasted allocation and a new
114
- * identity for anything comparing by reference. Invalidated in `ngOnChanges` when
115
- * `config` or `userRoles` change, which are its only inputs.
116
- */
117
- get permissions(): ReturnType<RbacService['getPermissions']>;
118
- private permissionsCache;
119
- /**
120
- * Whether the current roles may see this record at all.
121
- *
122
- * `permissions.view` used to be computed and thrown away — a user whose roles failed it
123
- * still received the complete form with every value in the DOM. It is honoured now.
124
- *
125
- * As with masking, this is presentational: it stops the browser rendering data, it does
126
- * not stop the data reaching the browser. Authorize on the server.
127
- */
128
- get canView(): boolean;
129
- /**
130
- * Whether the current roles may delete this record.
131
- *
132
- * The library ships no delete affordance, so this is surfaced for the consumer to gate
133
- * their own — exposed rather than dropped, because a permission the schema declares
134
- * should be answerable.
135
- */
136
- get canDelete(): boolean;
137
- get canSubmit(): boolean;
138
- /**
139
- * Errors raised by `validation` rules, keyed by the target they point at.
140
- *
141
- * A rule can declare the record invalid independently of Angular's validators, so form
142
- * validity alone is not the whole picture. `DynamicRecordFormComponent.saveSection()`
143
- * has always honoured these; `submit()` did not, which meant the same rule blocked one
144
- * save path and merely painted a banner on the other — and anything wiring
145
- * `(formSubmit)` to persistence wrote records the rules engine had already rejected.
146
- */
147
- get ruleValidationErrors(): Record<string, string>;
148
- /** True when a `validation` rule is currently blocking submission. */
149
- get hasRuleErrors(): boolean;
150
- /**
151
- * True while an async validator is still running.
152
- *
153
- * Angular reports a control with an outstanding async check as `pending`, and `invalid` is
154
- * false until it settles — so without this a form could be submitted in the gap before a
155
- * uniqueness check came back.
156
- */
157
- get isValidating(): boolean;
158
- /** Single source of truth for both the submit guard and the button's disabled state. */
159
- get submitBlocked(): boolean;
160
- ngOnInit(): void;
161
- ngOnChanges(changes: SimpleChanges): void;
162
- ngOnDestroy(): void;
163
- setActiveTab(tabId: string): void;
164
- setActiveSubTab(subTabId: string): void;
165
- getFieldSpan(field: NestedFieldConfig): string;
166
- /** A critical field stays read-only until the user deliberately unlocks it. */
167
- isFieldLocked(field: NestedFieldConfig): boolean;
168
- /**
169
- * Whether this field renders read-only, from any of the four reasons: the whole form,
170
- * the field's own flag, a caller-supplied `readOnlyFields` entry, or a criticalField lock.
171
- */
172
- isFieldReadonly(field: NestedFieldConfig): boolean;
173
- toggleFieldLock(field: NestedFieldConfig): void;
174
- resolveFieldLabel(field: NestedFieldConfig): string;
175
- private buildForm;
176
- private buildFieldControl;
177
- /** One row of an `array` field: a FormGroup when the field declares columns, else a bare control. */
178
- /**
179
- * The `FormArray` behind an `array` field, for callers that manage rows themselves —
180
- * the record editor's inline row drawer.
181
- */
182
- getArrayControl(fieldId: string, tabId?: string): FormArray | null;
183
- /**
184
- * Build one detached row for an `array` field, with the field's own validators applied.
185
- * Exposed so a row can be edited outside the form and pushed on save.
186
- */
187
- createArrayRow(field: NestedFieldConfig, value?: unknown): AbstractControl;
188
- private buildArrayRow;
189
- /**
190
- * Mark every control touched, then re-check the field components.
191
- *
192
- * They are OnPush and `touched` is flipped from outside their own template, so the second
193
- * half is what makes the error messages actually appear.
194
- */
195
- private markAllTouched;
196
- /**
197
- * Bring a record up to the config's `version` before it reaches the form.
198
- *
199
- * Done here because this is the one place a record enters the renderer, so a consumer
200
- * cannot forget it. Migration is a no-op unless the consumer registered steps *and* the
201
- * record is behind — an unstamped record is deliberately left alone (see `migrateRecord`).
202
- *
203
- * A missing or broken step throws from core. That is not swallowed: rendering a
204
- * half-understood record would write it back in a shape matching neither schema.
205
- */
206
- private upgradeRecord;
207
- private patchForm;
208
- /**
209
- * A record is nested by tab id (`{ tabId: { fieldId: value } }`) unless the tab sets
210
- * `flatData: true`. Handing a flat record to a nested tab finds nothing, so the fields
211
- * stay empty — with no error and no clue. That silent miss is the most expensive way to
212
- * lose an hour with this library, so name it in dev builds.
213
- *
214
- * Only top-level keys that match a known field id are reported: anything else is assumed
215
- * to be the consumer's own record metadata (ids, timestamps, `_configVersion`) and is not
216
- * our business.
217
- */
218
- private warnUnconsumedInitialData;
219
- /** Warn once per key per form instance — patchForm re-runs on every initialData change. */
220
- private readonly warnedUnconsumedKeys;
221
- /** Assemble full nested record from per-tab FormGroups, respecting flatData, refererField & arrays. */
222
- extractRecord(): Record<string, any>;
223
- private flattenFormValues;
224
- private getTabGroup;
225
- /**
226
- * `autoPatch` — copy mapped fields out of the record the user just selected in an
227
- * entity-ref field into the configured target tab's controls.
228
- */
229
- private runAutoPatch;
230
- /**
231
- * `patchOnTrue` — when a boolean/checkbox field transitions to `true`,
232
- * copy `from` → `to` within the current record.
233
- */
234
- private runPatchOnTrue;
235
- /** Prefer a control declared on the configured target tab; fall back to a top-level control. */
236
- private resolveTargetControl;
237
- /**
238
- * The fields the render filter operates on — a tab's own fields, at every tab depth.
239
- * Deliberately not `allFields()`: `group` and `array` children are shown or hidden with
240
- * their parent, and toggling a child's state independently would fight the parent's.
241
- */
242
- private tabFields;
243
- /**
244
- * Keep a hidden field's control out of form validity.
245
- *
246
- * A field hidden by a rule or a `showWhen` condition is filtered out of the render, but
247
- * its validators stay attached — so a hidden *required* field holds `form.invalid` true
248
- * forever, permanently disabling Save with nothing on screen to explain why.
249
- *
250
- * Disabling is the fix rather than stripping validators: Angular excludes disabled
251
- * controls from validity, and the field's own validators survive intact for when it
252
- * comes back. Values are untouched — `flattenFormValues` and `extractRecord` both read
253
- * `control.value`, which a disabled control still carries, so rule evaluation and the
254
- * emitted record see exactly what they saw before. That also keeps this from feeding
255
- * back on itself: hiding a field cannot change the values the rules are evaluated over.
256
- *
257
- * The visibility predicate is the same one `fieldsForActiveTab` filters with, so what is
258
- * rendered and what counts toward validity cannot drift apart.
259
- */
260
- private syncHiddenFieldState;
261
- /** Every field in the config, including `group`/`array` children. */
262
- private allFields;
263
- /** An empty `originalData` counts as "not supplied" — fall back to the captured baseline. */
264
- private baseline;
265
- private sameValue;
266
- getControl(fieldId: string, currentTabId?: string): AbstractControl | null;
267
- /**
268
- * Move focus into the newly shown panel.
269
- *
270
- * Activating a tab replaces everything below it while focus stays on the tab button, so a
271
- * screen-reader or keyboard user is given no indication that the content changed and must
272
- * tab back through the strip to reach it. Deferred a frame because the panel's contents
273
- * are rendered by the change detection this call is part of.
274
- */
275
- private focusActivePanel;
276
- /** Accessible name for the field panel — it is the tabpanel for the active tab. */
277
- get activeTabLabel(): string;
278
- /**
279
- * Why a referenced field is flagged.
280
- *
281
- * `hasDrift` was written by the builder and read by nothing at runtime, so a field whose
282
- * source definition had changed since it was linked looked entirely normal to the person
283
- * filling the form in.
284
- */
285
- driftHint(field: NestedFieldConfig): string;
286
- resolveTabLabel(tab: NestedTabConfig): string;
287
- submit(): Promise<void>;
288
- reset(): void;
289
- static ɵfac: i0.ɵɵFactoryDeclaration<DynamicFormComponent, never>;
290
- static ɵcmp: i0.ɵɵComponentDeclaration<DynamicFormComponent, "ngx-dynamic-form", never, { "config": { "alias": "config"; "required": false; }; "rules": { "alias": "rules"; "required": false; }; "initialData": { "alias": "initialData"; "required": false; }; "originalData": { "alias": "originalData"; "required": false; }; "userRoles": { "alias": "userRoles"; "required": false; }; "language": { "alias": "language"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "readOnlyFields": { "alias": "readOnlyFields"; "required": false; }; "showInfoBanners": { "alias": "showInfoBanners"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "error": { "alias": "error"; "required": false; }; "changeDebounceMs": { "alias": "changeDebounceMs"; "required": false; }; "preview": { "alias": "preview"; "required": false; }; }, { "formSubmit": "formSubmit"; "formChange": "formChange"; "formReset": "formReset"; "activeTabChange": "activeTabChange"; "saveRejected": "saveRejected"; }, never, never, true, never>;
291
- }