ngx-dynamic-entity 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -4
- package/fesm2022/ngx-dynamic-entity.mjs +320 -37
- package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
- package/package.json +6 -3
- package/types/ngx-dynamic-entity.d.ts +100 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ngx-dynamic-entity",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Angular standalone UI library for rendering dynamic entity forms.",
|
|
5
5
|
"//typings": "Local workspace resolution only: ng-packagr 21 writes types/ngx-dynamic-entity.d.ts into dist/package.json. Do NOT add a 'main' field here — ng-packagr does not generate one, so it would be copied verbatim and resolve wrongly in the published package.",
|
|
6
6
|
"typings": "types/ngx-dynamic-entity.d.ts",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"@angular/common": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
9
9
|
"@angular/core": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
10
10
|
"@angular/forms": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
11
|
-
"@dynamic-entity/core": "^2.
|
|
11
|
+
"@dynamic-entity/core": "^2.3.0",
|
|
12
12
|
"rxjs": "^7.0.0"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
"author": "Nizamudeen",
|
|
32
32
|
"module": "fesm2022/ngx-dynamic-entity.mjs",
|
|
33
33
|
"exports": {
|
|
34
|
+
"./styles.css": {
|
|
35
|
+
"default": "./styles.css"
|
|
36
|
+
},
|
|
34
37
|
"./package.json": {
|
|
35
38
|
"default": "./package.json"
|
|
36
39
|
},
|
|
@@ -43,4 +46,4 @@
|
|
|
43
46
|
"dependencies": {
|
|
44
47
|
"tslib": "^2.3.0"
|
|
45
48
|
}
|
|
46
|
-
}
|
|
49
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
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,
|
|
3
|
+
import { ReferenceOption, LocalizedText, EntityFormConfig, FormRule, ImportLookups, MappingPlan, CsvDelimiter, ImportResult, TemplateSpec, SheetGrid, CommonModuleEntry, EntityReferenceLoader, LookupListSource, RecordMigration, FileUploadHandler, FieldValidators, NestedFieldConfig, ReferenceLoaderContext, DropdownOption, LookupListValue, NestedTabConfig, RuleEvaluationResult, FieldScopeEntry, EvaluateFormRulesOptions, ImportColumn, MappingEntry, UnsupportedColumn, ImportRowError, SliderBounds } 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
5
|
import { ValidatorFn, AsyncValidatorFn, AbstractControl, ValidationErrors, FormGroup, FormArray } from '@angular/forms';
|
|
6
6
|
import * as rxjs from 'rxjs';
|
|
@@ -120,7 +120,15 @@ declare const DEFAULT_UI_TEXT: {
|
|
|
120
120
|
importGuessed: string;
|
|
121
121
|
importMappedCount: string;
|
|
122
122
|
importRequiredUnmapped: string;
|
|
123
|
+
importConstant: string;
|
|
124
|
+
importAddSlot: string;
|
|
125
|
+
importStaleHeading: string;
|
|
126
|
+
importStaleExplain: string;
|
|
127
|
+
importRemoveEntry: string;
|
|
123
128
|
importPreviewHeading: string;
|
|
129
|
+
importDelimiterComma: string;
|
|
130
|
+
importDelimiterSemicolon: string;
|
|
131
|
+
importDelimiterTab: string;
|
|
124
132
|
importRowRejected: string;
|
|
125
133
|
importRunImport: string;
|
|
126
134
|
importImporting: string;
|
|
@@ -219,27 +227,35 @@ declare class UiTextService {
|
|
|
219
227
|
/**
|
|
220
228
|
* Reads an uploaded file into headers and positional rows.
|
|
221
229
|
*
|
|
222
|
-
* Positional
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
230
|
+
* Positional, not keyed by header, because a real sheet carries duplicate headers and blank
|
|
231
|
+
* ones — and because a mapping entry addresses a column by index for exactly that reason. A
|
|
232
|
+
* parser that returned objects keyed by header would silently lose the second of two columns
|
|
233
|
+
* both called "Notes".
|
|
226
234
|
*
|
|
227
|
-
*
|
|
235
|
+
* Cells may keep their types (`SheetGrid`): a date cell handed over as a `Date` at UTC midnight
|
|
236
|
+
* is imported as that calendar date, exactly as the server reads the same workbook, where its
|
|
237
|
+
* text is whatever a library's formatter printed. A parser returning text (`SheetData`, as 2.2's
|
|
238
|
+
* did) still fits.
|
|
239
|
+
*
|
|
240
|
+
* The library ships a delimited-text reader and no more. Registering one that understands
|
|
228
241
|
* `.xlsx` is how a consumer adds it, and which spreadsheet library they use stays their
|
|
229
242
|
* choice — this package has no runtime dependency and gains none here.
|
|
230
243
|
*
|
|
231
244
|
* @example
|
|
245
|
+
* // SheetJS 0.20.3+ from https://cdn.sheetjs.com — the npm `xlsx` package stopped at 0.18.5,
|
|
246
|
+
* // which has known vulnerabilities. `UTC: true` matters: without it a date cell arrives at
|
|
247
|
+
* // *local* midnight, which is the previous day in UTC for anyone east of Greenwich.
|
|
232
248
|
* provideNgxDynamicEntity({
|
|
233
249
|
* sheetParser: async file => {
|
|
234
|
-
* const workbook = XLSX.read(await file.arrayBuffer());
|
|
250
|
+
* const workbook = XLSX.read(await file.arrayBuffer(), { cellDates: true });
|
|
235
251
|
* const rows = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]], {
|
|
236
|
-
* header: 1, raw:
|
|
237
|
-
* }) as
|
|
238
|
-
* return { headers: rows[0] ?? [], rows: rows.slice(1) };
|
|
252
|
+
* header: 1, raw: true, defval: '', UTC: true,
|
|
253
|
+
* }) as unknown[][];
|
|
254
|
+
* return { headers: (rows[0] ?? []).map(cellText), rows: rows.slice(1) };
|
|
239
255
|
* },
|
|
240
256
|
* })
|
|
241
257
|
*/
|
|
242
|
-
type SheetParser = (file: File) =>
|
|
258
|
+
type SheetParser = (file: File) => SheetGrid | Promise<SheetGrid>;
|
|
243
259
|
/** The formats a generated template can be written in. CSV needs no dependency. */
|
|
244
260
|
type TemplateFormat = 'csv' | 'xlsx';
|
|
245
261
|
/** What the wizard needs to know about the entity it is importing into. */
|
|
@@ -266,6 +282,13 @@ interface ImportPreview {
|
|
|
266
282
|
suggestion: MappingPlan;
|
|
267
283
|
/** Total data rows in the file, which may be far more than `sample` holds. */
|
|
268
284
|
rowCount: number;
|
|
285
|
+
/**
|
|
286
|
+
* Rows of each repeating field the suggestion was sized for (`arrayBoundFor`). Optional so a
|
|
287
|
+
* transport written for 2.2 keeps compiling; the mapper sizes itself the same way regardless.
|
|
288
|
+
*/
|
|
289
|
+
arrayBound?: number;
|
|
290
|
+
/** The field separator a delimited-text file used. Absent for a workbook. */
|
|
291
|
+
delimiter?: CsvDelimiter;
|
|
269
292
|
}
|
|
270
293
|
/**
|
|
271
294
|
* Where the work happens.
|
|
@@ -1001,6 +1024,11 @@ interface RuleSyncContext {
|
|
|
1001
1024
|
addressOf(field: NestedFieldConfig): string;
|
|
1002
1025
|
/** The control a scope entry addresses, or `null` when it has no static path. */
|
|
1003
1026
|
controlFor(entry: FieldScopeEntry): AbstractControl | null;
|
|
1027
|
+
/**
|
|
1028
|
+
* The config the entries came from. Needed to know which tab owns each field: a tab that is
|
|
1029
|
+
* not rendered hides every field it owns. Omitted, only field-level visibility applies.
|
|
1030
|
+
*/
|
|
1031
|
+
config?: EntityFormConfig | null;
|
|
1004
1032
|
}
|
|
1005
1033
|
/**
|
|
1006
1034
|
* RulesEvaluationService — evaluates rules, and owns what a result *means*.
|
|
@@ -1046,12 +1074,25 @@ declare class RulesEvaluationService {
|
|
|
1046
1074
|
* The predicate is `isFieldVisible`, the same one the render filter uses, so what is on
|
|
1047
1075
|
* screen and what counts toward validity cannot drift apart.
|
|
1048
1076
|
*
|
|
1077
|
+
* A tab that is not rendered — hidden by a rule, or `visibility: false` with no rule showing
|
|
1078
|
+
* it — hides everything it owns, sub-tabs and container children included. Only fields were
|
|
1079
|
+
* checked here, so a rule hiding a tab with a required field on it left the form invalid
|
|
1080
|
+
* with the field out of sight and out of the error summary: Save did nothing, silently.
|
|
1081
|
+
* Import already relaxed a hidden tab's fields, so the form refused rows import accepted.
|
|
1082
|
+
*
|
|
1049
1083
|
* Entries must arrive parent-first, which is what `collectFieldScopes` yields: a container's
|
|
1050
1084
|
* children are skipped once the container itself is hidden, because Angular's `disable()`
|
|
1051
1085
|
* cascades to descendants and a child's `enable()` walks back up recalculating ancestors.
|
|
1052
1086
|
* Toggling a child inside a hidden group would fight its parent.
|
|
1053
1087
|
*/
|
|
1054
1088
|
syncHiddenFieldState(entries: readonly FieldScopeEntry[], ctx: RuleSyncContext): void;
|
|
1089
|
+
/**
|
|
1090
|
+
* Every field owned by a tab that does not render, under `isTabVisible`'s precedence.
|
|
1091
|
+
*
|
|
1092
|
+
* Stops descending at the first hidden tab: `fieldsUnderTab` already covers its sub-tabs,
|
|
1093
|
+
* and a sub-tab of a hidden tab is not rendered whatever its own visibility says.
|
|
1094
|
+
*/
|
|
1095
|
+
private fieldsOnHiddenTabs;
|
|
1055
1096
|
/**
|
|
1056
1097
|
* The hidden and shown names as sets, memoised against the result they came from.
|
|
1057
1098
|
*
|
|
@@ -2135,6 +2176,8 @@ declare class EntityImportComponent {
|
|
|
2135
2176
|
protected readonly headers: i0.WritableSignal<string[]>;
|
|
2136
2177
|
protected readonly sample: i0.WritableSignal<string[][]>;
|
|
2137
2178
|
protected readonly rowCount: i0.WritableSignal<number>;
|
|
2179
|
+
/** The separator a delimited file turned out to use, so the user can see how it was read. */
|
|
2180
|
+
protected readonly delimiter: i0.WritableSignal<CsvDelimiter>;
|
|
2138
2181
|
protected readonly fileName: i0.WritableSignal<string>;
|
|
2139
2182
|
protected readonly plan: i0.WritableSignal<MappingPlan>;
|
|
2140
2183
|
protected readonly result: i0.WritableSignal<ImportResult>;
|
|
@@ -2186,6 +2229,10 @@ declare class EntityImportComponent {
|
|
|
2186
2229
|
* exact and `failed` is absent.
|
|
2187
2230
|
*/
|
|
2188
2231
|
protected failedRowCount(result: ImportResult): number;
|
|
2232
|
+
/** The decimal mark the commit will read numbers with, so the preview reads them the same. */
|
|
2233
|
+
protected decimal(): '.' | ',';
|
|
2234
|
+
/** How the file's columns were split, in words — or nothing for a workbook. */
|
|
2235
|
+
protected delimiterText(): string | null;
|
|
2189
2236
|
protected restart(): void;
|
|
2190
2237
|
static ɵfac: i0.ɵɵFactoryDeclaration<EntityImportComponent, never>;
|
|
2191
2238
|
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>;
|
|
@@ -2194,9 +2241,18 @@ declare class EntityImportComponent {
|
|
|
2194
2241
|
/** One target field, and which of the sheet's columns feeds it. */
|
|
2195
2242
|
interface MappingRow {
|
|
2196
2243
|
column: ImportColumn;
|
|
2197
|
-
/** Index into the sheet's headers, or `null` for "not imported". */
|
|
2244
|
+
/** Index into the sheet's headers, `CONSTANT` for a fixed value, or `null` for "not imported". */
|
|
2198
2245
|
source: number | null;
|
|
2199
2246
|
guessed: boolean;
|
|
2247
|
+
/** The incoming plan's entry for this field, emitted untouched until the user changes it. */
|
|
2248
|
+
entry?: MappingEntry;
|
|
2249
|
+
/** The user changed this row, so `entry` no longer describes it. */
|
|
2250
|
+
dirty: boolean;
|
|
2251
|
+
}
|
|
2252
|
+
/** A repeating field, for its "Add a row" button. */
|
|
2253
|
+
interface ArraySlots {
|
|
2254
|
+
arrayRef: string;
|
|
2255
|
+
label: string;
|
|
2200
2256
|
}
|
|
2201
2257
|
/**
|
|
2202
2258
|
* Match the sheet's columns to the config's fields.
|
|
@@ -2227,11 +2283,39 @@ declare class ImportMapperComponent implements OnChanges {
|
|
|
2227
2283
|
* attachment column was never going to arrive.
|
|
2228
2284
|
*/
|
|
2229
2285
|
protected readonly unsupported: i0.WritableSignal<number>;
|
|
2286
|
+
/** A plan entry whose field this config no longer has. Kept, and emitted, until removed. */
|
|
2287
|
+
protected readonly stale: i0.WritableSignal<MappingEntry[]>;
|
|
2288
|
+
/** Repeating fields the user can add a row to. */
|
|
2289
|
+
protected readonly arrays: i0.WritableSignal<ArraySlots[]>;
|
|
2290
|
+
protected readonly constant = -1;
|
|
2291
|
+
/** Rows added by hand per repeating field, on top of what the sheet and plan imply. */
|
|
2292
|
+
private readonly extraSlots;
|
|
2293
|
+
/** The incoming plan, read through the 2.2 alias. Every entry in it survives `toPlan`. */
|
|
2294
|
+
private incoming;
|
|
2230
2295
|
ngOnChanges(): void;
|
|
2296
|
+
/**
|
|
2297
|
+
* Rows for every field, sized so nothing the plan or the sheet names is out of reach.
|
|
2298
|
+
*
|
|
2299
|
+
* Sizing from the default three meant a sheet with `Phone 4` could not be mapped at all,
|
|
2300
|
+
* and a stored plan reaching further lost those entries on the next change — `toPlan` only
|
|
2301
|
+
* ever emitted rows, and there were none for them.
|
|
2302
|
+
*/
|
|
2303
|
+
private rebuild;
|
|
2231
2304
|
protected mappedCount(): number;
|
|
2232
2305
|
protected missingRequired(): number;
|
|
2306
|
+
protected hasConstant(row: MappingRow): boolean;
|
|
2307
|
+
protected constantText(entry: MappingEntry): string;
|
|
2233
2308
|
protected choose(row: MappingRow, source: number | null): void;
|
|
2234
|
-
/**
|
|
2309
|
+
/** One more row for one repeating field, for this session. */
|
|
2310
|
+
protected addSlot(arrayRef: string): void;
|
|
2311
|
+
protected removeStale(entry: MappingEntry): void;
|
|
2312
|
+
/**
|
|
2313
|
+
* The current selections as the plan that will be applied.
|
|
2314
|
+
*
|
|
2315
|
+
* Every entry of the incoming plan survives in its own position — a fixed value, a ref the
|
|
2316
|
+
* config no longer has — unless the user changed or removed it, and an untouched entry is
|
|
2317
|
+
* emitted exactly as it arrived. New selections follow, in field order.
|
|
2318
|
+
*/
|
|
2235
2319
|
private toPlan;
|
|
2236
2320
|
static ɵfac: i0.ɵɵFactoryDeclaration<ImportMapperComponent, never>;
|
|
2237
2321
|
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>;
|
|
@@ -2279,13 +2363,15 @@ declare class ImportPreviewComponent implements OnChanges {
|
|
|
2279
2363
|
/** The same rules the commit will apply; without them a rule-driven rejection is invisible. */
|
|
2280
2364
|
rules?: readonly FormRule[];
|
|
2281
2365
|
language: string;
|
|
2366
|
+
/** The decimal mark number cells use — the one the commit will read them with. */
|
|
2367
|
+
decimal: '.' | ',';
|
|
2282
2368
|
protected readonly ui: UiTextService;
|
|
2283
2369
|
/** The columns this plan fills, in the plan's own order. */
|
|
2284
2370
|
protected readonly columns: i0.WritableSignal<ImportColumn[]>;
|
|
2285
2371
|
protected readonly grid: i0.WritableSignal<PreviewRow[]>;
|
|
2286
2372
|
ngOnChanges(): void;
|
|
2287
2373
|
static ɵfac: i0.ɵɵFactoryDeclaration<ImportPreviewComponent, never>;
|
|
2288
|
-
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>;
|
|
2374
|
+
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; }; "decimal": { "alias": "decimal"; "required": false; }; }, {}, never, never, true, never>;
|
|
2289
2375
|
}
|
|
2290
2376
|
|
|
2291
2377
|
/** One field offered for the template, with the guidance that belongs to it. */
|