ngx-dynamic-entity 1.3.0 → 1.5.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 (91) hide show
  1. package/README.md +14 -0
  2. package/fesm2022/ngx-dynamic-entity.mjs +255 -157
  3. package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
  4. package/package.json +6 -7
  5. package/types/ngx-dynamic-entity.d.ts +1730 -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/date-time-field.component.mjs +0 -138
  12. package/esm2022/lib/field-types/dropdown-field.component.mjs +0 -176
  13. package/esm2022/lib/field-types/email-field.component.mjs +0 -102
  14. package/esm2022/lib/field-types/entity-ref-field.component.mjs +0 -195
  15. package/esm2022/lib/field-types/field-component.contract.mjs +0 -2
  16. package/esm2022/lib/field-types/file-field.component.mjs +0 -157
  17. package/esm2022/lib/field-types/group-field.component.mjs +0 -89
  18. package/esm2022/lib/field-types/image-field.component.mjs +0 -169
  19. package/esm2022/lib/field-types/month-year-field.component.mjs +0 -152
  20. package/esm2022/lib/field-types/multi-select-field.component.mjs +0 -146
  21. package/esm2022/lib/field-types/number-field.component.mjs +0 -112
  22. package/esm2022/lib/field-types/password-field.component.mjs +0 -110
  23. package/esm2022/lib/field-types/radio-field.component.mjs +0 -153
  24. package/esm2022/lib/field-types/text-field.component.mjs +0 -121
  25. package/esm2022/lib/field-types/textarea-field.component.mjs +0 -85
  26. package/esm2022/lib/field-types/time-field.component.mjs +0 -96
  27. package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +0 -152
  28. package/esm2022/lib/form/dynamic-form.component.mjs +0 -899
  29. package/esm2022/lib/form/dynamic-record-form.component.mjs +0 -531
  30. package/esm2022/lib/providers/provide-field-types.mjs +0 -75
  31. package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +0 -27
  32. package/esm2022/lib/services/cascade-data.service.mjs +0 -129
  33. package/esm2022/lib/services/config-source.service.mjs +0 -67
  34. package/esm2022/lib/services/entity-ref-cache.mjs +0 -20
  35. package/esm2022/lib/services/entity-ref-registry.service.mjs +0 -31
  36. package/esm2022/lib/services/entity-ref-selection.service.mjs +0 -27
  37. package/esm2022/lib/services/entity-reference.service.mjs +0 -168
  38. package/esm2022/lib/services/field-registry.service.mjs +0 -55
  39. package/esm2022/lib/services/file-upload.service.mjs +0 -89
  40. package/esm2022/lib/services/hook-registry.service.mjs +0 -26
  41. package/esm2022/lib/services/lookup-registry.service.mjs +0 -246
  42. package/esm2022/lib/services/rbac.service.mjs +0 -46
  43. package/esm2022/lib/services/rules-evaluation.service.mjs +0 -27
  44. package/esm2022/lib/services/validation-messages.service.mjs +0 -56
  45. package/esm2022/lib/services/validator-registry.service.mjs +0 -99
  46. package/esm2022/lib/tokens/injection-tokens.mjs +0 -126
  47. package/esm2022/ngx-dynamic-entity.mjs +0 -5
  48. package/esm2022/public-api.mjs +0 -58
  49. package/index.d.ts +0 -5
  50. package/lib/field-types/array-field.component.d.ts +0 -28
  51. package/lib/field-types/boolean-field.component.d.ts +0 -18
  52. package/lib/field-types/checkbox-field.component.d.ts +0 -13
  53. package/lib/field-types/currency-field.component.d.ts +0 -17
  54. package/lib/field-types/date-field.component.d.ts +0 -15
  55. package/lib/field-types/date-time-field.component.d.ts +0 -30
  56. package/lib/field-types/dropdown-field.component.d.ts +0 -34
  57. package/lib/field-types/email-field.component.d.ts +0 -15
  58. package/lib/field-types/entity-ref-field.component.d.ts +0 -49
  59. package/lib/field-types/field-component.contract.d.ts +0 -49
  60. package/lib/field-types/file-field.component.d.ts +0 -25
  61. package/lib/field-types/group-field.component.d.ts +0 -17
  62. package/lib/field-types/image-field.component.d.ts +0 -30
  63. package/lib/field-types/month-year-field.component.d.ts +0 -28
  64. package/lib/field-types/multi-select-field.component.d.ts +0 -27
  65. package/lib/field-types/number-field.component.d.ts +0 -16
  66. package/lib/field-types/password-field.component.d.ts +0 -17
  67. package/lib/field-types/radio-field.component.d.ts +0 -33
  68. package/lib/field-types/text-field.component.d.ts +0 -23
  69. package/lib/field-types/textarea-field.component.d.ts +0 -14
  70. package/lib/field-types/time-field.component.d.ts +0 -27
  71. package/lib/form/dynamic-field/dynamic-field.component.d.ts +0 -78
  72. package/lib/form/dynamic-form.component.d.ts +0 -293
  73. package/lib/form/dynamic-record-form.component.d.ts +0 -199
  74. package/lib/providers/provide-field-types.d.ts +0 -24
  75. package/lib/providers/provide-ngx-dynamic-entity.d.ts +0 -56
  76. package/lib/services/cascade-data.service.d.ts +0 -45
  77. package/lib/services/config-source.service.d.ts +0 -14
  78. package/lib/services/entity-ref-cache.d.ts +0 -27
  79. package/lib/services/entity-ref-registry.service.d.ts +0 -21
  80. package/lib/services/entity-ref-selection.service.d.ts +0 -24
  81. package/lib/services/entity-reference.service.d.ts +0 -49
  82. package/lib/services/field-registry.service.d.ts +0 -32
  83. package/lib/services/file-upload.service.d.ts +0 -27
  84. package/lib/services/hook-registry.service.d.ts +0 -12
  85. package/lib/services/lookup-registry.service.d.ts +0 -94
  86. package/lib/services/rbac.service.d.ts +0 -27
  87. package/lib/services/rules-evaluation.service.d.ts +0 -17
  88. package/lib/services/validation-messages.service.d.ts +0 -43
  89. package/lib/services/validator-registry.service.d.ts +0 -32
  90. package/lib/tokens/injection-tokens.d.ts +0 -134
  91. package/public-api.d.ts +0 -42
