ngx-dynamic-entity 0.0.1 → 1.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.
Files changed (84) hide show
  1. package/README.md +139 -0
  2. package/esm2022/lib/field-types/array-field.component.mjs +160 -0
  3. package/esm2022/lib/field-types/boolean-field.component.mjs +100 -0
  4. package/esm2022/lib/field-types/checkbox-field.component.mjs +49 -41
  5. package/esm2022/lib/field-types/currency-field.component.mjs +101 -0
  6. package/esm2022/lib/field-types/date-field.component.mjs +47 -39
  7. package/esm2022/lib/field-types/dropdown-field.component.mjs +145 -54
  8. package/esm2022/lib/field-types/email-field.component.mjs +101 -0
  9. package/esm2022/lib/field-types/entity-ref-field.component.mjs +134 -70
  10. package/esm2022/lib/field-types/file-field.component.mjs +156 -0
  11. package/esm2022/lib/field-types/group-field.component.mjs +89 -0
  12. package/esm2022/lib/field-types/image-field.component.mjs +168 -0
  13. package/esm2022/lib/field-types/month-year-field.component.mjs +151 -0
  14. package/esm2022/lib/field-types/multi-select-field.component.mjs +110 -56
  15. package/esm2022/lib/field-types/number-field.component.mjs +84 -41
  16. package/esm2022/lib/field-types/password-field.component.mjs +109 -0
  17. package/esm2022/lib/field-types/radio-field.component.mjs +152 -0
  18. package/esm2022/lib/field-types/text-field.component.mjs +82 -42
  19. package/esm2022/lib/field-types/textarea-field.component.mjs +52 -41
  20. package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +44 -26
  21. package/esm2022/lib/form/dynamic-form.component.mjs +501 -67
  22. package/esm2022/lib/form/dynamic-record-form.component.mjs +469 -0
  23. package/esm2022/lib/providers/provide-field-types.mjs +72 -0
  24. package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +3 -5
  25. package/esm2022/lib/services/cascade-data.service.mjs +129 -0
  26. package/esm2022/lib/services/config-source.service.mjs +67 -0
  27. package/esm2022/lib/services/entity-ref-cache.mjs +20 -0
  28. package/esm2022/lib/services/entity-ref-registry.service.mjs +3 -3
  29. package/esm2022/lib/services/entity-ref-selection.service.mjs +27 -0
  30. package/esm2022/lib/services/entity-reference.service.mjs +168 -0
  31. package/esm2022/lib/services/field-registry.service.mjs +35 -30
  32. package/esm2022/lib/services/file-upload.service.mjs +89 -0
  33. package/esm2022/lib/services/lookup-registry.service.mjs +246 -0
  34. package/esm2022/lib/services/rbac.service.mjs +5 -11
  35. package/esm2022/lib/services/rules-evaluation.service.mjs +27 -0
  36. package/esm2022/lib/services/validator-registry.service.mjs +24 -7
  37. package/esm2022/lib/tokens/injection-tokens.mjs +88 -7
  38. package/esm2022/public-api.mjs +14 -5
  39. package/fesm2022/ngx-dynamic-entity.mjs +3985 -876
  40. package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
  41. package/lib/field-types/array-field.component.d.ts +22 -0
  42. package/lib/field-types/boolean-field.component.d.ts +18 -0
  43. package/lib/field-types/checkbox-field.component.d.ts +3 -2
  44. package/lib/field-types/currency-field.component.d.ts +17 -0
  45. package/lib/field-types/date-field.component.d.ts +3 -2
  46. package/lib/field-types/dropdown-field.component.d.ts +24 -4
  47. package/lib/field-types/email-field.component.d.ts +15 -0
  48. package/lib/field-types/entity-ref-field.component.d.ts +24 -11
  49. package/lib/field-types/file-field.component.d.ts +25 -0
  50. package/lib/field-types/group-field.component.d.ts +17 -0
  51. package/lib/field-types/image-field.component.d.ts +30 -0
  52. package/lib/field-types/month-year-field.component.d.ts +28 -0
  53. package/lib/field-types/multi-select-field.component.d.ts +18 -4
  54. package/lib/field-types/number-field.component.d.ts +5 -2
  55. package/lib/field-types/password-field.component.d.ts +17 -0
  56. package/lib/field-types/radio-field.component.d.ts +33 -0
  57. package/lib/field-types/text-field.component.d.ts +5 -3
  58. package/lib/field-types/textarea-field.component.d.ts +4 -2
  59. package/lib/form/dynamic-field/dynamic-field.component.d.ts +20 -7
  60. package/lib/form/dynamic-form.component.d.ts +111 -19
  61. package/lib/form/dynamic-record-form.component.d.ts +169 -0
  62. package/lib/providers/provide-field-types.d.ts +24 -0
  63. package/lib/providers/provide-ngx-dynamic-entity.d.ts +12 -7
  64. package/lib/services/cascade-data.service.d.ts +45 -0
  65. package/lib/services/config-source.service.d.ts +14 -0
  66. package/lib/services/entity-ref-cache.d.ts +27 -0
  67. package/lib/services/entity-ref-registry.service.d.ts +4 -3
  68. package/lib/services/entity-ref-selection.service.d.ts +24 -0
  69. package/lib/services/entity-reference.service.d.ts +49 -0
  70. package/lib/services/field-registry.service.d.ts +17 -7
  71. package/lib/services/file-upload.service.d.ts +27 -0
  72. package/lib/services/lookup-registry.service.d.ts +94 -0
  73. package/lib/services/rbac.service.d.ts +4 -9
  74. package/lib/services/rules-evaluation.service.d.ts +17 -0
  75. package/lib/services/validator-registry.service.d.ts +5 -4
  76. package/lib/tokens/injection-tokens.d.ts +90 -7
  77. package/package.json +14 -3
  78. package/public-api.d.ts +11 -4
  79. package/esm2022/lib/services/config.service.mjs +0 -58
  80. package/esm2022/lib/services/version.service.mjs +0 -40
  81. package/esm2022/lib/table/dynamic-table.component.mjs +0 -160
  82. package/lib/services/config.service.d.ts +0 -25
  83. package/lib/services/version.service.d.ts +0 -26
  84. package/lib/table/dynamic-table.component.d.ts +0 -56
