@nexheal/nexheal-lib 0.0.5

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.
@@ -0,0 +1,2852 @@
1
+ import * as i1 from '@angular/common';
2
+ import { CommonModule, DatePipe, DOCUMENT } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { EventEmitter, ViewChild, Input, Output, Component, forwardRef, HostListener, inject, ElementRef, DestroyRef, Directive } from '@angular/core';
5
+ import * as i2 from '@angular/forms';
6
+ import { FormControl, ReactiveFormsModule, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
7
+ import { Subscription, debounceTime, distinctUntilChanged, fromEvent, Subject } from 'rxjs';
8
+ import { createPopper } from '@popperjs/core';
9
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
10
+
11
+ class AutocompleteControl {
12
+ subscription = new Subscription();
13
+ title;
14
+ required = false;
15
+ placeholder = "";
16
+ customClass;
17
+ clearVal = true;
18
+ field = "";
19
+ error = false;
20
+ errorMessage = "";
21
+ autocomplete = "";
22
+ inputLoader = false;
23
+ isAddNewItem = false;
24
+ optionDisplayProperty = "displayname";
25
+ optionSelected = new EventEmitter();
26
+ search = new EventEmitter();
27
+ selectionCleared = new EventEmitter();
28
+ addNewItemClicked = new EventEmitter();
29
+ blurEvent = new EventEmitter();
30
+ optionPatched = new EventEmitter();
31
+ _disabled = false;
32
+ _options = [];
33
+ set options(value) {
34
+ this._options = value || [];
35
+ this.processValueBuffer();
36
+ }
37
+ get options() {
38
+ return this._options;
39
+ }
40
+ get disabled() {
41
+ return this._disabled;
42
+ }
43
+ set disabled(value) {
44
+ this._disabled = value;
45
+ if (this.inputControl) {
46
+ if (value) {
47
+ this.inputControl.disable();
48
+ }
49
+ else {
50
+ this.inputControl.enable();
51
+ }
52
+ }
53
+ }
54
+ valueBuffer = null;
55
+ popperInstance;
56
+ preventDropdownReopen = false;
57
+ preventClearOnBlur = false;
58
+ onChange = () => { };
59
+ onTouched = () => { };
60
+ inputElement;
61
+ dropdownElement;
62
+ inputControl = new FormControl("");
63
+ isDropdownOpen = false;
64
+ hasFocus = false;
65
+ selectedItems;
66
+ filteredSuggestions = [];
67
+ highlightedIndex = null;
68
+ constructor() {
69
+ this.inputControl.valueChanges
70
+ .pipe(debounceTime(10), distinctUntilChanged())
71
+ .subscribe((value) => {
72
+ if (this.preventDropdownReopen) {
73
+ this.preventDropdownReopen = false;
74
+ return;
75
+ }
76
+ if (value && value.trim().length > 0) {
77
+ const filterValue = value;
78
+ this.filterSuggestions(filterValue);
79
+ this.search.emit(filterValue);
80
+ }
81
+ else {
82
+ this.isDropdownOpen = false;
83
+ }
84
+ });
85
+ }
86
+ ngOnInit() {
87
+ this.inputControl.markAsPristine();
88
+ }
89
+ ngAfterViewInit() {
90
+ this.createPopperInstance();
91
+ }
92
+ ngOnDestroy() {
93
+ this.subscription.unsubscribe();
94
+ if (this.popperInstance) {
95
+ this.popperInstance.destroy();
96
+ }
97
+ }
98
+ writeValue(value) {
99
+ this.preventDropdownReopen = true;
100
+ if (value == null) {
101
+ // Clear everything if we get null
102
+ this.valueBuffer = null;
103
+ this.selectedItems = null;
104
+ this.preventDropdownReopen = true;
105
+ this.inputControl.setValue("", { emitEvent: false });
106
+ }
107
+ else {
108
+ // Otherwise, treat as normal
109
+ this.valueBuffer = value;
110
+ this.processValueBuffer();
111
+ }
112
+ }
113
+ processValueBuffer() {
114
+ if (this.valueBuffer == null) {
115
+ this.selectedItems = null;
116
+ this.preventDropdownReopen = true;
117
+ // this.inputControl.setValue("", { emitEvent: false });
118
+ return;
119
+ }
120
+ if (this._options && this._options.length > 0) {
121
+ const matchedSuggestion = this._options.find((s) => s.id === this.valueBuffer);
122
+ if (matchedSuggestion) {
123
+ this.preventDropdownReopen = true;
124
+ this.inputControl.setValue(matchedSuggestion[this.optionDisplayProperty], { emitEvent: false });
125
+ this.selectedItems = matchedSuggestion;
126
+ this.onChange(matchedSuggestion.id);
127
+ this.optionPatched.emit(matchedSuggestion);
128
+ this.valueBuffer = null;
129
+ return;
130
+ }
131
+ }
132
+ this.inputControl.setValue("", { emitEvent: false });
133
+ this.selectedItems = null;
134
+ }
135
+ registerOnChange(fn) {
136
+ this.onChange = fn;
137
+ }
138
+ registerOnTouched(fn) {
139
+ this.onTouched = fn;
140
+ }
141
+ setDisabledState(isDisabled) {
142
+ if (isDisabled) {
143
+ this.inputControl.disable();
144
+ }
145
+ else {
146
+ this.inputControl.enable();
147
+ }
148
+ }
149
+ filterSuggestions(value) {
150
+ if (this.preventDropdownReopen || !this.hasFocus) {
151
+ this.preventDropdownReopen = false;
152
+ return;
153
+ }
154
+ if (value === "") {
155
+ this.filteredSuggestions = [];
156
+ this.isDropdownOpen = false;
157
+ return;
158
+ }
159
+ const filterValue = value.toString().toLowerCase();
160
+ this.filteredSuggestions = this.options.filter((suggestion) => suggestion[this.optionDisplayProperty]
161
+ ?.toLowerCase()
162
+ .includes(filterValue));
163
+ this.highlightedIndex = this.filteredSuggestions.length > 0 ? 0 : null;
164
+ this.isDropdownOpen = true;
165
+ setTimeout(() => {
166
+ this.createPopperInstance();
167
+ }, 0);
168
+ }
169
+ selectSuggestion(suggestion) {
170
+ this.preventDropdownReopen = true;
171
+ this.inputControl.setValue(suggestion[this.optionDisplayProperty], {
172
+ emitEvent: false,
173
+ });
174
+ this.onChange(suggestion.id);
175
+ this.inputControl.markAsTouched();
176
+ this.inputControl.updateValueAndValidity(); // Trigger validation update
177
+ this.isDropdownOpen = false;
178
+ if (this.selectedItems?.id !== suggestion.id) {
179
+ this.selectedItems = suggestion;
180
+ this.optionSelected.emit(suggestion);
181
+ }
182
+ }
183
+ // events
184
+ onFocus() {
185
+ this.hasFocus = true;
186
+ }
187
+ onBlur() {
188
+ this.blurEvent.emit();
189
+ this.onTouched();
190
+ // Delay onBlur handling so that mousedown and click can occur first.
191
+ setTimeout(() => {
192
+ this.hasFocus = false;
193
+ this.isDropdownOpen = false;
194
+ // If an option click is in progress, do not clear the input.
195
+ if (!this.preventClearOnBlur) {
196
+ // Optionally: check if the current value is valid against options.
197
+ // If not found, clear the input.
198
+ const currentValue = this.inputControl.value;
199
+ const found = this._options.find((opt) => String(opt[this.optionDisplayProperty]).toLowerCase() ===
200
+ String(currentValue).toLowerCase());
201
+ if (!found) {
202
+ this.inputControl.setValue("");
203
+ this.selectedItems = null;
204
+ this.onChange(null);
205
+ }
206
+ }
207
+ // Reset the flag after handling blur.
208
+ this.preventClearOnBlur = false;
209
+ }, 300);
210
+ }
211
+ onKeyDown(event) {
212
+ if (!this.isDropdownOpen) {
213
+ return;
214
+ }
215
+ switch (event.key) {
216
+ case "ArrowDown":
217
+ this.highlightedIndex =
218
+ this.highlightedIndex === null ||
219
+ this.highlightedIndex === this.filteredSuggestions.length - 1
220
+ ? 0
221
+ : this.highlightedIndex + 1;
222
+ event.preventDefault();
223
+ this.scrollHighlightedItemIntoView();
224
+ break;
225
+ case "ArrowUp":
226
+ this.highlightedIndex =
227
+ this.highlightedIndex === null || this.highlightedIndex === 0
228
+ ? this.filteredSuggestions.length - 1
229
+ : this.highlightedIndex - 1;
230
+ event.preventDefault();
231
+ this.scrollHighlightedItemIntoView();
232
+ break;
233
+ case "Enter":
234
+ if (this.highlightedIndex !== null &&
235
+ this.filteredSuggestions.length > 0) {
236
+ this.selectSuggestion(this.filteredSuggestions[this.highlightedIndex]);
237
+ }
238
+ else {
239
+ this.isDropdownOpen = false;
240
+ }
241
+ event.preventDefault();
242
+ break;
243
+ case "Escape":
244
+ this.isDropdownOpen = false;
245
+ break;
246
+ default:
247
+ break;
248
+ }
249
+ }
250
+ onOptionMouseDown() {
251
+ this.preventClearOnBlur = true;
252
+ }
253
+ onMouseOver(index) {
254
+ this.highlightedIndex = index;
255
+ }
256
+ // actions
257
+ onAddNewItemClick() {
258
+ this.addNewItemClicked.emit();
259
+ }
260
+ // popper
261
+ createPopperInstance() {
262
+ if (this.popperInstance) {
263
+ this.popperInstance.destroy();
264
+ }
265
+ if (this.inputElement && this.dropdownElement) {
266
+ this.popperInstance = createPopper(this.inputElement.nativeElement, this.dropdownElement.nativeElement, {
267
+ placement: "bottom-start",
268
+ modifiers: [
269
+ {
270
+ name: "offset",
271
+ options: {
272
+ offset: [0, 1],
273
+ },
274
+ },
275
+ {
276
+ name: "flip",
277
+ options: {
278
+ fallbackPlacements: ["top-start", "bottom-start"],
279
+ },
280
+ },
281
+ ],
282
+ });
283
+ }
284
+ }
285
+ scrollHighlightedItemIntoView() {
286
+ if (this.highlightedIndex !== null) {
287
+ const highlightedItem = this.dropdownElement.nativeElement.querySelectorAll(".list-item")[this.highlightedIndex];
288
+ if (highlightedItem) {
289
+ highlightedItem.scrollIntoView({ block: "nearest" });
290
+ }
291
+ }
292
+ }
293
+ // clear
294
+ resetInput() {
295
+ this.inputControl.setValue("");
296
+ this.selectionCleared.emit();
297
+ this.selectedItems = null;
298
+ this.valueBuffer = null;
299
+ }
300
+ removeItem(item) {
301
+ this.selectedItems = this.selectedItems.filter((i) => i !== item);
302
+ this.onChange(this.selectedItems);
303
+ this.optionSelected.emit(this.selectedItems);
304
+ }
305
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: AutocompleteControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
306
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.3", type: AutocompleteControl, isStandalone: true, selector: "autocomplete-control", inputs: { title: "title", required: "required", placeholder: "placeholder", customClass: "customClass", clearVal: "clearVal", field: "field", error: "error", errorMessage: "errorMessage", autocomplete: "autocomplete", inputLoader: "inputLoader", isAddNewItem: "isAddNewItem", optionDisplayProperty: "optionDisplayProperty", options: "options", disabled: "disabled" }, outputs: { optionSelected: "optionSelected", search: "search", selectionCleared: "selectionCleared", addNewItemClicked: "addNewItemClicked", blurEvent: "blurEvent", optionPatched: "optionPatched" }, providers: [
307
+ {
308
+ provide: NG_VALUE_ACCESSOR,
309
+ useExisting: AutocompleteControl,
310
+ multi: true,
311
+ },
312
+ ], viewQueries: [{ propertyName: "inputElement", first: true, predicate: ["inputElement"], descendants: true }, { propertyName: "dropdownElement", first: true, predicate: ["dropdownElement"], descendants: true }], ngImport: i0, template: "<div class=\"form-group auto-complete\" [ngClass]=\"customClass\">\r\n @if (title) {\r\n <label class=\"inp-label\" [ngClass]=\"{ 'required': required }\">{{ title }}</label>\r\n }\r\n\r\n <input #inputElement type=\"text\" class=\"form-control\" [placeholder]=\"placeholder\" [formControl]=\"inputControl\"\r\n (blur)=\"onBlur()\" (keydown)=\"onKeyDown($event)\" (focus)=\"onFocus()\" [ngClass]=\"{'is-invalid': error}\"\r\n [attr.autocomplete]=\"autocomplete || null\" />\r\n\r\n <span class=\"focus-border\"></span>\r\n\r\n @if (!inputLoader && inputControl.value && clearVal && hasFocus) {\r\n <label class=\"clear\" (click)=\"resetInput()\">\r\n <i class=\"he he-close\"></i>\r\n </label>\r\n }\r\n @if (isDropdownOpen) {\r\n <div #dropdownElement class=\"option-list\">\r\n @if (filteredSuggestions.length === 0) {\r\n <div class=\"no-results\">\r\n <div>No results found</div>\r\n @if (isAddNewItem) {\r\n <div (click)=\"onAddNewItemClick()\" class=\"btn-new\">\r\n Add New Item\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n @for (suggestion of filteredSuggestions; track suggestion[optionDisplayProperty]; let i = $index) {\r\n <div class=\"list-item\" [ngClass]=\"{\r\n 'active': suggestion === selectedItems,\r\n 'highlighted': highlightedIndex === i\r\n }\" (mousedown)=\"onOptionMouseDown()\" (click)=\"selectSuggestion(suggestion)\" (mouseover)=\"onMouseOver(i)\">\r\n @if (suggestion.countryCode) {\r\n <img src=\"https://flagcdn.com/w80/{{ suggestion.countryCode }}.png\" width=\"20\"\r\n alt=\"{{ suggestion.countryCode }} flag\" loading=\"lazy\" />\r\n }\r\n {{ suggestion[optionDisplayProperty] }}\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n @if (inputLoader) {\r\n <label class=\"loader input-loader\"></label>\r\n }\r\n\r\n @if (error) {\r\n <div class=\"val-msg\">{{ errorMessage }}</div>\r\n }\r\n</div>", styles: [".form-group.auto-complete .form-control{padding-right:unset}.form-group.auto-complete .clear{right:7px}.form-group.auto-complete .option-list .no-results{padding:10px;color:#9b9b9b;text-align:center}.form-group.auto-complete .option-list .no-results .btn-new{padding:5px 0;cursor:pointer;color:#0d6efd}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
313
+ }
314
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: AutocompleteControl, decorators: [{
315
+ type: Component,
316
+ args: [{ selector: "autocomplete-control", standalone: true, imports: [CommonModule, ReactiveFormsModule], providers: [
317
+ {
318
+ provide: NG_VALUE_ACCESSOR,
319
+ useExisting: AutocompleteControl,
320
+ multi: true,
321
+ },
322
+ ], template: "<div class=\"form-group auto-complete\" [ngClass]=\"customClass\">\r\n @if (title) {\r\n <label class=\"inp-label\" [ngClass]=\"{ 'required': required }\">{{ title }}</label>\r\n }\r\n\r\n <input #inputElement type=\"text\" class=\"form-control\" [placeholder]=\"placeholder\" [formControl]=\"inputControl\"\r\n (blur)=\"onBlur()\" (keydown)=\"onKeyDown($event)\" (focus)=\"onFocus()\" [ngClass]=\"{'is-invalid': error}\"\r\n [attr.autocomplete]=\"autocomplete || null\" />\r\n\r\n <span class=\"focus-border\"></span>\r\n\r\n @if (!inputLoader && inputControl.value && clearVal && hasFocus) {\r\n <label class=\"clear\" (click)=\"resetInput()\">\r\n <i class=\"he he-close\"></i>\r\n </label>\r\n }\r\n @if (isDropdownOpen) {\r\n <div #dropdownElement class=\"option-list\">\r\n @if (filteredSuggestions.length === 0) {\r\n <div class=\"no-results\">\r\n <div>No results found</div>\r\n @if (isAddNewItem) {\r\n <div (click)=\"onAddNewItemClick()\" class=\"btn-new\">\r\n Add New Item\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n @for (suggestion of filteredSuggestions; track suggestion[optionDisplayProperty]; let i = $index) {\r\n <div class=\"list-item\" [ngClass]=\"{\r\n 'active': suggestion === selectedItems,\r\n 'highlighted': highlightedIndex === i\r\n }\" (mousedown)=\"onOptionMouseDown()\" (click)=\"selectSuggestion(suggestion)\" (mouseover)=\"onMouseOver(i)\">\r\n @if (suggestion.countryCode) {\r\n <img src=\"https://flagcdn.com/w80/{{ suggestion.countryCode }}.png\" width=\"20\"\r\n alt=\"{{ suggestion.countryCode }} flag\" loading=\"lazy\" />\r\n }\r\n {{ suggestion[optionDisplayProperty] }}\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n @if (inputLoader) {\r\n <label class=\"loader input-loader\"></label>\r\n }\r\n\r\n @if (error) {\r\n <div class=\"val-msg\">{{ errorMessage }}</div>\r\n }\r\n</div>", styles: [".form-group.auto-complete .form-control{padding-right:unset}.form-group.auto-complete .clear{right:7px}.form-group.auto-complete .option-list .no-results{padding:10px;color:#9b9b9b;text-align:center}.form-group.auto-complete .option-list .no-results .btn-new{padding:5px 0;cursor:pointer;color:#0d6efd}\n"] }]
323
+ }], ctorParameters: () => [], propDecorators: { title: [{
324
+ type: Input
325
+ }], required: [{
326
+ type: Input
327
+ }], placeholder: [{
328
+ type: Input
329
+ }], customClass: [{
330
+ type: Input
331
+ }], clearVal: [{
332
+ type: Input
333
+ }], field: [{
334
+ type: Input
335
+ }], error: [{
336
+ type: Input
337
+ }], errorMessage: [{
338
+ type: Input
339
+ }], autocomplete: [{
340
+ type: Input
341
+ }], inputLoader: [{
342
+ type: Input
343
+ }], isAddNewItem: [{
344
+ type: Input
345
+ }], optionDisplayProperty: [{
346
+ type: Input
347
+ }], optionSelected: [{
348
+ type: Output
349
+ }], search: [{
350
+ type: Output
351
+ }], selectionCleared: [{
352
+ type: Output
353
+ }], addNewItemClicked: [{
354
+ type: Output
355
+ }], blurEvent: [{
356
+ type: Output
357
+ }], optionPatched: [{
358
+ type: Output
359
+ }], options: [{
360
+ type: Input
361
+ }], disabled: [{
362
+ type: Input
363
+ }], inputElement: [{
364
+ type: ViewChild,
365
+ args: ["inputElement"]
366
+ }], dropdownElement: [{
367
+ type: ViewChild,
368
+ args: ["dropdownElement"]
369
+ }] } });
370
+
371
+ class CalendarControl {
372
+ datePipe;
373
+ title;
374
+ required = false;
375
+ customClass = "";
376
+ clearVal = true;
377
+ deFocus = true;
378
+ error = false;
379
+ errorMessage = "";
380
+ inputLoader = false;
381
+ hourFormat = "24";
382
+ selectionMode = "single";
383
+ timeOnly = false;
384
+ dateFormat = "dd/MM/yyyy";
385
+ placeholder = "dd-mm-yyyy";
386
+ disabled = false;
387
+ readonly = false;
388
+ submitted = false;
389
+ inputPlaceholder = false;
390
+ closeVal = false;
391
+ showTime = false;
392
+ selectionCleared = new EventEmitter();
393
+ blurEvent = new EventEmitter();
394
+ dateSelected = new EventEmitter();
395
+ _minDate;
396
+ _maxDate;
397
+ get minDate() {
398
+ return this._minDate;
399
+ }
400
+ set minDate(value) {
401
+ this._minDate = value;
402
+ }
403
+ get maxDate() {
404
+ return this._maxDate;
405
+ }
406
+ set maxDate(value) {
407
+ this._maxDate = value;
408
+ }
409
+ get meridian() {
410
+ if (this.hourFormat !== '12')
411
+ return 'AM';
412
+ return this.selectedHour >= 12 ? 'PM' : 'AM';
413
+ }
414
+ static _currentlyOpen;
415
+ onChangeFn = () => { };
416
+ onTouchedFn = () => { };
417
+ popperInstance = null;
418
+ rootElement;
419
+ inputEl;
420
+ datePickerEl;
421
+ isOpen = false;
422
+ focus = false;
423
+ displayMonth;
424
+ displayYear;
425
+ selectedDate = null;
426
+ today = new Date();
427
+ todayYear = this.today.getFullYear();
428
+ todayMonth = this.today.getMonth();
429
+ todayDate = this.today.getDate();
430
+ selectedHour = this.today.getHours();
431
+ selectedMinute = this.today.getMinutes();
432
+ dayClassMap = {};
433
+ inputVal = null; // Replace with the appropriate type and default value
434
+ yearRangeSize = 15; // The year range view shows a chunk of years, for example 15 years at a time.
435
+ currentView = "day";
436
+ inputControl = new FormControl({ value: "", disabled: false });
437
+ constructor(datePipe) {
438
+ this.datePipe = datePipe;
439
+ const today = new Date();
440
+ this.displayMonth = today.getMonth();
441
+ this.displayYear = today.getFullYear();
442
+ }
443
+ ngOnInit() {
444
+ const now = new Date();
445
+ this.displayYear = now.getFullYear();
446
+ this.displayMonth = now.getMonth();
447
+ }
448
+ ngAfterViewChecked() {
449
+ if (this.isOpen) {
450
+ this.initializePopper();
451
+ }
452
+ }
453
+ ngAfterViewInit() {
454
+ if (this.isOpen) {
455
+ this.initializePopper();
456
+ }
457
+ }
458
+ ngOnDestroy() {
459
+ this.destroyPopper();
460
+ }
461
+ writeValue(value) {
462
+ // 1) Clear out if no value
463
+ if (!value) {
464
+ this.selectedDate = null;
465
+ this.inputControl.setValue("", { emitEvent: false });
466
+ return;
467
+ }
468
+ if (this.timeOnly) {
469
+ let hours, mins;
470
+ if (typeof value === "string") {
471
+ // “HH:mm” or “hh:mm AM/PM”
472
+ const m24 = value.match(/^(\d{1,2}):(\d{2})$/);
473
+ const m12 = value.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)$/i);
474
+ if (m24) {
475
+ hours = +m24[1];
476
+ mins = +m24[2];
477
+ }
478
+ else if (m12) {
479
+ let h = +m12[1];
480
+ if (/PM/i.test(m12[3]) && h < 12)
481
+ h += 12;
482
+ if (/AM/i.test(m12[3]) && h === 12)
483
+ h = 0;
484
+ hours = h;
485
+ mins = +m12[2];
486
+ }
487
+ else {
488
+ // fallback parse
489
+ const dt = new Date(value);
490
+ hours = dt.getHours();
491
+ mins = dt.getMinutes();
492
+ }
493
+ }
494
+ else if (value instanceof Date) {
495
+ hours = value.getHours();
496
+ mins = value.getMinutes();
497
+ }
498
+ else {
499
+ // maybe a timestamp or something else
500
+ const dt = new Date(value);
501
+ hours = dt.getHours();
502
+ mins = dt.getMinutes();
503
+ }
504
+ // 3) Apply
505
+ this.selectedHour = hours;
506
+ this.selectedMinute = mins;
507
+ // 4) Write back into the formControl & notify forms
508
+ const out = this.formatOutputTimeOnly();
509
+ this.inputControl.setValue(out, { emitEvent: false });
510
+ this.onChangeFn(out);
511
+ this.onTouchedFn();
512
+ return;
513
+ }
514
+ // 2) Parse the incoming string (dd/MM/yyyy or dd/MM/yyyy HH:mm)
515
+ const parsed = this.parseDate(value);
516
+ this.selectedDate = parsed;
517
+ if (this.showTime) {
518
+ const raw = value;
519
+ // split into date vs time
520
+ let datePart = raw, timePart = null;
521
+ const parts = raw.match(/^(.+?)\s+(.+)$/);
522
+ if (parts) {
523
+ datePart = parts[1];
524
+ timePart = parts[2];
525
+ }
526
+ // parse the date
527
+ const parsedDate = this.parseDate(datePart);
528
+ if (!parsedDate) {
529
+ return this.clearDate(new Event("manual"));
530
+ }
531
+ // parse the time portion (reuse your timeOnly logic)
532
+ if (timePart) {
533
+ let h, m;
534
+ const m24 = timePart.match(/^(\d{1,2}):(\d{2})$/);
535
+ const m12 = timePart.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)$/i);
536
+ if (m24) {
537
+ h = +m24[1];
538
+ m = +m24[2];
539
+ }
540
+ else if (m12) {
541
+ h = +m12[1];
542
+ m = +m12[2];
543
+ if (/PM/i.test(m12[3]) && h < 12)
544
+ h += 12;
545
+ if (/AM/i.test(m12[3]) && h === 12)
546
+ h = 0;
547
+ }
548
+ else {
549
+ const dt = new Date(`1970-01-01T${timePart}`);
550
+ h = dt.getHours();
551
+ m = dt.getMinutes();
552
+ }
553
+ parsedDate.setHours(h, m);
554
+ this.selectedHour = h;
555
+ this.selectedMinute = m;
556
+ }
557
+ // update calendar view & control value
558
+ this.selectedDate = parsedDate;
559
+ this.displayMonth = parsedDate.getMonth();
560
+ this.displayYear = parsedDate.getFullYear();
561
+ const out = this.formatOutput(parsedDate);
562
+ this.inputControl.setValue(out, { emitEvent: false });
563
+ this.onChangeFn(out);
564
+ this.onTouchedFn();
565
+ return;
566
+ }
567
+ if (parsed) {
568
+ // 3) Always update the calendar view
569
+ this.displayMonth = parsed.getMonth();
570
+ this.displayYear = parsed.getFullYear();
571
+ // 4) ONLY if showTime is on, pull hours/minutes
572
+ if (this.showTime) {
573
+ this.selectedHour = parsed.getHours();
574
+ this.selectedMinute = parsed.getMinutes();
575
+ }
576
+ // 5) Finally, set the input text via your formatter
577
+ this.inputControl.setValue(this.formatOutput(parsed), {
578
+ emitEvent: false,
579
+ });
580
+ }
581
+ }
582
+ registerOnChange(fn) {
583
+ this.onChangeFn = fn;
584
+ }
585
+ registerOnTouched(fn) {
586
+ this.onTouchedFn = fn;
587
+ }
588
+ setData(config) {
589
+ if (config.hasOwnProperty("placeholder")) {
590
+ this.placeholder = config.placeholder;
591
+ }
592
+ }
593
+ // events
594
+ onBlur() {
595
+ this.onTouchedFn();
596
+ const raw = this.inputControl.value;
597
+ if (this.showTime && !this.timeOnly) {
598
+ // split off the date vs. time
599
+ const match = raw.match(/^(.+?)\s+(.+)$/);
600
+ const datePart = match ? match[1] : raw;
601
+ const timePart = match ? match[2] : "";
602
+ // parse the date
603
+ const pd = this.parseDate(datePart);
604
+ if (!pd)
605
+ return this.clearDate(new Event("manual"));
606
+ // parse the time portion (reuse your timeOnly logic)
607
+ let h, m;
608
+ const m24 = timePart.match(/^(\d{1,2}):(\d{2})$/);
609
+ const m12 = timePart.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)$/i);
610
+ if (m24) {
611
+ h = +m24[1];
612
+ m = +m24[2];
613
+ }
614
+ else if (m12) {
615
+ h = +m12[1];
616
+ m = +m12[2];
617
+ if (/PM/i.test(m12[3]) && h < 12)
618
+ h += 12;
619
+ if (/AM/i.test(m12[3]) && h === 12)
620
+ h = 0;
621
+ }
622
+ else {
623
+ const tmp = new Date(`1970-01-01T${timePart}`);
624
+ h = tmp.getHours();
625
+ m = tmp.getMinutes();
626
+ }
627
+ // apply
628
+ pd.setHours(h, m);
629
+ this.selectedDate = pd;
630
+ this.selectedHour = h;
631
+ this.selectedMinute = m;
632
+ // update calendar view
633
+ this.displayMonth = pd.getMonth();
634
+ this.displayYear = pd.getFullYear();
635
+ // write back into the input & notify
636
+ const out = this.formatOutput(pd);
637
+ this.inputControl.setValue(out, { emitEvent: false });
638
+ this.onChangeFn(out);
639
+ this.dateSelected.emit(pd);
640
+ this.blurEvent.emit();
641
+ return;
642
+ }
643
+ if (this.timeOnly) {
644
+ // try 24-hour HH:mm
645
+ let m = raw.match(/^(\d{1,2}):(\d{2})$/);
646
+ // or 12-hour h:mm AM/PM
647
+ const mAmPm = !m && raw.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)$/i);
648
+ if (m) {
649
+ // 24h
650
+ const h = +m[1], min = +m[2];
651
+ if (h < 24 && min < 60) {
652
+ this.selectedHour = h;
653
+ this.selectedMinute = min;
654
+ }
655
+ else {
656
+ return this.clearDate(new Event("manual"));
657
+ }
658
+ }
659
+ else if (mAmPm) {
660
+ // 12h
661
+ let h = +mAmPm[1], min = +mAmPm[2];
662
+ const ap = mAmPm[3].toUpperCase();
663
+ if (h >= 1 && h <= 12 && min < 60) {
664
+ if (ap === "PM" && h < 12)
665
+ h += 12;
666
+ if (ap === "AM" && h === 12)
667
+ h = 0;
668
+ this.selectedHour = h;
669
+ this.selectedMinute = min;
670
+ }
671
+ else {
672
+ return this.clearDate(new Event("manual"));
673
+ }
674
+ }
675
+ else {
676
+ // not a valid time string
677
+ return this.clearDate(new Event("manual"));
678
+ }
679
+ const out = this.formatOutputTimeOnly();
680
+ this.inputControl.setValue(out, { emitEvent: false });
681
+ this.onChangeFn(out);
682
+ this.dateSelected.emit(this.selectedDate);
683
+ this.blurEvent.emit();
684
+ return;
685
+ }
686
+ if (this.showTime) {
687
+ this.blurEvent.emit();
688
+ return;
689
+ }
690
+ const parsed = this.parseDate(raw);
691
+ if (!parsed) {
692
+ this.clearDate(new Event("manual"));
693
+ }
694
+ else {
695
+ this.selectedDate = parsed;
696
+ this.displayMonth = parsed.getMonth();
697
+ this.displayYear = parsed.getFullYear();
698
+ this.selectedHour = parsed.getHours();
699
+ this.selectedMinute = parsed.getMinutes();
700
+ const formatted = this.formatOutput(parsed);
701
+ this.inputControl.setValue(formatted, { emitEvent: false });
702
+ this.onChangeFn(formatted);
703
+ }
704
+ this.blurEvent.emit();
705
+ }
706
+ onFocus() {
707
+ this.focus = true;
708
+ }
709
+ setDisabledState(isDisabled) {
710
+ this.disabled = isDisabled;
711
+ }
712
+ onInputKeydown(event) {
713
+ switch (event.key) {
714
+ case "ArrowDown":
715
+ event.preventDefault();
716
+ this.toggleCalendar();
717
+ break;
718
+ case "Escape":
719
+ this.isOpen = false;
720
+ break;
721
+ case " ":
722
+ case "Spacebar":
723
+ event.preventDefault();
724
+ this.toggleCalendar();
725
+ break;
726
+ case "Tab":
727
+ this.isOpen = false;
728
+ break;
729
+ }
730
+ }
731
+ // day
732
+ selectDay(day) {
733
+ if (!day)
734
+ return;
735
+ const picked = this.showTime
736
+ ? this.buildDateWithTime(day)
737
+ : new Date(this.displayYear, this.displayMonth, day);
738
+ if (this.isDateDisabled(picked))
739
+ return;
740
+ this.selectedDate = picked;
741
+ const out = this.formatOutput(picked);
742
+ this.inputControl.setValue(out, { emitEvent: false });
743
+ this.onChangeFn(out);
744
+ this.onTouchedFn();
745
+ this.dateSelected.emit(picked);
746
+ this.isOpen = false;
747
+ }
748
+ get daysInMonth() {
749
+ const firstDay = new Date(this.displayYear, this.displayMonth, 1).getDay();
750
+ const totalDays = new Date(this.displayYear, this.displayMonth + 1, 0).getDate();
751
+ const offset = firstDay;
752
+ const daysArray = Array(offset).fill(null);
753
+ this.dayClassMap = {}; // Reset
754
+ for (let i = 1; i <= totalDays; i++) {
755
+ const date = new Date(this.displayYear, this.displayMonth, i);
756
+ this.dayClassMap[i] = {
757
+ disabled: this.isDateDisabled(date),
758
+ selected: this.selectedDate?.getFullYear() === this.displayYear &&
759
+ this.selectedDate?.getMonth() === this.displayMonth &&
760
+ this.selectedDate?.getDate() === i,
761
+ today: this.todayYear === this.displayYear &&
762
+ this.todayMonth === this.displayMonth &&
763
+ this.todayDate === i,
764
+ };
765
+ daysArray.push(i);
766
+ }
767
+ return daysArray;
768
+ }
769
+ parseDate(val) {
770
+ if (typeof val === "string") {
771
+ const m = val.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
772
+ if (m) {
773
+ const d = +m[1], mo = +m[2] - 1, y = +m[3];
774
+ return new Date(y, mo, d);
775
+ }
776
+ }
777
+ // fallback
778
+ const date = new Date(val);
779
+ return isNaN(date.getTime()) ? null : date;
780
+ }
781
+ formatOutput(date) {
782
+ const datePart = this.datePipe.transform(date, this.dateFormat);
783
+ if (this.showTime) {
784
+ const timeFormat = this.hourFormat === "12" ? "hh:mm a" : "HH:mm";
785
+ const timePart = this.datePipe.transform(date, timeFormat);
786
+ return `${datePart} ${timePart}`;
787
+ }
788
+ return datePart;
789
+ }
790
+ isSelectedDay(day) {
791
+ if (!this.selectedDate || !day)
792
+ return false;
793
+ return (this.selectedDate.getFullYear() === this.displayYear &&
794
+ this.selectedDate.getMonth() === this.displayMonth &&
795
+ this.selectedDate.getDate() === day);
796
+ }
797
+ isDateDisabled(d) {
798
+ if (this._minDate && d < new Date(this._minDate))
799
+ return true;
800
+ if (this._maxDate && d > new Date(this._maxDate))
801
+ return true;
802
+ return false;
803
+ }
804
+ isDayDisabled(day) {
805
+ return (day !== null &&
806
+ this.isDateDisabled(new Date(this.displayYear, this.displayMonth, day)));
807
+ }
808
+ // month
809
+ goToMonthView() {
810
+ this.currentView = "month";
811
+ }
812
+ months = [
813
+ "Jan",
814
+ "Feb",
815
+ "Mar",
816
+ "Apr",
817
+ "May",
818
+ "Jun",
819
+ "Jul",
820
+ "Aug",
821
+ "Sep",
822
+ "Oct",
823
+ "Nov",
824
+ "Dec",
825
+ ];
826
+ selectMonth(index) {
827
+ this.displayMonth = index;
828
+ this.currentView = "day";
829
+ }
830
+ get displayMonthName() {
831
+ return this.months[this.displayMonth];
832
+ }
833
+ prevMonth() {
834
+ if (this.displayMonth === 0) {
835
+ this.displayMonth = 11;
836
+ this.displayYear--;
837
+ }
838
+ else {
839
+ this.displayMonth--;
840
+ }
841
+ }
842
+ nextMonth() {
843
+ if (this.displayMonth === 11) {
844
+ this.displayMonth = 0;
845
+ this.displayYear++;
846
+ }
847
+ else {
848
+ this.displayMonth++;
849
+ }
850
+ }
851
+ // year
852
+ goToYearRangeView() {
853
+ this.currentView = "yearRange";
854
+ }
855
+ get yearRange() {
856
+ const startYear = this.getStartYearForRange();
857
+ const years = [];
858
+ for (let i = 0; i < this.yearRangeSize; i++) {
859
+ years.push(startYear + i);
860
+ }
861
+ return years;
862
+ }
863
+ getStartYearForRange() {
864
+ const remainder = this.displayYear % this.yearRangeSize;
865
+ return this.displayYear - remainder;
866
+ }
867
+ selectYear(year) {
868
+ this.displayYear = year;
869
+ this.currentView = "month";
870
+ }
871
+ prevYearRange() {
872
+ this.displayYear -= this.yearRangeSize;
873
+ }
874
+ nextYearRange() {
875
+ this.displayYear += this.yearRangeSize;
876
+ }
877
+ // time picker
878
+ formatOutputTimeOnly() {
879
+ // you could choose 12h vs 24h based on hourFormat
880
+ return this.datePipe.transform(new Date(0, 0, 0, this.selectedHour, this.selectedMinute), this.hourFormat === "12" ? "hh:mm a" : "HH:mm");
881
+ }
882
+ patchDateWithTime() {
883
+ if (!this.selectedDate) {
884
+ const now = new Date();
885
+ this.selectedDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), this.selectedHour, this.selectedMinute);
886
+ }
887
+ else {
888
+ this.selectedDate.setHours(this.selectedHour, this.selectedMinute);
889
+ }
890
+ const output = this.timeOnly
891
+ ? this.formatOutputTimeOnly()
892
+ : this.formatOutput(this.selectedDate);
893
+ this.inputControl.setValue(output, { emitEvent: false });
894
+ this.onChangeFn(output);
895
+ this.onTouchedFn();
896
+ this.dateSelected.emit(this.selectedDate);
897
+ }
898
+ setMeridian(value) {
899
+ if (this.meridian === value)
900
+ return;
901
+ if (value === "AM" && this.selectedHour >= 12) {
902
+ this.selectedHour -= 12;
903
+ }
904
+ else if (value === "PM" && this.selectedHour < 12) {
905
+ this.selectedHour += 12;
906
+ }
907
+ this.patchDateWithTime();
908
+ }
909
+ buildDateWithTime(day) {
910
+ return new Date(this.displayYear, this.displayMonth, day, this.selectedHour, this.selectedMinute);
911
+ }
912
+ incrementHour() {
913
+ this.selectedHour = (this.selectedHour + 1) % 24;
914
+ if (this.showTime || this.timeOnly)
915
+ this.patchDateWithTime();
916
+ }
917
+ decrementHour() {
918
+ this.selectedHour = (this.selectedHour + 23) % 24;
919
+ if (this.showTime || this.timeOnly)
920
+ this.patchDateWithTime();
921
+ }
922
+ incrementMinute() {
923
+ this.selectedMinute++;
924
+ if (this.selectedMinute >= 60) {
925
+ this.selectedMinute = 0;
926
+ this.selectedHour = (this.selectedHour + 1) % 24;
927
+ }
928
+ if (this.showTime || this.timeOnly) {
929
+ this.patchDateWithTime();
930
+ }
931
+ }
932
+ decrementMinute() {
933
+ this.selectedMinute--;
934
+ if (this.selectedMinute < 0) {
935
+ this.selectedMinute = 59;
936
+ // go back an hour, wrapping 0 → 23
937
+ this.selectedHour = (this.selectedHour + 23) % 24;
938
+ }
939
+ if (this.showTime || this.timeOnly) {
940
+ this.patchDateWithTime();
941
+ }
942
+ }
943
+ // popper
944
+ initializePopper() {
945
+ if (!this.popperInstance &&
946
+ this.inputEl?.nativeElement &&
947
+ this.datePickerEl?.nativeElement) {
948
+ this.popperInstance = createPopper(this.inputEl.nativeElement, this.datePickerEl.nativeElement, {
949
+ placement: "bottom-start",
950
+ modifiers: [
951
+ { name: "offset", options: { offset: [0, 1] } },
952
+ { name: "preventOverflow", options: { boundary: "viewport" } },
953
+ { name: "flip", options: { fallbackPlacements: ["top-start"] } },
954
+ ],
955
+ });
956
+ this.popperInstance.update();
957
+ }
958
+ }
959
+ destroyPopper() {
960
+ if (this.popperInstance) {
961
+ this.popperInstance.destroy();
962
+ this.popperInstance = null;
963
+ }
964
+ }
965
+ // toggle, open, close and clear
966
+ toggleCalendar() {
967
+ if (this.disabled || this.readonly)
968
+ return;
969
+ // if another calendar is open, close it
970
+ if (!this.isOpen &&
971
+ CalendarControl._currentlyOpen &&
972
+ CalendarControl._currentlyOpen !== this) {
973
+ CalendarControl._currentlyOpen.closeCalendar();
974
+ }
975
+ // flip open/close
976
+ this.isOpen = !this.isOpen;
977
+ if (this.isOpen) {
978
+ // mark this one as current
979
+ CalendarControl._currentlyOpen = this;
980
+ // reset the month/year view
981
+ this.currentView = "day";
982
+ if (this.selectedDate) {
983
+ this.displayMonth = this.selectedDate.getMonth();
984
+ this.displayYear = this.selectedDate.getFullYear();
985
+ }
986
+ else {
987
+ const now = new Date();
988
+ this.displayMonth = now.getMonth();
989
+ this.displayYear = now.getFullYear();
990
+ }
991
+ // destroy any old popper
992
+ if (this.popperInstance) {
993
+ this.popperInstance.destroy();
994
+ this.popperInstance = null;
995
+ }
996
+ if (this.inputEl && this.datePickerEl) {
997
+ this.popperInstance = createPopper(this.inputEl.nativeElement, this.datePickerEl.nativeElement, {
998
+ placement: "bottom-start",
999
+ modifiers: [
1000
+ { name: "offset", options: { offset: [0, 1] } },
1001
+ { name: "preventOverflow", options: { boundary: "viewport" } },
1002
+ { name: "flip", options: { fallbackPlacements: ["top-start"] } },
1003
+ ],
1004
+ });
1005
+ this.popperInstance.update();
1006
+ }
1007
+ }
1008
+ else {
1009
+ this.destroyPopper();
1010
+ }
1011
+ }
1012
+ openCalendar() {
1013
+ if (this.disabled || this.readonly || this.isOpen)
1014
+ return;
1015
+ this.toggleCalendar();
1016
+ }
1017
+ clickOutside(event) {
1018
+ if (!this.rootElement.nativeElement.contains(event.target) && this.isOpen) {
1019
+ this.isOpen = false;
1020
+ }
1021
+ }
1022
+ closeCalendar() {
1023
+ this.destroyPopper();
1024
+ this.isOpen = false;
1025
+ if (CalendarControl._currentlyOpen === this) {
1026
+ CalendarControl._currentlyOpen = undefined;
1027
+ }
1028
+ }
1029
+ clearDate(event) {
1030
+ this.selectionCleared.emit();
1031
+ event.stopPropagation();
1032
+ this.selectedDate = null;
1033
+ this.selectedHour = this.today.getHours();
1034
+ this.selectedMinute = this.today.getMinutes();
1035
+ this.inputControl.setValue("", { emitEvent: false });
1036
+ this.onChangeFn(null);
1037
+ this.onTouchedFn();
1038
+ this.dateSelected.emit(null);
1039
+ }
1040
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: CalendarControl, deps: [{ token: i1.DatePipe }], target: i0.ɵɵFactoryTarget.Component });
1041
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.3", type: CalendarControl, isStandalone: true, selector: "calendar-control", inputs: { title: "title", required: "required", customClass: "customClass", clearVal: "clearVal", deFocus: "deFocus", error: "error", errorMessage: "errorMessage", inputLoader: "inputLoader", hourFormat: "hourFormat", selectionMode: "selectionMode", timeOnly: "timeOnly", dateFormat: "dateFormat", placeholder: "placeholder", disabled: "disabled", readonly: "readonly", submitted: "submitted", inputPlaceholder: "inputPlaceholder", closeVal: ["close-val", "closeVal"], showTime: "showTime", minDate: "minDate", maxDate: "maxDate" }, outputs: { selectionCleared: "selectionCleared", blurEvent: "blurEvent", dateSelected: "dateSelected" }, host: { listeners: { "document:click": "clickOutside($event)" } }, providers: [
1042
+ DatePipe,
1043
+ {
1044
+ provide: NG_VALUE_ACCESSOR,
1045
+ useExisting: forwardRef(() => CalendarControl),
1046
+ multi: true,
1047
+ },
1048
+ ], viewQueries: [{ propertyName: "rootElement", first: true, predicate: ["root"], descendants: true, static: true }, { propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, static: true }, { propertyName: "datePickerEl", first: true, predicate: ["datePicker"], descendants: true }], ngImport: i0, template: "<div class=\"form-group calendar\" [ngClass]=\"customClass\">\r\n <label class=\"inp-label\" [ngClass]=\"{'required' : required}\" *ngIf=\"title\">{{ title }}</label>\r\n <div class=\"form-group calendar\" #root (click)=\"openCalendar()\" >\r\n <input type=\"text\" #inputEl [placeholder]=\"(inputPlaceholder && placeholder) ? placeholder : ''\"\r\n [formControl]=\"inputControl\" class=\"form-control\" [ngClass]=\"{ 'is-invalid': error }\" (blur)=\"onBlur()\"\r\n (focus)=\"onFocus()\" [readonly]=\"readonly\" (click)=\"openCalendar(); $event.stopPropagation()\" (keydown)=\"onInputKeydown($event)\" />\r\n\r\n <span class=\"focus-border\" *ngIf=\"deFocus\"></span>\r\n <span class=\"calendar-icon\">\r\n <i class=\"he\" [ngClass]=\"!timeOnly ? 'he-calendar-blank' : 'he-clock'\"></i>\r\n </span>\r\n <label class=\"clear\" *ngIf=\"!inputLoader && (selectedDate && !disabled && !readonly) && clearVal\" (click)=\"clearDate($event)\">\r\n <i class=\"he he-close\"></i>\r\n </label>\r\n <label *ngIf=\"inputLoader\" class=\"loader input-loader\"></label>\r\n <div *ngIf=\"error\" class=\"val-msg\">{{ errorMessage }}</div>\r\n\r\n <div class=\"datepicker-group\" #datePicker *ngIf=\"isOpen\" (click)=\"$event.stopPropagation()\">\r\n \r\n <!-- time picker -->\r\n <ng-container *ngIf=\"timeOnly\">\r\n <div class=\"time-picker\">\r\n <div class=\"time-select\">\r\n <button (click)=\"incrementHour()\"><i class=\"he he-chevron-up\"></i></button>\r\n <ng-container *ngIf=\"hourFormat === '12'; else show24\">\r\n <div class=\"time-value\">\r\n {{ ((selectedHour % 12) || 12) | number:'2.0' }}\r\n </div>\r\n </ng-container>\r\n <ng-template #show24>\r\n <div class=\"time-value\">\r\n {{ selectedHour | number:'2.0' }}\r\n </div>\r\n </ng-template>\r\n\r\n <button (click)=\"decrementHour()\"><i class=\"he he-chevron-down\"></i></button>\r\n </div>\r\n <span class=\"time-separator\">:</span>\r\n <div class=\"time-select\">\r\n <button (click)=\"incrementMinute()\"><i class=\"he he-chevron-up\"></i></button>\r\n <div class=\"time-value\">{{ selectedMinute | number:'2.0' }}</div>\r\n <button (click)=\"decrementMinute()\"><i class=\"he he-chevron-down\"></i></button>\r\n </div>\r\n <div class=\"ampm-toggle\" *ngIf=\"hourFormat === '12'\">\r\n <button type=\"button\" [class.active]=\"meridian === 'AM'\" (click)=\"setMeridian('AM')\">AM</button>\r\n <button type=\"button\" [class.active]=\"meridian === 'PM'\" (click)=\"setMeridian('PM')\">PM</button>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- day view -->\r\n <ng-container *ngIf=\"!timeOnly && currentView === 'day'\">\r\n <div class=\"header\">\r\n <button class=\"calendar-arrow\" (click)=\"prevMonth()\"><i class=\"he he-chevron-left\"></i></button>\r\n <div class=\"title\" (click)=\"goToMonthView()\">\r\n <div>{{ displayMonthName }}</div>\r\n <div>{{ displayYear }}</div>\r\n </div>\r\n <button class=\"calendar-arrow\" (click)=\"nextMonth()\"><i class=\"he he-chevron-right\"></i></button>\r\n </div>\r\n <div class=\"week-header\">\r\n <div>Sun</div>\r\n <div>Mon</div>\r\n <div>Tue</div>\r\n <div>Wed</div>\r\n <div>Thu</div>\r\n <div>Fri</div>\r\n <div>Sat</div>\r\n </div>\r\n <div class=\"days-grid\">\r\n <div class=\"day-cell\" *ngFor=\"let day of daysInMonth\" (click)=\"selectDay(day)\"\r\n [class.disabled]=\"day !== null && dayClassMap[day].disabled\"\r\n [class.selected]=\"day !== null && dayClassMap[day].selected\" [class.today]=\"\r\n day !== null &&\r\n displayYear === todayYear &&\r\n displayMonth === todayMonth &&\r\n day === todayDate\r\n \">\r\n {{day ? day : ''}}\r\n </div>\r\n </div>\r\n <ng-container *ngIf=\"showTime\">\r\n <div class=\"time-picker\">\r\n <div class=\"time-select\">\r\n <button type=\"button\" (click)=\"incrementHour()\">\r\n <i class=\"he he-chevron-up\"></i>\r\n </button>\r\n <ng-container *ngIf=\"hourFormat === '12'; else show24\">\r\n <div class=\"time-value\">\r\n {{\r\n selectedHour % 12 === 0\r\n ? 12\r\n : selectedHour % 12\r\n | number:'2.0' }}\r\n </div>\r\n </ng-container>\r\n <ng-template #show24>\r\n <div class=\"time-value\">{{ selectedHour | number:'2.0' }}</div>\r\n </ng-template>\r\n <button type=\"button\" (click)=\"decrementHour()\">\r\n <i class=\"he he-chevron-down\"></i>\r\n </button>\r\n </div>\r\n <span class=\"time-separator\">:</span>\r\n <div class=\"time-select\">\r\n <button type=\"button\" (click)=\"incrementMinute()\">\r\n <i class=\"he he-chevron-up\"></i>\r\n </button>\r\n <div class=\"time-value\">\r\n {{ selectedMinute < 10 ? '0' +selectedMinute : selectedMinute }} </div>\r\n <button type=\"button\" (click)=\"decrementMinute()\">\r\n <i class=\"he he-chevron-down\"></i>\r\n </button>\r\n </div>\r\n <div class=\"ampm-toggle\" *ngIf=\"hourFormat === '12'\">\r\n <button type=\"button\" [class.active]=\"meridian === 'AM'\" (click)=\"setMeridian('AM')\">AM</button>\r\n <button type=\"button\" [class.active]=\"meridian === 'PM'\" (click)=\"setMeridian('PM')\">PM</button>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- month view -->\r\n <ng-container *ngIf=\"!timeOnly && currentView === 'month'\">\r\n <div class=\"header\">\r\n <button class=\"calendar-arrow\" (click)=\"displayYear = displayYear - 1\"><i\r\n class=\"he he-chevron-left\"></i></button>\r\n <div class=\"title\" (click)=\"goToYearRangeView()\">{{ displayYear }}</div>\r\n <button class=\"calendar-arrow\" (click)=\"displayYear = displayYear + 1\"><i\r\n class=\"he he-chevron-right\"></i></button>\r\n </div>\r\n <div class=\"month-grid\">\r\n <div class=\"month-cell\" *ngFor=\"let m of months; index as i\" (click)=\"selectMonth(i)\">{{ m }}\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- year range view -->\r\n <ng-container *ngIf=\"!timeOnly && currentView === 'yearRange'\">\r\n <div class=\"header\">\r\n <button class=\"calendar-arrow\" (click)=\"prevYearRange()\"><i class=\"he he-chevron-left\"></i></button>\r\n <div class=\"title\">{{ yearRange[0] }} ~ {{ yearRange[yearRangeSize-1] }}</div>\r\n <button class=\"calendar-arrow\" (click)=\"nextYearRange()\"><i class=\"he he-chevron-right\"></i></button>\r\n </div>\r\n <div class=\"year-grid\">\r\n <div class=\"year-cell\" *ngFor=\"let y of yearRange\" (click)=\"selectYear(y)\">{{ y }}</div>\r\n </div>\r\n </ng-container>\r\n\r\n </div>\r\n </div>\r\n</div>", styles: [".form-group.calendar{position:relative}.form-group.calendar .clear{top:9px;right:34px}.form-group.calendar .datepicker-group{left:0;width:100%;z-index:1000;padding:.5rem;color:#495057;min-width:240px;border-radius:3px;background:#fff;box-shadow:0 2px 4px -1px #0003,0 4px 5px #00000024,0 1px 10px #0000001f}.form-group.calendar .datepicker-group .header{display:flex;font-weight:600;padding:5px;align-items:center;justify-content:space-between;border-bottom:1px solid #dee2e6}.form-group.calendar .datepicker-group .header .title{gap:8px;display:flex;font-size:15px;cursor:pointer;font-weight:600}.form-group.calendar .datepicker-group .header .calendar-arrow{width:28px;height:28px;border:none;line-height:1;cursor:pointer;background:none}.form-group.calendar .datepicker-group .header .calendar-arrow i{font-size:14px}.form-group.calendar .datepicker-group .header .calendar-arrow:hover{background:#f0f0f0}.form-group.calendar .datepicker-group .week-header{display:grid;padding:8px 0;font-size:13px;font-weight:700;text-align:center;grid-template-columns:repeat(7,1fr)}.form-group.calendar .datepicker-group .days-grid,.form-group.calendar .datepicker-group .month-grid,.form-group.calendar .datepicker-group .year-grid{gap:3px;padding:5px;display:grid;grid-template-columns:repeat(7,1fr)}.form-group.calendar .datepicker-group .days-grid .day-cell,.form-group.calendar .datepicker-group .days-grid .month-cell,.form-group.calendar .datepicker-group .days-grid .year-cell,.form-group.calendar .datepicker-group .month-grid .day-cell,.form-group.calendar .datepicker-group .month-grid .month-cell,.form-group.calendar .datepicker-group .month-grid .year-cell,.form-group.calendar .datepicker-group .year-grid .day-cell,.form-group.calendar .datepicker-group .year-grid .month-cell,.form-group.calendar .datepicker-group .year-grid .year-cell{display:flex;font-size:14px;cursor:pointer;align-items:center;justify-content:center}.form-group.calendar .datepicker-group .days-grid .day-cell.today,.form-group.calendar .datepicker-group .days-grid .month-cell.today,.form-group.calendar .datepicker-group .days-grid .year-cell.today,.form-group.calendar .datepicker-group .month-grid .day-cell.today,.form-group.calendar .datepicker-group .month-grid .month-cell.today,.form-group.calendar .datepicker-group .month-grid .year-cell.today,.form-group.calendar .datepicker-group .year-grid .day-cell.today,.form-group.calendar .datepicker-group .year-grid .month-cell.today,.form-group.calendar .datepicker-group .year-grid .year-cell.today{color:#37c0b3;font-weight:700;border:1px solid #37c0b3}.form-group.calendar .datepicker-group .days-grid .day-cell.selected,.form-group.calendar .datepicker-group .days-grid .day-cell:hover,.form-group.calendar .datepicker-group .days-grid .month-cell.selected,.form-group.calendar .datepicker-group .days-grid .month-cell:hover,.form-group.calendar .datepicker-group .days-grid .year-cell.selected,.form-group.calendar .datepicker-group .days-grid .year-cell:hover,.form-group.calendar .datepicker-group .month-grid .day-cell.selected,.form-group.calendar .datepicker-group .month-grid .day-cell:hover,.form-group.calendar .datepicker-group .month-grid .month-cell.selected,.form-group.calendar .datepicker-group .month-grid .month-cell:hover,.form-group.calendar .datepicker-group .month-grid .year-cell.selected,.form-group.calendar .datepicker-group .month-grid .year-cell:hover,.form-group.calendar .datepicker-group .year-grid .day-cell.selected,.form-group.calendar .datepicker-group .year-grid .day-cell:hover,.form-group.calendar .datepicker-group .year-grid .month-cell.selected,.form-group.calendar .datepicker-group .year-grid .month-cell:hover,.form-group.calendar .datepicker-group .year-grid .year-cell.selected,.form-group.calendar .datepicker-group .year-grid .year-cell:hover{color:#fff;background:#37c0b3}.form-group.calendar .datepicker-group .days-grid .day-cell.disabled,.form-group.calendar .datepicker-group .days-grid .month-cell.disabled,.form-group.calendar .datepicker-group .days-grid .year-cell.disabled,.form-group.calendar .datepicker-group .month-grid .day-cell.disabled,.form-group.calendar .datepicker-group .month-grid .month-cell.disabled,.form-group.calendar .datepicker-group .month-grid .year-cell.disabled,.form-group.calendar .datepicker-group .year-grid .day-cell.disabled,.form-group.calendar .datepicker-group .year-grid .month-cell.disabled,.form-group.calendar .datepicker-group .year-grid .year-cell.disabled{color:#e0e0e0;background:none;-webkit-user-select:none;user-select:none;cursor:not-allowed}.form-group.calendar .datepicker-group .days-grid .day-cell.disabled.today,.form-group.calendar .datepicker-group .days-grid .month-cell.disabled.today,.form-group.calendar .datepicker-group .days-grid .year-cell.disabled.today,.form-group.calendar .datepicker-group .month-grid .day-cell.disabled.today,.form-group.calendar .datepicker-group .month-grid .month-cell.disabled.today,.form-group.calendar .datepicker-group .month-grid .year-cell.disabled.today,.form-group.calendar .datepicker-group .year-grid .day-cell.disabled.today,.form-group.calendar .datepicker-group .year-grid .month-cell.disabled.today,.form-group.calendar .datepicker-group .year-grid .year-cell.disabled.today{color:#37c0b3}.form-group.calendar .datepicker-group .days-grid .day-cell.disabled.today:hover,.form-group.calendar .datepicker-group .days-grid .month-cell.disabled.today:hover,.form-group.calendar .datepicker-group .days-grid .year-cell.disabled.today:hover,.form-group.calendar .datepicker-group .month-grid .day-cell.disabled.today:hover,.form-group.calendar .datepicker-group .month-grid .month-cell.disabled.today:hover,.form-group.calendar .datepicker-group .month-grid .year-cell.disabled.today:hover,.form-group.calendar .datepicker-group .year-grid .day-cell.disabled.today:hover,.form-group.calendar .datepicker-group .year-grid .month-cell.disabled.today:hover,.form-group.calendar .datepicker-group .year-grid .year-cell.disabled.today:hover{color:#fff;background:#37c0b3}.form-group.calendar .datepicker-group .days-grid .day-cell.disabled:hover,.form-group.calendar .datepicker-group .days-grid .month-cell.disabled:hover,.form-group.calendar .datepicker-group .days-grid .year-cell.disabled:hover,.form-group.calendar .datepicker-group .month-grid .day-cell.disabled:hover,.form-group.calendar .datepicker-group .month-grid .month-cell.disabled:hover,.form-group.calendar .datepicker-group .month-grid .year-cell.disabled:hover,.form-group.calendar .datepicker-group .year-grid .day-cell.disabled:hover,.form-group.calendar .datepicker-group .year-grid .month-cell.disabled:hover,.form-group.calendar .datepicker-group .year-grid .year-cell.disabled:hover{color:#e0e0e0;background:transparent}.form-group.calendar .datepicker-group .days-grid .day-cell,.form-group.calendar .datepicker-group .month-grid .day-cell,.form-group.calendar .datepicker-group .year-grid .day-cell{padding:5px 2px;border-radius:50%}.form-group.calendar .datepicker-group .days-grid .month-cell,.form-group.calendar .datepicker-group .days-grid .year-cell,.form-group.calendar .datepicker-group .month-grid .month-cell,.form-group.calendar .datepicker-group .month-grid .year-cell,.form-group.calendar .datepicker-group .year-grid .month-cell,.form-group.calendar .datepicker-group .year-grid .year-cell{padding:8px 2px;border-radius:3px}.form-group.calendar .datepicker-group .month-grid{grid-template-columns:repeat(3,1fr)}.form-group.calendar .datepicker-group .year-grid{grid-template-columns:repeat(4,1fr)}.form-group.calendar .datepicker-group .time-picker{display:flex;-webkit-user-select:none;user-select:none;align-items:center;justify-content:center}.form-group.calendar .datepicker-group .time-picker .ampm-toggle{gap:6px;display:flex;margin-left:1rem;flex-direction:column;justify-content:center}.form-group.calendar .datepicker-group .time-picker .ampm-toggle button{cursor:pointer;padding:3px 8px;font-size:.75rem;background:#fff;border:1px solid #cccccc}.form-group.calendar .datepicker-group .time-picker .ampm-toggle button.active{color:#fff;border-color:#37c0b3;background-color:#37c0b3}.form-group.calendar .datepicker-group .time-picker .time-select{display:flex;margin:0 .75rem;align-items:center;flex-direction:column}.form-group.calendar .datepicker-group .time-picker .time-select button{border:none;cursor:pointer;padding:4px 8px;background:transparent}.form-group.calendar .datepicker-group .time-picker .time-select button i{font-size:1rem}.form-group.calendar .datepicker-group .time-picker .time-select button:hover{background:#f0f0f0}.form-group.calendar .datepicker-group .time-picker .time-select .time-value{width:2rem;font-size:1rem;text-align:center;margin:2px 3px 4px}.form-group.calendar .datepicker-group .time-picker .time-separator{line-height:1;font-size:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }] });
1049
+ }
1050
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: CalendarControl, decorators: [{
1051
+ type: Component,
1052
+ args: [{ selector: "calendar-control", standalone: true, imports: [
1053
+ CommonModule,
1054
+ ReactiveFormsModule,
1055
+ FormsModule
1056
+ ], providers: [
1057
+ DatePipe,
1058
+ {
1059
+ provide: NG_VALUE_ACCESSOR,
1060
+ useExisting: forwardRef(() => CalendarControl),
1061
+ multi: true,
1062
+ },
1063
+ ], template: "<div class=\"form-group calendar\" [ngClass]=\"customClass\">\r\n <label class=\"inp-label\" [ngClass]=\"{'required' : required}\" *ngIf=\"title\">{{ title }}</label>\r\n <div class=\"form-group calendar\" #root (click)=\"openCalendar()\" >\r\n <input type=\"text\" #inputEl [placeholder]=\"(inputPlaceholder && placeholder) ? placeholder : ''\"\r\n [formControl]=\"inputControl\" class=\"form-control\" [ngClass]=\"{ 'is-invalid': error }\" (blur)=\"onBlur()\"\r\n (focus)=\"onFocus()\" [readonly]=\"readonly\" (click)=\"openCalendar(); $event.stopPropagation()\" (keydown)=\"onInputKeydown($event)\" />\r\n\r\n <span class=\"focus-border\" *ngIf=\"deFocus\"></span>\r\n <span class=\"calendar-icon\">\r\n <i class=\"he\" [ngClass]=\"!timeOnly ? 'he-calendar-blank' : 'he-clock'\"></i>\r\n </span>\r\n <label class=\"clear\" *ngIf=\"!inputLoader && (selectedDate && !disabled && !readonly) && clearVal\" (click)=\"clearDate($event)\">\r\n <i class=\"he he-close\"></i>\r\n </label>\r\n <label *ngIf=\"inputLoader\" class=\"loader input-loader\"></label>\r\n <div *ngIf=\"error\" class=\"val-msg\">{{ errorMessage }}</div>\r\n\r\n <div class=\"datepicker-group\" #datePicker *ngIf=\"isOpen\" (click)=\"$event.stopPropagation()\">\r\n \r\n <!-- time picker -->\r\n <ng-container *ngIf=\"timeOnly\">\r\n <div class=\"time-picker\">\r\n <div class=\"time-select\">\r\n <button (click)=\"incrementHour()\"><i class=\"he he-chevron-up\"></i></button>\r\n <ng-container *ngIf=\"hourFormat === '12'; else show24\">\r\n <div class=\"time-value\">\r\n {{ ((selectedHour % 12) || 12) | number:'2.0' }}\r\n </div>\r\n </ng-container>\r\n <ng-template #show24>\r\n <div class=\"time-value\">\r\n {{ selectedHour | number:'2.0' }}\r\n </div>\r\n </ng-template>\r\n\r\n <button (click)=\"decrementHour()\"><i class=\"he he-chevron-down\"></i></button>\r\n </div>\r\n <span class=\"time-separator\">:</span>\r\n <div class=\"time-select\">\r\n <button (click)=\"incrementMinute()\"><i class=\"he he-chevron-up\"></i></button>\r\n <div class=\"time-value\">{{ selectedMinute | number:'2.0' }}</div>\r\n <button (click)=\"decrementMinute()\"><i class=\"he he-chevron-down\"></i></button>\r\n </div>\r\n <div class=\"ampm-toggle\" *ngIf=\"hourFormat === '12'\">\r\n <button type=\"button\" [class.active]=\"meridian === 'AM'\" (click)=\"setMeridian('AM')\">AM</button>\r\n <button type=\"button\" [class.active]=\"meridian === 'PM'\" (click)=\"setMeridian('PM')\">PM</button>\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- day view -->\r\n <ng-container *ngIf=\"!timeOnly && currentView === 'day'\">\r\n <div class=\"header\">\r\n <button class=\"calendar-arrow\" (click)=\"prevMonth()\"><i class=\"he he-chevron-left\"></i></button>\r\n <div class=\"title\" (click)=\"goToMonthView()\">\r\n <div>{{ displayMonthName }}</div>\r\n <div>{{ displayYear }}</div>\r\n </div>\r\n <button class=\"calendar-arrow\" (click)=\"nextMonth()\"><i class=\"he he-chevron-right\"></i></button>\r\n </div>\r\n <div class=\"week-header\">\r\n <div>Sun</div>\r\n <div>Mon</div>\r\n <div>Tue</div>\r\n <div>Wed</div>\r\n <div>Thu</div>\r\n <div>Fri</div>\r\n <div>Sat</div>\r\n </div>\r\n <div class=\"days-grid\">\r\n <div class=\"day-cell\" *ngFor=\"let day of daysInMonth\" (click)=\"selectDay(day)\"\r\n [class.disabled]=\"day !== null && dayClassMap[day].disabled\"\r\n [class.selected]=\"day !== null && dayClassMap[day].selected\" [class.today]=\"\r\n day !== null &&\r\n displayYear === todayYear &&\r\n displayMonth === todayMonth &&\r\n day === todayDate\r\n \">\r\n {{day ? day : ''}}\r\n </div>\r\n </div>\r\n <ng-container *ngIf=\"showTime\">\r\n <div class=\"time-picker\">\r\n <div class=\"time-select\">\r\n <button type=\"button\" (click)=\"incrementHour()\">\r\n <i class=\"he he-chevron-up\"></i>\r\n </button>\r\n <ng-container *ngIf=\"hourFormat === '12'; else show24\">\r\n <div class=\"time-value\">\r\n {{\r\n selectedHour % 12 === 0\r\n ? 12\r\n : selectedHour % 12\r\n | number:'2.0' }}\r\n </div>\r\n </ng-container>\r\n <ng-template #show24>\r\n <div class=\"time-value\">{{ selectedHour | number:'2.0' }}</div>\r\n </ng-template>\r\n <button type=\"button\" (click)=\"decrementHour()\">\r\n <i class=\"he he-chevron-down\"></i>\r\n </button>\r\n </div>\r\n <span class=\"time-separator\">:</span>\r\n <div class=\"time-select\">\r\n <button type=\"button\" (click)=\"incrementMinute()\">\r\n <i class=\"he he-chevron-up\"></i>\r\n </button>\r\n <div class=\"time-value\">\r\n {{ selectedMinute < 10 ? '0' +selectedMinute : selectedMinute }} </div>\r\n <button type=\"button\" (click)=\"decrementMinute()\">\r\n <i class=\"he he-chevron-down\"></i>\r\n </button>\r\n </div>\r\n <div class=\"ampm-toggle\" *ngIf=\"hourFormat === '12'\">\r\n <button type=\"button\" [class.active]=\"meridian === 'AM'\" (click)=\"setMeridian('AM')\">AM</button>\r\n <button type=\"button\" [class.active]=\"meridian === 'PM'\" (click)=\"setMeridian('PM')\">PM</button>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- month view -->\r\n <ng-container *ngIf=\"!timeOnly && currentView === 'month'\">\r\n <div class=\"header\">\r\n <button class=\"calendar-arrow\" (click)=\"displayYear = displayYear - 1\"><i\r\n class=\"he he-chevron-left\"></i></button>\r\n <div class=\"title\" (click)=\"goToYearRangeView()\">{{ displayYear }}</div>\r\n <button class=\"calendar-arrow\" (click)=\"displayYear = displayYear + 1\"><i\r\n class=\"he he-chevron-right\"></i></button>\r\n </div>\r\n <div class=\"month-grid\">\r\n <div class=\"month-cell\" *ngFor=\"let m of months; index as i\" (click)=\"selectMonth(i)\">{{ m }}\r\n </div>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- year range view -->\r\n <ng-container *ngIf=\"!timeOnly && currentView === 'yearRange'\">\r\n <div class=\"header\">\r\n <button class=\"calendar-arrow\" (click)=\"prevYearRange()\"><i class=\"he he-chevron-left\"></i></button>\r\n <div class=\"title\">{{ yearRange[0] }} ~ {{ yearRange[yearRangeSize-1] }}</div>\r\n <button class=\"calendar-arrow\" (click)=\"nextYearRange()\"><i class=\"he he-chevron-right\"></i></button>\r\n </div>\r\n <div class=\"year-grid\">\r\n <div class=\"year-cell\" *ngFor=\"let y of yearRange\" (click)=\"selectYear(y)\">{{ y }}</div>\r\n </div>\r\n </ng-container>\r\n\r\n </div>\r\n </div>\r\n</div>", styles: [".form-group.calendar{position:relative}.form-group.calendar .clear{top:9px;right:34px}.form-group.calendar .datepicker-group{left:0;width:100%;z-index:1000;padding:.5rem;color:#495057;min-width:240px;border-radius:3px;background:#fff;box-shadow:0 2px 4px -1px #0003,0 4px 5px #00000024,0 1px 10px #0000001f}.form-group.calendar .datepicker-group .header{display:flex;font-weight:600;padding:5px;align-items:center;justify-content:space-between;border-bottom:1px solid #dee2e6}.form-group.calendar .datepicker-group .header .title{gap:8px;display:flex;font-size:15px;cursor:pointer;font-weight:600}.form-group.calendar .datepicker-group .header .calendar-arrow{width:28px;height:28px;border:none;line-height:1;cursor:pointer;background:none}.form-group.calendar .datepicker-group .header .calendar-arrow i{font-size:14px}.form-group.calendar .datepicker-group .header .calendar-arrow:hover{background:#f0f0f0}.form-group.calendar .datepicker-group .week-header{display:grid;padding:8px 0;font-size:13px;font-weight:700;text-align:center;grid-template-columns:repeat(7,1fr)}.form-group.calendar .datepicker-group .days-grid,.form-group.calendar .datepicker-group .month-grid,.form-group.calendar .datepicker-group .year-grid{gap:3px;padding:5px;display:grid;grid-template-columns:repeat(7,1fr)}.form-group.calendar .datepicker-group .days-grid .day-cell,.form-group.calendar .datepicker-group .days-grid .month-cell,.form-group.calendar .datepicker-group .days-grid .year-cell,.form-group.calendar .datepicker-group .month-grid .day-cell,.form-group.calendar .datepicker-group .month-grid .month-cell,.form-group.calendar .datepicker-group .month-grid .year-cell,.form-group.calendar .datepicker-group .year-grid .day-cell,.form-group.calendar .datepicker-group .year-grid .month-cell,.form-group.calendar .datepicker-group .year-grid .year-cell{display:flex;font-size:14px;cursor:pointer;align-items:center;justify-content:center}.form-group.calendar .datepicker-group .days-grid .day-cell.today,.form-group.calendar .datepicker-group .days-grid .month-cell.today,.form-group.calendar .datepicker-group .days-grid .year-cell.today,.form-group.calendar .datepicker-group .month-grid .day-cell.today,.form-group.calendar .datepicker-group .month-grid .month-cell.today,.form-group.calendar .datepicker-group .month-grid .year-cell.today,.form-group.calendar .datepicker-group .year-grid .day-cell.today,.form-group.calendar .datepicker-group .year-grid .month-cell.today,.form-group.calendar .datepicker-group .year-grid .year-cell.today{color:#37c0b3;font-weight:700;border:1px solid #37c0b3}.form-group.calendar .datepicker-group .days-grid .day-cell.selected,.form-group.calendar .datepicker-group .days-grid .day-cell:hover,.form-group.calendar .datepicker-group .days-grid .month-cell.selected,.form-group.calendar .datepicker-group .days-grid .month-cell:hover,.form-group.calendar .datepicker-group .days-grid .year-cell.selected,.form-group.calendar .datepicker-group .days-grid .year-cell:hover,.form-group.calendar .datepicker-group .month-grid .day-cell.selected,.form-group.calendar .datepicker-group .month-grid .day-cell:hover,.form-group.calendar .datepicker-group .month-grid .month-cell.selected,.form-group.calendar .datepicker-group .month-grid .month-cell:hover,.form-group.calendar .datepicker-group .month-grid .year-cell.selected,.form-group.calendar .datepicker-group .month-grid .year-cell:hover,.form-group.calendar .datepicker-group .year-grid .day-cell.selected,.form-group.calendar .datepicker-group .year-grid .day-cell:hover,.form-group.calendar .datepicker-group .year-grid .month-cell.selected,.form-group.calendar .datepicker-group .year-grid .month-cell:hover,.form-group.calendar .datepicker-group .year-grid .year-cell.selected,.form-group.calendar .datepicker-group .year-grid .year-cell:hover{color:#fff;background:#37c0b3}.form-group.calendar .datepicker-group .days-grid .day-cell.disabled,.form-group.calendar .datepicker-group .days-grid .month-cell.disabled,.form-group.calendar .datepicker-group .days-grid .year-cell.disabled,.form-group.calendar .datepicker-group .month-grid .day-cell.disabled,.form-group.calendar .datepicker-group .month-grid .month-cell.disabled,.form-group.calendar .datepicker-group .month-grid .year-cell.disabled,.form-group.calendar .datepicker-group .year-grid .day-cell.disabled,.form-group.calendar .datepicker-group .year-grid .month-cell.disabled,.form-group.calendar .datepicker-group .year-grid .year-cell.disabled{color:#e0e0e0;background:none;-webkit-user-select:none;user-select:none;cursor:not-allowed}.form-group.calendar .datepicker-group .days-grid .day-cell.disabled.today,.form-group.calendar .datepicker-group .days-grid .month-cell.disabled.today,.form-group.calendar .datepicker-group .days-grid .year-cell.disabled.today,.form-group.calendar .datepicker-group .month-grid .day-cell.disabled.today,.form-group.calendar .datepicker-group .month-grid .month-cell.disabled.today,.form-group.calendar .datepicker-group .month-grid .year-cell.disabled.today,.form-group.calendar .datepicker-group .year-grid .day-cell.disabled.today,.form-group.calendar .datepicker-group .year-grid .month-cell.disabled.today,.form-group.calendar .datepicker-group .year-grid .year-cell.disabled.today{color:#37c0b3}.form-group.calendar .datepicker-group .days-grid .day-cell.disabled.today:hover,.form-group.calendar .datepicker-group .days-grid .month-cell.disabled.today:hover,.form-group.calendar .datepicker-group .days-grid .year-cell.disabled.today:hover,.form-group.calendar .datepicker-group .month-grid .day-cell.disabled.today:hover,.form-group.calendar .datepicker-group .month-grid .month-cell.disabled.today:hover,.form-group.calendar .datepicker-group .month-grid .year-cell.disabled.today:hover,.form-group.calendar .datepicker-group .year-grid .day-cell.disabled.today:hover,.form-group.calendar .datepicker-group .year-grid .month-cell.disabled.today:hover,.form-group.calendar .datepicker-group .year-grid .year-cell.disabled.today:hover{color:#fff;background:#37c0b3}.form-group.calendar .datepicker-group .days-grid .day-cell.disabled:hover,.form-group.calendar .datepicker-group .days-grid .month-cell.disabled:hover,.form-group.calendar .datepicker-group .days-grid .year-cell.disabled:hover,.form-group.calendar .datepicker-group .month-grid .day-cell.disabled:hover,.form-group.calendar .datepicker-group .month-grid .month-cell.disabled:hover,.form-group.calendar .datepicker-group .month-grid .year-cell.disabled:hover,.form-group.calendar .datepicker-group .year-grid .day-cell.disabled:hover,.form-group.calendar .datepicker-group .year-grid .month-cell.disabled:hover,.form-group.calendar .datepicker-group .year-grid .year-cell.disabled:hover{color:#e0e0e0;background:transparent}.form-group.calendar .datepicker-group .days-grid .day-cell,.form-group.calendar .datepicker-group .month-grid .day-cell,.form-group.calendar .datepicker-group .year-grid .day-cell{padding:5px 2px;border-radius:50%}.form-group.calendar .datepicker-group .days-grid .month-cell,.form-group.calendar .datepicker-group .days-grid .year-cell,.form-group.calendar .datepicker-group .month-grid .month-cell,.form-group.calendar .datepicker-group .month-grid .year-cell,.form-group.calendar .datepicker-group .year-grid .month-cell,.form-group.calendar .datepicker-group .year-grid .year-cell{padding:8px 2px;border-radius:3px}.form-group.calendar .datepicker-group .month-grid{grid-template-columns:repeat(3,1fr)}.form-group.calendar .datepicker-group .year-grid{grid-template-columns:repeat(4,1fr)}.form-group.calendar .datepicker-group .time-picker{display:flex;-webkit-user-select:none;user-select:none;align-items:center;justify-content:center}.form-group.calendar .datepicker-group .time-picker .ampm-toggle{gap:6px;display:flex;margin-left:1rem;flex-direction:column;justify-content:center}.form-group.calendar .datepicker-group .time-picker .ampm-toggle button{cursor:pointer;padding:3px 8px;font-size:.75rem;background:#fff;border:1px solid #cccccc}.form-group.calendar .datepicker-group .time-picker .ampm-toggle button.active{color:#fff;border-color:#37c0b3;background-color:#37c0b3}.form-group.calendar .datepicker-group .time-picker .time-select{display:flex;margin:0 .75rem;align-items:center;flex-direction:column}.form-group.calendar .datepicker-group .time-picker .time-select button{border:none;cursor:pointer;padding:4px 8px;background:transparent}.form-group.calendar .datepicker-group .time-picker .time-select button i{font-size:1rem}.form-group.calendar .datepicker-group .time-picker .time-select button:hover{background:#f0f0f0}.form-group.calendar .datepicker-group .time-picker .time-select .time-value{width:2rem;font-size:1rem;text-align:center;margin:2px 3px 4px}.form-group.calendar .datepicker-group .time-picker .time-separator{line-height:1;font-size:1rem}\n"] }]
1064
+ }], ctorParameters: () => [{ type: i1.DatePipe }], propDecorators: { title: [{
1065
+ type: Input
1066
+ }], required: [{
1067
+ type: Input
1068
+ }], customClass: [{
1069
+ type: Input
1070
+ }], clearVal: [{
1071
+ type: Input
1072
+ }], deFocus: [{
1073
+ type: Input
1074
+ }], error: [{
1075
+ type: Input
1076
+ }], errorMessage: [{
1077
+ type: Input
1078
+ }], inputLoader: [{
1079
+ type: Input
1080
+ }], hourFormat: [{
1081
+ type: Input
1082
+ }], selectionMode: [{
1083
+ type: Input
1084
+ }], timeOnly: [{
1085
+ type: Input
1086
+ }], dateFormat: [{
1087
+ type: Input
1088
+ }], placeholder: [{
1089
+ type: Input
1090
+ }], disabled: [{
1091
+ type: Input
1092
+ }], readonly: [{
1093
+ type: Input
1094
+ }], submitted: [{
1095
+ type: Input
1096
+ }], inputPlaceholder: [{
1097
+ type: Input
1098
+ }], closeVal: [{
1099
+ type: Input,
1100
+ args: ["close-val"]
1101
+ }], showTime: [{
1102
+ type: Input
1103
+ }], selectionCleared: [{
1104
+ type: Output
1105
+ }], blurEvent: [{
1106
+ type: Output
1107
+ }], dateSelected: [{
1108
+ type: Output
1109
+ }], minDate: [{
1110
+ type: Input
1111
+ }], maxDate: [{
1112
+ type: Input
1113
+ }], rootElement: [{
1114
+ type: ViewChild,
1115
+ args: ["root", { static: true }]
1116
+ }], inputEl: [{
1117
+ type: ViewChild,
1118
+ args: ["inputEl", { static: true }]
1119
+ }], datePickerEl: [{
1120
+ type: ViewChild,
1121
+ args: ["datePicker", { static: false }]
1122
+ }], clickOutside: [{
1123
+ type: HostListener,
1124
+ args: ["document:click", ["$event"]]
1125
+ }] } });
1126
+
1127
+ class CheckboxControl {
1128
+ _renderer;
1129
+ _elementRef;
1130
+ title;
1131
+ customClass;
1132
+ type = 'boolean';
1133
+ readonly = false;
1134
+ set disabled(value) { this._disabled = value; this.setDisabledState(value); }
1135
+ checkboxChange = new EventEmitter();
1136
+ blurEvent = new EventEmitter();
1137
+ checkboxContainer;
1138
+ _disabled = false;
1139
+ checkboxControl = new FormControl({ value: false, disabled: this._disabled });
1140
+ onChange = () => { };
1141
+ onTouched = () => { };
1142
+ constructor(_renderer, _elementRef) {
1143
+ this._renderer = _renderer;
1144
+ this._elementRef = _elementRef;
1145
+ this.checkboxControl.valueChanges.subscribe(value => {
1146
+ if (this.readonly)
1147
+ return;
1148
+ const converted = this.convertType(value);
1149
+ this.onChange(converted);
1150
+ this.onTouched();
1151
+ this.checkboxChange.emit(converted);
1152
+ });
1153
+ }
1154
+ writeValue(value) {
1155
+ if (value !== undefined && value !== null) {
1156
+ this.checkboxControl.setValue(this.convertType(value), { emitEvent: false });
1157
+ }
1158
+ }
1159
+ registerOnChange(fn) {
1160
+ this.onChange = fn;
1161
+ }
1162
+ registerOnTouched(fn) {
1163
+ this.onTouched = fn;
1164
+ }
1165
+ convertType(value) {
1166
+ switch (this.type) {
1167
+ case 'number':
1168
+ return Number(value == 2 ? 0 : value == 0 ? 2 : value);
1169
+ case 'string':
1170
+ return String(value);
1171
+ default:
1172
+ return !!value;
1173
+ }
1174
+ }
1175
+ // events
1176
+ onFocus(event) {
1177
+ const targetElement = event.target;
1178
+ if (targetElement.matches(':focus-visible')) {
1179
+ this.checkboxContainer.nativeElement.classList.add('focused');
1180
+ }
1181
+ }
1182
+ onBlur() {
1183
+ this.blurEvent.emit();
1184
+ this.checkboxContainer.nativeElement.classList.remove('focused');
1185
+ }
1186
+ setDisabledState(isDisabled) {
1187
+ this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
1188
+ if (isDisabled) {
1189
+ this.checkboxControl.disable();
1190
+ }
1191
+ else {
1192
+ this.checkboxControl.enable();
1193
+ }
1194
+ }
1195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: CheckboxControl, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
1196
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.3", type: CheckboxControl, isStandalone: true, selector: "checkbox-control", inputs: { title: "title", customClass: "customClass", type: "type", readonly: "readonly", disabled: "disabled" }, outputs: { checkboxChange: "checkboxChange", blurEvent: "blurEvent" }, providers: [
1197
+ {
1198
+ provide: NG_VALUE_ACCESSOR,
1199
+ useExisting: forwardRef(() => CheckboxControl),
1200
+ multi: true,
1201
+ },
1202
+ ], viewQueries: [{ propertyName: "checkboxContainer", first: true, predicate: ["checkboxContainer"], descendants: true }], ngImport: i0, template: "<label class=\"checkbox-container\" [ngClass]=\"[customClass, readonly ? 'readonly' : '']\" #checkboxContainer>\r\n <input type=\"checkbox\" [formControl]=\"checkboxControl\" (focus)=\"onFocus($event)\" (blur)=\"onBlur()\" [attr.readonly]=\"readonly ? true : null\" />\r\n <span class=\"checkmark\"></span>{{ title }}\r\n</label>", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormsModule }] });
1203
+ }
1204
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: CheckboxControl, decorators: [{
1205
+ type: Component,
1206
+ args: [{ selector: 'checkbox-control', standalone: true, imports: [
1207
+ CommonModule,
1208
+ ReactiveFormsModule,
1209
+ FormsModule
1210
+ ], providers: [
1211
+ {
1212
+ provide: NG_VALUE_ACCESSOR,
1213
+ useExisting: forwardRef(() => CheckboxControl),
1214
+ multi: true,
1215
+ },
1216
+ ], template: "<label class=\"checkbox-container\" [ngClass]=\"[customClass, readonly ? 'readonly' : '']\" #checkboxContainer>\r\n <input type=\"checkbox\" [formControl]=\"checkboxControl\" (focus)=\"onFocus($event)\" (blur)=\"onBlur()\" [attr.readonly]=\"readonly ? true : null\" />\r\n <span class=\"checkmark\"></span>{{ title }}\r\n</label>" }]
1217
+ }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }], propDecorators: { title: [{
1218
+ type: Input
1219
+ }], customClass: [{
1220
+ type: Input
1221
+ }], type: [{
1222
+ type: Input
1223
+ }], readonly: [{
1224
+ type: Input
1225
+ }], disabled: [{
1226
+ type: Input
1227
+ }], checkboxChange: [{
1228
+ type: Output
1229
+ }], blurEvent: [{
1230
+ type: Output
1231
+ }], checkboxContainer: [{
1232
+ type: ViewChild,
1233
+ args: ['checkboxContainer']
1234
+ }] } });
1235
+
1236
+ class InputControl {
1237
+ type = "text";
1238
+ title;
1239
+ placeholder = "";
1240
+ customClass;
1241
+ clearVal = true;
1242
+ required = false;
1243
+ deFocus = true;
1244
+ error = false;
1245
+ errorMessage = "";
1246
+ autocomplete = '';
1247
+ readonly = false;
1248
+ search = false;
1249
+ price = false;
1250
+ maxlength;
1251
+ ngModel;
1252
+ advSearch = false;
1253
+ inputLoader = false;
1254
+ useReactiveForms = true;
1255
+ blurEvent = new EventEmitter();
1256
+ selectionCleared = new EventEmitter();
1257
+ inputChange = new EventEmitter();
1258
+ ngModelChange = new EventEmitter();
1259
+ searchClicked = new EventEmitter();
1260
+ advSearchClicked = new EventEmitter();
1261
+ _disabled = false;
1262
+ set disabled(isDisabled) {
1263
+ this._disabled = isDisabled;
1264
+ this.setDisabledState(isDisabled);
1265
+ }
1266
+ get disabled() {
1267
+ return this._disabled;
1268
+ }
1269
+ valueChangesSubscription;
1270
+ onChange = () => { };
1271
+ onTouched = () => { };
1272
+ isFocused = false;
1273
+ showPassword = false;
1274
+ inputControl = new FormControl("");
1275
+ ngOnInit() {
1276
+ if (this.useReactiveForms) {
1277
+ this.valueChangesSubscription = this.inputControl.valueChanges.subscribe((value) => {
1278
+ this.onChange(value);
1279
+ this.inputChange.emit(value ?? "");
1280
+ });
1281
+ }
1282
+ }
1283
+ ngOnDestroy() {
1284
+ if (this.valueChangesSubscription) {
1285
+ this.valueChangesSubscription.unsubscribe();
1286
+ }
1287
+ }
1288
+ // input type
1289
+ getInputType() {
1290
+ return this.type === "password" && this.showPassword ? "text" : this.type;
1291
+ }
1292
+ writeValue(value) {
1293
+ if (this.useReactiveForms) {
1294
+ if (value !== undefined) {
1295
+ this.inputControl.setValue(value, {
1296
+ emitEvent: false,
1297
+ });
1298
+ if (value != '') {
1299
+ this.onChange(value);
1300
+ }
1301
+ }
1302
+ if (this.inputControl.disabled !== this.disabled) {
1303
+ this.setDisabledState(this.disabled);
1304
+ }
1305
+ }
1306
+ else {
1307
+ this.ngModel = value;
1308
+ }
1309
+ }
1310
+ registerOnChange(fn) {
1311
+ this.onChange = fn;
1312
+ }
1313
+ registerOnTouched(fn) {
1314
+ this.onTouched = fn;
1315
+ }
1316
+ // events
1317
+ onBlur() {
1318
+ this.blurEvent.emit();
1319
+ setTimeout(() => {
1320
+ this.isFocused = false;
1321
+ this.onTouched();
1322
+ if (!this.useReactiveForms) {
1323
+ this.ngModelChange.emit(this.ngModel);
1324
+ }
1325
+ }, 200);
1326
+ }
1327
+ onFocus() {
1328
+ this.isFocused = true;
1329
+ }
1330
+ onKeyPress(event) {
1331
+ let allowedKeys = [];
1332
+ switch (this.type) {
1333
+ case "number":
1334
+ allowedKeys = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
1335
+ break;
1336
+ case "decimal":
1337
+ allowedKeys = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."];
1338
+ break;
1339
+ default:
1340
+ break;
1341
+ }
1342
+ if (this.search && event.key == "Enter") {
1343
+ this.onSearchClick();
1344
+ }
1345
+ if ((this.type == "number" || this.type == "decimal") &&
1346
+ !allowedKeys.includes(event.key)) {
1347
+ event.preventDefault();
1348
+ return;
1349
+ }
1350
+ if (this.maxlength != null && (this.type == "number" || this.type == "decimal")) {
1351
+ const currentValue = this.inputControl.value ?? "";
1352
+ if (currentValue.length >= this.maxlength) {
1353
+ event.preventDefault();
1354
+ return;
1355
+ }
1356
+ }
1357
+ }
1358
+ setDisabledState(isDisabled) {
1359
+ this._disabled = isDisabled;
1360
+ if (this._disabled && this.useReactiveForms) {
1361
+ this.inputControl.disable({
1362
+ emitEvent: false,
1363
+ });
1364
+ }
1365
+ else if (this.useReactiveForms) {
1366
+ this.inputControl.enable({
1367
+ emitEvent: false,
1368
+ });
1369
+ }
1370
+ }
1371
+ // actions
1372
+ passwordVisible() {
1373
+ this.showPassword = !this.showPassword;
1374
+ }
1375
+ onSearchClick() {
1376
+ this.searchClicked.emit();
1377
+ }
1378
+ onAdvSearchClick() {
1379
+ this.advSearchClicked.emit();
1380
+ }
1381
+ // clear
1382
+ showClearButton() {
1383
+ return (this.isFocused &&
1384
+ this.clearVal &&
1385
+ this.inputControl.value &&
1386
+ this.type !== "password");
1387
+ }
1388
+ resetInput() {
1389
+ this.selectionCleared.emit();
1390
+ if (this.useReactiveForms) {
1391
+ this.inputControl.setValue("");
1392
+ this.inputControl.markAsPristine({ onlySelf: true });
1393
+ }
1394
+ else {
1395
+ this.ngModel = "";
1396
+ this.ngModelChange.emit(this.ngModel);
1397
+ }
1398
+ }
1399
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: InputControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
1400
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.3", type: InputControl, isStandalone: true, selector: "input-control", inputs: { type: "type", title: "title", placeholder: "placeholder", customClass: "customClass", clearVal: "clearVal", required: "required", deFocus: "deFocus", error: "error", errorMessage: "errorMessage", autocomplete: "autocomplete", readonly: "readonly", search: "search", price: "price", maxlength: "maxlength", ngModel: "ngModel", advSearch: "advSearch", inputLoader: "inputLoader", useReactiveForms: "useReactiveForms", disabled: "disabled" }, outputs: { blurEvent: "blurEvent", selectionCleared: "selectionCleared", inputChange: "inputChange", ngModelChange: "ngModelChange", searchClicked: "searchClicked", advSearchClicked: "advSearchClicked" }, providers: [
1401
+ {
1402
+ provide: NG_VALUE_ACCESSOR,
1403
+ useExisting: forwardRef(() => InputControl),
1404
+ multi: true,
1405
+ },
1406
+ ], ngImport: i0, template: "<div class=\"form-group input\" [ngClass]=\"customClass\">\r\n @if (title) {\r\n <label class=\"inp-label\" [ngClass]=\"{ 'required': required }\">{{ title }}</label>\r\n }\r\n\r\n @if (useReactiveForms) {\r\n <input [type]=\"getInputType()\" class=\"form-control\" [placeholder]=\"placeholder\" [formControl]=\"inputControl\"\r\n [ngClass]=\"{ 'is-invalid': error }\" (focus)=\"onFocus()\" (blur)=\"onBlur()\" [readonly]=\"readonly\"\r\n (keypress)=\"onKeyPress($event)\" [attr.autocomplete]=\"autocomplete || null\" />\r\n } @else {\r\n <input [type]=\"getInputType()\" class=\"form-control\" [placeholder]=\"placeholder\" [(ngModel)]=\"ngModel\"\r\n [ngClass]=\"{ 'is-invalid': error }\" (focus)=\"onFocus()\" (blur)=\"onBlur()\" [readonly]=\"readonly\"\r\n (keypress)=\"onKeyPress($event)\" />\r\n }\r\n @if(deFocus){\r\n <span class=\"focus-border\"></span>\r\n }\r\n @if (!inputLoader && showClearButton()) {\r\n <label class=\"clear\" (click)=\"resetInput()\">\r\n <i class=\"he he-close\"></i>\r\n </label>\r\n }\r\n\r\n @if (price) {\r\n <label class=\"ls-price\">\r\n <i class=\"he he-rupee\"></i>\r\n </label>\r\n }\r\n\r\n @if (type === 'search') {\r\n <label class=\"ls-search\">\r\n <i class=\"he he-search\"></i>\r\n </label>\r\n }\r\n\r\n @if (advSearch) {\r\n <label class=\"rs-search\" (click)=\"onAdvSearchClick()\">\r\n <i class=\"he he-search-adv\"></i>\r\n </label>\r\n }\r\n\r\n @if (search) {\r\n <label class=\"rs-search\" (click)=\"onSearchClick()\">\r\n <i class=\"he he-search\"></i>\r\n </label>\r\n }\r\n\r\n @if (type === 'password' && !disabled) {\r\n <label class=\"toggle-eye\" (click)=\"passwordVisible()\">\r\n <i class=\"he\" [ngClass]=\"showPassword ? 'he-eye-off' : 'he-eye'\"></i>\r\n </label>\r\n }\r\n\r\n @if (inputLoader) {\r\n <label class=\"loader input-loader\"></label>\r\n }\r\n\r\n @if (error) {\r\n <div class=\"val-msg\">{{ errorMessage }}</div>\r\n }\r\n</div>", styles: [".form-group .toggle-eye,.form-group ::ng-deep .toggle-eye{top:28px;right:10px;padding:5px;line-height:1;position:absolute}.form-group .toggle-eye .he,.form-group ::ng-deep .toggle-eye .he{font-size:18px}.form-group .toggle-eye .rb,.form-group ::ng-deep .toggle-eye .rb{font-size:17px;color:#5f6554}.form-group.no-label .toggle-eye{top:9px}.form-group.group-sm .toggle-eye{top:5px}.form-group.left-input-search .ls-search{top:9px;left:10px;position:absolute}.form-group.left-input-search .ls-search .he{font-size:1em;color:#807a7a;font-weight:600}.form-group.left-input-search .form-control{text-indent:1.25rem}.form-group.right-input-search .rs-search{top:27px;right:7px;padding:5px;width:30px;height:30px;display:grid;cursor:pointer;position:absolute;border-radius:5px;place-items:center}.form-group.right-input-search .rs-search i{font-size:16px;color:#807a7a}.form-group.right-input-search .rs-search:hover{background:#ff7f5d1a}.form-group.right-input-search .rs-search:hover i{color:#ff7f5d}.form-group.right-input-search .rs-search+.rs-search{right:37px}.form-group.right-input-search .clear{right:2.5rem}.form-group.right-input-search .form-control{padding-right:4rem}.form-group.right-input-search.no-label .rs-search{top:7px}.form-group.right-input-search.no-label .clear{top:10px}.form-group.right-input-search.group-sm.no-label .rs-search{top:4px;width:27px;height:27px}.form-group.right-input-search.group-sm.no-label .rs-search i{font-size:14px}.form-group.right-input-search.group-sm.no-label .clear{top:6px;right:2.25rem}.form-group.right-input-search.double-search .form-control{padding-right:6rem}.form-group.right-input-search.double-search .clear{right:4.5rem}.form-group.price-input .form-control{text-indent:1rem}.form-group.price-input .ls-price{top:21px;padding:8px;display:flex;color:#2ba599;position:absolute;align-items:center;height:calc(var(--input-height) - 2px)}.form-group.price-input .ls-price i{top:1px;color:inherit;cursor:default;font-size:12px}.form-group .clear{z-index:4;cursor:pointer;background-color:transparent}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
1407
+ }
1408
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: InputControl, decorators: [{
1409
+ type: Component,
1410
+ args: [{ selector: "input-control", standalone: true, imports: [
1411
+ CommonModule,
1412
+ ReactiveFormsModule,
1413
+ FormsModule
1414
+ ], providers: [
1415
+ {
1416
+ provide: NG_VALUE_ACCESSOR,
1417
+ useExisting: forwardRef(() => InputControl),
1418
+ multi: true,
1419
+ },
1420
+ ], template: "<div class=\"form-group input\" [ngClass]=\"customClass\">\r\n @if (title) {\r\n <label class=\"inp-label\" [ngClass]=\"{ 'required': required }\">{{ title }}</label>\r\n }\r\n\r\n @if (useReactiveForms) {\r\n <input [type]=\"getInputType()\" class=\"form-control\" [placeholder]=\"placeholder\" [formControl]=\"inputControl\"\r\n [ngClass]=\"{ 'is-invalid': error }\" (focus)=\"onFocus()\" (blur)=\"onBlur()\" [readonly]=\"readonly\"\r\n (keypress)=\"onKeyPress($event)\" [attr.autocomplete]=\"autocomplete || null\" />\r\n } @else {\r\n <input [type]=\"getInputType()\" class=\"form-control\" [placeholder]=\"placeholder\" [(ngModel)]=\"ngModel\"\r\n [ngClass]=\"{ 'is-invalid': error }\" (focus)=\"onFocus()\" (blur)=\"onBlur()\" [readonly]=\"readonly\"\r\n (keypress)=\"onKeyPress($event)\" />\r\n }\r\n @if(deFocus){\r\n <span class=\"focus-border\"></span>\r\n }\r\n @if (!inputLoader && showClearButton()) {\r\n <label class=\"clear\" (click)=\"resetInput()\">\r\n <i class=\"he he-close\"></i>\r\n </label>\r\n }\r\n\r\n @if (price) {\r\n <label class=\"ls-price\">\r\n <i class=\"he he-rupee\"></i>\r\n </label>\r\n }\r\n\r\n @if (type === 'search') {\r\n <label class=\"ls-search\">\r\n <i class=\"he he-search\"></i>\r\n </label>\r\n }\r\n\r\n @if (advSearch) {\r\n <label class=\"rs-search\" (click)=\"onAdvSearchClick()\">\r\n <i class=\"he he-search-adv\"></i>\r\n </label>\r\n }\r\n\r\n @if (search) {\r\n <label class=\"rs-search\" (click)=\"onSearchClick()\">\r\n <i class=\"he he-search\"></i>\r\n </label>\r\n }\r\n\r\n @if (type === 'password' && !disabled) {\r\n <label class=\"toggle-eye\" (click)=\"passwordVisible()\">\r\n <i class=\"he\" [ngClass]=\"showPassword ? 'he-eye-off' : 'he-eye'\"></i>\r\n </label>\r\n }\r\n\r\n @if (inputLoader) {\r\n <label class=\"loader input-loader\"></label>\r\n }\r\n\r\n @if (error) {\r\n <div class=\"val-msg\">{{ errorMessage }}</div>\r\n }\r\n</div>", styles: [".form-group .toggle-eye,.form-group ::ng-deep .toggle-eye{top:28px;right:10px;padding:5px;line-height:1;position:absolute}.form-group .toggle-eye .he,.form-group ::ng-deep .toggle-eye .he{font-size:18px}.form-group .toggle-eye .rb,.form-group ::ng-deep .toggle-eye .rb{font-size:17px;color:#5f6554}.form-group.no-label .toggle-eye{top:9px}.form-group.group-sm .toggle-eye{top:5px}.form-group.left-input-search .ls-search{top:9px;left:10px;position:absolute}.form-group.left-input-search .ls-search .he{font-size:1em;color:#807a7a;font-weight:600}.form-group.left-input-search .form-control{text-indent:1.25rem}.form-group.right-input-search .rs-search{top:27px;right:7px;padding:5px;width:30px;height:30px;display:grid;cursor:pointer;position:absolute;border-radius:5px;place-items:center}.form-group.right-input-search .rs-search i{font-size:16px;color:#807a7a}.form-group.right-input-search .rs-search:hover{background:#ff7f5d1a}.form-group.right-input-search .rs-search:hover i{color:#ff7f5d}.form-group.right-input-search .rs-search+.rs-search{right:37px}.form-group.right-input-search .clear{right:2.5rem}.form-group.right-input-search .form-control{padding-right:4rem}.form-group.right-input-search.no-label .rs-search{top:7px}.form-group.right-input-search.no-label .clear{top:10px}.form-group.right-input-search.group-sm.no-label .rs-search{top:4px;width:27px;height:27px}.form-group.right-input-search.group-sm.no-label .rs-search i{font-size:14px}.form-group.right-input-search.group-sm.no-label .clear{top:6px;right:2.25rem}.form-group.right-input-search.double-search .form-control{padding-right:6rem}.form-group.right-input-search.double-search .clear{right:4.5rem}.form-group.price-input .form-control{text-indent:1rem}.form-group.price-input .ls-price{top:21px;padding:8px;display:flex;color:#2ba599;position:absolute;align-items:center;height:calc(var(--input-height) - 2px)}.form-group.price-input .ls-price i{top:1px;color:inherit;cursor:default;font-size:12px}.form-group .clear{z-index:4;cursor:pointer;background-color:transparent}\n"] }]
1421
+ }], propDecorators: { type: [{
1422
+ type: Input
1423
+ }], title: [{
1424
+ type: Input
1425
+ }], placeholder: [{
1426
+ type: Input
1427
+ }], customClass: [{
1428
+ type: Input
1429
+ }], clearVal: [{
1430
+ type: Input
1431
+ }], required: [{
1432
+ type: Input
1433
+ }], deFocus: [{
1434
+ type: Input
1435
+ }], error: [{
1436
+ type: Input
1437
+ }], errorMessage: [{
1438
+ type: Input
1439
+ }], autocomplete: [{
1440
+ type: Input
1441
+ }], readonly: [{
1442
+ type: Input
1443
+ }], search: [{
1444
+ type: Input
1445
+ }], price: [{
1446
+ type: Input
1447
+ }], maxlength: [{
1448
+ type: Input
1449
+ }], ngModel: [{
1450
+ type: Input
1451
+ }], advSearch: [{
1452
+ type: Input
1453
+ }], inputLoader: [{
1454
+ type: Input
1455
+ }], useReactiveForms: [{
1456
+ type: Input
1457
+ }], blurEvent: [{
1458
+ type: Output
1459
+ }], selectionCleared: [{
1460
+ type: Output
1461
+ }], inputChange: [{
1462
+ type: Output
1463
+ }], ngModelChange: [{
1464
+ type: Output
1465
+ }], searchClicked: [{
1466
+ type: Output
1467
+ }], advSearchClicked: [{
1468
+ type: Output
1469
+ }], disabled: [{
1470
+ type: Input
1471
+ }] } });
1472
+
1473
+ class ClickOutsideDirective {
1474
+ clickOutside = new EventEmitter();
1475
+ host = inject(ElementRef);
1476
+ document = inject(DOCUMENT);
1477
+ destroyRef = inject(DestroyRef);
1478
+ constructor() {
1479
+ fromEvent(this.document, 'click')
1480
+ .pipe(takeUntilDestroyed(this.destroyRef))
1481
+ .subscribe((event) => {
1482
+ const clickedInside = this.host.nativeElement.contains(event.target);
1483
+ if (!clickedInside) {
1484
+ this.clickOutside.emit(event);
1485
+ }
1486
+ });
1487
+ }
1488
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ClickOutsideDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1489
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.3", type: ClickOutsideDirective, isStandalone: true, selector: "[clickOutside]", outputs: { clickOutside: "clickOutside" }, ngImport: i0 });
1490
+ }
1491
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ClickOutsideDirective, decorators: [{
1492
+ type: Directive,
1493
+ args: [{
1494
+ selector: '[clickOutside]',
1495
+ standalone: true,
1496
+ }]
1497
+ }], ctorParameters: () => [], propDecorators: { clickOutside: [{
1498
+ type: Output
1499
+ }] } });
1500
+
1501
+ class MultiselectControl {
1502
+ cdRef;
1503
+ subscription = new Subscription();
1504
+ title;
1505
+ selectedItems = [];
1506
+ required = false;
1507
+ placeholder;
1508
+ customClass;
1509
+ clearVal = true;
1510
+ deFocus = true;
1511
+ optionValueProperty = "id";
1512
+ inputLoader = false;
1513
+ error = false;
1514
+ errorMessage = "";
1515
+ autocomplete = "";
1516
+ showSearch = false;
1517
+ optionDisplayProperty = "displayname";
1518
+ optionsSelected = new EventEmitter();
1519
+ selectionCleared = new EventEmitter();
1520
+ blurEvent = new EventEmitter();
1521
+ inputControl = new FormControl({ value: "", disabled: false });
1522
+ _options = [];
1523
+ set options(arr) {
1524
+ this._options = Array.isArray(arr) ? arr : [];
1525
+ this.filteredOptions = [...this._options];
1526
+ // If we previously received an array of primitive IDs, attempt to map them now that options have arrived
1527
+ if (this._isPrimitivePatch && this._pendingPrimitiveValues.length > 0) {
1528
+ this._mapPrimitivesToObjects();
1529
+ }
1530
+ this._recomputeSelectAllState();
1531
+ }
1532
+ get options() {
1533
+ return this._options;
1534
+ }
1535
+ _disabled = false;
1536
+ get disabled() {
1537
+ return this._disabled;
1538
+ }
1539
+ set disabled(value) {
1540
+ this._disabled = value;
1541
+ if (this.inputControl) {
1542
+ if (value) {
1543
+ this.inputControl.disable({ emitEvent: false });
1544
+ }
1545
+ else {
1546
+ this.inputControl.enable({ emitEvent: false });
1547
+ }
1548
+ }
1549
+ }
1550
+ _isPrimitivePatch = false;
1551
+ _pendingPrimitiveValues = [];
1552
+ onChange = () => { };
1553
+ onTouched = () => { };
1554
+ dropdown;
1555
+ input;
1556
+ filteredOptions = [];
1557
+ isDropdownOpen = false;
1558
+ selectAllChecked = false;
1559
+ keySearch = "";
1560
+ onSearch = new Subject();
1561
+ selectedItemList = "";
1562
+ highlightedIndex = null;
1563
+ dropdownInitialized = false;
1564
+ popperInstance;
1565
+ constructor(cdRef) {
1566
+ this.cdRef = cdRef;
1567
+ }
1568
+ ngOnInit() {
1569
+ // Subscribe to search‐term changes, debounce, then filter
1570
+ this.subscription = this.onSearch
1571
+ .pipe(debounceTime(300), distinctUntilChanged())
1572
+ .subscribe((searchText) => {
1573
+ this._applyFilter(searchText);
1574
+ this._recomputeSelectAllState();
1575
+ });
1576
+ // Initialize filteredOptions to match all options
1577
+ this.filteredOptions = [...this.options];
1578
+ }
1579
+ ngAfterViewChecked() {
1580
+ if (this.isDropdownOpen && !this.dropdownInitialized) {
1581
+ this._createPopperInstance();
1582
+ this.dropdownInitialized = true;
1583
+ }
1584
+ }
1585
+ ngOnDestroy() {
1586
+ this.subscription.unsubscribe();
1587
+ if (this.popperInstance) {
1588
+ this.popperInstance.destroy();
1589
+ }
1590
+ }
1591
+ ngOnChanges(changes) {
1592
+ if (changes["options"]) {
1593
+ // When the parent replaces “options,” we reapply any filtering and “select all” logic
1594
+ this.filteredOptions = [...this.options];
1595
+ this._recomputeSelectAllState();
1596
+ }
1597
+ }
1598
+ writeValue(value) {
1599
+ // If parent gave us something that is not an array, clear selection
1600
+ if (!Array.isArray(value)) {
1601
+ this._isPrimitivePatch = false;
1602
+ this._pendingPrimitiveValues = [];
1603
+ this.selectedItems = [];
1604
+ this.selectedItemList = "";
1605
+ this.selectAllChecked = false;
1606
+ return;
1607
+ }
1608
+ // CASE A: array of primitive IDs, e.g. [12,17]
1609
+ if (value.length > 0 &&
1610
+ (typeof value[0] === "string" || typeof value[0] === "number")) {
1611
+ this._isPrimitivePatch = true;
1612
+ this._pendingPrimitiveValues = [...value];
1613
+ this._mapPrimitivesToObjects();
1614
+ this._recomputeSelectAllState();
1615
+ return;
1616
+ }
1617
+ // CASE B: array of full objects (with `id` or whatever `optionValueProperty` is)
1618
+ if (value.length > 0 &&
1619
+ typeof value[0] === "object" &&
1620
+ value[0][this.optionValueProperty] != null) {
1621
+ // Convert to an array of IDs, then treat exactly like CASE A
1622
+ const idArray = value.map((obj) => obj[this.optionValueProperty]);
1623
+ this._isPrimitivePatch = true;
1624
+ this._pendingPrimitiveValues = idArray;
1625
+ this._mapPrimitivesToObjects();
1626
+ this._recomputeSelectAllState();
1627
+ return;
1628
+ }
1629
+ // CASE C: maybe parent passed the exact same object references that already exist in `options`
1630
+ // In that case, we can just use them directly.
1631
+ this._isPrimitivePatch = false;
1632
+ this._pendingPrimitiveValues = [];
1633
+ this.selectedItems = [...value];
1634
+ this.selectedItemList = this.selectedItems
1635
+ .map((item) => item[this.optionDisplayProperty])
1636
+ .join(", ");
1637
+ this.onChange(this.selectedItems);
1638
+ this._recomputeSelectAllState();
1639
+ }
1640
+ _mapPrimitivesToObjects() {
1641
+ if (!this._isPrimitivePatch ||
1642
+ this._pendingPrimitiveValues.length === 0 ||
1643
+ this._options.length === 0) {
1644
+ return;
1645
+ }
1646
+ // Find matching option‐objects whose ID property is in the pending list
1647
+ const matched = this._options.filter((opt) => this._pendingPrimitiveValues.includes(opt[this.optionValueProperty]));
1648
+ this.selectedItems = matched;
1649
+ this.selectedItemList = matched
1650
+ .map((item) => item[this.optionDisplayProperty])
1651
+ .join(", ");
1652
+ // Notify “onChange” with the full‐object selection
1653
+ this.onChange(this.selectedItems);
1654
+ this._isPrimitivePatch = false;
1655
+ this._pendingPrimitiveValues = [];
1656
+ }
1657
+ registerOnChange(fn) {
1658
+ this.onChange = fn;
1659
+ }
1660
+ registerOnTouched(fn) {
1661
+ this.onTouched = fn;
1662
+ }
1663
+ // events
1664
+ isSelected(option) {
1665
+ return (Array.isArray(this.selectedItems) && this.selectedItems.includes(option));
1666
+ }
1667
+ onBlur(event) {
1668
+ this.blurEvent.emit();
1669
+ // const target = event.relatedTarget as HTMLElement;
1670
+ // if (
1671
+ // target &&
1672
+ // (target.closest(".option-list") || target.closest(".list-filter"))
1673
+ // ) {
1674
+ // return;
1675
+ // }
1676
+ this.onTouched();
1677
+ // setTimeout(() => {
1678
+ // this.isDropdownOpen = false;
1679
+ // }, 100);
1680
+ }
1681
+ setDisabledState(isDisabled) {
1682
+ this.disabled = isDisabled;
1683
+ if (isDisabled) {
1684
+ this.inputControl.disable({ emitEvent: false });
1685
+ }
1686
+ else {
1687
+ this.inputControl.enable({ emitEvent: false });
1688
+ }
1689
+ }
1690
+ // search box
1691
+ handleInput(event) {
1692
+ const txt = event.target.value;
1693
+ this.onSearch.next(txt);
1694
+ }
1695
+ _applyFilter(value) {
1696
+ const lower = value.toLowerCase();
1697
+ this.filteredOptions = this._options
1698
+ .filter((opt) => opt[this.optionDisplayProperty]?.toLowerCase().includes(lower))
1699
+ .sort((a, b) => {
1700
+ const aIndex = a[this.optionDisplayProperty]
1701
+ .toLowerCase()
1702
+ .indexOf(lower);
1703
+ const bIndex = b[this.optionDisplayProperty]
1704
+ .toLowerCase()
1705
+ .indexOf(lower);
1706
+ return aIndex - bIndex;
1707
+ });
1708
+ // If an item was previously selected but is no longer in filteredOptions, remove it temporarily
1709
+ this.selectedItems = this.selectedItems.filter((item) => this.filteredOptions.includes(item));
1710
+ this.selectedItemList = this.selectedItems
1711
+ .map((item) => item[this.optionDisplayProperty])
1712
+ .join(", ");
1713
+ this.onChange(this.selectedItems);
1714
+ this.optionsSelected.emit(this.selectedItems);
1715
+ if (this.filteredOptions.length > 0) {
1716
+ this.highlightedIndex = this._options.indexOf(this.filteredOptions[0]);
1717
+ }
1718
+ else {
1719
+ this.highlightedIndex = null;
1720
+ }
1721
+ this._recomputeSelectAllState();
1722
+ this.cdRef.detectChanges();
1723
+ }
1724
+ // checkbox events
1725
+ _recomputeSelectAllState() {
1726
+ if (!this.filteredOptions || this.filteredOptions.length === 0) {
1727
+ this.selectAllChecked = false;
1728
+ return;
1729
+ }
1730
+ this.selectAllChecked = this.filteredOptions.every((opt) => this.selectedItems.includes(opt));
1731
+ }
1732
+ onSelectAllChange(event) {
1733
+ const input = event.target;
1734
+ this.selectAllChecked = input.checked;
1735
+ this._toggleSelectAll();
1736
+ }
1737
+ _toggleSelectAll() {
1738
+ if (this.selectAllChecked) {
1739
+ // Add all currently filtered options to selection
1740
+ this.selectedItems = Array.from(new Set([...this.selectedItems, ...this.filteredOptions]));
1741
+ }
1742
+ else {
1743
+ // Remove all filtered options from selection
1744
+ this.selectedItems = this.selectedItems.filter((item) => !this.filteredOptions.includes(item));
1745
+ }
1746
+ this.selectedItemList = this.selectedItems
1747
+ .map((item) => item[this.optionDisplayProperty])
1748
+ .join(", ");
1749
+ this.onChange(this.selectedItems);
1750
+ this.optionsSelected.emit(this.selectedItems);
1751
+ }
1752
+ removeSelectedOption(option) {
1753
+ this.selectedItems = this.selectedItems.filter((item) => item !== option);
1754
+ this.selectedItemList = this.selectedItems
1755
+ .map((item) => item[this.optionDisplayProperty])
1756
+ .join(", ");
1757
+ this.onChange(this.selectedItems);
1758
+ this.onTouched();
1759
+ this.optionsSelected.emit(this.selectedItems);
1760
+ }
1761
+ toggleCheckbox(option) {
1762
+ if (this.isSelected(option)) {
1763
+ this.selectedItems = this.selectedItems.filter((item) => item !== option);
1764
+ }
1765
+ else {
1766
+ this.selectedItems = [...this.selectedItems, option];
1767
+ }
1768
+ this.selectedItemList = this.selectedItems
1769
+ .map((item) => item[this.optionDisplayProperty])
1770
+ .join(", ");
1771
+ // Update “select all” checkbox
1772
+ this._recomputeSelectAllState();
1773
+ // Notify form that selection changed
1774
+ this.onChange(this.selectedItems);
1775
+ this.onTouched();
1776
+ this.optionsSelected.emit(this.selectedItems);
1777
+ }
1778
+ // Keyboard navigation inside the dropdown
1779
+ onKeyDown(event) {
1780
+ if (this.isDropdownOpen) {
1781
+ switch (event.key) {
1782
+ case "ArrowDown":
1783
+ this._highlightNext();
1784
+ event.preventDefault();
1785
+ break;
1786
+ case "ArrowUp":
1787
+ this._highlightPrevious();
1788
+ event.preventDefault();
1789
+ break;
1790
+ case "Enter":
1791
+ if (this.highlightedIndex !== null &&
1792
+ this.highlightedIndex < this.filteredOptions.length) {
1793
+ this.toggleCheckbox(this.filteredOptions[this.highlightedIndex]);
1794
+ }
1795
+ event.preventDefault();
1796
+ break;
1797
+ case "Escape":
1798
+ this.isDropdownOpen = false;
1799
+ break;
1800
+ case "Tab":
1801
+ this.isDropdownOpen = false;
1802
+ break;
1803
+ }
1804
+ }
1805
+ else if (event.key === " ") {
1806
+ this.toggleDropdown();
1807
+ event.preventDefault();
1808
+ }
1809
+ }
1810
+ handleWindowKeydown(event) {
1811
+ if (event.key === "Tab") {
1812
+ this.isDropdownOpen = false;
1813
+ }
1814
+ }
1815
+ _highlightNext() {
1816
+ if (this.highlightedIndex === null ||
1817
+ this.highlightedIndex === this.filteredOptions.length - 1) {
1818
+ this.highlightedIndex = 0;
1819
+ }
1820
+ else {
1821
+ this.highlightedIndex++;
1822
+ }
1823
+ this._scrollToHighlighted();
1824
+ }
1825
+ _highlightPrevious() {
1826
+ if (this.highlightedIndex === null || this.highlightedIndex === 0) {
1827
+ this.highlightedIndex = this.filteredOptions.length - 1;
1828
+ }
1829
+ else {
1830
+ this.highlightedIndex--;
1831
+ }
1832
+ this._scrollToHighlighted();
1833
+ }
1834
+ _scrollToHighlighted() {
1835
+ setTimeout(() => {
1836
+ const container = document.querySelector(".option-list");
1837
+ if (!container)
1838
+ return;
1839
+ const highlighted = container.querySelector(".highlighted");
1840
+ if (highlighted) {
1841
+ highlighted.scrollIntoView({ block: "nearest" });
1842
+ }
1843
+ }, 0);
1844
+ }
1845
+ // popper
1846
+ _createPopperInstance() {
1847
+ if (!this.dropdown || !this.input)
1848
+ return;
1849
+ this.popperInstance = createPopper(this.input.nativeElement, this.dropdown.nativeElement, {
1850
+ placement: "bottom-start",
1851
+ modifiers: [
1852
+ {
1853
+ name: "offset",
1854
+ options: {
1855
+ offset: [0, 1],
1856
+ },
1857
+ },
1858
+ {
1859
+ name: "flip",
1860
+ options: {
1861
+ fallbackPlacements: ["top-start", "bottom-start"],
1862
+ },
1863
+ },
1864
+ ],
1865
+ });
1866
+ }
1867
+ // toggle, reset, close and clear
1868
+ toggleDropdown() {
1869
+ if (this.inputControl.disabled)
1870
+ return;
1871
+ this.isDropdownOpen = !this.isDropdownOpen;
1872
+ this.dropdownInitialized = false;
1873
+ if (this.isDropdownOpen) {
1874
+ this.filteredOptions = [...this.options];
1875
+ this.highlightedIndex = null;
1876
+ }
1877
+ }
1878
+ selectListOutsideClick() {
1879
+ this.isDropdownOpen = false;
1880
+ }
1881
+ resetSelection() {
1882
+ this.selectedItems = [];
1883
+ this.selectedItemList = "";
1884
+ this.onChange([]);
1885
+ this.onTouched();
1886
+ this.isDropdownOpen = false;
1887
+ this.selectAllChecked = false;
1888
+ this.selectionCleared.emit();
1889
+ }
1890
+ onCloseDropdown() {
1891
+ this.isDropdownOpen = false;
1892
+ this._recomputeSelectAllState();
1893
+ }
1894
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: MultiselectControl, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1895
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.3", type: MultiselectControl, isStandalone: true, selector: "multiselect-control", inputs: { title: "title", selectedItems: "selectedItems", required: "required", placeholder: "placeholder", customClass: "customClass", clearVal: "clearVal", deFocus: "deFocus", optionValueProperty: "optionValueProperty", inputLoader: "inputLoader", error: "error", errorMessage: "errorMessage", autocomplete: "autocomplete", showSearch: "showSearch", optionDisplayProperty: "optionDisplayProperty", options: "options", disabled: "disabled" }, outputs: { optionsSelected: "optionsSelected", selectionCleared: "selectionCleared", blurEvent: "blurEvent" }, host: { listeners: { "window:keydown": "handleWindowKeydown($event)" } }, providers: [
1896
+ {
1897
+ provide: NG_VALUE_ACCESSOR,
1898
+ useExisting: forwardRef(() => MultiselectControl),
1899
+ multi: true,
1900
+ },
1901
+ ], viewQueries: [{ propertyName: "dropdown", first: true, predicate: ["dropdown"], descendants: true }, { propertyName: "input", first: true, predicate: ["input"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"form-group multi-select\" [ngClass]=\"customClass\" (clickOutside)=\"selectListOutsideClick()\">\r\n @if (title) {\r\n <label class=\"inp-label\" [ngClass]=\"{ 'required': required }\">{{ title }}</label>\r\n }\r\n\r\n <input #input type=\"text\" class=\"form-control\" placeholder=\"{{ placeholder }}\" [formControl]=\"inputControl\"\r\n [ngClass]=\"{'is-invalid': error}\" (blur)=\"onBlur($event)\" (click)=\"toggleDropdown()\" readonly\r\n [value]=\"selectedItemList\" (keydown)=\"onKeyDown($event)\" [attr.autocomplete]=\"autocomplete || null\" />\r\n\r\n @if (deFocus) {\r\n <span class=\"focus-border\"></span>\r\n }\r\n\r\n @if (!inputLoader && isDropdownOpen && clearVal && selectedItems.length > 0) {\r\n <label class=\"clear\" (click)=\"resetSelection()\">\r\n <i class=\"he he-close\"></i>\r\n </label>\r\n }\r\n\r\n @if (!inputLoader) {\r\n <label class=\"arrow\">\r\n <i class=\"he he-chevron-down\"></i>\r\n </label> }\r\n\r\n @if (isDropdownOpen) {\r\n <div #dropdown class=\"option-list\">\r\n @if (showSearch && filteredOptions.length > 5) {\r\n <div class=\"list-filter\">\r\n <div>\r\n <label class=\"checkbox-container select-all\">\r\n <input type=\"checkbox\" [checked]=\"selectAllChecked\" (change)=\"onSelectAllChange($event)\" tabindex=\"-1\" />\r\n <span class=\"checkmark\"></span>\r\n </label>\r\n </div>\r\n <div class=\"form-group\">\r\n <input type=\"text\" class=\"form-control\" placeholder=\"Search...\" (input)=\"handleInput($event) \"\r\n (keydown)=\"onKeyDown($event)\" />\r\n </div>\r\n <div class=\"ms-close\">\r\n <button type=\"button\" class=\"icon-button\">\r\n <a (click)=\"onCloseDropdown()\"><i class=\"he he-close\"></i></a>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"list-wrap\">\r\n @for (option of filteredOptions; track option[optionDisplayProperty]; let i = $index) {\r\n <div class=\"list-item\" (change)=\"toggleCheckbox(option)\" [ngClass]=\"{ 'highlighted': i === highlightedIndex }\">\r\n <label class=\"checkbox-container multi-select\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(option)\" tabindex=\"-1\" />\r\n <span class=\"checkmark\"></span>\r\n {{ option[optionDisplayProperty] }}\r\n </label>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (filteredOptions.length === 0) {\r\n <div class=\"no-results\">No results found</div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (inputLoader) {\r\n <label class=\"loader input-loader\"></label>\r\n }\r\n\r\n @if (error) {\r\n <div class=\"val-msg\">{{ errorMessage }}</div>\r\n }\r\n</div>", styles: [".form-group.multi-select .option-list .list-filter{gap:10px;display:flex;padding:9px 10px;align-items:center;border-bottom:1px solid #efefef}.form-group.multi-select .option-list .list-filter .checkbox-container.select-all{top:-10px;margin-bottom:0;padding-left:22px}.form-group.multi-select .option-list .list-filter .form-group{margin-bottom:0;width:calc(100% - 70px)}.form-group.multi-select .option-list .list-filter .form-group .form-control{height:34px;border-radius:5px;background:#fff;padding:.275rem .5rem;border:1px solid #cccccc!important}.form-group.multi-select .option-list .list-filter .form-group .form-control::placeholder{font-size:.925em}.form-group.multi-select .option-list .list-filter .ms-close .icon-button{background:#f0f0f0}.form-group.multi-select .option-list .list-filter .ms-close .icon-button a{width:28px;height:28px;display:grid;cursor:pointer;position:relative;border-radius:5px;place-items:center;text-decoration:none;border:1px solid #efefef;box-shadow:0 0 3px #ebebeb}.form-group.multi-select .option-list .list-filter .ms-close .icon-button a i{font-size:14px}.form-group.multi-select .option-list .list-filter .ms-close .icon-button a:hover{background:#ff7f5d1a}.form-group.multi-select .option-list .list-filter .ms-close .icon-button a:hover i{color:#ff7f5d}.form-group.multi-select .option-list .list-wrap{overflow:auto;max-height:180px}.form-group.multi-select .option-list .list-item{padding:0}.form-group.multi-select .option-list .list-item .checkbox-container.multi-select{width:100%;font-size:1em;margin-bottom:0;font-weight:400;color:#584e4e;letter-spacing:.015rem;padding:10px 10px 10px 40px}.form-group.multi-select .option-list .list-item .checkbox-container.multi-select .checkmark{top:9px;left:10px}.form-group.multi-select .option-list .no-results{padding:10px;color:#9b9b9b;text-align:center}.form-group.multi-select .clear{right:30px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: ClickOutsideDirective, selector: "[clickOutside]", outputs: ["clickOutside"] }] });
1902
+ }
1903
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: MultiselectControl, decorators: [{
1904
+ type: Component,
1905
+ args: [{ selector: "multiselect-control", standalone: true, imports: [
1906
+ CommonModule,
1907
+ ReactiveFormsModule,
1908
+ ClickOutsideDirective
1909
+ ], providers: [
1910
+ {
1911
+ provide: NG_VALUE_ACCESSOR,
1912
+ useExisting: forwardRef(() => MultiselectControl),
1913
+ multi: true,
1914
+ },
1915
+ ], template: "<div class=\"form-group multi-select\" [ngClass]=\"customClass\" (clickOutside)=\"selectListOutsideClick()\">\r\n @if (title) {\r\n <label class=\"inp-label\" [ngClass]=\"{ 'required': required }\">{{ title }}</label>\r\n }\r\n\r\n <input #input type=\"text\" class=\"form-control\" placeholder=\"{{ placeholder }}\" [formControl]=\"inputControl\"\r\n [ngClass]=\"{'is-invalid': error}\" (blur)=\"onBlur($event)\" (click)=\"toggleDropdown()\" readonly\r\n [value]=\"selectedItemList\" (keydown)=\"onKeyDown($event)\" [attr.autocomplete]=\"autocomplete || null\" />\r\n\r\n @if (deFocus) {\r\n <span class=\"focus-border\"></span>\r\n }\r\n\r\n @if (!inputLoader && isDropdownOpen && clearVal && selectedItems.length > 0) {\r\n <label class=\"clear\" (click)=\"resetSelection()\">\r\n <i class=\"he he-close\"></i>\r\n </label>\r\n }\r\n\r\n @if (!inputLoader) {\r\n <label class=\"arrow\">\r\n <i class=\"he he-chevron-down\"></i>\r\n </label> }\r\n\r\n @if (isDropdownOpen) {\r\n <div #dropdown class=\"option-list\">\r\n @if (showSearch && filteredOptions.length > 5) {\r\n <div class=\"list-filter\">\r\n <div>\r\n <label class=\"checkbox-container select-all\">\r\n <input type=\"checkbox\" [checked]=\"selectAllChecked\" (change)=\"onSelectAllChange($event)\" tabindex=\"-1\" />\r\n <span class=\"checkmark\"></span>\r\n </label>\r\n </div>\r\n <div class=\"form-group\">\r\n <input type=\"text\" class=\"form-control\" placeholder=\"Search...\" (input)=\"handleInput($event) \"\r\n (keydown)=\"onKeyDown($event)\" />\r\n </div>\r\n <div class=\"ms-close\">\r\n <button type=\"button\" class=\"icon-button\">\r\n <a (click)=\"onCloseDropdown()\"><i class=\"he he-close\"></i></a>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"list-wrap\">\r\n @for (option of filteredOptions; track option[optionDisplayProperty]; let i = $index) {\r\n <div class=\"list-item\" (change)=\"toggleCheckbox(option)\" [ngClass]=\"{ 'highlighted': i === highlightedIndex }\">\r\n <label class=\"checkbox-container multi-select\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(option)\" tabindex=\"-1\" />\r\n <span class=\"checkmark\"></span>\r\n {{ option[optionDisplayProperty] }}\r\n </label>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (filteredOptions.length === 0) {\r\n <div class=\"no-results\">No results found</div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (inputLoader) {\r\n <label class=\"loader input-loader\"></label>\r\n }\r\n\r\n @if (error) {\r\n <div class=\"val-msg\">{{ errorMessage }}</div>\r\n }\r\n</div>", styles: [".form-group.multi-select .option-list .list-filter{gap:10px;display:flex;padding:9px 10px;align-items:center;border-bottom:1px solid #efefef}.form-group.multi-select .option-list .list-filter .checkbox-container.select-all{top:-10px;margin-bottom:0;padding-left:22px}.form-group.multi-select .option-list .list-filter .form-group{margin-bottom:0;width:calc(100% - 70px)}.form-group.multi-select .option-list .list-filter .form-group .form-control{height:34px;border-radius:5px;background:#fff;padding:.275rem .5rem;border:1px solid #cccccc!important}.form-group.multi-select .option-list .list-filter .form-group .form-control::placeholder{font-size:.925em}.form-group.multi-select .option-list .list-filter .ms-close .icon-button{background:#f0f0f0}.form-group.multi-select .option-list .list-filter .ms-close .icon-button a{width:28px;height:28px;display:grid;cursor:pointer;position:relative;border-radius:5px;place-items:center;text-decoration:none;border:1px solid #efefef;box-shadow:0 0 3px #ebebeb}.form-group.multi-select .option-list .list-filter .ms-close .icon-button a i{font-size:14px}.form-group.multi-select .option-list .list-filter .ms-close .icon-button a:hover{background:#ff7f5d1a}.form-group.multi-select .option-list .list-filter .ms-close .icon-button a:hover i{color:#ff7f5d}.form-group.multi-select .option-list .list-wrap{overflow:auto;max-height:180px}.form-group.multi-select .option-list .list-item{padding:0}.form-group.multi-select .option-list .list-item .checkbox-container.multi-select{width:100%;font-size:1em;margin-bottom:0;font-weight:400;color:#584e4e;letter-spacing:.015rem;padding:10px 10px 10px 40px}.form-group.multi-select .option-list .list-item .checkbox-container.multi-select .checkmark{top:9px;left:10px}.form-group.multi-select .option-list .no-results{padding:10px;color:#9b9b9b;text-align:center}.form-group.multi-select .clear{right:30px}\n"] }]
1916
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { title: [{
1917
+ type: Input
1918
+ }], selectedItems: [{
1919
+ type: Input
1920
+ }], required: [{
1921
+ type: Input
1922
+ }], placeholder: [{
1923
+ type: Input
1924
+ }], customClass: [{
1925
+ type: Input
1926
+ }], clearVal: [{
1927
+ type: Input
1928
+ }], deFocus: [{
1929
+ type: Input
1930
+ }], optionValueProperty: [{
1931
+ type: Input
1932
+ }], inputLoader: [{
1933
+ type: Input
1934
+ }], error: [{
1935
+ type: Input
1936
+ }], errorMessage: [{
1937
+ type: Input
1938
+ }], autocomplete: [{
1939
+ type: Input
1940
+ }], showSearch: [{
1941
+ type: Input
1942
+ }], optionDisplayProperty: [{
1943
+ type: Input
1944
+ }], optionsSelected: [{
1945
+ type: Output
1946
+ }], selectionCleared: [{
1947
+ type: Output
1948
+ }], blurEvent: [{
1949
+ type: Output
1950
+ }], options: [{
1951
+ type: Input
1952
+ }], disabled: [{
1953
+ type: Input
1954
+ }], dropdown: [{
1955
+ type: ViewChild,
1956
+ args: ["dropdown", { static: false }]
1957
+ }], input: [{
1958
+ type: ViewChild,
1959
+ args: ["input", { static: false }]
1960
+ }], handleWindowKeydown: [{
1961
+ type: HostListener,
1962
+ args: ["window:keydown", ["$event"]]
1963
+ }] } });
1964
+
1965
+ class SelectControl {
1966
+ subscription = new Subscription();
1967
+ optionDisplayProperty = "displayname";
1968
+ required = false;
1969
+ placeholder;
1970
+ customClass;
1971
+ clearVal = true;
1972
+ deFocus = true;
1973
+ error = false;
1974
+ errorMessage = "";
1975
+ autocomplete = "";
1976
+ inputLoader = false;
1977
+ isAddNewItem = false;
1978
+ optionSelected = new EventEmitter();
1979
+ selectionCleared = new EventEmitter();
1980
+ addNewItemClicked = new EventEmitter();
1981
+ blurEvent = new EventEmitter();
1982
+ optionPatched = new EventEmitter();
1983
+ title;
1984
+ _options = [];
1985
+ set options(opts) {
1986
+ this._options = opts;
1987
+ this.filteredOptions = [...this._options];
1988
+ this.highlightSelectedItem();
1989
+ this.processValueBuffer();
1990
+ }
1991
+ get options() {
1992
+ return this._options;
1993
+ }
1994
+ get disabled() {
1995
+ return this._disabled;
1996
+ }
1997
+ set disabled(value) {
1998
+ this._disabled = value;
1999
+ if (this.inputControl) {
2000
+ if (value) {
2001
+ this.inputControl.disable({ emitEvent: false });
2002
+ }
2003
+ else {
2004
+ this.inputControl.enable({ emitEvent: false });
2005
+ }
2006
+ }
2007
+ }
2008
+ dropdown;
2009
+ input;
2010
+ filteredOptions = [];
2011
+ keySearch = "";
2012
+ selectedItem = null;
2013
+ onSearch = new Subject();
2014
+ inputControl = new FormControl({ value: "", disabled: false });
2015
+ isDropdownOpen = false;
2016
+ highlightedIndex = null;
2017
+ onChange = () => { };
2018
+ onTouched = () => { };
2019
+ valueBuffer = null;
2020
+ _disabled = false;
2021
+ dropdownInitialized = false;
2022
+ popperInstance;
2023
+ ngOnInit() {
2024
+ this.subscription = this.onSearch
2025
+ .pipe(debounceTime(300), distinctUntilChanged())
2026
+ .subscribe((searchText) => {
2027
+ this.focusOption(searchText);
2028
+ this.keySearch = "";
2029
+ });
2030
+ this.filteredOptions = this._options;
2031
+ // Sync the input control value with the selected item
2032
+ this.inputControl.valueChanges.subscribe((value) => {
2033
+ this.onChange(value);
2034
+ });
2035
+ }
2036
+ ngAfterViewChecked() {
2037
+ if (this.isDropdownOpen && !this.dropdownInitialized) {
2038
+ this.createPopperInstance();
2039
+ this.dropdownInitialized = true;
2040
+ }
2041
+ }
2042
+ ngOnChanges(changes) {
2043
+ if (changes["options"]) {
2044
+ this.filteredOptions = [...this._options];
2045
+ this.highlightSelectedItem();
2046
+ }
2047
+ if (changes["disabled"]) {
2048
+ this.disabled = changes["disabled"].currentValue;
2049
+ }
2050
+ }
2051
+ ngOnDestroy() {
2052
+ this.subscription.unsubscribe();
2053
+ }
2054
+ writeValue(value) {
2055
+ if (this.inputControl.disabled !== this.disabled) {
2056
+ this.setDisabledState(this.disabled);
2057
+ }
2058
+ this.valueBuffer = value;
2059
+ this.processValueBuffer();
2060
+ }
2061
+ processValueBuffer() {
2062
+ if (this.valueBuffer !== null &&
2063
+ this._options &&
2064
+ this._options.length > 0) {
2065
+ // Try to match selected item by ID
2066
+ const matchedItem = this._options.find((option) => option.id === this.valueBuffer);
2067
+ if (matchedItem) {
2068
+ this.selectedItem = matchedItem;
2069
+ const displayValue = matchedItem[this.optionDisplayProperty];
2070
+ this.inputControl.setValue(displayValue, { emitEvent: false });
2071
+ this.filteredOptions = [...this._options];
2072
+ this.onTouched();
2073
+ this.inputControl.markAsDirty();
2074
+ this.optionPatched.emit(this.selectedItem);
2075
+ }
2076
+ else {
2077
+ // If not found, don't clear the field automatically
2078
+ // You may choose to keep it or clear it. Here we clear it.
2079
+ this.selectedItem = null;
2080
+ this.inputControl.setValue("", { emitEvent: false });
2081
+ this.highlightedIndex = null;
2082
+ }
2083
+ }
2084
+ else if (this.valueBuffer === null) {
2085
+ this.selectedItem = null;
2086
+ this.inputControl.setValue("", { emitEvent: false });
2087
+ this.highlightedIndex = null;
2088
+ }
2089
+ }
2090
+ registerOnChange(fn) {
2091
+ this.onChange = fn;
2092
+ }
2093
+ registerOnTouched(fn) {
2094
+ this.onTouched = fn;
2095
+ }
2096
+ // events
2097
+ onBlur() {
2098
+ this.blurEvent.emit();
2099
+ this.onTouched();
2100
+ setTimeout(() => {
2101
+ this.isDropdownOpen = false;
2102
+ }, 300);
2103
+ }
2104
+ onKeyDown(event) {
2105
+ const isAlphabetOrNumberKey = /^[a-zA-Z0-9]$/.test(event.key);
2106
+ if (this.isDropdownOpen) {
2107
+ switch (event.key) {
2108
+ case "ArrowDown":
2109
+ this.highlightNext();
2110
+ event.preventDefault();
2111
+ break;
2112
+ case "ArrowUp":
2113
+ this.highlightPrevious();
2114
+ event.preventDefault();
2115
+ break;
2116
+ case "Enter":
2117
+ if (this.highlightedIndex !== null) {
2118
+ this.selectOption(this.filteredOptions[this.highlightedIndex]);
2119
+ }
2120
+ event.preventDefault();
2121
+ break;
2122
+ case "Escape":
2123
+ this.isDropdownOpen = false;
2124
+ break;
2125
+ default:
2126
+ if (isAlphabetOrNumberKey) {
2127
+ this.keySearch += event.key.toString();
2128
+ if (this.keySearch !== "") {
2129
+ this.onSearch.next(this.keySearch);
2130
+ }
2131
+ }
2132
+ break;
2133
+ }
2134
+ }
2135
+ else if (event.key === " ") {
2136
+ this.toggleDropdown();
2137
+ event.preventDefault();
2138
+ }
2139
+ }
2140
+ setDisabledState(isDisabled) {
2141
+ this.disabled = isDisabled;
2142
+ if (this.disabled) {
2143
+ this.inputControl.disable({ emitEvent: false });
2144
+ }
2145
+ else {
2146
+ this.inputControl.enable({ emitEvent: false });
2147
+ }
2148
+ }
2149
+ selectOption(option) {
2150
+ if (this.selectedItem !== option) {
2151
+ this.selectedItem = option;
2152
+ this.isDropdownOpen = false;
2153
+ const displayValue = this.selectedItem[this.optionDisplayProperty];
2154
+ this.inputControl.setValue(displayValue, { emitEvent: false });
2155
+ this.onChange(this.selectedItem.id);
2156
+ this.onTouched();
2157
+ this.inputControl.markAsDirty();
2158
+ this.optionSelected.emit(this.selectedItem);
2159
+ }
2160
+ }
2161
+ // highlight
2162
+ highlightSelectedItem() {
2163
+ const selectedIndex = this.filteredOptions.findIndex((option) => option === this.selectedItem);
2164
+ if (selectedIndex !== -1) {
2165
+ this.highlightedIndex = selectedIndex;
2166
+ this.scrollToHighlighted();
2167
+ }
2168
+ else {
2169
+ this.highlightedIndex = null;
2170
+ }
2171
+ }
2172
+ highlightNext() {
2173
+ if (this.highlightedIndex === null ||
2174
+ this.highlightedIndex === this.filteredOptions.length - 1) {
2175
+ this.highlightedIndex = 0;
2176
+ }
2177
+ else {
2178
+ this.highlightedIndex++;
2179
+ }
2180
+ this.scrollToHighlighted();
2181
+ }
2182
+ highlightPrevious() {
2183
+ if (this.highlightedIndex === null || this.highlightedIndex === 0) {
2184
+ this.highlightedIndex = this.filteredOptions.length - 1;
2185
+ }
2186
+ else {
2187
+ this.highlightedIndex--;
2188
+ }
2189
+ this.scrollToHighlighted();
2190
+ }
2191
+ focusOption(value) {
2192
+ const filterValue = value.toLowerCase();
2193
+ let foundIndex = this._options.findIndex((option) => option[this.optionDisplayProperty]?.toLowerCase() === filterValue);
2194
+ if (foundIndex === -1) {
2195
+ foundIndex = this._options.findIndex((option) => option[this.optionDisplayProperty]
2196
+ ?.toLowerCase()
2197
+ .startsWith(filterValue));
2198
+ }
2199
+ if (foundIndex === -1) {
2200
+ foundIndex = this._options.findIndex((option) => option[this.optionDisplayProperty]?.toLowerCase().includes(filterValue));
2201
+ }
2202
+ if (foundIndex !== -1) {
2203
+ this.highlightedIndex = foundIndex;
2204
+ this.scrollToHighlighted();
2205
+ }
2206
+ else {
2207
+ this.highlightedIndex = null;
2208
+ }
2209
+ }
2210
+ scrollToHighlighted() {
2211
+ setTimeout(() => {
2212
+ const container = document.querySelector(".option-list");
2213
+ if (container) {
2214
+ const highlighted = container.querySelector(".highlighted");
2215
+ if (highlighted) {
2216
+ highlighted.scrollIntoView({ block: "nearest" });
2217
+ }
2218
+ }
2219
+ }, 0);
2220
+ }
2221
+ // actions
2222
+ onAddNewItemClick() {
2223
+ this.addNewItemClicked.emit();
2224
+ }
2225
+ // popper
2226
+ createPopperInstance() {
2227
+ if (this.dropdown) {
2228
+ this.popperInstance = createPopper(this.input.nativeElement, this.dropdown.nativeElement, {
2229
+ placement: "bottom-start",
2230
+ modifiers: [
2231
+ {
2232
+ name: "offset",
2233
+ options: {
2234
+ offset: [0, 1],
2235
+ },
2236
+ },
2237
+ {
2238
+ name: "flip",
2239
+ options: {
2240
+ fallbackPlacements: ["top-start", "bottom-start"],
2241
+ },
2242
+ },
2243
+ ],
2244
+ });
2245
+ }
2246
+ }
2247
+ // toggle, open, hide, reset and clear
2248
+ toggleDropdown() {
2249
+ if (!this.inputControl.disabled) {
2250
+ this.isDropdownOpen = !this.isDropdownOpen;
2251
+ this.dropdownInitialized = false;
2252
+ if (this.isDropdownOpen && this.selectedItem) {
2253
+ this.highlightSelectedItem();
2254
+ }
2255
+ }
2256
+ }
2257
+ openDropdown(event) {
2258
+ if (event.ctrlKey && event.key === "Enter") {
2259
+ this.toggleDropdown();
2260
+ }
2261
+ }
2262
+ selectListOutsideClick(e) {
2263
+ this.isDropdownOpen = false;
2264
+ }
2265
+ hideDropdown() {
2266
+ setTimeout(() => {
2267
+ this.isDropdownOpen = false;
2268
+ }, 200);
2269
+ }
2270
+ resetSelection() {
2271
+ this.selectionCleared.emit();
2272
+ this.selectedItem = null;
2273
+ this.inputControl.setValue("", { emitEvent: false });
2274
+ this.onChange(null);
2275
+ this.onTouched();
2276
+ this.valueBuffer = null;
2277
+ this.isDropdownOpen = false;
2278
+ }
2279
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: SelectControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
2280
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.3", type: SelectControl, isStandalone: true, selector: "select-control", inputs: { optionDisplayProperty: "optionDisplayProperty", required: "required", placeholder: "placeholder", customClass: "customClass", clearVal: "clearVal", deFocus: "deFocus", error: "error", errorMessage: "errorMessage", autocomplete: "autocomplete", inputLoader: "inputLoader", isAddNewItem: "isAddNewItem", title: "title", options: "options", disabled: "disabled" }, outputs: { optionSelected: "optionSelected", selectionCleared: "selectionCleared", addNewItemClicked: "addNewItemClicked", blurEvent: "blurEvent", optionPatched: "optionPatched" }, providers: [
2281
+ {
2282
+ provide: NG_VALUE_ACCESSOR,
2283
+ useExisting: forwardRef(() => SelectControl),
2284
+ multi: true,
2285
+ },
2286
+ ], viewQueries: [{ propertyName: "dropdown", first: true, predicate: ["dropdown"], descendants: true }, { propertyName: "input", first: true, predicate: ["input"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"form-group select\" [ngClass]=\"customClass\" (clickOutside)=\"selectListOutsideClick($event)\">\r\n @if (title) {\r\n <label class=\"inp-label\" [ngClass]=\"{ 'required': required }\">{{ title }}</label>\r\n }\r\n\r\n <input #input type=\"text\" class=\"form-control\" [placeholder]=\"placeholder ? placeholder : ''\"\r\n [formControl]=\"inputControl\" [ngClass]=\"{ 'is-invalid': error }\" (blur)=\"onBlur()\" (click)=\"toggleDropdown()\"\r\n (keydown)=\"onKeyDown($event)\" readonly [attr.autocomplete]=\"autocomplete || null\" />\r\n\r\n @if (deFocus) {\r\n <span class=\"focus-border\"></span>\r\n }\r\n\r\n @if (!inputLoader && isDropdownOpen && selectedItem && clearVal) {\r\n <label class=\"clear\" (click)=\"resetSelection()\">\r\n <i class=\"he he-close\"></i>\r\n </label> }\r\n @if (!inputLoader) {\r\n <label class=\"arrow\">\r\n <i class=\"he he-chevron-down\"></i>\r\n </label>\r\n }\r\n\r\n @if (isDropdownOpen) {\r\n <div #dropdown class=\"option-list\">\r\n <div class=\"no-results\" *ngIf=\"filteredOptions.length === 0\">\r\n <div>No results found</div>\r\n <div *ngIf=\"isAddNewItem\" (click)=\"onAddNewItemClick()\" class=\"btn-new\">Add New Item</div>\r\n </div>\r\n\r\n @for (option of filteredOptions; track option[optionDisplayProperty]; let idx = $index) {\r\n <div class=\"list-item\" [ngClass]=\"{\r\n 'active': option === selectedItem,\r\n 'highlighted': idx === highlightedIndex\r\n }\" (click)=\"selectOption(option)\">\r\n @if (option.countryCode) {\r\n <img src=\"https://flagcdn.com/w80/{{ option.countryCode }}.png\" width=\"20\" alt=\"{{ option.countryCode }} flag\" loading=\"lazy\" />\r\n }\r\n {{ option[optionDisplayProperty] }}\r\n </div>\r\n }\r\n\r\n </div>\r\n }\r\n\r\n @if (inputLoader) {\r\n <label class=\"loader input-loader\"></label>\r\n }\r\n\r\n @if (error) {\r\n <div class=\"val-msg\">{{ errorMessage }}</div>\r\n }\r\n</div>", styles: [".form-group.select .option-list .no-results{padding:10px;color:#9b9b9b;text-align:center}.form-group.select .option-list .no-results .btn-new{padding:5px 0;cursor:pointer;color:#0d6efd}.form-group.select .clear{right:30px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: ClickOutsideDirective, selector: "[clickOutside]", outputs: ["clickOutside"] }] });
2287
+ }
2288
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: SelectControl, decorators: [{
2289
+ type: Component,
2290
+ args: [{ selector: "select-control", standalone: true, imports: [
2291
+ CommonModule,
2292
+ ReactiveFormsModule,
2293
+ ClickOutsideDirective
2294
+ ], providers: [
2295
+ {
2296
+ provide: NG_VALUE_ACCESSOR,
2297
+ useExisting: forwardRef(() => SelectControl),
2298
+ multi: true,
2299
+ },
2300
+ ], template: "<div class=\"form-group select\" [ngClass]=\"customClass\" (clickOutside)=\"selectListOutsideClick($event)\">\r\n @if (title) {\r\n <label class=\"inp-label\" [ngClass]=\"{ 'required': required }\">{{ title }}</label>\r\n }\r\n\r\n <input #input type=\"text\" class=\"form-control\" [placeholder]=\"placeholder ? placeholder : ''\"\r\n [formControl]=\"inputControl\" [ngClass]=\"{ 'is-invalid': error }\" (blur)=\"onBlur()\" (click)=\"toggleDropdown()\"\r\n (keydown)=\"onKeyDown($event)\" readonly [attr.autocomplete]=\"autocomplete || null\" />\r\n\r\n @if (deFocus) {\r\n <span class=\"focus-border\"></span>\r\n }\r\n\r\n @if (!inputLoader && isDropdownOpen && selectedItem && clearVal) {\r\n <label class=\"clear\" (click)=\"resetSelection()\">\r\n <i class=\"he he-close\"></i>\r\n </label> }\r\n @if (!inputLoader) {\r\n <label class=\"arrow\">\r\n <i class=\"he he-chevron-down\"></i>\r\n </label>\r\n }\r\n\r\n @if (isDropdownOpen) {\r\n <div #dropdown class=\"option-list\">\r\n <div class=\"no-results\" *ngIf=\"filteredOptions.length === 0\">\r\n <div>No results found</div>\r\n <div *ngIf=\"isAddNewItem\" (click)=\"onAddNewItemClick()\" class=\"btn-new\">Add New Item</div>\r\n </div>\r\n\r\n @for (option of filteredOptions; track option[optionDisplayProperty]; let idx = $index) {\r\n <div class=\"list-item\" [ngClass]=\"{\r\n 'active': option === selectedItem,\r\n 'highlighted': idx === highlightedIndex\r\n }\" (click)=\"selectOption(option)\">\r\n @if (option.countryCode) {\r\n <img src=\"https://flagcdn.com/w80/{{ option.countryCode }}.png\" width=\"20\" alt=\"{{ option.countryCode }} flag\" loading=\"lazy\" />\r\n }\r\n {{ option[optionDisplayProperty] }}\r\n </div>\r\n }\r\n\r\n </div>\r\n }\r\n\r\n @if (inputLoader) {\r\n <label class=\"loader input-loader\"></label>\r\n }\r\n\r\n @if (error) {\r\n <div class=\"val-msg\">{{ errorMessage }}</div>\r\n }\r\n</div>", styles: [".form-group.select .option-list .no-results{padding:10px;color:#9b9b9b;text-align:center}.form-group.select .option-list .no-results .btn-new{padding:5px 0;cursor:pointer;color:#0d6efd}.form-group.select .clear{right:30px}\n"] }]
2301
+ }], propDecorators: { optionDisplayProperty: [{
2302
+ type: Input
2303
+ }], required: [{
2304
+ type: Input
2305
+ }], placeholder: [{
2306
+ type: Input
2307
+ }], customClass: [{
2308
+ type: Input
2309
+ }], clearVal: [{
2310
+ type: Input
2311
+ }], deFocus: [{
2312
+ type: Input
2313
+ }], error: [{
2314
+ type: Input
2315
+ }], errorMessage: [{
2316
+ type: Input
2317
+ }], autocomplete: [{
2318
+ type: Input
2319
+ }], inputLoader: [{
2320
+ type: Input
2321
+ }], isAddNewItem: [{
2322
+ type: Input
2323
+ }], optionSelected: [{
2324
+ type: Output
2325
+ }], selectionCleared: [{
2326
+ type: Output
2327
+ }], addNewItemClicked: [{
2328
+ type: Output
2329
+ }], blurEvent: [{
2330
+ type: Output
2331
+ }], optionPatched: [{
2332
+ type: Output
2333
+ }], title: [{
2334
+ type: Input
2335
+ }], options: [{
2336
+ type: Input
2337
+ }], disabled: [{
2338
+ type: Input
2339
+ }], dropdown: [{
2340
+ type: ViewChild,
2341
+ args: ["dropdown", { static: false }]
2342
+ }], input: [{
2343
+ type: ViewChild,
2344
+ args: ["input", { static: false }]
2345
+ }] } });
2346
+
2347
+ class SwitchControl {
2348
+ _renderer;
2349
+ _elementRef;
2350
+ title;
2351
+ customClass;
2352
+ readonly = false;
2353
+ set disabled(value) {
2354
+ this._disabled = value;
2355
+ this.setDisabledState(value);
2356
+ }
2357
+ set checked(value) {
2358
+ this._checked = value;
2359
+ this.switchControl.setValue(value, { emitEvent: false }); // set without emitting
2360
+ }
2361
+ switchChange = new EventEmitter();
2362
+ blurEvent = new EventEmitter();
2363
+ switchContainer;
2364
+ get checked() {
2365
+ return this._checked;
2366
+ }
2367
+ _checked = false;
2368
+ _disabled = false;
2369
+ switchControl = new FormControl({ value: false, disabled: this._disabled });
2370
+ onChange = () => { };
2371
+ onTouched = () => { };
2372
+ constructor(_renderer, _elementRef) {
2373
+ this._renderer = _renderer;
2374
+ this._elementRef = _elementRef;
2375
+ this.switchControl.valueChanges.subscribe(value => {
2376
+ if (this.readonly)
2377
+ return;
2378
+ this.onChange(value ?? false);
2379
+ this.onTouched();
2380
+ this.switchChange.emit(value ?? false);
2381
+ });
2382
+ }
2383
+ writeValue(value) {
2384
+ if (value !== undefined) {
2385
+ this.switchControl.setValue(value, { emitEvent: false });
2386
+ }
2387
+ }
2388
+ registerOnChange(fn) {
2389
+ this.onChange = fn;
2390
+ }
2391
+ registerOnTouched(fn) {
2392
+ this.onTouched = fn;
2393
+ }
2394
+ // events
2395
+ onFocus() {
2396
+ this.switchContainer.nativeElement.classList.add('focused');
2397
+ }
2398
+ onBlur() {
2399
+ this.blurEvent.emit();
2400
+ this.onTouched();
2401
+ this.switchContainer.nativeElement.classList.remove('focused');
2402
+ }
2403
+ setDisabledState(isDisabled) {
2404
+ this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
2405
+ if (isDisabled) {
2406
+ this.switchControl.disable();
2407
+ }
2408
+ else {
2409
+ this.switchControl.enable();
2410
+ }
2411
+ }
2412
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: SwitchControl, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
2413
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.3", type: SwitchControl, isStandalone: true, selector: "switch-control", inputs: { title: "title", customClass: "customClass", readonly: "readonly", disabled: "disabled", checked: "checked" }, outputs: { switchChange: "switchChange", blurEvent: "blurEvent" }, providers: [
2414
+ {
2415
+ provide: NG_VALUE_ACCESSOR,
2416
+ useExisting: forwardRef(() => SwitchControl),
2417
+ multi: true,
2418
+ },
2419
+ ], viewQueries: [{ propertyName: "switchContainer", first: true, predicate: ["switchContainer"], descendants: true }], ngImport: i0, template: "<label class=\"switch-container\" [ngClass]=\"[customClass, readonly ? 'readonly' : '']\" #switchContainer>\r\n <input type=\"checkbox\" [formControl]=\"switchControl\" (focus)=\"onFocus()\" (blur)=\"onBlur()\">\r\n <span class=\"switch-slider\"></span>\r\n</label>", styles: [".switch-container{top:4px;width:32px;height:14px;position:relative;display:inline-block}.switch-container .switch-slider{inset:0;cursor:pointer;transition:.4s;position:absolute;border-radius:34px;background-color:#ccc}.switch-container .switch-slider:before{left:0;content:\"\";width:18px;bottom:-2px;height:18px;transition:.4s;position:absolute;border-radius:50%;border:1px solid #cccccc;background-color:#fff}.switch-container input{width:0;height:0;opacity:0}.switch-container input:checked+.switch-slider{background-color:#12ceb7}.switch-container input:checked+.switch-slider:before{transform:translate(14px);border:1px solid #12ceb7}.switch-container input:focus+.switch-slider{box-shadow:0 0 1px #12ceb7}.switch-container.square .switch-slider{border-radius:0}.switch-container.square .switch-slider:before{border-radius:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
2420
+ }
2421
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: SwitchControl, decorators: [{
2422
+ type: Component,
2423
+ args: [{ selector: 'switch-control', standalone: true, imports: [
2424
+ CommonModule,
2425
+ ReactiveFormsModule
2426
+ ], providers: [
2427
+ {
2428
+ provide: NG_VALUE_ACCESSOR,
2429
+ useExisting: forwardRef(() => SwitchControl),
2430
+ multi: true,
2431
+ },
2432
+ ], template: "<label class=\"switch-container\" [ngClass]=\"[customClass, readonly ? 'readonly' : '']\" #switchContainer>\r\n <input type=\"checkbox\" [formControl]=\"switchControl\" (focus)=\"onFocus()\" (blur)=\"onBlur()\">\r\n <span class=\"switch-slider\"></span>\r\n</label>", styles: [".switch-container{top:4px;width:32px;height:14px;position:relative;display:inline-block}.switch-container .switch-slider{inset:0;cursor:pointer;transition:.4s;position:absolute;border-radius:34px;background-color:#ccc}.switch-container .switch-slider:before{left:0;content:\"\";width:18px;bottom:-2px;height:18px;transition:.4s;position:absolute;border-radius:50%;border:1px solid #cccccc;background-color:#fff}.switch-container input{width:0;height:0;opacity:0}.switch-container input:checked+.switch-slider{background-color:#12ceb7}.switch-container input:checked+.switch-slider:before{transform:translate(14px);border:1px solid #12ceb7}.switch-container input:focus+.switch-slider{box-shadow:0 0 1px #12ceb7}.switch-container.square .switch-slider{border-radius:0}.switch-container.square .switch-slider:before{border-radius:0}\n"] }]
2433
+ }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }], propDecorators: { title: [{
2434
+ type: Input
2435
+ }], customClass: [{
2436
+ type: Input
2437
+ }], readonly: [{
2438
+ type: Input
2439
+ }], disabled: [{
2440
+ type: Input
2441
+ }], checked: [{
2442
+ type: Input
2443
+ }], switchChange: [{
2444
+ type: Output
2445
+ }], blurEvent: [{
2446
+ type: Output
2447
+ }], switchContainer: [{
2448
+ type: ViewChild,
2449
+ args: ['switchContainer']
2450
+ }] } });
2451
+
2452
+ class TextEditor {
2453
+ header = true;
2454
+ media = true;
2455
+ link = true;
2456
+ placeholder = 'Type here...';
2457
+ editorRef;
2458
+ onChange = () => { };
2459
+ onTouch = () => { };
2460
+ undoStack = [];
2461
+ redoStack = [];
2462
+ ngOnInit() { }
2463
+ ngAfterViewInit() {
2464
+ this.saveState();
2465
+ }
2466
+ writeValue(value) {
2467
+ this.editorRef.nativeElement.innerHTML = value || '';
2468
+ this.saveState();
2469
+ }
2470
+ registerOnChange(fn) {
2471
+ this.onChange = fn;
2472
+ }
2473
+ registerOnTouched(fn) {
2474
+ this.onTouch = fn;
2475
+ }
2476
+ onInput() {
2477
+ const html = this.editorRef.nativeElement.innerHTML;
2478
+ this.onChange(html);
2479
+ this.saveState();
2480
+ }
2481
+ // events
2482
+ onTouched() {
2483
+ this.onTouch();
2484
+ }
2485
+ setDisabledState(isDisabled) {
2486
+ this.editorRef.nativeElement.contentEditable = (!isDisabled).toString();
2487
+ }
2488
+ // ---- Selection & Range Utilities ----
2489
+ getSelectionRange() {
2490
+ const selection = window.getSelection();
2491
+ if (selection && selection.rangeCount > 0) {
2492
+ return selection.getRangeAt(0);
2493
+ }
2494
+ return null;
2495
+ }
2496
+ wrapSelection(htmlTag, style) {
2497
+ const range = this.getSelectionRange();
2498
+ if (!range || range.collapsed)
2499
+ return; // no selection
2500
+ const selectedText = range.extractContents();
2501
+ const el = document.createElement(htmlTag);
2502
+ if (style) {
2503
+ el.setAttribute('style', style);
2504
+ }
2505
+ el.appendChild(selectedText);
2506
+ range.insertNode(el);
2507
+ this.mergeAdjacentSimilarElements(el);
2508
+ this.onInput();
2509
+ }
2510
+ // Merges adjacent spans with the same style to avoid clutter
2511
+ mergeAdjacentSimilarElements(el) {
2512
+ const parent = el.parentElement;
2513
+ if (!parent)
2514
+ return;
2515
+ const siblings = Array.from(parent.childNodes);
2516
+ for (let i = 0; i < siblings.length - 1; i++) {
2517
+ const current = siblings[i];
2518
+ const next = siblings[i + 1];
2519
+ if (current.nodeType === 1 && next?.nodeType === 1) {
2520
+ if (current.tagName === next.tagName && current.getAttribute('style') === next.getAttribute('style')) {
2521
+ // merge them
2522
+ while (next.firstChild) {
2523
+ current.appendChild(next.firstChild);
2524
+ }
2525
+ next.remove();
2526
+ }
2527
+ }
2528
+ }
2529
+ }
2530
+ // ---- Inline Formatting ----
2531
+ // applyInlineStyle() toggles inline styles by wrapping selection in a styled span
2532
+ applyInlineStyle(styleType) {
2533
+ let style = '';
2534
+ if (styleType === 'bold')
2535
+ style = 'font-weight:bold;';
2536
+ else if (styleType === 'italic')
2537
+ style = 'font-style:italic;';
2538
+ else if (styleType === 'underline')
2539
+ style = 'text-decoration:underline;';
2540
+ this.wrapSelection('span', style);
2541
+ }
2542
+ applyColor(event) {
2543
+ const input = event.target;
2544
+ const color = input.value;
2545
+ // Now `color` is a string and `input` is never null
2546
+ this.wrapSelection('span', `color:${color};`);
2547
+ }
2548
+ applyHighlight(event) {
2549
+ const input = event.target;
2550
+ const color = input.value;
2551
+ this.wrapSelection('span', `background-color:${color};`);
2552
+ }
2553
+ // ---- Block Formatting (Headings, Paragraph) ----
2554
+ // Replaces the parent block element containing selection with a new block type
2555
+ applyBlockFormat(blockTag) {
2556
+ const range = this.getSelectionRange();
2557
+ if (!range)
2558
+ return;
2559
+ // Find the nearest block element
2560
+ let block = this.findBlockAncestor(range.commonAncestorContainer);
2561
+ if (!block) {
2562
+ // If no block found, wrap the selection in a block
2563
+ const wrapper = document.createElement(blockTag);
2564
+ wrapper.appendChild(range.extractContents());
2565
+ range.insertNode(wrapper);
2566
+ }
2567
+ else {
2568
+ // Replace the block with a new block type
2569
+ const newBlock = document.createElement(blockTag);
2570
+ // Move children
2571
+ while (block.firstChild) {
2572
+ newBlock.appendChild(block.firstChild);
2573
+ }
2574
+ block.replaceWith(newBlock);
2575
+ }
2576
+ this.onInput();
2577
+ }
2578
+ findBlockAncestor(node) {
2579
+ while (node && node !== this.editorRef.nativeElement) {
2580
+ if (this.isBlockElement(node)) {
2581
+ return node;
2582
+ }
2583
+ node = node.parentNode;
2584
+ }
2585
+ return null;
2586
+ }
2587
+ isBlockElement(node) {
2588
+ if (node.nodeType !== 1)
2589
+ return false;
2590
+ const display = window.getComputedStyle(node).display;
2591
+ return display === 'block' || display === 'list-item';
2592
+ }
2593
+ // ---- Lists ----
2594
+ // Convert selected lines into a list
2595
+ applyList(listType) {
2596
+ const range = this.getSelectionRange();
2597
+ if (!range)
2598
+ return;
2599
+ const commonBlock = this.findBlockAncestor(range.commonAncestorContainer);
2600
+ // Get the text in the selection
2601
+ const content = range.extractContents();
2602
+ const lines = this.splitContentByLine(content);
2603
+ const listEl = document.createElement(listType);
2604
+ for (const line of lines) {
2605
+ const li = document.createElement('li');
2606
+ li.appendChild(line);
2607
+ listEl.appendChild(li);
2608
+ }
2609
+ if (commonBlock) {
2610
+ // Insert the list right where the block was or at the selection
2611
+ range.insertNode(listEl);
2612
+ }
2613
+ else {
2614
+ // If no common block, just insert at current position
2615
+ range.insertNode(listEl);
2616
+ }
2617
+ this.onInput();
2618
+ }
2619
+ splitContentByLine(fragment) {
2620
+ // A very naive approach: split by <br> or block elements.
2621
+ // For advanced logic, detect line breaks more thoroughly.
2622
+ const lines = [];
2623
+ let currentFrag = document.createDocumentFragment();
2624
+ Array.from(fragment.childNodes).forEach((node) => {
2625
+ if (node.nodeName === 'BR' || this.isBlockElement(node)) {
2626
+ // This node signifies a new line
2627
+ lines.push(currentFrag);
2628
+ currentFrag = document.createDocumentFragment();
2629
+ if (this.isBlockElement(node)) {
2630
+ while (node.firstChild) {
2631
+ currentFrag.appendChild(node.firstChild);
2632
+ }
2633
+ lines.push(currentFrag);
2634
+ currentFrag = document.createDocumentFragment();
2635
+ }
2636
+ }
2637
+ else {
2638
+ currentFrag.appendChild(node);
2639
+ }
2640
+ });
2641
+ if (currentFrag.childNodes.length > 0) {
2642
+ lines.push(currentFrag);
2643
+ }
2644
+ return lines;
2645
+ }
2646
+ // ---- Links ----
2647
+ createLink() {
2648
+ const url = prompt('Enter URL', 'https://');
2649
+ if (!url)
2650
+ return;
2651
+ const range = this.getSelectionRange();
2652
+ if (!range || range.collapsed)
2653
+ return;
2654
+ const selectedContent = range.extractContents();
2655
+ const a = document.createElement('a');
2656
+ a.href = url;
2657
+ a.appendChild(selectedContent);
2658
+ range.insertNode(a);
2659
+ this.onInput();
2660
+ }
2661
+ // ---- Images ----
2662
+ insertImage() {
2663
+ const url = prompt('Enter image URL:');
2664
+ if (!url)
2665
+ return;
2666
+ const range = this.getSelectionRange();
2667
+ if (!range)
2668
+ return;
2669
+ const img = document.createElement('img');
2670
+ img.src = url;
2671
+ range.insertNode(img);
2672
+ this.onInput();
2673
+ }
2674
+ // ---- Alignment ----
2675
+ setAlignment(alignment) {
2676
+ // Find the block and set text-align
2677
+ const range = this.getSelectionRange();
2678
+ if (!range)
2679
+ return;
2680
+ const block = this.findBlockAncestor(range.commonAncestorContainer);
2681
+ if (block) {
2682
+ block.style.textAlign = alignment;
2683
+ this.onInput();
2684
+ }
2685
+ }
2686
+ // ---- Clear Formatting ----
2687
+ clearFormatting() {
2688
+ const html = this.editorRef.nativeElement.innerText;
2689
+ // Convert innerText to a plain <p> block for simplicity
2690
+ this.editorRef.nativeElement.innerHTML = `<p>${html}</p>`;
2691
+ this.onInput();
2692
+ }
2693
+ // ---- Undo/Redo ----
2694
+ saveState() {
2695
+ const currentHtml = this.editorRef.nativeElement.innerHTML;
2696
+ if (this.undoStack.length === 0 || this.undoStack[this.undoStack.length - 1].html !== currentHtml) {
2697
+ this.undoStack.push({ html: currentHtml });
2698
+ this.redoStack = []; // clear redo on new input
2699
+ }
2700
+ }
2701
+ // clear options
2702
+ undo() {
2703
+ if (this.undoStack.length > 1) {
2704
+ const current = this.undoStack.pop();
2705
+ this.redoStack.push(current);
2706
+ const previous = this.undoStack[this.undoStack.length - 1];
2707
+ this.editorRef.nativeElement.innerHTML = previous.html;
2708
+ this.onChange(previous.html);
2709
+ }
2710
+ }
2711
+ redo() {
2712
+ if (this.redoStack.length > 0) {
2713
+ const state = this.redoStack.pop();
2714
+ this.undoStack.push(state);
2715
+ this.editorRef.nativeElement.innerHTML = state.html;
2716
+ this.onChange(state.html);
2717
+ }
2718
+ }
2719
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TextEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
2720
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.3", type: TextEditor, isStandalone: true, selector: "app-text-editor", inputs: { header: "header", media: "media", link: "link", placeholder: "placeholder" }, viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }], ngImport: i0, template: "<div class=\"text-editor\">\r\n <div class=\"toolbar\">\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"applyInlineStyle('bold')\" tooltip=\"Bold\">\r\n <i class=\"he he-bold\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyInlineStyle('italic')\" tooltip=\"italic\">\r\n <i class=\"he he-italics\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyInlineStyle('underline')\" tooltip=\"Underline\">\r\n <i class=\"he he-underline\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\" *ngIf=\"header\">\r\n <button type=\"button\" (click)=\"applyBlockFormat('h1')\" tooltip=\"Header 1\">\r\n <i class=\"he he-heading-1\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyBlockFormat('h2')\" tooltip=\"Header 2\">\r\n <i class=\"he he-heading-2\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items more-gap\">\r\n <div class=\"input-wrap\" tooltip=\"Text Color\">\r\n <i class=\"he he-text-drop\"></i>\r\n <input type=\"color\" (change)=\"applyColor($event)\" />\r\n </div>\r\n <div class=\"input-wrap\" tooltip=\"Background Color\">\r\n <i class=\"he he-background-drop\"></i>\r\n <input type=\"color\" (change)=\"applyHighlight($event)\" />\r\n </div>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"setAlignment('left')\" tooltip=\"Justify Left\">\r\n <i class=\"he he-left-align\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"setAlignment('center')\" tooltip=\"Justify Center\">\r\n <i class=\"he he-center-align\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"setAlignment('right')\" tooltip=\"Justify Right\">\r\n <i class=\"he he-right-align\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"setAlignment('justify')\" tooltip=\"Justify Full\">\r\n <i class=\"he he-justify\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" tooltip=\"Indent\">\r\n <i class=\"he he-text-indent-left\"></i>\r\n </button>\r\n <button type=\"button\" tooltip=\"Outdent\">\r\n <i class=\"he he-text-indent-right\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\" *ngIf=\"media\">\r\n <button type=\"button\" (click)=\"applyList('ul')\" tooltip=\"Unordered list\">\r\n <i class=\"he he-unordered-list\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyList('ol')\" tooltip=\"Ordered list\">\r\n <i class=\"he he-ordered-list\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\" *ngIf=\"link\">\r\n <button type=\"button\" (click)=\"createLink()\" tooltip=\"Link\">\r\n <i class=\"he he-link\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"insertImage()\" tooltip=\"Image\">\r\n <i class=\"he he-image\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"undo()\" tooltip=\"Undo\">\r\n <i class=\"he he-undo\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"redo()\" tooltip=\"Redo\">\r\n <i class=\"he he-redo\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"clearFormatting()\" tooltip=\"Clear Formatting\">\r\n <i class=\"he he-text-clear-format\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div #editor class=\"editor-content\" contenteditable=\"true\" (input)=\"onInput()\" (blur)=\"onTouched()\"\r\n [attr.data-placeholder]=\"placeholder\">\r\n </div>\r\n</div>", styles: [".text-editor{border:1px solid #ccc}.text-editor .toolbar{gap:.75rem;display:flex;flex-wrap:wrap;background:#f8f8f8;padding:.75rem;border-bottom:1px solid #cccccc}.text-editor .toolbar .toolbar-items{height:18px;display:flex;flex-wrap:wrap;align-items:center;padding-right:.75rem;border-right:1px solid #969090}.text-editor .toolbar .toolbar-items button,.text-editor .toolbar .toolbar-items .input-wrap{cursor:pointer;min-width:25px;min-height:20px}.text-editor .toolbar .toolbar-items button:hover i,.text-editor .toolbar .toolbar-items .input-wrap:hover i{color:#000}.text-editor .toolbar .toolbar-items button{border:0;display:grid;place-items:center;background:transparent}.text-editor .toolbar .toolbar-items button:hover{background:#eee}.text-editor .toolbar .toolbar-items .input-wrap{gap:2px;display:flex;cursor:pointer;align-items:center;flex-direction:column;justify-content:center}.text-editor .toolbar .toolbar-items .input-wrap input[type=color]{width:80%;padding:0;height:2px;border:none;cursor:inherit}.text-editor .toolbar .toolbar-items i{font-size:13px;color:#3c4148}.text-editor .toolbar .toolbar-items i.he-bold{font-weight:600}.text-editor .toolbar .toolbar-items i.he-underline{font-size:14px}.text-editor .toolbar .toolbar-items i.he-text-drop{font-size:12px}.text-editor .toolbar .toolbar-items i.he-background-drop{top:-1px;font-size:12px}.text-editor .toolbar .toolbar-items i.he-heading-1,.text-editor .toolbar .toolbar-items i.he-link,.text-editor .toolbar .toolbar-items i.he-left-align,.text-editor .toolbar .toolbar-items i.he-center-align,.text-editor .toolbar .toolbar-items i.he-right-align,.text-editor .toolbar .toolbar-items i.he-justify{font-size:16px}.text-editor .toolbar .toolbar-items i.he-text-indent-left,.text-editor .toolbar .toolbar-items i.he-text-indent-right{font-size:17px}.text-editor .toolbar .toolbar-items i.he-heading-2{top:1px;font-size:16px}.text-editor .toolbar .toolbar-items i.he-unordered-list,.text-editor .toolbar .toolbar-items i.he-ordered-list{top:-1px;font-size:20px}.text-editor .toolbar .toolbar-items i.he-image{font-size:15px;font-weight:600}.text-editor .toolbar .toolbar-items i.he-redo,.text-editor .toolbar .toolbar-items i.he-undo{top:-1px;font-size:14px}.text-editor .toolbar .toolbar-items.more-gap{gap:5px}.text-editor .toolbar .toolbar-items:last-child{border-right:0}.text-editor .editor-content{outline:none;min-height:200px;position:relative;background:#fff;padding:.75rem}.text-editor .editor-content[data-placeholder]:empty:before{content:attr(data-placeholder);color:#aaa;pointer-events:none;position:absolute;left:.75rem;top:.75rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
2721
+ }
2722
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TextEditor, decorators: [{
2723
+ type: Component,
2724
+ args: [{ selector: 'app-text-editor', imports: [CommonModule,
2725
+ ], template: "<div class=\"text-editor\">\r\n <div class=\"toolbar\">\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"applyInlineStyle('bold')\" tooltip=\"Bold\">\r\n <i class=\"he he-bold\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyInlineStyle('italic')\" tooltip=\"italic\">\r\n <i class=\"he he-italics\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyInlineStyle('underline')\" tooltip=\"Underline\">\r\n <i class=\"he he-underline\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\" *ngIf=\"header\">\r\n <button type=\"button\" (click)=\"applyBlockFormat('h1')\" tooltip=\"Header 1\">\r\n <i class=\"he he-heading-1\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyBlockFormat('h2')\" tooltip=\"Header 2\">\r\n <i class=\"he he-heading-2\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items more-gap\">\r\n <div class=\"input-wrap\" tooltip=\"Text Color\">\r\n <i class=\"he he-text-drop\"></i>\r\n <input type=\"color\" (change)=\"applyColor($event)\" />\r\n </div>\r\n <div class=\"input-wrap\" tooltip=\"Background Color\">\r\n <i class=\"he he-background-drop\"></i>\r\n <input type=\"color\" (change)=\"applyHighlight($event)\" />\r\n </div>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"setAlignment('left')\" tooltip=\"Justify Left\">\r\n <i class=\"he he-left-align\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"setAlignment('center')\" tooltip=\"Justify Center\">\r\n <i class=\"he he-center-align\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"setAlignment('right')\" tooltip=\"Justify Right\">\r\n <i class=\"he he-right-align\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"setAlignment('justify')\" tooltip=\"Justify Full\">\r\n <i class=\"he he-justify\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" tooltip=\"Indent\">\r\n <i class=\"he he-text-indent-left\"></i>\r\n </button>\r\n <button type=\"button\" tooltip=\"Outdent\">\r\n <i class=\"he he-text-indent-right\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\" *ngIf=\"media\">\r\n <button type=\"button\" (click)=\"applyList('ul')\" tooltip=\"Unordered list\">\r\n <i class=\"he he-unordered-list\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyList('ol')\" tooltip=\"Ordered list\">\r\n <i class=\"he he-ordered-list\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\" *ngIf=\"link\">\r\n <button type=\"button\" (click)=\"createLink()\" tooltip=\"Link\">\r\n <i class=\"he he-link\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"insertImage()\" tooltip=\"Image\">\r\n <i class=\"he he-image\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"undo()\" tooltip=\"Undo\">\r\n <i class=\"he he-undo\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"redo()\" tooltip=\"Redo\">\r\n <i class=\"he he-redo\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"clearFormatting()\" tooltip=\"Clear Formatting\">\r\n <i class=\"he he-text-clear-format\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div #editor class=\"editor-content\" contenteditable=\"true\" (input)=\"onInput()\" (blur)=\"onTouched()\"\r\n [attr.data-placeholder]=\"placeholder\">\r\n </div>\r\n</div>", styles: [".text-editor{border:1px solid #ccc}.text-editor .toolbar{gap:.75rem;display:flex;flex-wrap:wrap;background:#f8f8f8;padding:.75rem;border-bottom:1px solid #cccccc}.text-editor .toolbar .toolbar-items{height:18px;display:flex;flex-wrap:wrap;align-items:center;padding-right:.75rem;border-right:1px solid #969090}.text-editor .toolbar .toolbar-items button,.text-editor .toolbar .toolbar-items .input-wrap{cursor:pointer;min-width:25px;min-height:20px}.text-editor .toolbar .toolbar-items button:hover i,.text-editor .toolbar .toolbar-items .input-wrap:hover i{color:#000}.text-editor .toolbar .toolbar-items button{border:0;display:grid;place-items:center;background:transparent}.text-editor .toolbar .toolbar-items button:hover{background:#eee}.text-editor .toolbar .toolbar-items .input-wrap{gap:2px;display:flex;cursor:pointer;align-items:center;flex-direction:column;justify-content:center}.text-editor .toolbar .toolbar-items .input-wrap input[type=color]{width:80%;padding:0;height:2px;border:none;cursor:inherit}.text-editor .toolbar .toolbar-items i{font-size:13px;color:#3c4148}.text-editor .toolbar .toolbar-items i.he-bold{font-weight:600}.text-editor .toolbar .toolbar-items i.he-underline{font-size:14px}.text-editor .toolbar .toolbar-items i.he-text-drop{font-size:12px}.text-editor .toolbar .toolbar-items i.he-background-drop{top:-1px;font-size:12px}.text-editor .toolbar .toolbar-items i.he-heading-1,.text-editor .toolbar .toolbar-items i.he-link,.text-editor .toolbar .toolbar-items i.he-left-align,.text-editor .toolbar .toolbar-items i.he-center-align,.text-editor .toolbar .toolbar-items i.he-right-align,.text-editor .toolbar .toolbar-items i.he-justify{font-size:16px}.text-editor .toolbar .toolbar-items i.he-text-indent-left,.text-editor .toolbar .toolbar-items i.he-text-indent-right{font-size:17px}.text-editor .toolbar .toolbar-items i.he-heading-2{top:1px;font-size:16px}.text-editor .toolbar .toolbar-items i.he-unordered-list,.text-editor .toolbar .toolbar-items i.he-ordered-list{top:-1px;font-size:20px}.text-editor .toolbar .toolbar-items i.he-image{font-size:15px;font-weight:600}.text-editor .toolbar .toolbar-items i.he-redo,.text-editor .toolbar .toolbar-items i.he-undo{top:-1px;font-size:14px}.text-editor .toolbar .toolbar-items.more-gap{gap:5px}.text-editor .toolbar .toolbar-items:last-child{border-right:0}.text-editor .editor-content{outline:none;min-height:200px;position:relative;background:#fff;padding:.75rem}.text-editor .editor-content[data-placeholder]:empty:before{content:attr(data-placeholder);color:#aaa;pointer-events:none;position:absolute;left:.75rem;top:.75rem}\n"] }]
2726
+ }], propDecorators: { header: [{
2727
+ type: Input
2728
+ }], media: [{
2729
+ type: Input
2730
+ }], link: [{
2731
+ type: Input
2732
+ }], placeholder: [{
2733
+ type: Input
2734
+ }], editorRef: [{
2735
+ type: ViewChild,
2736
+ args: ['editor']
2737
+ }] } });
2738
+
2739
+ class TextareaControl {
2740
+ title;
2741
+ placeholder = '';
2742
+ customClass;
2743
+ clearVal = true;
2744
+ disabled = false;
2745
+ readonly = false;
2746
+ required = false;
2747
+ error = false; // New input for error state
2748
+ errorMessage = ''; // New input for error message
2749
+ textareaChange = new EventEmitter();
2750
+ selectionCleared = new EventEmitter();
2751
+ blurEvent = new EventEmitter();
2752
+ onChange = () => { };
2753
+ onTouched = () => { };
2754
+ textareaControl = new FormControl('');
2755
+ constructor() {
2756
+ this.textareaControl.valueChanges.subscribe(value => {
2757
+ if (this.readonly)
2758
+ return;
2759
+ this.onChange(value);
2760
+ this.onTouched();
2761
+ this.textareaChange.emit(value ?? '');
2762
+ });
2763
+ }
2764
+ writeValue(value) {
2765
+ if (value !== undefined) {
2766
+ this.textareaControl.setValue(value, { emitEvent: false });
2767
+ }
2768
+ if (this.textareaControl.disabled !== this.disabled) {
2769
+ this.setDisabledState(this.disabled);
2770
+ }
2771
+ }
2772
+ registerOnChange(fn) {
2773
+ this.onChange = fn;
2774
+ }
2775
+ registerOnTouched(fn) {
2776
+ this.onTouched = fn;
2777
+ }
2778
+ // events
2779
+ onBlur() {
2780
+ this.blurEvent.emit();
2781
+ this.onTouched();
2782
+ }
2783
+ setDisabledState(isDisabled) {
2784
+ if (isDisabled) {
2785
+ this.textareaControl.disable({ emitEvent: false });
2786
+ }
2787
+ else {
2788
+ this.textareaControl.enable({ emitEvent: false });
2789
+ }
2790
+ }
2791
+ // clear
2792
+ resetTextarea() {
2793
+ this.textareaControl.setValue('');
2794
+ this.selectionCleared.emit();
2795
+ }
2796
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TextareaControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
2797
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.3", type: TextareaControl, isStandalone: true, selector: "textarea-control", inputs: { title: "title", placeholder: "placeholder", customClass: "customClass", clearVal: "clearVal", disabled: "disabled", readonly: "readonly", required: "required", error: "error", errorMessage: "errorMessage" }, outputs: { textareaChange: "textareaChange", selectionCleared: "selectionCleared", blurEvent: "blurEvent" }, providers: [
2798
+ {
2799
+ provide: NG_VALUE_ACCESSOR,
2800
+ useExisting: forwardRef(() => TextareaControl),
2801
+ multi: true,
2802
+ },
2803
+ ], ngImport: i0, template: "<div class=\"form-group textarea\" [ngClass]=\"customClass\">\r\n @if (title) {\r\n <label class=\"inp-label\" [ngClass]=\"{'required' : required}\">{{ title }}</label>\r\n }\r\n <textarea class=\"form-control\" [placeholder]=\"placeholder\" [formControl]=\"textareaControl\"\r\n [ngClass]=\"{ 'is-invalid': error }\" [readonly]=\"readonly\" (blur)=\"onBlur()\"></textarea>\r\n\r\n <span class=\"focus-border\"></span>\r\n\r\n @if (textareaControl.value && clearVal && !readonly) {\r\n <label class=\"clear\" (click)=\"resetTextarea()\">\r\n <i class=\"he he-close\"></i>\r\n </label>\r\n }\r\n\r\n @if (error) {\r\n <div class=\"val-msg\">{{ errorMessage }}</div>\r\n }\r\n</div>", styles: [".form-group textarea{resize:none;min-height:100px}.form-group textarea.resize{resize:auto}.form-group textarea::-webkit-scrollbar-thumb{border-radius:10px}.form-group textarea::-webkit-scrollbar-track{background:#e8eaf1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
2804
+ }
2805
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TextareaControl, decorators: [{
2806
+ type: Component,
2807
+ args: [{ selector: 'textarea-control', standalone: true, imports: [
2808
+ CommonModule,
2809
+ ReactiveFormsModule
2810
+ ], providers: [
2811
+ {
2812
+ provide: NG_VALUE_ACCESSOR,
2813
+ useExisting: forwardRef(() => TextareaControl),
2814
+ multi: true,
2815
+ },
2816
+ ], template: "<div class=\"form-group textarea\" [ngClass]=\"customClass\">\r\n @if (title) {\r\n <label class=\"inp-label\" [ngClass]=\"{'required' : required}\">{{ title }}</label>\r\n }\r\n <textarea class=\"form-control\" [placeholder]=\"placeholder\" [formControl]=\"textareaControl\"\r\n [ngClass]=\"{ 'is-invalid': error }\" [readonly]=\"readonly\" (blur)=\"onBlur()\"></textarea>\r\n\r\n <span class=\"focus-border\"></span>\r\n\r\n @if (textareaControl.value && clearVal && !readonly) {\r\n <label class=\"clear\" (click)=\"resetTextarea()\">\r\n <i class=\"he he-close\"></i>\r\n </label>\r\n }\r\n\r\n @if (error) {\r\n <div class=\"val-msg\">{{ errorMessage }}</div>\r\n }\r\n</div>", styles: [".form-group textarea{resize:none;min-height:100px}.form-group textarea.resize{resize:auto}.form-group textarea::-webkit-scrollbar-thumb{border-radius:10px}.form-group textarea::-webkit-scrollbar-track{background:#e8eaf1}\n"] }]
2817
+ }], ctorParameters: () => [], propDecorators: { title: [{
2818
+ type: Input
2819
+ }], placeholder: [{
2820
+ type: Input
2821
+ }], customClass: [{
2822
+ type: Input
2823
+ }], clearVal: [{
2824
+ type: Input
2825
+ }], disabled: [{
2826
+ type: Input
2827
+ }], readonly: [{
2828
+ type: Input
2829
+ }], required: [{
2830
+ type: Input
2831
+ }], error: [{
2832
+ type: Input
2833
+ }], errorMessage: [{
2834
+ type: Input
2835
+ }], textareaChange: [{
2836
+ type: Output
2837
+ }], selectionCleared: [{
2838
+ type: Output
2839
+ }], blurEvent: [{
2840
+ type: Output
2841
+ }] } });
2842
+
2843
+ /*
2844
+ * Public API Surface of heal-lib
2845
+ */
2846
+
2847
+ /**
2848
+ * Generated bundle index. Do not edit.
2849
+ */
2850
+
2851
+ export { AutocompleteControl, CalendarControl, CheckboxControl, InputControl, MultiselectControl, SelectControl, SwitchControl, TextEditor, TextareaControl };
2852
+ //# sourceMappingURL=nexheal-lib.mjs.map