ngx-dynamic-entity 1.13.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/ngx-dynamic-entity.mjs +2224 -303
- package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
- package/package.json +2 -2
- package/types/ngx-dynamic-entity.d.ts +802 -58
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Type, EnvironmentProviders, WritableSignal, OnInit, OnChanges, OnDestroy, EventEmitter, SimpleChanges } from '@angular/core';
|
|
3
|
-
import { ReferenceOption
|
|
2
|
+
import { InjectionToken, Type, EnvironmentProviders, WritableSignal, OnInit, OnChanges, OnDestroy, EventEmitter, SimpleChanges, Provider } from '@angular/core';
|
|
3
|
+
import { ReferenceOption, LocalizedText, EntityFormConfig, FormRule, ImportLookups, MappingPlan, ImportResult, TemplateSpec, SheetData, CommonModuleEntry, EntityReferenceLoader, LookupListSource, RecordMigration, FileUploadHandler, FieldValidators, NestedFieldConfig, ReferenceLoaderContext, DropdownOption, LookupListValue, NestedTabConfig, RuleEvaluationResult, FieldScopeEntry, EvaluateFormRulesOptions, ImportColumn, UnsupportedColumn, ImportRowError } from '@dynamic-entity/core';
|
|
4
4
|
export { AutoPatchConfig, CommonModuleEntry, ComponentClass, 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
|
|
5
|
+
import { ValidatorFn, AsyncValidatorFn, AbstractControl, ValidationErrors, FormGroup, FormArray } from '@angular/forms';
|
|
6
6
|
import * as rxjs from 'rxjs';
|
|
7
|
+
import * as ngx_dynamic_entity from 'ngx-dynamic-entity';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Backing store for cached reference options.
|
|
@@ -15,8 +16,8 @@ import * as rxjs from 'rxjs';
|
|
|
15
16
|
* that must not silently persist a tenant's data.
|
|
16
17
|
*/
|
|
17
18
|
interface EntityRefCacheStore {
|
|
18
|
-
get(key: string): ReferenceOption
|
|
19
|
-
set(key: string, value: ReferenceOption
|
|
19
|
+
get(key: string): ReferenceOption[] | undefined;
|
|
20
|
+
set(key: string, value: ReferenceOption[]): void;
|
|
20
21
|
delete(key: string): void;
|
|
21
22
|
/** Every key currently held — used for prefix invalidation of one entity. */
|
|
22
23
|
keys(): string[];
|
|
@@ -25,8 +26,8 @@ interface EntityRefCacheStore {
|
|
|
25
26
|
/** Default in-memory store. Lives as long as the injector that created it. */
|
|
26
27
|
declare class InMemoryEntityRefCacheStore implements EntityRefCacheStore {
|
|
27
28
|
private readonly map;
|
|
28
|
-
get(key: string): ReferenceOption
|
|
29
|
-
set(key: string, value: ReferenceOption
|
|
29
|
+
get(key: string): ReferenceOption[] | undefined;
|
|
30
|
+
set(key: string, value: ReferenceOption[]): void;
|
|
30
31
|
delete(key: string): void;
|
|
31
32
|
keys(): string[];
|
|
32
33
|
clear(): void;
|
|
@@ -95,6 +96,40 @@ declare const DEFAULT_UI_TEXT: {
|
|
|
95
96
|
criticalFieldUnlockHint: string;
|
|
96
97
|
dismissBanner: string;
|
|
97
98
|
jumpToField: string;
|
|
99
|
+
importUpload: string;
|
|
100
|
+
importMap: string;
|
|
101
|
+
importReview: string;
|
|
102
|
+
importDone: string;
|
|
103
|
+
importChooseFile: string;
|
|
104
|
+
importFileChosen: string;
|
|
105
|
+
importReading: string;
|
|
106
|
+
importNeedFile: string;
|
|
107
|
+
importTemplateHeading: string;
|
|
108
|
+
importTemplateExplain: string;
|
|
109
|
+
importTemplateDownload: string;
|
|
110
|
+
importTemplateSelectAll: string;
|
|
111
|
+
importTemplateSelectNone: string;
|
|
112
|
+
importTemplateUnsupported: string;
|
|
113
|
+
importColumnInFile: string;
|
|
114
|
+
importTargetField: string;
|
|
115
|
+
importUnmapped: string;
|
|
116
|
+
importGuessed: string;
|
|
117
|
+
importMappedCount: string;
|
|
118
|
+
importRequiredUnmapped: string;
|
|
119
|
+
importPreviewHeading: string;
|
|
120
|
+
importRowRejected: string;
|
|
121
|
+
importRunImport: string;
|
|
122
|
+
importImporting: string;
|
|
123
|
+
importSucceeded: string;
|
|
124
|
+
importSkippedRows: string;
|
|
125
|
+
importFailedRows: string;
|
|
126
|
+
importErrorsTruncated: string;
|
|
127
|
+
importStartOver: string;
|
|
128
|
+
importErrorsHeading: string;
|
|
129
|
+
importErrorRow: string;
|
|
130
|
+
importErrorField: string;
|
|
131
|
+
importErrorReason: string;
|
|
132
|
+
importPlanProblem: string;
|
|
98
133
|
entityLabel: string;
|
|
99
134
|
criticalFieldChanged: string;
|
|
100
135
|
recordModified: string;
|
|
@@ -162,6 +197,102 @@ declare class UiTextService {
|
|
|
162
197
|
static ɵprov: i0.ɵɵInjectableDeclaration<UiTextService>;
|
|
163
198
|
}
|
|
164
199
|
|
|
200
|
+
/**
|
|
201
|
+
* import-contracts.ts — the two seams the import wizard hangs on.
|
|
202
|
+
*
|
|
203
|
+
* Both follow the pattern the rest of this package already uses for anything the library
|
|
204
|
+
* cannot decide for a consumer — `FileUploadHandler`, `EntityReferenceLoader`,
|
|
205
|
+
* `LookupListLoader`: a function type here, a token to register it against, and a default
|
|
206
|
+
* that works without registering anything.
|
|
207
|
+
*
|
|
208
|
+
* **Why two rather than one.** Reading a file and deciding where the work happens are
|
|
209
|
+
* different questions with different answers. A consumer may want `.xlsx` support in a
|
|
210
|
+
* wizard that still runs entirely in the browser, or plain CSV that is nonetheless processed
|
|
211
|
+
* on a server because their files have fifty thousand rows. Collapsing them into one option
|
|
212
|
+
* would make each answer imply the other.
|
|
213
|
+
*/
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Reads an uploaded file into headers and positional rows.
|
|
217
|
+
*
|
|
218
|
+
* Positional (`string[][]`), not keyed by header, because a real sheet carries duplicate
|
|
219
|
+
* headers and blank ones — and because a mapping entry addresses a column by index for
|
|
220
|
+
* exactly that reason. A parser that returned objects keyed by header would silently lose
|
|
221
|
+
* the second of two columns both called "Notes".
|
|
222
|
+
*
|
|
223
|
+
* The library ships a CSV implementation and no more. Registering one that understands
|
|
224
|
+
* `.xlsx` is how a consumer adds it, and which spreadsheet library they use stays their
|
|
225
|
+
* choice — this package has no runtime dependency and gains none here.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* provideNgxDynamicEntity({
|
|
229
|
+
* sheetParser: async file => {
|
|
230
|
+
* const workbook = XLSX.read(await file.arrayBuffer());
|
|
231
|
+
* const rows = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]], {
|
|
232
|
+
* header: 1, raw: false, defval: '',
|
|
233
|
+
* }) as string[][];
|
|
234
|
+
* return { headers: rows[0] ?? [], rows: rows.slice(1) };
|
|
235
|
+
* },
|
|
236
|
+
* })
|
|
237
|
+
*/
|
|
238
|
+
type SheetParser = (file: File) => SheetData | Promise<SheetData>;
|
|
239
|
+
/** The formats a generated template can be written in. CSV needs no dependency. */
|
|
240
|
+
type TemplateFormat = 'csv' | 'xlsx';
|
|
241
|
+
/** What the wizard needs to know about the entity it is importing into. */
|
|
242
|
+
interface ImportContext {
|
|
243
|
+
config: EntityFormConfig;
|
|
244
|
+
/**
|
|
245
|
+
* Rules that apply to this entity.
|
|
246
|
+
*
|
|
247
|
+
* Without them an import checks field validators only, which is **not** what the form
|
|
248
|
+
* enforces: a rule can attach an error, and a rule can hide a field, which must relax
|
|
249
|
+
* `required`. Pass the same rules the form is rendered with or the two disagree.
|
|
250
|
+
*/
|
|
251
|
+
rules?: readonly FormRule[];
|
|
252
|
+
/** Values for any `listName` field, already resolved — core cannot reach `LOOKUP_REGISTRY`. */
|
|
253
|
+
lookups?: ImportLookups;
|
|
254
|
+
lang?: string;
|
|
255
|
+
}
|
|
256
|
+
/** What a first look at an uploaded file yields. */
|
|
257
|
+
interface ImportPreview {
|
|
258
|
+
headers: string[];
|
|
259
|
+
/** The first few rows, for the mapping screen to show under each column. */
|
|
260
|
+
sample: string[][];
|
|
261
|
+
/** Where the suggester got to. Every inferred match is tagged `guess`. */
|
|
262
|
+
suggestion: MappingPlan;
|
|
263
|
+
/** Total data rows in the file, which may be far more than `sample` holds. */
|
|
264
|
+
rowCount: number;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Where the work happens.
|
|
268
|
+
*
|
|
269
|
+
* The default implementation does everything in the browser with `@dynamic-entity/core` and
|
|
270
|
+
* never touches the network, so the wizard is fully usable with no backend at all. Register
|
|
271
|
+
* an implementation that posts to a server and the same components drive it instead — the
|
|
272
|
+
* engine underneath is the same code either way, which is the point of it being pure.
|
|
273
|
+
*
|
|
274
|
+
* This is the real answer to "should parsing happen on the client or the server?": both,
|
|
275
|
+
* chosen at registration, without a second implementation of the mapping rules.
|
|
276
|
+
*/
|
|
277
|
+
interface ImportTransport {
|
|
278
|
+
/** Headers, a sample, and a suggested mapping for a freshly chosen file. */
|
|
279
|
+
preview(file: File, context: ImportContext): Promise<ImportPreview>;
|
|
280
|
+
/** Apply a plan to the whole file. */
|
|
281
|
+
commit(file: File, plan: MappingPlan, context: ImportContext): Promise<ImportResult>;
|
|
282
|
+
/**
|
|
283
|
+
* Render a template for download.
|
|
284
|
+
*
|
|
285
|
+
* `fields` is the selection the user actually made — refs with row numbers stripped, one per
|
|
286
|
+
* choice — and `spec` is that selection already expanded against the config. A transport that
|
|
287
|
+
* renders locally wants the spec; one that asks a server wants the selection, because the
|
|
288
|
+
* server has the config and only the choice needs to cross the wire. Passing both means
|
|
289
|
+
* neither has to reconstruct the other: the HTTP transport used to recover the selection from
|
|
290
|
+
* the expanded spec, which worked only because `stripIndices` happens to be exactly the
|
|
291
|
+
* inverse of the expansion. Optional, so an existing implementation keeps compiling.
|
|
292
|
+
*/
|
|
293
|
+
template(spec: TemplateSpec, format: TemplateFormat, context: ImportContext, fields?: readonly string[]): Promise<Blob>;
|
|
294
|
+
}
|
|
295
|
+
|
|
165
296
|
/** Roles that see XXXXXXXXX for masked fields. Presentational only — enforce authz server-side. */
|
|
166
297
|
declare const MASKED_ROLES: InjectionToken<string[]>;
|
|
167
298
|
/**
|
|
@@ -279,6 +410,46 @@ declare const COMMON_MODULES_REGISTRY: InjectionToken<CommonModuleEntry[]>;
|
|
|
279
410
|
* { provide: UPLOAD_HANDLER, useFactory: () => (file: File) => myUploadService.upload(file) }
|
|
280
411
|
*/
|
|
281
412
|
declare const UPLOAD_HANDLER: InjectionToken<FileUploadHandler>;
|
|
413
|
+
/**
|
|
414
|
+
* Reads an uploaded spreadsheet into headers and positional rows, for the import wizard.
|
|
415
|
+
*
|
|
416
|
+
* Unregistered, the wizard reads CSV with the dependency-free parser in
|
|
417
|
+
* `@dynamic-entity/core`, so it works with nothing provided. Register one to accept `.xlsx`
|
|
418
|
+
* — which spreadsheet library that uses stays the consumer's choice, and this package keeps
|
|
419
|
+
* having no runtime dependency.
|
|
420
|
+
*
|
|
421
|
+
* Rows come back **positional** (`string[][]`) rather than keyed by header, because a real
|
|
422
|
+
* sheet has duplicate headers and blank ones; a header-keyed row silently loses the second
|
|
423
|
+
* of two columns both called "Notes".
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* provideNgxDynamicEntity({
|
|
427
|
+
* sheetParser: async file => {
|
|
428
|
+
* const wb = XLSX.read(await file.arrayBuffer());
|
|
429
|
+
* const rows = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], {
|
|
430
|
+
* header: 1, raw: false, defval: '',
|
|
431
|
+
* }) as string[][];
|
|
432
|
+
* return { headers: rows[0] ?? [], rows: rows.slice(1) };
|
|
433
|
+
* },
|
|
434
|
+
* })
|
|
435
|
+
*/
|
|
436
|
+
declare const SHEET_PARSER: InjectionToken<SheetParser>;
|
|
437
|
+
/**
|
|
438
|
+
* Where an import's work happens.
|
|
439
|
+
*
|
|
440
|
+
* Unregistered, everything runs in the browser through `@dynamic-entity/core` and nothing
|
|
441
|
+
* touches the network — the wizard is fully usable with no backend. Register one that posts
|
|
442
|
+
* to a server and the same components drive that instead, over the same pure mapping engine,
|
|
443
|
+
* which is what keeps a client-side import and a server-side one from disagreeing about what
|
|
444
|
+
* they produced.
|
|
445
|
+
*
|
|
446
|
+
* The reason to register one is size: the default reads the whole file into memory, which is
|
|
447
|
+
* right for a sheet somebody assembled and wrong for a fifty-thousand-row export.
|
|
448
|
+
*
|
|
449
|
+
* @example
|
|
450
|
+
* { provide: IMPORT_TRANSPORT, useClass: HttpImportTransport }
|
|
451
|
+
*/
|
|
452
|
+
declare const IMPORT_TRANSPORT: InjectionToken<ImportTransport>;
|
|
282
453
|
/**
|
|
283
454
|
* Backing store for the entity-reference options cache. Defaults to in-memory.
|
|
284
455
|
* Provide your own to make the cache outlive a page refresh.
|
|
@@ -375,13 +546,13 @@ interface NgxDynamicEntityConfig {
|
|
|
375
546
|
* Entity-ref option loaders keyed by entity key.
|
|
376
547
|
* Each receives `{ parentValue, filters, lang }` and may return an array, Promise, or Observable.
|
|
377
548
|
*/
|
|
378
|
-
entityRefs?: Record<string, EntityReferenceLoader
|
|
549
|
+
entityRefs?: Record<string, EntityReferenceLoader>;
|
|
379
550
|
/**
|
|
380
551
|
* Named master lists keyed by list name, for fields that set `listName`.
|
|
381
552
|
* Each may be the values themselves (array/Promise/Observable) or a loader function — prefer
|
|
382
553
|
* a loader for anything fetched, so an unused list is never loaded.
|
|
383
554
|
*/
|
|
384
|
-
lookups?: Record<string, LookupListSource
|
|
555
|
+
lookups?: Record<string, LookupListSource>;
|
|
385
556
|
/**
|
|
386
557
|
* Override the messages shown under invalid fields, keyed by Angular error key. A value is
|
|
387
558
|
* a string, or a function receiving `(language, error)` when it needs the error's detail.
|
|
@@ -414,7 +585,21 @@ interface NgxDynamicEntityConfig {
|
|
|
414
585
|
* Ordered steps that upgrade a saved record when `EntityFormConfig.version` moves ahead
|
|
415
586
|
* of the record's `_configVersion`. Applied to `initialData` before the form is patched.
|
|
416
587
|
*/
|
|
417
|
-
migrations?: RecordMigration
|
|
588
|
+
migrations?: RecordMigration[];
|
|
589
|
+
/**
|
|
590
|
+
* Reads an uploaded spreadsheet for the import wizard, as `{ headers, rows }` with rows
|
|
591
|
+
* positional rather than keyed by header.
|
|
592
|
+
*
|
|
593
|
+
* Omit it and CSV is read by the dependency-free parser in `@dynamic-entity/core`; supply
|
|
594
|
+
* one to accept `.xlsx`, using whichever spreadsheet library you already have.
|
|
595
|
+
*/
|
|
596
|
+
sheetParser?: SheetParser;
|
|
597
|
+
/**
|
|
598
|
+
* Where an import's work happens. Omit it and the whole import runs in the browser with no
|
|
599
|
+
* network at all; supply one that posts to a server for files too large to hold in memory.
|
|
600
|
+
* Either way the mapping, coercion and validation are the same pure functions.
|
|
601
|
+
*/
|
|
602
|
+
importTransport?: ImportTransport;
|
|
418
603
|
}
|
|
419
604
|
/**
|
|
420
605
|
* Provider function for ngx-dynamic-entity.
|
|
@@ -433,7 +618,7 @@ declare const provideNgxDynamicEntity: (config?: NgxDynamicEntityConfig) => Envi
|
|
|
433
618
|
*
|
|
434
619
|
* `FieldRegistryService` imports no components. Whatever you register here is what gets
|
|
435
620
|
* bundled, so an app that only uses text/number/dropdown pays for three components and
|
|
436
|
-
* their imports, not
|
|
621
|
+
* their imports, not twenty-one.
|
|
437
622
|
*
|
|
438
623
|
* ```ts
|
|
439
624
|
* // Everything (convenient; bundles all built-ins):
|
|
@@ -449,7 +634,7 @@ declare const provideNgxDynamicEntity: (config?: NgxDynamicEntityConfig) => Envi
|
|
|
449
634
|
declare function builtInFieldTypes(): Record<string, Type<any>>;
|
|
450
635
|
/** Register a specific set of field types. Only the components you name are bundled. */
|
|
451
636
|
declare function provideFieldTypes(types: Record<string, Type<any>>): EnvironmentProviders;
|
|
452
|
-
/** Register all
|
|
637
|
+
/** Register all 21 built-in field type keys. Convenience for apps that use most of them. */
|
|
453
638
|
declare function provideBuiltInFieldTypes(): EnvironmentProviders;
|
|
454
639
|
|
|
455
640
|
/**
|
|
@@ -457,7 +642,7 @@ declare function provideBuiltInFieldTypes(): EnvironmentProviders;
|
|
|
457
642
|
*
|
|
458
643
|
* **Tree-shaking contract:** this service holds *no* static imports of field components.
|
|
459
644
|
* Nothing is bundled until something registers it, which is what makes the built-ins
|
|
460
|
-
* opt-in. Register them with `provideBuiltInFieldTypes()` (all
|
|
645
|
+
* opt-in. Register them with `provideBuiltInFieldTypes()` (all 21 keys) or a narrower
|
|
461
646
|
* subset via `provideFieldTypes({ ... })` — see `providers/provide-field-types.ts`.
|
|
462
647
|
*
|
|
463
648
|
* Resolution order: consumer token registry → runtime registrations → nothing.
|
|
@@ -521,6 +706,22 @@ declare class ValidatorRegistryService {
|
|
|
521
706
|
* An unknown name is skipped rather than throwing, matching the synchronous behaviour.
|
|
522
707
|
*/
|
|
523
708
|
resolveAsyncFromConfig(config?: FieldValidators | string[]): AsyncValidatorFn[];
|
|
709
|
+
/**
|
|
710
|
+
* Say when a named validator resolved to nothing.
|
|
711
|
+
*
|
|
712
|
+
* It used to be dropped in silence, which is the worst failure mode this library has: the
|
|
713
|
+
* schema says the field is checked, the field is not checked, the form saves, and nothing
|
|
714
|
+
* anywhere said so. For `customAsync` that is typically a uniqueness check, so the duplicate
|
|
715
|
+
* lands in the database.
|
|
716
|
+
*
|
|
717
|
+
* Dev only, once per name — this runs while controls are built, which happens on every
|
|
718
|
+
* config change. `validateConfig` with `knownValidators` is the build-time version of the
|
|
719
|
+
* same check, and it fails CI rather than a console.
|
|
720
|
+
*/
|
|
721
|
+
private warnUnresolved;
|
|
722
|
+
/** Per service instance, which is per injector — so a test gets a clean slate. */
|
|
723
|
+
private readonly warned;
|
|
724
|
+
private warnOnce;
|
|
524
725
|
static ɵfac: i0.ɵɵFactoryDeclaration<ValidatorRegistryService, never>;
|
|
525
726
|
static ɵprov: i0.ɵɵInjectableDeclaration<ValidatorRegistryService>;
|
|
526
727
|
}
|
|
@@ -539,7 +740,7 @@ declare class EntityRefRegistryService {
|
|
|
539
740
|
* Get the option loader for an entity key.
|
|
540
741
|
* @returns The loader, or null if none is registered.
|
|
541
742
|
*/
|
|
542
|
-
resolve(entityKey: string): EntityReferenceLoader
|
|
743
|
+
resolve(entityKey: string): EntityReferenceLoader | null;
|
|
543
744
|
has(entityKey: string): boolean;
|
|
544
745
|
static ɵfac: i0.ɵɵFactoryDeclaration<EntityRefRegistryService, never>;
|
|
545
746
|
static ɵprov: i0.ɵɵInjectableDeclaration<EntityRefRegistryService>;
|
|
@@ -548,7 +749,7 @@ declare class EntityRefRegistryService {
|
|
|
548
749
|
interface EntityRefSelection {
|
|
549
750
|
fieldId: string;
|
|
550
751
|
/** The chosen option, or `null` when the selection was cleared. */
|
|
551
|
-
option: ReferenceOption
|
|
752
|
+
option: ReferenceOption | null;
|
|
552
753
|
}
|
|
553
754
|
/**
|
|
554
755
|
* EntityRefSelectionService — the channel an entity-ref field uses to publish *which record*
|
|
@@ -563,7 +764,7 @@ interface EntityRefSelection {
|
|
|
563
764
|
declare class EntityRefSelectionService {
|
|
564
765
|
private readonly subject;
|
|
565
766
|
readonly selection$: rxjs.Observable<EntityRefSelection>;
|
|
566
|
-
emit(fieldId: string, option: ReferenceOption
|
|
767
|
+
emit(fieldId: string, option: ReferenceOption | null): void;
|
|
567
768
|
static ɵfac: i0.ɵɵFactoryDeclaration<EntityRefSelectionService, never>;
|
|
568
769
|
static ɵprov: i0.ɵɵInjectableDeclaration<EntityRefSelectionService>;
|
|
569
770
|
}
|
|
@@ -588,7 +789,7 @@ declare class CascadeDataService {
|
|
|
588
789
|
* Load the options for a field, cascade-filtered against `parentValue`.
|
|
589
790
|
* Returns `[]` when no loader is registered, or when a cascade child has no parent value yet.
|
|
590
791
|
*/
|
|
591
|
-
load(field: NestedFieldConfig, ctx?: ReferenceLoaderContext): Promise<ReferenceOption
|
|
792
|
+
load(field: NestedFieldConfig, ctx?: ReferenceLoaderContext): Promise<ReferenceOption[]>;
|
|
592
793
|
/**
|
|
593
794
|
* Warm the cache for every entity a config's cascades depend on, so changing a parent
|
|
594
795
|
* resolves from memory instead of a round-trip.
|
|
@@ -603,7 +804,7 @@ declare class CascadeDataService {
|
|
|
603
804
|
* Child options for a parent selection, from cache only — no load is triggered.
|
|
604
805
|
* Returns `[]` when the entity has not been preloaded.
|
|
605
806
|
*/
|
|
606
|
-
getCachedChildOptions(field: NestedFieldConfig, parentValue: unknown, lang?: string): ReferenceOption
|
|
807
|
+
getCachedChildOptions(field: NestedFieldConfig, parentValue: unknown, lang?: string): ReferenceOption[];
|
|
607
808
|
/** Forget the preload for one config. The option cache itself is untouched. */
|
|
608
809
|
clearCache(formConfigId: string): void;
|
|
609
810
|
/** Every entity-ref field in the config that participates in a cascade. */
|
|
@@ -637,9 +838,9 @@ declare class EntityReferenceService {
|
|
|
637
838
|
* Options for an entity, from cache when present.
|
|
638
839
|
* Concurrent callers with the same key share one load.
|
|
639
840
|
*/
|
|
640
|
-
load(entityKey: string, ctx?: ReferenceLoaderContext): Promise<ReferenceOption
|
|
841
|
+
load(entityKey: string, ctx?: ReferenceLoaderContext): Promise<ReferenceOption[]>;
|
|
641
842
|
/** Cached options without triggering a load. `undefined` when not cached. */
|
|
642
|
-
peek(entityKey: string, ctx?: ReferenceLoaderContext): ReferenceOption
|
|
843
|
+
peek(entityKey: string, ctx?: ReferenceLoaderContext): ReferenceOption[] | undefined;
|
|
643
844
|
/**
|
|
644
845
|
* Display text for a stored reference value, synchronously.
|
|
645
846
|
* Returns `undefined` when the entity's options have never been loaded.
|
|
@@ -778,18 +979,88 @@ declare class RbacService {
|
|
|
778
979
|
static ɵprov: i0.ɵɵInjectableDeclaration<RbacService>;
|
|
779
980
|
}
|
|
780
981
|
|
|
982
|
+
/** What `syncHiddenFieldState` needs from the form it is operating on. */
|
|
983
|
+
interface RuleSyncContext {
|
|
984
|
+
result: RuleEvaluationResult;
|
|
985
|
+
/** The values `showWhen` is evaluated against — the flat map the rules engine reads. */
|
|
986
|
+
values: Record<string, unknown>;
|
|
987
|
+
/** Every name a field answers to: its bare id, and its bracketed path. */
|
|
988
|
+
namesOf(field: NestedFieldConfig): readonly string[];
|
|
989
|
+
/**
|
|
990
|
+
* The field's dotted address, unbracketed — `personal.address.city`.
|
|
991
|
+
*
|
|
992
|
+
* Separate from `namesOf` because it is compared as a *prefix*: a container hides its
|
|
993
|
+
* descendants, and `[personal.address]` is not a prefix of `[personal.address.city]` once
|
|
994
|
+
* the brackets are in the way.
|
|
995
|
+
*/
|
|
996
|
+
addressOf(field: NestedFieldConfig): string;
|
|
997
|
+
/** The control a scope entry addresses, or `null` when it has no static path. */
|
|
998
|
+
controlFor(entry: FieldScopeEntry): AbstractControl | null;
|
|
999
|
+
}
|
|
781
1000
|
/**
|
|
782
|
-
* RulesEvaluationService — evaluates
|
|
1001
|
+
* RulesEvaluationService — evaluates rules, and owns what a result *means*.
|
|
1002
|
+
*
|
|
1003
|
+
* It used to be two passthrough methods, with the interpretation spread across the component:
|
|
1004
|
+
* the render filter decided one way what a rule result did to a field, and the validity sync
|
|
1005
|
+
* decided again forty lines further down. They disagreed about what "every field" meant, and
|
|
1006
|
+
* the consequence was a required field inside a `group` that a rule had hidden holding
|
|
1007
|
+
* `form.invalid` true forever with nothing on screen to explain it.
|
|
1008
|
+
*
|
|
1009
|
+
* Precedence lives here now, in one method that both callers go through. Still stateless —
|
|
1010
|
+
* every method takes the result it is interpreting, so there is nothing to go stale against a
|
|
1011
|
+
* rebuilt form and nothing to reset between configs.
|
|
783
1012
|
*/
|
|
784
1013
|
declare class RulesEvaluationService {
|
|
785
1014
|
/**
|
|
786
1015
|
* Evaluate rules against form values and optional original baseline values.
|
|
787
1016
|
*/
|
|
788
|
-
evaluate(rules: FormRule[] | undefined | null, values: Record<string, unknown>, originalValues?: Record<string, unknown
|
|
1017
|
+
evaluate(rules: FormRule[] | undefined | null, values: Record<string, unknown>, originalValues?: Record<string, unknown>, options?: EvaluateFormRulesOptions): RuleEvaluationResult;
|
|
789
1018
|
/**
|
|
790
1019
|
* Filter rules pertinent to a single tab.
|
|
791
1020
|
*/
|
|
792
1021
|
filterForTab(rules: FormRule[] | undefined | null, tabId: string, config: EntityFormConfig): FormRule[];
|
|
1022
|
+
/**
|
|
1023
|
+
* Whether a field renders, under the precedence documented on `RuleEvaluationResult`.
|
|
1024
|
+
*
|
|
1025
|
+
* An explicit hide beats everything. A show beats static hiding — `visibility: false`, or a
|
|
1026
|
+
* `showWhen` that does not currently hold — because a rule that could only hide what the
|
|
1027
|
+
* config already hides would have nothing to say, and `{ visibility: true }` was a
|
|
1028
|
+
* documented no-op until it did. Otherwise the config's own visibility decides.
|
|
1029
|
+
*/
|
|
1030
|
+
isFieldVisible(result: RuleEvaluationResult, field: NestedFieldConfig, names: readonly string[], values: Record<string, unknown>): boolean;
|
|
1031
|
+
/** The same precedence for a tab. */
|
|
1032
|
+
isTabVisible(result: RuleEvaluationResult, tab: NestedTabConfig): boolean;
|
|
1033
|
+
/**
|
|
1034
|
+
* Keep a hidden field's control out of form validity.
|
|
1035
|
+
*
|
|
1036
|
+
* A field hidden by a rule or a `showWhen` is filtered out of the render, but its
|
|
1037
|
+
* validators stay attached — so a hidden *required* field holds `form.invalid` true
|
|
1038
|
+
* forever, permanently disabling Save with nothing on screen to explain why.
|
|
1039
|
+
*
|
|
1040
|
+
* Disabling is the fix rather than stripping validators: Angular excludes disabled controls
|
|
1041
|
+
* from validity, and the field's own validators survive intact for when it comes back.
|
|
1042
|
+
* Values are untouched — a disabled control still carries its value, so rule evaluation and
|
|
1043
|
+
* the emitted record see exactly what they saw before. That also keeps this from feeding
|
|
1044
|
+
* back on itself: hiding a field cannot change the values the rules are evaluated over.
|
|
1045
|
+
*
|
|
1046
|
+
* The predicate is `isFieldVisible`, the same one the render filter uses, so what is on
|
|
1047
|
+
* screen and what counts toward validity cannot drift apart.
|
|
1048
|
+
*
|
|
1049
|
+
* Entries must arrive parent-first, which is what `collectFieldScopes` yields: a container's
|
|
1050
|
+
* children are skipped once the container itself is hidden, because Angular's `disable()`
|
|
1051
|
+
* cascades to descendants and a child's `enable()` walks back up recalculating ancestors.
|
|
1052
|
+
* Toggling a child inside a hidden group would fight its parent.
|
|
1053
|
+
*/
|
|
1054
|
+
syncHiddenFieldState(entries: readonly FieldScopeEntry[], ctx: RuleSyncContext): void;
|
|
1055
|
+
/**
|
|
1056
|
+
* The hidden and shown names as sets, memoised against the result they came from.
|
|
1057
|
+
*
|
|
1058
|
+
* `isFieldVisible` runs once per field per change-detection pass; building two sets from
|
|
1059
|
+
* two arrays each time turns a rule result into allocation proportional to the form. A
|
|
1060
|
+
* `WeakMap` keyed by the result means it is built once per evaluation and collected with it.
|
|
1061
|
+
*/
|
|
1062
|
+
private nameSets;
|
|
1063
|
+
private readonly nameSetCache;
|
|
793
1064
|
static ɵfac: i0.ɵɵFactoryDeclaration<RulesEvaluationService, never>;
|
|
794
1065
|
static ɵprov: i0.ɵɵInjectableDeclaration<RulesEvaluationService>;
|
|
795
1066
|
}
|
|
@@ -961,6 +1232,8 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
961
1232
|
private readonly hookRegistry;
|
|
962
1233
|
private readonly rbacService;
|
|
963
1234
|
private readonly rulesEvaluation;
|
|
1235
|
+
/** config ⇄ FormGroup: where a value lives. See `FormStructureService`. */
|
|
1236
|
+
private readonly structure;
|
|
964
1237
|
/** Resolves what is wrong with a field, for the error summary — see `resolveForField`. */
|
|
965
1238
|
private readonly messages;
|
|
966
1239
|
private readonly entityRefSelection;
|
|
@@ -990,7 +1263,10 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
990
1263
|
/** Previous values, for detecting `patchOnTrue` false→true transitions. */
|
|
991
1264
|
private previousValues;
|
|
992
1265
|
handleKeyboardEvent(event: KeyboardEvent): void;
|
|
993
|
-
readonly ruleResult: i0.Signal<RuleEvaluationResult>;
|
|
1266
|
+
readonly ruleResult: i0.Signal<ngx_dynamic_entity.RuleEvaluationResult>;
|
|
1267
|
+
/** Warned-about rule problems, per component instance — see `warnedAmbiguousIds`. */
|
|
1268
|
+
private readonly warnedProblems;
|
|
1269
|
+
private warnOnce;
|
|
994
1270
|
/**
|
|
995
1271
|
* Critical fields whose value differs from the session baseline.
|
|
996
1272
|
* These drive the deferred `VALUE_CHANGED` banner — a critical edit is announced once,
|
|
@@ -999,6 +1275,7 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
999
1275
|
readonly changedCriticalFields: i0.Signal<NestedFieldConfig[]>;
|
|
1000
1276
|
get tabs(): NestedTabConfig[];
|
|
1001
1277
|
get visibleTabs(): NestedTabConfig[];
|
|
1278
|
+
private isTabVisible;
|
|
1002
1279
|
get activeTabConfig(): NestedTabConfig | null;
|
|
1003
1280
|
get visibleSubTabs(): NestedTabConfig[];
|
|
1004
1281
|
get activeSubTabConfig(): NestedTabConfig | null;
|
|
@@ -1018,6 +1295,12 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1018
1295
|
private readonly warnedSelectorModules;
|
|
1019
1296
|
private warnSelectorModule;
|
|
1020
1297
|
get fieldsForActiveTab(): NestedFieldConfig[];
|
|
1298
|
+
/**
|
|
1299
|
+
* Whether a field renders. Precedence lives in `RulesEvaluationService`, which is also
|
|
1300
|
+
* what `syncHiddenFieldState` asks — so what is on screen and what counts toward validity
|
|
1301
|
+
* cannot drift apart.
|
|
1302
|
+
*/
|
|
1303
|
+
private isFieldVisible;
|
|
1021
1304
|
/**
|
|
1022
1305
|
* Entity-level permissions for the current roles.
|
|
1023
1306
|
*
|
|
@@ -1159,6 +1442,14 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1159
1442
|
* library never reaches for the global `document`.
|
|
1160
1443
|
*/
|
|
1161
1444
|
jumpToField(fieldId: string): void;
|
|
1445
|
+
/**
|
|
1446
|
+
* Which tab (and sub-tab) holds a field.
|
|
1447
|
+
*
|
|
1448
|
+
* Descends into `group`/`array` children: this compared top-level fields only, so "jump to
|
|
1449
|
+
* the first invalid field" found nothing for a nested one and silently did nothing. Only
|
|
1450
|
+
* sub-tabs one level deep are named, because that is what the tab strip can select —
|
|
1451
|
+
* anything deeper resolves to the ancestor it renders under.
|
|
1452
|
+
*/
|
|
1162
1453
|
private locateField;
|
|
1163
1454
|
/** A critical field stays read-only until the user deliberately unlocks it. */
|
|
1164
1455
|
isFieldLocked(field: NestedFieldConfig): boolean;
|
|
@@ -1185,8 +1476,6 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1185
1476
|
*/
|
|
1186
1477
|
changedCriticalFieldLabels(): string;
|
|
1187
1478
|
private buildForm;
|
|
1188
|
-
private buildFieldControl;
|
|
1189
|
-
/** One row of an `array` field: a FormGroup when the field declares columns, else a bare control. */
|
|
1190
1479
|
/**
|
|
1191
1480
|
* The `FormArray` behind an `array` field, for callers that manage rows themselves —
|
|
1192
1481
|
* the record editor's inline row drawer.
|
|
@@ -1223,9 +1512,8 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1223
1512
|
* stay empty — with no error and no clue. That silent miss is the most expensive way to
|
|
1224
1513
|
* lose an hour with this library, so name it in dev builds.
|
|
1225
1514
|
*
|
|
1226
|
-
*
|
|
1227
|
-
*
|
|
1228
|
-
* our business.
|
|
1515
|
+
* Which keys were missed is `FormStructureService`'s answer; warning once per key per form
|
|
1516
|
+
* is this component's, because it is the thing that lives as long as the form does.
|
|
1229
1517
|
*/
|
|
1230
1518
|
private warnUnconsumedInitialData;
|
|
1231
1519
|
/** Warn once per key per form instance — patchForm re-runs on every initialData change. */
|
|
@@ -1244,7 +1532,16 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1244
1532
|
* consumers that never call the validator still get a signal in development.
|
|
1245
1533
|
*/
|
|
1246
1534
|
private warnAmbiguousRuleReferences;
|
|
1247
|
-
|
|
1535
|
+
/**
|
|
1536
|
+
* Ids already warned about, per component instance.
|
|
1537
|
+
*
|
|
1538
|
+
* This was `private static`, so it was module-global mutable state that nothing ever
|
|
1539
|
+
* cleared: under SSR every render of every form in the process accumulated into one set
|
|
1540
|
+
* that lived as long as the server did, and the first request to render a config
|
|
1541
|
+
* suppressed the warning for every request after it. Per instance, the warning is once per
|
|
1542
|
+
* form — which is what "warn once" was meant to mean.
|
|
1543
|
+
*/
|
|
1544
|
+
private readonly warnedAmbiguousIds;
|
|
1248
1545
|
/**
|
|
1249
1546
|
* The value map rules and `showWhen` are evaluated against.
|
|
1250
1547
|
*
|
|
@@ -1258,17 +1555,53 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1258
1555
|
* unknown>` and needs no knowledge of refs at all — the extra keys simply resolve.
|
|
1259
1556
|
*/
|
|
1260
1557
|
/**
|
|
1261
|
-
*
|
|
1558
|
+
* Every name a rule may address this field by.
|
|
1262
1559
|
*
|
|
1263
1560
|
* A rule target is either a bare field id — how every config so far addresses a field — or
|
|
1264
|
-
* a bracketed path, which is the only way to name one of two fields that share an id.
|
|
1265
|
-
*
|
|
1561
|
+
* a bracketed path, which is the only way to name one of two fields that share an id. Both
|
|
1562
|
+
* have to match, or a rule written either way would fail to hide what it targeted.
|
|
1563
|
+
*
|
|
1564
|
+
* The path comes last, and `syncHiddenFieldState` relies on that: it uses the last entry
|
|
1565
|
+
* as the field's address when deciding whether a hidden container already covers it.
|
|
1566
|
+
*/
|
|
1567
|
+
private namesOf;
|
|
1568
|
+
/**
|
|
1569
|
+
* The field's dotted address, unbracketed.
|
|
1570
|
+
*
|
|
1571
|
+
* The positional path rather than an authored `refererField`: this is compared as a prefix
|
|
1572
|
+
* to decide whether a hidden container already covers a field, and containment is a fact
|
|
1573
|
+
* about where the field *is*. A `refererField` is a binding override — it says where the
|
|
1574
|
+
* value goes, not which group renders it.
|
|
1266
1575
|
*/
|
|
1267
|
-
private
|
|
1576
|
+
private addressOf;
|
|
1268
1577
|
/** The field's declared path, or the one its position implies when it carries none. */
|
|
1269
1578
|
private pathFor;
|
|
1579
|
+
/**
|
|
1580
|
+
* Every field in the config with the scope its value is stored under, cached per config.
|
|
1581
|
+
*
|
|
1582
|
+
* `collectFieldScopes` walks the whole tree. It was called once per field from `pathFor`,
|
|
1583
|
+
* which `namesField` calls, which the render filter calls for every field on the active
|
|
1584
|
+
* tab — a quadratic walk on every change-detection pass. Keyed by config identity, so a
|
|
1585
|
+
* new config invalidates it without an explicit lifecycle hook to forget.
|
|
1586
|
+
*/
|
|
1587
|
+
private fieldScopes;
|
|
1588
|
+
private scopeEntryFor;
|
|
1589
|
+
private scopeCache?;
|
|
1590
|
+
/**
|
|
1591
|
+
* The control a scope entry addresses, named by path rather than by bare id.
|
|
1592
|
+
*
|
|
1593
|
+
* `getControl(field.id)` with no tab falls through to a first-match recursive search of the
|
|
1594
|
+
* whole form, so with `address` on two tabs, hiding one disabled the other. The control
|
|
1595
|
+
* tree mirrors the scope path exactly — `buildTabControls` nests by tab id and
|
|
1596
|
+
* `buildFieldControl` nests a `group` under its own id — so a `[path]` ref resolves through
|
|
1597
|
+
* `form.get()` on `getControl`'s first branch and names exactly one control.
|
|
1598
|
+
*
|
|
1599
|
+
* A field inside an `array` has no static path: its controls live in `FormArray` rows built
|
|
1600
|
+
* per row. `form.get()` returns null for those and they are left alone, which is correct —
|
|
1601
|
+
* a row's controls are created and destroyed with the row.
|
|
1602
|
+
*/
|
|
1603
|
+
private controlForEntry;
|
|
1270
1604
|
private flattenFormValues;
|
|
1271
|
-
private getTabGroup;
|
|
1272
1605
|
/**
|
|
1273
1606
|
* `autoPatch` — copy mapped fields out of the record the user just selected in an
|
|
1274
1607
|
* entity-ref field into the configured target tab's controls.
|
|
@@ -1279,37 +1612,44 @@ declare class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
|
|
|
1279
1612
|
* copy `from` → `to` within the current record.
|
|
1280
1613
|
*/
|
|
1281
1614
|
private runPatchOnTrue;
|
|
1282
|
-
/** Prefer a control declared on the configured target tab; fall back to a top-level control. */
|
|
1283
|
-
private resolveTargetControl;
|
|
1284
1615
|
/**
|
|
1285
|
-
*
|
|
1286
|
-
*
|
|
1287
|
-
*
|
|
1616
|
+
* Prefer a control declared on the configured target tab; fall back to a top-level control.
|
|
1617
|
+
*
|
|
1618
|
+
* "Declared on the tab" now means anywhere under it, sub-tabs and `group` children
|
|
1619
|
+
* included. It meant the tab's own top-level fields only, so an `autoPatch` mapping onto a
|
|
1620
|
+
* field inside a group resolved to `null` and copied nothing, silently.
|
|
1288
1621
|
*/
|
|
1289
|
-
private
|
|
1622
|
+
private resolveTargetControl;
|
|
1290
1623
|
/**
|
|
1291
|
-
* Keep a hidden field's control out of form validity
|
|
1292
|
-
*
|
|
1293
|
-
* A field hidden by a rule or a `showWhen` condition is filtered out of the render, but
|
|
1294
|
-
* its validators stay attached — so a hidden *required* field holds `form.invalid` true
|
|
1295
|
-
* forever, permanently disabling Save with nothing on screen to explain why.
|
|
1296
|
-
*
|
|
1297
|
-
* Disabling is the fix rather than stripping validators: Angular excludes disabled
|
|
1298
|
-
* controls from validity, and the field's own validators survive intact for when it
|
|
1299
|
-
* comes back. Values are untouched — `flattenFormValues` and `extractRecord` both read
|
|
1300
|
-
* `control.value`, which a disabled control still carries, so rule evaluation and the
|
|
1301
|
-
* emitted record see exactly what they saw before. That also keeps this from feeding
|
|
1302
|
-
* back on itself: hiding a field cannot change the values the rules are evaluated over.
|
|
1624
|
+
* Keep a hidden field's control out of form validity — see
|
|
1625
|
+
* `RulesEvaluationService.syncHiddenFieldState`, which owns the rule and the reasoning.
|
|
1303
1626
|
*
|
|
1304
|
-
*
|
|
1305
|
-
*
|
|
1627
|
+
* This supplies the three things it cannot know: which fields exist and in what order
|
|
1628
|
+
* (`collectFieldScopes`, parent-first), what each one is called, and which control each
|
|
1629
|
+
* one addresses.
|
|
1306
1630
|
*/
|
|
1307
1631
|
private syncHiddenFieldState;
|
|
1308
1632
|
/** Every field in the config, including `group`/`array` children. */
|
|
1309
1633
|
private allFields;
|
|
1310
1634
|
/** An empty `originalData` counts as "not supplied" — fall back to the captured baseline. */
|
|
1311
1635
|
private baseline;
|
|
1636
|
+
/**
|
|
1637
|
+
* Whether a critical field still holds what it held when the session started.
|
|
1638
|
+
*
|
|
1639
|
+
* The same question `VALUE_CHANGED` asks, so it is answered by the same comparator rather
|
|
1640
|
+
* than by a third one. The local version compared objects through `JSON.stringify`, which
|
|
1641
|
+
* is key-order sensitive — two serialisers writing the same option in a different key
|
|
1642
|
+
* order made the field read as edited — and had its own idea of which values count as
|
|
1643
|
+
* empty. `valuesEqual` is order-independent and treats `null` and `undefined` as the same
|
|
1644
|
+
* absence; the empty-string case is kept here, because a cleared text input is not an edit.
|
|
1645
|
+
*/
|
|
1312
1646
|
private sameValue;
|
|
1647
|
+
/**
|
|
1648
|
+
* Find a control by a bracketed path or a bare field id.
|
|
1649
|
+
*
|
|
1650
|
+
* Public API, so it stays on the component and delegates — see `FormStructureService`,
|
|
1651
|
+
* which owns the resolution rule and is where the behaviour is documented.
|
|
1652
|
+
*/
|
|
1313
1653
|
getControl(fieldId: string, currentTabId?: string): AbstractControl | null;
|
|
1314
1654
|
/**
|
|
1315
1655
|
* Move focus into the newly shown panel.
|
|
@@ -1432,7 +1772,7 @@ declare class DynamicRecordFormComponent implements OnChanges {
|
|
|
1432
1772
|
* building a row's FormGroup inside the tab view races the tab's own initialisation, so
|
|
1433
1773
|
* the row form is kept detached and only pushed into the FormArray on save.
|
|
1434
1774
|
*/
|
|
1435
|
-
readonly inlineRowField: i0.WritableSignal<
|
|
1775
|
+
readonly inlineRowField: i0.WritableSignal<NestedFieldConfig>;
|
|
1436
1776
|
readonly inlineRowIndex: i0.WritableSignal<number>;
|
|
1437
1777
|
inlineRowForm: FormGroup | null;
|
|
1438
1778
|
/** `array` fields on the tab currently shown, so the template can list their rows. */
|
|
@@ -1667,6 +2007,376 @@ declare class DynamicFieldComponent implements OnChanges, OnDestroy {
|
|
|
1667
2007
|
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>;
|
|
1668
2008
|
}
|
|
1669
2009
|
|
|
2010
|
+
/**
|
|
2011
|
+
* sheet-parser.ts — the built-in CSV reader, and the reason it is the only one.
|
|
2012
|
+
*
|
|
2013
|
+
* CSV is what every spreadsheet tool exports and what a text editor can produce, and
|
|
2014
|
+
* `@dynamic-entity/core` parses it with no dependency. Shipping that as the default means the
|
|
2015
|
+
* import wizard works on a fresh `npm install` with nothing registered — `SHEET_PARSER` then
|
|
2016
|
+
* exists for the consumer who *also* wants `.xlsx`, rather than for the consumer who wants
|
|
2017
|
+
* the feature to work at all.
|
|
2018
|
+
*
|
|
2019
|
+
* The alternative was a hard dependency on a spreadsheet library in a package that currently
|
|
2020
|
+
* has none, for a file format many consumers will never see.
|
|
2021
|
+
*/
|
|
2022
|
+
|
|
2023
|
+
/**
|
|
2024
|
+
* Read a text file as CSV.
|
|
2025
|
+
*
|
|
2026
|
+
* A binary workbook is refused by name rather than attempted: `.xlsx` is a zip, so reading it
|
|
2027
|
+
* as text yields a header row of mojibake and a mapping screen full of nonsense, which is a
|
|
2028
|
+
* much harder thing to diagnose than being told the format is not supported.
|
|
2029
|
+
*/
|
|
2030
|
+
declare const defaultSheetParser: SheetParser;
|
|
2031
|
+
|
|
2032
|
+
declare class LocalImportTransport implements ImportTransport {
|
|
2033
|
+
private readonly registered;
|
|
2034
|
+
/**
|
|
2035
|
+
* One parse per file, not one per call.
|
|
2036
|
+
*
|
|
2037
|
+
* A wizard run reads the same file twice — once to show its headers, once to import it —
|
|
2038
|
+
* and this is the transport whose stated limit is that it holds the file in memory. Doing
|
|
2039
|
+
* the work twice doubled both the parse and the peak memory of the one implementation that
|
|
2040
|
+
* can least afford it.
|
|
2041
|
+
*
|
|
2042
|
+
* A `WeakMap` keyed on the `File` itself: the entry lives exactly as long as something
|
|
2043
|
+
* still holds the file, so choosing a different file drops the old sheet without anything
|
|
2044
|
+
* having to remember to. Keying on a name or a size would collide between two files a user
|
|
2045
|
+
* picked in sequence.
|
|
2046
|
+
*/
|
|
2047
|
+
private readonly parsed;
|
|
2048
|
+
/** The registered parser, or the built-in CSV one. */
|
|
2049
|
+
private read;
|
|
2050
|
+
preview(file: File, context: ImportContext): Promise<ImportPreview>;
|
|
2051
|
+
commit(file: File, plan: MappingPlan, context: ImportContext): Promise<ImportResult>;
|
|
2052
|
+
template(spec: TemplateSpec, format: TemplateFormat): Promise<Blob>;
|
|
2053
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LocalImportTransport, never>;
|
|
2054
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<LocalImportTransport>;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
/**
|
|
2058
|
+
* http-import-transport.ts — the same wizard, with the work happening on a server.
|
|
2059
|
+
*
|
|
2060
|
+
* The components do not change at all: they already talk to an `ImportTransport`, which was
|
|
2061
|
+
* the point of the seam. Registering this one moves the mapping, coercion and validation from
|
|
2062
|
+
* the tab to a server that runs the *same* `@dynamic-entity/core` functions — so the reason to
|
|
2063
|
+
* do it is size, not behaviour.
|
|
2064
|
+
*
|
|
2065
|
+
* **Built on `fetch`, not `HttpClient`.** This package has no `@angular/common/http` dependency
|
|
2066
|
+
* today, and four requests is not the reason to acquire one — a consumer who wants interceptors
|
|
2067
|
+
* passes their own `fetch`, which is a smaller seam than a dependency.
|
|
2068
|
+
*/
|
|
2069
|
+
|
|
2070
|
+
interface HttpImportTransportOptions {
|
|
2071
|
+
/** Where the router is mounted, e.g. `/api/import`. A trailing slash is ignored. */
|
|
2072
|
+
baseUrl: string;
|
|
2073
|
+
/** Supply one to add interceptors, retries, or credentials. Defaults to the global `fetch`. */
|
|
2074
|
+
fetch?: typeof fetch;
|
|
2075
|
+
/** Headers on every request — an `Authorization`, a tenant id. Never a `Content-Type`. */
|
|
2076
|
+
headers?: Record<string, string> | (() => Record<string, string>);
|
|
2077
|
+
/**
|
|
2078
|
+
* Where a warning goes. Defaults to `console.warn`.
|
|
2079
|
+
*
|
|
2080
|
+
* There is exactly one warning, and it is the engine-version mismatch below — which is a
|
|
2081
|
+
* developer's problem, not a user's, so it does not belong in the wizard's error banner.
|
|
2082
|
+
*/
|
|
2083
|
+
warn?: (message: string) => void;
|
|
2084
|
+
}
|
|
2085
|
+
/**
|
|
2086
|
+
* Not `@Injectable`, and registered by value rather than by class.
|
|
2087
|
+
*
|
|
2088
|
+
* Its constructor takes options rather than dependencies, so there is nothing for an injector
|
|
2089
|
+
* to resolve — and a consumer who wants two of them, pointed at two backends, gets that for
|
|
2090
|
+
* free.
|
|
2091
|
+
*/
|
|
2092
|
+
declare class HttpImportTransport implements ImportTransport {
|
|
2093
|
+
private readonly options;
|
|
2094
|
+
constructor(options: HttpImportTransportOptions);
|
|
2095
|
+
private get base();
|
|
2096
|
+
private get send();
|
|
2097
|
+
private entityOf;
|
|
2098
|
+
/**
|
|
2099
|
+
* Warn when the server's engine is not the one this bundle was built against.
|
|
2100
|
+
*
|
|
2101
|
+
* The two halves are separately deployed and may differ. `MappingPlan.configVersion` catches
|
|
2102
|
+
* *config* drift and says nothing about *engine* drift, so "one engine, both sides" is only
|
|
2103
|
+
* true if something checks — and this is the something. A warning rather than a refusal: a
|
|
2104
|
+
* patch release is not a reason to stop somebody importing.
|
|
2105
|
+
*/
|
|
2106
|
+
private checkEngine;
|
|
2107
|
+
preview(file: File, context: ImportContext): Promise<ImportPreview>;
|
|
2108
|
+
commit(file: File, plan: MappingPlan, context: ImportContext): Promise<ImportResult>;
|
|
2109
|
+
template(spec: TemplateSpec, format: TemplateFormat, context: ImportContext, fields?: readonly string[]): Promise<Blob>;
|
|
2110
|
+
}
|
|
2111
|
+
/**
|
|
2112
|
+
* Register the HTTP transport.
|
|
2113
|
+
*
|
|
2114
|
+
* ```ts
|
|
2115
|
+
* provideNgxDynamicEntity({}),
|
|
2116
|
+
* provideHttpImportTransport({ baseUrl: '/api/import' }),
|
|
2117
|
+
* ```
|
|
2118
|
+
*
|
|
2119
|
+
* A plain `Provider` rather than `EnvironmentProviders`, so it composes inside a component's
|
|
2120
|
+
* or a route's `providers` array as well as at the root — which is how a consumer points one
|
|
2121
|
+
* part of an app at a different backend.
|
|
2122
|
+
*/
|
|
2123
|
+
declare function provideHttpImportTransport(options: HttpImportTransportOptions): Provider;
|
|
2124
|
+
|
|
2125
|
+
type Step = 'upload' | 'map' | 'review' | 'done';
|
|
2126
|
+
/**
|
|
2127
|
+
* The import wizard: choose a file, match its columns, look at what that produced, commit.
|
|
2128
|
+
*
|
|
2129
|
+
* It holds no import logic of its own. Everything it does goes through an `ImportTransport`,
|
|
2130
|
+
* which by default is the in-browser one built on `@dynamic-entity/core` — so the wizard
|
|
2131
|
+
* works with no backend, and a consumer who registers a server transport gets the same
|
|
2132
|
+
* screens over the same mapping rules rather than a second implementation of them.
|
|
2133
|
+
*
|
|
2134
|
+
* The review step exists because the expensive mistake here is not a file that fails to
|
|
2135
|
+
* import, it is one that imports *successfully into the wrong fields*. That is invisible in a
|
|
2136
|
+
* summary and obvious in five rendered rows.
|
|
2137
|
+
*/
|
|
2138
|
+
declare class EntityImportComponent {
|
|
2139
|
+
config: EntityFormConfig;
|
|
2140
|
+
/**
|
|
2141
|
+
* The rules the form is rendered with.
|
|
2142
|
+
*
|
|
2143
|
+
* Pass them, or the import checks field validators only — which is not what the form
|
|
2144
|
+
* enforces, and a required field hidden by a rule will fail every row.
|
|
2145
|
+
*/
|
|
2146
|
+
rules?: readonly FormRule[];
|
|
2147
|
+
/**
|
|
2148
|
+
* Named lists, overriding what the wizard already resolves from `LOOKUP_REGISTRY`.
|
|
2149
|
+
*
|
|
2150
|
+
* Not something a host has to supply: every `listName` a config mentions is loaded from the
|
|
2151
|
+
* registry the renderer is already holding. Pass this only to override one.
|
|
2152
|
+
*/
|
|
2153
|
+
lookups?: ImportLookups;
|
|
2154
|
+
language: string;
|
|
2155
|
+
/** Format asked of the transport when a template is downloaded. */
|
|
2156
|
+
templateFormat: TemplateFormat;
|
|
2157
|
+
/** The finished import, once it has run. */
|
|
2158
|
+
readonly importComplete: EventEmitter<ImportResult>;
|
|
2159
|
+
/** A template the transport produced, for a host that would rather save it its own way. */
|
|
2160
|
+
readonly templateReady: EventEmitter<Blob>;
|
|
2161
|
+
protected readonly ui: UiTextService;
|
|
2162
|
+
protected readonly steps: Step[];
|
|
2163
|
+
protected readonly step: i0.WritableSignal<Step>;
|
|
2164
|
+
protected readonly busy: i0.WritableSignal<boolean>;
|
|
2165
|
+
protected readonly problem: i0.WritableSignal<string>;
|
|
2166
|
+
protected readonly headers: i0.WritableSignal<string[]>;
|
|
2167
|
+
protected readonly sample: i0.WritableSignal<string[][]>;
|
|
2168
|
+
protected readonly rowCount: i0.WritableSignal<number>;
|
|
2169
|
+
protected readonly fileName: i0.WritableSignal<string>;
|
|
2170
|
+
protected readonly plan: i0.WritableSignal<MappingPlan>;
|
|
2171
|
+
protected readonly result: i0.WritableSignal<ImportResult>;
|
|
2172
|
+
private readonly registered;
|
|
2173
|
+
private readonly local;
|
|
2174
|
+
private readonly lookupRegistry;
|
|
2175
|
+
private file;
|
|
2176
|
+
/** Named lists resolved from `LOOKUP_REGISTRY`, merged under the `lookups` input. */
|
|
2177
|
+
protected readonly resolvedLookups: i0.WritableSignal<ImportLookups>;
|
|
2178
|
+
private get transport();
|
|
2179
|
+
private context;
|
|
2180
|
+
/**
|
|
2181
|
+
* Load every named list this config's fields refer to.
|
|
2182
|
+
*
|
|
2183
|
+
* `@dynamic-entity/core` is framework-agnostic and cannot reach `LOOKUP_REGISTRY`, so
|
|
2184
|
+
* `coerceCell` takes the values as an argument. That is a reason for *core* to ask, not a
|
|
2185
|
+
* reason for the host to be asked — this package is holding the registry, and making the
|
|
2186
|
+
* consumer re-supply what the library already has is how a `listName` column ends up
|
|
2187
|
+
* storing the raw text `"Gold"` instead of the option object. Such a record renders
|
|
2188
|
+
* correctly and then matches no rule, which is the exact failure the option-shape contract
|
|
2189
|
+
* exists to prevent.
|
|
2190
|
+
*
|
|
2191
|
+
* The `lookups` input still wins where it names a list, so a host can override one without
|
|
2192
|
+
* having to provide all of them.
|
|
2193
|
+
*/
|
|
2194
|
+
private loadLookups;
|
|
2195
|
+
protected stepLabel(step: Step): string;
|
|
2196
|
+
protected chooseFile(event: Event): Promise<void>;
|
|
2197
|
+
protected commit(): Promise<void>;
|
|
2198
|
+
protected downloadTemplate(fields: string[]): Promise<void>;
|
|
2199
|
+
/**
|
|
2200
|
+
* How many records were written.
|
|
2201
|
+
*
|
|
2202
|
+
* `records.length` for the in-browser transport, which returns every record it built, and
|
|
2203
|
+
* `imported` for a server one, which streamed the file precisely so that fifty thousand
|
|
2204
|
+
* records never had to exist at once and therefore has a number rather than a list. Reading
|
|
2205
|
+
* `records.length` alone would report a successful import of nothing.
|
|
2206
|
+
*/
|
|
2207
|
+
protected importedCount(result: ImportResult): number;
|
|
2208
|
+
/**
|
|
2209
|
+
* How many distinct rows failed, not how many problems they had between them.
|
|
2210
|
+
*
|
|
2211
|
+
* `failed` when the transport reports it, and the distinct rows in `errors` only when it does
|
|
2212
|
+
* not. Counting the errors alone was wrong the moment a transport started capping them: a run
|
|
2213
|
+
* of a thousand rows where two hundred failed said **seven**, because seven was how many
|
|
2214
|
+
* distinct rows fitted inside the first twenty retained problems. This is the number a user
|
|
2215
|
+
* acts on — which rows do I go and fix — so a confident wrong answer is the worst of the
|
|
2216
|
+
* available ones. The in-browser transport returns every error, so counting them there is
|
|
2217
|
+
* exact and `failed` is absent.
|
|
2218
|
+
*/
|
|
2219
|
+
protected failedRowCount(result: ImportResult): number;
|
|
2220
|
+
protected restart(): void;
|
|
2221
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<EntityImportComponent, never>;
|
|
2222
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<EntityImportComponent, "ngx-entity-import", never, { "config": { "alias": "config"; "required": true; }; "rules": { "alias": "rules"; "required": false; }; "lookups": { "alias": "lookups"; "required": false; }; "language": { "alias": "language"; "required": false; }; "templateFormat": { "alias": "templateFormat"; "required": false; }; }, { "importComplete": "importComplete"; "templateReady": "templateReady"; }, never, never, true, never>;
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
/** One target field, and which of the sheet's columns feeds it. */
|
|
2226
|
+
interface MappingRow {
|
|
2227
|
+
column: ImportColumn;
|
|
2228
|
+
/** Index into the sheet's headers, or `null` for "not imported". */
|
|
2229
|
+
source: number | null;
|
|
2230
|
+
guessed: boolean;
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* Match the sheet's columns to the config's fields.
|
|
2234
|
+
*
|
|
2235
|
+
* The table is driven by the **target fields**, not by the sheet's columns, which is the
|
|
2236
|
+
* choice that decides how the screen reads. A user is answering "where does Start Date come
|
|
2237
|
+
* from?", which they can do; column-first would ask "what is column G?", which they often
|
|
2238
|
+
* cannot, and would make a sheet with forty irrelevant columns forty questions long.
|
|
2239
|
+
*
|
|
2240
|
+
* A selection is a **column index**, never header text. Two columns headed "Notes" are two
|
|
2241
|
+
* different answers, and text cannot tell them apart.
|
|
2242
|
+
*/
|
|
2243
|
+
declare class ImportMapperComponent implements OnChanges {
|
|
2244
|
+
config: EntityFormConfig;
|
|
2245
|
+
headers: readonly string[];
|
|
2246
|
+
/** The starting point, normally `suggestMapping`'s output. */
|
|
2247
|
+
plan: MappingPlan | null;
|
|
2248
|
+
language: string;
|
|
2249
|
+
/** Emitted whenever the user changes a selection. */
|
|
2250
|
+
readonly planChange: EventEmitter<MappingPlan>;
|
|
2251
|
+
protected readonly ui: UiTextService;
|
|
2252
|
+
protected readonly rows: i0.WritableSignal<MappingRow[]>;
|
|
2253
|
+
/**
|
|
2254
|
+
* How many fields a spreadsheet cannot carry at all.
|
|
2255
|
+
*
|
|
2256
|
+
* Shown here as well as in the template picker, because a user who already had a sheet
|
|
2257
|
+
* skipped that screen — and this is the only place they would otherwise find out that an
|
|
2258
|
+
* attachment column was never going to arrive.
|
|
2259
|
+
*/
|
|
2260
|
+
protected readonly unsupported: i0.WritableSignal<number>;
|
|
2261
|
+
ngOnChanges(): void;
|
|
2262
|
+
protected mappedCount(): number;
|
|
2263
|
+
protected missingRequired(): number;
|
|
2264
|
+
protected choose(row: MappingRow, source: number | null): void;
|
|
2265
|
+
/** The current selections as the plan that will be applied. */
|
|
2266
|
+
private toPlan;
|
|
2267
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ImportMapperComponent, never>;
|
|
2268
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ImportMapperComponent, "ngx-import-mapper", never, { "config": { "alias": "config"; "required": true; }; "headers": { "alias": "headers"; "required": false; }; "plan": { "alias": "plan"; "required": false; }; "language": { "alias": "language"; "required": false; }; }, { "planChange": "planChange"; }, never, never, true, never>;
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
/** One cell, already turned into the text the record will show. */
|
|
2272
|
+
interface PreviewCell {
|
|
2273
|
+
text: string;
|
|
2274
|
+
failed: boolean;
|
|
2275
|
+
}
|
|
2276
|
+
/** A row of cells, plus why the import will refuse it — if it will. */
|
|
2277
|
+
interface PreviewRow {
|
|
2278
|
+
cells: PreviewCell[];
|
|
2279
|
+
/** Empty when the row will be imported. */
|
|
2280
|
+
rejections: string[];
|
|
2281
|
+
}
|
|
2282
|
+
/**
|
|
2283
|
+
* The first few rows as the record will hold them, not as the sheet holds them.
|
|
2284
|
+
*
|
|
2285
|
+
* Every cell goes through the same `coerceCell` the import will use and then through
|
|
2286
|
+
* `formatDisplayValue`, which is what the renderer uses to show a stored value. So a
|
|
2287
|
+
* `dropdown` cell reading `Aktiv` shows as `Active` here if that is the option it resolved
|
|
2288
|
+
* to, and a cell that will fail shows its reason before anyone commits to anything.
|
|
2289
|
+
*
|
|
2290
|
+
* Showing the raw text instead would be easier and would preview the wrong thing: the point
|
|
2291
|
+
* of this screen is to catch a mapping that is subtly wrong, and a column of unchanged text
|
|
2292
|
+
* looks correct whatever field it was pointed at.
|
|
2293
|
+
*
|
|
2294
|
+
* **The rows are also run through the real import**, not just through coercion. `coerceCell`
|
|
2295
|
+
* alone catches a cell that will not parse and says nothing about a row that will be rejected
|
|
2296
|
+
* — a missing required field, a rule that forbids the combination — so a heading promising
|
|
2297
|
+
* "as they will be saved" was showing rows that would never be saved at all, looking exactly
|
|
2298
|
+
* like the ones that would. `applyMapping` over the sample answers that with the same engine
|
|
2299
|
+
* the commit uses, rather than a second opinion about what counts as valid.
|
|
2300
|
+
*
|
|
2301
|
+
* The whole grid is built once per input change rather than from template method calls.
|
|
2302
|
+
* Deriving the config's columns inside a `@for` means re-walking the entire schema for every
|
|
2303
|
+
* cell on every change-detection pass, which is a lot of work to produce five rows.
|
|
2304
|
+
*/
|
|
2305
|
+
declare class ImportPreviewComponent implements OnChanges {
|
|
2306
|
+
config: EntityFormConfig;
|
|
2307
|
+
plan: MappingPlan;
|
|
2308
|
+
rows: readonly (readonly string[])[];
|
|
2309
|
+
lookups?: ImportLookups;
|
|
2310
|
+
/** The same rules the commit will apply; without them a rule-driven rejection is invisible. */
|
|
2311
|
+
rules?: readonly FormRule[];
|
|
2312
|
+
language: string;
|
|
2313
|
+
protected readonly ui: UiTextService;
|
|
2314
|
+
/** The columns this plan fills, in the plan's own order. */
|
|
2315
|
+
protected readonly columns: i0.WritableSignal<ImportColumn[]>;
|
|
2316
|
+
protected readonly grid: i0.WritableSignal<PreviewRow[]>;
|
|
2317
|
+
ngOnChanges(): void;
|
|
2318
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ImportPreviewComponent, never>;
|
|
2319
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ImportPreviewComponent, "ngx-import-preview", never, { "config": { "alias": "config"; "required": true; }; "plan": { "alias": "plan"; "required": true; }; "rows": { "alias": "rows"; "required": false; }; "lookups": { "alias": "lookups"; "required": false; }; "rules": { "alias": "rules"; "required": false; }; "language": { "alias": "language"; "required": false; }; }, {}, never, never, true, never>;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
/** One field offered for the template, with the guidance that belongs to it. */
|
|
2323
|
+
interface TemplateChoice {
|
|
2324
|
+
/** The ref with any row number removed, so a repeating field is offered once. */
|
|
2325
|
+
ref: string;
|
|
2326
|
+
header: string;
|
|
2327
|
+
note: string;
|
|
2328
|
+
selected: boolean;
|
|
2329
|
+
}
|
|
2330
|
+
/**
|
|
2331
|
+
* Pick the fields a template should carry, then download it.
|
|
2332
|
+
*
|
|
2333
|
+
* The guidance for each field is shown **here**, beside the checkbox, rather than written
|
|
2334
|
+
* into the file as a second header row. CSV has one header row and no concept of a second,
|
|
2335
|
+
* so a guidance row inside the file would come back as a record on re-import and fail row 2
|
|
2336
|
+
* of every import the user ever ran.
|
|
2337
|
+
*
|
|
2338
|
+
* A repeating field is offered once, not once per row number: "Contacts / Name" is a decision,
|
|
2339
|
+
* and "Contacts / Name 1", "Contacts / Name 2", "Contacts / Name 3" is the same decision
|
|
2340
|
+
* asked three times.
|
|
2341
|
+
*/
|
|
2342
|
+
declare class ImportTemplateComponent implements OnChanges {
|
|
2343
|
+
config: EntityFormConfig;
|
|
2344
|
+
language: string;
|
|
2345
|
+
/** The refs to include, with row numbers stripped — the caller builds the spec. */
|
|
2346
|
+
readonly download: EventEmitter<string[]>;
|
|
2347
|
+
protected readonly ui: UiTextService;
|
|
2348
|
+
protected readonly choices: i0.WritableSignal<TemplateChoice[]>;
|
|
2349
|
+
protected readonly unsupported: i0.WritableSignal<UnsupportedColumn[]>;
|
|
2350
|
+
ngOnChanges(): void;
|
|
2351
|
+
protected selectedCount(): number;
|
|
2352
|
+
protected selectedRefs(): string[];
|
|
2353
|
+
protected toggle(ref: string): void;
|
|
2354
|
+
protected selectAll(selected: boolean): void;
|
|
2355
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ImportTemplateComponent, never>;
|
|
2356
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ImportTemplateComponent, "ngx-import-template", never, { "config": { "alias": "config"; "required": true; }; "language": { "alias": "language"; "required": false; }; }, { "download": "download"; }, never, never, true, never>;
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
/**
|
|
2360
|
+
* The rows an import could not take, and why.
|
|
2361
|
+
*
|
|
2362
|
+
* Grouped by row rather than listed flat: a row with four problems is one row to go and fix,
|
|
2363
|
+
* and four separate lines invite the reader to think there are four. The row number is the
|
|
2364
|
+
* one the spreadsheet shows in its gutter — header counted as row 1 — because that is the
|
|
2365
|
+
* number they will scroll to.
|
|
2366
|
+
*/
|
|
2367
|
+
declare class ImportErrorsComponent {
|
|
2368
|
+
errors: readonly ImportRowError[];
|
|
2369
|
+
language: string;
|
|
2370
|
+
protected readonly ui: UiTextService;
|
|
2371
|
+
/** Row number → its problems, in the order the rows appear in the file. */
|
|
2372
|
+
protected grouped(): {
|
|
2373
|
+
row: number;
|
|
2374
|
+
problems: ImportRowError[];
|
|
2375
|
+
}[];
|
|
2376
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ImportErrorsComponent, never>;
|
|
2377
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ImportErrorsComponent, "ngx-import-errors", never, { "errors": { "alias": "errors"; "required": false; }; "language": { "alias": "language"; "required": false; }; }, {}, never, never, true, never>;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
1670
2380
|
/**
|
|
1671
2381
|
* What a field component must accept to be registered with `provideFieldTypes`.
|
|
1672
2382
|
*
|
|
@@ -1745,7 +2455,7 @@ declare function fieldDomId(field: {
|
|
|
1745
2455
|
* is read first. `null` rather than an empty string when there is nothing to describe — an
|
|
1746
2456
|
* empty `aria-describedby` is a dangling reference, not an absent one.
|
|
1747
2457
|
*
|
|
1748
|
-
* Shared rather than written into each of the
|
|
2458
|
+
* Shared rather than written into each of the twenty-one field components, because the rule is
|
|
1749
2459
|
* the same everywhere and getting it wrong is invisible until somebody uses a screen reader.
|
|
1750
2460
|
*/
|
|
1751
2461
|
declare function fieldDescribedBy(domId: (suffix?: string) => string, parts: {
|
|
@@ -1876,7 +2586,14 @@ declare class MarkdownFieldComponent implements OnChanges {
|
|
|
1876
2586
|
* A renderer is consumer code and may throw on input it does not like. Letting that
|
|
1877
2587
|
* escape would take down the whole form over one malformed field, so it falls back to the
|
|
1878
2588
|
* source text — which is what the field would have shown with no renderer at all.
|
|
2589
|
+
*
|
|
2590
|
+
* Memoised on the source, because this is called from the template: without it the
|
|
2591
|
+
* consumer's markdown parser ran on every change-detection pass, for every markdown field
|
|
2592
|
+
* on screen, over the whole document — which on a form holding a few long ones is a parse
|
|
2593
|
+
* per keystroke *elsewhere* in the form. The cache is keyed by the exact source string,
|
|
2594
|
+
* so a value that comes back to something already rendered is free too.
|
|
1879
2595
|
*/
|
|
2596
|
+
private cached?;
|
|
1880
2597
|
protected rendered(): string | null;
|
|
1881
2598
|
/**
|
|
1882
2599
|
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
@@ -2317,6 +3034,15 @@ declare class DropdownFieldComponent {
|
|
|
2317
3034
|
isObjectVal(option: DropdownOption): boolean;
|
|
2318
3035
|
get label(): string;
|
|
2319
3036
|
get placeholder(): string;
|
|
3037
|
+
/**
|
|
3038
|
+
* The value this option stores — the option object itself.
|
|
3039
|
+
*
|
|
3040
|
+
* Kept as a method rather than binding `option` straight into the template, because it is
|
|
3041
|
+
* the one place that says *why* the two are the same thing. It used to delegate to
|
|
3042
|
+
* `getOptionStoredValue`, an exported identity function whose two branches returned their
|
|
3043
|
+
* argument unchanged; naming a transformation that does not happen is worse than naming
|
|
3044
|
+
* nothing, because a reader has to go and check.
|
|
3045
|
+
*/
|
|
2320
3046
|
getOptStoredVal(option: DropdownOption): unknown;
|
|
2321
3047
|
getOptLabel(option: DropdownOption): string;
|
|
2322
3048
|
/**
|
|
@@ -2359,6 +3085,15 @@ declare class RadioFieldComponent {
|
|
|
2359
3085
|
/** Inline `options`, or the field's named list resolved through the registry (§6.3). */
|
|
2360
3086
|
readonly options: i0.WritableSignal<DropdownOption[]>;
|
|
2361
3087
|
get label(): string;
|
|
3088
|
+
/**
|
|
3089
|
+
* The value this option stores — the option object itself.
|
|
3090
|
+
*
|
|
3091
|
+
* Kept as a method rather than binding `option` straight into the template, because it is
|
|
3092
|
+
* the one place that says *why* the two are the same thing. It used to delegate to
|
|
3093
|
+
* `getOptionStoredValue`, an exported identity function whose two branches returned their
|
|
3094
|
+
* argument unchanged; naming a transformation that does not happen is worse than naming
|
|
3095
|
+
* nothing, because a reader has to go and check.
|
|
3096
|
+
*/
|
|
2362
3097
|
getOptStoredVal(option: DropdownOption): unknown;
|
|
2363
3098
|
/**
|
|
2364
3099
|
* Stable DOM id for one radio, so the <label for> association works.
|
|
@@ -2419,6 +3154,15 @@ declare class MultiSelectFieldComponent {
|
|
|
2419
3154
|
readonly compareFn: (o1: unknown, o2: unknown) => boolean;
|
|
2420
3155
|
isObjectVal(option: DropdownOption): boolean;
|
|
2421
3156
|
get label(): string;
|
|
3157
|
+
/**
|
|
3158
|
+
* The value this option stores — the option object itself.
|
|
3159
|
+
*
|
|
3160
|
+
* Kept as a method rather than binding `option` straight into the template, because it is
|
|
3161
|
+
* the one place that says *why* the two are the same thing. It used to delegate to
|
|
3162
|
+
* `getOptionStoredValue`, an exported identity function whose two branches returned their
|
|
3163
|
+
* argument unchanged; naming a transformation that does not happen is worse than naming
|
|
3164
|
+
* nothing, because a reader has to go and check.
|
|
3165
|
+
*/
|
|
2422
3166
|
getOptStoredVal(option: DropdownOption): unknown;
|
|
2423
3167
|
getOptLabel(option: DropdownOption): string;
|
|
2424
3168
|
/** Read-only display — synchronous, per §6.2. See `DropdownFieldComponent.getLabel`. */
|
|
@@ -2641,5 +3385,5 @@ declare class FileFieldComponent {
|
|
|
2641
3385
|
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>;
|
|
2642
3386
|
}
|
|
2643
3387
|
|
|
2644
|
-
export { ASYNC_VALIDATOR_REGISTRY, ArrayFieldComponent, BooleanFieldComponent, COMMON_MODULES_REGISTRY, CONFIG_SOURCE, CascadeDataService, CheckboxFieldComponent, ConfigSourceService, CurrencyFieldComponent, DEFAULT_UI_TEXT, 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, MARKDOWN_RENDERER, MASKED_PLACEHOLDER, MASKED_ROLES, MarkdownFieldComponent, MonthYearFieldComponent, MultiSelectFieldComponent, NumberFieldComponent, PasswordFieldComponent, RECORD_MIGRATIONS, RadioFieldComponent, RbacService, RulesEvaluationService, SYSTEM_DEFAULT_CAN_EDIT, TextFieldComponent, TextareaFieldComponent, TimeFieldComponent, UI_TEXT, UPLOAD_HANDLER, UiTextService, VALIDATION_MESSAGES, VALIDATOR_REGISTRY, ValidationMessagesService, ValidatorRegistryService, builtInFieldTypes, fieldDescribedBy, fieldDomId, nextFieldInstanceId, provideBuiltInFieldTypes, provideFieldTypes, provideNgxDynamicEntity, refreshChoiceOptions, resolveUiText };
|
|
2645
|
-
export type { ChoiceOptionHost, ConfigSourceHandler, DynamicFieldComponentContract, EntityRefCacheStore, EntityRefSelection, HookFn, InvalidField, MessageOrderEntry, NgxDynamicEntityConfig, UiTextKey, UiTextOverrides, UiTextOverridesFor, UiTextParams, UiTextResolver, UiTextResolverFor, UiTextValue, ValidationMessage };
|
|
3388
|
+
export { ASYNC_VALIDATOR_REGISTRY, ArrayFieldComponent, BooleanFieldComponent, COMMON_MODULES_REGISTRY, CONFIG_SOURCE, CascadeDataService, CheckboxFieldComponent, ConfigSourceService, CurrencyFieldComponent, DEFAULT_UI_TEXT, DEFAULT_VALIDATION_MESSAGES, DateFieldComponent, DateTimeFieldComponent, DropdownFieldComponent, DynamicFieldComponent, DynamicFormComponent, DynamicRecordFormComponent, ENTITY_REF_CACHE_STORE, ENTITY_REF_REGISTRY, EmailFieldComponent, EntityImportComponent, EntityRefFieldComponent, EntityRefRegistryService, EntityRefSelectionService, EntityReferenceService, FIELD_TYPE_REGISTRY, FIELD_TYPE_SETS, FieldRegistryService, FileFieldComponent, GroupFieldComponent, HOOK_REGISTRY, HookRegistryService, HttpImportTransport, IMPORT_TRANSPORT, ImageFieldComponent, ImportErrorsComponent, ImportMapperComponent, ImportPreviewComponent, ImportTemplateComponent, InMemoryEntityRefCacheStore, LOOKUP_REGISTRY, LocalImportTransport, LookupRegistryService, MARKDOWN_RENDERER, MASKED_PLACEHOLDER, MASKED_ROLES, MarkdownFieldComponent, MonthYearFieldComponent, MultiSelectFieldComponent, NumberFieldComponent, PasswordFieldComponent, RECORD_MIGRATIONS, RadioFieldComponent, RbacService, RulesEvaluationService, SHEET_PARSER, SYSTEM_DEFAULT_CAN_EDIT, TextFieldComponent, TextareaFieldComponent, TimeFieldComponent, UI_TEXT, UPLOAD_HANDLER, UiTextService, VALIDATION_MESSAGES, VALIDATOR_REGISTRY, ValidationMessagesService, ValidatorRegistryService, builtInFieldTypes, defaultSheetParser, fieldDescribedBy, fieldDomId, nextFieldInstanceId, provideBuiltInFieldTypes, provideFieldTypes, provideHttpImportTransport, provideNgxDynamicEntity, refreshChoiceOptions, resolveUiText };
|
|
3389
|
+
export type { ChoiceOptionHost, ConfigSourceHandler, DynamicFieldComponentContract, EntityRefCacheStore, EntityRefSelection, HookFn, HttpImportTransportOptions, ImportContext, ImportPreview, ImportTransport, InvalidField, MessageOrderEntry, NgxDynamicEntityConfig, RuleSyncContext, SheetParser, TemplateFormat, UiTextKey, UiTextOverrides, UiTextOverridesFor, UiTextParams, UiTextResolver, UiTextResolverFor, UiTextValue, ValidationMessage };
|