@vgip/meta-ui 2.1.2 → 2.1.3

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 (111) hide show
  1. package/.eslintrc.json +57 -0
  2. package/karma.conf.js +35 -0
  3. package/ng-package.json +10 -0
  4. package/package.json +3 -15
  5. package/src/lib/common/fieldNormalizer/boolean.ts +11 -0
  6. package/src/lib/common/fieldNormalizer/datetime.ts +8 -0
  7. package/src/lib/common/fieldNormalizer/index.ts +171 -0
  8. package/src/lib/common/fieldNormalizer/number.ts +13 -0
  9. package/src/lib/common/fieldNormalizer/options.ts +48 -0
  10. package/src/lib/common/fieldNormalizer/radio.ts +29 -0
  11. package/src/lib/common/fieldNormalizer/reference.ts +32 -0
  12. package/src/lib/common/fieldNormalizer/richtext.ts +15 -0
  13. package/src/lib/common/fieldNormalizer/string.ts +23 -0
  14. package/src/lib/common/fieldNormalizer/text.ts +17 -0
  15. package/src/lib/common/fieldNormalizer/uniqueNameFilter.ts +21 -0
  16. package/src/lib/common/metaAutofocus.directive.ts +31 -0
  17. package/src/lib/common/metaContext.resolver.ts +25 -0
  18. package/src/lib/common/metaIcons.pipe.spec.ts +15 -0
  19. package/src/lib/common/metaIcons.pipe.ts +29 -0
  20. package/src/lib/common/metaModel.pipe.ts +19 -0
  21. package/src/lib/common/metaNormalizer.ts +366 -0
  22. package/src/lib/common/metaStripHtml.pipe.ts +18 -0
  23. package/src/lib/common/utils/colorThemes.ts +86 -0
  24. package/src/lib/common/utils/indexedDbStore/index.ts +244 -0
  25. package/src/lib/common/utils/indexedDbStore/indexedDbStore.spec.ts +149 -0
  26. package/src/lib/common/utils/relativeTimeBuilder.ts +49 -0
  27. package/src/lib/common/utils/resourceCardLabel.ts +25 -0
  28. package/src/lib/common/utils/smartProp.spec.ts +24 -0
  29. package/src/lib/common/utils/smartProp.ts +28 -0
  30. package/src/lib/common/utils/templateBuilder.ts +99 -0
  31. package/src/lib/field.scss +207 -0
  32. package/src/lib/fieldAbstract.ts +327 -0
  33. package/src/lib/fieldBoolean/index.ts +55 -0
  34. package/src/lib/fieldBoolean/style.scss +22 -0
  35. package/src/lib/fieldBoolean/test.spec.ts +43 -0
  36. package/src/lib/fieldBoolean/view.html +30 -0
  37. package/src/lib/fieldComposite/index.ts +86 -0
  38. package/src/lib/fieldComposite/style.scss +6 -0
  39. package/src/lib/fieldComposite/test.spec.ts +43 -0
  40. package/src/lib/fieldComposite/view.html +9 -0
  41. package/src/lib/fieldDatetime/index.ts +359 -0
  42. package/src/lib/fieldDatetime/style.scss +81 -0
  43. package/src/lib/fieldDatetime/test.spec.ts +43 -0
  44. package/src/lib/fieldDatetime/view.html +26 -0
  45. package/src/lib/fieldHidden/index.ts +15 -0
  46. package/src/lib/fieldHidden/view.html +0 -0
  47. package/src/lib/fieldInput/index.ts +477 -0
  48. package/src/lib/fieldInput/style.scss +128 -0
  49. package/src/lib/fieldInput/test.spec.ts +43 -0
  50. package/src/lib/fieldInput/view.html +81 -0
  51. package/src/lib/fieldList/index.ts +73 -0
  52. package/src/lib/fieldList/style.scss +26 -0
  53. package/src/lib/fieldList/test.spec.ts +43 -0
  54. package/src/lib/fieldList/view.html +25 -0
  55. package/src/lib/fieldRadio/index.ts +93 -0
  56. package/src/lib/fieldRadio/style.scss +32 -0
  57. package/src/lib/fieldRadio/test.spec.ts +43 -0
  58. package/src/lib/fieldRadio/view.html +24 -0
  59. package/src/lib/fieldReference/index.ts +871 -0
  60. package/src/lib/fieldReference/style.scss +273 -0
  61. package/src/lib/fieldReference/test.spec.ts +44 -0
  62. package/src/lib/fieldReference/view.html +163 -0
  63. package/src/lib/fieldRichtext/index.ts +98 -0
  64. package/src/lib/fieldRichtext/quill.scss +6 -0
  65. package/src/lib/fieldRichtext/style.scss +87 -0
  66. package/src/lib/fieldRichtext/test.spec.ts +43 -0
  67. package/src/lib/fieldRichtext/view.html +17 -0
  68. package/src/lib/fieldSelect/index.ts +597 -0
  69. package/src/lib/fieldSelect/style.scss +165 -0
  70. package/src/lib/fieldSelect/test.spec.ts +44 -0
  71. package/src/lib/fieldSelect/view.html +128 -0
  72. package/src/lib/fieldText/index.ts +86 -0
  73. package/src/lib/fieldText/style.scss +24 -0
  74. package/src/lib/fieldText/test.spec.ts +43 -0
  75. package/src/lib/fieldText/view.html +23 -0
  76. package/src/lib/fieldUnknown/index.ts +15 -0
  77. package/src/lib/fieldUnknown/test.spec.ts +34 -0
  78. package/src/lib/fieldUnknown/view.html +9 -0
  79. package/src/lib/index.ts +127 -0
  80. package/src/lib/layout/index.ts +255 -0
  81. package/src/lib/layout/style.scss +67 -0
  82. package/src/lib/layout/view.html +45 -0
  83. package/src/lib/metaField/index.ts +133 -0
  84. package/src/lib/metaField/test.spec.ts +32 -0
  85. package/src/lib/refDialog/index.ts +157 -0
  86. package/src/lib/refDialog/style.scss +154 -0
  87. package/src/lib/refDialog/view.html +24 -0
  88. package/src/lib/resource/index.ts +559 -0
  89. package/src/lib/resource/style.scss +132 -0
  90. package/src/lib/resource/view.html +70 -0
  91. package/src/lib/resourceCard/index.ts +44 -0
  92. package/src/lib/resourceCard/style.scss +7 -0
  93. package/src/lib/resourceCard/view.html +14 -0
  94. package/src/lib/services/metaContext/index.ts +61 -0
  95. package/src/lib/services/metaMsg/index.ts +84 -0
  96. package/src/lib/services/metaReference/index.ts +98 -0
  97. package/src/lib/services/metaResource/index.ts +163 -0
  98. package/src/lib/services/metaResource/metaHttpClient.ts +76 -0
  99. package/src/lib/services/metaResource/metaResource.spec.ts +24 -0
  100. package/src/lib/services/metaTracker/index.ts +38 -0
  101. package/src/lib/services/resourceDrafts/index.ts +81 -0
  102. package/src/lib/services/resourceDrafts/resourceDrafts.spec.ts +24 -0
  103. package/src/lib/styles.scss +13 -0
  104. package/src/public-api.ts +5 -0
  105. package/src/test.ts +17 -0
  106. package/tsconfig.lib.json +25 -0
  107. package/tsconfig.lib.prod.json +9 -0
  108. package/tsconfig.spec.json +17 -0
  109. package/fesm2022/vgip-meta-ui.mjs +0 -6079
  110. package/fesm2022/vgip-meta-ui.mjs.map +0 -1
  111. package/index.d.ts +0 -709
