ngx-dynamic-entity 0.0.1
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/esm2022/lib/field-types/checkbox-field.component.mjs +73 -0
- package/esm2022/lib/field-types/date-field.component.mjs +82 -0
- package/esm2022/lib/field-types/dropdown-field.component.mjs +83 -0
- package/esm2022/lib/field-types/entity-ref-field.component.mjs +117 -0
- package/esm2022/lib/field-types/multi-select-field.component.mjs +91 -0
- package/esm2022/lib/field-types/number-field.component.mjs +73 -0
- package/esm2022/lib/field-types/text-field.component.mjs +74 -0
- package/esm2022/lib/field-types/textarea-field.component.mjs +73 -0
- package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +86 -0
- package/esm2022/lib/form/dynamic-form.component.mjs +167 -0
- package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +23 -0
- package/esm2022/lib/services/config.service.mjs +58 -0
- package/esm2022/lib/services/entity-ref-registry.service.mjs +31 -0
- package/esm2022/lib/services/field-registry.service.mjs +50 -0
- package/esm2022/lib/services/hook-registry.service.mjs +26 -0
- package/esm2022/lib/services/rbac.service.mjs +52 -0
- package/esm2022/lib/services/validator-registry.service.mjs +49 -0
- package/esm2022/lib/services/version.service.mjs +40 -0
- package/esm2022/lib/table/dynamic-table.component.mjs +160 -0
- package/esm2022/lib/tokens/injection-tokens.mjs +16 -0
- package/esm2022/ngx-dynamic-entity.mjs +5 -0
- package/esm2022/public-api.mjs +26 -0
- package/fesm2022/ngx-dynamic-entity.mjs +1352 -0
- package/fesm2022/ngx-dynamic-entity.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/field-types/checkbox-field.component.d.ts +12 -0
- package/lib/field-types/date-field.component.d.ts +14 -0
- package/lib/field-types/dropdown-field.component.d.ts +13 -0
- package/lib/field-types/entity-ref-field.component.d.ts +26 -0
- package/lib/field-types/multi-select-field.component.d.ts +13 -0
- package/lib/field-types/number-field.component.d.ts +12 -0
- package/lib/field-types/text-field.component.d.ts +13 -0
- package/lib/field-types/textarea-field.component.d.ts +12 -0
- package/lib/form/dynamic-field/dynamic-field.component.d.ts +30 -0
- package/lib/form/dynamic-form.component.d.ts +52 -0
- package/lib/providers/provide-ngx-dynamic-entity.d.ts +29 -0
- package/lib/services/config.service.d.ts +25 -0
- package/lib/services/entity-ref-registry.service.d.ts +20 -0
- package/lib/services/field-registry.service.d.ts +22 -0
- package/lib/services/hook-registry.service.d.ts +12 -0
- package/lib/services/rbac.service.d.ts +32 -0
- package/lib/services/validator-registry.service.d.ts +19 -0
- package/lib/services/version.service.d.ts +26 -0
- package/lib/table/dynamic-table.component.d.ts +56 -0
- package/lib/tokens/injection-tokens.d.ts +16 -0
- package/package.json +37 -0
- package/public-api.d.ts +21 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Injectable, inject } from '@angular/core';
|
|
2
|
+
import { FIELD_TYPE_REGISTRY } from '../tokens/injection-tokens';
|
|
3
|
+
// Built-in field components — pre-registered in this service
|
|
4
|
+
import { TextFieldComponent } from '../field-types/text-field.component';
|
|
5
|
+
import { TextareaFieldComponent } from '../field-types/textarea-field.component';
|
|
6
|
+
import { NumberFieldComponent } from '../field-types/number-field.component';
|
|
7
|
+
import { CheckboxFieldComponent } from '../field-types/checkbox-field.component';
|
|
8
|
+
import { DateFieldComponent } from '../field-types/date-field.component';
|
|
9
|
+
import { DropdownFieldComponent } from '../field-types/dropdown-field.component';
|
|
10
|
+
import { MultiSelectFieldComponent } from '../field-types/multi-select-field.component';
|
|
11
|
+
import { EntityRefFieldComponent } from '../field-types/entity-ref-field.component';
|
|
12
|
+
import * as i0 from "@angular/core";
|
|
13
|
+
/**
|
|
14
|
+
* FieldRegistryService — resolves field type strings to Angular component classes.
|
|
15
|
+
* Open/Closed: open for extension via consumer fieldTypes config, closed for modification.
|
|
16
|
+
* DynamicFieldComponent uses this to mount the right component via createComponent().
|
|
17
|
+
*/
|
|
18
|
+
export class FieldRegistryService {
|
|
19
|
+
consumerRegistry = inject(FIELD_TYPE_REGISTRY, { optional: true }) ?? new Map();
|
|
20
|
+
/** Built-in field type → component map. Never remove an entry. */
|
|
21
|
+
builtInRegistry = new Map([
|
|
22
|
+
['text', TextFieldComponent],
|
|
23
|
+
['textarea', TextareaFieldComponent],
|
|
24
|
+
['number', NumberFieldComponent],
|
|
25
|
+
['checkbox', CheckboxFieldComponent],
|
|
26
|
+
['date', DateFieldComponent],
|
|
27
|
+
['dropdown', DropdownFieldComponent],
|
|
28
|
+
['multiSelect', MultiSelectFieldComponent],
|
|
29
|
+
['entity-ref', EntityRefFieldComponent],
|
|
30
|
+
]);
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a field type string to a component class.
|
|
33
|
+
* Consumer registry takes precedence over built-ins (open for extension).
|
|
34
|
+
* Returns null if type is unknown — caller must handle gracefully.
|
|
35
|
+
*/
|
|
36
|
+
resolve(fieldType) {
|
|
37
|
+
return this.consumerRegistry.get(fieldType) ?? this.builtInRegistry.get(fieldType) ?? null;
|
|
38
|
+
}
|
|
39
|
+
/** Check if a field type has a registered component */
|
|
40
|
+
has(fieldType) {
|
|
41
|
+
return this.consumerRegistry.has(fieldType) || this.builtInRegistry.has(fieldType);
|
|
42
|
+
}
|
|
43
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FieldRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
44
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FieldRegistryService, providedIn: 'root' });
|
|
45
|
+
}
|
|
46
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FieldRegistryService, decorators: [{
|
|
47
|
+
type: Injectable,
|
|
48
|
+
args: [{ providedIn: 'root' }]
|
|
49
|
+
}] });
|
|
50
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmllbGQtcmVnaXN0cnkuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvc2VydmljZXMvZmllbGQtcmVnaXN0cnkuc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFRLE1BQU0sRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUN6RCxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUVqRSw2REFBNkQ7QUFDN0QsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFDekUsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDakYsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sdUNBQXVDLENBQUM7QUFDN0UsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDakYsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFDekUsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDakYsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sNkNBQTZDLENBQUM7QUFDeEYsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0sMkNBQTJDLENBQUM7O0FBRXBGOzs7O0dBSUc7QUFFSCxNQUFNLE9BQU8sb0JBQW9CO0lBQ2QsZ0JBQWdCLEdBQUcsTUFBTSxDQUFDLG1CQUFtQixFQUFFLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxDQUFDLElBQUksSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUVqRyxrRUFBa0U7SUFDakQsZUFBZSxHQUFHLElBQUksR0FBRyxDQUFvQjtRQUM1RCxDQUFDLE1BQU0sRUFBRSxrQkFBa0IsQ0FBQztRQUM1QixDQUFDLFVBQVUsRUFBRSxzQkFBc0IsQ0FBQztRQUNwQyxDQUFDLFFBQVEsRUFBRSxvQkFBb0IsQ0FBQztRQUNoQyxDQUFDLFVBQVUsRUFBRSxzQkFBc0IsQ0FBQztRQUNwQyxDQUFDLE1BQU0sRUFBRSxrQkFBa0IsQ0FBQztRQUM1QixDQUFDLFVBQVUsRUFBRSxzQkFBc0IsQ0FBQztRQUNwQyxDQUFDLGFBQWEsRUFBRSx5QkFBeUIsQ0FBQztRQUMxQyxDQUFDLFlBQVksRUFBRSx1QkFBdUIsQ0FBQztLQUN4QyxDQUFDLENBQUM7SUFFSDs7OztPQUlHO0lBQ0gsT0FBTyxDQUFDLFNBQWlCO1FBQ3ZCLE9BQU8sSUFBSSxDQUFDLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsSUFBSSxJQUFJLENBQUMsZUFBZSxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsSUFBSSxJQUFJLENBQUM7SUFDN0YsQ0FBQztJQUVELHVEQUF1RDtJQUN2RCxHQUFHLENBQUMsU0FBaUI7UUFDbkIsT0FBTyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxJQUFJLElBQUksQ0FBQyxlQUFlLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ3JGLENBQUM7d0dBM0JVLG9CQUFvQjs0R0FBcEIsb0JBQW9CLGNBRFAsTUFBTTs7NEZBQ25CLG9CQUFvQjtrQkFEaEMsVUFBVTttQkFBQyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3RhYmxlLCBUeXBlLCBpbmplY3QgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEZJRUxEX1RZUEVfUkVHSVNUUlkgfSBmcm9tICcuLi90b2tlbnMvaW5qZWN0aW9uLXRva2Vucyc7XG5cbi8vIEJ1aWx0LWluIGZpZWxkIGNvbXBvbmVudHMg4oCUIHByZS1yZWdpc3RlcmVkIGluIHRoaXMgc2VydmljZVxuaW1wb3J0IHsgVGV4dEZpZWxkQ29tcG9uZW50IH0gZnJvbSAnLi4vZmllbGQtdHlwZXMvdGV4dC1maWVsZC5jb21wb25lbnQnO1xuaW1wb3J0IHsgVGV4dGFyZWFGaWVsZENvbXBvbmVudCB9IGZyb20gJy4uL2ZpZWxkLXR5cGVzL3RleHRhcmVhLWZpZWxkLmNvbXBvbmVudCc7XG5pbXBvcnQgeyBOdW1iZXJGaWVsZENvbXBvbmVudCB9IGZyb20gJy4uL2ZpZWxkLXR5cGVzL251bWJlci1maWVsZC5jb21wb25lbnQnO1xuaW1wb3J0IHsgQ2hlY2tib3hGaWVsZENvbXBvbmVudCB9IGZyb20gJy4uL2ZpZWxkLXR5cGVzL2NoZWNrYm94LWZpZWxkLmNvbXBvbmVudCc7XG5pbXBvcnQgeyBEYXRlRmllbGRDb21wb25lbnQgfSBmcm9tICcuLi9maWVsZC10eXBlcy9kYXRlLWZpZWxkLmNvbXBvbmVudCc7XG5pbXBvcnQgeyBEcm9wZG93bkZpZWxkQ29tcG9uZW50IH0gZnJvbSAnLi4vZmllbGQtdHlwZXMvZHJvcGRvd24tZmllbGQuY29tcG9uZW50JztcbmltcG9ydCB7IE11bHRpU2VsZWN0RmllbGRDb21wb25lbnQgfSBmcm9tICcuLi9maWVsZC10eXBlcy9tdWx0aS1zZWxlY3QtZmllbGQuY29tcG9uZW50JztcbmltcG9ydCB7IEVudGl0eVJlZkZpZWxkQ29tcG9uZW50IH0gZnJvbSAnLi4vZmllbGQtdHlwZXMvZW50aXR5LXJlZi1maWVsZC5jb21wb25lbnQnO1xuXG4vKipcbiAqIEZpZWxkUmVnaXN0cnlTZXJ2aWNlIOKAlCByZXNvbHZlcyBmaWVsZCB0eXBlIHN0cmluZ3MgdG8gQW5ndWxhciBjb21wb25lbnQgY2xhc3Nlcy5cbiAqIE9wZW4vQ2xvc2VkOiBvcGVuIGZvciBleHRlbnNpb24gdmlhIGNvbnN1bWVyIGZpZWxkVHlwZXMgY29uZmlnLCBjbG9zZWQgZm9yIG1vZGlmaWNhdGlvbi5cbiAqIER5bmFtaWNGaWVsZENvbXBvbmVudCB1c2VzIHRoaXMgdG8gbW91bnQgdGhlIHJpZ2h0IGNvbXBvbmVudCB2aWEgY3JlYXRlQ29tcG9uZW50KCkuXG4gKi9cbkBJbmplY3RhYmxlKHsgcHJvdmlkZWRJbjogJ3Jvb3QnIH0pXG5leHBvcnQgY2xhc3MgRmllbGRSZWdpc3RyeVNlcnZpY2Uge1xuICBwcml2YXRlIHJlYWRvbmx5IGNvbnN1bWVyUmVnaXN0cnkgPSBpbmplY3QoRklFTERfVFlQRV9SRUdJU1RSWSwgeyBvcHRpb25hbDogdHJ1ZSB9KSA/PyBuZXcgTWFwKCk7XG5cbiAgLyoqIEJ1aWx0LWluIGZpZWxkIHR5cGUg4oaSIGNvbXBvbmVudCBtYXAuIE5ldmVyIHJlbW92ZSBhbiBlbnRyeS4gKi9cbiAgcHJpdmF0ZSByZWFkb25seSBidWlsdEluUmVnaXN0cnkgPSBuZXcgTWFwPHN0cmluZywgVHlwZTxhbnk+PihbXG4gICAgWyd0ZXh0JywgVGV4dEZpZWxkQ29tcG9uZW50XSxcbiAgICBbJ3RleHRhcmVhJywgVGV4dGFyZWFGaWVsZENvbXBvbmVudF0sXG4gICAgWydudW1iZXInLCBOdW1iZXJGaWVsZENvbXBvbmVudF0sXG4gICAgWydjaGVja2JveCcsIENoZWNrYm94RmllbGRDb21wb25lbnRdLFxuICAgIFsnZGF0ZScsIERhdGVGaWVsZENvbXBvbmVudF0sXG4gICAgWydkcm9wZG93bicsIERyb3Bkb3duRmllbGRDb21wb25lbnRdLFxuICAgIFsnbXVsdGlTZWxlY3QnLCBNdWx0aVNlbGVjdEZpZWxkQ29tcG9uZW50XSxcbiAgICBbJ2VudGl0eS1yZWYnLCBFbnRpdHlSZWZGaWVsZENvbXBvbmVudF0sXG4gIF0pO1xuXG4gIC8qKlxuICAgKiBSZXNvbHZlIGEgZmllbGQgdHlwZSBzdHJpbmcgdG8gYSBjb21wb25lbnQgY2xhc3MuXG4gICAqIENvbnN1bWVyIHJlZ2lzdHJ5IHRha2VzIHByZWNlZGVuY2Ugb3ZlciBidWlsdC1pbnMgKG9wZW4gZm9yIGV4dGVuc2lvbikuXG4gICAqIFJldHVybnMgbnVsbCBpZiB0eXBlIGlzIHVua25vd24g4oCUIGNhbGxlciBtdXN0IGhhbmRsZSBncmFjZWZ1bGx5LlxuICAgKi9cbiAgcmVzb2x2ZShmaWVsZFR5cGU6IHN0cmluZyk6IFR5cGU8YW55PiB8IG51bGwge1xuICAgIHJldHVybiB0aGlzLmNvbnN1bWVyUmVnaXN0cnkuZ2V0KGZpZWxkVHlwZSkgPz8gdGhpcy5idWlsdEluUmVnaXN0cnkuZ2V0KGZpZWxkVHlwZSkgPz8gbnVsbDtcbiAgfVxuXG4gIC8qKiBDaGVjayBpZiBhIGZpZWxkIHR5cGUgaGFzIGEgcmVnaXN0ZXJlZCBjb21wb25lbnQgKi9cbiAgaGFzKGZpZWxkVHlwZTogc3RyaW5nKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMuY29uc3VtZXJSZWdpc3RyeS5oYXMoZmllbGRUeXBlKSB8fCB0aGlzLmJ1aWx0SW5SZWdpc3RyeS5oYXMoZmllbGRUeXBlKTtcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Injectable, inject } from '@angular/core';
|
|
2
|
+
import { HOOK_REGISTRY } from '../tokens/injection-tokens';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* HookRegistryService — stores and runs Angular-side pre/post hooks.
|
|
6
|
+
* Consumers register hooks via provideNgxDynamicEntity({ hooks: { 'clients:beforeSave': fn } }).
|
|
7
|
+
*/
|
|
8
|
+
export class HookRegistryService {
|
|
9
|
+
registry = inject(HOOK_REGISTRY, { optional: true }) ?? new Map();
|
|
10
|
+
has(key) {
|
|
11
|
+
return this.registry.has(key);
|
|
12
|
+
}
|
|
13
|
+
async run(key, data, context = {}) {
|
|
14
|
+
const hook = this.registry.get(key);
|
|
15
|
+
if (!hook)
|
|
16
|
+
return data;
|
|
17
|
+
return hook(data, context);
|
|
18
|
+
}
|
|
19
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HookRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
20
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HookRegistryService, providedIn: 'root' });
|
|
21
|
+
}
|
|
22
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HookRegistryService, decorators: [{
|
|
23
|
+
type: Injectable,
|
|
24
|
+
args: [{ providedIn: 'root' }]
|
|
25
|
+
}] });
|
|
26
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaG9vay1yZWdpc3RyeS5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9zZXJ2aWNlcy9ob29rLXJlZ2lzdHJ5LnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDbkQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDRCQUE0QixDQUFDOztBQUUzRDs7O0dBR0c7QUFFSCxNQUFNLE9BQU8sbUJBQW1CO0lBQ2IsUUFBUSxHQUFHLE1BQU0sQ0FBQyxhQUFhLEVBQUUsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLENBQUMsSUFBSSxJQUFJLEdBQUcsRUFBRSxDQUFDO0lBRW5GLEdBQUcsQ0FBQyxHQUFXO1FBQ2IsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNoQyxDQUFDO0lBRUQsS0FBSyxDQUFDLEdBQUcsQ0FBQyxHQUFXLEVBQUUsSUFBUyxFQUFFLFVBQWUsRUFBRTtRQUNqRCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNwQyxJQUFJLENBQUMsSUFBSTtZQUFFLE9BQU8sSUFBSSxDQUFDO1FBQ3ZCLE9BQU8sSUFBSSxDQUFDLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQztJQUM3QixDQUFDO3dHQVhVLG1CQUFtQjs0R0FBbkIsbUJBQW1CLGNBRE4sTUFBTTs7NEZBQ25CLG1CQUFtQjtrQkFEL0IsVUFBVTttQkFBQyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3RhYmxlLCBpbmplY3QgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEhPT0tfUkVHSVNUUlkgfSBmcm9tICcuLi90b2tlbnMvaW5qZWN0aW9uLXRva2Vucyc7XG5cbi8qKlxuICogSG9va1JlZ2lzdHJ5U2VydmljZSDigJQgc3RvcmVzIGFuZCBydW5zIEFuZ3VsYXItc2lkZSBwcmUvcG9zdCBob29rcy5cbiAqIENvbnN1bWVycyByZWdpc3RlciBob29rcyB2aWEgcHJvdmlkZU5neER5bmFtaWNFbnRpdHkoeyBob29rczogeyAnY2xpZW50czpiZWZvcmVTYXZlJzogZm4gfSB9KS5cbiAqL1xuQEluamVjdGFibGUoeyBwcm92aWRlZEluOiAncm9vdCcgfSlcbmV4cG9ydCBjbGFzcyBIb29rUmVnaXN0cnlTZXJ2aWNlIHtcbiAgcHJpdmF0ZSByZWFkb25seSByZWdpc3RyeSA9IGluamVjdChIT09LX1JFR0lTVFJZLCB7IG9wdGlvbmFsOiB0cnVlIH0pID8/IG5ldyBNYXAoKTtcblxuICBoYXMoa2V5OiBzdHJpbmcpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy5yZWdpc3RyeS5oYXMoa2V5KTtcbiAgfVxuXG4gIGFzeW5jIHJ1bihrZXk6IHN0cmluZywgZGF0YTogYW55LCBjb250ZXh0OiBhbnkgPSB7fSk6IFByb21pc2U8YW55PiB7XG4gICAgY29uc3QgaG9vayA9IHRoaXMucmVnaXN0cnkuZ2V0KGtleSk7XG4gICAgaWYgKCFob29rKSByZXR1cm4gZGF0YTtcbiAgICByZXR1cm4gaG9vayhkYXRhLCBjb250ZXh0KTtcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Injectable, inject } from '@angular/core';
|
|
2
|
+
import { MASKED_ROLES } from '../tokens/injection-tokens';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* RbacService — ALL permission checks and masking logic live here.
|
|
6
|
+
* CRITICAL SYNC: resolveEffectiveMask() must be identical to rbac.utils.js in dynamic-entity-server.
|
|
7
|
+
* Changing one without the other is a critical production bug (ADR-003).
|
|
8
|
+
*/
|
|
9
|
+
export class RbacService {
|
|
10
|
+
maskedRoles = inject(MASKED_ROLES, { optional: true }) ?? [];
|
|
11
|
+
/**
|
|
12
|
+
* Check if any of the user's roles satisfy the required roles.
|
|
13
|
+
* hasPermission(roles, undefined) → true (no restriction).
|
|
14
|
+
*/
|
|
15
|
+
hasPermission(userRoles, requiredRoles) {
|
|
16
|
+
if (!requiredRoles?.length)
|
|
17
|
+
return true;
|
|
18
|
+
return userRoles.some(r => requiredRoles.includes(r));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* ADR-003: 3-level OR resolution.
|
|
22
|
+
* MUST match rbac.utils.js resolveEffectiveMask() exactly.
|
|
23
|
+
*/
|
|
24
|
+
resolveEffectiveMask(formMask, tabMask, fieldMask) {
|
|
25
|
+
return !!(formMask || tabMask || fieldMask);
|
|
26
|
+
}
|
|
27
|
+
/** Check if the user's roles include any masked role */
|
|
28
|
+
isUserMaskedRole(userRoles) {
|
|
29
|
+
return this.maskedRoles.some(r => userRoles.includes(r));
|
|
30
|
+
}
|
|
31
|
+
/** Determine if a specific field should be masked for this user */
|
|
32
|
+
shouldMaskField(field, tab, config, userRoles) {
|
|
33
|
+
if (!this.isUserMaskedRole(userRoles))
|
|
34
|
+
return false;
|
|
35
|
+
return this.resolveEffectiveMask(config.maskData, tab?.maskData, field.maskData);
|
|
36
|
+
}
|
|
37
|
+
/** Get view/edit/delete permissions for a config */
|
|
38
|
+
getPermissions(config, userRoles) {
|
|
39
|
+
return {
|
|
40
|
+
canView: this.hasPermission(userRoles, config.permissions?.view),
|
|
41
|
+
canEdit: this.hasPermission(userRoles, config.permissions?.edit),
|
|
42
|
+
canDelete: this.hasPermission(userRoles, config.permissions?.delete),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RbacService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
46
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RbacService, providedIn: 'root' });
|
|
47
|
+
}
|
|
48
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RbacService, decorators: [{
|
|
49
|
+
type: Injectable,
|
|
50
|
+
args: [{ providedIn: 'root' }]
|
|
51
|
+
}] });
|
|
52
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmJhYy5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9zZXJ2aWNlcy9yYmFjLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFbkQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLDRCQUE0QixDQUFDOztBQUUxRDs7OztHQUlHO0FBRUgsTUFBTSxPQUFPLFdBQVc7SUFDTCxXQUFXLEdBQUcsTUFBTSxDQUFDLFlBQVksRUFBRSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUU5RTs7O09BR0c7SUFDSCxhQUFhLENBQUMsU0FBbUIsRUFBRSxhQUF3QjtRQUN6RCxJQUFJLENBQUMsYUFBYSxFQUFFLE1BQU07WUFBRSxPQUFPLElBQUksQ0FBQztRQUN4QyxPQUFPLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDeEQsQ0FBQztJQUVEOzs7T0FHRztJQUNILG9CQUFvQixDQUFDLFFBQWtCLEVBQUUsT0FBaUIsRUFBRSxTQUFtQjtRQUM3RSxPQUFPLENBQUMsQ0FBQyxDQUFDLFFBQVEsSUFBSSxPQUFPLElBQUksU0FBUyxDQUFDLENBQUM7SUFDOUMsQ0FBQztJQUVELHdEQUF3RDtJQUN4RCxnQkFBZ0IsQ0FBQyxTQUFtQjtRQUNsQyxPQUFPLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzNELENBQUM7SUFFRCxtRUFBbUU7SUFDbkUsZUFBZSxDQUFDLEtBQWtCLEVBQUUsR0FBMEIsRUFBRSxNQUFvQixFQUFFLFNBQW1CO1FBQ3ZHLElBQUksQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxDQUFDO1lBQUUsT0FBTyxLQUFLLENBQUM7UUFDcEQsT0FBTyxJQUFJLENBQUMsb0JBQW9CLENBQUMsTUFBTSxDQUFDLFFBQVEsRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNuRixDQUFDO0lBRUQsb0RBQW9EO0lBQ3BELGNBQWMsQ0FBQyxNQUFvQixFQUFFLFNBQW1CO1FBQ3RELE9BQU87WUFDTCxPQUFPLEVBQUUsSUFBSSxDQUFDLGFBQWEsQ0FBQyxTQUFTLEVBQUUsTUFBTSxDQUFDLFdBQVcsRUFBRSxJQUFJLENBQUM7WUFDaEUsT0FBTyxFQUFFLElBQUksQ0FBQyxhQUFhLENBQUMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxXQUFXLEVBQUUsSUFBSSxDQUFDO1lBQ2hFLFNBQVMsRUFBRSxJQUFJLENBQUMsYUFBYSxDQUFDLFNBQVMsRUFBRSxNQUFNLENBQUMsV0FBVyxFQUFFLE1BQU0sQ0FBQztTQUNyRSxDQUFDO0lBQ0osQ0FBQzt3R0F0Q1UsV0FBVzs0R0FBWCxXQUFXLGNBREUsTUFBTTs7NEZBQ25CLFdBQVc7a0JBRHZCLFVBQVU7bUJBQUMsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSwgaW5qZWN0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgdHlwZSB7IEVudGl0eUNvbmZpZywgRmllbGRDb25maWcsIFRhYkNvbmZpZyB9IGZyb20gJ0BkeW5hbWljLWVudGl0eS9jb3JlJztcbmltcG9ydCB7IE1BU0tFRF9ST0xFUyB9IGZyb20gJy4uL3Rva2Vucy9pbmplY3Rpb24tdG9rZW5zJztcblxuLyoqXG4gKiBSYmFjU2VydmljZSDigJQgQUxMIHBlcm1pc3Npb24gY2hlY2tzIGFuZCBtYXNraW5nIGxvZ2ljIGxpdmUgaGVyZS5cbiAqIENSSVRJQ0FMIFNZTkM6IHJlc29sdmVFZmZlY3RpdmVNYXNrKCkgbXVzdCBiZSBpZGVudGljYWwgdG8gcmJhYy51dGlscy5qcyBpbiBkeW5hbWljLWVudGl0eS1zZXJ2ZXIuXG4gKiBDaGFuZ2luZyBvbmUgd2l0aG91dCB0aGUgb3RoZXIgaXMgYSBjcml0aWNhbCBwcm9kdWN0aW9uIGJ1ZyAoQURSLTAwMykuXG4gKi9cbkBJbmplY3RhYmxlKHsgcHJvdmlkZWRJbjogJ3Jvb3QnIH0pXG5leHBvcnQgY2xhc3MgUmJhY1NlcnZpY2Uge1xuICBwcml2YXRlIHJlYWRvbmx5IG1hc2tlZFJvbGVzID0gaW5qZWN0KE1BU0tFRF9ST0xFUywgeyBvcHRpb25hbDogdHJ1ZSB9KSA/PyBbXTtcblxuICAvKipcbiAgICogQ2hlY2sgaWYgYW55IG9mIHRoZSB1c2VyJ3Mgcm9sZXMgc2F0aXNmeSB0aGUgcmVxdWlyZWQgcm9sZXMuXG4gICAqIGhhc1Blcm1pc3Npb24ocm9sZXMsIHVuZGVmaW5lZCkg4oaSIHRydWUgKG5vIHJlc3RyaWN0aW9uKS5cbiAgICovXG4gIGhhc1Blcm1pc3Npb24odXNlclJvbGVzOiBzdHJpbmdbXSwgcmVxdWlyZWRSb2xlcz86IHN0cmluZ1tdKTogYm9vbGVhbiB7XG4gICAgaWYgKCFyZXF1aXJlZFJvbGVzPy5sZW5ndGgpIHJldHVybiB0cnVlO1xuICAgIHJldHVybiB1c2VyUm9sZXMuc29tZShyID0+IHJlcXVpcmVkUm9sZXMuaW5jbHVkZXMocikpO1xuICB9XG5cbiAgLyoqXG4gICAqIEFEUi0wMDM6IDMtbGV2ZWwgT1IgcmVzb2x1dGlvbi5cbiAgICogTVVTVCBtYXRjaCByYmFjLnV0aWxzLmpzIHJlc29sdmVFZmZlY3RpdmVNYXNrKCkgZXhhY3RseS5cbiAgICovXG4gIHJlc29sdmVFZmZlY3RpdmVNYXNrKGZvcm1NYXNrPzogYm9vbGVhbiwgdGFiTWFzaz86IGJvb2xlYW4sIGZpZWxkTWFzaz86IGJvb2xlYW4pOiBib29sZWFuIHtcbiAgICByZXR1cm4gISEoZm9ybU1hc2sgfHwgdGFiTWFzayB8fCBmaWVsZE1hc2spO1xuICB9XG5cbiAgLyoqIENoZWNrIGlmIHRoZSB1c2VyJ3Mgcm9sZXMgaW5jbHVkZSBhbnkgbWFza2VkIHJvbGUgKi9cbiAgaXNVc2VyTWFza2VkUm9sZSh1c2VyUm9sZXM6IHN0cmluZ1tdKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMubWFza2VkUm9sZXMuc29tZShyID0+IHVzZXJSb2xlcy5pbmNsdWRlcyhyKSk7XG4gIH1cblxuICAvKiogRGV0ZXJtaW5lIGlmIGEgc3BlY2lmaWMgZmllbGQgc2hvdWxkIGJlIG1hc2tlZCBmb3IgdGhpcyB1c2VyICovXG4gIHNob3VsZE1hc2tGaWVsZChmaWVsZDogRmllbGRDb25maWcsIHRhYjogVGFiQ29uZmlnIHwgdW5kZWZpbmVkLCBjb25maWc6IEVudGl0eUNvbmZpZywgdXNlclJvbGVzOiBzdHJpbmdbXSk6IGJvb2xlYW4ge1xuICAgIGlmICghdGhpcy5pc1VzZXJNYXNrZWRSb2xlKHVzZXJSb2xlcykpIHJldHVybiBmYWxzZTtcbiAgICByZXR1cm4gdGhpcy5yZXNvbHZlRWZmZWN0aXZlTWFzayhjb25maWcubWFza0RhdGEsIHRhYj8ubWFza0RhdGEsIGZpZWxkLm1hc2tEYXRhKTtcbiAgfVxuXG4gIC8qKiBHZXQgdmlldy9lZGl0L2RlbGV0ZSBwZXJtaXNzaW9ucyBmb3IgYSBjb25maWcgKi9cbiAgZ2V0UGVybWlzc2lvbnMoY29uZmlnOiBFbnRpdHlDb25maWcsIHVzZXJSb2xlczogc3RyaW5nW10pIHtcbiAgICByZXR1cm4ge1xuICAgICAgY2FuVmlldzogdGhpcy5oYXNQZXJtaXNzaW9uKHVzZXJSb2xlcywgY29uZmlnLnBlcm1pc3Npb25zPy52aWV3KSxcbiAgICAgIGNhbkVkaXQ6IHRoaXMuaGFzUGVybWlzc2lvbih1c2VyUm9sZXMsIGNvbmZpZy5wZXJtaXNzaW9ucz8uZWRpdCksXG4gICAgICBjYW5EZWxldGU6IHRoaXMuaGFzUGVybWlzc2lvbih1c2VyUm9sZXMsIGNvbmZpZy5wZXJtaXNzaW9ucz8uZGVsZXRlKSxcbiAgICB9O1xuICB9XG59XG4iXX0=
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Injectable, inject } from '@angular/core';
|
|
2
|
+
import { Validators } from '@angular/forms';
|
|
3
|
+
import { VALIDATOR_REGISTRY } from '../tokens/injection-tokens';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
/**
|
|
6
|
+
* ValidatorRegistryService — resolves validator key strings to Angular ValidatorFns.
|
|
7
|
+
* Built-in validators: 'required', 'email', 'min:N', 'max:N', 'minLength:N', 'maxLength:N'.
|
|
8
|
+
* Consumers can register custom validators via provideNgxDynamicEntity({ validators: {...} }).
|
|
9
|
+
*/
|
|
10
|
+
export class ValidatorRegistryService {
|
|
11
|
+
consumerRegistry = inject(VALIDATOR_REGISTRY, { optional: true }) ?? new Map();
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a validator key to a ValidatorFn.
|
|
14
|
+
* Supports parameterized built-ins like 'min:0', 'maxLength:255'.
|
|
15
|
+
*/
|
|
16
|
+
resolve(validatorKey) {
|
|
17
|
+
// Consumer validators take precedence
|
|
18
|
+
if (this.consumerRegistry.has(validatorKey)) {
|
|
19
|
+
return this.consumerRegistry.get(validatorKey);
|
|
20
|
+
}
|
|
21
|
+
// Built-in validators
|
|
22
|
+
if (validatorKey === 'required')
|
|
23
|
+
return Validators.required;
|
|
24
|
+
if (validatorKey === 'email')
|
|
25
|
+
return Validators.email;
|
|
26
|
+
const [name, param] = validatorKey.split(':');
|
|
27
|
+
const value = parseFloat(param);
|
|
28
|
+
if (name === 'min' && !isNaN(value))
|
|
29
|
+
return Validators.min(value);
|
|
30
|
+
if (name === 'max' && !isNaN(value))
|
|
31
|
+
return Validators.max(value);
|
|
32
|
+
if (name === 'minLength' && !isNaN(value))
|
|
33
|
+
return Validators.minLength(value);
|
|
34
|
+
if (name === 'maxLength' && !isNaN(value))
|
|
35
|
+
return Validators.maxLength(value);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/** Resolve multiple validator keys into a ValidatorFn[] */
|
|
39
|
+
resolveAll(validatorKeys = []) {
|
|
40
|
+
return validatorKeys.map(k => this.resolve(k)).filter((v) => v !== null);
|
|
41
|
+
}
|
|
42
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidatorRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
43
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidatorRegistryService, providedIn: 'root' });
|
|
44
|
+
}
|
|
45
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidatorRegistryService, decorators: [{
|
|
46
|
+
type: Injectable,
|
|
47
|
+
args: [{ providedIn: 'root' }]
|
|
48
|
+
}] });
|
|
49
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9yLXJlZ2lzdHJ5LnNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3NlcnZpY2VzL3ZhbGlkYXRvci1yZWdpc3RyeS5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ25ELE9BQU8sRUFBZ0MsVUFBVSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDMUUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7O0FBRWhFOzs7O0dBSUc7QUFFSCxNQUFNLE9BQU8sd0JBQXdCO0lBQ2xCLGdCQUFnQixHQUFHLE1BQU0sQ0FBQyxrQkFBa0IsRUFBRSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxJQUFJLElBQUksR0FBRyxFQUFFLENBQUM7SUFFaEc7OztPQUdHO0lBQ0gsT0FBTyxDQUFDLFlBQW9CO1FBQzFCLHNDQUFzQztRQUN0QyxJQUFJLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztZQUM1QyxPQUFPLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDLENBQUM7UUFDakQsQ0FBQztRQUVELHNCQUFzQjtRQUN0QixJQUFJLFlBQVksS0FBSyxVQUFVO1lBQUUsT0FBTyxVQUFVLENBQUMsUUFBUSxDQUFDO1FBQzVELElBQUksWUFBWSxLQUFLLE9BQU87WUFBRSxPQUFPLFVBQVUsQ0FBQyxLQUFLLENBQUM7UUFFdEQsTUFBTSxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsR0FBRyxZQUFZLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQzlDLE1BQU0sS0FBSyxHQUFHLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUVoQyxJQUFJLElBQUksS0FBSyxLQUFLLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDO1lBQUUsT0FBTyxVQUFVLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ2xFLElBQUksSUFBSSxLQUFLLEtBQUssSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUM7WUFBRSxPQUFPLFVBQVUsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDbEUsSUFBSSxJQUFJLEtBQUssV0FBVyxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQztZQUFFLE9BQU8sVUFBVSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUM5RSxJQUFJLElBQUksS0FBSyxXQUFXLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDO1lBQUUsT0FBTyxVQUFVLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBRTlFLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELDJEQUEyRDtJQUMzRCxVQUFVLENBQUMsZ0JBQTBCLEVBQUU7UUFDckMsT0FBTyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBb0IsRUFBRSxDQUFDLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FBQztJQUM3RixDQUFDO3dHQS9CVSx3QkFBd0I7NEdBQXhCLHdCQUF3QixjQURYLE1BQU07OzRGQUNuQix3QkFBd0I7a0JBRHBDLFVBQVU7bUJBQUMsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSwgaW5qZWN0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBBYnN0cmFjdENvbnRyb2wsIFZhbGlkYXRvckZuLCBWYWxpZGF0b3JzIH0gZnJvbSAnQGFuZ3VsYXIvZm9ybXMnO1xuaW1wb3J0IHsgVkFMSURBVE9SX1JFR0lTVFJZIH0gZnJvbSAnLi4vdG9rZW5zL2luamVjdGlvbi10b2tlbnMnO1xuXG4vKipcbiAqIFZhbGlkYXRvclJlZ2lzdHJ5U2VydmljZSDigJQgcmVzb2x2ZXMgdmFsaWRhdG9yIGtleSBzdHJpbmdzIHRvIEFuZ3VsYXIgVmFsaWRhdG9yRm5zLlxuICogQnVpbHQtaW4gdmFsaWRhdG9yczogJ3JlcXVpcmVkJywgJ2VtYWlsJywgJ21pbjpOJywgJ21heDpOJywgJ21pbkxlbmd0aDpOJywgJ21heExlbmd0aDpOJy5cbiAqIENvbnN1bWVycyBjYW4gcmVnaXN0ZXIgY3VzdG9tIHZhbGlkYXRvcnMgdmlhIHByb3ZpZGVOZ3hEeW5hbWljRW50aXR5KHsgdmFsaWRhdG9yczogey4uLn0gfSkuXG4gKi9cbkBJbmplY3RhYmxlKHsgcHJvdmlkZWRJbjogJ3Jvb3QnIH0pXG5leHBvcnQgY2xhc3MgVmFsaWRhdG9yUmVnaXN0cnlTZXJ2aWNlIHtcbiAgcHJpdmF0ZSByZWFkb25seSBjb25zdW1lclJlZ2lzdHJ5ID0gaW5qZWN0KFZBTElEQVRPUl9SRUdJU1RSWSwgeyBvcHRpb25hbDogdHJ1ZSB9KSA/PyBuZXcgTWFwKCk7XG5cbiAgLyoqXG4gICAqIFJlc29sdmUgYSB2YWxpZGF0b3Iga2V5IHRvIGEgVmFsaWRhdG9yRm4uXG4gICAqIFN1cHBvcnRzIHBhcmFtZXRlcml6ZWQgYnVpbHQtaW5zIGxpa2UgJ21pbjowJywgJ21heExlbmd0aDoyNTUnLlxuICAgKi9cbiAgcmVzb2x2ZSh2YWxpZGF0b3JLZXk6IHN0cmluZyk6IFZhbGlkYXRvckZuIHwgbnVsbCB7XG4gICAgLy8gQ29uc3VtZXIgdmFsaWRhdG9ycyB0YWtlIHByZWNlZGVuY2VcbiAgICBpZiAodGhpcy5jb25zdW1lclJlZ2lzdHJ5Lmhhcyh2YWxpZGF0b3JLZXkpKSB7XG4gICAgICByZXR1cm4gdGhpcy5jb25zdW1lclJlZ2lzdHJ5LmdldCh2YWxpZGF0b3JLZXkpO1xuICAgIH1cblxuICAgIC8vIEJ1aWx0LWluIHZhbGlkYXRvcnNcbiAgICBpZiAodmFsaWRhdG9yS2V5ID09PSAncmVxdWlyZWQnKSByZXR1cm4gVmFsaWRhdG9ycy5yZXF1aXJlZDtcbiAgICBpZiAodmFsaWRhdG9yS2V5ID09PSAnZW1haWwnKSByZXR1cm4gVmFsaWRhdG9ycy5lbWFpbDtcblxuICAgIGNvbnN0IFtuYW1lLCBwYXJhbV0gPSB2YWxpZGF0b3JLZXkuc3BsaXQoJzonKTtcbiAgICBjb25zdCB2YWx1ZSA9IHBhcnNlRmxvYXQocGFyYW0pO1xuXG4gICAgaWYgKG5hbWUgPT09ICdtaW4nICYmICFpc05hTih2YWx1ZSkpIHJldHVybiBWYWxpZGF0b3JzLm1pbih2YWx1ZSk7XG4gICAgaWYgKG5hbWUgPT09ICdtYXgnICYmICFpc05hTih2YWx1ZSkpIHJldHVybiBWYWxpZGF0b3JzLm1heCh2YWx1ZSk7XG4gICAgaWYgKG5hbWUgPT09ICdtaW5MZW5ndGgnICYmICFpc05hTih2YWx1ZSkpIHJldHVybiBWYWxpZGF0b3JzLm1pbkxlbmd0aCh2YWx1ZSk7XG4gICAgaWYgKG5hbWUgPT09ICdtYXhMZW5ndGgnICYmICFpc05hTih2YWx1ZSkpIHJldHVybiBWYWxpZGF0b3JzLm1heExlbmd0aCh2YWx1ZSk7XG5cbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIC8qKiBSZXNvbHZlIG11bHRpcGxlIHZhbGlkYXRvciBrZXlzIGludG8gYSBWYWxpZGF0b3JGbltdICovXG4gIHJlc29sdmVBbGwodmFsaWRhdG9yS2V5czogc3RyaW5nW10gPSBbXSk6IFZhbGlkYXRvckZuW10ge1xuICAgIHJldHVybiB2YWxpZGF0b3JLZXlzLm1hcChrID0+IHRoaXMucmVzb2x2ZShrKSkuZmlsdGVyKCh2KTogdiBpcyBWYWxpZGF0b3JGbiA9PiB2ICE9PSBudWxsKTtcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Injectable, inject } from '@angular/core';
|
|
2
|
+
import { MIGRATION_STRATEGY } from '../tokens/injection-tokens';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* VersionService — checks config version staleness and migration strategy.
|
|
6
|
+
* ADR-005: Frontend strategy is 'strict' | 'graceful' only. Never 'auto'.
|
|
7
|
+
*/
|
|
8
|
+
export class VersionService {
|
|
9
|
+
strategy = inject(MIGRATION_STRATEGY, { optional: true }) ?? 'graceful';
|
|
10
|
+
/**
|
|
11
|
+
* Check if a record is stale compared to the current config version.
|
|
12
|
+
* Returns true if the record needs migration.
|
|
13
|
+
*/
|
|
14
|
+
needsMigration(record, config) {
|
|
15
|
+
if (!record._configVersion)
|
|
16
|
+
return false;
|
|
17
|
+
return record._configVersion < config.version || !!record._needsMigration;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get the effective migration strategy for frontend use.
|
|
21
|
+
* Only 'strict' or 'graceful' — never 'auto' (ADR-005).
|
|
22
|
+
*/
|
|
23
|
+
getStrategy() {
|
|
24
|
+
return this.strategy === 'strict' ? 'strict' : 'graceful';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Determine if form submission should be blocked for a stale record.
|
|
28
|
+
* 'strict' → block; 'graceful' → allow with warning.
|
|
29
|
+
*/
|
|
30
|
+
shouldBlockSubmit(record, config) {
|
|
31
|
+
return this.getStrategy() === 'strict' && this.needsMigration(record, config);
|
|
32
|
+
}
|
|
33
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VersionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
34
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VersionService, providedIn: 'root' });
|
|
35
|
+
}
|
|
36
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VersionService, decorators: [{
|
|
37
|
+
type: Injectable,
|
|
38
|
+
args: [{ providedIn: 'root' }]
|
|
39
|
+
}] });
|
|
40
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmVyc2lvbi5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9zZXJ2aWNlcy92ZXJzaW9uLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFbkQsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7O0FBRWhFOzs7R0FHRztBQUVILE1BQU0sT0FBTyxjQUFjO0lBQ1IsUUFBUSxHQUFHLE1BQU0sQ0FBQyxrQkFBa0IsRUFBRSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxJQUFJLFVBQVUsQ0FBQztJQUV6Rjs7O09BR0c7SUFDSCxjQUFjLENBQUMsTUFBZ0MsRUFBRSxNQUFvQjtRQUNuRSxJQUFJLENBQUMsTUFBTSxDQUFDLGNBQWM7WUFBRSxPQUFPLEtBQUssQ0FBQztRQUN6QyxPQUFPLE1BQU0sQ0FBQyxjQUFjLEdBQUcsTUFBTSxDQUFDLE9BQU8sSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFDLGVBQWUsQ0FBQztJQUM1RSxDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsV0FBVztRQUNULE9BQU8sSUFBSSxDQUFDLFFBQVEsS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDO0lBQzVELENBQUM7SUFFRDs7O09BR0c7SUFDSCxpQkFBaUIsQ0FBQyxNQUFnQyxFQUFFLE1BQW9CO1FBQ3RFLE9BQU8sSUFBSSxDQUFDLFdBQVcsRUFBRSxLQUFLLFFBQVEsSUFBSSxJQUFJLENBQUMsY0FBYyxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQztJQUNoRixDQUFDO3dHQTFCVSxjQUFjOzRHQUFkLGNBQWMsY0FERCxNQUFNOzs0RkFDbkIsY0FBYztrQkFEMUIsVUFBVTttQkFBQyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3RhYmxlLCBpbmplY3QgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB0eXBlIHsgVmVyc2lvbmVkUmVjb3JkLCBFbnRpdHlDb25maWcsIE1pZ3JhdGlvblN0cmF0ZWd5IH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuaW1wb3J0IHsgTUlHUkFUSU9OX1NUUkFURUdZIH0gZnJvbSAnLi4vdG9rZW5zL2luamVjdGlvbi10b2tlbnMnO1xuXG4vKipcbiAqIFZlcnNpb25TZXJ2aWNlIOKAlCBjaGVja3MgY29uZmlnIHZlcnNpb24gc3RhbGVuZXNzIGFuZCBtaWdyYXRpb24gc3RyYXRlZ3kuXG4gKiBBRFItMDA1OiBGcm9udGVuZCBzdHJhdGVneSBpcyAnc3RyaWN0JyB8ICdncmFjZWZ1bCcgb25seS4gTmV2ZXIgJ2F1dG8nLlxuICovXG5ASW5qZWN0YWJsZSh7IHByb3ZpZGVkSW46ICdyb290JyB9KVxuZXhwb3J0IGNsYXNzIFZlcnNpb25TZXJ2aWNlIHtcbiAgcHJpdmF0ZSByZWFkb25seSBzdHJhdGVneSA9IGluamVjdChNSUdSQVRJT05fU1RSQVRFR1ksIHsgb3B0aW9uYWw6IHRydWUgfSkgPz8gJ2dyYWNlZnVsJztcblxuICAvKipcbiAgICogQ2hlY2sgaWYgYSByZWNvcmQgaXMgc3RhbGUgY29tcGFyZWQgdG8gdGhlIGN1cnJlbnQgY29uZmlnIHZlcnNpb24uXG4gICAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgcmVjb3JkIG5lZWRzIG1pZ3JhdGlvbi5cbiAgICovXG4gIG5lZWRzTWlncmF0aW9uKHJlY29yZDogUGFydGlhbDxWZXJzaW9uZWRSZWNvcmQ+LCBjb25maWc6IEVudGl0eUNvbmZpZyk6IGJvb2xlYW4ge1xuICAgIGlmICghcmVjb3JkLl9jb25maWdWZXJzaW9uKSByZXR1cm4gZmFsc2U7XG4gICAgcmV0dXJuIHJlY29yZC5fY29uZmlnVmVyc2lvbiA8IGNvbmZpZy52ZXJzaW9uIHx8ICEhcmVjb3JkLl9uZWVkc01pZ3JhdGlvbjtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgdGhlIGVmZmVjdGl2ZSBtaWdyYXRpb24gc3RyYXRlZ3kgZm9yIGZyb250ZW5kIHVzZS5cbiAgICogT25seSAnc3RyaWN0JyBvciAnZ3JhY2VmdWwnIOKAlCBuZXZlciAnYXV0bycgKEFEUi0wMDUpLlxuICAgKi9cbiAgZ2V0U3RyYXRlZ3koKTogJ3N0cmljdCcgfCAnZ3JhY2VmdWwnIHtcbiAgICByZXR1cm4gdGhpcy5zdHJhdGVneSA9PT0gJ3N0cmljdCcgPyAnc3RyaWN0JyA6ICdncmFjZWZ1bCc7XG4gIH1cblxuICAvKipcbiAgICogRGV0ZXJtaW5lIGlmIGZvcm0gc3VibWlzc2lvbiBzaG91bGQgYmUgYmxvY2tlZCBmb3IgYSBzdGFsZSByZWNvcmQuXG4gICAqICdzdHJpY3QnIOKGkiBibG9jazsgJ2dyYWNlZnVsJyDihpIgYWxsb3cgd2l0aCB3YXJuaW5nLlxuICAgKi9cbiAgc2hvdWxkQmxvY2tTdWJtaXQocmVjb3JkOiBQYXJ0aWFsPFZlcnNpb25lZFJlY29yZD4sIGNvbmZpZzogRW50aXR5Q29uZmlnKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMuZ2V0U3RyYXRlZ3koKSA9PT0gJ3N0cmljdCcgJiYgdGhpcy5uZWVkc01pZ3JhdGlvbihyZWNvcmQsIGNvbmZpZyk7XG4gIH1cbn1cbiJdfQ==
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, signal, computed, inject } from '@angular/core';
|
|
2
|
+
import { RbacService } from '../services/rbac.service';
|
|
3
|
+
import { VersionService } from '../services/version.service';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
/**
|
|
6
|
+
* DynamicTableComponent — renders entity records as a searchable, sortable, paginated table.
|
|
7
|
+
* Columns determined by FieldConfig.tableColumn === true.
|
|
8
|
+
* Cell masking applied via RbacService (defense-in-depth).
|
|
9
|
+
* Pagination, sort, and search emit events — parent controls data fetching (smart/dumb pattern).
|
|
10
|
+
*/
|
|
11
|
+
export class DynamicTableComponent {
|
|
12
|
+
// ─── Inputs ───────────────────────────────────────────────────────────────
|
|
13
|
+
config;
|
|
14
|
+
data = [];
|
|
15
|
+
userRoles = [];
|
|
16
|
+
language = 'en';
|
|
17
|
+
loading = false;
|
|
18
|
+
error = null;
|
|
19
|
+
totalRecords = 0;
|
|
20
|
+
pageSize = 20;
|
|
21
|
+
currentPage = 1;
|
|
22
|
+
// ─── Outputs ──────────────────────────────────────────────────────────────
|
|
23
|
+
rowClick = new EventEmitter();
|
|
24
|
+
rowEdit = new EventEmitter();
|
|
25
|
+
rowDelete = new EventEmitter();
|
|
26
|
+
pageChange = new EventEmitter();
|
|
27
|
+
pageSizeChange = new EventEmitter();
|
|
28
|
+
sortChange = new EventEmitter();
|
|
29
|
+
searchChange = new EventEmitter();
|
|
30
|
+
// ─── Services ─────────────────────────────────────────────────────────────
|
|
31
|
+
rbacService = inject(RbacService);
|
|
32
|
+
versionService = inject(VersionService);
|
|
33
|
+
// ─── Signals ──────────────────────────────────────────────────────────────
|
|
34
|
+
sortField = signal('');
|
|
35
|
+
sortDir = signal('desc');
|
|
36
|
+
searchTerm = signal('');
|
|
37
|
+
// ─── Computed ─────────────────────────────────────────────────────────────
|
|
38
|
+
tableColumns = computed(() => (this.config?.fields || []).filter(f => f.tableColumn === true));
|
|
39
|
+
get permissions() {
|
|
40
|
+
return this.rbacService.getPermissions(this.config, this.userRoles);
|
|
41
|
+
}
|
|
42
|
+
get totalPages() {
|
|
43
|
+
return Math.ceil(this.totalRecords / this.pageSize) || 0;
|
|
44
|
+
}
|
|
45
|
+
ngOnChanges(_changes) {
|
|
46
|
+
// Pre-resolve masking per column once (ONEHERMES hardening: pre-resolved at extract time)
|
|
47
|
+
this._resolveColumnMasks();
|
|
48
|
+
}
|
|
49
|
+
columnMaskCache = new Map();
|
|
50
|
+
_resolveColumnMasks() {
|
|
51
|
+
if (!this.config)
|
|
52
|
+
return;
|
|
53
|
+
this.columnMaskCache.clear();
|
|
54
|
+
for (const field of this.tableColumns()) {
|
|
55
|
+
const tab = (this.config.tabs || []).find(t => t.id === field.tab);
|
|
56
|
+
const masked = this.rbacService.shouldMaskField(field, tab, this.config, this.userRoles);
|
|
57
|
+
this.columnMaskCache.set(field.id, masked);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
61
|
+
isCellMasked(fieldId) {
|
|
62
|
+
return this.columnMaskCache.get(fieldId) ?? false;
|
|
63
|
+
}
|
|
64
|
+
getCellValue(record, fieldId) {
|
|
65
|
+
if (this.isCellMasked(fieldId))
|
|
66
|
+
return 'XXXXXXXXX';
|
|
67
|
+
const value = record[fieldId];
|
|
68
|
+
if (value === null || value === undefined)
|
|
69
|
+
return '—';
|
|
70
|
+
if (typeof value === 'boolean')
|
|
71
|
+
return value ? 'Yes' : 'No';
|
|
72
|
+
if (value instanceof Date || (typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}/))) {
|
|
73
|
+
try {
|
|
74
|
+
return new Date(value).toLocaleDateString();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return String(value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(value))
|
|
81
|
+
return value.join(', ');
|
|
82
|
+
return String(value);
|
|
83
|
+
}
|
|
84
|
+
needsMigration(record) {
|
|
85
|
+
return this.versionService.needsMigration(record, this.config);
|
|
86
|
+
}
|
|
87
|
+
getColumnLabel(fieldId) {
|
|
88
|
+
const field = (this.config?.fields || []).find(f => f.id === fieldId);
|
|
89
|
+
return field?.label?.[this.language] || field?.label?.['en'] || fieldId;
|
|
90
|
+
}
|
|
91
|
+
// ─── Sort / Search / Pagination ───────────────────────────────────────────
|
|
92
|
+
onSort(fieldId) {
|
|
93
|
+
if (this.sortField() === fieldId) {
|
|
94
|
+
const next = this.sortDir() === 'asc' ? 'desc' : 'asc';
|
|
95
|
+
this.sortDir.set(next);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
this.sortField.set(fieldId);
|
|
99
|
+
this.sortDir.set('asc');
|
|
100
|
+
}
|
|
101
|
+
this.sortChange.emit({ field: this.sortField(), dir: this.sortDir() });
|
|
102
|
+
}
|
|
103
|
+
onSearch(event) {
|
|
104
|
+
const value = event.target.value;
|
|
105
|
+
this.searchTerm.set(value);
|
|
106
|
+
this.searchChange.emit(value);
|
|
107
|
+
}
|
|
108
|
+
onPageChange(page) {
|
|
109
|
+
if (page < 1 || page > this.totalPages)
|
|
110
|
+
return;
|
|
111
|
+
this.pageChange.emit(page);
|
|
112
|
+
}
|
|
113
|
+
onEdit(event, record) {
|
|
114
|
+
event.stopPropagation();
|
|
115
|
+
this.rowEdit.emit(record);
|
|
116
|
+
}
|
|
117
|
+
onDelete(event, record) {
|
|
118
|
+
event.stopPropagation();
|
|
119
|
+
this.rowDelete.emit(record);
|
|
120
|
+
}
|
|
121
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
122
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DynamicTableComponent, isStandalone: true, selector: "ngx-dynamic-table", inputs: { config: "config", data: "data", userRoles: "userRoles", language: "language", loading: "loading", error: "error", totalRecords: "totalRecords", pageSize: "pageSize", currentPage: "currentPage" }, outputs: { rowClick: "rowClick", rowEdit: "rowEdit", rowDelete: "rowDelete", pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", searchChange: "searchChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"ngx-table\">\n\n <!-- Search bar -->\n <div class=\"ngx-table__toolbar\">\n <input\n class=\"ngx-table__search\"\n type=\"search\"\n placeholder=\"Search...\"\n [value]=\"searchTerm()\"\n (input)=\"onSearch($event)\"\n aria-label=\"Search records\"\n />\n </div>\n\n <!-- Error -->\n @if (error) {\n <div class=\"ngx-table__error\" role=\"alert\">{{ error }}</div>\n }\n\n <!-- Table -->\n <table class=\"ngx-table__table\">\n <thead>\n <tr>\n @for (col of tableColumns(); track col.id) {\n <th\n class=\"ngx-table__th\"\n [style.width]=\"col.columnWidth || 'auto'\"\n (click)=\"onSort(col.id)\"\n [attr.aria-sort]=\"sortField() === col.id ? sortDir() : 'none'\"\n role=\"columnheader\"\n >\n {{ getColumnLabel(col.id) }}\n @if (sortField() === col.id) {\n <span class=\"ngx-table__sort-icon\">{{ sortDir() === 'asc' ? '\u2191' : '\u2193' }}</span>\n }\n </th>\n }\n <!-- Actions column -->\n @if (permissions.canEdit || permissions.canDelete) {\n <th class=\"ngx-table__th ngx-table__th--actions\">Actions</th>\n }\n </tr>\n </thead>\n <tbody>\n @if (loading) {\n <tr>\n <td [attr.colspan]=\"tableColumns().length + 1\" class=\"ngx-table__loading\">Loading\u2026</td>\n </tr>\n } @else if (!data || !data.length) {\n <tr>\n <td [attr.colspan]=\"tableColumns().length + 1\" class=\"ngx-table__empty\">No records found</td>\n </tr>\n } @else {\n @for (record of data; track record['_id']) {\n <tr class=\"ngx-table__row\" (click)=\"rowClick.emit(record)\">\n @for (col of tableColumns(); track col.id) {\n <td\n class=\"ngx-table__td\"\n [class.ngx-table__td--masked]=\"isCellMasked(col.id)\"\n >\n {{ getCellValue(record, col.id) }}\n <!-- Migration badge \u2014 shows when record is stale -->\n @if (tableColumns().length > 0 && col.id === tableColumns()[0].id && needsMigration(record)) {\n <span class=\"ngx-table__migration-badge\" title=\"Needs migration\">\u26A0</span>\n }\n </td>\n }\n <!-- Action buttons \u2014 gated by RBAC permissions -->\n @if (permissions.canEdit || permissions.canDelete) {\n <td class=\"ngx-table__td ngx-table__actions\">\n @if (permissions.canEdit) {\n <button\n type=\"button\"\n class=\"ngx-table__action ngx-table__action--edit\"\n (click)=\"onEdit($event, record)\"\n title=\"Edit\"\n >\u270F</button>\n }\n @if (permissions.canDelete) {\n <button\n type=\"button\"\n class=\"ngx-table__action ngx-table__action--delete\"\n (click)=\"onDelete($event, record)\"\n title=\"Delete\"\n >\uD83D\uDDD1</button>\n }\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n\n <!-- Pagination -->\n @if (totalPages > 1) {\n <div class=\"ngx-table__pagination\">\n <button\n type=\"button\"\n class=\"ngx-table__page-btn\"\n [disabled]=\"currentPage <= 1\"\n (click)=\"onPageChange(currentPage - 1)\"\n aria-label=\"Previous page\"\n >\u2190</button>\n\n <span class=\"ngx-table__page-info\">\n Page {{ currentPage }} of {{ totalPages }} ({{ totalRecords }} records)\n </span>\n\n <button\n type=\"button\"\n class=\"ngx-table__page-btn\"\n [disabled]=\"currentPage >= totalPages\"\n (click)=\"onPageChange(currentPage + 1)\"\n aria-label=\"Next page\"\n >\u2192</button>\n </div>\n }\n\n</div>\n" });
|
|
123
|
+
}
|
|
124
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicTableComponent, decorators: [{
|
|
125
|
+
type: Component,
|
|
126
|
+
args: [{ selector: 'ngx-dynamic-table', standalone: true, imports: [], template: "<div class=\"ngx-table\">\n\n <!-- Search bar -->\n <div class=\"ngx-table__toolbar\">\n <input\n class=\"ngx-table__search\"\n type=\"search\"\n placeholder=\"Search...\"\n [value]=\"searchTerm()\"\n (input)=\"onSearch($event)\"\n aria-label=\"Search records\"\n />\n </div>\n\n <!-- Error -->\n @if (error) {\n <div class=\"ngx-table__error\" role=\"alert\">{{ error }}</div>\n }\n\n <!-- Table -->\n <table class=\"ngx-table__table\">\n <thead>\n <tr>\n @for (col of tableColumns(); track col.id) {\n <th\n class=\"ngx-table__th\"\n [style.width]=\"col.columnWidth || 'auto'\"\n (click)=\"onSort(col.id)\"\n [attr.aria-sort]=\"sortField() === col.id ? sortDir() : 'none'\"\n role=\"columnheader\"\n >\n {{ getColumnLabel(col.id) }}\n @if (sortField() === col.id) {\n <span class=\"ngx-table__sort-icon\">{{ sortDir() === 'asc' ? '\u2191' : '\u2193' }}</span>\n }\n </th>\n }\n <!-- Actions column -->\n @if (permissions.canEdit || permissions.canDelete) {\n <th class=\"ngx-table__th ngx-table__th--actions\">Actions</th>\n }\n </tr>\n </thead>\n <tbody>\n @if (loading) {\n <tr>\n <td [attr.colspan]=\"tableColumns().length + 1\" class=\"ngx-table__loading\">Loading\u2026</td>\n </tr>\n } @else if (!data || !data.length) {\n <tr>\n <td [attr.colspan]=\"tableColumns().length + 1\" class=\"ngx-table__empty\">No records found</td>\n </tr>\n } @else {\n @for (record of data; track record['_id']) {\n <tr class=\"ngx-table__row\" (click)=\"rowClick.emit(record)\">\n @for (col of tableColumns(); track col.id) {\n <td\n class=\"ngx-table__td\"\n [class.ngx-table__td--masked]=\"isCellMasked(col.id)\"\n >\n {{ getCellValue(record, col.id) }}\n <!-- Migration badge \u2014 shows when record is stale -->\n @if (tableColumns().length > 0 && col.id === tableColumns()[0].id && needsMigration(record)) {\n <span class=\"ngx-table__migration-badge\" title=\"Needs migration\">\u26A0</span>\n }\n </td>\n }\n <!-- Action buttons \u2014 gated by RBAC permissions -->\n @if (permissions.canEdit || permissions.canDelete) {\n <td class=\"ngx-table__td ngx-table__actions\">\n @if (permissions.canEdit) {\n <button\n type=\"button\"\n class=\"ngx-table__action ngx-table__action--edit\"\n (click)=\"onEdit($event, record)\"\n title=\"Edit\"\n >\u270F</button>\n }\n @if (permissions.canDelete) {\n <button\n type=\"button\"\n class=\"ngx-table__action ngx-table__action--delete\"\n (click)=\"onDelete($event, record)\"\n title=\"Delete\"\n >\uD83D\uDDD1</button>\n }\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n\n <!-- Pagination -->\n @if (totalPages > 1) {\n <div class=\"ngx-table__pagination\">\n <button\n type=\"button\"\n class=\"ngx-table__page-btn\"\n [disabled]=\"currentPage <= 1\"\n (click)=\"onPageChange(currentPage - 1)\"\n aria-label=\"Previous page\"\n >\u2190</button>\n\n <span class=\"ngx-table__page-info\">\n Page {{ currentPage }} of {{ totalPages }} ({{ totalRecords }} records)\n </span>\n\n <button\n type=\"button\"\n class=\"ngx-table__page-btn\"\n [disabled]=\"currentPage >= totalPages\"\n (click)=\"onPageChange(currentPage + 1)\"\n aria-label=\"Next page\"\n >\u2192</button>\n </div>\n }\n\n</div>\n" }]
|
|
127
|
+
}], propDecorators: { config: [{
|
|
128
|
+
type: Input
|
|
129
|
+
}], data: [{
|
|
130
|
+
type: Input
|
|
131
|
+
}], userRoles: [{
|
|
132
|
+
type: Input
|
|
133
|
+
}], language: [{
|
|
134
|
+
type: Input
|
|
135
|
+
}], loading: [{
|
|
136
|
+
type: Input
|
|
137
|
+
}], error: [{
|
|
138
|
+
type: Input
|
|
139
|
+
}], totalRecords: [{
|
|
140
|
+
type: Input
|
|
141
|
+
}], pageSize: [{
|
|
142
|
+
type: Input
|
|
143
|
+
}], currentPage: [{
|
|
144
|
+
type: Input
|
|
145
|
+
}], rowClick: [{
|
|
146
|
+
type: Output
|
|
147
|
+
}], rowEdit: [{
|
|
148
|
+
type: Output
|
|
149
|
+
}], rowDelete: [{
|
|
150
|
+
type: Output
|
|
151
|
+
}], pageChange: [{
|
|
152
|
+
type: Output
|
|
153
|
+
}], pageSizeChange: [{
|
|
154
|
+
type: Output
|
|
155
|
+
}], sortChange: [{
|
|
156
|
+
type: Output
|
|
157
|
+
}], searchChange: [{
|
|
158
|
+
type: Output
|
|
159
|
+
}] } });
|
|
160
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHluYW1pYy10YWJsZS5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3RhYmxlL2R5bmFtaWMtdGFibGUuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vc3JjL2xpYi90YWJsZS9keW5hbWljLXRhYmxlLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxZQUFZLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQTRCLE1BQU0sZUFBZSxDQUFDO0FBRTNILE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sNkJBQTZCLENBQUM7O0FBRTdEOzs7OztHQUtHO0FBT0gsTUFBTSxPQUFPLHFCQUFxQjtJQUNoQyw2RUFBNkU7SUFDcEUsTUFBTSxDQUFnQjtJQUN0QixJQUFJLEdBQXNCLEVBQUUsQ0FBQztJQUM3QixTQUFTLEdBQWEsRUFBRSxDQUFDO0lBQ3pCLFFBQVEsR0FBVyxJQUFJLENBQUM7SUFDeEIsT0FBTyxHQUFZLEtBQUssQ0FBQztJQUN6QixLQUFLLEdBQWtCLElBQUksQ0FBQztJQUM1QixZQUFZLEdBQVcsQ0FBQyxDQUFDO0lBQ3pCLFFBQVEsR0FBVyxFQUFFLENBQUM7SUFDdEIsV0FBVyxHQUFXLENBQUMsQ0FBQztJQUVqQyw2RUFBNkU7SUFDbkUsUUFBUSxHQUFHLElBQUksWUFBWSxFQUFtQixDQUFDO0lBQy9DLE9BQU8sR0FBRyxJQUFJLFlBQVksRUFBbUIsQ0FBQztJQUM5QyxTQUFTLEdBQUcsSUFBSSxZQUFZLEVBQW1CLENBQUM7SUFDaEQsVUFBVSxHQUFHLElBQUksWUFBWSxFQUFVLENBQUM7SUFDeEMsY0FBYyxHQUFHLElBQUksWUFBWSxFQUFVLENBQUM7SUFDNUMsVUFBVSxHQUFHLElBQUksWUFBWSxF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SEE7OzRGRHZHYSxxQkFBcUI7a0JBTmpDLFNBQVM7K0JBQ0UsbUJBQW1CLGNBQ2pCLElBQUksV0FDUCxFQUFFOzhCQUtGLE1BQU07c0JBQWQsS0FBSztnQkFDRyxJQUFJO3NCQUFaLEtBQUs7Z0JBQ0csU0FBUztzQkFBakIsS0FBSztnQkFDRyxRQUFRO3NCQUFoQixLQUFLO2dCQUNHLE9BQU87c0JBQWYsS0FBSztnQkFDRyxLQUFLO3NCQUFiLEtBQUs7Z0JBQ0csWUFBWTtzQkFBcEIsS0FBSztnQkFDRyxRQUFRO3NCQUFoQixLQUFLO2dCQUNHLFdBQVc7c0JBQW5CLEtBQUs7Z0JBR0ksUUFBUTtzQkFBakIsTUFBTTtnQkFDRyxPQUFPO3NCQUFoQixNQUFNO2dCQUNHLFNBQVM7c0JBQWxCLE1BQU07Z0JBQ0csVUFBVTtzQkFBbkIsTUFBTTtnQkFDRyxjQUFjO3NCQUF2QixNQUFNO2dCQUNHLFVBQVU7c0JBQW5CLE1BQU07Z0JBQ0csWUFBWTtzQkFBckIsTUFBTSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbXBvbmVudCwgSW5wdXQsIE91dHB1dCwgRXZlbnRFbWl0dGVyLCBzaWduYWwsIGNvbXB1dGVkLCBpbmplY3QsIE9uQ2hhbmdlcywgU2ltcGxlQ2hhbmdlcyB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHR5cGUgeyBFbnRpdHlDb25maWcsIFZlcnNpb25lZFJlY29yZCB9IGZyb20gJ0BkeW5hbWljLWVudGl0eS9jb3JlJztcbmltcG9ydCB7IFJiYWNTZXJ2aWNlIH0gZnJvbSAnLi4vc2VydmljZXMvcmJhYy5zZXJ2aWNlJztcbmltcG9ydCB7IFZlcnNpb25TZXJ2aWNlIH0gZnJvbSAnLi4vc2VydmljZXMvdmVyc2lvbi5zZXJ2aWNlJztcblxuLyoqXG4gKiBEeW5hbWljVGFibGVDb21wb25lbnQg4oCUIHJlbmRlcnMgZW50aXR5IHJlY29yZHMgYXMgYSBzZWFyY2hhYmxlLCBzb3J0YWJsZSwgcGFnaW5hdGVkIHRhYmxlLlxuICogQ29sdW1ucyBkZXRlcm1pbmVkIGJ5IEZpZWxkQ29uZmlnLnRhYmxlQ29sdW1uID09PSB0cnVlLlxuICogQ2VsbCBtYXNraW5nIGFwcGxpZWQgdmlhIFJiYWNTZXJ2aWNlIChkZWZlbnNlLWluLWRlcHRoKS5cbiAqIFBhZ2luYXRpb24sIHNvcnQsIGFuZCBzZWFyY2ggZW1pdCBldmVudHMg4oCUIHBhcmVudCBjb250cm9scyBkYXRhIGZldGNoaW5nIChzbWFydC9kdW1iIHBhdHRlcm4pLlxuICovXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICduZ3gtZHluYW1pYy10YWJsZScsXG4gIHN0YW5kYWxvbmU6IHRydWUsXG4gIGltcG9ydHM6IFtdLFxuICB0ZW1wbGF0ZVVybDogJy4vZHluYW1pYy10YWJsZS5jb21wb25lbnQuaHRtbCcsXG59KVxuZXhwb3J0IGNsYXNzIER5bmFtaWNUYWJsZUNvbXBvbmVudCBpbXBsZW1lbnRzIE9uQ2hhbmdlcyB7XG4gIC8vIOKUgOKUgOKUgCBJbnB1dHMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG4gIEBJbnB1dCgpIGNvbmZpZyE6IEVudGl0eUNvbmZpZztcbiAgQElucHV0KCkgZGF0YTogVmVyc2lvbmVkUmVjb3JkW10gPSBbXTtcbiAgQElucHV0KCkgdXNlclJvbGVzOiBzdHJpbmdbXSA9IFtdO1xuICBASW5wdXQoKSBsYW5ndWFnZTogc3RyaW5nID0gJ2VuJztcbiAgQElucHV0KCkgbG9hZGluZzogYm9vbGVhbiA9IGZhbHNlO1xuICBASW5wdXQoKSBlcnJvcjogc3RyaW5nIHwgbnVsbCA9IG51bGw7XG4gIEBJbnB1dCgpIHRvdGFsUmVjb3JkczogbnVtYmVyID0gMDtcbiAgQElucHV0KCkgcGFnZVNpemU6IG51bWJlciA9IDIwO1xuICBASW5wdXQoKSBjdXJyZW50UGFnZTogbnVtYmVyID0gMTtcblxuICAvLyDilIDilIDilIAgT3V0cHV0cyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbiAgQE91dHB1dCgpIHJvd0NsaWNrID0gbmV3IEV2ZW50RW1pdHRlcjxWZXJzaW9uZWRSZWNvcmQ+KCk7XG4gIEBPdXRwdXQoKSByb3dFZGl0ID0gbmV3IEV2ZW50RW1pdHRlcjxWZXJzaW9uZWRSZWNvcmQ+KCk7XG4gIEBPdXRwdXQoKSByb3dEZWxldGUgPSBuZXcgRXZlbnRFbWl0dGVyPFZlcnNpb25lZFJlY29yZD4oKTtcbiAgQE91dHB1dCgpIHBhZ2VDaGFuZ2UgPSBuZXcgRXZlbnRFbWl0dGVyPG51bWJlcj4oKTtcbiAgQE91dHB1dCgpIHBhZ2VTaXplQ2hhbmdlID0gbmV3IEV2ZW50RW1pdHRlcjxudW1iZXI+KCk7XG4gIEBPdXRwdXQoKSBzb3J0Q2hhbmdlID0gbmV3IEV2ZW50RW1pdHRlcjx7IGZpZWxkOiBzdHJpbmc7IGRpcjogJ2FzYycgfCAnZGVzYycgfT4oKTtcbiAgQE91dHB1dCgpIHNlYXJjaENoYW5nZSA9IG5ldyBFdmVudEVtaXR0ZXI8c3RyaW5nPigpO1xuXG4gIC8vIOKUgOKUgOKUgCBTZXJ2aWNlcyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbiAgcHJpdmF0ZSByZWFkb25seSByYmFjU2VydmljZSA9IGluamVjdChSYmFjU2VydmljZSk7XG4gIHByaXZhdGUgcmVhZG9ubHkgdmVyc2lvblNlcnZpY2UgPSBpbmplY3QoVmVyc2lvblNlcnZpY2UpO1xuXG4gIC8vIOKUgOKUgOKUgCBTaWduYWxzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuICByZWFkb25seSBzb3J0RmllbGQgPSBzaWduYWw8c3RyaW5nPignJyk7XG4gIHJlYWRvbmx5IHNvcnREaXIgPSBzaWduYWw8J2FzYycgfCAnZGVzYyc+KCdkZXNjJyk7XG4gIHJlYWRvbmx5IHNlYXJjaFRlcm0gPSBzaWduYWw8c3RyaW5nPignJyk7XG5cbiAgLy8g4pSA4pSA4pSAIENvbXB1dGVkIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuICByZWFkb25seSB0YWJsZUNvbHVtbnMgPSBjb21wdXRlZCgoKSA9PiBcbiAgICAodGhpcy5jb25maWc/LmZpZWxkcyB8fCBbXSkuZmlsdGVyKGYgPT4gZi50YWJsZUNvbHVtbiA9PT0gdHJ1ZSlcbiAgKTtcblxuICBnZXQgcGVybWlzc2lvbnMoKSB7XG4gICAgcmV0dXJuIHRoaXMucmJhY1NlcnZpY2UuZ2V0UGVybWlzc2lvbnModGhpcy5jb25maWcsIHRoaXMudXNlclJvbGVzKTtcbiAgfVxuXG4gIGdldCB0b3RhbFBhZ2VzKCk6IG51bWJlciB7XG4gICAgcmV0dXJuIE1hdGguY2VpbCh0aGlzLnRvdGFsUmVjb3JkcyAvIHRoaXMucGFnZVNpemUpIHx8IDA7XG4gIH1cblxuICBuZ09uQ2hhbmdlcyhfY2hhbmdlczogU2ltcGxlQ2hhbmdlcyk6IHZvaWQge1xuICAgIC8vIFByZS1yZXNvbHZlIG1hc2tpbmcgcGVyIGNvbHVtbiBvbmNlIChPTkVIRVJNRVMgaGFyZGVuaW5nOiBwcmUtcmVzb2x2ZWQgYXQgZXh0cmFjdCB0aW1lKVxuICAgIHRoaXMuX3Jlc29sdmVDb2x1bW5NYXNrcygpO1xuICB9XG5cbiAgcHJpdmF0ZSBjb2x1bW5NYXNrQ2FjaGUgPSBuZXcgTWFwPHN0cmluZywgYm9vbGVhbj4oKTtcblxuICBwcml2YXRlIF9yZXNvbHZlQ29sdW1uTWFza3MoKTogdm9pZCB7XG4gICAgaWYgKCF0aGlzLmNvbmZpZykgcmV0dXJuO1xuICAgIHRoaXMuY29sdW1uTWFza0NhY2hlLmNsZWFyKCk7XG4gICAgZm9yIChjb25zdCBmaWVsZCBvZiB0aGlzLnRhYmxlQ29sdW1ucygpKSB7XG4gICAgICBjb25zdCB0YWIgPSAodGhpcy5jb25maWcudGFicyB8fCBbXSkuZmluZCh0ID0+IHQuaWQgPT09IGZpZWxkLnRhYik7XG4gICAgICBjb25zdCBtYXNrZWQgPSB0aGlzLnJiYWNTZXJ2aWNlLnNob3VsZE1hc2tGaWVsZChmaWVsZCwgdGFiLCB0aGlzLmNvbmZpZywgdGhpcy51c2VyUm9sZXMpO1xuICAgICAgdGhpcy5jb2x1bW5NYXNrQ2FjaGUuc2V0KGZpZWxkLmlkLCBtYXNrZWQpO1xuICAgIH1cbiAgfVxuXG4gIC8vIOKUgOKUgOKUgCBIZWxwZXJzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG4gIGlzQ2VsbE1hc2tlZChmaWVsZElkOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy5jb2x1bW5NYXNrQ2FjaGUuZ2V0KGZpZWxkSWQpID8/IGZhbHNlO1xuICB9XG5cbiAgZ2V0Q2VsbFZhbHVlKHJlY29yZDogVmVyc2lvbmVkUmVjb3JkLCBmaWVsZElkOiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIGlmICh0aGlzLmlzQ2VsbE1hc2tlZChmaWVsZElkKSkgcmV0dXJuICdYWFhYWFhYWFgnO1xuICAgIGNvbnN0IHZhbHVlID0gcmVjb3JkW2ZpZWxkSWRdO1xuICAgIGlmICh2YWx1ZSA9PT0gbnVsbCB8fCB2YWx1ZSA9PT0gdW5kZWZpbmVkKSByZXR1cm4gJ+KAlCc7XG4gICAgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ2Jvb2xlYW4nKSByZXR1cm4gdmFsdWUgPyAnWWVzJyA6ICdObyc7XG4gICAgaWYgKHZhbHVlIGluc3RhbmNlb2YgRGF0ZSB8fCAodHlwZW9mIHZhbHVlID09PSAnc3RyaW5nJyAmJiB2YWx1ZS5tYXRjaCgvXlxcZHs0fS1cXGR7Mn0tXFxkezJ9LykpKSB7XG4gICAgICB0cnkge1xuICAgICAgICByZXR1cm4gbmV3IERhdGUodmFsdWUpLnRvTG9jYWxlRGF0ZVN0cmluZygpO1xuICAgICAgfSBjYXRjaCB7XG4gICAgICAgIHJldHVybiBTdHJpbmcodmFsdWUpO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAoQXJyYXkuaXNBcnJheSh2YWx1ZSkpIHJldHVybiB2YWx1ZS5qb2luKCcsICcpO1xuICAgIHJldHVybiBTdHJpbmcodmFsdWUpO1xuICB9XG5cbiAgbmVlZHNNaWdyYXRpb24ocmVjb3JkOiBWZXJzaW9uZWRSZWNvcmQpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy52ZXJzaW9uU2VydmljZS5uZWVkc01pZ3JhdGlvbihyZWNvcmQsIHRoaXMuY29uZmlnKTtcbiAgfVxuXG4gIGdldENvbHVtbkxhYmVsKGZpZWxkSWQ6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgY29uc3QgZmllbGQgPSAodGhpcy5jb25maWc/LmZpZWxkcyB8fCBbXSkuZmluZChmID0+IGYuaWQgPT09IGZpZWxkSWQpO1xuICAgIHJldHVybiBmaWVsZD8ubGFiZWw/Llt0aGlzLmxhbmd1YWdlXSB8fCBmaWVsZD8ubGFiZWw/LlsnZW4nXSB8fCBmaWVsZElkO1xuICB9XG5cbiAgLy8g4pSA4pSA4pSAIFNvcnQgLyBTZWFyY2ggLyBQYWdpbmF0aW9uIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG4gIG9uU29ydChmaWVsZElkOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBpZiAodGhpcy5zb3J0RmllbGQoKSA9PT0gZmllbGRJZCkge1xuICAgICAgY29uc3QgbmV4dCA9IHRoaXMuc29ydERpcigpID09PSAnYXNjJyA/ICdkZXNjJyA6ICdhc2MnO1xuICAgICAgdGhpcy5zb3J0RGlyLnNldChuZXh0KTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5zb3J0RmllbGQuc2V0KGZpZWxkSWQpO1xuICAgICAgdGhpcy5zb3J0RGlyLnNldCgnYXNjJyk7XG4gICAgfVxuICAgIHRoaXMuc29ydENoYW5nZS5lbWl0KHsgZmllbGQ6IHRoaXMuc29ydEZpZWxkKCksIGRpcjogdGhpcy5zb3J0RGlyKCkgfSk7XG4gIH1cblxuICBvblNlYXJjaChldmVudDogRXZlbnQpOiB2b2lkIHtcbiAgICBjb25zdCB2YWx1ZSA9IChldmVudC50YXJnZXQgYXMgSFRNTElucHV0RWxlbWVudCkudmFsdWU7XG4gICAgdGhpcy5zZWFyY2hUZXJtLnNldCh2YWx1ZSk7XG4gICAgdGhpcy5zZWFyY2hDaGFuZ2UuZW1pdCh2YWx1ZSk7XG4gIH1cblxuICBvblBhZ2VDaGFuZ2UocGFnZTogbnVtYmVyKTogdm9pZCB7XG4gICAgaWYgKHBhZ2UgPCAxIHx8IHBhZ2UgPiB0aGlzLnRvdGFsUGFnZXMpIHJldHVybjtcbiAgICB0aGlzLnBhZ2VDaGFuZ2UuZW1pdChwYWdlKTtcbiAgfVxuXG4gIG9uRWRpdChldmVudDogRXZlbnQsIHJlY29yZDogVmVyc2lvbmVkUmVjb3JkKTogdm9pZCB7XG4gICAgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgdGhpcy5yb3dFZGl0LmVtaXQocmVjb3JkKTtcbiAgfVxuXG4gIG9uRGVsZXRlKGV2ZW50OiBFdmVudCwgcmVjb3JkOiBWZXJzaW9uZWRSZWNvcmQpOiB2b2lkIHtcbiAgICBldmVudC5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICB0aGlzLnJvd0RlbGV0ZS5lbWl0KHJlY29yZCk7XG4gIH1cbn1cbiIsIjxkaXYgY2xhc3M9XCJuZ3gtdGFibGVcIj5cblxuICA8IS0tIFNlYXJjaCBiYXIgLS0+XG4gIDxkaXYgY2xhc3M9XCJuZ3gtdGFibGVfX3Rvb2xiYXJcIj5cbiAgICA8aW5wdXRcbiAgICAgIGNsYXNzPVwibmd4LXRhYmxlX19zZWFyY2hcIlxuICAgICAgdHlwZT1cInNlYXJjaFwiXG4gICAgICBwbGFjZWhvbGRlcj1cIlNlYXJjaC4uLlwiXG4gICAgICBbdmFsdWVdPVwic2VhcmNoVGVybSgpXCJcbiAgICAgIChpbnB1dCk9XCJvblNlYXJjaCgkZXZlbnQpXCJcbiAgICAgIGFyaWEtbGFiZWw9XCJTZWFyY2ggcmVjb3Jkc1wiXG4gICAgLz5cbiAgPC9kaXY+XG5cbiAgPCEtLSBFcnJvciAtLT5cbiAgQGlmIChlcnJvcikge1xuICAgIDxkaXYgY2xhc3M9XCJuZ3gtdGFibGVfX2Vycm9yXCIgcm9sZT1cImFsZXJ0XCI+e3sgZXJyb3IgfX08L2Rpdj5cbiAgfVxuXG4gIDwhLS0gVGFibGUgLS0+XG4gIDx0YWJsZSBjbGFzcz1cIm5neC10YWJsZV9fdGFibGVcIj5cbiAgICA8dGhlYWQ+XG4gICAgICA8dHI+XG4gICAgICAgIEBmb3IgKGNvbCBvZiB0YWJsZUNvbHVtbnMoKTsgdHJhY2sgY29sLmlkKSB7XG4gICAgICAgICAgPHRoXG4gICAgICAgICAgICBjbGFzcz1cIm5neC10YWJsZV9fdGhcIlxuICAgICAgICAgICAgW3N0eWxlLndpZHRoXT1cImNvbC5jb2x1bW5XaWR0aCB8fCAnYXV0bydcIlxuICAgICAgICAgICAgKGNsaWNrKT1cIm9uU29ydChjb2wuaWQpXCJcbiAgICAgICAgICAgIFthdHRyLmFyaWEtc29ydF09XCJzb3J0RmllbGQoKSA9PT0gY29sLmlkID8gc29ydERpcigpIDogJ25vbmUnXCJcbiAgICAgICAgICAgIHJvbGU9XCJjb2x1bW5oZWFkZXJcIlxuICAgICAgICAgID5cbiAgICAgICAgICAgIHt7IGdldENvbHVtbkxhYmVsKGNvbC5pZCkgfX1cbiAgICAgICAgICAgIEBpZiAoc29ydEZpZWxkKCkgPT09IGNvbC5pZCkge1xuICAgICAgICAgICAgICA8c3BhbiBjbGFzcz1cIm5neC10YWJsZV9fc29ydC1pY29uXCI+e3sgc29ydERpcigpID09PSAnYXNjJyA/ICfihpEnIDogJ+KGkycgfX08L3NwYW4+XG4gICAgICAgICAgICB9XG4gICAgICAgICAgPC90aD5cbiAgICAgICAgfVxuICAgICAgICA8IS0tIEFjdGlvbnMgY29sdW1uIC0tPlxuICAgICAgICBAaWYgKHBlcm1pc3Npb25zLmNhbkVkaXQgfHwgcGVybWlzc2lvbnMuY2FuRGVsZXRlKSB7XG4gICAgICAgICAgPHRoIGNsYXNzPVwibmd4LXRhYmxlX190aCBuZ3gtdGFibGVfX3RoLS1hY3Rpb25zXCI+QWN0aW9uczwvdGg+XG4gICAgICAgIH1cbiAgICAgIDwvdHI+XG4gICAgPC90aGVhZD5cbiAgICA8dGJvZHk+XG4gICAgICBAaWYgKGxvYWRpbmcpIHtcbiAgICAgICAgPHRyPlxuICAgICAgICAgIDx0ZCBbYXR0ci5jb2xzcGFuXT1cInRhYmxlQ29sdW1ucygpLmxlbmd0aCArIDFcIiBjbGFzcz1cIm5neC10YWJsZV9fbG9hZGluZ1wiPkxvYWRpbmfigKY8L3RkPlxuICAgICAgICA8L3RyPlxuICAgICAgfSBAZWxzZSBpZiAoIWRhdGEgfHwgIWRhdGEubGVuZ3RoKSB7XG4gICAgICAgIDx0cj5cbiAgICAgICAgICA8dGQgW2F0dHIuY29sc3Bhbl09XCJ0YWJsZUNvbHVtbnMoKS5sZW5ndGggKyAxXCIgY2xhc3M9XCJuZ3gtdGFibGVfX2VtcHR5XCI+Tm8gcmVjb3JkcyBmb3VuZDwvdGQ+XG4gICAgICAgIDwvdHI+XG4gICAgICB9IEBlbHNlIHtcbiAgICAgICAgQGZvciAocmVjb3JkIG9mIGRhdGE7IHRyYWNrIHJlY29yZFsnX2lkJ10pIHtcbiAgICAgICAgICA8dHIgY2xhc3M9XCJuZ3gtdGFibGVfX3Jvd1wiIChjbGljayk9XCJyb3dDbGljay5lbWl0KHJlY29yZClcIj5cbiAgICAgICAgICAgIEBmb3IgKGNvbCBvZiB0YWJsZUNvbHVtbnMoKTsgdHJhY2sgY29sLmlkKSB7XG4gICAgICAgICAgICAgIDx0ZFxuICAgICAgICAgICAgICAgIGNsYXNzPVwibmd4LXRhYmxlX190ZFwiXG4gICAgICAgICAgICAgICAgW2NsYXNzLm5neC10YWJsZV9fdGQtLW1hc2tlZF09XCJpc0NlbGxNYXNrZWQoY29sLmlkKVwiXG4gICAgICAgICAgICAgID5cbiAgICAgICAgICAgICAgICB7eyBnZXRDZWxsVmFsdWUocmVjb3JkLCBjb2wuaWQpIH19XG4gICAgICAgICAgICAgICAgPCEtLSBNaWdyYXRpb24gYmFkZ2Ug4oCUIHNob3dzIHdoZW4gcmVjb3JkIGlzIHN0YWxlIC0tPlxuICAgICAgICAgICAgICAgIEBpZiAodGFibGVDb2x1bW5zKCkubGVuZ3RoID4gMCAmJiBjb2wuaWQgPT09IHRhYmxlQ29sdW1ucygpWzBdLmlkICYmIG5lZWRzTWlncmF0aW9uKHJlY29yZCkpIHtcbiAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LXRhYmxlX19taWdyYXRpb24tYmFkZ2VcIiB0aXRsZT1cIk5lZWRzIG1pZ3JhdGlvblwiPuKaoDwvc3Bhbj5cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIDwvdGQ+XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICA8IS0tIEFjdGlvbiBidXR0b25zIOKAlCBnYXRlZCBieSBSQkFDIHBlcm1pc3Npb25zIC0tPlxuICAgICAgICAgICAgQGlmIChwZXJtaXNzaW9ucy5jYW5FZGl0IHx8IHBlcm1pc3Npb25zLmNhbkRlbGV0ZSkge1xuICAgICAgICAgICAgICA8dGQgY2xhc3M9XCJuZ3gtdGFibGVfX3RkIG5neC10YWJsZV9fYWN0aW9uc1wiPlxuICAgICAgICAgICAgICAgIEBpZiAocGVybWlzc2lvbnMuY2FuRWRpdCkge1xuICAgICAgICAgICAgICAgICAgPGJ1dHRvblxuICAgICAgICAgICAgICAgICAgICB0eXBlPVwiYnV0dG9uXCJcbiAgICAgICAgICAgICAgICAgICAgY2xhc3M9XCJuZ3gtdGFibGVfX2FjdGlvbiBuZ3gtdGFibGVfX2FjdGlvbi0tZWRpdFwiXG4gICAgICAgICAgICAgICAgICAgIChjbGljayk9XCJvbkVkaXQoJGV2ZW50LCByZWNvcmQpXCJcbiAgICAgICAgICAgICAgICAgICAgdGl0bGU9XCJFZGl0XCJcbiAgICAgICAgICAgICAgICAgID7inI88L2J1dHRvbj5cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgQGlmIChwZXJtaXNzaW9ucy5jYW5EZWxldGUpIHtcbiAgICAgICAgICAgICAgICAgIDxidXR0b25cbiAgICAgICAgICAgICAgICAgICAgdHlwZT1cImJ1dHRvblwiXG4gICAgICAgICAgICAgICAgICAgIGNsYXNzPVwibmd4LXRhYmxlX19hY3Rpb24gbmd4LXRhYmxlX19hY3Rpb24tLWRlbGV0ZVwiXG4gICAgICAgICAgICAgICAgICAgIChjbGljayk9XCJvbkRlbGV0ZSgkZXZlbnQsIHJlY29yZClcIlxuICAgICAgICAgICAgICAgICAgICB0aXRsZT1cIkRlbGV0ZVwiXG4gICAgICAgICAgICAgICAgICA+8J+XkTwvYnV0dG9uPlxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgPC90ZD5cbiAgICAgICAgICAgIH1cbiAgICAgICAgICA8L3RyPlxuICAgICAgICB9XG4gICAgICB9XG4gICAgPC90Ym9keT5cbiAgPC90YWJsZT5cblxuICA8IS0tIFBhZ2luYXRpb24gLS0+XG4gIEBpZiAodG90YWxQYWdlcyA+IDEpIHtcbiAgICA8ZGl2IGNsYXNzPVwibmd4LXRhYmxlX19wYWdpbmF0aW9uXCI+XG4gICAgICA8YnV0dG9uXG4gICAgICAgIHR5cGU9XCJidXR0b25cIlxuICAgICAgICBjbGFzcz1cIm5neC10YWJsZV9fcGFnZS1idG5cIlxuICAgICAgICBbZGlzYWJsZWRdPVwiY3VycmVudFBhZ2UgPD0gMVwiXG4gICAgICAgIChjbGljayk9XCJvblBhZ2VDaGFuZ2UoY3VycmVudFBhZ2UgLSAxKVwiXG4gICAgICAgIGFyaWEtbGFiZWw9XCJQcmV2aW91cyBwYWdlXCJcbiAgICAgID7ihpA8L2J1dHRvbj5cblxuICAgICAgPHNwYW4gY2xhc3M9XCJuZ3gtdGFibGVfX3BhZ2UtaW5mb1wiPlxuICAgICAgICBQYWdlIHt7IGN1cnJlbnRQYWdlIH19IG9mIHt7IHRvdGFsUGFnZXMgfX0gKHt7IHRvdGFsUmVjb3JkcyB9fSByZWNvcmRzKVxuICAgICAgPC9zcGFuPlxuXG4gICAgICA8YnV0dG9uXG4gICAgICAgIHR5cGU9XCJidXR0b25cIlxuICAgICAgICBjbGFzcz1cIm5neC10YWJsZV9fcGFnZS1idG5cIlxuICAgICAgICBbZGlzYWJsZWRdPVwiY3VycmVudFBhZ2UgPj0gdG90YWxQYWdlc1wiXG4gICAgICAgIChjbGljayk9XCJvblBhZ2VDaGFuZ2UoY3VycmVudFBhZ2UgKyAxKVwiXG4gICAgICAgIGFyaWEtbGFiZWw9XCJOZXh0IHBhZ2VcIlxuICAgICAgPuKGkjwvYnV0dG9uPlxuICAgIDwvZGl2PlxuICB9XG5cbjwvZGl2PlxuIl19
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
/** Base URL for the dynamic-entity-server API */
|
|
3
|
+
export const DYNAMIC_ENTITY_API_URL = new InjectionToken('DYNAMIC_ENTITY_API_URL');
|
|
4
|
+
/** Roles that see XXXXXXXXX for masked fields (ADR-003) */
|
|
5
|
+
export const MASKED_ROLES = new InjectionToken('MASKED_ROLES');
|
|
6
|
+
/** Migration strategy for the frontend — 'strict' | 'graceful' only (ADR-005, never 'auto') */
|
|
7
|
+
export const MIGRATION_STRATEGY = new InjectionToken('MIGRATION_STRATEGY');
|
|
8
|
+
/** Registry: fieldType string → Angular component class */
|
|
9
|
+
export const FIELD_TYPE_REGISTRY = new InjectionToken('FIELD_TYPE_REGISTRY');
|
|
10
|
+
/** Registry: entity key → async () => options[] loader function */
|
|
11
|
+
export const ENTITY_REF_REGISTRY = new InjectionToken('ENTITY_REF_REGISTRY');
|
|
12
|
+
/** Registry: validator key → ValidatorFn */
|
|
13
|
+
export const VALIDATOR_REGISTRY = new InjectionToken('VALIDATOR_REGISTRY');
|
|
14
|
+
/** Registry: hook key → async (data, context) => data */
|
|
15
|
+
export const HOOK_REGISTRY = new InjectionToken('HOOK_REGISTRY');
|
|
16
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5qZWN0aW9uLXRva2Vucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvdG9rZW5zL2luamVjdGlvbi10b2tlbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUkvQyxpREFBaUQ7QUFDakQsTUFBTSxDQUFDLE1BQU0sc0JBQXNCLEdBQUcsSUFBSSxjQUFjLENBQVMsd0JBQXdCLENBQUMsQ0FBQztBQUUzRiwyREFBMkQ7QUFDM0QsTUFBTSxDQUFDLE1BQU0sWUFBWSxHQUFHLElBQUksY0FBYyxDQUFXLGNBQWMsQ0FBQyxDQUFDO0FBRXpFLCtGQUErRjtBQUMvRixNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLGNBQWMsQ0FBd0Isb0JBQW9CLENBQUMsQ0FBQztBQUVsRywyREFBMkQ7QUFDM0QsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQUcsSUFBSSxjQUFjLENBQXlCLHFCQUFxQixDQUFDLENBQUM7QUFFckcsbUVBQW1FO0FBQ25FLE1BQU0sQ0FBQyxNQUFNLG1CQUFtQixHQUFHLElBQUksY0FBYyxDQUNuRCxxQkFBcUIsQ0FDdEIsQ0FBQztBQUVGLDRDQUE0QztBQUM1QyxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLGNBQWMsQ0FBbUIsb0JBQW9CLENBQUMsQ0FBQztBQUU3Rix5REFBeUQ7QUFDekQsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLElBQUksY0FBYyxDQUF3QixlQUFlLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGlvblRva2VuIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgdHlwZSB7IEVudGl0eUNvbmZpZywgRmllbGRDb25maWcsIE1pZ3JhdGlvblN0cmF0ZWd5IH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuaW1wb3J0IHR5cGUgeyBUeXBlIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5cbi8qKiBCYXNlIFVSTCBmb3IgdGhlIGR5bmFtaWMtZW50aXR5LXNlcnZlciBBUEkgKi9cbmV4cG9ydCBjb25zdCBEWU5BTUlDX0VOVElUWV9BUElfVVJMID0gbmV3IEluamVjdGlvblRva2VuPHN0cmluZz4oJ0RZTkFNSUNfRU5USVRZX0FQSV9VUkwnKTtcblxuLyoqIFJvbGVzIHRoYXQgc2VlIFhYWFhYWFhYWCBmb3IgbWFza2VkIGZpZWxkcyAoQURSLTAwMykgKi9cbmV4cG9ydCBjb25zdCBNQVNLRURfUk9MRVMgPSBuZXcgSW5qZWN0aW9uVG9rZW48c3RyaW5nW10+KCdNQVNLRURfUk9MRVMnKTtcblxuLyoqIE1pZ3JhdGlvbiBzdHJhdGVneSBmb3IgdGhlIGZyb250ZW5kIOKAlCAnc3RyaWN0JyB8ICdncmFjZWZ1bCcgb25seSAoQURSLTAwNSwgbmV2ZXIgJ2F1dG8nKSAqL1xuZXhwb3J0IGNvbnN0IE1JR1JBVElPTl9TVFJBVEVHWSA9IG5ldyBJbmplY3Rpb25Ub2tlbjwnc3RyaWN0JyB8ICdncmFjZWZ1bCc+KCdNSUdSQVRJT05fU1RSQVRFR1knKTtcblxuLyoqIFJlZ2lzdHJ5OiBmaWVsZFR5cGUgc3RyaW5nIOKGkiBBbmd1bGFyIGNvbXBvbmVudCBjbGFzcyAqL1xuZXhwb3J0IGNvbnN0IEZJRUxEX1RZUEVfUkVHSVNUUlkgPSBuZXcgSW5qZWN0aW9uVG9rZW48TWFwPHN0cmluZywgVHlwZTxhbnk+Pj4oJ0ZJRUxEX1RZUEVfUkVHSVNUUlknKTtcblxuLyoqIFJlZ2lzdHJ5OiBlbnRpdHkga2V5IOKGkiBhc3luYyAoKSA9PiBvcHRpb25zW10gbG9hZGVyIGZ1bmN0aW9uICovXG5leHBvcnQgY29uc3QgRU5USVRZX1JFRl9SRUdJU1RSWSA9IG5ldyBJbmplY3Rpb25Ub2tlbjxNYXA8c3RyaW5nLCAoKSA9PiBQcm9taXNlPGFueVtdPj4+KFxuICAnRU5USVRZX1JFRl9SRUdJU1RSWScsXG4pO1xuXG4vKiogUmVnaXN0cnk6IHZhbGlkYXRvciBrZXkg4oaSIFZhbGlkYXRvckZuICovXG5leHBvcnQgY29uc3QgVkFMSURBVE9SX1JFR0lTVFJZID0gbmV3IEluamVjdGlvblRva2VuPE1hcDxzdHJpbmcsIGFueT4+KCdWQUxJREFUT1JfUkVHSVNUUlknKTtcblxuLyoqIFJlZ2lzdHJ5OiBob29rIGtleSDihpIgYXN5bmMgKGRhdGEsIGNvbnRleHQpID0+IGRhdGEgKi9cbmV4cG9ydCBjb25zdCBIT09LX1JFR0lTVFJZID0gbmV3IEluamVjdGlvblRva2VuPE1hcDxzdHJpbmcsIEZ1bmN0aW9uPj4oJ0hPT0tfUkVHSVNUUlknKTtcbiJdfQ==
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated bundle index. Do not edit.
|
|
3
|
+
*/
|
|
4
|
+
export * from './public-api';
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LWR5bmFtaWMtZW50aXR5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL25neC1keW5hbWljLWVudGl0eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsY0FBYyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBHZW5lcmF0ZWQgYnVuZGxlIGluZGV4LiBEbyBub3QgZWRpdC5cbiAqL1xuXG5leHBvcnQgKiBmcm9tICcuL3B1YmxpYy1hcGknO1xuIl19
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Tokens
|
|
2
|
+
export * from './lib/tokens/injection-tokens';
|
|
3
|
+
// Providers
|
|
4
|
+
export * from './lib/providers/provide-ngx-dynamic-entity';
|
|
5
|
+
// Services
|
|
6
|
+
export * from './lib/services/config.service';
|
|
7
|
+
export * from './lib/services/field-registry.service';
|
|
8
|
+
export * from './lib/services/hook-registry.service';
|
|
9
|
+
export * from './lib/services/validator-registry.service';
|
|
10
|
+
export * from './lib/services/entity-ref-registry.service';
|
|
11
|
+
export * from './lib/services/version.service';
|
|
12
|
+
export * from './lib/services/rbac.service';
|
|
13
|
+
// Components
|
|
14
|
+
export * from './lib/form/dynamic-form.component';
|
|
15
|
+
export * from './lib/form/dynamic-field/dynamic-field.component';
|
|
16
|
+
export * from './lib/table/dynamic-table.component';
|
|
17
|
+
// Field type components (individually importable — reusability rule)
|
|
18
|
+
export * from './lib/field-types/text-field.component';
|
|
19
|
+
export * from './lib/field-types/textarea-field.component';
|
|
20
|
+
export * from './lib/field-types/number-field.component';
|
|
21
|
+
export * from './lib/field-types/checkbox-field.component';
|
|
22
|
+
export * from './lib/field-types/date-field.component';
|
|
23
|
+
export * from './lib/field-types/dropdown-field.component';
|
|
24
|
+
export * from './lib/field-types/multi-select-field.component';
|
|
25
|
+
export * from './lib/field-types/entity-ref-field.component';
|
|
26
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9wdWJsaWMtYXBpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFNBQVM7QUFDVCxjQUFjLCtCQUErQixDQUFDO0FBRTlDLFlBQVk7QUFDWixjQUFjLDRDQUE0QyxDQUFDO0FBRTNELFdBQVc7QUFDWCxjQUFjLCtCQUErQixDQUFDO0FBQzlDLGNBQWMsdUNBQXVDLENBQUM7QUFDdEQsY0FBYyxzQ0FBc0MsQ0FBQztBQUNyRCxjQUFjLDJDQUEyQyxDQUFDO0FBQzFELGNBQWMsNENBQTRDLENBQUM7QUFDM0QsY0FBYyxnQ0FBZ0MsQ0FBQztBQUMvQyxjQUFjLDZCQUE2QixDQUFDO0FBRTVDLGFBQWE7QUFDYixjQUFjLG1DQUFtQyxDQUFDO0FBQ2xELGNBQWMsa0RBQWtELENBQUM7QUFDakUsY0FBYyxxQ0FBcUMsQ0FBQztBQUVwRCxxRUFBcUU7QUFDckUsY0FBYyx3Q0FBd0MsQ0FBQztBQUN2RCxjQUFjLDRDQUE0QyxDQUFDO0FBQzNELGNBQWMsMENBQTBDLENBQUM7QUFDekQsY0FBYyw0Q0FBNEMsQ0FBQztBQUMzRCxjQUFjLHdDQUF3QyxDQUFDO0FBQ3ZELGNBQWMsNENBQTRDLENBQUM7QUFDM0QsY0FBYyxnREFBZ0QsQ0FBQztBQUMvRCxjQUFjLDhDQUE4QyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gVG9rZW5zXG5leHBvcnQgKiBmcm9tICcuL2xpYi90b2tlbnMvaW5qZWN0aW9uLXRva2Vucyc7XG5cbi8vIFByb3ZpZGVyc1xuZXhwb3J0ICogZnJvbSAnLi9saWIvcHJvdmlkZXJzL3Byb3ZpZGUtbmd4LWR5bmFtaWMtZW50aXR5JztcblxuLy8gU2VydmljZXNcbmV4cG9ydCAqIGZyb20gJy4vbGliL3NlcnZpY2VzL2NvbmZpZy5zZXJ2aWNlJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL3NlcnZpY2VzL2ZpZWxkLXJlZ2lzdHJ5LnNlcnZpY2UnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvc2VydmljZXMvaG9vay1yZWdpc3RyeS5zZXJ2aWNlJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL3NlcnZpY2VzL3ZhbGlkYXRvci1yZWdpc3RyeS5zZXJ2aWNlJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL3NlcnZpY2VzL2VudGl0eS1yZWYtcmVnaXN0cnkuc2VydmljZSc7XG5leHBvcnQgKiBmcm9tICcuL2xpYi9zZXJ2aWNlcy92ZXJzaW9uLnNlcnZpY2UnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvc2VydmljZXMvcmJhYy5zZXJ2aWNlJztcblxuLy8gQ29tcG9uZW50c1xuZXhwb3J0ICogZnJvbSAnLi9saWIvZm9ybS9keW5hbWljLWZvcm0uY29tcG9uZW50JztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2Zvcm0vZHluYW1pYy1maWVsZC9keW5hbWljLWZpZWxkLmNvbXBvbmVudCc7XG5leHBvcnQgKiBmcm9tICcuL2xpYi90YWJsZS9keW5hbWljLXRhYmxlLmNvbXBvbmVudCc7XG5cbi8vIEZpZWxkIHR5cGUgY29tcG9uZW50cyAoaW5kaXZpZHVhbGx5IGltcG9ydGFibGUg4oCUIHJldXNhYmlsaXR5IHJ1bGUpXG5leHBvcnQgKiBmcm9tICcuL2xpYi9maWVsZC10eXBlcy90ZXh0LWZpZWxkLmNvbXBvbmVudCc7XG5leHBvcnQgKiBmcm9tICcuL2xpYi9maWVsZC10eXBlcy90ZXh0YXJlYS1maWVsZC5jb21wb25lbnQnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvZmllbGQtdHlwZXMvbnVtYmVyLWZpZWxkLmNvbXBvbmVudCc7XG5leHBvcnQgKiBmcm9tICcuL2xpYi9maWVsZC10eXBlcy9jaGVja2JveC1maWVsZC5jb21wb25lbnQnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvZmllbGQtdHlwZXMvZGF0ZS1maWVsZC5jb21wb25lbnQnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvZmllbGQtdHlwZXMvZHJvcGRvd24tZmllbGQuY29tcG9uZW50JztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2ZpZWxkLXR5cGVzL211bHRpLXNlbGVjdC1maWVsZC5jb21wb25lbnQnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvZmllbGQtdHlwZXMvZW50aXR5LXJlZi1maWVsZC5jb21wb25lbnQnO1xuXG4vLyBSZS1leHBvcnQgY29yZSB0eXBlcyBzbyBjb25zdW1lcnMgbmVlZCBvbmx5IG9uZSBpbXBvcnRcbmV4cG9ydCB0eXBlIHtcbiAgRW50aXR5Q29uZmlnLFxuICBGaWVsZENvbmZpZyxcbiAgVGFiQ29uZmlnLFxuICBEcm9wZG93bk9wdGlvbixcbiAgRmllbGREZXBlbmRlbmN5LFxuICBFbnRpdHlIb29rcyxcbiAgVmVyc2lvbmVkUmVjb3JkLFxuICBFbnRpdHlQZXJtaXNzaW9ucyxcbiAgUmJhY0NvbnRleHQsXG4gIE1pZ3JhdGlvblN0cmF0ZWd5LFxuICBRdWVyeU9wdGlvbnMsXG4gIFBhZ2luYXRlZFJlc3VsdCxcbn0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuIl19
|