@@ -1,27 +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 MultiSelectFieldComponent {
5
- private readonly lookups;
6
- private _field;
7
- private _language;
8
- /** Setter-based so options resolve however the input is set — see `refreshChoiceOptions`. */
9
- set field(value: NestedFieldConfig);
10
- get field(): NestedFieldConfig;
11
- set language(value: string);
12
- get language(): string;
13
- control: AbstractControl;
14
- readonly: boolean;
15
- masked: boolean;
16
- /** Inline `options`, or the field's named list resolved through the registry (§6.3). */
17
- readonly options: import("@angular/core").WritableSignal<DropdownOption[]>;
18
- readonly compareFn: (o1: any, o2: any) => boolean;
19
- isObjectVal(option: any): boolean;
20
- get label(): string;
21
- getOptStoredVal(option: any): any;
22
- getOptLabel(option: any): string;
23
- /** Read-only display — synchronous, per §6.2. See `DropdownFieldComponent.getLabel`. */
24
- getLabels(values: any[]): string;
25
- static ɵfac: i0.ɵɵFactoryDeclaration<MultiSelectFieldComponent, never>;
26
- static ɵcmp: i0.ɵɵComponentDeclaration<MultiSelectFieldComponent, "ngx-multi-select-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>;
27
- }
@@ -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,27 +0,0 @@
1
- import { AbstractControl } from '@angular/forms';
2
- import type { NestedFieldConfig } from '@dynamic-entity/core';
3
- import * as i0 from "@angular/core";
4
- /**
5
- * Time field: a bare time of day, with no date and no zone attached.
6
- *
7
- * Stored as `HH:mm` — exactly the value `<input type="time">` reads and writes, so the
8
- * control binds straight through with no conversion. That is the whole reason this is a
9
- * separate type from `datetime`: a recurring 09:00 opening time is not a moment in time,
10
- * and converting it to UTC would move it whenever the offset changed.
11
- */
12
- export declare class TimeFieldComponent {
13
- field: NestedFieldConfig;
14
- control: AbstractControl;
15
- language: string;
16
- readonly: boolean;
17
- masked: boolean;
18
- get label(): string;
19
- /**
20
- * Defers to core rather than parsing `HH:mm` locally. The other date-ish components each
21
- * grew their own formatter, and `datetime` ended up showing one thing in the field and
22
- * another in the record summary because the two drifted apart.
23
- */
24
- formatTime(value: unknown): string;
25
- static ɵfac: i0.ɵɵFactoryDeclaration<TimeFieldComponent, never>;
26
- static ɵcmp: i0.ɵɵComponentDeclaration<TimeFieldComponent, "ngx-time-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>;
27
- }
@@ -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,293 +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 cdr;
69
- private readonly fb;
70
- private readonly validatorRegistry;
71
- private readonly hookRegistry;
72
- private readonly rbacService;
73
- private readonly rulesEvaluation;
74
- private readonly entityRefSelection;
75
- private readonly commonModulesRegistry;
76
- private readonly migrations;
77
- protected readonly Object: ObjectConstructor;
78
- readonly activeTab: import("@angular/core").WritableSignal<string>;
79
- readonly activeSubTab: import("@angular/core").WritableSignal<string>;
80
- readonly isSaving: import("@angular/core").WritableSignal<boolean>;
81
- readonly formValues: import("@angular/core").WritableSignal<Record<string, any>>;
82
- /** Baseline captured at first build, used when `originalData` is not supplied. */
83
- readonly sessionBaseline: import("@angular/core").WritableSignal<Record<string, any>>;
84
- /** Critical fields the user has explicitly unlocked for editing. */
85
- readonly unlockedFields: import("@angular/core").WritableSignal<ReadonlySet<string>>;
86
- /** The active tab's field panel, focused after a tab switch. */
87
- private formPanel?;
88
- /** The hosted field components, so an external control-state change can reach them. */
89
- private fieldHosts?;
90
- form: FormGroup;
91
- private valueSub?;
92
- private statusSub?;
93
- private selectionSub?;
94
- /** Previous values, for detecting `patchOnTrue` false→true transitions. */
95
- private previousValues;
96
- handleKeyboardEvent(event: KeyboardEvent): void;
97
- readonly ruleResult: import("@angular/core").Signal<RuleEvaluationResult>;
98
- /**
99
- * Critical fields whose value differs from the session baseline.
100
- * These drive the deferred `VALUE_CHANGED` banner — a critical edit is announced once,
101
- * against the value the record had when the session started, not per keystroke.
102
- */
103
- readonly changedCriticalFields: import("@angular/core").Signal<NestedFieldConfig[]>;
104
- get tabs(): NestedTabConfig[];
105
- get visibleTabs(): NestedTabConfig[];
106
- get activeTabConfig(): NestedTabConfig | null;
107
- get visibleSubTabs(): NestedTabConfig[];
108
- get activeSubTabConfig(): NestedTabConfig | null;
109
- get activeTabModuleComponent(): any | null;
110
- get fieldsForActiveTab(): NestedFieldConfig[];
111
- /**
112
- * Entity-level permissions for the current roles.
113
- *
114
- * Cached rather than recomputed: this is read from the template several times per change
115
- * detection pass, and a fresh object each time is both wasted allocation and a new
116
- * identity for anything comparing by reference. Invalidated in `ngOnChanges` when
117
- * `config` or `userRoles` change, which are its only inputs.
118
- */
119
- get permissions(): ReturnType<RbacService['getPermissions']>;
120
- private permissionsCache;
121
- /**
122
- * Whether the current roles may see this record at all.
123
- *
124
- * `permissions.view` used to be computed and thrown away — a user whose roles failed it
125
- * still received the complete form with every value in the DOM. It is honoured now.
126
- *
127
- * As with masking, this is presentational: it stops the browser rendering data, it does
128
- * not stop the data reaching the browser. Authorize on the server.
129
- */
130
- get canView(): boolean;
131
- /**
132
- * Whether the current roles may delete this record.
133
- *
134
- * The library ships no delete affordance, so this is surfaced for the consumer to gate
135
- * their own — exposed rather than dropped, because a permission the schema declares
136
- * should be answerable.
137
- */
138
- get canDelete(): boolean;
139
- get canSubmit(): boolean;
140
- /**
141
- * Errors raised by `validation` rules, keyed by the target they point at.
142
- *
143
- * A rule can declare the record invalid independently of Angular's validators, so form
144
- * validity alone is not the whole picture. `DynamicRecordFormComponent.saveSection()`
145
- * has always honoured these; `submit()` did not, which meant the same rule blocked one
146
- * save path and merely painted a banner on the other — and anything wiring
147
- * `(formSubmit)` to persistence wrote records the rules engine had already rejected.
148
- */
149
- get ruleValidationErrors(): Record<string, string>;
150
- /** True when a `validation` rule is currently blocking submission. */
151
- get hasRuleErrors(): boolean;
152
- /**
153
- * True while an async validator is still running.
154
- *
155
- * Angular reports a control with an outstanding async check as `pending`, and `invalid` is
156
- * false until it settles — so without this a form could be submitted in the gap before a
157
- * uniqueness check came back.
158
- */
159
- get isValidating(): boolean;
160
- /** Single source of truth for both the submit guard and the button's disabled state. */
161
- get submitBlocked(): boolean;
162
- ngOnInit(): void;
163
- ngOnChanges(changes: SimpleChanges): void;
164
- ngOnDestroy(): void;
165
- setActiveTab(tabId: string): void;
166
- setActiveSubTab(subTabId: string): void;
167
- getFieldSpan(field: NestedFieldConfig): string;
168
- /** A critical field stays read-only until the user deliberately unlocks it. */
169
- isFieldLocked(field: NestedFieldConfig): boolean;
170
- /**
171
- * Whether this field renders read-only, from any of the four reasons: the whole form,
172
- * the field's own flag, a caller-supplied `readOnlyFields` entry, or a criticalField lock.
173
- */
174
- isFieldReadonly(field: NestedFieldConfig): boolean;
175
- toggleFieldLock(field: NestedFieldConfig): void;
176
- resolveFieldLabel(field: NestedFieldConfig): string;
177
- private buildForm;
178
- private buildFieldControl;
179
- /** One row of an `array` field: a FormGroup when the field declares columns, else a bare control. */
180
- /**
181
- * The `FormArray` behind an `array` field, for callers that manage rows themselves —
182
- * the record editor's inline row drawer.
183
- */
184
- getArrayControl(fieldId: string, tabId?: string): FormArray | null;
185
- /**
186
- * Build one detached row for an `array` field, with the field's own validators applied.
187
- * Exposed so a row can be edited outside the form and pushed on save.
188
- */
189
- createArrayRow(field: NestedFieldConfig, value?: unknown): AbstractControl;
190
- private buildArrayRow;
191
- /**
192
- * Mark every control touched, then re-check the field components.
193
- *
194
- * They are OnPush and `touched` is flipped from outside their own template, so the second
195
- * half is what makes the error messages actually appear.
196
- */
197
- private markAllTouched;
198
- /**
199
- * Bring a record up to the config's `version` before it reaches the form.
200
- *
201
- * Done here because this is the one place a record enters the renderer, so a consumer
202
- * cannot forget it. Migration is a no-op unless the consumer registered steps *and* the
203
- * record is behind — an unstamped record is deliberately left alone (see `migrateRecord`).
204
- *
205
- * A missing or broken step throws from core. That is not swallowed: rendering a
206
- * half-understood record would write it back in a shape matching neither schema.
207
- */
208
- private upgradeRecord;
209
- private patchForm;
210
- /**
211
- * A record is nested by tab id (`{ tabId: { fieldId: value } }`) unless the tab sets
212
- * `flatData: true`. Handing a flat record to a nested tab finds nothing, so the fields
213
- * stay empty — with no error and no clue. That silent miss is the most expensive way to
214
- * lose an hour with this library, so name it in dev builds.
215
- *
216
- * Only top-level keys that match a known field id are reported: anything else is assumed
217
- * to be the consumer's own record metadata (ids, timestamps, `_configVersion`) and is not
218
- * our business.
219
- */
220
- private warnUnconsumedInitialData;
221
- /** Warn once per key per form instance — patchForm re-runs on every initialData change. */
222
- private readonly warnedUnconsumedKeys;
223
- /** Assemble full nested record from per-tab FormGroups, respecting flatData, refererField & arrays. */
224
- extractRecord(): Record<string, any>;
225
- private flattenFormValues;
226
- private getTabGroup;
227
- /**
228
- * `autoPatch` — copy mapped fields out of the record the user just selected in an
229
- * entity-ref field into the configured target tab's controls.
230
- */
231
- private runAutoPatch;
232
- /**
233
- * `patchOnTrue` — when a boolean/checkbox field transitions to `true`,
234
- * copy `from` → `to` within the current record.
235
- */
236
- private runPatchOnTrue;
237
- /** Prefer a control declared on the configured target tab; fall back to a top-level control. */
238
- private resolveTargetControl;
239
- /**
240
- * The fields the render filter operates on — a tab's own fields, at every tab depth.
241
- * Deliberately not `allFields()`: `group` and `array` children are shown or hidden with
242
- * their parent, and toggling a child's state independently would fight the parent's.
243
- */
244
- private tabFields;
245
- /**
246
- * Keep a hidden field's control out of form validity.
247
- *
248
- * A field hidden by a rule or a `showWhen` condition is filtered out of the render, but
249
- * its validators stay attached — so a hidden *required* field holds `form.invalid` true
250
- * forever, permanently disabling Save with nothing on screen to explain why.
251
- *
252
- * Disabling is the fix rather than stripping validators: Angular excludes disabled
253
- * controls from validity, and the field's own validators survive intact for when it
254
- * comes back. Values are untouched — `flattenFormValues` and `extractRecord` both read
255
- * `control.value`, which a disabled control still carries, so rule evaluation and the
256
- * emitted record see exactly what they saw before. That also keeps this from feeding
257
- * back on itself: hiding a field cannot change the values the rules are evaluated over.
258
- *
259
- * The visibility predicate is the same one `fieldsForActiveTab` filters with, so what is
260
- * rendered and what counts toward validity cannot drift apart.
261
- */
262
- private syncHiddenFieldState;
263
- /** Every field in the config, including `group`/`array` children. */
264
- private allFields;
265
- /** An empty `originalData` counts as "not supplied" — fall back to the captured baseline. */
266
- private baseline;
267
- private sameValue;
268
- getControl(fieldId: string, currentTabId?: string): AbstractControl | null;
269
- /**
270
- * Move focus into the newly shown panel.
271
- *
272
- * Activating a tab replaces everything below it while focus stays on the tab button, so a
273
- * screen-reader or keyboard user is given no indication that the content changed and must
274
- * tab back through the strip to reach it. Deferred a frame because the panel's contents
275
- * are rendered by the change detection this call is part of.
276
- */
277
- private focusActivePanel;
278
- /** Accessible name for the field panel — it is the tabpanel for the active tab. */
279
- get activeTabLabel(): string;
280
- /**
281
- * Why a referenced field is flagged.
282
- *
283
- * `hasDrift` was written by the builder and read by nothing at runtime, so a field whose
284
- * source definition had changed since it was linked looked entirely normal to the person
285
- * filling the form in.
286
- */
287
- driftHint(field: NestedFieldConfig): string;
288
- resolveTabLabel(tab: NestedTabConfig): string;
289
- submit(): Promise<void>;
290
- reset(): void;
291
- static ɵfac: i0.ɵɵFactoryDeclaration<DynamicFormComponent, never>;
292
- 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>;
293
- }