@@ -0,0 +1,168 @@
1
+ import { Component, inject, Input, signal } from '@angular/core';
2
+ import { resolveLabel } from '@dynamic-entity/core';
3
+ import { FileUploadService } from '../services/file-upload.service';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * Image field: preview thumbnail + upload button.
7
+ *
8
+ * With `UPLOAD_HANDLER` registered the file is persisted and stored as `{ url }`; without one
9
+ * it is stored as `{ file }` and previewed from an object URL (revoked when replaced/destroyed).
10
+ */
11
+ export class ImageFieldComponent {
12
+ field;
13
+ control;
14
+ language = 'en';
15
+ readonly = false;
16
+ masked = false;
17
+ uploads = inject(FileUploadService);
18
+ uploading = signal(false);
19
+ uploadError = signal(null);
20
+ /** Cached so an object URL is minted once per File, not once per change-detection pass. */
21
+ previewSource = null;
22
+ previewCache = null;
23
+ get label() {
24
+ return resolveLabel(this.field?.label, this.language);
25
+ }
26
+ previewUrl() {
27
+ const value = this.control?.value ?? null;
28
+ if (value === this.previewSource)
29
+ return this.previewCache;
30
+ this.uploads.revokePreviewUrl(this.previewCache);
31
+ this.previewSource = value;
32
+ this.previewCache = this.uploads.previewUrlFor(value);
33
+ return this.previewCache;
34
+ }
35
+ async onFileSelect(file) {
36
+ if (!file)
37
+ return;
38
+ this.uploadError.set(null);
39
+ this.uploading.set(true);
40
+ try {
41
+ this.control.setValue(await this.uploads.toFileRef(file));
42
+ this.control.markAsTouched();
43
+ }
44
+ catch {
45
+ this.uploadError.set('Upload failed. Please try again.');
46
+ }
47
+ finally {
48
+ this.uploading.set(false);
49
+ }
50
+ }
51
+ remove() {
52
+ this.control.setValue(null);
53
+ this.control.markAsTouched();
54
+ }
55
+ ngOnDestroy() {
56
+ this.uploads.revokePreviewUrl(this.previewCache);
57
+ }
58
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ImageFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
59
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: ImageFieldComponent, isStandalone: true, selector: "ngx-image-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
60
+ <div class="ngx-field ngx-field--image"
61
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
62
+ <label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
63
+ @if (masked) {
64
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
65
+ } @else {
66
+ <div class="ngx-field__image-wrap">
67
+ @if (previewUrl()) {
68
+ <img class="ngx-field__image-preview" [src]="previewUrl()" [alt]="label" />
69
+ } @else {
70
+ <div class="ngx-field__image-placeholder">
71
+ <span>📷</span>
72
+ <span>No image</span>
73
+ </div>
74
+ }
75
+ @if (!readonly) {
76
+ <div class="ngx-field__image-actions">
77
+ <label class="ngx-field__upload-btn" [class.ngx-field__upload-btn--loading]="uploading()">
78
+ @if (uploading()) {
79
+ <span>Uploading…</span>
80
+ } @else {
81
+ <span>{{ previewUrl() ? 'Change' : 'Upload' }}</span>
82
+ <input
83
+ type="file"
84
+ class="ngx-field__file-input"
85
+ [id]="'field-' + field.id"
86
+ accept="image/*"
87
+ [disabled]="field.disabled || uploading()"
88
+ (change)="onFileSelect($any($event.target).files[0])"
89
+ />
90
+ }
91
+ </label>
92
+ @if (previewUrl()) {
93
+ <button type="button" class="ngx-field__remove-btn" (click)="remove()">Remove</button>
94
+ }
95
+ </div>
96
+ }
97
+ </div>
98
+ @if (uploadError()) {
99
+ <span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">{{ uploadError() }}</span>
100
+ }
101
+ }
102
+ </div>
103
+ `, isInline: true });
104
+ }
105
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ImageFieldComponent, decorators: [{
106
+ type: Component,
107
+ args: [{
108
+ selector: 'ngx-image-field',
109
+ standalone: true,
110
+ imports: [],
111
+ template: `
112
+ <div class="ngx-field ngx-field--image"
113
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
114
+ <label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
115
+ @if (masked) {
116
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
117
+ } @else {
118
+ <div class="ngx-field__image-wrap">
119
+ @if (previewUrl()) {
120
+ <img class="ngx-field__image-preview" [src]="previewUrl()" [alt]="label" />
121
+ } @else {
122
+ <div class="ngx-field__image-placeholder">
123
+ <span>📷</span>
124
+ <span>No image</span>
125
+ </div>
126
+ }
127
+ @if (!readonly) {
128
+ <div class="ngx-field__image-actions">
129
+ <label class="ngx-field__upload-btn" [class.ngx-field__upload-btn--loading]="uploading()">
130
+ @if (uploading()) {
131
+ <span>Uploading…</span>
132
+ } @else {
133
+ <span>{{ previewUrl() ? 'Change' : 'Upload' }}</span>
134
+ <input
135
+ type="file"
136
+ class="ngx-field__file-input"
137
+ [id]="'field-' + field.id"
138
+ accept="image/*"
139
+ [disabled]="field.disabled || uploading()"
140
+ (change)="onFileSelect($any($event.target).files[0])"
141
+ />
142
+ }
143
+ </label>
144
+ @if (previewUrl()) {
145
+ <button type="button" class="ngx-field__remove-btn" (click)="remove()">Remove</button>
146
+ }
147
+ </div>
148
+ }
149
+ </div>
150
+ @if (uploadError()) {
151
+ <span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">{{ uploadError() }}</span>
152
+ }
153
+ }
154
+ </div>
155
+ `,
156
+ }]
157
+ }], propDecorators: { field: [{
158
+ type: Input
159
+ }], control: [{
160
+ type: Input
161
+ }], language: [{
162
+ type: Input
163
+ }], readonly: [{
164
+ type: Input
165
+ }], masked: [{
166
+ type: Input
167
+ }] } });
168
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2UtZmllbGQuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9maWVsZC10eXBlcy9pbWFnZS1maWVsZC5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBYSxNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUc1RSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDcEQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0saUNBQWlDLENBQUM7O0FBRXBFOzs7OztHQUtHO0FBbURILE1BQU0sT0FBTyxtQkFBbUI7SUFDckIsS0FBSyxDQUFxQjtJQUMxQixPQUFPLENBQW1CO0lBQzFCLFFBQVEsR0FBVyxJQUFJLENBQUM7SUFDeEIsUUFBUSxHQUFZLEtBQUssQ0FBQztJQUMxQixNQUFNLEdBQVksS0FBSyxDQUFDO0lBRWhCLE9BQU8sR0FBRyxNQUFNLENBQUMsaUJBQWlCLENBQUMsQ0FBQztJQUU1QyxTQUFTLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzFCLFdBQVcsR0FBRyxNQUFNLENBQWdCLElBQUksQ0FBQyxDQUFDO0lBRW5ELDJGQUEyRjtJQUNuRixhQUFhLEdBQW1CLElBQUksQ0FBQztJQUNyQyxZQUFZLEdBQWtCLElBQUksQ0FBQztJQUUzQyxJQUFJLEtBQUs7UUFDUCxPQUFPLFlBQVksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDeEQsQ0FBQztJQUVELFVBQVU7UUFDUixNQUFNLEtBQUssR0FBSSxJQUFJLENBQUMsT0FBTyxFQUFFLEtBQXdCLElBQUksSUFBSSxDQUFDO1FBQzlELElBQUksS0FBSyxLQUFLLElBQUksQ0FBQyxhQUFhO1lBQUUsT0FBTyxJQUFJLENBQUMsWUFBWSxDQUFDO1FBRTNELElBQUksQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQ2pELElBQUksQ0FBQyxhQUFhLEdBQUcsS0FBSyxDQUFDO1FBQzNCLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDdEQsT0FBTyxJQUFJLENBQUMsWUFBWSxDQUFDO0lBQzNCLENBQUM7SUFFRCxLQUFLLENBQUMsWUFBWSxDQUFDLElBQXNCO1FBQ3ZDLElBQUksQ0FBQyxJQUFJO1lBQUUsT0FBTztRQUNsQixJQUFJLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMzQixJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN6QixJQUFJLENBQUM7WUFDSCxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7WUFDMUQsSUFBSSxDQUFDLE9BQU8sQ0FBQyxhQUFhLEVBQUUsQ0FBQztRQUMvQixDQUFDO1FBQUMsTUFBTSxDQUFDO1lBQ1AsSUFBSSxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsa0NBQWtDLENBQUMsQ0FBQztRQUMzRCxDQUFDO2dCQUFTLENBQUM7WUFDVCxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUM1QixDQUFDO0lBQ0gsQ0FBQztJQUVELE1BQU07UUFDSixJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM1QixJQUFJLENBQUMsT0FBTyxDQUFDLGFBQWEsRUFBRSxDQUFDO0lBQy9CLENBQUM7SUFFRCxXQUFXO1FBQ1QsSUFBSSxDQUFDLE9BQU8sQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUM7SUFDbkQsQ0FBQzt3R0FuRFUsbUJBQW1COzRGQUFuQixtQkFBbUIseUxBOUNwQjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E0Q1Q7OzRGQUVVLG1CQUFtQjtrQkFsRC9CLFNBQVM7bUJBQUM7b0JBQ1QsUUFBUSxFQUFFLGlCQUFpQjtvQkFDM0IsVUFBVSxFQUFFLElBQUk7b0JBQ2hCLE9BQU8sRUFBRSxFQUFFO29CQUNYLFFBQVEsRUFBRTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E0Q1Q7aUJBQ0Y7OEJBRVUsS0FBSztzQkFBYixLQUFLO2dCQUNHLE9BQU87c0JBQWYsS0FBSztnQkFDRyxRQUFRO3NCQUFoQixLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csTUFBTTtzQkFBZCxLQUFLIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBPbkRlc3Ryb3ksIGluamVjdCwgSW5wdXQsIHNpZ25hbCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xyXG5pbXBvcnQgeyBBYnN0cmFjdENvbnRyb2wgfSBmcm9tICdAYW5ndWxhci9mb3Jtcyc7XHJcbmltcG9ydCB0eXBlIHsgRmlsZVJlZiwgTmVzdGVkRmllbGRDb25maWcgfSBmcm9tICdAZHluYW1pYy1lbnRpdHkvY29yZSc7XHJcbmltcG9ydCB7IHJlc29sdmVMYWJlbCB9IGZyb20gJ0BkeW5hbWljLWVudGl0eS9jb3JlJztcclxuaW1wb3J0IHsgRmlsZVVwbG9hZFNlcnZpY2UgfSBmcm9tICcuLi9zZXJ2aWNlcy9maWxlLXVwbG9hZC5zZXJ2aWNlJztcclxuXHJcbi8qKlxyXG4gKiBJbWFnZSBmaWVsZDogcHJldmlldyB0aHVtYm5haWwgKyB1cGxvYWQgYnV0dG9uLlxyXG4gKlxyXG4gKiBXaXRoIGBVUExPQURfSEFORExFUmAgcmVnaXN0ZXJlZCB0aGUgZmlsZSBpcyBwZXJzaXN0ZWQgYW5kIHN0b3JlZCBhcyBgeyB1cmwgfWA7IHdpdGhvdXQgb25lXHJcbiAqIGl0IGlzIHN0b3JlZCBhcyBgeyBmaWxlIH1gIGFuZCBwcmV2aWV3ZWQgZnJvbSBhbiBvYmplY3QgVVJMIChyZXZva2VkIHdoZW4gcmVwbGFjZWQvZGVzdHJveWVkKS5cclxuICovXHJcbkBDb21wb25lbnQoe1xyXG4gIHNlbGVjdG9yOiAnbmd4LWltYWdlLWZpZWxkJyxcclxuICBzdGFuZGFsb25lOiB0cnVlLFxyXG4gIGltcG9ydHM6IFtdLFxyXG4gIHRlbXBsYXRlOiBgXHJcbiAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkIG5neC1maWVsZC0taW1hZ2VcIlxyXG4gICAgICBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkXCIgW2F0dHIuZGF0YS1maWVsZC10eXBlXT1cImZpZWxkLnR5cGVcIiBbY2xhc3Mubmd4LWZpZWxkLS1yZWFkb25seV09XCJyZWFkb25seVwiIFtjbGFzcy5uZ3gtZmllbGQtLW1hc2tlZF09XCJtYXNrZWRcIj5cclxuICAgICAgPGxhYmVsIGNsYXNzPVwibmd4LWZpZWxkX19sYWJlbFwiIFthdHRyLmZvcl09XCInZmllbGQtJyArIGZpZWxkLmlkXCI+e3sgbGFiZWwgfX08L2xhYmVsPlxyXG4gICAgICBAaWYgKG1hc2tlZCkge1xyXG4gICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX192YWx1ZSBuZ3gtZmllbGRfX3ZhbHVlLS1tYXNrZWRcIiBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkICsgJy1tYXNrZWQnXCI+WFhYWFhYWFhYPC9zcGFuPlxyXG4gICAgICB9IEBlbHNlIHtcclxuICAgICAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkX19pbWFnZS13cmFwXCI+XHJcbiAgICAgICAgICBAaWYgKHByZXZpZXdVcmwoKSkge1xyXG4gICAgICAgICAgICA8aW1nIGNsYXNzPVwibmd4LWZpZWxkX19pbWFnZS1wcmV2aWV3XCIgW3NyY109XCJwcmV2aWV3VXJsKClcIiBbYWx0XT1cImxhYmVsXCIgLz5cclxuICAgICAgICAgIH0gQGVsc2Uge1xyXG4gICAgICAgICAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkX19pbWFnZS1wbGFjZWhvbGRlclwiPlxyXG4gICAgICAgICAgICAgIDxzcGFuPvCfk7c8L3NwYW4+XHJcbiAgICAgICAgICAgICAgPHNwYW4+Tm8gaW1hZ2U8L3NwYW4+XHJcbiAgICAgICAgICAgIDwvZGl2PlxyXG4gICAgICAgICAgfVxyXG4gICAgICAgICAgQGlmICghcmVhZG9ubHkpIHtcclxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cIm5neC1maWVsZF9faW1hZ2UtYWN0aW9uc1wiPlxyXG4gICAgICAgICAgICAgIDxsYWJlbCBjbGFzcz1cIm5neC1maWVsZF9fdXBsb2FkLWJ0blwiIFtjbGFzcy5uZ3gtZmllbGRfX3VwbG9hZC1idG4tLWxvYWRpbmddPVwidXBsb2FkaW5nKClcIj5cclxuICAgICAgICAgICAgICAgIEBpZiAodXBsb2FkaW5nKCkpIHtcclxuICAgICAgICAgICAgICAgICAgPHNwYW4+VXBsb2FkaW5n4oCmPC9zcGFuPlxyXG4gICAgICAgICAgICAgICAgfSBAZWxzZSB7XHJcbiAgICAgICAgICAgICAgICAgIDxzcGFuPnt7IHByZXZpZXdVcmwoKSA/ICdDaGFuZ2UnIDogJ1VwbG9hZCcgfX08L3NwYW4+XHJcbiAgICAgICAgICAgICAgICAgIDxpbnB1dFxyXG4gICAgICAgICAgICAgICAgICAgIHR5cGU9XCJmaWxlXCJcclxuICAgICAgICAgICAgICAgICAgICBjbGFzcz1cIm5neC1maWVsZF9fZmlsZS1pbnB1dFwiXHJcbiAgICAgICAgICAgICAgICAgICAgW2lkXT1cIidmaWVsZC0nICsgZmllbGQuaWRcIlxyXG4gICAgICAgICAgICAgICAgICAgIGFjY2VwdD1cImltYWdlLypcIlxyXG4gICAgICAgICAgICAgICAgICAgIFtkaXNhYmxlZF09XCJmaWVsZC5kaXNhYmxlZCB8fCB1cGxvYWRpbmcoKVwiXHJcbiAgICAgICAgICAgICAgICAgICAgKGNoYW5nZSk9XCJvbkZpbGVTZWxlY3QoJGFueSgkZXZlbnQudGFyZ2V0KS5maWxlc1swXSlcIlxyXG4gICAgICAgICAgICAgICAgICAvPlxyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgIDwvbGFiZWw+XHJcbiAgICAgICAgICAgICAgQGlmIChwcmV2aWV3VXJsKCkpIHtcclxuICAgICAgICAgICAgICAgIDxidXR0b24gdHlwZT1cImJ1dHRvblwiIGNsYXNzPVwibmd4LWZpZWxkX19yZW1vdmUtYnRuXCIgKGNsaWNrKT1cInJlbW92ZSgpXCI+UmVtb3ZlPC9idXR0b24+XHJcbiAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICA8L2Rpdj5cclxuICAgICAgICAgIH1cclxuICAgICAgICA8L2Rpdj5cclxuICAgICAgICBAaWYgKHVwbG9hZEVycm9yKCkpIHtcclxuICAgICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX19lcnJvclwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLWVycm9yJ1wiPnt7IHVwbG9hZEVycm9yKCkgfX08L3NwYW4+XHJcbiAgICAgICAgfVxyXG4gICAgICB9XHJcbiAgICA8L2Rpdj5cclxuICBgLFxyXG59KVxyXG5leHBvcnQgY2xhc3MgSW1hZ2VGaWVsZENvbXBvbmVudCBpbXBsZW1lbnRzIE9uRGVzdHJveSB7XHJcbiAgQElucHV0KCkgZmllbGQhOiBOZXN0ZWRGaWVsZENvbmZpZztcclxuICBASW5wdXQoKSBjb250cm9sITogQWJzdHJhY3RDb250cm9sO1xyXG4gIEBJbnB1dCgpIGxhbmd1YWdlOiBzdHJpbmcgPSAnZW4nO1xyXG4gIEBJbnB1dCgpIHJlYWRvbmx5OiBib29sZWFuID0gZmFsc2U7XHJcbiAgQElucHV0KCkgbWFza2VkOiBib29sZWFuID0gZmFsc2U7XHJcblxyXG4gIHByaXZhdGUgcmVhZG9ubHkgdXBsb2FkcyA9IGluamVjdChGaWxlVXBsb2FkU2VydmljZSk7XHJcblxyXG4gIHJlYWRvbmx5IHVwbG9hZGluZyA9IHNpZ25hbChmYWxzZSk7XHJcbiAgcmVhZG9ubHkgdXBsb2FkRXJyb3IgPSBzaWduYWw8c3RyaW5nIHwgbnVsbD4obnVsbCk7XHJcblxyXG4gIC8qKiBDYWNoZWQgc28gYW4gb2JqZWN0IFVSTCBpcyBtaW50ZWQgb25jZSBwZXIgRmlsZSwgbm90IG9uY2UgcGVyIGNoYW5nZS1kZXRlY3Rpb24gcGFzcy4gKi9cclxuICBwcml2YXRlIHByZXZpZXdTb3VyY2U6IEZpbGVSZWYgfCBudWxsID0gbnVsbDtcclxuICBwcml2YXRlIHByZXZpZXdDYWNoZTogc3RyaW5nIHwgbnVsbCA9IG51bGw7XHJcblxyXG4gIGdldCBsYWJlbCgpOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIHJlc29sdmVMYWJlbCh0aGlzLmZpZWxkPy5sYWJlbCwgdGhpcy5sYW5ndWFnZSk7XHJcbiAgfVxyXG5cclxuICBwcmV2aWV3VXJsKCk6IHN0cmluZyB8IG51bGwge1xyXG4gICAgY29uc3QgdmFsdWUgPSAodGhpcy5jb250cm9sPy52YWx1ZSBhcyBGaWxlUmVmIHwgbnVsbCkgPz8gbnVsbDtcclxuICAgIGlmICh2YWx1ZSA9PT0gdGhpcy5wcmV2aWV3U291cmNlKSByZXR1cm4gdGhpcy5wcmV2aWV3Q2FjaGU7XHJcblxyXG4gICAgdGhpcy51cGxvYWRzLnJldm9rZVByZXZpZXdVcmwodGhpcy5wcmV2aWV3Q2FjaGUpO1xyXG4gICAgdGhpcy5wcmV2aWV3U291cmNlID0gdmFsdWU7XHJcbiAgICB0aGlzLnByZXZpZXdDYWNoZSA9IHRoaXMudXBsb2Fkcy5wcmV2aWV3VXJsRm9yKHZhbHVlKTtcclxuICAgIHJldHVybiB0aGlzLnByZXZpZXdDYWNoZTtcclxuICB9XHJcblxyXG4gIGFzeW5jIG9uRmlsZVNlbGVjdChmaWxlOiBGaWxlIHwgdW5kZWZpbmVkKTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICBpZiAoIWZpbGUpIHJldHVybjtcclxuICAgIHRoaXMudXBsb2FkRXJyb3Iuc2V0KG51bGwpO1xyXG4gICAgdGhpcy51cGxvYWRpbmcuc2V0KHRydWUpO1xyXG4gICAgdHJ5IHtcclxuICAgICAgdGhpcy5jb250cm9sLnNldFZhbHVlKGF3YWl0IHRoaXMudXBsb2Fkcy50b0ZpbGVSZWYoZmlsZSkpO1xyXG4gICAgICB0aGlzLmNvbnRyb2wubWFya0FzVG91Y2hlZCgpO1xyXG4gICAgfSBjYXRjaCB7XHJcbiAgICAgIHRoaXMudXBsb2FkRXJyb3Iuc2V0KCdVcGxvYWQgZmFpbGVkLiBQbGVhc2UgdHJ5IGFnYWluLicpO1xyXG4gICAgfSBmaW5hbGx5IHtcclxuICAgICAgdGhpcy51cGxvYWRpbmcuc2V0KGZhbHNlKTtcclxuICAgIH1cclxuICB9XHJcblxyXG4gIHJlbW92ZSgpOiB2b2lkIHtcclxuICAgIHRoaXMuY29udHJvbC5zZXRWYWx1ZShudWxsKTtcclxuICAgIHRoaXMuY29udHJvbC5tYXJrQXNUb3VjaGVkKCk7XHJcbiAgfVxyXG5cclxuICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcclxuICAgIHRoaXMudXBsb2Fkcy5yZXZva2VQcmV2aWV3VXJsKHRoaXMucHJldmlld0NhY2hlKTtcclxuICB9XHJcbn1cclxuIl19
@@ -0,0 +1,151 @@
1
+ import { Component, Input } from '@angular/core';
2
+ import { ReactiveFormsModule } from '@angular/forms';
3
+ import { resolveLabel } from '@dynamic-entity/core';
4
+ import * as i0 from "@angular/core";
5
+ import * as i1 from "@angular/forms";
6
+ const MONTH_NAMES = [
7
+ 'January', 'February', 'March', 'April', 'May', 'June',
8
+ 'July', 'August', 'September', 'October', 'November', 'December',
9
+ ];
10
+ const CURRENT_YEAR = new Date().getFullYear();
11
+ const YEARS = Array.from({ length: 80 }, (_, i) => CURRENT_YEAR - i);
12
+ /**
13
+ * Month-Year field: two linked selects (Month + Year).
14
+ * Value stored as ISO partial date string "YYYY-MM" for unambiguous storage.
15
+ */
16
+ export class MonthYearFieldComponent {
17
+ field;
18
+ control;
19
+ language = 'en';
20
+ readonly = false;
21
+ masked = false;
22
+ months = MONTH_NAMES.map((label, i) => ({ value: String(i + 1).padStart(2, '0'), label }));
23
+ years = YEARS;
24
+ get label() {
25
+ return resolveLabel(this.field?.label, this.language);
26
+ }
27
+ /** Parses "YYYY-MM" or "YYYY-MM-DD" stored value → month part. */
28
+ get selectedMonth() {
29
+ const v = String(this.control.value ?? '');
30
+ return v.length >= 7 ? v.substring(5, 7) : '';
31
+ }
32
+ get selectedYear() {
33
+ const v = String(this.control.value ?? '');
34
+ return v.length >= 4 ? v.substring(0, 4) : '';
35
+ }
36
+ onMonthChange(month) {
37
+ const year = this.selectedYear || new Date().getFullYear().toString();
38
+ this.control.setValue(month ? `${year}-${month}` : null);
39
+ this.control.markAsTouched();
40
+ }
41
+ onYearChange(year) {
42
+ const month = this.selectedMonth || '01';
43
+ this.control.setValue(year ? `${year}-${month}` : null);
44
+ this.control.markAsTouched();
45
+ }
46
+ formatValue(value) {
47
+ if (!value)
48
+ return '—';
49
+ const [year, monthNum] = String(value).split('-');
50
+ const monthName = MONTH_NAMES[parseInt(monthNum, 10) - 1];
51
+ return monthName && year ? `${monthName} ${year}` : String(value);
52
+ }
53
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MonthYearFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
54
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: MonthYearFieldComponent, isStandalone: true, selector: "ngx-month-year-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
55
+ <div class="ngx-field ngx-field--month-year"
56
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
57
+ <label class="ngx-field__label">{{ label }}</label>
58
+ @if (masked) {
59
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
60
+ } @else if (readonly) {
61
+ <span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatValue(control.value) }}</span>
62
+ } @else {
63
+ <div class="ngx-field__month-year-wrap">
64
+ <select
65
+ class="ngx-field__input ngx-field__input--month" [attr.data-testid]="'field-' + field.id + '-month'"
66
+ [value]="selectedMonth"
67
+ (change)="onMonthChange($any($event.target).value)"
68
+ [attr.disabled]="field.disabled ? true : null"
69
+ >
70
+ <option value="">Month</option>
71
+ @for (m of months; track m.value) {
72
+ <option [value]="m.value">{{ m.label }}</option>
73
+ }
74
+ </select>
75
+ <select
76
+ class="ngx-field__input ngx-field__input--year" [attr.data-testid]="'field-' + field.id + '-year'"
77
+ [value]="selectedYear"
78
+ (change)="onYearChange($any($event.target).value)"
79
+ [attr.disabled]="field.disabled ? true : null"
80
+ >
81
+ <option value="">Year</option>
82
+ @for (y of years; track y) {
83
+ <option [value]="y">{{ y }}</option>
84
+ }
85
+ </select>
86
+ </div>
87
+ @if (control.invalid && control.touched) {
88
+ <span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
89
+ }
90
+ }
91
+ </div>
92
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }] });
93
+ }
94
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MonthYearFieldComponent, decorators: [{
95
+ type: Component,
96
+ args: [{
97
+ selector: 'ngx-month-year-field',
98
+ standalone: true,
99
+ imports: [ReactiveFormsModule],
100
+ template: `
101
+ <div class="ngx-field ngx-field--month-year"
102
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
103
+ <label class="ngx-field__label">{{ label }}</label>
104
+ @if (masked) {
105
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
106
+ } @else if (readonly) {
107
+ <span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatValue(control.value) }}</span>
108
+ } @else {
109
+ <div class="ngx-field__month-year-wrap">
110
+ <select
111
+ class="ngx-field__input ngx-field__input--month" [attr.data-testid]="'field-' + field.id + '-month'"
112
+ [value]="selectedMonth"
113
+ (change)="onMonthChange($any($event.target).value)"
114
+ [attr.disabled]="field.disabled ? true : null"
115
+ >
116
+ <option value="">Month</option>
117
+ @for (m of months; track m.value) {
118
+ <option [value]="m.value">{{ m.label }}</option>
119
+ }
120
+ </select>
121
+ <select
122
+ class="ngx-field__input ngx-field__input--year" [attr.data-testid]="'field-' + field.id + '-year'"
123
+ [value]="selectedYear"
124
+ (change)="onYearChange($any($event.target).value)"
125
+ [attr.disabled]="field.disabled ? true : null"
126
+ >
127
+ <option value="">Year</option>
128
+ @for (y of years; track y) {
129
+ <option [value]="y">{{ y }}</option>
130
+ }
131
+ </select>
132
+ </div>
133
+ @if (control.invalid && control.touched) {
134
+ <span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
135
+ }
136
+ }
137
+ </div>
138
+ `,
139
+ }]
140
+ }], propDecorators: { field: [{
141
+ type: Input
142
+ }], control: [{
143
+ type: Input
144
+ }], language: [{
145
+ type: Input
146
+ }], readonly: [{
147
+ type: Input
148
+ }], masked: [{
149
+ type: Input
150
+ }] } });
151
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9udGgteWVhci1maWVsZC5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL2ZpZWxkLXR5cGVzL21vbnRoLXllYXItZmllbGQuY29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ2pELE9BQU8sRUFBbUIsbUJBQW1CLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUV0RSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7OztBQUVwRCxNQUFNLFdBQVcsR0FBRztJQUNsQixTQUFTLEVBQUUsVUFBVSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU07SUFDdEQsTUFBTSxFQUFFLFFBQVEsRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxVQUFVO0NBQ2pFLENBQUM7QUFFRixNQUFNLFlBQVksR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDO0FBQzlDLE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxZQUFZLEdBQUcsQ0FBQyxDQUFDLENBQUM7QUFFckU7OztHQUdHO0FBNkNILE1BQU0sT0FBTyx1QkFBdUI7SUFDekIsS0FBSyxDQUFxQjtJQUMxQixPQUFPLENBQW1CO0lBQzFCLFFBQVEsR0FBVyxJQUFJLENBQUM7SUFDeEIsUUFBUSxHQUFZLEtBQUssQ0FBQztJQUMxQixNQUFNLEdBQVksS0FBSyxDQUFDO0lBRXhCLE1BQU0sR0FBRyxXQUFXLENBQUMsR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxFQUFFLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FB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QkFBYixLQUFLO2dCQUNHLE9BQU87c0JBQWYsS0FBSztnQkFDRyxRQUFRO3NCQUFoQixLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csTUFBTTtzQkFBZCxLQUFLIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBJbnB1dCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xyXG5pbXBvcnQgeyBBYnN0cmFjdENvbnRyb2wsIFJlYWN0aXZlRm9ybXNNb2R1bGUgfSBmcm9tICdAYW5ndWxhci9mb3Jtcyc7XHJcbmltcG9ydCB0eXBlIHsgTmVzdGVkRmllbGRDb25maWcgfSBmcm9tICdAZHluYW1pYy1lbnRpdHkvY29yZSc7XHJcbmltcG9ydCB7IHJlc29sdmVMYWJlbCB9IGZyb20gJ0BkeW5hbWljLWVudGl0eS9jb3JlJztcclxuXHJcbmNvbnN0IE1PTlRIX05BTUVTID0gW1xyXG4gICdKYW51YXJ5JywgJ0ZlYnJ1YXJ5JywgJ01hcmNoJywgJ0FwcmlsJywgJ01heScsICdKdW5lJyxcclxuICAnSnVseScsICdBdWd1c3QnLCAnU2VwdGVtYmVyJywgJ09jdG9iZXInLCAnTm92ZW1iZXInLCAnRGVjZW1iZXInLFxyXG5dO1xyXG5cclxuY29uc3QgQ1VSUkVOVF9ZRUFSID0gbmV3IERhdGUoKS5nZXRGdWxsWWVhcigpO1xyXG5jb25zdCBZRUFSUyA9IEFycmF5LmZyb20oeyBsZW5ndGg6IDgwIH0sIChfLCBpKSA9PiBDVVJSRU5UX1lFQVIgLSBpKTtcclxuXHJcbi8qKlxyXG4gKiBNb250aC1ZZWFyIGZpZWxkOiB0d28gbGlua2VkIHNlbGVjdHMgKE1vbnRoICsgWWVhcikuXHJcbiAqIFZhbHVlIHN0b3JlZCBhcyBJU08gcGFydGlhbCBkYXRlIHN0cmluZyBcIllZWVktTU1cIiBmb3IgdW5hbWJpZ3VvdXMgc3RvcmFnZS5cclxuICovXHJcbkBDb21wb25lbnQoe1xyXG4gIHNlbGVjdG9yOiAnbmd4LW1vbnRoLXllYXItZmllbGQnLFxyXG4gIHN0YW5kYWxvbmU6IHRydWUsXHJcbiAgaW1wb3J0czogW1JlYWN0aXZlRm9ybXNNb2R1bGVdLFxyXG4gIHRlbXBsYXRlOiBgXHJcbiAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkIG5neC1maWVsZC0tbW9udGgteWVhclwiXHJcbiAgICAgIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWRcIiBbYXR0ci5kYXRhLWZpZWxkLXR5cGVdPVwiZmllbGQudHlwZVwiIFtjbGFzcy5uZ3gtZmllbGQtLXJlYWRvbmx5XT1cInJlYWRvbmx5XCIgW2NsYXNzLm5neC1maWVsZC0tbWFza2VkXT1cIm1hc2tlZFwiPlxyXG4gICAgICA8bGFiZWwgY2xhc3M9XCJuZ3gtZmllbGRfX2xhYmVsXCI+e3sgbGFiZWwgfX08L2xhYmVsPlxyXG4gICAgICBAaWYgKG1hc2tlZCkge1xyXG4gICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX192YWx1ZSBuZ3gtZmllbGRfX3ZhbHVlLS1tYXNrZWRcIiBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkICsgJy1tYXNrZWQnXCI+WFhYWFhYWFhYPC9zcGFuPlxyXG4gICAgICB9IEBlbHNlIGlmIChyZWFkb25seSkge1xyXG4gICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX192YWx1ZVwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLXZhbHVlJ1wiPnt7IGZvcm1hdFZhbHVlKGNvbnRyb2wudmFsdWUpIH19PC9zcGFuPlxyXG4gICAgICB9IEBlbHNlIHtcclxuICAgICAgICA8ZGl2IGNsYXNzPVwibmd4LWZpZWxkX19tb250aC15ZWFyLXdyYXBcIj5cclxuICAgICAgICAgIDxzZWxlY3RcclxuICAgICAgICAgICAgY2xhc3M9XCJuZ3gtZmllbGRfX2lucHV0IG5neC1maWVsZF9faW5wdXQtLW1vbnRoXCIgW2F0dHIuZGF0YS10ZXN0aWRdPVwiJ2ZpZWxkLScgKyBmaWVsZC5pZCArICctbW9udGgnXCJcclxuICAgICAgICAgICAgW3ZhbHVlXT1cInNlbGVjdGVkTW9udGhcIlxyXG4gICAgICAgICAgICAoY2hhbmdlKT1cIm9uTW9udGhDaGFuZ2UoJGFueSgkZXZlbnQudGFyZ2V0KS52YWx1ZSlcIlxyXG4gICAgICAgICAgICBbYXR0ci5kaXNhYmxlZF09XCJmaWVsZC5kaXNhYmxlZCA/IHRydWUgOiBudWxsXCJcclxuICAgICAgICAgID5cclxuICAgICAgICAgICAgPG9wdGlvbiB2YWx1ZT1cIlwiPk1vbnRoPC9vcHRpb24+XHJcbiAgICAgICAgICAgIEBmb3IgKG0gb2YgbW9udGhzOyB0cmFjayBtLnZhbHVlKSB7XHJcbiAgICAgICAgICAgICAgPG9wdGlvbiBbdmFsdWVdPVwibS52YWx1ZVwiPnt7IG0ubGFiZWwgfX08L29wdGlvbj5cclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgPC9zZWxlY3Q+XHJcbiAgICAgICAgICA8c2VsZWN0XHJcbiAgICAgICAgICAgIGNsYXNzPVwibmd4LWZpZWxkX19pbnB1dCBuZ3gtZmllbGRfX2lucHV0LS15ZWFyXCIgW2F0dHIuZGF0YS10ZXN0aWRdPVwiJ2ZpZWxkLScgKyBmaWVsZC5pZCArICcteWVhcidcIlxyXG4gICAgICAgICAgICBbdmFsdWVdPVwic2VsZWN0ZWRZZWFyXCJcclxuICAgICAgICAgICAgKGNoYW5nZSk9XCJvblllYXJDaGFuZ2UoJGFueSgkZXZlbnQudGFyZ2V0KS52YWx1ZSlcIlxyXG4gICAgICAgICAgICBbYXR0ci5kaXNhYmxlZF09XCJmaWVsZC5kaXNhYmxlZCA/IHRydWUgOiBudWxsXCJcclxuICAgICAgICAgID5cclxuICAgICAgICAgICAgPG9wdGlvbiB2YWx1ZT1cIlwiPlllYXI8L29wdGlvbj5cclxuICAgICAgICAgICAgQGZvciAoeSBvZiB5ZWFyczsgdHJhY2sgeSkge1xyXG4gICAgICAgICAgICAgIDxvcHRpb24gW3ZhbHVlXT1cInlcIj57eyB5IH19PC9vcHRpb24+XHJcbiAgICAgICAgICAgIH1cclxuICAgICAgICAgIDwvc2VsZWN0PlxyXG4gICAgICAgIDwvZGl2PlxyXG4gICAgICAgIEBpZiAoY29udHJvbC5pbnZhbGlkICYmIGNvbnRyb2wudG91Y2hlZCkge1xyXG4gICAgICAgICAgPHNwYW4gY2xhc3M9XCJuZ3gtZmllbGRfX2Vycm9yXCIgW2F0dHIuZGF0YS10ZXN0aWRdPVwiJ2ZpZWxkLScgKyBmaWVsZC5pZCArICctZXJyb3InXCI+VGhpcyBmaWVsZCBoYXMgYW4gZXJyb3I8L3NwYW4+XHJcbiAgICAgICAgfVxyXG4gICAgICB9XHJcbiAgICA8L2Rpdj5cclxuICBgLFxyXG59KVxyXG5leHBvcnQgY2xhc3MgTW9udGhZZWFyRmllbGRDb21wb25lbnQge1xyXG4gIEBJbnB1dCgpIGZpZWxkITogTmVzdGVkRmllbGRDb25maWc7XHJcbiAgQElucHV0KCkgY29udHJvbCE6IEFic3RyYWN0Q29udHJvbDtcclxuICBASW5wdXQoKSBsYW5ndWFnZTogc3RyaW5nID0gJ2VuJztcclxuICBASW5wdXQoKSByZWFkb25seTogYm9vbGVhbiA9IGZhbHNlO1xyXG4gIEBJbnB1dCgpIG1hc2tlZDogYm9vbGVhbiA9IGZhbHNlO1xyXG5cclxuICByZWFkb25seSBtb250aHMgPSBNT05USF9OQU1FUy5tYXAoKGxhYmVsLCBpKSA9PiAoeyB2YWx1ZTogU3RyaW5nKGkgKyAxKS5wYWRTdGFydCgyLCAnMCcpLCBsYWJlbCB9KSk7XHJcbiAgcmVhZG9ubHkgeWVhcnMgPSBZRUFSUztcclxuXHJcbiAgZ2V0IGxhYmVsKCk6IHN0cmluZyB7XHJcbiAgICByZXR1cm4gcmVzb2x2ZUxhYmVsKHRoaXMuZmllbGQ/LmxhYmVsLCB0aGlzLmxhbmd1YWdlKTtcclxuICB9XHJcblxyXG4gIC8qKiBQYXJzZXMgXCJZWVlZLU1NXCIgb3IgXCJZWVlZLU1NLUREXCIgc3RvcmVkIHZhbHVlIOKGkiBtb250aCBwYXJ0LiAqL1xyXG4gIGdldCBzZWxlY3RlZE1vbnRoKCk6IHN0cmluZyB7XHJcbiAgICBjb25zdCB2ID0gU3RyaW5nKHRoaXMuY29udHJvbC52YWx1ZSA/PyAnJyk7XHJcbiAgICByZXR1cm4gdi5sZW5ndGggPj0gNyA/IHYuc3Vic3RyaW5nKDUsIDcpIDogJyc7XHJcbiAgfVxyXG5cclxuICBnZXQgc2VsZWN0ZWRZZWFyKCk6IHN0cmluZyB7XHJcbiAgICBjb25zdCB2ID0gU3RyaW5nKHRoaXMuY29udHJvbC52YWx1ZSA/PyAnJyk7XHJcbiAgICByZXR1cm4gdi5sZW5ndGggPj0gNCA/IHYuc3Vic3RyaW5nKDAsIDQpIDogJyc7XHJcbiAgfVxyXG5cclxuICBvbk1vbnRoQ2hhbmdlKG1vbnRoOiBzdHJpbmcpOiB2b2lkIHtcclxuICAgIGNvbnN0IHllYXIgPSB0aGlzLnNlbGVjdGVkWWVhciB8fCBuZXcgRGF0ZSgpLmdldEZ1bGxZZWFyKCkudG9TdHJpbmcoKTtcclxuICAgIHRoaXMuY29udHJvbC5zZXRWYWx1ZShtb250aCA/IGAke3llYXJ9LSR7bW9udGh9YCA6IG51bGwpO1xyXG4gICAgdGhpcy5jb250cm9sLm1hcmtBc1RvdWNoZWQoKTtcclxuICB9XHJcblxyXG4gIG9uWWVhckNoYW5nZSh5ZWFyOiBzdHJpbmcpOiB2b2lkIHtcclxuICAgIGNvbnN0IG1vbnRoID0gdGhpcy5zZWxlY3RlZE1vbnRoIHx8ICcwMSc7XHJcbiAgICB0aGlzLmNvbnRyb2wuc2V0VmFsdWUoeWVhciA/IGAke3llYXJ9LSR7bW9udGh9YCA6IG51bGwpO1xyXG4gICAgdGhpcy5jb250cm9sLm1hcmtBc1RvdWNoZWQoKTtcclxuICB9XHJcblxyXG4gIGZvcm1hdFZhbHVlKHZhbHVlOiB1bmtub3duKTogc3RyaW5nIHtcclxuICAgIGlmICghdmFsdWUpIHJldHVybiAn4oCUJztcclxuICAgIGNvbnN0IFt5ZWFyLCBtb250aE51bV0gPSBTdHJpbmcodmFsdWUpLnNwbGl0KCctJyk7XHJcbiAgICBjb25zdCBtb250aE5hbWUgPSBNT05USF9OQU1FU1twYXJzZUludChtb250aE51bSwgMTApIC0gMV07XHJcbiAgICByZXR1cm4gbW9udGhOYW1lICYmIHllYXIgPyBgJHttb250aE5hbWV9ICR7eWVhcn1gIDogU3RyaW5nKHZhbHVlKTtcclxuICB9XHJcbn1cclxuIl19
@@ -1,48 +1,95 @@
1
- import { Component, Input } from '@angular/core';
1
+ import { Component, Input, inject, signal } from '@angular/core';
2
2
  import { ReactiveFormsModule } from '@angular/forms';
3
+ import { getOptionStoredValue, resolveLabel, resolveOptionLabel, valuesMatch, } from '@dynamic-entity/core';
4
+ import { LookupRegistryService, refreshChoiceOptions } from '../services/lookup-registry.service';
3
5
  import * as i0 from "@angular/core";
4
6
  import * as i1 from "@angular/forms";
5
7
  export class MultiSelectFieldComponent {
6
- field;
8
+ lookups = inject(LookupRegistryService);
9
+ _field;
10
+ _language = 'en';
11
+ /** Setter-based so options resolve however the input is set — see `refreshChoiceOptions`. */
12
+ set field(value) {
13
+ this._field = value;
14
+ refreshChoiceOptions(this, this.lookups);
15
+ }
16
+ get field() {
17
+ return this._field;
18
+ }
19
+ set language(value) {
20
+ this._language = value || 'en';
21
+ refreshChoiceOptions(this, this.lookups);
22
+ }
23
+ get language() {
24
+ return this._language;
25
+ }
7
26
  control;
8
- language = 'en';
9
27
  readonly = false;
10
28
  masked = false;
29
+ /** Inline `options`, or the field's named list resolved through the registry (§6.3). */
30
+ options = signal([]);
31
+ compareFn = (o1, o2) => valuesMatch(o1, o2, this.language);
32
+ isObjectVal(option) {
33
+ const val = getOptionStoredValue(option);
34
+ return typeof val === 'object' && val !== null;
35
+ }
36
+ get label() {
37
+ return resolveLabel(this.field?.label, this.language);
38
+ }
39
+ getOptStoredVal(option) {
40
+ return getOptionStoredValue(option);
41
+ }
42
+ getOptLabel(option) {
43
+ return resolveOptionLabel(option, this.language);
44
+ }
45
+ /** Read-only display — synchronous, per §6.2. See `DropdownFieldComponent.getLabel`. */
11
46
  getLabels(values) {
12
47
  if (!Array.isArray(values) || !values.length)
13
48
  return '—';
14
49
  return values
15
50
  .map(v => {
16
- const opt = (this.field.options || []).find(o => o.value === v);
17
- return opt ? (opt.label[this.language] || opt.label['en']) : String(v);
51
+ const opt = this.options().find(o => valuesMatch(getOptionStoredValue(o), v, this.language));
52
+ if (opt)
53
+ return this.getOptLabel(opt);
54
+ const cached = this.lookups.labelFor(this.field?.listName, v, this.language);
55
+ if (cached)
56
+ return cached;
57
+ return typeof v === 'object' ? resolveLabel(v, this.language) : String(v);
18
58
  })
19
59
  .join(', ');
20
60
  }
21
61
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MultiSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
22
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: MultiSelectFieldComponent, isStandalone: true, selector: "ngx-multi-select-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
23
- <div class="ngx-field ngx-field--multiSelect" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
24
- <label class="ngx-field__label">{{ field.label[language] || field.label['en'] }}</label>
25
- @if (masked) {
26
- <span class="ngx-field__value ngx-field__value--masked">XXXXXXXXX</span>
27
- } @else if (readonly) {
28
- <span class="ngx-field__value">{{ getLabels(control.value) }}</span>
29
- } @else {
30
- <select
31
- class="ngx-field__input"
32
- [formControl]="$any(control)"
33
- [attr.disabled]="field.disabled ? true : null"
34
- multiple
35
- size="4"
36
- >
37
- @for (option of field.options || []; track option.value) {
38
- <option [value]="option.value">{{ option.label[language] || option.label['en'] }}</option>
39
- }
40
- </select>
41
- @if (control.invalid && control.touched) {
42
- <span class="ngx-field__error">This field has an error</span>
43
- }
44
- }
45
- </div>
62
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: MultiSelectFieldComponent, isStandalone: true, selector: "ngx-multi-select-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
63
+ <div class="ngx-field ngx-field--multiSelect"
64
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
65
+ <label class="ngx-field__label">{{ label }}</label>
66
+ @if (masked) {
67
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
68
+ } @else if (readonly) {
69
+ <span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabels(control.value) }}</span>
70
+ } @else {
71
+ <select
72
+ [id]="field.id"
73
+ class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
74
+ [formControl]="$any(control)"
75
+ [compareWith]="compareFn"
76
+ [attr.disabled]="field.disabled ? true : null"
77
+ multiple
78
+ size="4"
79
+ >
80
+ @for (option of options(); track getOptLabel(option)) {
81
+ @if (isObjectVal(option)) {
82
+ <option [ngValue]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
83
+ } @else {
84
+ <option [value]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
85
+ }
86
+ }
87
+ </select>
88
+ @if (control.invalid && control.touched) {
89
+ <span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
90
+ }
91
+ }
92
+ </div>
46
93
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectMultipleControlValueAccessor, selector: "select[multiple][formControlName],select[multiple][formControl],select[multiple][ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
47
94
  }
48
95
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MultiSelectFieldComponent, decorators: [{
@@ -51,41 +98,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
51
98
  selector: 'ngx-multi-select-field',
52
99
  standalone: true,
53
100
  imports: [ReactiveFormsModule],
54
- template: `
55
- <div class="ngx-field ngx-field--multiSelect" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
56
- <label class="ngx-field__label">{{ field.label[language] || field.label['en'] }}</label>
57
- @if (masked) {
58
- <span class="ngx-field__value ngx-field__value--masked">XXXXXXXXX</span>
59
- } @else if (readonly) {
60
- <span class="ngx-field__value">{{ getLabels(control.value) }}</span>
61
- } @else {
62
- <select
63
- class="ngx-field__input"
64
- [formControl]="$any(control)"
65
- [attr.disabled]="field.disabled ? true : null"
66
- multiple
67
- size="4"
68
- >
69
- @for (option of field.options || []; track option.value) {
70
- <option [value]="option.value">{{ option.label[language] || option.label['en'] }}</option>
71
- }
72
- </select>
73
- @if (control.invalid && control.touched) {
74
- <span class="ngx-field__error">This field has an error</span>
75
- }
76
- }
77
- </div>
101
+ template: `
102
+ <div class="ngx-field ngx-field--multiSelect"
103
+ [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
104
+ <label class="ngx-field__label">{{ label }}</label>
105
+ @if (masked) {
106
+ <span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
107
+ } @else if (readonly) {
108
+ <span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabels(control.value) }}</span>
109
+ } @else {
110
+ <select
111
+ [id]="field.id"
112
+ class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
113
+ [formControl]="$any(control)"
114
+ [compareWith]="compareFn"
115
+ [attr.disabled]="field.disabled ? true : null"
116
+ multiple
117
+ size="4"
118
+ >
119
+ @for (option of options(); track getOptLabel(option)) {
120
+ @if (isObjectVal(option)) {
121
+ <option [ngValue]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
122
+ } @else {
123
+ <option [value]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
124
+ }
125
+ }
126
+ </select>
127
+ @if (control.invalid && control.touched) {
128
+ <span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
129
+ }
130
+ }
131
+ </div>
78
132
  `,
