ngx-dynamic-entity 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/esm2022/lib/field-types/array-field.component.mjs +13 -5
- package/esm2022/lib/field-types/boolean-field.component.mjs +4 -3
- package/esm2022/lib/field-types/checkbox-field.component.mjs +4 -3
- package/esm2022/lib/field-types/currency-field.component.mjs +4 -3
- package/esm2022/lib/field-types/date-field.component.mjs +4 -3
- package/esm2022/lib/field-types/date-time-field.component.mjs +138 -0
- package/esm2022/lib/field-types/dropdown-field.component.mjs +9 -7
- package/esm2022/lib/field-types/email-field.component.mjs +4 -3
- package/esm2022/lib/field-types/entity-ref-field.component.mjs +21 -7
- package/esm2022/lib/field-types/field-component.contract.mjs +2 -0
- package/esm2022/lib/field-types/file-field.component.mjs +4 -3
- package/esm2022/lib/field-types/group-field.component.mjs +5 -5
- package/esm2022/lib/field-types/image-field.component.mjs +4 -3
- package/esm2022/lib/field-types/month-year-field.component.mjs +4 -3
- package/esm2022/lib/field-types/multi-select-field.component.mjs +4 -3
- package/esm2022/lib/field-types/number-field.component.mjs +7 -11
- package/esm2022/lib/field-types/password-field.component.mjs +4 -3
- package/esm2022/lib/field-types/radio-field.component.mjs +4 -3
- package/esm2022/lib/field-types/text-field.component.mjs +25 -18
- package/esm2022/lib/field-types/textarea-field.component.mjs +4 -3
- package/esm2022/lib/field-types/time-field.component.mjs +96 -0
- package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +55 -7
- package/esm2022/lib/form/dynamic-form.component.mjs +140 -15
- package/esm2022/lib/form/dynamic-record-form.component.mjs +54 -17
- package/esm2022/lib/providers/provide-field-types.mjs +5 -2
- package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +7 -2
- package/esm2022/lib/services/cascade-data.service.mjs +2 -2
- package/esm2022/lib/services/entity-ref-registry.service.mjs +2 -2
- package/esm2022/lib/services/lookup-registry.service.mjs +2 -2
- package/esm2022/lib/services/validation-messages.service.mjs +56 -0
- package/esm2022/lib/services/validator-registry.service.mjs +24 -2
- package/esm2022/lib/tokens/injection-tokens.mjs +19 -6
- package/esm2022/public-api.mjs +9 -2
- package/fesm2022/ngx-dynamic-entity.mjs +663 -98
- package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
- package/lib/field-types/array-field.component.d.ts +7 -1
- package/lib/field-types/date-time-field.component.d.ts +30 -0
- package/lib/field-types/dropdown-field.component.d.ts +1 -0
- package/lib/field-types/entity-ref-field.component.d.ts +11 -1
- package/lib/field-types/field-component.contract.d.ts +49 -0
- package/lib/field-types/group-field.component.d.ts +1 -1
- package/lib/field-types/number-field.component.d.ts +1 -0
- package/lib/field-types/text-field.component.d.ts +10 -2
- package/lib/field-types/time-field.component.d.ts +27 -0
- package/lib/form/dynamic-field/dynamic-field.component.d.ts +39 -4
- package/lib/form/dynamic-form.component.d.ts +64 -2
- package/lib/form/dynamic-record-form.component.d.ts +19 -0
- package/lib/providers/provide-ngx-dynamic-entity.d.ts +17 -1
- package/lib/services/cascade-data.service.d.ts +1 -1
- package/lib/services/entity-ref-registry.service.d.ts +1 -1
- package/lib/services/lookup-registry.service.d.ts +1 -1
- package/lib/services/validation-messages.service.d.ts +43 -0
- package/lib/services/validator-registry.service.d.ts +13 -1
- package/lib/tokens/injection-tokens.d.ts +18 -5
- package/package.json +2 -2
- package/public-api.d.ts +4 -0
- package/styles.css +579 -0
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { AbstractControl, FormArray } from '@angular/forms';
|
|
2
2
|
import type { NestedFieldConfig, EntityFormConfig } from '@dynamic-entity/core';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
|
-
/**
|
|
4
|
+
/** Renders repeating rows for a FormArray, with Add Item and Remove. */
|
|
5
5
|
export declare class ArrayFieldComponent {
|
|
6
|
+
/**
|
|
7
|
+
* addItem/removeItem are public and may be called from outside this component's template,
|
|
8
|
+
* which under OnPush would not re-render. The host also watches the control, but this
|
|
9
|
+
* keeps the component correct on its own.
|
|
10
|
+
*/
|
|
11
|
+
private readonly cdr;
|
|
6
12
|
field: NestedFieldConfig;
|
|
7
13
|
control: AbstractControl;
|
|
8
14
|
config: EntityFormConfig;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AbstractControl } from '@angular/forms';
|
|
2
|
+
import type { NestedFieldConfig } from '@dynamic-entity/core';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* Date & Time field: a single `datetime-local` input.
|
|
6
|
+
*
|
|
7
|
+
* The type is stored as ISO 8601 UTC, per the same convention `date` follows, while the
|
|
8
|
+
* input works in the viewer's local zone — `datetime-local` has no offset, so the two
|
|
9
|
+
* conversions here are what keep a value stable across a round trip.
|
|
10
|
+
*/
|
|
11
|
+
export declare class DateTimeFieldComponent {
|
|
12
|
+
field: NestedFieldConfig;
|
|
13
|
+
control: AbstractControl;
|
|
14
|
+
language: string;
|
|
15
|
+
readonly: boolean;
|
|
16
|
+
masked: boolean;
|
|
17
|
+
get label(): string;
|
|
18
|
+
/** Stored ISO 8601 → the `YYYY-MM-DDTHH:mm` local form the input requires. */
|
|
19
|
+
get inputValue(): string;
|
|
20
|
+
/** The input's local `YYYY-MM-DDTHH:mm` → stored ISO 8601 UTC. */
|
|
21
|
+
onInput(value: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Displays date *and* time. `DateFieldComponent` used `toLocaleDateString()` here, so a
|
|
24
|
+
* datetime read back through the field showed no time while the same value in the record
|
|
25
|
+
* summary — which goes through core's `formatDisplayValue` — showed it.
|
|
26
|
+
*/
|
|
27
|
+
formatDateTime(value: unknown): string;
|
|
28
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<DateTimeFieldComponent, never>;
|
|
29
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<DateTimeFieldComponent, "ngx-date-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>;
|
|
30
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { NestedFieldConfig } from '@dynamic-entity/core';
|
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
export declare class DropdownFieldComponent {
|
|
5
5
|
private readonly lookups;
|
|
6
|
+
private readonly messages;
|
|
6
7
|
private _field;
|
|
7
8
|
private _language;
|
|
8
9
|
/** Setter-based so options resolve however the input is set — see `refreshChoiceOptions`. */
|
|
@@ -29,7 +29,17 @@ export declare class EntityRefFieldComponent implements OnInit {
|
|
|
29
29
|
ngOnInit(): void;
|
|
30
30
|
/** Reload options against the parent's current value. */
|
|
31
31
|
reload(): Promise<void>;
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Publish the selected option so autoPatch can copy from its record.
|
|
34
|
+
*
|
|
35
|
+
* Driven from `valueChanges` rather than the select's `change` event: that event is the
|
|
36
|
+
* same one the `formControl` directive listens to, so reading the control inside a
|
|
37
|
+
* template `(change)` handler can see the previous value. `valueChanges` fires after the
|
|
38
|
+
* model has updated — including Playwright `selectOption`, which updates the control
|
|
39
|
+
* through the value accessor.
|
|
40
|
+
*/
|
|
41
|
+
onSelectionChange(value?: string): void;
|
|
42
|
+
private publishSelection;
|
|
33
43
|
getLabel(value: any): string;
|
|
34
44
|
private parentValue;
|
|
35
45
|
/** Reload (and drop a stale selection) whenever the parent control changes. */
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AbstractControl } from '@angular/forms';
|
|
2
|
+
import type { NestedFieldConfig } from '@dynamic-entity/core';
|
|
3
|
+
/**
|
|
4
|
+
* What a field component must accept to be registered with `provideFieldTypes`.
|
|
5
|
+
*
|
|
6
|
+
* The renderer instantiates a field component dynamically and assigns exactly these five
|
|
7
|
+
* inputs — no more, no less — so any component satisfying this interface can stand in for a
|
|
8
|
+
* built-in type or add a new one.
|
|
9
|
+
*
|
|
10
|
+
* Implement it explicitly and TypeScript will tell you when you have missed one:
|
|
11
|
+
*
|
|
12
|
+
* ```typescript
|
|
13
|
+
* @Component({
|
|
14
|
+
* selector: 'app-signature-field',
|
|
15
|
+
* standalone: true,
|
|
16
|
+
* template: `<canvas [attr.aria-label]="field.id"></canvas>`,
|
|
17
|
+
* })
|
|
18
|
+
* export class SignatureFieldComponent implements DynamicFieldComponentContract {
|
|
19
|
+
* @Input() field!: NestedFieldConfig;
|
|
20
|
+
* @Input() control!: AbstractControl;
|
|
21
|
+
* @Input() language = 'en';
|
|
22
|
+
* @Input() readonly = false;
|
|
23
|
+
* @Input() masked = false;
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* provideFieldTypes({ signature: SignatureFieldComponent });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Two implementation notes that are easy to get wrong:
|
|
30
|
+
*
|
|
31
|
+
* - Declare the inputs with definite assignment (`field!: …`) or an initialiser. The
|
|
32
|
+
* renderer sets them through `ComponentRef.setInput()` after construction, so they are
|
|
33
|
+
* genuinely undefined until the first assignment.
|
|
34
|
+
* - `masked` means the value must not be displayed. Render a placeholder rather than the
|
|
35
|
+
* control's value. This is presentational: the real value stays in the control and is
|
|
36
|
+
* included in the submitted record, so it is not an access-control boundary.
|
|
37
|
+
*/
|
|
38
|
+
export interface DynamicFieldComponentContract {
|
|
39
|
+
/** The field's schema entry: id, type, label, validators, options, and so on. */
|
|
40
|
+
field: NestedFieldConfig;
|
|
41
|
+
/** The reactive control backing this field. A `FormGroup` for `group`, `FormArray` for `array`. */
|
|
42
|
+
control: AbstractControl;
|
|
43
|
+
/** Active language, for resolving `LocalizedText` labels, placeholders and options. */
|
|
44
|
+
language: string;
|
|
45
|
+
/** Render the value as text rather than an editable control. */
|
|
46
|
+
readonly: boolean;
|
|
47
|
+
/** Hide the value from display. See the note above — presentational only. */
|
|
48
|
+
masked: boolean;
|
|
49
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AbstractControl } from '@angular/forms';
|
|
2
2
|
import type { NestedFieldConfig, EntityFormConfig } from '@dynamic-entity/core';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
|
-
/**
|
|
4
|
+
/** Renders a nested fieldset container for a group field's children. */
|
|
5
5
|
export declare class GroupFieldComponent {
|
|
6
6
|
field: NestedFieldConfig;
|
|
7
7
|
control: AbstractControl;
|
|
@@ -2,6 +2,7 @@ import { AbstractControl } from '@angular/forms';
|
|
|
2
2
|
import type { NestedFieldConfig } from '@dynamic-entity/core';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
export declare class NumberFieldComponent {
|
|
5
|
+
private readonly messages;
|
|
5
6
|
field: NestedFieldConfig;
|
|
6
7
|
control: AbstractControl;
|
|
7
8
|
language: string;
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import { OnDestroy } from '@angular/core';
|
|
1
2
|
import { AbstractControl } from '@angular/forms';
|
|
2
3
|
import type { NestedFieldConfig } from '@dynamic-entity/core';
|
|
3
4
|
import * as i0 from "@angular/core";
|
|
4
|
-
export declare class TextFieldComponent {
|
|
5
|
+
export declare class TextFieldComponent implements OnDestroy {
|
|
6
|
+
private readonly messages;
|
|
7
|
+
private displaySub?;
|
|
5
8
|
field: NestedFieldConfig;
|
|
6
|
-
control: AbstractControl;
|
|
9
|
+
set control(value: AbstractControl);
|
|
10
|
+
get control(): AbstractControl;
|
|
11
|
+
private _control;
|
|
7
12
|
language: string;
|
|
8
13
|
readonly: boolean;
|
|
9
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;
|
|
10
18
|
get label(): string;
|
|
11
19
|
get placeholder(): string;
|
|
12
20
|
get errorMessage(): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
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,16 +1,17 @@
|
|
|
1
|
-
import { OnChanges, SimpleChanges } from '@angular/core';
|
|
1
|
+
import { OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
|
|
2
2
|
import { AbstractControl } from '@angular/forms';
|
|
3
3
|
import type { NestedFieldConfig, EntityFormConfig } from '@dynamic-entity/core';
|
|
4
4
|
import * as i0 from "@angular/core";
|
|
5
5
|
/**
|
|
6
6
|
* DynamicFieldComponent — mounts the correct field component for a given NestedFieldConfig.
|
|
7
|
-
* Uses ViewContainerRef.createComponent() and passes inputs via setInput()
|
|
7
|
+
* Uses ViewContainerRef.createComponent() and passes inputs via setInput(); see
|
|
8
|
+
* DynamicFieldComponentContract for the five inputs every field component receives.
|
|
8
9
|
* Never add field-type-specific logic here — this component must remain generic.
|
|
9
10
|
*
|
|
10
11
|
* The 5 inputs (field, control, language, readonly, masked) are passed to every mounted
|
|
11
12
|
* component uniformly — no special-casing per type.
|
|
12
13
|
*/
|
|
13
|
-
export declare class DynamicFieldComponent implements OnChanges {
|
|
14
|
+
export declare class DynamicFieldComponent implements OnChanges, OnDestroy {
|
|
14
15
|
field: NestedFieldConfig;
|
|
15
16
|
control: AbstractControl;
|
|
16
17
|
config: EntityFormConfig;
|
|
@@ -21,6 +22,12 @@ export declare class DynamicFieldComponent implements OnChanges {
|
|
|
21
22
|
private readonly fieldHost;
|
|
22
23
|
private readonly fieldRegistry;
|
|
23
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;
|
|
24
31
|
private componentRef;
|
|
25
32
|
ngOnChanges(_changes: SimpleChanges): void;
|
|
26
33
|
private mountField;
|
|
@@ -32,7 +39,35 @@ export declare class DynamicFieldComponent implements OnChanges {
|
|
|
32
39
|
private warnUnresolved;
|
|
33
40
|
private static readonly warnedTypes;
|
|
34
41
|
/**
|
|
35
|
-
*
|
|
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.
|
|
36
71
|
* Must use setInput(), not property assignment: the field components declare inputs with
|
|
37
72
|
* definite assignment and no initializer, and the lib compiles with
|
|
38
73
|
* useDefineForClassFields:false, so `'field' in instance` is false until first set.
|
|
@@ -10,7 +10,21 @@ import * as i0 from "@angular/core";
|
|
|
10
10
|
* keyboard shortcuts (Ctrl+S, Esc), and RBAC-gated submission.
|
|
11
11
|
*/
|
|
12
12
|
export declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
13
|
-
|
|
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?;
|
|
14
28
|
rules?: FormRule[];
|
|
15
29
|
initialData?: Record<string, any>;
|
|
16
30
|
/** Session-original values for `VALUE_CHANGED` rules. Captured from the first build when omitted. */
|
|
@@ -43,6 +57,15 @@ export declare class DynamicFormComponent implements OnInit, OnChanges, OnDestro
|
|
|
43
57
|
formReset: EventEmitter<void>;
|
|
44
58
|
/** The visible tab changed. Lets a host track it without reaching into this component. */
|
|
45
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;
|
|
46
69
|
private readonly fb;
|
|
47
70
|
private readonly validatorRegistry;
|
|
48
71
|
private readonly hookRegistry;
|
|
@@ -60,8 +83,13 @@ export declare class DynamicFormComponent implements OnInit, OnChanges, OnDestro
|
|
|
60
83
|
readonly sessionBaseline: import("@angular/core").WritableSignal<Record<string, any>>;
|
|
61
84
|
/** Critical fields the user has explicitly unlocked for editing. */
|
|
62
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?;
|
|
63
90
|
form: FormGroup;
|
|
64
91
|
private valueSub?;
|
|
92
|
+
private statusSub?;
|
|
65
93
|
private selectionSub?;
|
|
66
94
|
/** Previous values, for detecting `patchOnTrue` false→true transitions. */
|
|
67
95
|
private previousValues;
|
|
@@ -121,6 +149,14 @@ export declare class DynamicFormComponent implements OnInit, OnChanges, OnDestro
|
|
|
121
149
|
get ruleValidationErrors(): Record<string, string>;
|
|
122
150
|
/** True when a `validation` rule is currently blocking submission. */
|
|
123
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;
|
|
124
160
|
/** Single source of truth for both the submit guard and the button's disabled state. */
|
|
125
161
|
get submitBlocked(): boolean;
|
|
126
162
|
ngOnInit(): void;
|
|
@@ -152,6 +188,13 @@ export declare class DynamicFormComponent implements OnInit, OnChanges, OnDestro
|
|
|
152
188
|
*/
|
|
153
189
|
createArrayRow(field: NestedFieldConfig, value?: unknown): AbstractControl;
|
|
154
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;
|
|
155
198
|
/**
|
|
156
199
|
* Bring a record up to the config's `version` before it reaches the form.
|
|
157
200
|
*
|
|
@@ -223,9 +266,28 @@ export declare class DynamicFormComponent implements OnInit, OnChanges, OnDestro
|
|
|
223
266
|
private baseline;
|
|
224
267
|
private sameValue;
|
|
225
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;
|
|
226
288
|
resolveTabLabel(tab: NestedTabConfig): string;
|
|
227
289
|
submit(): Promise<void>;
|
|
228
290
|
reset(): void;
|
|
229
291
|
static ɵfac: i0.ɵɵFactoryDeclaration<DynamicFormComponent, never>;
|
|
230
|
-
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"; }, never, never, true, 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>;
|
|
231
293
|
}
|
|
@@ -39,6 +39,8 @@ export declare class DynamicRecordFormComponent implements OnChanges {
|
|
|
39
39
|
}>;
|
|
40
40
|
dynamicFormComp?: DynamicFormComponent;
|
|
41
41
|
private readonly rulesEvaluation;
|
|
42
|
+
private readonly host;
|
|
43
|
+
private readonly injector;
|
|
42
44
|
readonly currentData: import("@angular/core").WritableSignal<Record<string, any>>;
|
|
43
45
|
readonly originalBaseline: import("@angular/core").WritableSignal<Record<string, any>>;
|
|
44
46
|
/**
|
|
@@ -174,6 +176,23 @@ export declare class DynamicRecordFormComponent implements OnChanges {
|
|
|
174
176
|
formatFieldLabel(field: NestedFieldConfig): string;
|
|
175
177
|
/** Summary values render through the shared core formatter, not a local stringifier. */
|
|
176
178
|
formatFieldValue(field: NestedFieldConfig): string;
|
|
179
|
+
/**
|
|
180
|
+
* Finds which tab — and, where the field lives one level down, which sub-tab — owns a
|
|
181
|
+
* field. The walk used to check top-level `fields` only, so every field in a sub-tab was
|
|
182
|
+
* simply not found and the jump did nothing at all.
|
|
183
|
+
*/
|
|
184
|
+
private locateField;
|
|
185
|
+
/**
|
|
186
|
+
* Switches to the tab holding `fieldId`, then scrolls it into view and moves focus to it.
|
|
187
|
+
*
|
|
188
|
+
* The wait for the new tab to render used to be a 50 ms `setTimeout`, which was wrong three
|
|
189
|
+
* ways: `document` is undefined on a server render, 50 ms is a guess that a large tab can
|
|
190
|
+
* outrun, and nothing cancelled the timer if the component went away first.
|
|
191
|
+
* `afterNextRender` fixes all three — it never runs on the server, it runs when the panel
|
|
192
|
+
* has actually rendered rather than when a guess expires, and it is tied to the injector so
|
|
193
|
+
* destroying the component cancels it. The query is scoped to this component's own element,
|
|
194
|
+
* so the library no longer reaches for the global `document` at all.
|
|
195
|
+
*/
|
|
177
196
|
jumpToField(fieldId: string): void;
|
|
178
197
|
static ɵfac: i0.ɵɵFactoryDeclaration<DynamicRecordFormComponent, never>;
|
|
179
198
|
static ɵcmp: i0.ɵɵComponentDeclaration<DynamicRecordFormComponent, "ngx-dynamic-record-form", never, { "config": { "alias": "config"; "required": false; }; "rules": { "alias": "rules"; "required": false; }; "initialData": { "alias": "initialData"; "required": false; }; "userRoles": { "alias": "userRoles"; "required": false; }; "language": { "alias": "language"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "error": { "alias": "error"; "required": false; }; "isReadOnly": { "alias": "isReadOnly"; "required": false; }; "readOnlyFields": { "alias": "readOnlyFields"; "required": false; }; "viewMode": { "alias": "viewMode"; "required": false; }; }, { "formSubmit": "formSubmit"; "formChange": "formChange"; "formReset": "formReset"; "sectionSave": "sectionSave"; }, never, never, true, never>;
|
|
@@ -7,7 +7,7 @@ export interface NgxDynamicEntityConfig {
|
|
|
7
7
|
/** Custom field type components keyed by type string */
|
|
8
8
|
fieldTypes?: Record<string, Type<any>>;
|
|
9
9
|
/**
|
|
10
|
-
* Entity-ref option loaders keyed by entity key
|
|
10
|
+
* Entity-ref option loaders keyed by entity key.
|
|
11
11
|
* Each receives `{ parentValue, filters, lang }` and may return an array, Promise, or Observable.
|
|
12
12
|
*/
|
|
13
13
|
entityRefs?: Record<string, EntityReferenceLoader>;
|
|
@@ -17,6 +17,22 @@ export interface NgxDynamicEntityConfig {
|
|
|
17
17
|
* a loader for anything fetched, so an unused list is never loaded.
|
|
18
18
|
*/
|
|
19
19
|
lookups?: Record<string, LookupListSource>;
|
|
20
|
+
/**
|
|
21
|
+
* Override the messages shown under invalid fields, keyed by Angular error key. A value is
|
|
22
|
+
* a string, or a function receiving `(language, error)` when it needs the error's detail.
|
|
23
|
+
* Unlisted keys keep their English default.
|
|
24
|
+
*/
|
|
25
|
+
validationMessages?: Record<string, string | ((language: string, error: any) => string)>;
|
|
26
|
+
/**
|
|
27
|
+
* Async validator functions keyed by name, for checks that need a server — uniqueness,
|
|
28
|
+
* availability, a remote rule. Name them from a schema with
|
|
29
|
+
* `validators: { customAsync: ['uniqueEmail'] }`.
|
|
30
|
+
*
|
|
31
|
+
* Each is an Angular `AsyncValidatorFn`: it receives the control and returns a Promise or
|
|
32
|
+
* Observable of `ValidationErrors | null`. The control is `pending` while it runs, and the
|
|
33
|
+
* form cannot be submitted until every pending check settles.
|
|
34
|
+
*/
|
|
35
|
+
asyncValidators?: Record<string, any>;
|
|
20
36
|
/** Custom validator functions keyed by validator name */
|
|
21
37
|
validators?: Record<string, any>;
|
|
22
38
|
/** Hook functions keyed by hook name */
|
|
@@ -3,7 +3,7 @@ import * as i0 from "@angular/core";
|
|
|
3
3
|
/**
|
|
4
4
|
* CascadeDataService — loads entity-reference options and applies parent→child cascades.
|
|
5
5
|
*
|
|
6
|
-
* Loaders come from the registry only
|
|
6
|
+
* Loaders come from the registry only. This service owns the three things a
|
|
7
7
|
* field component should not: resolving the loader, coercing whatever it returned
|
|
8
8
|
* (array | Promise | Observable) into a Promise, and filtering by the parent's value.
|
|
9
9
|
*/
|
|
@@ -2,7 +2,7 @@ import type { EntityReferenceLoader } from '@dynamic-entity/core';
|
|
|
2
2
|
import * as i0 from "@angular/core";
|
|
3
3
|
/**
|
|
4
4
|
* EntityRefRegistryService — resolves entity-ref option loader functions.
|
|
5
|
-
*
|
|
5
|
+
* Entity-ref options come ONLY from this registry.
|
|
6
6
|
* Never pass loaders via @Input() on field components.
|
|
7
7
|
*
|
|
8
8
|
* Consumers register loaders via provideNgxDynamicEntity({ entityRefs: { clients: () => [...] } }).
|
|
@@ -81,7 +81,7 @@ export interface ChoiceOptionHost {
|
|
|
81
81
|
* Point a choice component's `options` signal at the right source (parity plan §6.3).
|
|
82
82
|
*
|
|
83
83
|
* Called from the `field` / `language` input setters of `dropdown`, `radio` and `multiSelect` —
|
|
84
|
-
* the wiring is theirs, the decision is the service's, which is what keeps the
|
|
84
|
+
* the wiring is theirs, the decision is the service's, which is what keeps the field
|
|
85
85
|
* five-input contract intact: options are never pushed in as a sixth input.
|
|
86
86
|
*
|
|
87
87
|
* Setters rather than `ngOnChanges` on purpose. `ngOnChanges` only fires for inputs Angular
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ValidationErrors } from '@angular/forms';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Resolves an Angular `ValidationErrors` object to the message shown under a field.
|
|
5
|
+
*
|
|
6
|
+
* These strings used to be hardcoded English literals inside each field component, so a
|
|
7
|
+
* German form meant re-implementing every field type — the labels honoured `language` while
|
|
8
|
+
* the errors underneath them did not.
|
|
9
|
+
*
|
|
10
|
+
* A message is a plain string, or a function when it needs the error's own detail
|
|
11
|
+
* (`minlength.requiredLength`, `min.min`, and so on). The active `language` is passed
|
|
12
|
+
* through, so one registration can serve every locale:
|
|
13
|
+
*
|
|
14
|
+
* ```typescript
|
|
15
|
+
* provideNgxDynamicEntity({
|
|
16
|
+
* validationMessages: {
|
|
17
|
+
* required: lang => (lang === 'de' ? 'Pflichtfeld.' : 'This field is required.'),
|
|
18
|
+
* minlength: (lang, err) => `Mindestens ${err['requiredLength']} Zeichen.`,
|
|
19
|
+
* },
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Unregistered keys fall back to the defaults below, so overriding one message does not
|
|
24
|
+
* mean supplying them all.
|
|
25
|
+
*/
|
|
26
|
+
export type ValidationMessage = string | ((language: string, error: any) => string);
|
|
27
|
+
/** The built-in English messages. Every key here can be overridden individually. */
|
|
28
|
+
export declare const DEFAULT_VALIDATION_MESSAGES: Record<string, ValidationMessage>;
|
|
29
|
+
export declare class ValidationMessagesService {
|
|
30
|
+
private readonly overrides;
|
|
31
|
+
/**
|
|
32
|
+
* The message for the first error present, in the order the caller lists.
|
|
33
|
+
*
|
|
34
|
+
* Order is the caller's because it is field-specific: a dropdown wants
|
|
35
|
+
* `requiredSelection` where a text input wants `required`, and the first match wins so the
|
|
36
|
+
* most specific message is the one shown.
|
|
37
|
+
*/
|
|
38
|
+
resolve(errors: ValidationErrors | null | undefined, language: string, order: readonly (string | readonly [errorKey: string, messageKey: string])[], fallbackKey?: string): string;
|
|
39
|
+
/** One message by key, honouring an override before the default. */
|
|
40
|
+
messageFor(key: string, language: string, error: unknown): string;
|
|
41
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ValidationMessagesService, never>;
|
|
42
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<ValidationMessagesService>;
|
|
43
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ValidatorFn } from '@angular/forms';
|
|
1
|
+
import { AsyncValidatorFn, ValidatorFn } from '@angular/forms';
|
|
2
2
|
import type { FieldValidators } from '@dynamic-entity/core';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
/**
|
|
@@ -6,6 +6,7 @@ import * as i0 from "@angular/core";
|
|
|
6
6
|
*/
|
|
7
7
|
export declare class ValidatorRegistryService {
|
|
8
8
|
private readonly consumerRegistry;
|
|
9
|
+
private readonly asyncRegistry;
|
|
9
10
|
/**
|
|
10
11
|
* Resolve a validator key string to a ValidatorFn.
|
|
11
12
|
* Supports parameterized built-ins like 'min:0', 'maxLength:255'.
|
|
@@ -15,6 +16,17 @@ export declare class ValidatorRegistryService {
|
|
|
15
16
|
resolveAll(validatorKeys?: string[]): ValidatorFn[];
|
|
16
17
|
/** Resolve a FieldValidators object or string[] into ValidatorFn[] */
|
|
17
18
|
resolveFromConfig(config?: FieldValidators | string[]): ValidatorFn[];
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the async validators a field names, in `validators.customAsync`.
|
|
21
|
+
*
|
|
22
|
+
* Async validators are deliberately a separate registry and a separate config key: Angular
|
|
23
|
+
* attaches them through `setAsyncValidators`, runs them only once the synchronous ones
|
|
24
|
+
* pass, and holds the control in `pending` until they settle. Mixing them into the
|
|
25
|
+
* synchronous list would silently never run them.
|
|
26
|
+
*
|
|
27
|
+
* An unknown name is skipped rather than throwing, matching the synchronous behaviour.
|
|
28
|
+
*/
|
|
29
|
+
resolveAsyncFromConfig(config?: FieldValidators | string[]): AsyncValidatorFn[];
|
|
18
30
|
static ɵfac: i0.ɵɵFactoryDeclaration<ValidatorRegistryService, never>;
|
|
19
31
|
static ɵprov: i0.ɵɵInjectableDeclaration<ValidatorRegistryService>;
|
|
20
32
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InjectionToken } from '@angular/core';
|
|
2
2
|
import type { Type } from '@angular/core';
|
|
3
3
|
import { type EntityRefCacheStore } from '../services/entity-ref-cache';
|
|
4
|
-
/** Roles that see XXXXXXXXX for masked fields
|
|
4
|
+
/** Roles that see XXXXXXXXX for masked fields. Presentational only — enforce authz server-side. */
|
|
5
5
|
export declare const MASKED_ROLES: InjectionToken<string[]>;
|
|
6
6
|
/**
|
|
7
7
|
* Consumer **overrides**: fieldType string → Angular component class.
|
|
@@ -58,8 +58,25 @@ export declare const LOOKUP_REGISTRY: InjectionToken<Map<string, LookupListSourc
|
|
|
58
58
|
* })
|
|
59
59
|
*/
|
|
60
60
|
export declare const RECORD_MIGRATIONS: InjectionToken<RecordMigration[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Overrides for the messages shown under an invalid field, keyed by Angular error key
|
|
63
|
+
* (`required`, `minlength`, `pattern`, …) plus a few field-specific keys.
|
|
64
|
+
*
|
|
65
|
+
* The built-in messages are English literals. Registering here is what makes a non-English
|
|
66
|
+
* form possible without re-implementing every field component; unlisted keys keep their
|
|
67
|
+
* default, so overriding one does not mean supplying them all.
|
|
68
|
+
*/
|
|
69
|
+
export declare const VALIDATION_MESSAGES: InjectionToken<Record<string, any>>;
|
|
61
70
|
/** Registry: validator key → ValidatorFn */
|
|
62
71
|
export declare const VALIDATOR_REGISTRY: InjectionToken<Map<string, any>>;
|
|
72
|
+
/**
|
|
73
|
+
* Registry: validator key → AsyncValidatorFn.
|
|
74
|
+
*
|
|
75
|
+
* Kept separate from VALIDATOR_REGISTRY because Angular applies the two differently: an async
|
|
76
|
+
* validator runs only once the synchronous ones pass, and puts the control into `pending`
|
|
77
|
+
* while it does. Name them from a schema with `validators: { customAsync: ['uniqueEmail'] }`.
|
|
78
|
+
*/
|
|
79
|
+
export declare const ASYNC_VALIDATOR_REGISTRY: InjectionToken<Map<string, any>>;
|
|
63
80
|
/**
|
|
64
81
|
* A registered hook: receives the payload, returns it (or a replacement), sync or async.
|
|
65
82
|
*
|
|
@@ -104,10 +121,6 @@ export declare const UPLOAD_HANDLER: InjectionToken<FileUploadHandler>;
|
|
|
104
121
|
* { provide: ENTITY_REF_CACHE_STORE, useClass: SessionStorageRefCache }
|
|
105
122
|
*/
|
|
106
123
|
export declare const ENTITY_REF_CACHE_STORE: InjectionToken<EntityRefCacheStore>;
|
|
107
|
-
/**
|
|
108
|
-
* Consumer-provided predicate function to check whether user roles can edit system default tabs or fields.
|
|
109
|
-
* Defaults to allowing edits if not provided.
|
|
110
|
-
*/
|
|
111
124
|
/**
|
|
112
125
|
* Consumer-provided predicate function to check whether user roles can edit system default tabs or fields.
|
|
113
126
|
* Defaults to allowing edits if not provided.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ngx-dynamic-entity",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Angular standalone UI library for rendering dynamic entity forms.",
|
|
5
5
|
"//typings": "Local workspace resolution only: ng-packagr overwrites this with the correct in-tarball path ('index.d.ts') when generating dist/package.json. Do NOT add a 'main' field here — ng-packagr does not generate one, so it would be copied verbatim and resolve to dist/dist/... in the published package.",
|
|
6
6
|
"typings": "index.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": "^1.
|
|
11
|
+
"@dynamic-entity/core": "^1.3.0",
|
|
12
12
|
"rxjs": "^7.0.0"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
package/public-api.d.ts
CHANGED
|
@@ -13,9 +13,11 @@ export * from './lib/services/lookup-registry.service';
|
|
|
13
13
|
export * from './lib/services/rbac.service';
|
|
14
14
|
export * from './lib/services/rules-evaluation.service';
|
|
15
15
|
export * from './lib/services/config-source.service';
|
|
16
|
+
export * from './lib/services/validation-messages.service';
|
|
16
17
|
export * from './lib/form/dynamic-form.component';
|
|
17
18
|
export * from './lib/form/dynamic-record-form.component';
|
|
18
19
|
export * from './lib/form/dynamic-field/dynamic-field.component';
|
|
20
|
+
export * from './lib/field-types/field-component.contract';
|
|
19
21
|
export * from './lib/field-types/text-field.component';
|
|
20
22
|
export * from './lib/field-types/textarea-field.component';
|
|
21
23
|
export * from './lib/field-types/number-field.component';
|
|
@@ -25,6 +27,8 @@ export * from './lib/field-types/password-field.component';
|
|
|
25
27
|
export * from './lib/field-types/checkbox-field.component';
|
|
26
28
|
export * from './lib/field-types/boolean-field.component';
|
|
27
29
|
export * from './lib/field-types/date-field.component';
|
|
30
|
+
export * from './lib/field-types/date-time-field.component';
|
|
31
|
+
export * from './lib/field-types/time-field.component';
|
|
28
32
|
export * from './lib/field-types/month-year-field.component';
|
|
29
33
|
export * from './lib/field-types/dropdown-field.component';
|
|
30
34
|
export * from './lib/field-types/radio-field.component';
|