@@ -0,0 +1,43 @@
1
+ /*
2
+ * @Author: Alexander.Vangelov@vonage.com
3
+ * @Date: 2019-09-19 17:35:19
4
+ * @Last Modified by: Alexander.Vangelov@vonage.com
5
+ * @Last Modified time: 2020-02-27 12:17:16
6
+ */
7
+
8
+ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
9
+ import { NgForm } from '@angular/forms';
10
+
11
+ import { MetaModule } from '..';
12
+
13
+ import { FieldRichtext } from '.';
14
+
15
+ describe('FieldRichtext', () => {
16
+ let component: FieldRichtext;
17
+ let fixture: ComponentFixture<FieldRichtext>;
18
+
19
+ beforeEach(waitForAsync(() => {
20
+ TestBed.configureTestingModule({
21
+ declarations: [],
22
+ imports: [
23
+ MetaModule
24
+ ],
25
+ providers: [
26
+ { provide: NgForm, useValue: new NgForm([], []) }
27
+ ]
28
+ })
29
+ .compileComponents();
30
+ }));
31
+
32
+ beforeEach(() => {
33
+ fixture = TestBed.createComponent(FieldRichtext);
34
+ component = fixture.componentInstance;
35
+ component.parent = {};
36
+ component.meta = { name: 'f1' };
37
+ fixture.detectChanges();
38
+ });
39
+
40
+ it('should create', () => {
41
+ expect(component).toBeTruthy();
42
+ });
43
+ });
@@ -0,0 +1,17 @@
1
+ <div class='vgip-meta-field-preview' *ngIf='preview && parent[meta.name]'>
2
+ <div class='vgip-meta-field-label' [title]='meta.label || meta.name'>{{meta.label || meta.name}}</div>
3
+ <div class='vgip-meta-field-value __gu'>{{parent[meta.name]}}</div>
4
+ </div>
5
+ <div *ngIf='!preview' class="Vlt-form__element" [ngClass]="{ 'Vlt-form__element--error': f.invalid && ((f | metaModel)._parent.submitted || (fq | metaModel).touched) }">
6
+ <label class="Vlt-label">{{meta.label || meta.name}}<span *ngIf='validations.required' class='Vlt-red'>*</span></label> <!-- eslint-disable-line -->
7
+ <div class="Vlt-textarea" [ngClass]='{ active: active }'>
8
+ <input type='hidden' class='main model' [required]='validations.required' [minlength]='validations.minlength' [maxlength]='validations.maxlength' [(ngModel)]='parent[meta.name]' #f='ngModel' [name]='name'/>
9
+ <quill-editor (onFocus)='active = true' (onBlur)='active = false' class='main model' [(ngModel)]='parent[meta.name]' #fq='ngModel' (ngModelChange)="onModelChangeLocal($event)" [ngModelOptions]='{ standalone: true }' placeholder=' ' [modules]="quillConfigModules"></quill-editor>
10
+ </div>
11
+ <small *ngIf='f.invalid && ((f | metaModel)._parent.submitted || (fq | metaModel).touched)' class="Vlt-form__element__error">
12
+ <span *ngIf="f.errors.required">Required</span>
13
+ <span *ngIf="f.errors.maxlength">Length can not exceed {{validations.maxlength}} characters</span>
14
+ <span *ngIf="f.errors.custom">{{f.errors.custom}}&nbsp;</span>
15
+ </small>
16
+ <small *ngIf='meta.helpText' class="Vlt-form__element__hint">{{meta.helpText}}</small>
17
+ </div>
@@ -0,0 +1,597 @@
1
+ import { Component, OnInit, ViewContainerRef } from '@angular/core';
2
+ import { FieldAbstract } from '../fieldAbstract';
3
+ import { ControlContainer, NgForm } from '@angular/forms';
4
+ import { MetaReferenceService } from '../services/metaReference';
5
+ import { MetaResourceService, IMetaResourceConfig } from '../services/metaResource';
6
+ import { MetaRefDialog } from '../refDialog';
7
+
8
+ @Component({
9
+ templateUrl: './view.html',
10
+ styleUrls: ['./style.scss'],
11
+ viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
12
+ standalone: false
13
+ })
14
+ export class FieldSelect extends FieldAbstract implements OnInit {
15
+ multiple: boolean;
16
+ input: HTMLInputElement;
17
+ dropdown: HTMLElement;
18
+ dropdownScrollContainer: HTMLElement;
19
+ showText: string;
20
+ activeSuggestionIndex: number;
21
+ value: any;
22
+ isCreatable = false;
23
+ isUpdatable = false;
24
+ asyncOptions: Array<any>;
25
+ searching: boolean;
26
+ filteredOptions: Array<any>;
27
+ searchInputText: '';
28
+ searchInput: HTMLInputElement;
29
+ private desiredValue: any;
30
+
31
+ constructor(
32
+ private referenceService: MetaReferenceService,
33
+ private metaResource: MetaResourceService,
34
+ private viewContainerRef: ViewContainerRef
35
+ ) {
36
+ super();
37
+ }
38
+
39
+ get options() {
40
+ return this.filteredOptions || this.asyncOptions || this.meta.options || this.meta.picklist || [];
41
+ }
42
+
43
+ get groupedOptions() {
44
+ return this.options.reduce((r, { group }) => {
45
+ if (!r.some(o => o.group === group)) {
46
+ r.push({ group, groupItem: this.options.filter(v => v.group === group) });
47
+ }
48
+ return r;
49
+ }, []);
50
+ }
51
+
52
+ get resourceService() {
53
+ if (!this.metaResourceService) {
54
+ const metaResourceConfig: IMetaResourceConfig = {
55
+ integrationCode: this.integrationCode,
56
+ resourceType: this.resourceType,
57
+ delegate: this.delegate
58
+ };
59
+ this.metaResourceService = this.metaResource.new(metaResourceConfig);
60
+ }
61
+ return this.metaResourceService;
62
+ }
63
+
64
+ get optionsBackup() {
65
+ return this.asyncOptions || this.meta.options || this.meta.picklist || [];
66
+ }
67
+
68
+ // static setup(instance, parent, meta) { } // eslint-disable-line @typescript-eslint/no-unused-vars
69
+
70
+ ngOnInit() {
71
+ // console.log('parent', this.parent);
72
+ this.validationAttributes = ['required'];
73
+ this.multiple = (this.meta.type === 'multipicklist') || this.meta.multiple;
74
+ const origValue = this.parent[this.meta.name];
75
+ if (this.meta.reference) {
76
+ this.isCreatable = true;
77
+ this.isUpdatable = this.meta.reference.updatable
78
+ }
79
+ Object.defineProperty(this.parent, this.meta.name, {
80
+ set: (value) => {
81
+ this.desiredValue = value;
82
+ if (this.multiple) {
83
+ if (value && value instanceof Array === false) {
84
+ for (const o of this.optionsBackup) {
85
+ if ((value.id || value) === (o.id || o.value || o.name || 0)) {
86
+ this.model = [o];
87
+ this.value = [this.modelToValue(o, this.meta.valueType)];
88
+ break;
89
+ }
90
+ }
91
+ }
92
+ if (!value || value.length) {
93
+ const values = (value instanceof Array) ? value : (value || '').split(';'); // should not handle incorrect values
94
+ this.value = [];
95
+ this.model = [];
96
+ for (const v of values) {
97
+ if (v) {
98
+ for (const o of this.optionsBackup) {
99
+ if ((v.id || v) === (o.id || o.value || o.name || 0)) {
100
+ this.model.push(o);
101
+ this.value.push(this.modelToValue(o, this.meta.valueType));
102
+ break;
103
+ }
104
+ }
105
+ }
106
+ }
107
+ if (!this.model.length && !this.isPersistedParent) {
108
+ delete this.model;
109
+ delete this.value;
110
+ delete this.showText;
111
+ } else {
112
+ this.showText = (this.model || []).map((o) => o.label || o).join(', ');
113
+ }
114
+ }
115
+ } else {
116
+ if (value) {
117
+ for (const o of this.options) {
118
+ if ((value.id || value) === (o.id || o.value || o.name || o)) {
119
+ this.model = o;
120
+ this.value = this.modelToValue(o, this.meta.valueType);
121
+ this.showText = o.label || o;
122
+ break;
123
+ }
124
+ }
125
+ } else {
126
+ delete this.model;
127
+ delete this.value;
128
+ delete this.showText;
129
+ }
130
+ }
131
+ this.onSubjectChange(this.model);
132
+ this.meta.$optional = this.isOptional;
133
+ },
134
+ get: () => this.value,
135
+ enumerable: this.sendToServer,
136
+ configurable: true
137
+ });
138
+
139
+ if (origValue) {
140
+ this.parent[this.meta.name] = origValue;
141
+ }
142
+
143
+ if (this.default && !this.model) {
144
+ setTimeout(() => {
145
+ if (!this.model) {
146
+ this.parent[this.meta.name] = this.default.id || this.default;
147
+ }
148
+ }, 0);
149
+ }
150
+
151
+ if (typeof (this.meta.enabled) !== 'undefined') {
152
+ if (typeof (this.meta.enabled) === 'object') {
153
+ for (const par of Object.keys(this.meta.enabled)) {
154
+ console.log('this.meta.enabled', this.meta.enabled)
155
+ const parValue = this.parent[par];
156
+ if (parValue) {
157
+ // logic for specific values, this works only for true
158
+ } else {
159
+ this.logicalDisabled = true;
160
+ this.clear();
161
+ }
162
+ this.parentChangeSubject.subscribe((value) => {
163
+ if (value && value.hasOwnProperty(par)) {
164
+ setTimeout(() => {
165
+ this.logicalDisabled = !value[par];
166
+ if (this.logicalDisabled) {
167
+ this.clear();
168
+ } else {
169
+ this.filterOptions();
170
+ }
171
+ }, 0 );
172
+ }
173
+ });
174
+ }
175
+ this.filterOptions();
176
+ }
177
+ }
178
+
179
+ if (this.meta.search) {
180
+ const searchParams = this.meta.search.params;
181
+ if (searchParams) {
182
+ for (const par of Object.keys(searchParams)) {
183
+ const parValue = this.parent[searchParams[par]];
184
+ if (parValue) {
185
+ const params = {};
186
+ params[par] = parValue;
187
+ this.searchOptions(params);
188
+ }
189
+ this.parentChangeSubject.subscribe((value) => {
190
+ if (value?.hasOwnProperty(searchParams[par])) {
191
+ if (!this.logicalDisabled) {
192
+ this.searchOptions(value);
193
+ }
194
+ }
195
+ });
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ onActivated(ev) {
202
+ // ev.preventDefault();
203
+ // ev.stopPropagation();
204
+ this.input = ev.srcElement;
205
+ if (!this.dropdown) {
206
+ this.dropdown = ev.srcElement.parentNode.parentNode.parentNode;
207
+ this.searchInput = this.dropdown.querySelector('.vgip-search_input');
208
+ }
209
+ this.showDropdown();
210
+ }
211
+
212
+ detectSearchBlock(element) {
213
+ return this.elementRef.nativeElement.querySelector('.Vlt-search_wrap').contains(element);
214
+ }
215
+
216
+ onBlur(ev) {
217
+ let internalControl = false;
218
+ if (ev.relatedTarget) {
219
+
220
+ if (!this.detectSearchBlock(ev.relatedTarget) && !this.elementRef.nativeElement.contains(ev.relatedTarget)) {
221
+ this.dismissDropdown();
222
+ } else {
223
+ internalControl = true;
224
+ }
225
+ }
226
+ if (this.keyListenerActive && !internalControl) {
227
+ this.input.removeEventListener('keydown', this.keydown);
228
+ if (this.searchInput) {
229
+ this.searchInput.removeEventListener('keydown', this.keydown);
230
+ }
231
+ setTimeout(() => {
232
+ if (!this.keyListenerActive) {
233
+ document.removeEventListener('click', this.clickout);
234
+ }
235
+ }, 400);
236
+ delete this.keyListenerActive;
237
+ }
238
+ this.onLeave.emit(ev);
239
+ }
240
+
241
+ onOptionSelect(ev, option) {
242
+ if (ev) {
243
+ ev.preventDefault();
244
+ ev.stopPropagation();
245
+ }
246
+ this.focus();
247
+ if (this.multiple) {
248
+ if (!this.model) {
249
+ this.model = [];
250
+ }
251
+ const optionIndex = this.model.indexOf(option);
252
+ if (optionIndex === -1) {
253
+ this.model.push(option);
254
+ } else {
255
+ this.model.splice(optionIndex, 1);
256
+ if (!this.model.length) {
257
+ delete this.model;
258
+ }
259
+ }
260
+ this.onModelChange(this.model);
261
+ if (this.model) {
262
+ this.showText = (this.model || []).map((o) => o.label || o).join(', ');
263
+ } else {
264
+ delete this.showText;
265
+ }
266
+ } else {
267
+ this.dismissDropdown();
268
+ if (typeof option !== 'undefined') {
269
+ this.model = option;
270
+ this.onModelChange(option);
271
+ this.showText = option.label || (option.id === '' ? '--empty--' : option);
272
+ if (typeof (option) === 'object' && !option.id && option.id !== '') {
273
+ delete this.showText;
274
+ }
275
+ }
276
+ }
277
+ }
278
+
279
+ clear(ev?) {
280
+ delete this.model;
281
+ this.onModelChange(this.model);
282
+ delete this.showText;
283
+ if (ev) {
284
+ ev.preventDefault();
285
+ ev.stopPropagation();
286
+ return false;
287
+ }
288
+ }
289
+
290
+ openResource(ev) {
291
+ this.dismissDropdown();
292
+ if (this.model && typeof(this.meta.reference.updatable) === 'function') {
293
+ return this.meta.reference.updatable(this);
294
+ } else if (typeof(this.meta.reference.creatable) === 'function') {
295
+ return this.meta.reference.creatable(this);
296
+ }
297
+ const refDialogComponent = this.viewContainerRef.createComponent(MetaRefDialog);
298
+ let theme = this.elementRef.nativeElement.dataset.theme;
299
+ if (!theme || theme === 'inherit') {
300
+ let parentComponent = this.elementRef.nativeElement.closest('vgip-meta-layout');
301
+ if (parentComponent) {
302
+ theme = parentComponent.dataset.theme;
303
+ }
304
+ if (!theme || theme === 'inherit') {
305
+ parentComponent = this.elementRef.nativeElement.closest('vgip-meta-resource');
306
+ if (parentComponent) {
307
+ theme = parentComponent.dataset.theme;
308
+ }
309
+ }
310
+ }
311
+ this.referenceService.openDialog(
312
+ refDialogComponent, this.metaResource, this.integrationCode, this.meta.reference, null, false, null, theme, this.overlayContainer
313
+ ).subscribe((result: any) => {
314
+ if (result) {
315
+ const existingOptions = (this.meta.options || []).find((o) => (o.id === result.id && o.type === result.type));
316
+ if (existingOptions) {
317
+ existingOptions.label = result.label;
318
+ } else {
319
+ if (!this.meta.options) {
320
+ this.meta.options = [];
321
+ }
322
+ this.meta.options.unshift(result);
323
+ }
324
+ this.onOptionSelect(ev, result);
325
+ }
326
+ });
327
+ }
328
+
329
+ showDropdown(updateActiveSuggestionIndex = true) {
330
+ this.dropdown.classList.add('Vlt-dropdown--expanded');
331
+ if (!this.keyListenerActive) {
332
+ if (this.searchInput) {
333
+ this.searchInput.focus(); // Focus directly on search input first visit
334
+ this.searchInput.addEventListener('keydown', this.keydown);
335
+ }
336
+ this.input.addEventListener('keydown', this.keydown);
337
+ setTimeout(() => {
338
+ document.addEventListener('click', this.clickout);
339
+ }, 200);
340
+ this.keyListenerActive = true;
341
+ }
342
+ if (updateActiveSuggestionIndex && this.options && this.model) {
343
+ for (let suggestionIndex = 0; suggestionIndex < this.options.length; suggestionIndex++) {
344
+ if (this.model === this.options[suggestionIndex]) {
345
+ this.activeSuggestionIndex = suggestionIndex;
346
+ break;
347
+ }
348
+ }
349
+ }
350
+ this.ensureDropdownIsVisible();
351
+ }
352
+
353
+ dismissDropdown(event?) {
354
+ if (event) {
355
+ this.focus();
356
+ }
357
+ if (this.dropdown) {
358
+ this.dropdown.classList.remove('Vlt-dropdown--expanded');
359
+ }
360
+ if (this.searchInput) {
361
+ this.clearSearchField();
362
+ }
363
+ delete this.activeSuggestionIndex;
364
+ }
365
+
366
+ onSearchChange() {
367
+ const search = new RegExp(this.searchInputText, 'gi');
368
+ if (this.searchInputText === '') {
369
+ this.filteredOptions = [...this.optionsBackup];
370
+ } else {
371
+ this.filteredOptions = this.optionsBackup.filter(option => (option.label || option.id || option.name || option).match(search));
372
+ }
373
+ }
374
+
375
+ clearSearchField(ev?) {
376
+ this.searchInputText = '';
377
+ this.onSearchChange();
378
+ if (ev) {
379
+ ev.preventDefault();
380
+ ev.stopPropagation();
381
+ return false;
382
+ }
383
+ }
384
+
385
+ private ensureDropdownIsVisible() {
386
+ setTimeout(() => {
387
+ const holder = this.dropdown.closest('.Vlt-card__content, .Vlt-modal__content');
388
+ if (holder) {
389
+ const el = this.elementRef.nativeElement.querySelector('.Vlt-dropdown__panel');
390
+ const elRect = el.getBoundingClientRect();
391
+ const holderRect = holder.getBoundingClientRect();
392
+ if (holderRect.top + holderRect.height < elRect.top + elRect.height) {
393
+ el.scrollIntoView({ block: 'end' });
394
+ }
395
+ }
396
+ }, 400);
397
+ }
398
+
399
+ private ensureDropdownOptionIsVisible() {
400
+ if (!this.dropdownScrollContainer) {
401
+ this.dropdownScrollContainer = this.dropdown.querySelector('.Vlt-dropdown__scroll');
402
+ }
403
+ if (this.dropdownScrollContainer && typeof (this.activeSuggestionIndex) === 'number') {
404
+ const scrollTop = 44 * this.activeSuggestionIndex;
405
+ const scrollBottom = 44 * (this.activeSuggestionIndex - 7);
406
+ if (scrollTop < this.dropdownScrollContainer.scrollTop) {
407
+ this.dropdownScrollContainer.scrollTop = scrollTop;
408
+ } else if (scrollBottom > this.dropdownScrollContainer.scrollTop) {
409
+ this.dropdownScrollContainer.scrollTop = scrollBottom;
410
+ }
411
+ }
412
+ }
413
+
414
+ private filterOptions() {
415
+ console.log('filterOptions')
416
+ this.asyncOptions = [];
417
+ if (!this.logicalDisabled) {
418
+ for (const o of this.meta.options) {
419
+ if (o.for) {
420
+ for (const par of Object.keys(o.for)) {
421
+ const parValue = this.parent[par];
422
+ if (o.for[par].indexOf(parValue) !== -1) {
423
+ this.asyncOptions.push(o);
424
+ }
425
+ }
426
+ } else {
427
+ this.asyncOptions.push(o);
428
+ }
429
+ }
430
+ }
431
+ if (this.model) {
432
+ // replace current selections with valid ones from the new options list (support duplicate id)
433
+ if (this.multiple) {
434
+ const validSelections = [];
435
+ for (let i = this.model.length - 1; i >= 0; i--) {
436
+ const applicable = this.asyncOptions.find((o) => o.id === this.model[i].id);
437
+ if (applicable) {
438
+ validSelections.push(applicable);
439
+ }
440
+ }
441
+ this.model = validSelections;
442
+ if (this.model) {
443
+ this.showText = (this.model || []).map((o) => o.label || o).join(', ');
444
+ } else {
445
+ delete this.showText;
446
+ }
447
+ } else {
448
+ this.model = this.asyncOptions.find((m) => m.id === this.model.id);
449
+ }
450
+ this.onModelChange(this.model);
451
+ }
452
+ }
453
+
454
+ private clickout = (event) => {
455
+ if (
456
+ event.target.className === 'Vlt-dropdown__link' && this.elementRef.nativeElement.contains(event.target)
457
+ ) {
458
+ this.focus();
459
+ } else if (
460
+ event.target !== this.input &&
461
+ !event.target.classList.contains('Vlt-dropdown__block') &&
462
+ !this.detectSearchBlock(event.target)
463
+ ) {
464
+ this.dismissDropdown();
465
+ } else if (this.detectSearchBlock(event.target)) {
466
+ event.target.focus();
467
+ } else {
468
+ this.focus();
469
+ }
470
+ };
471
+
472
+ private keydown = (event) => {
473
+ switch (event.key) {
474
+ case 'ArrowDown': {
475
+ this.showDropdown(false);
476
+ if (typeof (this.activeSuggestionIndex) === 'undefined') {
477
+ this.activeSuggestionIndex = 0;
478
+ } else {
479
+ this.activeSuggestionIndex++;
480
+ if (this.activeSuggestionIndex >= this.options.length) {
481
+ this.activeSuggestionIndex = 0;
482
+ }
483
+ }
484
+ this.ensureDropdownOptionIsVisible();
485
+ event.preventDefault();
486
+ event.stopPropagation();
487
+ break;
488
+ }
489
+ case 'ArrowUp': {
490
+ this.showDropdown(false);
491
+ if (typeof (this.activeSuggestionIndex) === 'undefined') {
492
+ this.activeSuggestionIndex = this.options.length - 1;
493
+ } else {
494
+ this.activeSuggestionIndex--;
495
+ if (this.activeSuggestionIndex < 0) {
496
+ this.activeSuggestionIndex = this.options.length - 1;
497
+ }
498
+ }
499
+ this.ensureDropdownOptionIsVisible();
500
+ event.preventDefault();
501
+ event.stopPropagation();
502
+ break;
503
+ }
504
+ case 'Enter':
505
+ case 'Space':
506
+ case ' ': {
507
+ if (typeof (this.activeSuggestionIndex) !== 'undefined') {
508
+ event.preventDefault();
509
+ event.stopPropagation();
510
+ this.onOptionSelect(event, this.options[this.activeSuggestionIndex]);
511
+ if (!this.multiple) {
512
+ delete this.activeSuggestionIndex;
513
+ }
514
+ }
515
+ break;
516
+ }
517
+ case 'Escape': {
518
+ event.preventDefault();
519
+ event.stopPropagation();
520
+ this.dismissDropdown();
521
+ break;
522
+ }
523
+ case 'Backspace': {
524
+ if (!this.dropdown.classList.contains('Vlt-dropdown--expanded')) {
525
+ event.preventDefault();
526
+ event.stopPropagation();
527
+ this.clear();
528
+ }
529
+ break;
530
+ }
531
+ }
532
+ };
533
+
534
+ private searchOptions(params?) { // eslint-disable-line @typescript-eslint/no-unused-vars
535
+ let searchUrl = `/fields/${this.meta.name}/search`;
536
+ if (this.meta.search) {
537
+ const searchParams = this.meta.search.params;
538
+ if (this.meta.search.url) {
539
+ searchUrl = this.meta.search.url.replace(/\${\s*([\w.]+)\s*}/g, (match, key) => {
540
+ if (searchParams.hasOwnProperty(key)) {
541
+ const props = searchParams[key].split('.');
542
+ const field = props[0];
543
+ const prop = props[1];
544
+ delete searchParams[key]; // if it is URL param, remove it as query param
545
+ let parValue = this.parent[field];
546
+ if (parValue) {
547
+ if (prop) {
548
+ parValue = parValue[prop];
549
+ }
550
+ if (parValue) {
551
+ return parValue;
552
+ }
553
+ }
554
+ }
555
+ return 'undefined';
556
+ });
557
+ }
558
+ if (searchParams) {
559
+ for (const par of Object.keys(searchParams)) {
560
+ const val = this.parent[searchParams[par]];
561
+ if (val) {
562
+ searchUrl += `${searchUrl.indexOf('?') === -1 ? '?' : '&'}${par}=${val.id || val}`;
563
+ }
564
+ }
565
+ }
566
+ }
567
+ this.searching = true;
568
+ this.resourceService.getByPath(searchUrl).subscribe((options) => {
569
+ this.asyncOptions = options;
570
+ let validValue = false;
571
+ const value = this.value || this.desiredValue;
572
+ if (this.multiple) {
573
+ if (value) {
574
+ const values = value instanceof Array ? value : (value || "").split(";");
575
+ validValue = values.some(
576
+ (v) =>
577
+ options.find(
578
+ (o) => (v.id || v) === (o.id || o.value || o.name || 0)
579
+ ) !== undefined
580
+ );
581
+ }
582
+ } else {
583
+ if (value) {
584
+ validValue = options.find((o) => (o.id || o) === (value.id || value)) !== undefined;
585
+ }
586
+ }
587
+
588
+ if (!validValue) {
589
+ this.clear();
590
+ } else {
591
+ this.parent[this.meta.name] = value;
592
+ }
593
+ delete this.searching;
594
+ });
595
+ }
596
+
597
+ }