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,199 +0,0 @@
1
- import { EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
2
- import { FormGroup } from '@angular/forms';
3
- import type { EntityFormConfig, FormRule, NestedFieldConfig } from '@dynamic-entity/core';
4
- import { DynamicFormComponent } from './dynamic-form.component';
5
- import * as i0 from "@angular/core";
6
- /**
7
- * DynamicRecordFormComponent — comprehensive tabbed record view & edit component.
8
- * Supports cross-tab rule evaluation, profile header, summary drawer (showOnMinimize),
9
- * interactive quick-jump links, and baseline modification tracking.
10
- */
11
- export declare class DynamicRecordFormComponent implements OnChanges {
12
- config: EntityFormConfig;
13
- rules?: FormRule[];
14
- initialData?: Record<string, any>;
15
- userRoles: string[];
16
- language: string;
17
- readonly: boolean;
18
- loading: boolean;
19
- error: string | null;
20
- /** Whole record read-only, regardless of RBAC. Equivalent to the reference's `isReadOnly`. */
21
- isReadOnly: boolean;
22
- /** Specific field ids forced read-only while the rest of the record stays editable. */
23
- readOnlyFields: string[];
24
- /**
25
- * Open the record read-only, with a per-tab "Edit section" flow — one tab is edited and
26
- * validated at a time. This is the framework's `EntityRecordComponent` model and the
27
- * default here.
28
- *
29
- * Set false for a directly editable record with no view/edit distinction.
30
- */
31
- viewMode: boolean;
32
- formSubmit: EventEmitter<Record<string, any>>;
33
- formChange: EventEmitter<Record<string, any>>;
34
- formReset: EventEmitter<void>;
35
- /** One tab section was saved. Carries the whole record, plus which tab was edited. */
36
- sectionSave: EventEmitter<{
37
- tabId: string;
38
- record: Record<string, any>;
39
- }>;
40
- dynamicFormComp?: DynamicFormComponent;
41
- private readonly rulesEvaluation;
42
- private readonly host;
43
- private readonly injector;
44
- readonly currentData: import("@angular/core").WritableSignal<Record<string, any>>;
45
- readonly originalBaseline: import("@angular/core").WritableSignal<Record<string, any>>;
46
- /**
47
- * Info banners the user has dismissed, by field id.
48
- *
49
- * The reference's contract: an info banner persists until dismissed, **not** until the
50
- * value changes. Dismissal is keyed by field so re-triggering a different field's rule
51
- * still shows. Re-armed when a different record is loaded.
52
- */
53
- private readonly dismissed;
54
- /**
55
- * Info banners from the rules engine that are still showing.
56
- *
57
- * A getter, not a `computed`: the source is `dynamicFormComp`, a ViewChild. A computed
58
- * evaluated before the view initialises would capture no dependency on the child's
59
- * `ruleResult` signal and then never recompute. Read per change-detection pass instead.
60
- */
61
- get infoBanners(): {
62
- fieldId: string;
63
- message: string;
64
- }[];
65
- dismissInfoBanner(fieldId: string): void;
66
- /** True when nothing in the record may be edited. */
67
- get recordReadOnly(): boolean;
68
- /**
69
- * A row of an `array` field is edited in a drawer, not inline in the tab panel.
70
- *
71
- * The drawer is rendered outside the tab container on purpose: the reference found that
72
- * building a row's FormGroup inside the tab view races the tab's own initialisation, so
73
- * the row form is kept detached and only pushed into the FormArray on save.
74
- */
75
- readonly inlineRowField: import("@angular/core").WritableSignal<any>;
76
- readonly inlineRowIndex: import("@angular/core").WritableSignal<number>;
77
- inlineRowForm: FormGroup | null;
78
- /** `array` fields on the tab currently shown, so the template can list their rows. */
79
- get arrayFieldsForActiveTab(): NestedFieldConfig[];
80
- /**
81
- * Rows currently held by an `array` field, read from the record value.
82
- *
83
- * Deliberately not read off the child's FormArray: that control only exists once the
84
- * child has initialised, so rendering from it changed value mid-pass. `currentData` is a
85
- * signal seeded from `initialData` and kept current by the child's `formChange`.
86
- */
87
- rowsOf(field: NestedFieldConfig): Record<string, unknown>[];
88
- /** One row rendered as text, so the list is readable without opening the drawer. */
89
- rowSummary(field: NestedFieldConfig, row: Record<string, unknown>): string;
90
- openAddRow(field: NestedFieldConfig): void;
91
- openEditRow(field: NestedFieldConfig, index: number): void;
92
- cancelRow(): void;
93
- /** Commit the drawer into the FormArray. Rules re-evaluate off the resulting value change. */
94
- saveRow(): void;
95
- deleteRow(field: NestedFieldConfig, index: number): void;
96
- /**
97
- * The tab currently open for editing, or null when the record is being viewed.
98
- *
99
- * The reference edits one tab section at a time rather than the whole record, and
100
- * validates only that section on save — a required field on an untouched tab must not
101
- * block saving the tab you are actually working on.
102
- */
103
- readonly editingTabId: import("@angular/core").WritableSignal<string>;
104
- /**
105
- * The tab the inner form is showing.
106
- *
107
- * Tracked here rather than read off the ViewChild. The child resolves its first tab as
108
- * part of its own initialisation, so reading it during this component's render changed
109
- * value mid-pass — an ExpressionChangedAfterItHasBeenChecked error. Seeded from the
110
- * config and kept in step by the child's `activeTabChange` output.
111
- */
112
- private readonly activeTabIdSig;
113
- get activeTabId(): string | null;
114
- onActiveTabChange(tabId: string): void;
115
- /** First tab a user can see, used to seed the active tab before the child reports one. */
116
- private firstVisibleTabId;
117
- /** Errors from the last save attempt, by field id. */
118
- readonly sectionErrors: import("@angular/core").WritableSignal<Record<string, string>>;
119
- /** Template access to `Object.keys` for iterating the error map. */
120
- protected readonly Object: ObjectConstructor;
121
- get isEditingActiveTab(): boolean;
122
- /** Fields render read-only unless their own tab is the one being edited. */
123
- get sectionReadOnly(): boolean;
124
- editSection(): void;
125
- /** Discard this section's edits, restoring each field from the session baseline. */
126
- cancelSection(): void;
127
- /**
128
- * Validate this section only, then emit it. Validation is scoped two ways: Angular
129
- * validity of the tab's own controls, and rules filtered to this tab — a rule targeting
130
- * a hidden tab must not block the save (the reference's OV0-968 fix).
131
- */
132
- saveSection(): void;
133
- /**
134
- * Seed the session baseline from the loaded record so `VALUE_CHANGED` rules and the
135
- * modification banner compare against the record as it was opened — not as it was
136
- * after the first keystroke.
137
- */
138
- ngOnChanges(changes: SimpleChanges): void;
139
- get recordTitle(): string;
140
- get avatarLetter(): string;
141
- /** Every field in the config, at any depth. */
142
- private allFields;
143
- /** The field flagged `isProfileImage`, rendered as the record avatar. */
144
- get headerProfileField(): NestedFieldConfig | null;
145
- /** The field flagged `isHeaderToggle`, rendered as a status switch in the header. */
146
- get headerToggleField(): NestedFieldConfig | null;
147
- /** Avatar image URL, when the profile field holds a persisted FileRef. */
148
- get profileImageUrl(): string | null;
149
- get headerToggleValue(): boolean;
150
- /**
151
- * Flip the header toggle. It writes straight to the control rather than going through
152
- * section editing — the reference treats it as a record-level status switch, not part of
153
- * any one tab's section.
154
- */
155
- toggleHeaderStatus(): void;
156
- /** Locate a field's control wherever it lives, since header fields may be on any tab. */
157
- private controlFor;
158
- /**
159
- * A field's current value, read from the record so it is stable during render.
160
- *
161
- * Resolved through `getTabData` so it honours exactly the nesting contract the form
162
- * patches with — `{ tabId: { fieldId } }`, or the record root when the tab sets
163
- * `flatData`. Reading the record flat instead is not a shortcut: it makes the summary
164
- * display values the form never loaded, which reads as "the data is there" while every
165
- * control behind it is empty.
166
- *
167
- * Walks sub-tabs as well: a summary, header, or profile field may sit on any tab at any
168
- * depth.
169
- */
170
- private fieldValue;
171
- readonly isModified: import("@angular/core").Signal<boolean>;
172
- readonly summaryFields: import("@angular/core").Signal<NestedFieldConfig[]>;
173
- onFormChange(data: Record<string, any>): void;
174
- onFormSubmit(data: Record<string, any>): void;
175
- onFormReset(): void;
176
- formatFieldLabel(field: NestedFieldConfig): string;
177
- /** Summary values render through the shared core formatter, not a local stringifier. */
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
- */
196
- jumpToField(fieldId: string): void;
197
- static ɵfac: i0.ɵɵFactoryDeclaration<DynamicRecordFormComponent, never>;
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>;
199
- }
@@ -1,24 +0,0 @@
1
- import { EnvironmentProviders, Type } from '@angular/core';
2
- /**
3
- * Field-type registration — the tree-shaking seam.
4
- *
5
- * `FieldRegistryService` imports no components. Whatever you register here is what gets
6
- * bundled, so an app that only uses text/number/dropdown pays for three components and
7
- * their imports, not nineteen.
8
- *
9
- * ```ts
10
- * // Everything (convenient; bundles all built-ins):
11
- * provideNgxDynamicEntity(), provideBuiltInFieldTypes()
12
- *
13
- * // Only what this app renders:
14
- * provideFieldTypes({ text: TextFieldComponent, number: NumberFieldComponent })
15
- * ```
16
- *
17
- * Both merge into `FIELD_TYPE_REGISTRY`; later registrations win on key collision.
18
- */
19
- /** Map of every built-in field type key → component. Referencing this pulls in all of them. */
20
- export declare function builtInFieldTypes(): Record<string, Type<any>>;
21
- /** Register a specific set of field types. Only the components you name are bundled. */
22
- export declare function provideFieldTypes(types: Record<string, Type<any>>): EnvironmentProviders;
23
- /** Register all 19 built-in field type keys. Convenience for apps that use most of them. */
24
- export declare function provideBuiltInFieldTypes(): EnvironmentProviders;
@@ -1,56 +0,0 @@
1
- import { EnvironmentProviders, Type } from '@angular/core';
2
- import type { EntityReferenceLoader, LookupListSource, RecordMigration } from '@dynamic-entity/core';
3
- import { type HookFn } from '../tokens/injection-tokens';
4
- export interface NgxDynamicEntityConfig {
5
- /** Roles that see masked field values as XXXXXXXXX */
6
- maskedRoles?: string[];
7
- /** Custom field type components keyed by type string */
8
- fieldTypes?: Record<string, Type<any>>;
9
- /**
10
- * Entity-ref option loaders keyed by entity key.
11
- * Each receives `{ parentValue, filters, lang }` and may return an array, Promise, or Observable.
12
- */
13
- entityRefs?: Record<string, EntityReferenceLoader>;
14
- /**
15
- * Named master lists keyed by list name, for fields that set `listName`.
16
- * Each may be the values themselves (array/Promise/Observable) or a loader function — prefer
17
- * a loader for anything fetched, so an unused list is never loaded.
18
- */
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>;
36
- /** Custom validator functions keyed by validator name */
37
- validators?: Record<string, any>;
38
- /** Hook functions keyed by hook name */
39
- hooks?: Record<string, HookFn>;
40
- /**
41
- * Ordered steps that upgrade a saved record when `EntityFormConfig.version` moves ahead
42
- * of the record's `_configVersion`. Applied to `initialData` before the form is patched.
43
- */
44
- migrations?: RecordMigration[];
45
- }
46
- /**
47
- * Provider function for ngx-dynamic-entity.
48
- * All options are optional — consumers provide only what they use (ISP).
49
- *
50
- * Usage in app.config.ts:
51
- * provideNgxDynamicEntity({
52
- * maskedRoles: ['IT_SUPPORT'],
53
- * entityRefs: { clients: () => clientService.getOptions() },
54
- * })
55
- */
56
- export declare const provideNgxDynamicEntity: (config?: NgxDynamicEntityConfig) => EnvironmentProviders;
@@ -1,45 +0,0 @@
1
- import type { EntityFormConfig, NestedFieldConfig, ReferenceLoaderContext, ReferenceOption } from '@dynamic-entity/core';
2
- import * as i0 from "@angular/core";
3
- /**
4
- * CascadeDataService — loads entity-reference options and applies parent→child cascades.
5
- *
6
- * Loaders come from the registry only. This service owns the three things a
7
- * field component should not: resolving the loader, coercing whatever it returned
8
- * (array | Promise | Observable) into a Promise, and filtering by the parent's value.
9
- */
10
- export declare class CascadeDataService {
11
- private readonly registry;
12
- private readonly references;
13
- /** Preload runs by formConfigId, so a second call joins the first rather than re-fetching. */
14
- private readonly preloads;
15
- /** Registry key for a field: explicit `linkedEntityKey`, else the field id. */
16
- entityKeyFor(field: NestedFieldConfig): string;
17
- /** Whether a loader is registered for this field. */
18
- canLoad(field: NestedFieldConfig): boolean;
19
- /**
20
- * Load the options for a field, cascade-filtered against `parentValue`.
21
- * Returns `[]` when no loader is registered, or when a cascade child has no parent value yet.
22
- */
23
- load(field: NestedFieldConfig, ctx?: ReferenceLoaderContext): Promise<ReferenceOption[]>;
24
- /**
25
- * Warm the cache for every entity a config's cascades depend on, so changing a parent
26
- * resolves from memory instead of a round-trip.
27
- *
28
- * Idempotent per `formConfigId`: calling it again returns the in-flight or completed run
29
- * rather than re-fetching.
30
- */
31
- initializeCascadeData(formConfigId: string, config: EntityFormConfig, lang?: string): Promise<void>;
32
- /** Resolves true once `initializeCascadeData` for this config has finished. */
33
- waitForData(formConfigId: string): Promise<boolean>;
34
- /**
35
- * Child options for a parent selection, from cache only — no load is triggered.
36
- * Returns `[]` when the entity has not been preloaded.
37
- */
38
- getCachedChildOptions(field: NestedFieldConfig, parentValue: unknown, lang?: string): ReferenceOption[];
39
- /** Forget the preload for one config. The option cache itself is untouched. */
40
- clearCache(formConfigId: string): void;
41
- /** Every entity-ref field in the config that participates in a cascade. */
42
- private cascadeFields;
43
- static ɵfac: i0.ɵɵFactoryDeclaration<CascadeDataService, never>;
44
- static ɵprov: i0.ɵɵInjectableDeclaration<CascadeDataService>;
45
- }
@@ -1,14 +0,0 @@
1
- import type { EntityFormConfig } from '@dynamic-entity/core';
2
- import * as i0 from "@angular/core";
3
- export declare class ConfigSourceService {
4
- private readonly cache;
5
- private readonly inFlight;
6
- private readonly handler?;
7
- constructor(handler?: any);
8
- /** Retrieve an entity config by key, with caching and in-flight deduplication. */
9
- getConfig(entityKey: string): Promise<EntityFormConfig | undefined>;
10
- /** Clear cached configs (e.g. after a config edit in the builder). */
11
- clearCache(entityKey?: string): void;
12
- static ɵfac: i0.ɵɵFactoryDeclaration<ConfigSourceService, [{ optional: true; }]>;
13
- static ɵprov: i0.ɵɵInjectableDeclaration<ConfigSourceService>;
14
- }
@@ -1,27 +0,0 @@
1
- import type { ReferenceOption } from '@dynamic-entity/core';
2
- /**
3
- * Backing store for cached reference options.
4
- *
5
- * Pluggable on purpose. The reference implementation kept its cache in a service instance,
6
- * so every entity label was re-fetched on a browser refresh (issue #22 in the architecture
7
- * doc). A consumer that wants the cache to survive a reload can back this with
8
- * sessionStorage; the default keeps it in memory, which is the right call for a library
9
- * that must not silently persist a tenant's data.
10
- */
11
- export interface EntityRefCacheStore {
12
- get(key: string): ReferenceOption[] | undefined;
13
- set(key: string, value: ReferenceOption[]): void;
14
- delete(key: string): void;
15
- /** Every key currently held — used for prefix invalidation of one entity. */
16
- keys(): string[];
17
- clear(): void;
18
- }
19
- /** Default in-memory store. Lives as long as the injector that created it. */
20
- export declare class InMemoryEntityRefCacheStore implements EntityRefCacheStore {
21
- private readonly map;
22
- get(key: string): ReferenceOption[] | undefined;
23
- set(key: string, value: ReferenceOption[]): void;
24
- delete(key: string): void;
25
- keys(): string[];
26
- clear(): void;
27
- }
@@ -1,21 +0,0 @@
1
- import type { EntityReferenceLoader } from '@dynamic-entity/core';
2
- import * as i0 from "@angular/core";
3
- /**
4
- * EntityRefRegistryService — resolves entity-ref option loader functions.
5
- * Entity-ref options come ONLY from this registry.
6
- * Never pass loaders via @Input() on field components.
7
- *
8
- * Consumers register loaders via provideNgxDynamicEntity({ entityRefs: { clients: () => [...] } }).
9
- * EntityRefFieldComponent injects this service and calls resolve(field.component ?? field.id).
10
- */
11
- export declare class EntityRefRegistryService {
12
- private readonly registry;
13
- /**
14
- * Get the option loader for an entity key.
15
- * @returns The loader, or null if none is registered.
16
- */
17
- resolve(entityKey: string): EntityReferenceLoader | null;
18
- has(entityKey: string): boolean;
19
- static ɵfac: i0.ɵɵFactoryDeclaration<EntityRefRegistryService, never>;
20
- static ɵprov: i0.ɵɵInjectableDeclaration<EntityRefRegistryService>;
21
- }
@@ -1,24 +0,0 @@
1
- import type { ReferenceOption } from '@dynamic-entity/core';
2
- import * as i0 from "@angular/core";
3
- export interface EntityRefSelection {
4
- fieldId: string;
5
- /** The chosen option, or `null` when the selection was cleared. */
6
- option: ReferenceOption | null;
7
- }
8
- /**
9
- * EntityRefSelectionService — the channel an entity-ref field uses to publish *which record*
10
- * the user picked, so the owning form can run `autoPatch` against it.
11
- *
12
- * The control value alone is not enough: `autoPatch` copies fields out of the selected
13
- * record, which only the field component has after loading its options.
14
- *
15
- * Provided at `DynamicFormComponent` level so each form instance gets its own channel;
16
- * the `root` fallback keeps standalone field components injectable in isolation.
17
- */
18
- export declare class EntityRefSelectionService {
19
- private readonly subject;
20
- readonly selection$: import("rxjs").Observable<EntityRefSelection>;
21
- emit(fieldId: string, option: ReferenceOption | null): void;
22
- static ɵfac: i0.ɵɵFactoryDeclaration<EntityRefSelectionService, never>;
23
- static ɵprov: i0.ɵɵInjectableDeclaration<EntityRefSelectionService>;
24
- }
@@ -1,49 +0,0 @@
1
- import type { ReferenceLoaderContext, ReferenceOption } from '@dynamic-entity/core';
2
- import * as i0 from "@angular/core";
3
- /**
4
- * EntityReferenceService — the cache in front of consumer-registered option loaders.
5
- *
6
- * Three layers, matching the reference implementation:
7
- * 1. **options** — resolved `ReferenceOption[]` per (entity, lang, displayFields, filters).
8
- * 2. **in-flight** — the pending Promise, so N fields referencing one entity trigger one
9
- * load rather than N. This is the layer that actually removes duplicate HTTP calls;
10
- * an options cache alone does nothing for concurrent first loads.
11
- * 3. **labels** — synchronous value → text, for rendering a stored reference without
12
- * waiting on a load.
13
- *
14
- * Invalidation is per entity, not global. The reference could only `clearCache()` everything
15
- * (issue #16), so saving one record re-fetched every entity's options.
16
- */
17
- export declare class EntityReferenceService {
18
- private readonly registry;
19
- private readonly store;
20
- /** Loads in progress, keyed the same way as the options cache. */
21
- private readonly inFlight;
22
- /** `entityKey|lang|canonicalValue` -> display text. */
23
- private readonly labels;
24
- /**
25
- * Options for an entity, from cache when present.
26
- * Concurrent callers with the same key share one load.
27
- */
28
- load(entityKey: string, ctx?: ReferenceLoaderContext): Promise<ReferenceOption[]>;
29
- /** Cached options without triggering a load. `undefined` when not cached. */
30
- peek(entityKey: string, ctx?: ReferenceLoaderContext): ReferenceOption[] | undefined;
31
- /**
32
- * Display text for a stored reference value, synchronously.
33
- * Returns `undefined` when the entity's options have never been loaded.
34
- */
35
- labelFor(entityKey: string, value: unknown, lang?: string): string | undefined;
36
- /** Drop everything cached for one entity, leaving other entities intact. */
37
- invalidate(entityKey: string): void;
38
- clear(): void;
39
- private rememberLabels;
40
- /** Label-cache key. An object value collapses to its resolved text for the active language. */
41
- private labelKey;
42
- private invoke;
43
- private toPromise;
44
- private isSubscribable;
45
- /** Exposed for cascade filtering, which matches a parent value against option values. */
46
- matches(a: unknown, b: unknown, lang?: string): boolean;
47
- static ɵfac: i0.ɵɵFactoryDeclaration<EntityReferenceService, never>;
48
- static ɵprov: i0.ɵɵInjectableDeclaration<EntityReferenceService>;
49
- }
@@ -1,32 +0,0 @@
1
- import { Type } from '@angular/core';
2
- import * as i0 from "@angular/core";
3
- /**
4
- * FieldRegistryService — resolves field type strings to Angular component classes.
5
- *
6
- * **Tree-shaking contract:** this service holds *no* static imports of field components.
7
- * Nothing is bundled until something registers it, which is what makes the built-ins
8
- * opt-in. Register them with `provideBuiltInFieldTypes()` (all 19 keys) or a narrower
9
- * subset via `provideFieldTypes({ ... })` — see `providers/provide-field-types.ts`.
10
- *
11
- * Resolution order: consumer token registry → runtime registrations → nothing.
12
- */
13
- export declare class FieldRegistryService {
14
- private readonly consumerRegistry;
15
- private readonly dynamicRegistry;
16
- constructor();
17
- /** Register a field type component mapping at runtime. */
18
- register(type: string, component: Type<any>): void;
19
- /** Register multiple field type component mappings at runtime. */
20
- registerAll(map: Record<string, Type<any>>): void;
21
- /**
22
- * Resolve a field type string to a component class.
23
- * Consumer token registry takes priority over runtime registrations.
24
- */
25
- resolve(fieldType: string): Type<any> | null;
26
- /** Check if a field type has a registered component. */
27
- has(fieldType: string): boolean;
28
- /** Every registered field type key. Consumer overrides and runtime registrations combined. */
29
- registeredTypes(): string[];
30
- static ɵfac: i0.ɵɵFactoryDeclaration<FieldRegistryService, never>;
31
- static ɵprov: i0.ɵɵInjectableDeclaration<FieldRegistryService>;
32
- }
@@ -1,27 +0,0 @@
1
- import type { FileRef } from '@dynamic-entity/core';
2
- import * as i0 from "@angular/core";
3
- /**
4
- * FileUploadService — the single place image-field and file-field agree on how a
5
- * selected `File` becomes a `FileRef`.
6
- *
7
- * With `UPLOAD_HANDLER` registered the file is persisted and stored as `{ url }`;
8
- * without one it is stored as `{ file }` for the consumer to upload at submit time.
9
- */
10
- export declare class FileUploadService {
11
- private readonly handler;
12
- get hasHandler(): boolean;
13
- /** Metadata common to both persisted and unpersisted refs. */
14
- private describe;
15
- /**
16
- * Turn a selected File into the value to store on the control.
17
- * @throws when a registered handler rejects — callers surface it as a field error.
18
- */
19
- toFileRef(file: File): Promise<FileRef>;
20
- /** Object URL for previewing an unpersisted File. Callers must revoke it. */
21
- previewUrlFor(ref: FileRef | null | undefined): string | null;
22
- revokePreviewUrl(url: string | null): void;
23
- private normalize;
24
- private isSubscribable;
25
- static ɵfac: i0.ɵɵFactoryDeclaration<FileUploadService, never>;
26
- static ɵprov: i0.ɵɵInjectableDeclaration<FileUploadService>;
27
- }
@@ -1,12 +0,0 @@
1
- import * as i0 from "@angular/core";
2
- /**
3
- * HookRegistryService — stores and runs Angular-side pre/post hooks.
4
- * Consumers register hooks via provideNgxDynamicEntity({ hooks: { 'clients:beforeSave': fn } }).
5
- */
6
- export declare class HookRegistryService {
7
- private readonly registry;
8
- has(key: string): boolean;
9
- run(key: string, data: any, context?: any): Promise<any>;
10
- static ɵfac: i0.ɵɵFactoryDeclaration<HookRegistryService, never>;
11
- static ɵprov: i0.ɵɵInjectableDeclaration<HookRegistryService>;
12
- }
@@ -1,94 +0,0 @@
1
- import { type WritableSignal } from '@angular/core';
2
- import type { DropdownOption, LookupListValue, NestedFieldConfig } from '@dynamic-entity/core';
3
- import * as i0 from "@angular/core";
4
- /**
5
- * LookupRegistryService — the cache in front of consumer-registered master lists.
6
- *
7
- * Three layers, deliberately the same shape as `EntityReferenceService`, because the problem is
8
- * the same: one list is typically used by many fields across many entities.
9
- * 1. **options** — resolved `DropdownOption[]` per (list name, lang).
10
- * 2. **in-flight** — the pending Promise, so N fields on one list trigger one load, not N.
11
- * 3. **labels** — synchronous value → text (parity plan §6.2). This is not a nicety: since
12
- * `viewMode` defaults to true a record opens read-only, so rendering a stored value as
13
- * text is the *first* thing a `listName` field has to do. An async-only registry would
14
- * paint the raw stored value and then flip, on every record open.
15
- *
16
- * A failed load rejects and caches nothing, so one transient error does not serve an empty
17
- * list forever — the same fix made for entity references in phase 5.
18
- */
19
- export declare class LookupRegistryService {
20
- private readonly registry;
21
- /** `listName|lang` -> resolved options. */
22
- private readonly options;
23
- /** `listName|lang` -> normalised values, so a consumer can reach `code` / `isSystemDefined`. */
24
- private readonly values;
25
- /** Loads in progress, keyed as above. */
26
- private readonly inFlight;
27
- /** `listName|lang|text` -> display text, for the synchronous read-only path. */
28
- private readonly labels;
29
- has(listName: string): boolean;
30
- /**
31
- * Options for a named list, from cache when present.
32
- * Concurrent callers with the same key share one load.
33
- */
34
- load(listName: string, lang?: string): Promise<DropdownOption[]>;
35
- /** Cached options without triggering a load. `undefined` when not loaded. */
36
- peek(listName: string, lang?: string): DropdownOption[] | undefined;
37
- /**
38
- * Cached list values, carrying `_id`, `code`, `isSystemDefined` and `from`.
39
- * The library never reads those; this is the seam for a consumer that needs them.
40
- */
41
- valuesFor(listName: string, lang?: string): LookupListValue[] | undefined;
42
- /**
43
- * Display text for a stored value, synchronously. `undefined` when the list has not loaded
44
- * or holds no matching value — the caller keeps its own fallback, which is the stored text.
45
- *
46
- * Every language of an option is indexed, so a record saved under one language renders in
47
- * another: a value stored as `'Aktiv'` resolves to `'Active'` under `lang: 'en'`.
48
- */
49
- labelFor(listName: string | undefined, value: unknown, lang?: string): string | undefined;
50
- /** True when this field's options have to come from the registry. */
51
- needsResolve(field: NestedFieldConfig | undefined): boolean;
52
- /**
53
- * The options to render right now, without awaiting: inline options, else whatever the
54
- * registry already holds. Empty on a cold miss — pair with `resolveOptions`.
55
- */
56
- optionsFor(field: NestedFieldConfig | undefined, lang?: string): DropdownOption[];
57
- /** The options for a field, loading the named list if needed. */
58
- resolveOptions(field: NestedFieldConfig | undefined, lang?: string): Promise<DropdownOption[]>;
59
- /** Drop everything cached for one list, leaving other lists intact. */
60
- invalidate(listName: string): void;
61
- clear(): void;
62
- private cacheKey;
63
- private labelKey;
64
- /** Index an option under every language it carries, so any stored spelling finds it. */
65
- private rememberLabels;
66
- /** The texts a stored value could have been indexed under. */
67
- private candidateTexts;
68
- private invoke;
69
- private toPromise;
70
- private isSubscribable;
71
- static ɵfac: i0.ɵɵFactoryDeclaration<LookupRegistryService, never>;
72
- static ɵprov: i0.ɵɵInjectableDeclaration<LookupRegistryService>;
73
- }
74
- /** A choice field component, as far as option resolution is concerned. */
75
- export interface ChoiceOptionHost {
76
- readonly field: NestedFieldConfig | undefined;
77
- readonly language: string;
78
- readonly options: WritableSignal<DropdownOption[]>;
79
- }
80
- /**
81
- * Point a choice component's `options` signal at the right source (parity plan §6.3).
82
- *
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 field
85
- * five-input contract intact: options are never pushed in as a sixth input.
86
- *
87
- * Setters rather than `ngOnChanges` on purpose. `ngOnChanges` only fires for inputs Angular
88
- * itself sets, so a component whose `field` is assigned directly — which the field specs do,
89
- * and any consumer holding a `ComponentRef` may do — would render no options at all.
90
- *
91
- * Paints synchronously first (inline options, or a warm list), then fills in when a cold list
92
- * lands. The late result is dropped if the field or language changed while it was in flight.
93
- */
94
- export declare function refreshChoiceOptions(host: ChoiceOptionHost, lookups: LookupRegistryService): void;