ngx-dynamic-entity 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +14 -0
  2. package/fesm2022/ngx-dynamic-entity.mjs +228 -150
  3. package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
  4. package/package.json +6 -7
  5. package/types/ngx-dynamic-entity.d.ts +1719 -0
  6. package/esm2022/lib/field-types/array-field.component.mjs +0 -168
  7. package/esm2022/lib/field-types/boolean-field.component.mjs +0 -101
  8. package/esm2022/lib/field-types/checkbox-field.component.mjs +0 -82
  9. package/esm2022/lib/field-types/currency-field.component.mjs +0 -102
  10. package/esm2022/lib/field-types/date-field.component.mjs +0 -91
  11. package/esm2022/lib/field-types/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
@@ -0,0 +1,1719 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, Type, EnvironmentProviders, WritableSignal, OnInit, OnChanges, OnDestroy, EventEmitter, SimpleChanges } from '@angular/core';
3
+ import { ReferenceOption as ReferenceOption$1, EntityReferenceLoader as EntityReferenceLoader$1, LookupListSource as LookupListSource$1, RecordMigration as RecordMigration$1, FieldValidators, NestedFieldConfig, ReferenceLoaderContext, EntityFormConfig, DropdownOption, LookupListValue, NestedTabConfig, FormRule, RuleEvaluationResult as RuleEvaluationResult$1 } from '@dynamic-entity/core';
4
+ export { AutoPatchConfig, CommonModuleEntry, DropdownOption, EntityFormConfig, EntityPermissions, EntityReferenceConfig, EntityReferenceLoader, FieldTableConfig, FieldValidators, FileRef, FormRule, LocalizedText, NestedFieldConfig, NestedTabConfig, PatchOnTrueMapping, RbacContext, ReferenceLoaderContext, ReferenceOption, RichFieldType, RuleAction, RuleCondition, RuleEvaluationResult, VersionedRecord, formatDisplayValue, normalizeConfig, normalizeOption, resolveLabel, resolveOptionLabel, resolveOptionValue, valuesMatch } from '@dynamic-entity/core';
5
+ import { ValidatorFn, AsyncValidatorFn, ValidationErrors, FormGroup, FormArray, AbstractControl } from '@angular/forms';
6
+ import * as rxjs from 'rxjs';
7
+
8
+ /**
9
+ * Backing store for cached reference options.
10
+ *
11
+ * Pluggable on purpose. The reference implementation kept its cache in a service instance,
12
+ * so every entity label was re-fetched on a browser refresh (issue #22 in the architecture
13
+ * doc). A consumer that wants the cache to survive a reload can back this with
14
+ * sessionStorage; the default keeps it in memory, which is the right call for a library
15
+ * that must not silently persist a tenant's data.
16
+ */
17
+ interface EntityRefCacheStore {
18
+ get(key: string): ReferenceOption$1[] | undefined;
19
+ set(key: string, value: ReferenceOption$1[]): void;
20
+ delete(key: string): void;
21
+ /** Every key currently held — used for prefix invalidation of one entity. */
22
+ keys(): string[];
23
+ clear(): void;
24
+ }
25
+ /** Default in-memory store. Lives as long as the injector that created it. */
26
+ declare class InMemoryEntityRefCacheStore implements EntityRefCacheStore {
27
+ private readonly map;
28
+ get(key: string): ReferenceOption$1[] | undefined;
29
+ set(key: string, value: ReferenceOption$1[]): void;
30
+ delete(key: string): void;
31
+ keys(): string[];
32
+ clear(): void;
33
+ }
34
+
35
+ /** Roles that see XXXXXXXXX for masked fields. Presentational only — enforce authz server-side. */
36
+ declare const MASKED_ROLES: InjectionToken<string[]>;
37
+ /**
38
+ * Consumer **overrides**: fieldType string → Angular component class.
39
+ * Highest priority — beats anything registered through `provideFieldTypes()`.
40
+ */
41
+ declare const FIELD_TYPE_REGISTRY: InjectionToken<Map<string, Type<any>>>;
42
+ /**
43
+ * Multi-provider of field-type sets, contributed by `provideFieldTypes()` /
44
+ * `provideBuiltInFieldTypes()`. Multi so several calls compose instead of clobbering;
45
+ * later sets win on key collision. This is the seam that keeps unused field components
46
+ * out of the bundle — nothing is imported until a set names it.
47
+ */
48
+ declare const FIELD_TYPE_SETS: InjectionToken<Record<string, Type<any>>[]>;
49
+ /**
50
+ * Registry: entity key → option loader.
51
+ *
52
+ * A loader receives a `ReferenceLoaderContext` (`parentValue`, `filters`, `lang`) and may
53
+ * return an array, a Promise, or an Observable of `ReferenceOption[]`. `ctx` is optional,
54
+ * so a zero-arg loader (`() => svc.list()`) is still a valid registration.
55
+ */
56
+ declare const ENTITY_REF_REGISTRY: InjectionToken<Map<string, EntityReferenceLoader>>;
57
+ /**
58
+ * Registry: list name → the values of a named master list (`field.listName`).
59
+ *
60
+ * A source may be the values themselves (array / Promise / Observable) or a **loader function**,
61
+ * which is what you want for anything fetched: a bare Promise is created when the provider is
62
+ * built, so every list would load whether or not a form uses it.
63
+ *
64
+ * Values are normalised by `normalizeLookupValues` — bare strings and `LocalizedText` are
65
+ * accepted alongside the full `{ code, name, sortOrder }` shape.
66
+ *
67
+ * @example
68
+ * provideNgxDynamicEntity({
69
+ * lookups: {
70
+ * employeeStatus: () => listService.getByName('employeeStatus'),
71
+ * grades: ['Junior', 'Senior'],
72
+ * },
73
+ * })
74
+ */
75
+ declare const LOOKUP_REGISTRY: InjectionToken<Map<string, LookupListSource>>;
76
+ /**
77
+ * Ordered steps that move a saved record forward when a config's `version` increases.
78
+ *
79
+ * Without these, `EntityFormConfig.version` and `VersionedRecord._configVersion` are
80
+ * declarations nothing acts on: a schema can be edited freely while records keep their old
81
+ * shape, and nothing reconciles the two. Registering migrations makes the version numbers
82
+ * mean something.
83
+ *
84
+ * @example
85
+ * provideNgxDynamicEntity({
86
+ * migrations: [
87
+ * { from: 1, to: 2, description: 'split name', migrate: r => ({ ...r, firstName: r['name'] }) },
88
+ * ],
89
+ * })
90
+ */
91
+ declare const RECORD_MIGRATIONS: InjectionToken<RecordMigration[]>;
92
+ /**
93
+ * Overrides for the messages shown under an invalid field, keyed by Angular error key
94
+ * (`required`, `minlength`, `pattern`, …) plus a few field-specific keys.
95
+ *
96
+ * The built-in messages are English literals. Registering here is what makes a non-English
97
+ * form possible without re-implementing every field component; unlisted keys keep their
98
+ * default, so overriding one does not mean supplying them all.
99
+ */
100
+ declare const VALIDATION_MESSAGES: InjectionToken<Record<string, any>>;
101
+ /** Registry: validator key → ValidatorFn */
102
+ declare const VALIDATOR_REGISTRY: InjectionToken<Map<string, any>>;
103
+ /**
104
+ * Registry: validator key → AsyncValidatorFn.
105
+ *
106
+ * Kept separate from VALIDATOR_REGISTRY because Angular applies the two differently: an async
107
+ * validator runs only once the synchronous ones pass, and puts the control into `pending`
108
+ * while it does. Name them from a schema with `validators: { customAsync: ['uniqueEmail'] }`.
109
+ */
110
+ declare const ASYNC_VALIDATOR_REGISTRY: InjectionToken<Map<string, any>>;
111
+ /**
112
+ * A registered hook: receives the payload, returns it (or a replacement), sync or async.
113
+ *
114
+ * Spelled out rather than typed as `Function`, which accepts class declarations and any
115
+ * arity and gives a consumer no signature to write against.
116
+ */
117
+ type HookFn = (data: any, context?: unknown) => unknown | Promise<unknown>;
118
+ /** Registry: hook key → async (data, context) => data */
119
+ declare const HOOK_REGISTRY: InjectionToken<Map<string, HookFn>>;
120
+ /**
121
+ * Consumer-registered list of common module tabs (documents, audit, tasks, etc.).
122
+ * The library ships no built-in implementations. Consumers provide their own list
123
+ * so the builder's module-picker and the renderer's tab switcher know what's available.
124
+ *
125
+ * @example
126
+ * providers: [
127
+ * {
128
+ * provide: COMMON_MODULES_REGISTRY,
129
+ * useValue: [
130
+ * { id: 'documents', label: { en: 'Documents' }, component: 'app-documents-view' },
131
+ * { id: 'audit', label: { en: 'Audit Log' }, component: 'app-audit' },
132
+ * ] satisfies CommonModuleEntry[]
133
+ * }
134
+ * ]
135
+ */
136
+ declare const COMMON_MODULES_REGISTRY: InjectionToken<CommonModuleEntry[]>;
137
+ /**
138
+ * Consumer-provided file/image upload handler.
139
+ * When supplied, image-field and file-field call it to persist the File and receive a stable
140
+ * URL back. Without it the field stores `{ file, name, size, mimeType }` for the consumer to
141
+ * upload on submit. May return the result directly, as a Promise, or as an Observable.
142
+ *
143
+ * @example
144
+ * { provide: UPLOAD_HANDLER, useFactory: () => (file: File) => myUploadService.upload(file) }
145
+ */
146
+ declare const UPLOAD_HANDLER: InjectionToken<FileUploadHandler>;
147
+ /**
148
+ * Backing store for the entity-reference options cache. Defaults to in-memory.
149
+ * Provide your own to make the cache outlive a page refresh.
150
+ *
151
+ * @example
152
+ * { provide: ENTITY_REF_CACHE_STORE, useClass: SessionStorageRefCache }
153
+ */
154
+ declare const ENTITY_REF_CACHE_STORE: InjectionToken<EntityRefCacheStore>;
155
+ /**
156
+ * Consumer-provided predicate function to check whether user roles can edit system default tabs or fields.
157
+ * Defaults to allowing edits if not provided.
158
+ */
159
+ declare const SYSTEM_DEFAULT_CAN_EDIT: InjectionToken<(roles: string[]) => boolean>;
160
+ type ConfigSourceHandler = (entityKey: string) => any;
161
+ /**
162
+ * Registry/Resolver for resolving an EntityFormConfig by entity key across entities (Phase 8).
163
+ * Enables cross-entity field referencing (`isReferenced`, `referencedEntityKey`, `referencedFieldId`, `hasDrift`).
164
+ */
165
+ declare const CONFIG_SOURCE: InjectionToken<ConfigSourceHandler>;
166
+
167
+ interface NgxDynamicEntityConfig {
168
+ /** Roles that see masked field values as XXXXXXXXX */
169
+ maskedRoles?: string[];
170
+ /** Custom field type components keyed by type string */
171
+ fieldTypes?: Record<string, Type<any>>;
172
+ /**
173
+ * Entity-ref option loaders keyed by entity key.
174
+ * Each receives `{ parentValue, filters, lang }` and may return an array, Promise, or Observable.
175
+ */
176
+ entityRefs?: Record<string, EntityReferenceLoader$1>;
177
+ /**
178
+ * Named master lists keyed by list name, for fields that set `listName`.
179
+ * Each may be the values themselves (array/Promise/Observable) or a loader function — prefer
180
+ * a loader for anything fetched, so an unused list is never loaded.
181
+ */
182
+ lookups?: Record<string, LookupListSource$1>;
183
+ /**
184
+ * Override the messages shown under invalid fields, keyed by Angular error key. A value is
185
+ * a string, or a function receiving `(language, error)` when it needs the error's detail.
186
+ * Unlisted keys keep their English default.
187
+ */
188
+ validationMessages?: Record<string, string | ((language: string, error: any) => string)>;
189
+ /**
190
+ * Async validator functions keyed by name, for checks that need a server — uniqueness,
191
+ * availability, a remote rule. Name them from a schema with
192
+ * `validators: { customAsync: ['uniqueEmail'] }`.
193
+ *
194
+ * Each is an Angular `AsyncValidatorFn`: it receives the control and returns a Promise or
195
+ * Observable of `ValidationErrors | null`. The control is `pending` while it runs, and the
196
+ * form cannot be submitted until every pending check settles.
197
+ */
198
+ asyncValidators?: Record<string, any>;
199
+ /** Custom validator functions keyed by validator name */
200
+ validators?: Record<string, any>;
201
+ /** Hook functions keyed by hook name */
202
+ hooks?: Record<string, HookFn>;
203
+ /**
204
+ * Ordered steps that upgrade a saved record when `EntityFormConfig.version` moves ahead
205
+ * of the record's `_configVersion`. Applied to `initialData` before the form is patched.
206
+ */
207
+ migrations?: RecordMigration$1[];
208
+ }
209
+ /**
210
+ * Provider function for ngx-dynamic-entity.
211
+ * All options are optional — consumers provide only what they use (ISP).
212
+ *
213
+ * Usage in app.config.ts:
214
+ * provideNgxDynamicEntity({
215
+ * maskedRoles: ['IT_SUPPORT'],
216
+ * entityRefs: { clients: () => clientService.getOptions() },
217
+ * })
218
+ */
219
+ declare const provideNgxDynamicEntity: (config?: NgxDynamicEntityConfig) => EnvironmentProviders;
220
+
221
+ /**
222
+ * Field-type registration — the tree-shaking seam.
223
+ *
224
+ * `FieldRegistryService` imports no components. Whatever you register here is what gets
225
+ * bundled, so an app that only uses text/number/dropdown pays for three components and
226
+ * their imports, not nineteen.
227
+ *
228
+ * ```ts
229
+ * // Everything (convenient; bundles all built-ins):
230
+ * provideNgxDynamicEntity(), provideBuiltInFieldTypes()
231
+ *
232
+ * // Only what this app renders:
233
+ * provideFieldTypes({ text: TextFieldComponent, number: NumberFieldComponent })
234
+ * ```
235
+ *
236
+ * Both merge into `FIELD_TYPE_REGISTRY`; later registrations win on key collision.
237
+ */
238
+ /** Map of every built-in field type key → component. Referencing this pulls in all of them. */
239
+ declare function builtInFieldTypes(): Record<string, Type<any>>;
240
+ /** Register a specific set of field types. Only the components you name are bundled. */
241
+ declare function provideFieldTypes(types: Record<string, Type<any>>): EnvironmentProviders;
242
+ /** Register all 19 built-in field type keys. Convenience for apps that use most of them. */
243
+ declare function provideBuiltInFieldTypes(): EnvironmentProviders;
244
+
245
+ /**
246
+ * FieldRegistryService — resolves field type strings to Angular component classes.
247
+ *
248
+ * **Tree-shaking contract:** this service holds *no* static imports of field components.
249
+ * Nothing is bundled until something registers it, which is what makes the built-ins
250
+ * opt-in. Register them with `provideBuiltInFieldTypes()` (all 19 keys) or a narrower
251
+ * subset via `provideFieldTypes({ ... })` — see `providers/provide-field-types.ts`.
252
+ *
253
+ * Resolution order: consumer token registry → runtime registrations → nothing.
254
+ */
255
+ declare class FieldRegistryService {
256
+ private readonly consumerRegistry;
257
+ private readonly dynamicRegistry;
258
+ constructor();
259
+ /** Register a field type component mapping at runtime. */
260
+ register(type: string, component: Type<any>): void;
261
+ /** Register multiple field type component mappings at runtime. */
262
+ registerAll(map: Record<string, Type<any>>): void;
263
+ /**
264
+ * Resolve a field type string to a component class.
265
+ * Consumer token registry takes priority over runtime registrations.
266
+ */
267
+ resolve(fieldType: string): Type<any> | null;
268
+ /** Check if a field type has a registered component. */
269
+ has(fieldType: string): boolean;
270
+ /** Every registered field type key. Consumer overrides and runtime registrations combined. */
271
+ registeredTypes(): string[];
272
+ static ɵfac: i0.ɵɵFactoryDeclaration<FieldRegistryService, never>;
273
+ static ɵprov: i0.ɵɵInjectableDeclaration<FieldRegistryService>;
274
+ }
275
+
276
+ /**
277
+ * HookRegistryService — stores and runs Angular-side pre/post hooks.
278
+ * Consumers register hooks via provideNgxDynamicEntity({ hooks: { 'clients:beforeSave': fn } }).
279
+ */
280
+ declare class HookRegistryService {
281
+ private readonly registry;
282
+ has(key: string): boolean;
283
+ run(key: string, data: any, context?: any): Promise<any>;
284
+ static ɵfac: i0.ɵɵFactoryDeclaration<HookRegistryService, never>;
285
+ static ɵprov: i0.ɵɵInjectableDeclaration<HookRegistryService>;
286
+ }
287
+
288
+ /**
289
+ * ValidatorRegistryService — resolves validator configs / key strings to Angular ValidatorFns.
290
+ */
291
+ declare class ValidatorRegistryService {
292
+ private readonly consumerRegistry;
293
+ private readonly asyncRegistry;
294
+ /**
295
+ * Resolve a validator key string to a ValidatorFn.
296
+ * Supports parameterized built-ins like 'min:0', 'maxLength:255'.
297
+ */
298
+ resolve(validatorKey: string): ValidatorFn | null;
299
+ /** Resolve multiple validator keys into a ValidatorFn[] */
300
+ resolveAll(validatorKeys?: string[]): ValidatorFn[];
301
+ /** Resolve a FieldValidators object or string[] into ValidatorFn[] */
302
+ resolveFromConfig(config?: FieldValidators | string[]): ValidatorFn[];
303
+ /**
304
+ * Resolve the async validators a field names, in `validators.customAsync`.
305
+ *
306
+ * Async validators are deliberately a separate registry and a separate config key: Angular
307
+ * attaches them through `setAsyncValidators`, runs them only once the synchronous ones
308
+ * pass, and holds the control in `pending` until they settle. Mixing them into the
309
+ * synchronous list would silently never run them.
310
+ *
311
+ * An unknown name is skipped rather than throwing, matching the synchronous behaviour.
312
+ */
313
+ resolveAsyncFromConfig(config?: FieldValidators | string[]): AsyncValidatorFn[];
314
+ static ɵfac: i0.ɵɵFactoryDeclaration<ValidatorRegistryService, never>;
315
+ static ɵprov: i0.ɵɵInjectableDeclaration<ValidatorRegistryService>;
316
+ }
317
+
318
+ /**
319
+ * EntityRefRegistryService — resolves entity-ref option loader functions.
320
+ * Entity-ref options come ONLY from this registry.
321
+ * Never pass loaders via @Input() on field components.
322
+ *
323
+ * Consumers register loaders via provideNgxDynamicEntity({ entityRefs: { clients: () => [...] } }).
324
+ * EntityRefFieldComponent injects this service and calls resolve(field.component ?? field.id).
325
+ */
326
+ declare class EntityRefRegistryService {
327
+ private readonly registry;
328
+ /**
329
+ * Get the option loader for an entity key.
330
+ * @returns The loader, or null if none is registered.
331
+ */
332
+ resolve(entityKey: string): EntityReferenceLoader$1 | null;
333
+ has(entityKey: string): boolean;
334
+ static ɵfac: i0.ɵɵFactoryDeclaration<EntityRefRegistryService, never>;
335
+ static ɵprov: i0.ɵɵInjectableDeclaration<EntityRefRegistryService>;
336
+ }
337
+
338
+ interface EntityRefSelection {
339
+ fieldId: string;
340
+ /** The chosen option, or `null` when the selection was cleared. */
341
+ option: ReferenceOption$1 | null;
342
+ }
343
+ /**
344
+ * EntityRefSelectionService — the channel an entity-ref field uses to publish *which record*
345
+ * the user picked, so the owning form can run `autoPatch` against it.
346
+ *
347
+ * The control value alone is not enough: `autoPatch` copies fields out of the selected
348
+ * record, which only the field component has after loading its options.
349
+ *
350
+ * Provided at `DynamicFormComponent` level so each form instance gets its own channel;
351
+ * the `root` fallback keeps standalone field components injectable in isolation.
352
+ */
353
+ declare class EntityRefSelectionService {
354
+ private readonly subject;
355
+ readonly selection$: rxjs.Observable<EntityRefSelection>;
356
+ emit(fieldId: string, option: ReferenceOption$1 | null): void;
357
+ static ɵfac: i0.ɵɵFactoryDeclaration<EntityRefSelectionService, never>;
358
+ static ɵprov: i0.ɵɵInjectableDeclaration<EntityRefSelectionService>;
359
+ }
360
+
361
+ /**
362
+ * CascadeDataService — loads entity-reference options and applies parent→child cascades.
363
+ *
364
+ * Loaders come from the registry only. This service owns the three things a
365
+ * field component should not: resolving the loader, coercing whatever it returned
366
+ * (array | Promise | Observable) into a Promise, and filtering by the parent's value.
367
+ */
368
+ declare class CascadeDataService {
369
+ private readonly registry;
370
+ private readonly references;
371
+ /** Preload runs by formConfigId, so a second call joins the first rather than re-fetching. */
372
+ private readonly preloads;
373
+ /** Registry key for a field: explicit `linkedEntityKey`, else the field id. */
374
+ entityKeyFor(field: NestedFieldConfig): string;
375
+ /** Whether a loader is registered for this field. */
376
+ canLoad(field: NestedFieldConfig): boolean;
377
+ /**
378
+ * Load the options for a field, cascade-filtered against `parentValue`.
379
+ * Returns `[]` when no loader is registered, or when a cascade child has no parent value yet.
380
+ */
381
+ load(field: NestedFieldConfig, ctx?: ReferenceLoaderContext): Promise<ReferenceOption$1[]>;
382
+ /**
383
+ * Warm the cache for every entity a config's cascades depend on, so changing a parent
384
+ * resolves from memory instead of a round-trip.
385
+ *
386
+ * Idempotent per `formConfigId`: calling it again returns the in-flight or completed run
387
+ * rather than re-fetching.
388
+ */
389
+ initializeCascadeData(formConfigId: string, config: EntityFormConfig, lang?: string): Promise<void>;
390
+ /** Resolves true once `initializeCascadeData` for this config has finished. */
391
+ waitForData(formConfigId: string): Promise<boolean>;
392
+ /**
393
+ * Child options for a parent selection, from cache only — no load is triggered.
394
+ * Returns `[]` when the entity has not been preloaded.
395
+ */
396
+ getCachedChildOptions(field: NestedFieldConfig, parentValue: unknown, lang?: string): ReferenceOption$1[];
397
+ /** Forget the preload for one config. The option cache itself is untouched. */
398
+ clearCache(formConfigId: string): void;
399
+ /** Every entity-ref field in the config that participates in a cascade. */
400
+ private cascadeFields;
401
+ static ɵfac: i0.ɵɵFactoryDeclaration<CascadeDataService, never>;
402
+ static ɵprov: i0.ɵɵInjectableDeclaration<CascadeDataService>;
403
+ }
404
+
405
+ /**
406
+ * EntityReferenceService — the cache in front of consumer-registered option loaders.
407
+ *
408
+ * Three layers, matching the reference implementation:
409
+ * 1. **options** — resolved `ReferenceOption[]` per (entity, lang, displayFields, filters).
410
+ * 2. **in-flight** — the pending Promise, so N fields referencing one entity trigger one
411
+ * load rather than N. This is the layer that actually removes duplicate HTTP calls;
412
+ * an options cache alone does nothing for concurrent first loads.
413
+ * 3. **labels** — synchronous value → text, for rendering a stored reference without
414
+ * waiting on a load.
415
+ *
416
+ * Invalidation is per entity, not global. The reference could only `clearCache()` everything
417
+ * (issue #16), so saving one record re-fetched every entity's options.
418
+ */
419
+ declare class EntityReferenceService {
420
+ private readonly registry;
421
+ private readonly store;
422
+ /** Loads in progress, keyed the same way as the options cache. */
423
+ private readonly inFlight;
424
+ /** `entityKey|lang|canonicalValue` -> display text. */
425
+ private readonly labels;
426
+ /**
427
+ * Options for an entity, from cache when present.
428
+ * Concurrent callers with the same key share one load.
429
+ */
430
+ load(entityKey: string, ctx?: ReferenceLoaderContext): Promise<ReferenceOption$1[]>;
431
+ /** Cached options without triggering a load. `undefined` when not cached. */
432
+ peek(entityKey: string, ctx?: ReferenceLoaderContext): ReferenceOption$1[] | undefined;
433
+ /**
434
+ * Display text for a stored reference value, synchronously.
435
+ * Returns `undefined` when the entity's options have never been loaded.
436
+ */
437
+ labelFor(entityKey: string, value: unknown, lang?: string): string | undefined;
438
+ /** Drop everything cached for one entity, leaving other entities intact. */
439
+ invalidate(entityKey: string): void;
440
+ clear(): void;
441
+ private rememberLabels;
442
+ /** Label-cache key. An object value collapses to its resolved text for the active language. */
443
+ private labelKey;
444
+ private invoke;
445
+ private toPromise;
446
+ private isSubscribable;
447
+ /** Exposed for cascade filtering, which matches a parent value against option values. */
448
+ matches(a: unknown, b: unknown, lang?: string): boolean;
449
+ static ɵfac: i0.ɵɵFactoryDeclaration<EntityReferenceService, never>;
450
+ static ɵprov: i0.ɵɵInjectableDeclaration<EntityReferenceService>;
451
+ }
452
+
453
+ /**
454
+ * LookupRegistryService — the cache in front of consumer-registered master lists.
455
+ *
456
+ * Three layers, deliberately the same shape as `EntityReferenceService`, because the problem is
457
+ * the same: one list is typically used by many fields across many entities.
458
+ * 1. **options** — resolved `DropdownOption[]` per (list name, lang).
459
+ * 2. **in-flight** — the pending Promise, so N fields on one list trigger one load, not N.
460
+ * 3. **labels** — synchronous value → text (parity plan §6.2). This is not a nicety: since
461
+ * `viewMode` defaults to true a record opens read-only, so rendering a stored value as
462
+ * text is the *first* thing a `listName` field has to do. An async-only registry would
463
+ * paint the raw stored value and then flip, on every record open.
464
+ *
465
+ * A failed load rejects and caches nothing, so one transient error does not serve an empty
466
+ * list forever — the same fix made for entity references in phase 5.
467
+ */
468
+ declare class LookupRegistryService {
469
+ private readonly registry;
470
+ /** `listName|lang` -> resolved options. */
471
+ private readonly options;
472
+ /** `listName|lang` -> normalised values, so a consumer can reach `code` / `isSystemDefined`. */
473
+ private readonly values;
474
+ /** Loads in progress, keyed as above. */
475
+ private readonly inFlight;
476
+ /** `listName|lang|text` -> display text, for the synchronous read-only path. */
477
+ private readonly labels;
478
+ has(listName: string): boolean;
479
+ /**
480
+ * Options for a named list, from cache when present.
481
+ * Concurrent callers with the same key share one load.
482
+ */
483
+ load(listName: string, lang?: string): Promise<DropdownOption[]>;
484
+ /** Cached options without triggering a load. `undefined` when not loaded. */
485
+ peek(listName: string, lang?: string): DropdownOption[] | undefined;
486
+ /**
487
+ * Cached list values, carrying `_id`, `code`, `isSystemDefined` and `from`.
488
+ * The library never reads those; this is the seam for a consumer that needs them.
489
+ */
490
+ valuesFor(listName: string, lang?: string): LookupListValue[] | undefined;
491
+ /**
492
+ * Display text for a stored value, synchronously. `undefined` when the list has not loaded
493
+ * or holds no matching value — the caller keeps its own fallback, which is the stored text.
494
+ *
495
+ * Every language of an option is indexed, so a record saved under one language renders in
496
+ * another: a value stored as `'Aktiv'` resolves to `'Active'` under `lang: 'en'`.
497
+ */
498
+ labelFor(listName: string | undefined, value: unknown, lang?: string): string | undefined;
499
+ /** True when this field's options have to come from the registry. */
500
+ needsResolve(field: NestedFieldConfig | undefined): boolean;
501
+ /**
502
+ * The options to render right now, without awaiting: inline options, else whatever the
503
+ * registry already holds. Empty on a cold miss — pair with `resolveOptions`.
504
+ */
505
+ optionsFor(field: NestedFieldConfig | undefined, lang?: string): DropdownOption[];
506
+ /** The options for a field, loading the named list if needed. */
507
+ resolveOptions(field: NestedFieldConfig | undefined, lang?: string): Promise<DropdownOption[]>;
508
+ /** Drop everything cached for one list, leaving other lists intact. */
509
+ invalidate(listName: string): void;
510
+ clear(): void;
511
+ private cacheKey;
512
+ private labelKey;
513
+ /** Index an option under every language it carries, so any stored spelling finds it. */
514
+ private rememberLabels;
515
+ /** The texts a stored value could have been indexed under. */
516
+ private candidateTexts;
517
+ private invoke;
518
+ private toPromise;
519
+ private isSubscribable;
520
+ static ɵfac: i0.ɵɵFactoryDeclaration<LookupRegistryService, never>;
521
+ static ɵprov: i0.ɵɵInjectableDeclaration<LookupRegistryService>;
522
+ }
523
+ /** A choice field component, as far as option resolution is concerned. */
524
+ interface ChoiceOptionHost {
525
+ readonly field: NestedFieldConfig | undefined;
526
+ readonly language: string;
527
+ readonly options: WritableSignal<DropdownOption[]>;
528
+ }
529
+ /**
530
+ * Point a choice component's `options` signal at the right source (parity plan §6.3).
531
+ *
532
+ * Called from the `field` / `language` input setters of `dropdown`, `radio` and `multiSelect` —
533
+ * the wiring is theirs, the decision is the service's, which is what keeps the field
534
+ * five-input contract intact: options are never pushed in as a sixth input.
535
+ *
536
+ * Setters rather than `ngOnChanges` on purpose. `ngOnChanges` only fires for inputs Angular
537
+ * itself sets, so a component whose `field` is assigned directly — which the field specs do,
538
+ * and any consumer holding a `ComponentRef` may do — would render no options at all.
539
+ *
540
+ * Paints synchronously first (inline options, or a warm list), then fills in when a cold list
541
+ * lands. The late result is dropped if the field or language changed while it was in flight.
542
+ */
543
+ declare function refreshChoiceOptions(host: ChoiceOptionHost, lookups: LookupRegistryService): void;
544
+
545
+ /**
546
+ * RbacService — ALL permission checks and masking logic live here.
547
+ */
548
+ declare class RbacService {
549
+ private readonly maskedRoles;
550
+ /**
551
+ * Check if any of the user's roles satisfy the required roles.
552
+ * hasPermission(roles, undefined) → true (no restriction).
553
+ */
554
+ hasPermission(userRoles: string[], requiredRoles?: string[]): boolean;
555
+ /** 3-level OR resolution */
556
+ resolveEffectiveMask(formMask?: boolean, tabMask?: boolean, fieldMask?: boolean): boolean;
557
+ /** Check if the user's roles include any masked role */
558
+ isUserMaskedRole(userRoles: string[]): boolean;
559
+ /** Determine if a specific field should be masked for this user */
560
+ shouldMaskField(field: NestedFieldConfig, tab: NestedTabConfig | undefined, config: EntityFormConfig, userRoles: string[]): boolean;
561
+ /** Get view/edit/delete permissions for a config */
562
+ getPermissions(config: EntityFormConfig, userRoles: string[]): {
563
+ canView: boolean;
564
+ canEdit: boolean;
565
+ canDelete: boolean;
566
+ };
567
+ static ɵfac: i0.ɵɵFactoryDeclaration<RbacService, never>;
568
+ static ɵprov: i0.ɵɵInjectableDeclaration<RbacService>;
569
+ }
570
+
571
+ /**
572
+ * RulesEvaluationService — evaluates dynamic form rules against active reactive form state.
573
+ */
574
+ declare class RulesEvaluationService {
575
+ /**
576
+ * Evaluate rules against form values and optional original baseline values.
577
+ */
578
+ evaluate(rules: FormRule[] | undefined | null, values: Record<string, unknown>, originalValues?: Record<string, unknown>): RuleEvaluationResult$1;
579
+ /**
580
+ * Filter rules pertinent to a single tab.
581
+ */
582
+ filterForTab(rules: FormRule[] | undefined | null, tabId: string, config: EntityFormConfig): FormRule[];
583
+ static ɵfac: i0.ɵɵFactoryDeclaration<RulesEvaluationService, never>;
584
+ static ɵprov: i0.ɵɵInjectableDeclaration<RulesEvaluationService>;
585
+ }
586
+
587
+ declare class ConfigSourceService {
588
+ private readonly cache;
589
+ private readonly inFlight;
590
+ private readonly handler?;
591
+ constructor(handler?: any);
592
+ /** Retrieve an entity config by key, with caching and in-flight deduplication. */
593
+ getConfig(entityKey: string): Promise<EntityFormConfig | undefined>;
594
+ /** Clear cached configs (e.g. after a config edit in the builder). */
595
+ clearCache(entityKey?: string): void;
596
+ static ɵfac: i0.ɵɵFactoryDeclaration<ConfigSourceService, [{ optional: true; }]>;
597
+ static ɵprov: i0.ɵɵInjectableDeclaration<ConfigSourceService>;
598
+ }
599
+
600
+ /**
601
+ * Resolves an Angular `ValidationErrors` object to the message shown under a field.
602
+ *
603
+ * These strings used to be hardcoded English literals inside each field component, so a
604
+ * German form meant re-implementing every field type — the labels honoured `language` while
605
+ * the errors underneath them did not.
606
+ *
607
+ * A message is a plain string, or a function when it needs the error's own detail
608
+ * (`minlength.requiredLength`, `min.min`, and so on). The active `language` is passed
609
+ * through, so one registration can serve every locale:
610
+ *
611
+ * ```typescript
612
+ * provideNgxDynamicEntity({
613
+ * validationMessages: {
614
+ * required: lang => (lang === 'de' ? 'Pflichtfeld.' : 'This field is required.'),
615
+ * minlength: (lang, err) => `Mindestens ${err['requiredLength']} Zeichen.`,
616
+ * },
617
+ * });
618
+ * ```
619
+ *
620
+ * Unregistered keys fall back to the defaults below, so overriding one message does not
621
+ * mean supplying them all.
622
+ */
623
+ type ValidationMessage = string | ((language: string, error: any) => string);
624
+ /** The built-in English messages. Every key here can be overridden individually. */
625
+ declare const DEFAULT_VALIDATION_MESSAGES: Record<string, ValidationMessage>;
626
+ declare class ValidationMessagesService {
627
+ private readonly overrides;
628
+ /**
629
+ * The message for the first error present, in the order the caller lists.
630
+ *
631
+ * Order is the caller's because it is field-specific: a dropdown wants
632
+ * `requiredSelection` where a text input wants `required`, and the first match wins so the
633
+ * most specific message is the one shown.
634
+ */
635
+ resolve(errors: ValidationErrors | null | undefined, language: string, order: readonly (string | readonly [errorKey: string, messageKey: string])[], fallbackKey?: string): string;
636
+ /** One message by key, honouring an override before the default. */
637
+ messageFor(key: string, language: string, error: unknown): string;
638
+ static ɵfac: i0.ɵɵFactoryDeclaration<ValidationMessagesService, never>;
639
+ static ɵprov: i0.ɵɵInjectableDeclaration<ValidationMessagesService>;
640
+ }
641
+
642
+ /**
643
+ * DynamicFormComponent — the main form component.
644
+ * Renders a reactive form from EntityFormConfig with tab support, responsive 12-col grid,
645
+ * rules evaluation, conditional visibility, autoPatch/patchOnTrue, criticalField locking,
646
+ * keyboard shortcuts (Ctrl+S, Esc), and RBAC-gated submission.
647
+ */
648
+ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
649
+ /**
650
+ * The schema to render.
651
+ *
652
+ * An accessor pair rather than a plain field: `ngOnChanges` normalises the option shape,
653
+ * and that result is kept here instead of being written back over the input. Reassigning
654
+ * an `@Input` works, but it mutates what the parent handed us — and a parent using OnPush
655
+ * or signals would never observe the replacement anyway.
656
+ *
657
+ * Reads of `this.config` get the normalised copy once it exists, so every internal caller
658
+ * is unaffected.
659
+ */
660
+ set config(value: EntityFormConfig);
661
+ get config(): EntityFormConfig;
662
+ private rawConfig;
663
+ private normalizedConfig?;
664
+ rules?: FormRule[];
665
+ initialData?: Record<string, any>;
666
+ /** Session-original values for `VALUE_CHANGED` rules. Captured from the first build when omitted. */
667
+ originalData?: Record<string, any>;
668
+ userRoles: string[];
669
+ language: string;
670
+ readonly: boolean;
671
+ /** Field ids forced read-only while the rest of the form stays editable. */
672
+ readOnlyFields: string[];
673
+ /**
674
+ * Render rule `info` banners inline. The record editor sets this false and renders its
675
+ * own dismissible versions, so the two do not double up.
676
+ */
677
+ showInfoBanners: boolean;
678
+ loading: boolean;
679
+ error: string | null;
680
+ /**
681
+ * Milliseconds to debounce `formChange`. The reference debounces at 300ms so a consumer
682
+ * re-rendering on every value change is not driven per keystroke. Zero emits
683
+ * synchronously, which the record editor relies on for its own bookkeeping.
684
+ */
685
+ changeDebounceMs: number;
686
+ /**
687
+ * Preview mode: seed one empty row per `array` field so the structure is visible, then
688
+ * disable the form. Used by the builder's live preview.
689
+ */
690
+ preview: boolean;
691
+ formSubmit: EventEmitter<Record<string, any>>;
692
+ formChange: EventEmitter<Record<string, any>>;
693
+ formReset: EventEmitter<void>;
694
+ /** The visible tab changed. Lets a host track it without reaching into this component. */
695
+ activeTabChange: EventEmitter<string>;
696
+ /**
697
+ * Emitted when a `beforeSave` hook aborted the save — by returning false or throwing.
698
+ * `formSubmit` does not fire in that case.
699
+ */
700
+ saveRejected: EventEmitter<{
701
+ reason: string;
702
+ error?: unknown;
703
+ }>;
704
+ private readonly cdr;
705
+ private readonly fb;
706
+ private readonly validatorRegistry;
707
+ private readonly hookRegistry;
708
+ private readonly rbacService;
709
+ private readonly rulesEvaluation;
710
+ private readonly entityRefSelection;
711
+ private readonly commonModulesRegistry;
712
+ private readonly migrations;
713
+ protected readonly Object: ObjectConstructor;
714
+ readonly activeTab: i0.WritableSignal<string>;
715
+ readonly activeSubTab: i0.WritableSignal<string>;
716
+ readonly isSaving: i0.WritableSignal<boolean>;
717
+ readonly formValues: i0.WritableSignal<Record<string, any>>;
718
+ /** Baseline captured at first build, used when `originalData` is not supplied. */
719
+ readonly sessionBaseline: i0.WritableSignal<Record<string, any>>;
720
+ /** Critical fields the user has explicitly unlocked for editing. */
721
+ readonly unlockedFields: i0.WritableSignal<ReadonlySet<string>>;
722
+ /** The active tab's field panel, focused after a tab switch. */
723
+ private formPanel?;
724
+ /** The hosted field components, so an external control-state change can reach them. */
725
+ private fieldHosts?;
726
+ form: FormGroup;
727
+ private valueSub?;
728
+ private statusSub?;
729
+ private selectionSub?;
730
+ /** Previous values, for detecting `patchOnTrue` false→true transitions. */
731
+ private previousValues;
732
+ handleKeyboardEvent(event: KeyboardEvent): void;
733
+ readonly ruleResult: i0.Signal<RuleEvaluationResult>;
734
+ /**
735
+ * Critical fields whose value differs from the session baseline.
736
+ * These drive the deferred `VALUE_CHANGED` banner — a critical edit is announced once,
737
+ * against the value the record had when the session started, not per keystroke.
738
+ */
739
+ readonly changedCriticalFields: i0.Signal<NestedFieldConfig[]>;
740
+ get tabs(): NestedTabConfig[];
741
+ get visibleTabs(): NestedTabConfig[];
742
+ get activeTabConfig(): NestedTabConfig | null;
743
+ get visibleSubTabs(): NestedTabConfig[];
744
+ get activeSubTabConfig(): NestedTabConfig | null;
745
+ get activeTabModuleComponent(): any | null;
746
+ get fieldsForActiveTab(): NestedFieldConfig[];
747
+ /**
748
+ * Entity-level permissions for the current roles.
749
+ *
750
+ * Cached rather than recomputed: this is read from the template several times per change
751
+ * detection pass, and a fresh object each time is both wasted allocation and a new
752
+ * identity for anything comparing by reference. Invalidated in `ngOnChanges` when
753
+ * `config` or `userRoles` change, which are its only inputs.
754
+ */
755
+ get permissions(): ReturnType<RbacService['getPermissions']>;
756
+ private permissionsCache;
757
+ /**
758
+ * Whether the current roles may see this record at all.
759
+ *
760
+ * `permissions.view` used to be computed and thrown away — a user whose roles failed it
761
+ * still received the complete form with every value in the DOM. It is honoured now.
762
+ *
763
+ * As with masking, this is presentational: it stops the browser rendering data, it does
764
+ * not stop the data reaching the browser. Authorize on the server.
765
+ */
766
+ get canView(): boolean;
767
+ /**
768
+ * Whether the current roles may delete this record.
769
+ *
770
+ * The library ships no delete affordance, so this is surfaced for the consumer to gate
771
+ * their own — exposed rather than dropped, because a permission the schema declares
772
+ * should be answerable.
773
+ */
774
+ get canDelete(): boolean;
775
+ get canSubmit(): boolean;
776
+ /**
777
+ * Errors raised by `validation` rules, keyed by the target they point at.
778
+ *
779
+ * A rule can declare the record invalid independently of Angular's validators, so form
780
+ * validity alone is not the whole picture. `DynamicRecordFormComponent.saveSection()`
781
+ * has always honoured these; `submit()` did not, which meant the same rule blocked one
782
+ * save path and merely painted a banner on the other — and anything wiring
783
+ * `(formSubmit)` to persistence wrote records the rules engine had already rejected.
784
+ */
785
+ get ruleValidationErrors(): Record<string, string>;
786
+ /** True when a `validation` rule is currently blocking submission. */
787
+ get hasRuleErrors(): boolean;
788
+ /**
789
+ * True while an async validator is still running.
790
+ *
791
+ * Angular reports a control with an outstanding async check as `pending`, and `invalid` is
792
+ * false until it settles — so without this a form could be submitted in the gap before a
793
+ * uniqueness check came back.
794
+ */
795
+ get isValidating(): boolean;
796
+ /** Single source of truth for both the submit guard and the button's disabled state. */
797
+ get submitBlocked(): boolean;
798
+ ngOnInit(): void;
799
+ ngOnChanges(changes: SimpleChanges): void;
800
+ ngOnDestroy(): void;
801
+ setActiveTab(tabId: string): void;
802
+ setActiveSubTab(subTabId: string): void;
803
+ getFieldSpan(field: NestedFieldConfig): string;
804
+ /** A critical field stays read-only until the user deliberately unlocks it. */
805
+ isFieldLocked(field: NestedFieldConfig): boolean;
806
+ /**
807
+ * Whether this field renders read-only, from any of the four reasons: the whole form,
808
+ * the field's own flag, a caller-supplied `readOnlyFields` entry, or a criticalField lock.
809
+ */
810
+ isFieldReadonly(field: NestedFieldConfig): boolean;
811
+ toggleFieldLock(field: NestedFieldConfig): void;
812
+ resolveFieldLabel(field: NestedFieldConfig): string;
813
+ private buildForm;
814
+ private buildFieldControl;
815
+ /** One row of an `array` field: a FormGroup when the field declares columns, else a bare control. */
816
+ /**
817
+ * The `FormArray` behind an `array` field, for callers that manage rows themselves —
818
+ * the record editor's inline row drawer.
819
+ */
820
+ getArrayControl(fieldId: string, tabId?: string): FormArray | null;
821
+ /**
822
+ * Build one detached row for an `array` field, with the field's own validators applied.
823
+ * Exposed so a row can be edited outside the form and pushed on save.
824
+ */
825
+ createArrayRow(field: NestedFieldConfig, value?: unknown): AbstractControl;
826
+ private buildArrayRow;
827
+ /**
828
+ * Mark every control touched, then re-check the field components.
829
+ *
830
+ * They are OnPush and `touched` is flipped from outside their own template, so the second
831
+ * half is what makes the error messages actually appear.
832
+ */
833
+ private markAllTouched;
834
+ /**
835
+ * Bring a record up to the config's `version` before it reaches the form.
836
+ *
837
+ * Done here because this is the one place a record enters the renderer, so a consumer
838
+ * cannot forget it. Migration is a no-op unless the consumer registered steps *and* the
839
+ * record is behind — an unstamped record is deliberately left alone (see `migrateRecord`).
840
+ *
841
+ * A missing or broken step throws from core. That is not swallowed: rendering a
842
+ * half-understood record would write it back in a shape matching neither schema.
843
+ */
844
+ private upgradeRecord;
845
+ private patchForm;
846
+ /**
847
+ * A record is nested by tab id (`{ tabId: { fieldId: value } }`) unless the tab sets
848
+ * `flatData: true`. Handing a flat record to a nested tab finds nothing, so the fields
849
+ * stay empty — with no error and no clue. That silent miss is the most expensive way to
850
+ * lose an hour with this library, so name it in dev builds.
851
+ *
852
+ * Only top-level keys that match a known field id are reported: anything else is assumed
853
+ * to be the consumer's own record metadata (ids, timestamps, `_configVersion`) and is not
854
+ * our business.
855
+ */
856
+ private warnUnconsumedInitialData;
857
+ /** Warn once per key per form instance — patchForm re-runs on every initialData change. */
858
+ private readonly warnedUnconsumedKeys;
859
+ /** Assemble full nested record from per-tab FormGroups, respecting flatData, refererField & arrays. */
860
+ extractRecord(): Record<string, any>;
861
+ /**
862
+ * Warns when a rule names a field id that two scopes define.
863
+ *
864
+ * Field ids are unique per scope, so `address` may exist on both Personal Details and Work
865
+ * Details — but a rule carries a bare id and no scope, so it cannot say which one it means.
866
+ * `flattenFormValues` keys by bare id, so one of the two silently wins and the rule reads
867
+ * whichever landed there.
868
+ *
869
+ * `validateConfig` catches this for `showWhen` and cascade parents, which live in the
870
+ * config. Rules do not: they arrive as a separate `@Input`, so the config alone cannot be
871
+ * checked for them and this is the only place both are in hand.
872
+ */
873
+ private warnAmbiguousRuleReferences;
874
+ private static readonly warnedAmbiguousIds;
875
+ /**
876
+ * The value map rules and `showWhen` are evaluated against.
877
+ *
878
+ * Every field appears under two keys: its bare id, and its `refererField` path wrapped in brackets.
879
+ * The bare id is what every config written so far uses and is kept exactly as it was — but
880
+ * ids are unique per scope, so when two scopes define one the last field walked wins and
881
+ * the rule reads whichever that is. `[personal.address]` names one field and cannot be
882
+ * ambiguous, which is why a rule that has to distinguish them uses the ref.
883
+ *
884
+ * Both live in one flat map on purpose: `evaluateFormRules` takes a `Record<string,
885
+ * unknown>` and needs no knowledge of refs at all — the extra keys simply resolve.
886
+ */
887
+ /**
888
+ * Whether a rule result names this field.
889
+ *
890
+ * A rule target is either a bare field id — how every config so far addresses a field — or
891
+ * a bracketed path, which is the only way to name one of two fields that share an id.
892
+ * Both have to match, or a rule written either way would fail to hide what it targeted.
893
+ */
894
+ private namesField;
895
+ /** The field's declared path, or the one its position implies when it carries none. */
896
+ private pathFor;
897
+ private flattenFormValues;
898
+ private getTabGroup;
899
+ /**
900
+ * `autoPatch` — copy mapped fields out of the record the user just selected in an
901
+ * entity-ref field into the configured target tab's controls.
902
+ */
903
+ private runAutoPatch;
904
+ /**
905
+ * `patchOnTrue` — when a boolean/checkbox field transitions to `true`,
906
+ * copy `from` → `to` within the current record.
907
+ */
908
+ private runPatchOnTrue;
909
+ /** Prefer a control declared on the configured target tab; fall back to a top-level control. */
910
+ private resolveTargetControl;
911
+ /**
912
+ * The fields the render filter operates on — a tab's own fields, at every tab depth.
913
+ * Deliberately not `allFields()`: `group` and `array` children are shown or hidden with
914
+ * their parent, and toggling a child's state independently would fight the parent's.
915
+ */
916
+ private tabFields;
917
+ /**
918
+ * Keep a hidden field's control out of form validity.
919
+ *
920
+ * A field hidden by a rule or a `showWhen` condition is filtered out of the render, but
921
+ * its validators stay attached — so a hidden *required* field holds `form.invalid` true
922
+ * forever, permanently disabling Save with nothing on screen to explain why.
923
+ *
924
+ * Disabling is the fix rather than stripping validators: Angular excludes disabled
925
+ * controls from validity, and the field's own validators survive intact for when it
926
+ * comes back. Values are untouched — `flattenFormValues` and `extractRecord` both read
927
+ * `control.value`, which a disabled control still carries, so rule evaluation and the
928
+ * emitted record see exactly what they saw before. That also keeps this from feeding
929
+ * back on itself: hiding a field cannot change the values the rules are evaluated over.
930
+ *
931
+ * The visibility predicate is the same one `fieldsForActiveTab` filters with, so what is
932
+ * rendered and what counts toward validity cannot drift apart.
933
+ */
934
+ private syncHiddenFieldState;
935
+ /** Every field in the config, including `group`/`array` children. */
936
+ private allFields;
937
+ /** An empty `originalData` counts as "not supplied" — fall back to the captured baseline. */
938
+ private baseline;
939
+ private sameValue;
940
+ getControl(fieldId: string, currentTabId?: string): AbstractControl | null;
941
+ /**
942
+ * Move focus into the newly shown panel.
943
+ *
944
+ * Activating a tab replaces everything below it while focus stays on the tab button, so a
945
+ * screen-reader or keyboard user is given no indication that the content changed and must
946
+ * tab back through the strip to reach it. Deferred a frame because the panel's contents
947
+ * are rendered by the change detection this call is part of.
948
+ */
949
+ private focusActivePanel;
950
+ /** Accessible name for the field panel — it is the tabpanel for the active tab. */
951
+ get activeTabLabel(): string;
952
+ /**
953
+ * Why a referenced field is flagged.
954
+ *
955
+ * `hasDrift` was written by the builder and read by nothing at runtime, so a field whose
956
+ * source definition had changed since it was linked looked entirely normal to the person
957
+ * filling the form in.
958
+ */
959
+ driftHint(field: NestedFieldConfig): string;
960
+ resolveTabLabel(tab: NestedTabConfig): string;
961
+ submit(): Promise<void>;
962
+ reset(): void;
963
+ static ɵfac: i0.ɵɵFactoryDeclaration<DynamicFormComponent, never>;
964
+ 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>;
965
+ }
966
+
967
+ /**
968
+ * DynamicRecordFormComponent — comprehensive tabbed record view & edit component.
969
+ * Supports cross-tab rule evaluation, profile header, summary drawer (showOnMinimize),
970
+ * interactive quick-jump links, and baseline modification tracking.
971
+ */
972
+ declare class DynamicRecordFormComponent implements OnChanges {
973
+ config: EntityFormConfig;
974
+ rules?: FormRule[];
975
+ initialData?: Record<string, any>;
976
+ userRoles: string[];
977
+ language: string;
978
+ readonly: boolean;
979
+ loading: boolean;
980
+ error: string | null;
981
+ /** Whole record read-only, regardless of RBAC. Equivalent to the reference's `isReadOnly`. */
982
+ isReadOnly: boolean;
983
+ /** Specific field ids forced read-only while the rest of the record stays editable. */
984
+ readOnlyFields: string[];
985
+ /**
986
+ * Open the record read-only, with a per-tab "Edit section" flow — one tab is edited and
987
+ * validated at a time. This is the framework's `EntityRecordComponent` model and the
988
+ * default here.
989
+ *
990
+ * Set false for a directly editable record with no view/edit distinction.
991
+ */
992
+ viewMode: boolean;
993
+ formSubmit: EventEmitter<Record<string, any>>;
994
+ formChange: EventEmitter<Record<string, any>>;
995
+ formReset: EventEmitter<void>;
996
+ /** One tab section was saved. Carries the whole record, plus which tab was edited. */
997
+ sectionSave: EventEmitter<{
998
+ tabId: string;
999
+ record: Record<string, any>;
1000
+ }>;
1001
+ dynamicFormComp?: DynamicFormComponent;
1002
+ private readonly rulesEvaluation;
1003
+ private readonly host;
1004
+ private readonly injector;
1005
+ readonly currentData: i0.WritableSignal<Record<string, any>>;
1006
+ readonly originalBaseline: i0.WritableSignal<Record<string, any>>;
1007
+ /**
1008
+ * Info banners the user has dismissed, by field id.
1009
+ *
1010
+ * The reference's contract: an info banner persists until dismissed, **not** until the
1011
+ * value changes. Dismissal is keyed by field so re-triggering a different field's rule
1012
+ * still shows. Re-armed when a different record is loaded.
1013
+ */
1014
+ private readonly dismissed;
1015
+ /**
1016
+ * Info banners from the rules engine that are still showing.
1017
+ *
1018
+ * A getter, not a `computed`: the source is `dynamicFormComp`, a ViewChild. A computed
1019
+ * evaluated before the view initialises would capture no dependency on the child's
1020
+ * `ruleResult` signal and then never recompute. Read per change-detection pass instead.
1021
+ */
1022
+ get infoBanners(): {
1023
+ fieldId: string;
1024
+ message: string;
1025
+ }[];
1026
+ dismissInfoBanner(fieldId: string): void;
1027
+ /** True when nothing in the record may be edited. */
1028
+ get recordReadOnly(): boolean;
1029
+ /**
1030
+ * A row of an `array` field is edited in a drawer, not inline in the tab panel.
1031
+ *
1032
+ * The drawer is rendered outside the tab container on purpose: the reference found that
1033
+ * building a row's FormGroup inside the tab view races the tab's own initialisation, so
1034
+ * the row form is kept detached and only pushed into the FormArray on save.
1035
+ */
1036
+ readonly inlineRowField: i0.WritableSignal<any>;
1037
+ readonly inlineRowIndex: i0.WritableSignal<number>;
1038
+ inlineRowForm: FormGroup | null;
1039
+ /** `array` fields on the tab currently shown, so the template can list their rows. */
1040
+ get arrayFieldsForActiveTab(): NestedFieldConfig[];
1041
+ /**
1042
+ * Rows currently held by an `array` field, read from the record value.
1043
+ *
1044
+ * Deliberately not read off the child's FormArray: that control only exists once the
1045
+ * child has initialised, so rendering from it changed value mid-pass. `currentData` is a
1046
+ * signal seeded from `initialData` and kept current by the child's `formChange`.
1047
+ */
1048
+ rowsOf(field: NestedFieldConfig): Record<string, unknown>[];
1049
+ /** One row rendered as text, so the list is readable without opening the drawer. */
1050
+ rowSummary(field: NestedFieldConfig, row: Record<string, unknown>): string;
1051
+ openAddRow(field: NestedFieldConfig): void;
1052
+ openEditRow(field: NestedFieldConfig, index: number): void;
1053
+ cancelRow(): void;
1054
+ /** Commit the drawer into the FormArray. Rules re-evaluate off the resulting value change. */
1055
+ saveRow(): void;
1056
+ deleteRow(field: NestedFieldConfig, index: number): void;
1057
+ /**
1058
+ * The tab currently open for editing, or null when the record is being viewed.
1059
+ *
1060
+ * The reference edits one tab section at a time rather than the whole record, and
1061
+ * validates only that section on save — a required field on an untouched tab must not
1062
+ * block saving the tab you are actually working on.
1063
+ */
1064
+ readonly editingTabId: i0.WritableSignal<string>;
1065
+ /**
1066
+ * The tab the inner form is showing.
1067
+ *
1068
+ * Tracked here rather than read off the ViewChild. The child resolves its first tab as
1069
+ * part of its own initialisation, so reading it during this component's render changed
1070
+ * value mid-pass — an ExpressionChangedAfterItHasBeenChecked error. Seeded from the
1071
+ * config and kept in step by the child's `activeTabChange` output.
1072
+ */
1073
+ private readonly activeTabIdSig;
1074
+ get activeTabId(): string | null;
1075
+ onActiveTabChange(tabId: string): void;
1076
+ /** First tab a user can see, used to seed the active tab before the child reports one. */
1077
+ private firstVisibleTabId;
1078
+ /** Errors from the last save attempt, by field id. */
1079
+ readonly sectionErrors: i0.WritableSignal<Record<string, string>>;
1080
+ /** Template access to `Object.keys` for iterating the error map. */
1081
+ protected readonly Object: ObjectConstructor;
1082
+ get isEditingActiveTab(): boolean;
1083
+ /** Fields render read-only unless their own tab is the one being edited. */
1084
+ get sectionReadOnly(): boolean;
1085
+ editSection(): void;
1086
+ /** Discard this section's edits, restoring each field from the session baseline. */
1087
+ cancelSection(): void;
1088
+ /**
1089
+ * Validate this section only, then emit it. Validation is scoped two ways: Angular
1090
+ * validity of the tab's own controls, and rules filtered to this tab — a rule targeting
1091
+ * a hidden tab must not block the save (the reference's OV0-968 fix).
1092
+ */
1093
+ saveSection(): void;
1094
+ /**
1095
+ * Seed the session baseline from the loaded record so `VALUE_CHANGED` rules and the
1096
+ * modification banner compare against the record as it was opened — not as it was
1097
+ * after the first keystroke.
1098
+ */
1099
+ ngOnChanges(changes: SimpleChanges): void;
1100
+ get recordTitle(): string;
1101
+ get avatarLetter(): string;
1102
+ /** Every field in the config, at any depth. */
1103
+ private allFields;
1104
+ /** The field flagged `isProfileImage`, rendered as the record avatar. */
1105
+ get headerProfileField(): NestedFieldConfig | null;
1106
+ /** The field flagged `isHeaderToggle`, rendered as a status switch in the header. */
1107
+ get headerToggleField(): NestedFieldConfig | null;
1108
+ /** Avatar image URL, when the profile field holds a persisted FileRef. */
1109
+ get profileImageUrl(): string | null;
1110
+ get headerToggleValue(): boolean;
1111
+ /**
1112
+ * Flip the header toggle. It writes straight to the control rather than going through
1113
+ * section editing — the reference treats it as a record-level status switch, not part of
1114
+ * any one tab's section.
1115
+ */
1116
+ toggleHeaderStatus(): void;
1117
+ /** Locate a field's control wherever it lives, since header fields may be on any tab. */
1118
+ private controlFor;
1119
+ /**
1120
+ * A field's current value, read from the record so it is stable during render.
1121
+ *
1122
+ * Resolved through `getTabData` so it honours exactly the nesting contract the form
1123
+ * patches with — `{ tabId: { fieldId } }`, or the record root when the tab sets
1124
+ * `flatData`. Reading the record flat instead is not a shortcut: it makes the summary
1125
+ * display values the form never loaded, which reads as "the data is there" while every
1126
+ * control behind it is empty.
1127
+ *
1128
+ * Walks sub-tabs as well: a summary, header, or profile field may sit on any tab at any
1129
+ * depth.
1130
+ */
1131
+ private fieldValue;
1132
+ readonly isModified: i0.Signal<boolean>;
1133
+ readonly summaryFields: i0.Signal<NestedFieldConfig[]>;
1134
+ onFormChange(data: Record<string, any>): void;
1135
+ onFormSubmit(data: Record<string, any>): void;
1136
+ onFormReset(): void;
1137
+ formatFieldLabel(field: NestedFieldConfig): string;
1138
+ /** Summary values render through the shared core formatter, not a local stringifier. */
1139
+ formatFieldValue(field: NestedFieldConfig): string;
1140
+ /**
1141
+ * Finds which tab — and, where the field lives one level down, which sub-tab — owns a
1142
+ * field. The walk used to check top-level `fields` only, so every field in a sub-tab was
1143
+ * simply not found and the jump did nothing at all.
1144
+ */
1145
+ private locateField;
1146
+ /**
1147
+ * Switches to the tab holding `fieldId`, then scrolls it into view and moves focus to it.
1148
+ *
1149
+ * The wait for the new tab to render used to be a 50 ms `setTimeout`, which was wrong three
1150
+ * ways: `document` is undefined on a server render, 50 ms is a guess that a large tab can
1151
+ * outrun, and nothing cancelled the timer if the component went away first.
1152
+ * `afterNextRender` fixes all three — it never runs on the server, it runs when the panel
1153
+ * has actually rendered rather than when a guess expires, and it is tied to the injector so
1154
+ * destroying the component cancels it. The query is scoped to this component's own element,
1155
+ * so the library no longer reaches for the global `document` at all.
1156
+ */
1157
+ jumpToField(fieldId: string): void;
1158
+ static ɵfac: i0.ɵɵFactoryDeclaration<DynamicRecordFormComponent, never>;
1159
+ 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>;
1160
+ }
1161
+
1162
+ /**
1163
+ * DynamicFieldComponent — mounts the correct field component for a given NestedFieldConfig.
1164
+ * Uses ViewContainerRef.createComponent() and passes inputs via setInput(); see
1165
+ * DynamicFieldComponentContract for the five inputs every field component receives.
1166
+ * Never add field-type-specific logic here — this component must remain generic.
1167
+ *
1168
+ * The 5 inputs (field, control, language, readonly, masked) are passed to every mounted
1169
+ * component uniformly — no special-casing per type.
1170
+ */
1171
+ declare class DynamicFieldComponent implements OnChanges, OnDestroy {
1172
+ field: NestedFieldConfig;
1173
+ control: AbstractControl;
1174
+ config: EntityFormConfig;
1175
+ currentTabId?: string;
1176
+ language: string;
1177
+ readonly: boolean;
1178
+ userRoles: string[];
1179
+ private readonly fieldHost;
1180
+ private readonly fieldRegistry;
1181
+ private readonly rbacService;
1182
+ /**
1183
+ * Passed into `createComponent` so a hosted field sees this host's injector — including
1184
+ * `DynamicFormComponent`'s scoped `EntityRefSelectionService`. Without it, a standalone
1185
+ * field resolves the root bus and `autoPatch` never runs.
1186
+ */
1187
+ private readonly injector;
1188
+ private componentRef;
1189
+ ngOnChanges(_changes: SimpleChanges): void;
1190
+ private mountField;
1191
+ /**
1192
+ * Field types are opt-in (see `provideFieldTypes` / `provideBuiltInFieldTypes`), so an
1193
+ * unregistered type renders nothing. Say so once per type in dev builds rather than
1194
+ * leaving a silently blank slot.
1195
+ */
1196
+ private warnUnresolved;
1197
+ private static readonly warnedTypes;
1198
+ /**
1199
+ * Re-check the hosted field component.
1200
+ *
1201
+ * The field components are OnPush, so they re-render on an input change, an event from
1202
+ * their own template, or a signal they read. `form.markAllAsTouched()` is none of those —
1203
+ * it flips `touched` from outside, and every field's error message depends on it. Without
1204
+ * this, a blocked submit would mark the form touched and no error would appear.
1205
+ */
1206
+ refresh(): void;
1207
+ /**
1208
+ * Re-check the hosted component whenever its control changes from outside.
1209
+ *
1210
+ * The field components are OnPush, so they re-render on an input change or an event from
1211
+ * their own template. A control mutated externally is neither — and that is not an edge
1212
+ * case: patchForm, reset, autoPatch and patchOnTrue all do exactly that. Without this, a
1213
+ * field patched from a record would keep rendering its previous value.
1214
+ *
1215
+ * `detectChanges()` rather than `markForCheck()`: a selection that arrives from a native
1216
+ * `change` event (or Playwright) may not be followed by another Angular tick, so a dirty
1217
+ * flag would never be flushed. Running CD on the hosted component now is the copy showing up.
1218
+ *
1219
+ * Done here rather than in each of the eighteen field components: the host owns the
1220
+ * component reference, so one subscription covers every type including custom ones.
1221
+ */
1222
+ private watchControl;
1223
+ private controlSub?;
1224
+ ngOnDestroy(): void;
1225
+ /**
1226
+ * Pass all 5 contract inputs via ComponentRef.setInput() — uniform for all types.
1227
+ * The public shape is DynamicFieldComponentContract.
1228
+ * Must use setInput(), not property assignment: the field components declare inputs with
1229
+ * definite assignment and no initializer, and the lib compiles with
1230
+ * useDefineForClassFields:false, so `'field' in instance` is false until first set.
1231
+ */
1232
+ private setInputs;
1233
+ static ɵfac: i0.ɵɵFactoryDeclaration<DynamicFieldComponent, never>;
1234
+ 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>;
1235
+ }
1236
+
1237
+ /**
1238
+ * What a field component must accept to be registered with `provideFieldTypes`.
1239
+ *
1240
+ * The renderer instantiates a field component dynamically and assigns exactly these five
1241
+ * inputs — no more, no less — so any component satisfying this interface can stand in for a
1242
+ * built-in type or add a new one.
1243
+ *
1244
+ * Implement it explicitly and TypeScript will tell you when you have missed one:
1245
+ *
1246
+ * ```typescript
1247
+ * @Component({
1248
+ * selector: 'app-signature-field',
1249
+ * standalone: true,
1250
+ * template: `<canvas [attr.aria-label]="field.id"></canvas>`,
1251
+ * })
1252
+ * export class SignatureFieldComponent implements DynamicFieldComponentContract {
1253
+ * @Input() field!: NestedFieldConfig;
1254
+ * @Input() control!: AbstractControl;
1255
+ * @Input() language = 'en';
1256
+ * @Input() readonly = false;
1257
+ * @Input() masked = false;
1258
+ * }
1259
+ *
1260
+ * provideFieldTypes({ signature: SignatureFieldComponent });
1261
+ * ```
1262
+ *
1263
+ * Two implementation notes that are easy to get wrong:
1264
+ *
1265
+ * - Declare the inputs with definite assignment (`field!: …`) or an initialiser. The
1266
+ * renderer sets them through `ComponentRef.setInput()` after construction, so they are
1267
+ * genuinely undefined until the first assignment.
1268
+ * - `masked` means the value must not be displayed. Render a placeholder rather than the
1269
+ * control's value. This is presentational: the real value stays in the control and is
1270
+ * included in the submitted record, so it is not an access-control boundary.
1271
+ */
1272
+ interface DynamicFieldComponentContract {
1273
+ /** The field's schema entry: id, type, label, validators, options, and so on. */
1274
+ field: NestedFieldConfig;
1275
+ /** The reactive control backing this field. A `FormGroup` for `group`, `FormArray` for `array`. */
1276
+ control: AbstractControl;
1277
+ /** Active language, for resolving `LocalizedText` labels, placeholders and options. */
1278
+ language: string;
1279
+ /** Render the value as text rather than an editable control. */
1280
+ readonly: boolean;
1281
+ /** Hide the value from display. See the note above — presentational only. */
1282
+ masked: boolean;
1283
+ }
1284
+
1285
+ declare class TextFieldComponent implements OnDestroy {
1286
+ private readonly messages;
1287
+ private displaySub?;
1288
+ field: NestedFieldConfig;
1289
+ set control(value: AbstractControl);
1290
+ get control(): AbstractControl;
1291
+ private _control;
1292
+ language: string;
1293
+ readonly: boolean;
1294
+ masked: boolean;
1295
+ /** Readonly markup reads this signal so an external patch (autoPatch) is visible under OnPush. */
1296
+ readonly displayValue: i0.WritableSignal<unknown>;
1297
+ ngOnDestroy(): void;
1298
+ get label(): string;
1299
+ get placeholder(): string;
1300
+ get errorMessage(): string;
1301
+ static ɵfac: i0.ɵɵFactoryDeclaration<TextFieldComponent, never>;
1302
+ 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>;
1303
+ }
1304
+
1305
+ declare class TextareaFieldComponent {
1306
+ field: NestedFieldConfig;
1307
+ control: AbstractControl;
1308
+ language: string;
1309
+ readonly: boolean;
1310
+ masked: boolean;
1311
+ get label(): string;
1312
+ get placeholder(): string;
1313
+ static ɵfac: i0.ɵɵFactoryDeclaration<TextareaFieldComponent, never>;
1314
+ 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>;
1315
+ }
1316
+
1317
+ declare class NumberFieldComponent {
1318
+ private readonly messages;
1319
+ field: NestedFieldConfig;
1320
+ control: AbstractControl;
1321
+ language: string;
1322
+ readonly: boolean;
1323
+ masked: boolean;
1324
+ get label(): string;
1325
+ get placeholder(): string;
1326
+ get errorMessage(): string;
1327
+ static ɵfac: i0.ɵɵFactoryDeclaration<NumberFieldComponent, never>;
1328
+ 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>;
1329
+ }
1330
+
1331
+ /** Currency field: number input with locale-aware currency symbol prefix. */
1332
+ declare class CurrencyFieldComponent {
1333
+ field: NestedFieldConfig;
1334
+ control: AbstractControl;
1335
+ language: string;
1336
+ readonly: boolean;
1337
+ masked: boolean;
1338
+ get label(): string;
1339
+ get placeholder(): string;
1340
+ /** Picks the currency symbol from the locale — defaults to '$'. */
1341
+ get symbol(): string;
1342
+ static ɵfac: i0.ɵɵFactoryDeclaration<CurrencyFieldComponent, never>;
1343
+ static ɵcmp: i0.ɵɵComponentDeclaration<CurrencyFieldComponent, "ngx-currency-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>;
1344
+ }
1345
+
1346
+ /** Email field: type="email" input with inline validation hint. */
1347
+ declare class EmailFieldComponent {
1348
+ field: NestedFieldConfig;
1349
+ control: AbstractControl;
1350
+ language: string;
1351
+ readonly: boolean;
1352
+ masked: boolean;
1353
+ get label(): string;
1354
+ get placeholder(): string;
1355
+ static ɵfac: i0.ɵɵFactoryDeclaration<EmailFieldComponent, never>;
1356
+ static ɵcmp: i0.ɵɵComponentDeclaration<EmailFieldComponent, "ngx-email-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>;
1357
+ }
1358
+
1359
+ /** Password field: masked input with show/hide eye toggle. */
1360
+ declare class PasswordFieldComponent {
1361
+ field: NestedFieldConfig;
1362
+ control: AbstractControl;
1363
+ language: string;
1364
+ readonly: boolean;
1365
+ masked: boolean;
1366
+ readonly visible: i0.WritableSignal<boolean>;
1367
+ get label(): string;
1368
+ get placeholder(): string;
1369
+ toggleVisibility(): void;
1370
+ static ɵfac: i0.ɵɵFactoryDeclaration<PasswordFieldComponent, never>;
1371
+ 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>;
1372
+ }
1373
+
1374
+ declare class CheckboxFieldComponent {
1375
+ field: NestedFieldConfig;
1376
+ control: AbstractControl;
1377
+ language: string;
1378
+ readonly: boolean;
1379
+ masked: boolean;
1380
+ get label(): string;
1381
+ static ɵfac: i0.ɵɵFactoryDeclaration<CheckboxFieldComponent, never>;
1382
+ static ɵcmp: i0.ɵɵComponentDeclaration<CheckboxFieldComponent, "ngx-checkbox-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>;
1383
+ }
1384
+
1385
+ /**
1386
+ * Boolean field: a slide-toggle style switch (true/false).
1387
+ * Distinct from `checkbox` (which is a standalone bool) in that it renders as a
1388
+ * toggle switch with Yes/No labels for clearer binary state communication.
1389
+ */
1390
+ declare class BooleanFieldComponent {
1391
+ field: NestedFieldConfig;
1392
+ control: AbstractControl;
1393
+ language: string;
1394
+ readonly: boolean;
1395
+ masked: boolean;
1396
+ get label(): string;
1397
+ static ɵfac: i0.ɵɵFactoryDeclaration<BooleanFieldComponent, never>;
1398
+ static ɵcmp: i0.ɵɵComponentDeclaration<BooleanFieldComponent, "ngx-boolean-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>;
1399
+ }
1400
+
1401
+ declare class DateFieldComponent {
1402
+ field: NestedFieldConfig;
1403
+ control: AbstractControl;
1404
+ language: string;
1405
+ readonly: boolean;
1406
+ masked: boolean;
1407
+ get label(): string;
1408
+ /** ISO 8601 date → locale display string (ONEHERMES convention: always UTC, display in user locale) */
1409
+ formatDate(value: string | null): string;
1410
+ static ɵfac: i0.ɵɵFactoryDeclaration<DateFieldComponent, never>;
1411
+ static ɵcmp: i0.ɵɵComponentDeclaration<DateFieldComponent, "ngx-date-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>;
1412
+ }
1413
+
1414
+ /**
1415
+ * Date & Time field: a single `datetime-local` input.
1416
+ *
1417
+ * The type is stored as ISO 8601 UTC, per the same convention `date` follows, while the
1418
+ * input works in the viewer's local zone — `datetime-local` has no offset, so the two
1419
+ * conversions here are what keep a value stable across a round trip.
1420
+ */
1421
+ declare class DateTimeFieldComponent {
1422
+ field: NestedFieldConfig;
1423
+ control: AbstractControl;
1424
+ language: string;
1425
+ readonly: boolean;
1426
+ masked: boolean;
1427
+ get label(): string;
1428
+ /** Stored ISO 8601 → the `YYYY-MM-DDTHH:mm` local form the input requires. */
1429
+ get inputValue(): string;
1430
+ /** The input's local `YYYY-MM-DDTHH:mm` → stored ISO 8601 UTC. */
1431
+ onInput(value: string): void;
1432
+ /**
1433
+ * Displays date *and* time. `DateFieldComponent` used `toLocaleDateString()` here, so a
1434
+ * datetime read back through the field showed no time while the same value in the record
1435
+ * summary — which goes through core's `formatDisplayValue` — showed it.
1436
+ */
1437
+ formatDateTime(value: unknown): string;
1438
+ static ɵfac: i0.ɵɵFactoryDeclaration<DateTimeFieldComponent, never>;
1439
+ 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>;
1440
+ }
1441
+
1442
+ /**
1443
+ * Time field: a bare time of day, with no date and no zone attached.
1444
+ *
1445
+ * Stored as `HH:mm` — exactly the value `<input type="time">` reads and writes, so the
1446
+ * control binds straight through with no conversion. That is the whole reason this is a
1447
+ * separate type from `datetime`: a recurring 09:00 opening time is not a moment in time,
1448
+ * and converting it to UTC would move it whenever the offset changed.
1449
+ */
1450
+ declare class TimeFieldComponent {
1451
+ field: NestedFieldConfig;
1452
+ control: AbstractControl;
1453
+ language: string;
1454
+ readonly: boolean;
1455
+ masked: boolean;
1456
+ get label(): string;
1457
+ /**
1458
+ * Defers to core rather than parsing `HH:mm` locally. The other date-ish components each
1459
+ * grew their own formatter, and `datetime` ended up showing one thing in the field and
1460
+ * another in the record summary because the two drifted apart.
1461
+ */
1462
+ formatTime(value: unknown): string;
1463
+ static ɵfac: i0.ɵɵFactoryDeclaration<TimeFieldComponent, never>;
1464
+ 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>;
1465
+ }
1466
+
1467
+ /**
1468
+ * Month-Year field: two linked selects (Month + Year).
1469
+ * Value stored as ISO partial date string "YYYY-MM" for unambiguous storage.
1470
+ */
1471
+ declare class MonthYearFieldComponent {
1472
+ field: NestedFieldConfig;
1473
+ control: AbstractControl;
1474
+ language: string;
1475
+ readonly: boolean;
1476
+ masked: boolean;
1477
+ readonly months: {
1478
+ value: string;
1479
+ label: string;
1480
+ }[];
1481
+ readonly years: number[];
1482
+ get label(): string;
1483
+ /** Parses "YYYY-MM" or "YYYY-MM-DD" stored value → month part. */
1484
+ get selectedMonth(): string;
1485
+ get selectedYear(): string;
1486
+ onMonthChange(month: string): void;
1487
+ onYearChange(year: string): void;
1488
+ formatValue(value: unknown): string;
1489
+ static ɵfac: i0.ɵɵFactoryDeclaration<MonthYearFieldComponent, never>;
1490
+ static ɵcmp: i0.ɵɵComponentDeclaration<MonthYearFieldComponent, "ngx-month-year-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>;
1491
+ }
1492
+
1493
+ declare class DropdownFieldComponent {
1494
+ private readonly lookups;
1495
+ private readonly messages;
1496
+ private _field;
1497
+ private _language;
1498
+ /** Setter-based so options resolve however the input is set — see `refreshChoiceOptions`. */
1499
+ set field(value: NestedFieldConfig);
1500
+ get field(): NestedFieldConfig;
1501
+ set language(value: string);
1502
+ get language(): string;
1503
+ control: AbstractControl;
1504
+ readonly: boolean;
1505
+ masked: boolean;
1506
+ /** Inline `options`, or the field's named list resolved through the registry (§6.3). */
1507
+ readonly options: i0.WritableSignal<DropdownOption[]>;
1508
+ readonly compareFn: (o1: unknown, o2: unknown) => boolean;
1509
+ isObjectVal(option: DropdownOption): boolean;
1510
+ get label(): string;
1511
+ get placeholder(): string;
1512
+ getOptStoredVal(option: DropdownOption): unknown;
1513
+ getOptLabel(option: DropdownOption): string;
1514
+ /**
1515
+ * Read-only display. Never awaits (§6.2): a warm list resolves the label, a cold one falls
1516
+ * back to the stored text — which under the §2 contract *is* the display value, so it is
1517
+ * never wrong, only unlocalised.
1518
+ */
1519
+ getLabel(value: unknown): string;
1520
+ get errorMessage(): string;
1521
+ static ɵfac: i0.ɵɵFactoryDeclaration<DropdownFieldComponent, never>;
1522
+ static ɵcmp: i0.ɵɵComponentDeclaration<DropdownFieldComponent, "ngx-dropdown-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>;
1523
+ }
1524
+
1525
+ /** Radio field: a group of radio buttons built from field.options. */
1526
+ declare class RadioFieldComponent {
1527
+ private readonly lookups;
1528
+ private _field;
1529
+ private _language;
1530
+ /** Setter-based so options resolve however the input is set — see `refreshChoiceOptions`. */
1531
+ set field(value: NestedFieldConfig);
1532
+ get field(): NestedFieldConfig;
1533
+ set language(value: string);
1534
+ get language(): string;
1535
+ control: AbstractControl;
1536
+ readonly: boolean;
1537
+ masked: boolean;
1538
+ /** Inline `options`, or the field's named list resolved through the registry (§6.3). */
1539
+ readonly options: i0.WritableSignal<DropdownOption[]>;
1540
+ get label(): string;
1541
+ getOptStoredVal(option: DropdownOption): unknown;
1542
+ /**
1543
+ * Stable DOM id for one radio, so the <label for> association works.
1544
+ * Slugified because the option text is the value now — "On Leave" must not put a space
1545
+ * into an id attribute.
1546
+ */
1547
+ getRadioId(option: DropdownOption): string;
1548
+ getOptVal(option: DropdownOption): string | number | boolean;
1549
+ getOptLabel(option: DropdownOption): string;
1550
+ /** Read-only display — synchronous, per §6.2. See `DropdownFieldComponent.getLabel`. */
1551
+ getSelectedLabel(): string;
1552
+ static ɵfac: i0.ɵɵFactoryDeclaration<RadioFieldComponent, never>;
1553
+ 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>;
1554
+ }
1555
+
1556
+ declare class MultiSelectFieldComponent {
1557
+ private readonly lookups;
1558
+ private _field;
1559
+ private _language;
1560
+ /** Setter-based so options resolve however the input is set — see `refreshChoiceOptions`. */
1561
+ set field(value: NestedFieldConfig);
1562
+ get field(): NestedFieldConfig;
1563
+ set language(value: string);
1564
+ get language(): string;
1565
+ control: AbstractControl;
1566
+ readonly: boolean;
1567
+ masked: boolean;
1568
+ /** Inline `options`, or the field's named list resolved through the registry (§6.3). */
1569
+ readonly options: i0.WritableSignal<DropdownOption[]>;
1570
+ readonly compareFn: (o1: unknown, o2: unknown) => boolean;
1571
+ isObjectVal(option: DropdownOption): boolean;
1572
+ get label(): string;
1573
+ getOptStoredVal(option: DropdownOption): unknown;
1574
+ getOptLabel(option: DropdownOption): string;
1575
+ /** Read-only display — synchronous, per §6.2. See `DropdownFieldComponent.getLabel`. */
1576
+ getLabels(values: unknown): string;
1577
+ static ɵfac: i0.ɵɵFactoryDeclaration<MultiSelectFieldComponent, never>;
1578
+ 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>;
1579
+ }
1580
+
1581
+ /**
1582
+ * EntityRefFieldComponent — a select populated from a consumer-registered loader.
1583
+ *
1584
+ * Cascades: when `entityReference.parentField` is set, the component watches that sibling
1585
+ * control (via `control.parent`) and reloads its own options on every parent change,
1586
+ * clearing a now-invalid selection. It publishes the picked record on
1587
+ * `EntityRefSelectionService` so the owning form can run `autoPatch`.
1588
+ */
1589
+ declare class EntityRefFieldComponent implements OnInit {
1590
+ field: NestedFieldConfig;
1591
+ control: AbstractControl;
1592
+ language: string;
1593
+ readonly: boolean;
1594
+ masked: boolean;
1595
+ private readonly cascade;
1596
+ private readonly selectionBus;
1597
+ private readonly destroyRef;
1598
+ readonly options: i0.WritableSignal<ReferenceOption[]>;
1599
+ readonly loading: i0.WritableSignal<boolean>;
1600
+ /** True when this field cascades but its parent has no value yet. */
1601
+ readonly awaitingParent: i0.WritableSignal<boolean>;
1602
+ get label(): string;
1603
+ get placeholder(): string;
1604
+ get parentFieldId(): string | undefined;
1605
+ ngOnInit(): void;
1606
+ /** Reload options against the parent's current value. */
1607
+ reload(): Promise<void>;
1608
+ /**
1609
+ * Publish the selected option so autoPatch can copy from its record.
1610
+ *
1611
+ * Driven from `valueChanges` rather than the select's `change` event: that event is the
1612
+ * same one the `formControl` directive listens to, so reading the control inside a
1613
+ * template `(change)` handler can see the previous value. `valueChanges` fires after the
1614
+ * model has updated — including Playwright `selectOption`, which updates the control
1615
+ * through the value accessor.
1616
+ */
1617
+ onSelectionChange(value?: string): void;
1618
+ private publishSelection;
1619
+ getLabel(value: any): string;
1620
+ private parentValue;
1621
+ /** Reload (and drop a stale selection) whenever the parent control changes. */
1622
+ private watchParent;
1623
+ static ɵfac: i0.ɵɵFactoryDeclaration<EntityRefFieldComponent, never>;
1624
+ static ɵcmp: i0.ɵɵComponentDeclaration<EntityRefFieldComponent, "ngx-entity-ref-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>;
1625
+ }
1626
+
1627
+ /** Renders a nested fieldset container for a group field's children. */
1628
+ declare class GroupFieldComponent {
1629
+ field: NestedFieldConfig;
1630
+ control: AbstractControl;
1631
+ config: EntityFormConfig;
1632
+ language: string;
1633
+ readonly: boolean;
1634
+ masked: boolean;
1635
+ userRoles: string[];
1636
+ get label(): string;
1637
+ getChildControl(childId: string): AbstractControl;
1638
+ static ɵfac: i0.ɵɵFactoryDeclaration<GroupFieldComponent, never>;
1639
+ static ɵcmp: i0.ɵɵComponentDeclaration<GroupFieldComponent, "ngx-group-field", never, { "field": { "alias": "field"; "required": false; }; "control": { "alias": "control"; "required": false; }; "config": { "alias": "config"; "required": false; }; "language": { "alias": "language"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "masked": { "alias": "masked"; "required": false; }; "userRoles": { "alias": "userRoles"; "required": false; }; }, {}, never, never, true, never>;
1640
+ }
1641
+
1642
+ /** Renders repeating rows for a FormArray, with Add Item and Remove. */
1643
+ declare class ArrayFieldComponent {
1644
+ /**
1645
+ * addItem/removeItem are public and may be called from outside this component's template,
1646
+ * which under OnPush would not re-render. The host also watches the control, but this
1647
+ * keeps the component correct on its own.
1648
+ */
1649
+ private readonly cdr;
1650
+ field: NestedFieldConfig;
1651
+ control: AbstractControl;
1652
+ config: EntityFormConfig;
1653
+ language: string;
1654
+ readonly: boolean;
1655
+ masked: boolean;
1656
+ userRoles: string[];
1657
+ private readonly fb;
1658
+ private readonly validatorRegistry;
1659
+ get label(): string;
1660
+ get formArray(): FormArray;
1661
+ getItemChildControl(itemGroup: AbstractControl, childId: string): AbstractControl;
1662
+ addItem(): void;
1663
+ removeItem(index: number): void;
1664
+ static ɵfac: i0.ɵɵFactoryDeclaration<ArrayFieldComponent, never>;
1665
+ static ɵcmp: i0.ɵɵComponentDeclaration<ArrayFieldComponent, "ngx-array-field", never, { "field": { "alias": "field"; "required": false; }; "control": { "alias": "control"; "required": false; }; "config": { "alias": "config"; "required": false; }; "language": { "alias": "language"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "masked": { "alias": "masked"; "required": false; }; "userRoles": { "alias": "userRoles"; "required": false; }; }, {}, never, never, true, never>;
1666
+ }
1667
+
1668
+ /**
1669
+ * Image field: preview thumbnail + upload button.
1670
+ *
1671
+ * With `UPLOAD_HANDLER` registered the file is persisted and stored as `{ url }`; without one
1672
+ * it is stored as `{ file }` and previewed from an object URL (revoked when replaced/destroyed).
1673
+ */
1674
+ declare class ImageFieldComponent implements OnDestroy {
1675
+ field: NestedFieldConfig;
1676
+ control: AbstractControl;
1677
+ language: string;
1678
+ readonly: boolean;
1679
+ masked: boolean;
1680
+ private readonly uploads;
1681
+ readonly uploading: i0.WritableSignal<boolean>;
1682
+ readonly uploadError: i0.WritableSignal<string>;
1683
+ /** Cached so an object URL is minted once per File, not once per change-detection pass. */
1684
+ private previewSource;
1685
+ private previewCache;
1686
+ get label(): string;
1687
+ previewUrl(): string | null;
1688
+ onFileSelect(file: File | undefined): Promise<void>;
1689
+ remove(): void;
1690
+ ngOnDestroy(): void;
1691
+ static ɵfac: i0.ɵɵFactoryDeclaration<ImageFieldComponent, never>;
1692
+ static ɵcmp: i0.ɵɵComponentDeclaration<ImageFieldComponent, "ngx-image-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>;
1693
+ }
1694
+
1695
+ /**
1696
+ * File field: file input with filename display and download link.
1697
+ * Shares the `FileRef` / `UPLOAD_HANDLER` contract with image-field via FileUploadService.
1698
+ */
1699
+ declare class FileFieldComponent {
1700
+ field: NestedFieldConfig;
1701
+ control: AbstractControl;
1702
+ language: string;
1703
+ readonly: boolean;
1704
+ masked: boolean;
1705
+ private readonly uploads;
1706
+ readonly uploading: i0.WritableSignal<boolean>;
1707
+ readonly uploadError: i0.WritableSignal<string>;
1708
+ get label(): string;
1709
+ private get value();
1710
+ fileUrl(): string | null;
1711
+ fileName(): string | null;
1712
+ onFileSelect(file: File | undefined): Promise<void>;
1713
+ remove(): void;
1714
+ static ɵfac: i0.ɵɵFactoryDeclaration<FileFieldComponent, never>;
1715
+ static ɵcmp: i0.ɵɵComponentDeclaration<FileFieldComponent, "ngx-file-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>;
1716
+ }
1717
+
1718
+ export { ASYNC_VALIDATOR_REGISTRY, ArrayFieldComponent, BooleanFieldComponent, COMMON_MODULES_REGISTRY, CONFIG_SOURCE, CascadeDataService, CheckboxFieldComponent, ConfigSourceService, CurrencyFieldComponent, DEFAULT_VALIDATION_MESSAGES, DateFieldComponent, DateTimeFieldComponent, DropdownFieldComponent, DynamicFieldComponent, DynamicFormComponent, DynamicRecordFormComponent, ENTITY_REF_CACHE_STORE, ENTITY_REF_REGISTRY, EmailFieldComponent, EntityRefFieldComponent, EntityRefRegistryService, EntityRefSelectionService, EntityReferenceService, FIELD_TYPE_REGISTRY, FIELD_TYPE_SETS, FieldRegistryService, FileFieldComponent, GroupFieldComponent, HOOK_REGISTRY, HookRegistryService, ImageFieldComponent, InMemoryEntityRefCacheStore, LOOKUP_REGISTRY, LookupRegistryService, MASKED_ROLES, MonthYearFieldComponent, MultiSelectFieldComponent, NumberFieldComponent, PasswordFieldComponent, RECORD_MIGRATIONS, RadioFieldComponent, RbacService, RulesEvaluationService, SYSTEM_DEFAULT_CAN_EDIT, TextFieldComponent, TextareaFieldComponent, TimeFieldComponent, UPLOAD_HANDLER, VALIDATION_MESSAGES, VALIDATOR_REGISTRY, ValidationMessagesService, ValidatorRegistryService, builtInFieldTypes, provideBuiltInFieldTypes, provideFieldTypes, provideNgxDynamicEntity, refreshChoiceOptions };
1719
+ export type { ChoiceOptionHost, ConfigSourceHandler, DynamicFieldComponentContract, EntityRefCacheStore, EntityRefSelection, HookFn, NgxDynamicEntityConfig, ValidationMessage };