79
133
  }]
80
134
  }], propDecorators: { field: [{
81
135
  type: Input
82
- }], control: [{
83
- type: Input
84
136
  }], language: [{
85
137
  type: Input
138
+ }], control: [{
139
+ type: Input
86
140
  }], readonly: [{
87
141
  type: Input
88
142
  }], masked: [{
89
143
  type: Input
90
144
  }] } });
91
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibXVsdGktc2VsZWN0LWZpZWxkLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvZmllbGQtdHlwZXMvbXVsdGktc2VsZWN0LWZpZWxkLmNvbXBvbmVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUNqRCxPQUFPLEVBQW1CLG1CQUFtQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7OztBQWlDdEUsTUFBTSxPQUFPLHlCQUF5QjtJQUMzQixLQUFLLENBQWU7SUFDcEIsT0FBTyxDQUFtQjtJQUMxQixRQUFRLEdBQVcsSUFBSSxDQUFDO0lBQ3hCLFFBQVEsR0FBWSxLQUFLLENBQUM7SUFDMUIsTUFBTSxHQUFZLEtBQUssQ0FBQztJQUVqQyxTQUFTLENBQUMsTUFBYTtRQUNyQixJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNO1lBQUUsT0FBTyxHQUFHLENBQUM7UUFDekQsT0FBTyxNQUFNO2FBQ1YsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFO1lBQ1AsTUFBTSxHQUFHLEdBQUcsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsS0FBSyxLQUFLLENBQUMsQ0FBQyxDQUFDO1lBQ2hFLE9BQU8sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3pFLENBQUMsQ0FBQzthQUNELElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNoQixDQUFDO3dHQWZVLHlCQUF5Qjs0RkFBekIseUJBQXlCLGdNQTFCMUI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXdCVCwyREF6QlMsbUJBQW1COzs0RkEyQmxCLHlCQUF5QjtrQkE5QnJDLFNBQVM7bUJBQUM7b0JBQ1QsUUFBUSxFQUFFLHdCQUF3QjtvQkFDbEMsVUFBVSxFQUFFLElBQUk7b0JBQ2hCLE9BQU8sRUFBRSxDQUFDLG1CQUFtQixDQUFDO29CQUM5QixRQUFRLEVBQUU7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXdCVDtpQkFDRjs4QkFFVSxLQUFLO3NCQUFiLEtBQUs7Z0JBQ0csT0FBTztzQkFBZixLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csUUFBUTtzQkFBaEIsS0FBSztnQkFDRyxNQUFNO3NCQUFkLEtBQUsiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIElucHV0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBBYnN0cmFjdENvbnRyb2wsIFJlYWN0aXZlRm9ybXNNb2R1bGUgfSBmcm9tICdAYW5ndWxhci9mb3Jtcyc7XG5pbXBvcnQgdHlwZSB7IEZpZWxkQ29uZmlnIH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICduZ3gtbXVsdGktc2VsZWN0LWZpZWxkJyxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW1JlYWN0aXZlRm9ybXNNb2R1bGVdLFxuICB0ZW1wbGF0ZTogYFxuICAgIDxkaXYgY2xhc3M9XCJuZ3gtZmllbGQgbmd4LWZpZWxkLS1tdWx0aVNlbGVjdFwiIFtjbGFzcy5uZ3gtZmllbGQtLXJlYWRvbmx5XT1cInJlYWRvbmx5XCIgW2NsYXNzLm5neC1maWVsZC0tbWFza2VkXT1cIm1hc2tlZFwiPlxuICAgICAgPGxhYmVsIGNsYXNzPVwibmd4LWZpZWxkX19sYWJlbFwiPnt7IGZpZWxkLmxhYmVsW2xhbmd1YWdlXSB8fCBmaWVsZC5sYWJlbFsnZW4nXSB9fTwvbGFiZWw+XG4gICAgICBAaWYgKG1hc2tlZCkge1xuICAgICAgICA8c3BhbiBjbGFzcz1cIm5neC1maWVsZF9fdmFsdWUgbmd4LWZpZWxkX192YWx1ZS0tbWFza2VkXCI+WFhYWFhYWFhYPC9zcGFuPlxuICAgICAgfSBAZWxzZSBpZiAocmVhZG9ubHkpIHtcbiAgICAgICAgPHNwYW4gY2xhc3M9XCJuZ3gtZmllbGRfX3ZhbHVlXCI+e3sgZ2V0TGFiZWxzKGNvbnRyb2wudmFsdWUpIH19PC9zcGFuPlxuICAgICAgfSBAZWxzZSB7XG4gICAgICAgIDxzZWxlY3RcbiAgICAgICAgICBjbGFzcz1cIm5neC1maWVsZF9faW5wdXRcIlxuICAgICAgICAgIFtmb3JtQ29udHJvbF09XCIkYW55KGNvbnRyb2wpXCJcbiAgICAgICAgICBbYXR0ci5kaXNhYmxlZF09XCJmaWVsZC5kaXNhYmxlZCA/IHRydWUgOiBudWxsXCJcbiAgICAgICAgICBtdWx0aXBsZVxuICAgICAgICAgIHNpemU9XCI0XCJcbiAgICAgICAgPlxuICAgICAgICAgIEBmb3IgKG9wdGlvbiBvZiBmaWVsZC5vcHRpb25zIHx8IFtdOyB0cmFjayBvcHRpb24udmFsdWUpIHtcbiAgICAgICAgICAgIDxvcHRpb24gW3ZhbHVlXT1cIm9wdGlvbi52YWx1ZVwiPnt7IG9wdGlvbi5sYWJlbFtsYW5ndWFnZV0gfHwgb3B0aW9uLmxhYmVsWydlbiddIH19PC9vcHRpb24+XG4gICAgICAgICAgfVxuICAgICAgICA8L3NlbGVjdD5cbiAgICAgICAgQGlmIChjb250cm9sLmludmFsaWQgJiYgY29udHJvbC50b3VjaGVkKSB7XG4gICAgICAgICAgPHNwYW4gY2xhc3M9XCJuZ3gtZmllbGRfX2Vycm9yXCI+VGhpcyBmaWVsZCBoYXMgYW4gZXJyb3I8L3NwYW4+XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICA8L2Rpdj5cbiAgYCxcbn0pXG5leHBvcnQgY2xhc3MgTXVsdGlTZWxlY3RGaWVsZENvbXBvbmVudCB7XG4gIEBJbnB1dCgpIGZpZWxkITogRmllbGRDb25maWc7XG4gIEBJbnB1dCgpIGNvbnRyb2whOiBBYnN0cmFjdENvbnRyb2w7XG4gIEBJbnB1dCgpIGxhbmd1YWdlOiBzdHJpbmcgPSAnZW4nO1xuICBASW5wdXQoKSByZWFkb25seTogYm9vbGVhbiA9IGZhbHNlO1xuICBASW5wdXQoKSBtYXNrZWQ6IGJvb2xlYW4gPSBmYWxzZTtcblxuICBnZXRMYWJlbHModmFsdWVzOiBhbnlbXSk6IHN0cmluZyB7XG4gICAgaWYgKCFBcnJheS5pc0FycmF5KHZhbHVlcykgfHwgIXZhbHVlcy5sZW5ndGgpIHJldHVybiAn4oCUJztcbiAgICByZXR1cm4gdmFsdWVzXG4gICAgICAubWFwKHYgPT4ge1xuICAgICAgICBjb25zdCBvcHQgPSAodGhpcy5maWVsZC5vcHRpb25zIHx8IFtdKS5maW5kKG8gPT4gby52YWx1ZSA9PT0gdik7XG4gICAgICAgIHJldHVybiBvcHQgPyAob3B0LmxhYmVsW3RoaXMubGFuZ3VhZ2VdIHx8IG9wdC5sYWJlbFsnZW4nXSkgOiBTdHJpbmcodik7XG4gICAgICB9KVxuICAgICAgLmpvaW4oJywgJyk7XG4gIH1cbn1cbiJdfQ==
145
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibXVsdGktc2VsZWN0LWZpZWxkLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvZmllbGQtdHlwZXMvbXVsdGktc2VsZWN0LWZpZWxkLmNvbXBvbmVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ2pFLE9BQU8sRUFBbUIsbUJBQW1CLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUV0RSxPQUFPLEVBQ0wsb0JBQW9CLEVBQ3BCLFlBQVksRUFDWixrQkFBa0IsRUFDbEIsV0FBVyxHQUNaLE1BQU0sc0JBQXNCLENBQUM7QUFDOUIsT0FBTyxFQUFFLHFCQUFxQixFQUFFLG9CQUFvQixFQUFFLE1BQU0scUNBQXFDLENBQUM7OztBQXVDbEcsTUFBTSxPQUFPLHlCQUF5QjtJQUNuQixPQUFPLEdBQUcsTUFBTSxDQUFDLHFCQUFxQixDQUFDLENBQUM7SUFFakQsTUFBTSxDQUFxQjtJQUMzQixTQUFTLEdBQUcsSUFBSSxDQUFDO0lBRXpCLDZGQUE2RjtJQUM3RixJQUFhLEtBQUssQ0FBQyxLQUF3QjtRQUN6QyxJQUFJLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQztRQUNwQixvQkFBb0IsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzNDLENBQUM7SUFDRCxJQUFJLEtBQUs7UUFDUCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUM7SUFDckIsQ0FBQztJQUVELElBQWEsUUFBUSxDQUFDLEtBQWE7UUFDakMsSUFBSSxDQUFDLFNBQVMsR0FBRyxLQUFLLElBQUksSUFBSSxDQUFDO1FBQy9CLG9CQUFvQixDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDM0MsQ0FBQztJQUNELElBQUksUUFBUTtRQUNWLE9BQU8sSUFBSSxDQUFD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fSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuaW1wb3J0IHsgQWJzdHJhY3RDb250cm9sLCBSZWFjdGl2ZUZvcm1zTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvZm9ybXMnO1xyXG5pbXBvcnQgdHlwZSB7IERyb3Bkb3duT3B0aW9uLCBOZXN0ZWRGaWVsZENvbmZpZyB9IGZyb20gJ0BkeW5hbWljLWVudGl0eS9jb3JlJztcclxuaW1wb3J0IHtcclxuICBnZXRPcHRpb25TdG9yZWRWYWx1ZSxcclxuICByZXNvbHZlTGFiZWwsXHJcbiAgcmVzb2x2ZU9wdGlvbkxhYmVsLFxyXG4gIHZhbHVlc01hdGNoLFxyXG59IGZyb20gJ0BkeW5hbWljLWVudGl0eS9jb3JlJztcclxuaW1wb3J0IHsgTG9va3VwUmVnaXN0cnlTZXJ2aWNlLCByZWZyZXNoQ2hvaWNlT3B0aW9ucyB9IGZyb20gJy4uL3NlcnZpY2VzL2xvb2t1cC1yZWdpc3RyeS5zZXJ2aWNlJztcclxuXHJcbkBDb21wb25lbnQoe1xyXG4gIHNlbGVjdG9yOiAnbmd4LW11bHRpLXNlbGVjdC1maWVsZCcsXHJcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcclxuICBpbXBvcnRzOiBbUmVhY3RpdmVGb3Jtc01vZHVsZV0sXHJcbiAgdGVtcGxhdGU6IGBcclxuICAgIDxkaXYgY2xhc3M9XCJuZ3gtZmllbGQgbmd4LWZpZWxkLS1tdWx0aVNlbGVjdFwiXHJcbiAgICAgIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWRcIiBbYXR0ci5kYXRhLWZpZWxkLXR5cGVdPVwiZmllbGQudHlwZVwiIFtjbGFzcy5uZ3gtZmllbGQtLXJlYWRvbmx5XT1cInJlYWRvbmx5XCIgW2NsYXNzLm5neC1maWVsZC0tbWFza2VkXT1cIm1hc2tlZFwiPlxyXG4gICAgICA8bGFiZWwgY2xhc3M9XCJuZ3gtZmllbGRfX2xhYmVsXCI+e3sgbGFiZWwgfX08L2xhYmVsPlxyXG4gICAgICBAaWYgKG1hc2tlZCkge1xyXG4gICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX192YWx1ZSBuZ3gtZmllbGRfX3ZhbHVlLS1tYXNrZWRcIiBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkICsgJy1tYXNrZWQnXCI+WFhYWFhYWFhYPC9zcGFuPlxyXG4gICAgICB9IEBlbHNlIGlmIChyZWFkb25seSkge1xyXG4gICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LWZpZWxkX192YWx1ZVwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLXZhbHVlJ1wiPnt7IGdldExhYmVscyhjb250cm9sLnZhbHVlKSB9fTwvc3Bhbj5cclxuICAgICAgfSBAZWxzZSB7XHJcbiAgICAgICAgPHNlbGVjdFxyXG4gICAgICAgICAgW2lkXT1cImZpZWxkLmlkXCJcclxuICAgICAgICAgIGNsYXNzPVwibmd4LWZpZWxkX19pbnB1dFwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidmaWVsZC0nICsgZmllbGQuaWQgKyAnLWlucHV0J1wiXHJcbiAgICAgICAgICBbZm9ybUNvbnRyb2xdPVwiJGFueShjb250cm9sKVwiXHJcbiAgICAgICAgICBbY29tcGFyZVdpdGhdPVwiY29tcGFyZUZuXCJcclxuICAgICAgICAgIFthdHRyLmRpc2FibGVkXT1cImZpZWxkLmRpc2FibGVkID8gdHJ1ZSA6IG51bGxcIlxyXG4gICAgICAgICAgbXVsdGlwbGVcclxuICAgICAgICAgIHNpemU9XCI0XCJcclxuICAgICAgICA+XHJcbiAgICAgICAgICBAZm9yIChvcHRpb24gb2Ygb3B0aW9ucygpOyB0cmFjayBnZXRPcHRMYWJlbChvcHRpb24pKSB7XHJcbiAgICAgICAgICAgIEBpZiAoaXNPYmplY3RWYWwob3B0aW9uKSkge1xyXG4gICAgICAgICAgICAgIDxvcHRpb24gW25nVmFsdWVdPVwiZ2V0T3B0U3RvcmVkVmFsKG9wdGlvbilcIj57eyBnZXRPcHRMYWJlbChvcHRpb24pIH19PC9vcHRpb24+XHJcbiAgICAgICAgICAgIH0gQGVsc2Uge1xyXG4gICAgICAgICAgICAgIDxvcHRpb24gW3ZhbHVlXT1cImdldE9wdFN0b3JlZFZhbChvcHRpb24pXCI+e3sgZ2V0T3B0TGFiZWwob3B0aW9uKSB9fTwvb3B0aW9uPlxyXG4gICAgICAgICAgICB9XHJcbiAgICAgICAgICB9XHJcbiAgICAgICAgPC9zZWxlY3Q+XHJcbiAgICAgICAgQGlmIChjb250cm9sLmludmFsaWQgJiYgY29udHJvbC50b3VjaGVkKSB7XHJcbiAgICAgICAgICA8c3BhbiBjbGFzcz1cIm5neC1maWVsZF9fZXJyb3JcIiBbYXR0ci5kYXRhLXRlc3RpZF09XCInZmllbGQtJyArIGZpZWxkLmlkICsgJy1lcnJvcidcIj5UaGlzIGZpZWxkIGhhcyBhbiBlcnJvcjwvc3Bhbj5cclxuICAgICAgICB9XHJcbiAgICAgIH1cclxuICAgIDwvZGl2PlxyXG4gIGAsXHJcbn0pXHJcbmV4cG9ydCBjbGFzcyBNdWx0aVNlbGVjdEZpZWxkQ29tcG9uZW50IHtcclxuICBwcml2YXRlIHJlYWRvbmx5IGxvb2t1cHMgPSBpbmplY3QoTG9va3VwUmVnaXN0cnlTZXJ2aWNlKTtcclxuXHJcbiAgcHJpdmF0ZSBfZmllbGQhOiBOZXN0ZWRGaWVsZENvbmZpZztcclxuICBwcml2YXRlIF9sYW5ndWFnZSA9ICdlbic7XHJcblxyXG4gIC8qKiBTZXR0ZXItYmFzZWQgc28gb3B0aW9ucyByZXNvbHZlIGhvd2V2ZXIgdGhlIGlucHV0IGlzIHNldCDigJQgc2VlIGByZWZyZXNoQ2hvaWNlT3B0aW9uc2AuICovXHJcbiAgQElucHV0KCkgc2V0IGZpZWxkKHZhbHVlOiBOZXN0ZWRGaWVsZENvbmZpZykge1xyXG4gICAgdGhpcy5fZmllbGQgPSB2YWx1ZTtcclxuICAgIHJlZnJlc2hDaG9pY2VPcHRpb25zKHRoaXMsIHRoaXMubG9va3Vwcyk7XHJcbiAgfVxyXG4gIGdldCBmaWVsZCgpOiBOZXN0ZWRGaWVsZENvbmZpZyB7XHJcbiAgICByZXR1cm4gdGhpcy5fZmllbGQ7XHJcbiAgfVxyXG5cclxuICBASW5wdXQoKSBzZXQgbGFuZ3VhZ2UodmFsdWU6IHN0cmluZykge1xyXG4gICAgdGhpcy5fbGFuZ3VhZ2UgPSB2YWx1ZSB8fCAnZW4nO1xyXG4gICAgcmVmcmVzaENob2ljZU9wdGlvbnModGhpcywgdGhpcy5sb29rdXBzKTtcclxuICB9XHJcbiAgZ2V0IGxhbmd1YWdlKCk6IHN0cmluZyB7XHJcbiAgICByZXR1cm4gdGhpcy5fbGFuZ3VhZ2U7XHJcbiAgfVxyXG5cclxuICBASW5wdXQoKSBjb250cm9sITogQWJzdHJhY3RDb250cm9sO1xyXG4gIEBJbnB1dCgpIHJlYWRvbmx5OiBib29sZWFuID0gZmFsc2U7XHJcbiAgQElucHV0KCkgbWFza2VkOiBib29sZWFuID0gZmFsc2U7XHJcblxyXG4gIC8qKiBJbmxpbmUgYG9wdGlvbnNgLCBvciB0aGUgZmllbGQncyBuYW1lZCBsaXN0IHJlc29sdmVkIHRocm91Z2ggdGhlIHJlZ2lzdHJ5ICjCpzYuMykuICovXHJcbiAgcmVhZG9ubHkgb3B0aW9ucyA9IHNpZ25hbDxEcm9wZG93bk9wdGlvbltdPihbXSk7XHJcblxyXG4gIHJlYWRvbmx5IGNvbXBhcmVGbiA9IChvMTogYW55LCBvMjogYW55KTogYm9vbGVhbiA9PiB2YWx1ZXNNYXRjaChvMSwgbzIsIHRoaXMubGFuZ3VhZ2UpO1xyXG5cclxuICBpc09iamVjdFZhbChvcHRpb246IGFueSk6IGJvb2xlYW4ge1xyXG4gICAgY29uc3QgdmFsID0gZ2V0T3B0aW9uU3RvcmVkVmFsdWUob3B0aW9uKTtcclxuICAgIHJldHVybiB0eXBlb2YgdmFsID09PSAnb2JqZWN0JyAmJiB2YWwgIT09IG51bGw7XHJcbiAgfVxyXG5cclxuICBnZXQgbGFiZWwoKTogc3RyaW5nIHtcclxuICAgIHJldHVybiByZXNvbHZlTGFiZWwodGhpcy5maWVsZD8ubGFiZWwsIHRoaXMubGFuZ3VhZ2UpO1xyXG4gIH1cclxuXHJcbiAgZ2V0T3B0U3RvcmVkVmFsKG9wdGlvbjogYW55KTogYW55IHtcclxuICAgIHJldHVybiBnZXRPcHRpb25TdG9yZWRWYWx1ZShvcHRpb24pO1xyXG4gIH1cclxuXHJcbiAgZ2V0T3B0TGFiZWwob3B0aW9uOiBhbnkpOiBzdHJpbmcge1xyXG4gICAgcmV0dXJuIHJlc29sdmVPcHRpb25MYWJlbChvcHRpb24sIHRoaXMubGFuZ3VhZ2UpO1xyXG4gIH1cclxuXHJcbiAgLyoqIFJlYWQtb25seSBkaXNwbGF5IOKAlCBzeW5jaHJvbm91cywgcGVyIMKnNi4yLiBTZWUgYERyb3Bkb3duRmllbGRDb21wb25lbnQuZ2V0TGFiZWxgLiAqL1xyXG4gIGdldExhYmVscyh2YWx1ZXM6IGFueVtdKTogc3RyaW5nIHtcclxuICAgIGlmICghQXJyYXkuaXNBcnJheSh2YWx1ZXMpIHx8ICF2YWx1ZXMubGVuZ3RoKSByZXR1cm4gJ+KAlCc7XHJcbiAgICByZXR1cm4gdmFsdWVzXHJcbiAgICAgIC5tYXAodiA9PiB7XHJcbiAgICAgICAgY29uc3Qgb3B0ID0gdGhpcy5vcHRpb25zKCkuZmluZChvID0+IHZhbHVlc01hdGNoKGdldE9wdGlvblN0b3JlZFZhbHVlKG8pLCB2LCB0aGlzLmxhbmd1YWdlKSk7XHJcbiAgICAgICAgaWYgKG9wdCkgcmV0dXJuIHRoaXMuZ2V0T3B0TGFiZWwob3B0KTtcclxuICAgICAgICBjb25zdCBjYWNoZWQgPSB0aGlzLmxvb2t1cHMubGFiZWxGb3IodGhpcy5maWVsZD8ubGlzdE5hbWUsIHYsIHRoaXMubGFuZ3VhZ2UpO1xyXG4gICAgICAgIGlmIChjYWNoZWQpIHJldHVybiBjYWNoZWQ7XHJcbiAgICAgICAgcmV0dXJuIHR5cGVvZiB2ID09PSAnb2JqZWN0JyA/IHJlc29sdmVMYWJlbCh2LCB0aGlzLmxhbmd1YWdlKSA6IFN0cmluZyh2KTtcclxuICAgICAgfSlcclxuICAgICAgLmpvaW4oJywgJyk7XHJcbiAgfVxyXG59XHJcbiJdfQ==