nexheal-lib 0.0.2 → 0.0.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 (62) hide show
  1. package/.editorconfig +17 -0
  2. package/.vscode/extensions.json +4 -0
  3. package/.vscode/launch.json +20 -0
  4. package/.vscode/tasks.json +42 -0
  5. package/README.md +15 -19
  6. package/angular.json +36 -0
  7. package/package.json +47 -23
  8. package/projects/nexheal-lib/README.md +63 -0
  9. package/projects/nexheal-lib/ng-package.json +9 -0
  10. package/projects/nexheal-lib/package.json +12 -0
  11. package/projects/nexheal-lib/src/directives/clickoutside.directive.ts +34 -0
  12. package/projects/nexheal-lib/src/lib/controls/autocomplete-control/autocomplete-control.component.html +52 -0
  13. package/projects/nexheal-lib/src/lib/controls/autocomplete-control/autocomplete-control.component.scss +22 -0
  14. package/projects/nexheal-lib/src/lib/controls/autocomplete-control/autocomplete-control.component.spec.ts +22 -0
  15. package/projects/nexheal-lib/src/lib/controls/autocomplete-control/autocomplete-control.component.ts +367 -0
  16. package/projects/nexheal-lib/src/lib/controls/calendar-control/calendar-control.component.html +152 -0
  17. package/projects/nexheal-lib/src/lib/controls/calendar-control/calendar-control.component.scss +194 -0
  18. package/projects/nexheal-lib/src/lib/controls/calendar-control/calendar-control.component.spec.ts +22 -0
  19. package/projects/nexheal-lib/src/lib/controls/calendar-control/calendar-control.component.ts +759 -0
  20. package/projects/nexheal-lib/src/lib/controls/checkbox-control/checkbox-control.component.html +4 -0
  21. package/projects/nexheal-lib/src/lib/controls/checkbox-control/checkbox-control.component.spec.ts +22 -0
  22. package/projects/nexheal-lib/src/lib/controls/checkbox-control/checkbox-control.component.ts +94 -0
  23. package/projects/nexheal-lib/src/lib/controls/input-control/input-control.component.html +61 -0
  24. package/projects/nexheal-lib/src/lib/controls/input-control/input-control.component.scss +132 -0
  25. package/projects/nexheal-lib/src/lib/controls/input-control/input-control.component.spec.ts +22 -0
  26. package/projects/nexheal-lib/src/lib/controls/input-control/input-control.component.ts +202 -0
  27. package/projects/nexheal-lib/src/lib/controls/multiselect-control/multiselect-control.component.html +72 -0
  28. package/projects/nexheal-lib/src/lib/controls/multiselect-control/multiselect-control.component.scss +90 -0
  29. package/projects/nexheal-lib/src/lib/controls/multiselect-control/multiselect-control.component.spec.ts +22 -0
  30. package/projects/nexheal-lib/src/lib/controls/multiselect-control/multiselect-control.component.ts +482 -0
  31. package/projects/nexheal-lib/src/lib/controls/select-control/select-control.component.html +53 -0
  32. package/projects/nexheal-lib/src/lib/controls/select-control/select-control.component.scss +19 -0
  33. package/projects/nexheal-lib/src/lib/controls/select-control/select-control.component.spec.ts +22 -0
  34. package/projects/nexheal-lib/src/lib/controls/select-control/select-control.component.ts +375 -0
  35. package/projects/nexheal-lib/src/lib/controls/switch-control/switch-control.component.html +4 -0
  36. package/projects/nexheal-lib/src/lib/controls/switch-control/switch-control.component.scss +53 -0
  37. package/projects/nexheal-lib/src/lib/controls/switch-control/switch-control.component.spec.ts +22 -0
  38. package/projects/nexheal-lib/src/lib/controls/switch-control/switch-control.component.ts +93 -0
  39. package/projects/nexheal-lib/src/lib/controls/text-editor/text-editor.component.html +88 -0
  40. package/projects/nexheal-lib/src/lib/controls/text-editor/text-editor.component.scss +122 -0
  41. package/projects/nexheal-lib/src/lib/controls/text-editor/text-editor.component.spec.ts +22 -0
  42. package/projects/nexheal-lib/src/lib/controls/text-editor/text-editor.component.ts +314 -0
  43. package/projects/nexheal-lib/src/lib/controls/textarea-control/textarea-control.component.html +19 -0
  44. package/projects/nexheal-lib/src/lib/controls/textarea-control/textarea-control.component.scss +15 -0
  45. package/projects/nexheal-lib/src/lib/controls/textarea-control/textarea-control.component.spec.ts +22 -0
  46. package/projects/nexheal-lib/src/lib/controls/textarea-control/textarea-control.component.ts +83 -0
  47. package/projects/nexheal-lib/src/public-api.ts +13 -0
  48. package/projects/nexheal-lib/src/styles/nexheal.scss +1 -0
  49. package/projects/nexheal-lib/tsconfig.lib.json +18 -0
  50. package/projects/nexheal-lib/tsconfig.lib.prod.json +11 -0
  51. package/projects/nexheal-lib/tsconfig.spec.json +14 -0
  52. package/tsconfig.json +39 -0
  53. package/fesm2022/nexheal-lib.mjs +0 -2837
  54. package/fesm2022/nexheal-lib.mjs.map +0 -1
  55. package/index.d.ts +0 -498
  56. package/src/styles/fonts/icomoon.eot +0 -0
  57. package/src/styles/fonts/icomoon.svg +0 -46
  58. package/src/styles/fonts/icomoon.ttf +0 -0
  59. package/src/styles/fonts/icomoon.woff +0 -0
  60. package/src/styles/icon.css +0 -133
  61. package/src/styles/nexheal.scss +0 -2
  62. /package/{src → projects/nexheal-lib/src}/styles/_formcontrols.scss +0 -0
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { MultiselectControl } from './multiselect-control.component';
3
+
4
+ describe('MultiselectControl', () => {
5
+ let component: MultiselectControl;
6
+ let fixture: ComponentFixture<MultiselectControl>;
7
+
8
+ beforeEach(async () => {
9
+ await TestBed.configureTestingModule({
10
+ imports: [MultiselectControl]
11
+ })
12
+ .compileComponents();
13
+
14
+ fixture = TestBed.createComponent(MultiselectControl);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it('should create', () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });
@@ -0,0 +1,482 @@
1
+ import { CommonModule } from "@angular/common";
2
+ import {
3
+ Component, Input, Output, EventEmitter, forwardRef, OnInit, OnDestroy, ViewChild,
4
+ ElementRef, HostListener, SimpleChanges, ChangeDetectorRef
5
+ } from "@angular/core";
6
+ import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl, ReactiveFormsModule } from "@angular/forms";
7
+ import { debounceTime, distinctUntilChanged, Subject, Subscription } from "rxjs";
8
+ import { createPopper, Instance as PopperInstance } from "@popperjs/core";
9
+ import { ClickOutsideDirective } from "../../../directives/clickoutside.directive";
10
+
11
+ @Component({
12
+ selector: "multiselect-control",
13
+ standalone: true,
14
+ imports: [
15
+ CommonModule,
16
+ ReactiveFormsModule,
17
+ ClickOutsideDirective
18
+ ],
19
+ providers: [
20
+ {
21
+ provide: NG_VALUE_ACCESSOR,
22
+ useExisting: forwardRef(() => MultiselectControl),
23
+ multi: true,
24
+ },
25
+ ],
26
+ templateUrl: "./multiselect-control.component.html",
27
+ styleUrls: ["./multiselect-control.component.scss"],
28
+ })
29
+ export class MultiselectControl
30
+ implements ControlValueAccessor, OnInit, OnDestroy {
31
+
32
+ private subscription: Subscription = new Subscription();
33
+
34
+ @Input() title!: string;
35
+ @Input() selectedItems: any[] = [];
36
+ @Input() required: boolean = false;
37
+ @Input() placeholder!: string;
38
+ @Input() customClass!: string;
39
+ @Input() clearVal: boolean = true;
40
+ @Input() deFocus: boolean = true;
41
+ @Input() optionValueProperty: string = "id";
42
+ @Input() inputLoader: boolean = false;
43
+ @Input() error: boolean = false;
44
+ @Input() errorMessage: string = "";
45
+ @Input() autocomplete: string = "";
46
+ @Input() showSearch: boolean = false;
47
+ @Input() optionDisplayProperty: string = "displayname";
48
+ @Output() optionsSelected = new EventEmitter<any[]>();
49
+ @Output() selectionCleared = new EventEmitter<void>();
50
+ @Output() blurEvent = new EventEmitter<void>();
51
+
52
+ inputControl: FormControl = new FormControl({ value: "", disabled: false });
53
+ private _options: any[] = [];
54
+ @Input()
55
+ public set options(arr: any[]) {
56
+ this._options = Array.isArray(arr) ? arr : [];
57
+ this.filteredOptions = [...this._options];
58
+
59
+ // If we previously received an array of primitive IDs, attempt to map them now that options have arrived
60
+ if (this._isPrimitivePatch && this._pendingPrimitiveValues.length > 0) {
61
+ this._mapPrimitivesToObjects();
62
+ }
63
+
64
+ this._recomputeSelectAllState();
65
+ }
66
+ public get options(): any[] {
67
+ return this._options;
68
+ }
69
+ private _disabled: boolean = false;
70
+ @Input()
71
+ get disabled(): boolean {
72
+ return this._disabled;
73
+ }
74
+ set disabled(value: boolean) {
75
+ this._disabled = value;
76
+ if (this.inputControl) {
77
+ if (value) {
78
+ this.inputControl.disable({ emitEvent: false });
79
+ } else {
80
+ this.inputControl.enable({ emitEvent: false });
81
+ }
82
+ }
83
+ }
84
+ private _isPrimitivePatch: boolean = false;
85
+ private _pendingPrimitiveValues: any[] = [];
86
+ private onChange: (value: any) => void = () => { };
87
+ private onTouched: () => void = () => { };
88
+
89
+ @ViewChild("dropdown", { static: false }) dropdown!: ElementRef;
90
+ @ViewChild("input", { static: false }) input!: ElementRef;
91
+
92
+ filteredOptions: any[] = [];
93
+ isDropdownOpen: boolean = false;
94
+ selectAllChecked: boolean = false;
95
+ keySearch: string = "";
96
+ onSearch = new Subject<string>();
97
+ selectedItemList: string = "";
98
+ highlightedIndex: number | null = null;
99
+
100
+ private dropdownInitialized = false;
101
+ private popperInstance!: PopperInstance;
102
+
103
+ constructor(
104
+ private cdRef: ChangeDetectorRef
105
+ ) { }
106
+
107
+ ngOnInit() {
108
+ // Subscribe to search‐term changes, debounce, then filter
109
+ this.subscription = this.onSearch
110
+ .pipe(debounceTime(300), distinctUntilChanged())
111
+ .subscribe((searchText) => {
112
+ this._applyFilter(searchText);
113
+ this._recomputeSelectAllState();
114
+ });
115
+
116
+ // Initialize filteredOptions to match all options
117
+ this.filteredOptions = [...this.options];
118
+ }
119
+
120
+ ngAfterViewChecked() {
121
+ if (this.isDropdownOpen && !this.dropdownInitialized) {
122
+ this._createPopperInstance();
123
+ this.dropdownInitialized = true;
124
+ }
125
+ }
126
+
127
+ ngOnDestroy() {
128
+ this.subscription.unsubscribe();
129
+ if (this.popperInstance) {
130
+ this.popperInstance.destroy();
131
+ }
132
+ }
133
+
134
+ ngOnChanges(changes: SimpleChanges) {
135
+ if (changes["options"]) {
136
+ // When the parent replaces “options,” we reapply any filtering and “select all” logic
137
+ this.filteredOptions = [...this.options];
138
+ this._recomputeSelectAllState();
139
+ }
140
+ }
141
+
142
+ writeValue(value: any): void {
143
+ // If parent gave us something that is not an array, clear selection
144
+ if (!Array.isArray(value)) {
145
+ this._isPrimitivePatch = false;
146
+ this._pendingPrimitiveValues = [];
147
+ this.selectedItems = [];
148
+ this.selectedItemList = "";
149
+ this.selectAllChecked = false;
150
+ return;
151
+ }
152
+
153
+ // CASE A: array of primitive IDs, e.g. [12,17]
154
+ if (
155
+ value.length > 0 &&
156
+ (typeof value[0] === "string" || typeof value[0] === "number")
157
+ ) {
158
+ this._isPrimitivePatch = true;
159
+ this._pendingPrimitiveValues = [...value];
160
+ this._mapPrimitivesToObjects();
161
+ this._recomputeSelectAllState();
162
+ return;
163
+ }
164
+
165
+ // CASE B: array of full objects (with `id` or whatever `optionValueProperty` is)
166
+ if (
167
+ value.length > 0 &&
168
+ typeof value[0] === "object" &&
169
+ value[0][this.optionValueProperty] != null
170
+ ) {
171
+ // Convert to an array of IDs, then treat exactly like CASE A
172
+ const idArray = (value as any[]).map(
173
+ (obj) => obj[this.optionValueProperty]
174
+ );
175
+ this._isPrimitivePatch = true;
176
+ this._pendingPrimitiveValues = idArray;
177
+ this._mapPrimitivesToObjects();
178
+ this._recomputeSelectAllState();
179
+ return;
180
+ }
181
+
182
+ // CASE C: maybe parent passed the exact same object references that already exist in `options`
183
+ // In that case, we can just use them directly.
184
+ this._isPrimitivePatch = false;
185
+ this._pendingPrimitiveValues = [];
186
+ this.selectedItems = [...value];
187
+ this.selectedItemList = this.selectedItems
188
+ .map((item) => item[this.optionDisplayProperty])
189
+ .join(", ");
190
+ this.onChange(this.selectedItems);
191
+ this._recomputeSelectAllState();
192
+ }
193
+
194
+ private _mapPrimitivesToObjects() {
195
+ if (
196
+ !this._isPrimitivePatch ||
197
+ this._pendingPrimitiveValues.length === 0 ||
198
+ this._options.length === 0
199
+ ) {
200
+ return;
201
+ }
202
+
203
+ // Find matching option‐objects whose ID property is in the pending list
204
+ const matched = this._options.filter((opt) =>
205
+ this._pendingPrimitiveValues.includes(opt[this.optionValueProperty])
206
+ );
207
+
208
+ this.selectedItems = matched;
209
+ this.selectedItemList = matched
210
+ .map((item) => item[this.optionDisplayProperty])
211
+ .join(", ");
212
+
213
+ // Notify “onChange” with the full‐object selection
214
+ this.onChange(this.selectedItems);
215
+
216
+ this._isPrimitivePatch = false;
217
+ this._pendingPrimitiveValues = [];
218
+ }
219
+
220
+ registerOnChange(fn: any): void {
221
+ this.onChange = fn;
222
+ }
223
+ registerOnTouched(fn: any): void {
224
+ this.onTouched = fn;
225
+ }
226
+
227
+ // events
228
+ isSelected(option: any): boolean {
229
+ return (
230
+ Array.isArray(this.selectedItems) && this.selectedItems.includes(option)
231
+ );
232
+ }
233
+ onBlur(event: FocusEvent) {
234
+ this.blurEvent.emit();
235
+ // const target = event.relatedTarget as HTMLElement;
236
+ // if (
237
+ // target &&
238
+ // (target.closest(".option-list") || target.closest(".list-filter"))
239
+ // ) {
240
+ // return;
241
+ // }
242
+ this.onTouched();
243
+ // setTimeout(() => {
244
+ // this.isDropdownOpen = false;
245
+ // }, 100);
246
+ }
247
+ setDisabledState(isDisabled: boolean): void {
248
+ this.disabled = isDisabled;
249
+ if (isDisabled) {
250
+ this.inputControl.disable({ emitEvent: false });
251
+ } else {
252
+ this.inputControl.enable({ emitEvent: false });
253
+ }
254
+ }
255
+
256
+ // search box
257
+ handleInput(event: Event) {
258
+ const txt = (event.target as HTMLInputElement).value;
259
+ this.onSearch.next(txt);
260
+ }
261
+ private _applyFilter(value: string) {
262
+ const lower = value.toLowerCase();
263
+ this.filteredOptions = this._options
264
+ .filter((opt) =>
265
+ opt[this.optionDisplayProperty]?.toLowerCase().includes(lower)
266
+ )
267
+ .sort((a, b) => {
268
+ const aIndex = a[this.optionDisplayProperty]
269
+ .toLowerCase()
270
+ .indexOf(lower);
271
+ const bIndex = b[this.optionDisplayProperty]
272
+ .toLowerCase()
273
+ .indexOf(lower);
274
+ return aIndex - bIndex;
275
+ });
276
+
277
+ // If an item was previously selected but is no longer in filteredOptions, remove it temporarily
278
+ this.selectedItems = this.selectedItems.filter((item) =>
279
+ this.filteredOptions.includes(item)
280
+ );
281
+ this.selectedItemList = this.selectedItems
282
+ .map((item) => item[this.optionDisplayProperty])
283
+ .join(", ");
284
+
285
+ this.onChange(this.selectedItems);
286
+ this.optionsSelected.emit(this.selectedItems);
287
+ if (this.filteredOptions.length > 0) {
288
+ this.highlightedIndex = this._options.indexOf(this.filteredOptions[0]);
289
+ } else {
290
+ this.highlightedIndex = null;
291
+ }
292
+
293
+ this._recomputeSelectAllState();
294
+ this.cdRef.detectChanges();
295
+ }
296
+
297
+ // checkbox events
298
+ private _recomputeSelectAllState() {
299
+ if (!this.filteredOptions || this.filteredOptions.length === 0) {
300
+ this.selectAllChecked = false;
301
+ return;
302
+ }
303
+ this.selectAllChecked = this.filteredOptions.every((opt) =>
304
+ this.selectedItems.includes(opt)
305
+ );
306
+ }
307
+ onSelectAllChange(event: Event) {
308
+ const input = event.target as HTMLInputElement;
309
+ this.selectAllChecked = input.checked;
310
+ this._toggleSelectAll();
311
+ }
312
+ private _toggleSelectAll() {
313
+ if (this.selectAllChecked) {
314
+ // Add all currently filtered options to selection
315
+ this.selectedItems = Array.from(
316
+ new Set([...this.selectedItems, ...this.filteredOptions])
317
+ );
318
+ } else {
319
+ // Remove all filtered options from selection
320
+ this.selectedItems = this.selectedItems.filter(
321
+ (item) => !this.filteredOptions.includes(item)
322
+ );
323
+ }
324
+
325
+ this.selectedItemList = this.selectedItems
326
+ .map((item) => item[this.optionDisplayProperty])
327
+ .join(", ");
328
+
329
+ this.onChange(this.selectedItems);
330
+ this.optionsSelected.emit(this.selectedItems);
331
+ }
332
+ removeSelectedOption(option: any) {
333
+ this.selectedItems = this.selectedItems.filter((item) => item !== option);
334
+ this.selectedItemList = this.selectedItems
335
+ .map((item) => item[this.optionDisplayProperty])
336
+ .join(", ");
337
+ this.onChange(this.selectedItems);
338
+ this.onTouched();
339
+ this.optionsSelected.emit(this.selectedItems);
340
+ }
341
+ toggleCheckbox(option: any) {
342
+ if (this.isSelected(option)) {
343
+ this.selectedItems = this.selectedItems.filter((item) => item !== option);
344
+ } else {
345
+ this.selectedItems = [...this.selectedItems, option];
346
+ }
347
+
348
+ this.selectedItemList = this.selectedItems
349
+ .map((item) => item[this.optionDisplayProperty])
350
+ .join(", ");
351
+
352
+ // Update “select all” checkbox
353
+ this._recomputeSelectAllState();
354
+
355
+ // Notify form that selection changed
356
+ this.onChange(this.selectedItems);
357
+ this.onTouched();
358
+ this.optionsSelected.emit(this.selectedItems);
359
+ }
360
+
361
+ // Keyboard navigation inside the dropdown
362
+ onKeyDown(event: KeyboardEvent) {
363
+ if (this.isDropdownOpen) {
364
+ switch (event.key) {
365
+ case "ArrowDown":
366
+ this._highlightNext();
367
+ event.preventDefault();
368
+ break;
369
+ case "ArrowUp":
370
+ this._highlightPrevious();
371
+ event.preventDefault();
372
+ break;
373
+ case "Enter":
374
+ if (
375
+ this.highlightedIndex !== null &&
376
+ this.highlightedIndex < this.filteredOptions.length
377
+ ) {
378
+ this.toggleCheckbox(this.filteredOptions[this.highlightedIndex]);
379
+ }
380
+ event.preventDefault();
381
+ break;
382
+ case "Escape":
383
+ this.isDropdownOpen = false;
384
+ break;
385
+ case "Tab":
386
+ this.isDropdownOpen = false;
387
+ break;
388
+ }
389
+ } else if (event.key === " ") {
390
+ this.toggleDropdown();
391
+ event.preventDefault();
392
+ }
393
+ }
394
+ @HostListener("window:keydown", ["$event"])
395
+ handleWindowKeydown(event: KeyboardEvent) {
396
+ if (event.key === "Tab") {
397
+ this.isDropdownOpen = false;
398
+ }
399
+ }
400
+ private _highlightNext() {
401
+ if (
402
+ this.highlightedIndex === null ||
403
+ this.highlightedIndex === this.filteredOptions.length - 1
404
+ ) {
405
+ this.highlightedIndex = 0;
406
+ } else {
407
+ this.highlightedIndex!++;
408
+ }
409
+ this._scrollToHighlighted();
410
+ }
411
+ private _highlightPrevious() {
412
+ if (this.highlightedIndex === null || this.highlightedIndex === 0) {
413
+ this.highlightedIndex = this.filteredOptions.length - 1;
414
+ } else {
415
+ this.highlightedIndex!--;
416
+ }
417
+ this._scrollToHighlighted();
418
+ }
419
+ private _scrollToHighlighted() {
420
+ setTimeout(() => {
421
+ const container = document.querySelector(".option-list");
422
+ if (!container) return;
423
+ const highlighted = container.querySelector(".highlighted");
424
+ if (highlighted) {
425
+ (highlighted as HTMLElement).scrollIntoView({ block: "nearest" });
426
+ }
427
+ }, 0);
428
+ }
429
+
430
+ // popper
431
+ private _createPopperInstance() {
432
+ if (!this.dropdown || !this.input) return;
433
+ this.popperInstance = createPopper(
434
+ this.input.nativeElement,
435
+ this.dropdown.nativeElement,
436
+ {
437
+ placement: "bottom-start",
438
+ modifiers: [
439
+ {
440
+ name: "offset",
441
+ options: {
442
+ offset: [0, 1],
443
+ },
444
+ },
445
+ {
446
+ name: "flip",
447
+ options: {
448
+ fallbackPlacements: ["top-start", "bottom-start"],
449
+ },
450
+ },
451
+ ],
452
+ }
453
+ );
454
+ }
455
+
456
+ // toggle, reset, close and clear
457
+ toggleDropdown() {
458
+ if (this.inputControl.disabled) return;
459
+ this.isDropdownOpen = !this.isDropdownOpen;
460
+ this.dropdownInitialized = false;
461
+ if (this.isDropdownOpen) {
462
+ this.filteredOptions = [...this.options];
463
+ this.highlightedIndex = null;
464
+ }
465
+ }
466
+ selectListOutsideClick() {
467
+ this.isDropdownOpen = false;
468
+ }
469
+ resetSelection() {
470
+ this.selectedItems = [];
471
+ this.selectedItemList = "";
472
+ this.onChange([]);
473
+ this.onTouched();
474
+ this.isDropdownOpen = false;
475
+ this.selectAllChecked = false;
476
+ this.selectionCleared.emit();
477
+ }
478
+ onCloseDropdown(): void {
479
+ this.isDropdownOpen = false;
480
+ this._recomputeSelectAllState();
481
+ }
482
+ }
@@ -0,0 +1,53 @@
1
+ <div class="form-group select" [ngClass]="customClass" (clickOutside)="selectListOutsideClick($event)">
2
+ @if (title) {
3
+ <label class="inp-label" [ngClass]="{ 'required': required }">{{ title }}</label>
4
+ }
5
+
6
+ <input #input type="text" class="form-control" [placeholder]="placeholder ? placeholder : ''"
7
+ [formControl]="inputControl" [ngClass]="{ 'is-invalid': error }" (blur)="onBlur()" (click)="toggleDropdown()"
8
+ (keydown)="onKeyDown($event)" readonly [attr.autocomplete]="autocomplete || null" />
9
+
10
+ @if (deFocus) {
11
+ <span class="focus-border"></span>
12
+ }
13
+
14
+ @if (!inputLoader && isDropdownOpen && selectedItem && clearVal) {
15
+ <label class="clear" (click)="resetSelection()">
16
+ <i class="he he-close"></i>
17
+ </label> }
18
+ @if (!inputLoader) {
19
+ <label class="arrow">
20
+ <i class="he he-chevron-down"></i>
21
+ </label>
22
+ }
23
+
24
+ @if (isDropdownOpen) {
25
+ <div #dropdown class="option-list">
26
+ <div class="no-results" *ngIf="filteredOptions.length === 0">
27
+ <div>No results found</div>
28
+ <div *ngIf="isAddNewItem" (click)="onAddNewItemClick()" class="btn-new">Add New Item</div>
29
+ </div>
30
+
31
+ @for (option of filteredOptions; track option[optionDisplayProperty]; let idx = $index) {
32
+ <div class="list-item" [ngClass]="{
33
+ 'active': option === selectedItem,
34
+ 'highlighted': idx === highlightedIndex
35
+ }" (click)="selectOption(option)">
36
+ @if (option.countryCode) {
37
+ <img src="https://flagcdn.com/w80/{{ option.countryCode }}.png" width="20" alt="{{ option.countryCode }} flag" loading="lazy" />
38
+ }
39
+ {{ option[optionDisplayProperty] }}
40
+ </div>
41
+ }
42
+
43
+ </div>
44
+ }
45
+
46
+ @if (inputLoader) {
47
+ <label class="loader input-loader"></label>
48
+ }
49
+
50
+ @if (error) {
51
+ <div class="val-msg">{{ errorMessage }}</div>
52
+ }
53
+ </div>
@@ -0,0 +1,19 @@
1
+ .form-group {
2
+ &.select {
3
+ .option-list {
4
+ .no-results {
5
+ padding: 10px;
6
+ color: #9b9b9b;
7
+ text-align: center;
8
+ .btn-new {
9
+ padding: 5px 0;
10
+ cursor: pointer;
11
+ color: #0d6efd;
12
+ }
13
+ }
14
+ }
15
+ .clear {
16
+ right: 30px;
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { SelectControl } from './select-control.component';
3
+
4
+ describe('SelectControl', () => {
5
+ let component: SelectControl;
6
+ let fixture: ComponentFixture<SelectControl>;
7
+
8
+ beforeEach(async () => {
9
+ await TestBed.configureTestingModule({
10
+ imports: [SelectControl]
11
+ })
12
+ .compileComponents();
13
+
14
+ fixture = TestBed.createComponent(SelectControl);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it('should create', () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });