nexheal-lib 0.0.2

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,2837 @@
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()\" (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) && 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()\" (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) && 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
+ set disabled(value) { this._disabled = value; this.setDisabledState(value); }
1134
+ checkboxChange = new EventEmitter();
1135
+ blurEvent = new EventEmitter();
1136
+ checkboxContainer;
1137
+ _disabled = false;
1138
+ checkboxControl = new FormControl({ value: false, disabled: this._disabled });
1139
+ onChange = () => { };
1140
+ onTouched = () => { };
1141
+ constructor(_renderer, _elementRef) {
1142
+ this._renderer = _renderer;
1143
+ this._elementRef = _elementRef;
1144
+ this.checkboxControl.valueChanges.subscribe(value => {
1145
+ const converted = this.convertType(value);
1146
+ this.onChange(converted);
1147
+ this.onTouched();
1148
+ this.checkboxChange.emit(converted);
1149
+ });
1150
+ }
1151
+ writeValue(value) {
1152
+ if (value !== undefined && value !== null) {
1153
+ this.checkboxControl.setValue(this.convertType(value), { emitEvent: false });
1154
+ }
1155
+ }
1156
+ registerOnChange(fn) {
1157
+ this.onChange = fn;
1158
+ }
1159
+ registerOnTouched(fn) {
1160
+ this.onTouched = fn;
1161
+ }
1162
+ convertType(value) {
1163
+ switch (this.type) {
1164
+ case 'number':
1165
+ return Number(value == 2 ? 0 : value == 0 ? 2 : value);
1166
+ case 'string':
1167
+ return String(value);
1168
+ default:
1169
+ return !!value;
1170
+ }
1171
+ }
1172
+ // events
1173
+ onFocus(event) {
1174
+ const targetElement = event.target;
1175
+ if (targetElement.matches(':focus-visible')) {
1176
+ this.checkboxContainer.nativeElement.classList.add('focused');
1177
+ }
1178
+ }
1179
+ onBlur() {
1180
+ this.blurEvent.emit();
1181
+ this.checkboxContainer.nativeElement.classList.remove('focused');
1182
+ }
1183
+ setDisabledState(isDisabled) {
1184
+ this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
1185
+ if (isDisabled) {
1186
+ this.checkboxControl.disable();
1187
+ }
1188
+ else {
1189
+ this.checkboxControl.enable();
1190
+ }
1191
+ }
1192
+ 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 });
1193
+ 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", disabled: "disabled" }, outputs: { checkboxChange: "checkboxChange", blurEvent: "blurEvent" }, providers: [
1194
+ {
1195
+ provide: NG_VALUE_ACCESSOR,
1196
+ useExisting: forwardRef(() => CheckboxControl),
1197
+ multi: true,
1198
+ },
1199
+ ], viewQueries: [{ propertyName: "checkboxContainer", first: true, predicate: ["checkboxContainer"], descendants: true }], ngImport: i0, template: "<label class=\"checkbox-container\" [ngClass]=\"customClass\" #checkboxContainer>\r\n <input type=\"checkbox\" [formControl]=\"checkboxControl\" (focus)=\"onFocus($event)\" (blur)=\"onBlur()\" />\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 }] });
1200
+ }
1201
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: CheckboxControl, decorators: [{
1202
+ type: Component,
1203
+ args: [{ selector: 'checkbox-control', standalone: true, imports: [
1204
+ CommonModule,
1205
+ ReactiveFormsModule,
1206
+ FormsModule
1207
+ ], providers: [
1208
+ {
1209
+ provide: NG_VALUE_ACCESSOR,
1210
+ useExisting: forwardRef(() => CheckboxControl),
1211
+ multi: true,
1212
+ },
1213
+ ], template: "<label class=\"checkbox-container\" [ngClass]=\"customClass\" #checkboxContainer>\r\n <input type=\"checkbox\" [formControl]=\"checkboxControl\" (focus)=\"onFocus($event)\" (blur)=\"onBlur()\" />\r\n <span class=\"checkmark\"></span>{{ title }}\r\n</label>" }]
1214
+ }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }], propDecorators: { title: [{
1215
+ type: Input
1216
+ }], customClass: [{
1217
+ type: Input
1218
+ }], type: [{
1219
+ type: Input
1220
+ }], disabled: [{
1221
+ type: Input
1222
+ }], checkboxChange: [{
1223
+ type: Output
1224
+ }], blurEvent: [{
1225
+ type: Output
1226
+ }], checkboxContainer: [{
1227
+ type: ViewChild,
1228
+ args: ['checkboxContainer']
1229
+ }] } });
1230
+
1231
+ class InputControl {
1232
+ type = "text";
1233
+ title;
1234
+ placeholder = "";
1235
+ customClass;
1236
+ clearVal = true;
1237
+ required = false;
1238
+ deFocus = true;
1239
+ error = false;
1240
+ errorMessage = "";
1241
+ autocomplete = '';
1242
+ readonly = false;
1243
+ search = false;
1244
+ price = false;
1245
+ maxlength;
1246
+ ngModel;
1247
+ advSearch = false;
1248
+ inputLoader = false;
1249
+ useReactiveForms = true;
1250
+ blurEvent = new EventEmitter();
1251
+ selectionCleared = new EventEmitter();
1252
+ inputChange = new EventEmitter();
1253
+ ngModelChange = new EventEmitter();
1254
+ searchClicked = new EventEmitter();
1255
+ advSearchClicked = new EventEmitter();
1256
+ _disabled = false;
1257
+ set disabled(isDisabled) {
1258
+ this._disabled = isDisabled;
1259
+ this.setDisabledState(isDisabled);
1260
+ }
1261
+ get disabled() {
1262
+ return this._disabled;
1263
+ }
1264
+ valueChangesSubscription;
1265
+ onChange = () => { };
1266
+ onTouched = () => { };
1267
+ isFocused = false;
1268
+ showPassword = false;
1269
+ inputControl = new FormControl("");
1270
+ ngOnInit() {
1271
+ if (this.useReactiveForms) {
1272
+ this.valueChangesSubscription = this.inputControl.valueChanges.subscribe((value) => {
1273
+ this.onChange(value);
1274
+ this.inputChange.emit(value ?? "");
1275
+ });
1276
+ }
1277
+ }
1278
+ ngOnDestroy() {
1279
+ if (this.valueChangesSubscription) {
1280
+ this.valueChangesSubscription.unsubscribe();
1281
+ }
1282
+ }
1283
+ // input type
1284
+ getInputType() {
1285
+ return this.type === "password" && this.showPassword ? "text" : this.type;
1286
+ }
1287
+ writeValue(value) {
1288
+ if (this.useReactiveForms) {
1289
+ if (value !== undefined) {
1290
+ this.inputControl.setValue(value, {
1291
+ emitEvent: false,
1292
+ });
1293
+ if (value != '') {
1294
+ this.onChange(value);
1295
+ }
1296
+ }
1297
+ if (this.inputControl.disabled !== this.disabled) {
1298
+ this.setDisabledState(this.disabled);
1299
+ }
1300
+ }
1301
+ else {
1302
+ this.ngModel = value;
1303
+ }
1304
+ }
1305
+ registerOnChange(fn) {
1306
+ this.onChange = fn;
1307
+ }
1308
+ registerOnTouched(fn) {
1309
+ this.onTouched = fn;
1310
+ }
1311
+ // events
1312
+ onBlur() {
1313
+ this.blurEvent.emit();
1314
+ setTimeout(() => {
1315
+ this.isFocused = false;
1316
+ this.onTouched();
1317
+ if (!this.useReactiveForms) {
1318
+ this.ngModelChange.emit(this.ngModel);
1319
+ }
1320
+ }, 200);
1321
+ }
1322
+ onFocus() {
1323
+ this.isFocused = true;
1324
+ }
1325
+ onKeyPress(event) {
1326
+ let allowedKeys = [];
1327
+ switch (this.type) {
1328
+ case "number":
1329
+ allowedKeys = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
1330
+ break;
1331
+ case "decimal":
1332
+ allowedKeys = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."];
1333
+ break;
1334
+ default:
1335
+ break;
1336
+ }
1337
+ if (this.search && event.key == "Enter") {
1338
+ this.onSearchClick();
1339
+ }
1340
+ if ((this.type == "number" || this.type == "decimal") &&
1341
+ !allowedKeys.includes(event.key)) {
1342
+ event.preventDefault();
1343
+ return;
1344
+ }
1345
+ if (this.maxlength != null && (this.type == "number" || this.type == "decimal")) {
1346
+ const currentValue = this.inputControl.value ?? "";
1347
+ if (currentValue.length >= this.maxlength) {
1348
+ event.preventDefault();
1349
+ return;
1350
+ }
1351
+ }
1352
+ }
1353
+ setDisabledState(isDisabled) {
1354
+ this._disabled = isDisabled;
1355
+ if (this._disabled && this.useReactiveForms) {
1356
+ this.inputControl.disable({
1357
+ emitEvent: false,
1358
+ });
1359
+ }
1360
+ else if (this.useReactiveForms) {
1361
+ this.inputControl.enable({
1362
+ emitEvent: false,
1363
+ });
1364
+ }
1365
+ }
1366
+ // actions
1367
+ passwordVisible() {
1368
+ this.showPassword = !this.showPassword;
1369
+ }
1370
+ onSearchClick() {
1371
+ this.searchClicked.emit();
1372
+ }
1373
+ onAdvSearchClick() {
1374
+ this.advSearchClicked.emit();
1375
+ }
1376
+ // clear
1377
+ showClearButton() {
1378
+ return (this.isFocused &&
1379
+ this.clearVal &&
1380
+ this.inputControl.value &&
1381
+ this.type !== "password");
1382
+ }
1383
+ resetInput() {
1384
+ this.selectionCleared.emit();
1385
+ if (this.useReactiveForms) {
1386
+ this.inputControl.setValue("");
1387
+ this.inputControl.markAsPristine({ onlySelf: true });
1388
+ }
1389
+ else {
1390
+ this.ngModel = "";
1391
+ this.ngModelChange.emit(this.ngModel);
1392
+ }
1393
+ }
1394
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: InputControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
1395
+ 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: [
1396
+ {
1397
+ provide: NG_VALUE_ACCESSOR,
1398
+ useExisting: forwardRef(() => InputControl),
1399
+ multi: true,
1400
+ },
1401
+ ], 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"] }] });
1402
+ }
1403
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: InputControl, decorators: [{
1404
+ type: Component,
1405
+ args: [{ selector: "input-control", standalone: true, imports: [
1406
+ CommonModule,
1407
+ ReactiveFormsModule,
1408
+ FormsModule
1409
+ ], providers: [
1410
+ {
1411
+ provide: NG_VALUE_ACCESSOR,
1412
+ useExisting: forwardRef(() => InputControl),
1413
+ multi: true,
1414
+ },
1415
+ ], 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"] }]
1416
+ }], propDecorators: { type: [{
1417
+ type: Input
1418
+ }], title: [{
1419
+ type: Input
1420
+ }], placeholder: [{
1421
+ type: Input
1422
+ }], customClass: [{
1423
+ type: Input
1424
+ }], clearVal: [{
1425
+ type: Input
1426
+ }], required: [{
1427
+ type: Input
1428
+ }], deFocus: [{
1429
+ type: Input
1430
+ }], error: [{
1431
+ type: Input
1432
+ }], errorMessage: [{
1433
+ type: Input
1434
+ }], autocomplete: [{
1435
+ type: Input
1436
+ }], readonly: [{
1437
+ type: Input
1438
+ }], search: [{
1439
+ type: Input
1440
+ }], price: [{
1441
+ type: Input
1442
+ }], maxlength: [{
1443
+ type: Input
1444
+ }], ngModel: [{
1445
+ type: Input
1446
+ }], advSearch: [{
1447
+ type: Input
1448
+ }], inputLoader: [{
1449
+ type: Input
1450
+ }], useReactiveForms: [{
1451
+ type: Input
1452
+ }], blurEvent: [{
1453
+ type: Output
1454
+ }], selectionCleared: [{
1455
+ type: Output
1456
+ }], inputChange: [{
1457
+ type: Output
1458
+ }], ngModelChange: [{
1459
+ type: Output
1460
+ }], searchClicked: [{
1461
+ type: Output
1462
+ }], advSearchClicked: [{
1463
+ type: Output
1464
+ }], disabled: [{
1465
+ type: Input
1466
+ }] } });
1467
+
1468
+ class ClickOutsideDirective {
1469
+ clickOutside = new EventEmitter();
1470
+ host = inject(ElementRef);
1471
+ document = inject(DOCUMENT);
1472
+ destroyRef = inject(DestroyRef);
1473
+ constructor() {
1474
+ fromEvent(this.document, 'click')
1475
+ .pipe(takeUntilDestroyed(this.destroyRef))
1476
+ .subscribe((event) => {
1477
+ const clickedInside = this.host.nativeElement.contains(event.target);
1478
+ if (!clickedInside) {
1479
+ this.clickOutside.emit(event);
1480
+ }
1481
+ });
1482
+ }
1483
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ClickOutsideDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1484
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.3", type: ClickOutsideDirective, isStandalone: true, selector: "[clickOutside]", outputs: { clickOutside: "clickOutside" }, ngImport: i0 });
1485
+ }
1486
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ClickOutsideDirective, decorators: [{
1487
+ type: Directive,
1488
+ args: [{
1489
+ selector: '[clickOutside]',
1490
+ standalone: true,
1491
+ }]
1492
+ }], ctorParameters: () => [], propDecorators: { clickOutside: [{
1493
+ type: Output
1494
+ }] } });
1495
+
1496
+ class MultiselectControl {
1497
+ cdRef;
1498
+ subscription = new Subscription();
1499
+ title;
1500
+ selectedItems = [];
1501
+ required = false;
1502
+ placeholder;
1503
+ customClass;
1504
+ clearVal = true;
1505
+ deFocus = true;
1506
+ optionValueProperty = "id";
1507
+ inputLoader = false;
1508
+ error = false;
1509
+ errorMessage = "";
1510
+ autocomplete = "";
1511
+ showSearch = false;
1512
+ optionDisplayProperty = "displayname";
1513
+ optionsSelected = new EventEmitter();
1514
+ selectionCleared = new EventEmitter();
1515
+ blurEvent = new EventEmitter();
1516
+ inputControl = new FormControl({ value: "", disabled: false });
1517
+ _options = [];
1518
+ set options(arr) {
1519
+ this._options = Array.isArray(arr) ? arr : [];
1520
+ this.filteredOptions = [...this._options];
1521
+ // If we previously received an array of primitive IDs, attempt to map them now that options have arrived
1522
+ if (this._isPrimitivePatch && this._pendingPrimitiveValues.length > 0) {
1523
+ this._mapPrimitivesToObjects();
1524
+ }
1525
+ this._recomputeSelectAllState();
1526
+ }
1527
+ get options() {
1528
+ return this._options;
1529
+ }
1530
+ _disabled = false;
1531
+ get disabled() {
1532
+ return this._disabled;
1533
+ }
1534
+ set disabled(value) {
1535
+ this._disabled = value;
1536
+ if (this.inputControl) {
1537
+ if (value) {
1538
+ this.inputControl.disable({ emitEvent: false });
1539
+ }
1540
+ else {
1541
+ this.inputControl.enable({ emitEvent: false });
1542
+ }
1543
+ }
1544
+ }
1545
+ _isPrimitivePatch = false;
1546
+ _pendingPrimitiveValues = [];
1547
+ onChange = () => { };
1548
+ onTouched = () => { };
1549
+ dropdown;
1550
+ input;
1551
+ filteredOptions = [];
1552
+ isDropdownOpen = false;
1553
+ selectAllChecked = false;
1554
+ keySearch = "";
1555
+ onSearch = new Subject();
1556
+ selectedItemList = "";
1557
+ highlightedIndex = null;
1558
+ dropdownInitialized = false;
1559
+ popperInstance;
1560
+ constructor(cdRef) {
1561
+ this.cdRef = cdRef;
1562
+ }
1563
+ ngOnInit() {
1564
+ // Subscribe to search‐term changes, debounce, then filter
1565
+ this.subscription = this.onSearch
1566
+ .pipe(debounceTime(300), distinctUntilChanged())
1567
+ .subscribe((searchText) => {
1568
+ this._applyFilter(searchText);
1569
+ this._recomputeSelectAllState();
1570
+ });
1571
+ // Initialize filteredOptions to match all options
1572
+ this.filteredOptions = [...this.options];
1573
+ }
1574
+ ngAfterViewChecked() {
1575
+ if (this.isDropdownOpen && !this.dropdownInitialized) {
1576
+ this._createPopperInstance();
1577
+ this.dropdownInitialized = true;
1578
+ }
1579
+ }
1580
+ ngOnDestroy() {
1581
+ this.subscription.unsubscribe();
1582
+ if (this.popperInstance) {
1583
+ this.popperInstance.destroy();
1584
+ }
1585
+ }
1586
+ ngOnChanges(changes) {
1587
+ if (changes["options"]) {
1588
+ // When the parent replaces “options,” we reapply any filtering and “select all” logic
1589
+ this.filteredOptions = [...this.options];
1590
+ this._recomputeSelectAllState();
1591
+ }
1592
+ }
1593
+ writeValue(value) {
1594
+ // If parent gave us something that is not an array, clear selection
1595
+ if (!Array.isArray(value)) {
1596
+ this._isPrimitivePatch = false;
1597
+ this._pendingPrimitiveValues = [];
1598
+ this.selectedItems = [];
1599
+ this.selectedItemList = "";
1600
+ this.selectAllChecked = false;
1601
+ return;
1602
+ }
1603
+ // CASE A: array of primitive IDs, e.g. [12,17]
1604
+ if (value.length > 0 &&
1605
+ (typeof value[0] === "string" || typeof value[0] === "number")) {
1606
+ this._isPrimitivePatch = true;
1607
+ this._pendingPrimitiveValues = [...value];
1608
+ this._mapPrimitivesToObjects();
1609
+ this._recomputeSelectAllState();
1610
+ return;
1611
+ }
1612
+ // CASE B: array of full objects (with `id` or whatever `optionValueProperty` is)
1613
+ if (value.length > 0 &&
1614
+ typeof value[0] === "object" &&
1615
+ value[0][this.optionValueProperty] != null) {
1616
+ // Convert to an array of IDs, then treat exactly like CASE A
1617
+ const idArray = value.map((obj) => obj[this.optionValueProperty]);
1618
+ this._isPrimitivePatch = true;
1619
+ this._pendingPrimitiveValues = idArray;
1620
+ this._mapPrimitivesToObjects();
1621
+ this._recomputeSelectAllState();
1622
+ return;
1623
+ }
1624
+ // CASE C: maybe parent passed the exact same object references that already exist in `options`
1625
+ // In that case, we can just use them directly.
1626
+ this._isPrimitivePatch = false;
1627
+ this._pendingPrimitiveValues = [];
1628
+ this.selectedItems = [...value];
1629
+ this.selectedItemList = this.selectedItems
1630
+ .map((item) => item[this.optionDisplayProperty])
1631
+ .join(", ");
1632
+ this.onChange(this.selectedItems);
1633
+ this._recomputeSelectAllState();
1634
+ }
1635
+ _mapPrimitivesToObjects() {
1636
+ if (!this._isPrimitivePatch ||
1637
+ this._pendingPrimitiveValues.length === 0 ||
1638
+ this._options.length === 0) {
1639
+ return;
1640
+ }
1641
+ // Find matching option‐objects whose ID property is in the pending list
1642
+ const matched = this._options.filter((opt) => this._pendingPrimitiveValues.includes(opt[this.optionValueProperty]));
1643
+ this.selectedItems = matched;
1644
+ this.selectedItemList = matched
1645
+ .map((item) => item[this.optionDisplayProperty])
1646
+ .join(", ");
1647
+ // Notify “onChange” with the full‐object selection
1648
+ this.onChange(this.selectedItems);
1649
+ this._isPrimitivePatch = false;
1650
+ this._pendingPrimitiveValues = [];
1651
+ }
1652
+ registerOnChange(fn) {
1653
+ this.onChange = fn;
1654
+ }
1655
+ registerOnTouched(fn) {
1656
+ this.onTouched = fn;
1657
+ }
1658
+ // events
1659
+ isSelected(option) {
1660
+ return (Array.isArray(this.selectedItems) && this.selectedItems.includes(option));
1661
+ }
1662
+ onBlur(event) {
1663
+ this.blurEvent.emit();
1664
+ // const target = event.relatedTarget as HTMLElement;
1665
+ // if (
1666
+ // target &&
1667
+ // (target.closest(".option-list") || target.closest(".list-filter"))
1668
+ // ) {
1669
+ // return;
1670
+ // }
1671
+ this.onTouched();
1672
+ // setTimeout(() => {
1673
+ // this.isDropdownOpen = false;
1674
+ // }, 100);
1675
+ }
1676
+ setDisabledState(isDisabled) {
1677
+ this.disabled = isDisabled;
1678
+ if (isDisabled) {
1679
+ this.inputControl.disable({ emitEvent: false });
1680
+ }
1681
+ else {
1682
+ this.inputControl.enable({ emitEvent: false });
1683
+ }
1684
+ }
1685
+ // search box
1686
+ handleInput(event) {
1687
+ const txt = event.target.value;
1688
+ this.onSearch.next(txt);
1689
+ }
1690
+ _applyFilter(value) {
1691
+ const lower = value.toLowerCase();
1692
+ this.filteredOptions = this._options
1693
+ .filter((opt) => opt[this.optionDisplayProperty]?.toLowerCase().includes(lower))
1694
+ .sort((a, b) => {
1695
+ const aIndex = a[this.optionDisplayProperty]
1696
+ .toLowerCase()
1697
+ .indexOf(lower);
1698
+ const bIndex = b[this.optionDisplayProperty]
1699
+ .toLowerCase()
1700
+ .indexOf(lower);
1701
+ return aIndex - bIndex;
1702
+ });
1703
+ // If an item was previously selected but is no longer in filteredOptions, remove it temporarily
1704
+ this.selectedItems = this.selectedItems.filter((item) => this.filteredOptions.includes(item));
1705
+ this.selectedItemList = this.selectedItems
1706
+ .map((item) => item[this.optionDisplayProperty])
1707
+ .join(", ");
1708
+ this.onChange(this.selectedItems);
1709
+ this.optionsSelected.emit(this.selectedItems);
1710
+ if (this.filteredOptions.length > 0) {
1711
+ this.highlightedIndex = this._options.indexOf(this.filteredOptions[0]);
1712
+ }
1713
+ else {
1714
+ this.highlightedIndex = null;
1715
+ }
1716
+ this._recomputeSelectAllState();
1717
+ this.cdRef.detectChanges();
1718
+ }
1719
+ // checkbox events
1720
+ _recomputeSelectAllState() {
1721
+ if (!this.filteredOptions || this.filteredOptions.length === 0) {
1722
+ this.selectAllChecked = false;
1723
+ return;
1724
+ }
1725
+ this.selectAllChecked = this.filteredOptions.every((opt) => this.selectedItems.includes(opt));
1726
+ }
1727
+ onSelectAllChange(event) {
1728
+ const input = event.target;
1729
+ this.selectAllChecked = input.checked;
1730
+ this._toggleSelectAll();
1731
+ }
1732
+ _toggleSelectAll() {
1733
+ if (this.selectAllChecked) {
1734
+ // Add all currently filtered options to selection
1735
+ this.selectedItems = Array.from(new Set([...this.selectedItems, ...this.filteredOptions]));
1736
+ }
1737
+ else {
1738
+ // Remove all filtered options from selection
1739
+ this.selectedItems = this.selectedItems.filter((item) => !this.filteredOptions.includes(item));
1740
+ }
1741
+ this.selectedItemList = this.selectedItems
1742
+ .map((item) => item[this.optionDisplayProperty])
1743
+ .join(", ");
1744
+ this.onChange(this.selectedItems);
1745
+ this.optionsSelected.emit(this.selectedItems);
1746
+ }
1747
+ removeSelectedOption(option) {
1748
+ this.selectedItems = this.selectedItems.filter((item) => item !== option);
1749
+ this.selectedItemList = this.selectedItems
1750
+ .map((item) => item[this.optionDisplayProperty])
1751
+ .join(", ");
1752
+ this.onChange(this.selectedItems);
1753
+ this.onTouched();
1754
+ this.optionsSelected.emit(this.selectedItems);
1755
+ }
1756
+ toggleCheckbox(option) {
1757
+ if (this.isSelected(option)) {
1758
+ this.selectedItems = this.selectedItems.filter((item) => item !== option);
1759
+ }
1760
+ else {
1761
+ this.selectedItems = [...this.selectedItems, option];
1762
+ }
1763
+ this.selectedItemList = this.selectedItems
1764
+ .map((item) => item[this.optionDisplayProperty])
1765
+ .join(", ");
1766
+ // Update “select all” checkbox
1767
+ this._recomputeSelectAllState();
1768
+ // Notify form that selection changed
1769
+ this.onChange(this.selectedItems);
1770
+ this.onTouched();
1771
+ this.optionsSelected.emit(this.selectedItems);
1772
+ }
1773
+ // Keyboard navigation inside the dropdown
1774
+ onKeyDown(event) {
1775
+ if (this.isDropdownOpen) {
1776
+ switch (event.key) {
1777
+ case "ArrowDown":
1778
+ this._highlightNext();
1779
+ event.preventDefault();
1780
+ break;
1781
+ case "ArrowUp":
1782
+ this._highlightPrevious();
1783
+ event.preventDefault();
1784
+ break;
1785
+ case "Enter":
1786
+ if (this.highlightedIndex !== null &&
1787
+ this.highlightedIndex < this.filteredOptions.length) {
1788
+ this.toggleCheckbox(this.filteredOptions[this.highlightedIndex]);
1789
+ }
1790
+ event.preventDefault();
1791
+ break;
1792
+ case "Escape":
1793
+ this.isDropdownOpen = false;
1794
+ break;
1795
+ case "Tab":
1796
+ this.isDropdownOpen = false;
1797
+ break;
1798
+ }
1799
+ }
1800
+ else if (event.key === " ") {
1801
+ this.toggleDropdown();
1802
+ event.preventDefault();
1803
+ }
1804
+ }
1805
+ handleWindowKeydown(event) {
1806
+ if (event.key === "Tab") {
1807
+ this.isDropdownOpen = false;
1808
+ }
1809
+ }
1810
+ _highlightNext() {
1811
+ if (this.highlightedIndex === null ||
1812
+ this.highlightedIndex === this.filteredOptions.length - 1) {
1813
+ this.highlightedIndex = 0;
1814
+ }
1815
+ else {
1816
+ this.highlightedIndex++;
1817
+ }
1818
+ this._scrollToHighlighted();
1819
+ }
1820
+ _highlightPrevious() {
1821
+ if (this.highlightedIndex === null || this.highlightedIndex === 0) {
1822
+ this.highlightedIndex = this.filteredOptions.length - 1;
1823
+ }
1824
+ else {
1825
+ this.highlightedIndex--;
1826
+ }
1827
+ this._scrollToHighlighted();
1828
+ }
1829
+ _scrollToHighlighted() {
1830
+ setTimeout(() => {
1831
+ const container = document.querySelector(".option-list");
1832
+ if (!container)
1833
+ return;
1834
+ const highlighted = container.querySelector(".highlighted");
1835
+ if (highlighted) {
1836
+ highlighted.scrollIntoView({ block: "nearest" });
1837
+ }
1838
+ }, 0);
1839
+ }
1840
+ // popper
1841
+ _createPopperInstance() {
1842
+ if (!this.dropdown || !this.input)
1843
+ return;
1844
+ this.popperInstance = createPopper(this.input.nativeElement, this.dropdown.nativeElement, {
1845
+ placement: "bottom-start",
1846
+ modifiers: [
1847
+ {
1848
+ name: "offset",
1849
+ options: {
1850
+ offset: [0, 1],
1851
+ },
1852
+ },
1853
+ {
1854
+ name: "flip",
1855
+ options: {
1856
+ fallbackPlacements: ["top-start", "bottom-start"],
1857
+ },
1858
+ },
1859
+ ],
1860
+ });
1861
+ }
1862
+ // toggle, reset, close and clear
1863
+ toggleDropdown() {
1864
+ if (this.inputControl.disabled)
1865
+ return;
1866
+ this.isDropdownOpen = !this.isDropdownOpen;
1867
+ this.dropdownInitialized = false;
1868
+ if (this.isDropdownOpen) {
1869
+ this.filteredOptions = [...this.options];
1870
+ this.highlightedIndex = null;
1871
+ }
1872
+ }
1873
+ selectListOutsideClick() {
1874
+ this.isDropdownOpen = false;
1875
+ }
1876
+ resetSelection() {
1877
+ this.selectedItems = [];
1878
+ this.selectedItemList = "";
1879
+ this.onChange([]);
1880
+ this.onTouched();
1881
+ this.isDropdownOpen = false;
1882
+ this.selectAllChecked = false;
1883
+ this.selectionCleared.emit();
1884
+ }
1885
+ onCloseDropdown() {
1886
+ this.isDropdownOpen = false;
1887
+ this._recomputeSelectAllState();
1888
+ }
1889
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: MultiselectControl, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1890
+ 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: [
1891
+ {
1892
+ provide: NG_VALUE_ACCESSOR,
1893
+ useExisting: forwardRef(() => MultiselectControl),
1894
+ multi: true,
1895
+ },
1896
+ ], 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"] }] });
1897
+ }
1898
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: MultiselectControl, decorators: [{
1899
+ type: Component,
1900
+ args: [{ selector: "multiselect-control", standalone: true, imports: [
1901
+ CommonModule,
1902
+ ReactiveFormsModule,
1903
+ ClickOutsideDirective
1904
+ ], providers: [
1905
+ {
1906
+ provide: NG_VALUE_ACCESSOR,
1907
+ useExisting: forwardRef(() => MultiselectControl),
1908
+ multi: true,
1909
+ },
1910
+ ], 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"] }]
1911
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { title: [{
1912
+ type: Input
1913
+ }], selectedItems: [{
1914
+ type: Input
1915
+ }], required: [{
1916
+ type: Input
1917
+ }], placeholder: [{
1918
+ type: Input
1919
+ }], customClass: [{
1920
+ type: Input
1921
+ }], clearVal: [{
1922
+ type: Input
1923
+ }], deFocus: [{
1924
+ type: Input
1925
+ }], optionValueProperty: [{
1926
+ type: Input
1927
+ }], inputLoader: [{
1928
+ type: Input
1929
+ }], error: [{
1930
+ type: Input
1931
+ }], errorMessage: [{
1932
+ type: Input
1933
+ }], autocomplete: [{
1934
+ type: Input
1935
+ }], showSearch: [{
1936
+ type: Input
1937
+ }], optionDisplayProperty: [{
1938
+ type: Input
1939
+ }], optionsSelected: [{
1940
+ type: Output
1941
+ }], selectionCleared: [{
1942
+ type: Output
1943
+ }], blurEvent: [{
1944
+ type: Output
1945
+ }], options: [{
1946
+ type: Input
1947
+ }], disabled: [{
1948
+ type: Input
1949
+ }], dropdown: [{
1950
+ type: ViewChild,
1951
+ args: ["dropdown", { static: false }]
1952
+ }], input: [{
1953
+ type: ViewChild,
1954
+ args: ["input", { static: false }]
1955
+ }], handleWindowKeydown: [{
1956
+ type: HostListener,
1957
+ args: ["window:keydown", ["$event"]]
1958
+ }] } });
1959
+
1960
+ class SelectControl {
1961
+ subscription = new Subscription();
1962
+ optionDisplayProperty = "displayname";
1963
+ required = false;
1964
+ placeholder;
1965
+ customClass;
1966
+ clearVal = true;
1967
+ deFocus = true;
1968
+ error = false;
1969
+ errorMessage = "";
1970
+ autocomplete = "";
1971
+ inputLoader = false;
1972
+ isAddNewItem = false;
1973
+ optionSelected = new EventEmitter();
1974
+ selectionCleared = new EventEmitter();
1975
+ addNewItemClicked = new EventEmitter();
1976
+ blurEvent = new EventEmitter();
1977
+ optionPatched = new EventEmitter();
1978
+ title;
1979
+ _options = [];
1980
+ set options(opts) {
1981
+ this._options = opts;
1982
+ this.filteredOptions = [...this._options];
1983
+ this.highlightSelectedItem();
1984
+ this.processValueBuffer();
1985
+ }
1986
+ get options() {
1987
+ return this._options;
1988
+ }
1989
+ get disabled() {
1990
+ return this._disabled;
1991
+ }
1992
+ set disabled(value) {
1993
+ this._disabled = value;
1994
+ if (this.inputControl) {
1995
+ if (value) {
1996
+ this.inputControl.disable({ emitEvent: false });
1997
+ }
1998
+ else {
1999
+ this.inputControl.enable({ emitEvent: false });
2000
+ }
2001
+ }
2002
+ }
2003
+ dropdown;
2004
+ input;
2005
+ filteredOptions = [];
2006
+ keySearch = "";
2007
+ selectedItem = null;
2008
+ onSearch = new Subject();
2009
+ inputControl = new FormControl({ value: "", disabled: false });
2010
+ isDropdownOpen = false;
2011
+ highlightedIndex = null;
2012
+ onChange = () => { };
2013
+ onTouched = () => { };
2014
+ valueBuffer = null;
2015
+ _disabled = false;
2016
+ dropdownInitialized = false;
2017
+ popperInstance;
2018
+ ngOnInit() {
2019
+ this.subscription = this.onSearch
2020
+ .pipe(debounceTime(300), distinctUntilChanged())
2021
+ .subscribe((searchText) => {
2022
+ this.focusOption(searchText);
2023
+ this.keySearch = "";
2024
+ });
2025
+ this.filteredOptions = this._options;
2026
+ // Sync the input control value with the selected item
2027
+ this.inputControl.valueChanges.subscribe((value) => {
2028
+ this.onChange(value);
2029
+ });
2030
+ }
2031
+ ngAfterViewChecked() {
2032
+ if (this.isDropdownOpen && !this.dropdownInitialized) {
2033
+ this.createPopperInstance();
2034
+ this.dropdownInitialized = true;
2035
+ }
2036
+ }
2037
+ ngOnChanges(changes) {
2038
+ if (changes["options"]) {
2039
+ this.filteredOptions = [...this._options];
2040
+ this.highlightSelectedItem();
2041
+ }
2042
+ if (changes["disabled"]) {
2043
+ this.disabled = changes["disabled"].currentValue;
2044
+ }
2045
+ }
2046
+ ngOnDestroy() {
2047
+ this.subscription.unsubscribe();
2048
+ }
2049
+ writeValue(value) {
2050
+ if (this.inputControl.disabled !== this.disabled) {
2051
+ this.setDisabledState(this.disabled);
2052
+ }
2053
+ this.valueBuffer = value;
2054
+ this.processValueBuffer();
2055
+ }
2056
+ processValueBuffer() {
2057
+ if (this.valueBuffer !== null &&
2058
+ this._options &&
2059
+ this._options.length > 0) {
2060
+ // Try to match selected item by ID
2061
+ const matchedItem = this._options.find((option) => option.id === this.valueBuffer);
2062
+ if (matchedItem) {
2063
+ this.selectedItem = matchedItem;
2064
+ const displayValue = matchedItem[this.optionDisplayProperty];
2065
+ this.inputControl.setValue(displayValue, { emitEvent: false });
2066
+ this.filteredOptions = [...this._options];
2067
+ this.onTouched();
2068
+ this.inputControl.markAsDirty();
2069
+ this.optionPatched.emit(this.selectedItem);
2070
+ }
2071
+ else {
2072
+ // If not found, don't clear the field automatically
2073
+ // You may choose to keep it or clear it. Here we clear it.
2074
+ this.selectedItem = null;
2075
+ this.inputControl.setValue("", { emitEvent: false });
2076
+ this.highlightedIndex = null;
2077
+ }
2078
+ }
2079
+ else if (this.valueBuffer === null) {
2080
+ this.selectedItem = null;
2081
+ this.inputControl.setValue("", { emitEvent: false });
2082
+ this.highlightedIndex = null;
2083
+ }
2084
+ }
2085
+ registerOnChange(fn) {
2086
+ this.onChange = fn;
2087
+ }
2088
+ registerOnTouched(fn) {
2089
+ this.onTouched = fn;
2090
+ }
2091
+ // events
2092
+ onBlur() {
2093
+ this.blurEvent.emit();
2094
+ this.onTouched();
2095
+ setTimeout(() => {
2096
+ this.isDropdownOpen = false;
2097
+ }, 300);
2098
+ }
2099
+ onKeyDown(event) {
2100
+ const isAlphabetOrNumberKey = /^[a-zA-Z0-9]$/.test(event.key);
2101
+ if (this.isDropdownOpen) {
2102
+ switch (event.key) {
2103
+ case "ArrowDown":
2104
+ this.highlightNext();
2105
+ event.preventDefault();
2106
+ break;
2107
+ case "ArrowUp":
2108
+ this.highlightPrevious();
2109
+ event.preventDefault();
2110
+ break;
2111
+ case "Enter":
2112
+ if (this.highlightedIndex !== null) {
2113
+ this.selectOption(this.filteredOptions[this.highlightedIndex]);
2114
+ }
2115
+ event.preventDefault();
2116
+ break;
2117
+ case "Escape":
2118
+ this.isDropdownOpen = false;
2119
+ break;
2120
+ default:
2121
+ if (isAlphabetOrNumberKey) {
2122
+ this.keySearch += event.key.toString();
2123
+ if (this.keySearch !== "") {
2124
+ this.onSearch.next(this.keySearch);
2125
+ }
2126
+ }
2127
+ break;
2128
+ }
2129
+ }
2130
+ else if (event.key === " ") {
2131
+ this.toggleDropdown();
2132
+ event.preventDefault();
2133
+ }
2134
+ }
2135
+ setDisabledState(isDisabled) {
2136
+ this.disabled = isDisabled;
2137
+ if (this.disabled) {
2138
+ this.inputControl.disable({ emitEvent: false });
2139
+ }
2140
+ else {
2141
+ this.inputControl.enable({ emitEvent: false });
2142
+ }
2143
+ }
2144
+ selectOption(option) {
2145
+ if (this.selectedItem !== option) {
2146
+ this.selectedItem = option;
2147
+ this.isDropdownOpen = false;
2148
+ const displayValue = this.selectedItem[this.optionDisplayProperty];
2149
+ this.inputControl.setValue(displayValue, { emitEvent: false });
2150
+ this.onChange(this.selectedItem.id);
2151
+ this.onTouched();
2152
+ this.inputControl.markAsDirty();
2153
+ this.optionSelected.emit(this.selectedItem);
2154
+ }
2155
+ }
2156
+ // highlight
2157
+ highlightSelectedItem() {
2158
+ const selectedIndex = this.filteredOptions.findIndex((option) => option === this.selectedItem);
2159
+ if (selectedIndex !== -1) {
2160
+ this.highlightedIndex = selectedIndex;
2161
+ this.scrollToHighlighted();
2162
+ }
2163
+ else {
2164
+ this.highlightedIndex = null;
2165
+ }
2166
+ }
2167
+ highlightNext() {
2168
+ if (this.highlightedIndex === null ||
2169
+ this.highlightedIndex === this.filteredOptions.length - 1) {
2170
+ this.highlightedIndex = 0;
2171
+ }
2172
+ else {
2173
+ this.highlightedIndex++;
2174
+ }
2175
+ this.scrollToHighlighted();
2176
+ }
2177
+ highlightPrevious() {
2178
+ if (this.highlightedIndex === null || this.highlightedIndex === 0) {
2179
+ this.highlightedIndex = this.filteredOptions.length - 1;
2180
+ }
2181
+ else {
2182
+ this.highlightedIndex--;
2183
+ }
2184
+ this.scrollToHighlighted();
2185
+ }
2186
+ focusOption(value) {
2187
+ const filterValue = value.toLowerCase();
2188
+ let foundIndex = this._options.findIndex((option) => option[this.optionDisplayProperty]?.toLowerCase() === filterValue);
2189
+ if (foundIndex === -1) {
2190
+ foundIndex = this._options.findIndex((option) => option[this.optionDisplayProperty]
2191
+ ?.toLowerCase()
2192
+ .startsWith(filterValue));
2193
+ }
2194
+ if (foundIndex === -1) {
2195
+ foundIndex = this._options.findIndex((option) => option[this.optionDisplayProperty]?.toLowerCase().includes(filterValue));
2196
+ }
2197
+ if (foundIndex !== -1) {
2198
+ this.highlightedIndex = foundIndex;
2199
+ this.scrollToHighlighted();
2200
+ }
2201
+ else {
2202
+ this.highlightedIndex = null;
2203
+ }
2204
+ }
2205
+ scrollToHighlighted() {
2206
+ setTimeout(() => {
2207
+ const container = document.querySelector(".option-list");
2208
+ if (container) {
2209
+ const highlighted = container.querySelector(".highlighted");
2210
+ if (highlighted) {
2211
+ highlighted.scrollIntoView({ block: "nearest" });
2212
+ }
2213
+ }
2214
+ }, 0);
2215
+ }
2216
+ // actions
2217
+ onAddNewItemClick() {
2218
+ this.addNewItemClicked.emit();
2219
+ }
2220
+ // popper
2221
+ createPopperInstance() {
2222
+ if (this.dropdown) {
2223
+ this.popperInstance = createPopper(this.input.nativeElement, this.dropdown.nativeElement, {
2224
+ placement: "bottom-start",
2225
+ modifiers: [
2226
+ {
2227
+ name: "offset",
2228
+ options: {
2229
+ offset: [0, 1],
2230
+ },
2231
+ },
2232
+ {
2233
+ name: "flip",
2234
+ options: {
2235
+ fallbackPlacements: ["top-start", "bottom-start"],
2236
+ },
2237
+ },
2238
+ ],
2239
+ });
2240
+ }
2241
+ }
2242
+ // toggle, open, hide, reset and clear
2243
+ toggleDropdown() {
2244
+ if (!this.inputControl.disabled) {
2245
+ this.isDropdownOpen = !this.isDropdownOpen;
2246
+ this.dropdownInitialized = false;
2247
+ if (this.isDropdownOpen && this.selectedItem) {
2248
+ this.highlightSelectedItem();
2249
+ }
2250
+ }
2251
+ }
2252
+ openDropdown(event) {
2253
+ if (event.ctrlKey && event.key === "Enter") {
2254
+ this.toggleDropdown();
2255
+ }
2256
+ }
2257
+ selectListOutsideClick(e) {
2258
+ this.isDropdownOpen = false;
2259
+ }
2260
+ hideDropdown() {
2261
+ setTimeout(() => {
2262
+ this.isDropdownOpen = false;
2263
+ }, 200);
2264
+ }
2265
+ resetSelection() {
2266
+ this.selectionCleared.emit();
2267
+ this.selectedItem = null;
2268
+ this.inputControl.setValue("", { emitEvent: false });
2269
+ this.onChange(null);
2270
+ this.onTouched();
2271
+ this.valueBuffer = null;
2272
+ this.isDropdownOpen = false;
2273
+ }
2274
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: SelectControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
2275
+ 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: [
2276
+ {
2277
+ provide: NG_VALUE_ACCESSOR,
2278
+ useExisting: forwardRef(() => SelectControl),
2279
+ multi: true,
2280
+ },
2281
+ ], 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"] }] });
2282
+ }
2283
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: SelectControl, decorators: [{
2284
+ type: Component,
2285
+ args: [{ selector: "select-control", standalone: true, imports: [
2286
+ CommonModule,
2287
+ ReactiveFormsModule,
2288
+ ClickOutsideDirective
2289
+ ], providers: [
2290
+ {
2291
+ provide: NG_VALUE_ACCESSOR,
2292
+ useExisting: forwardRef(() => SelectControl),
2293
+ multi: true,
2294
+ },
2295
+ ], 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"] }]
2296
+ }], propDecorators: { optionDisplayProperty: [{
2297
+ type: Input
2298
+ }], required: [{
2299
+ type: Input
2300
+ }], placeholder: [{
2301
+ type: Input
2302
+ }], customClass: [{
2303
+ type: Input
2304
+ }], clearVal: [{
2305
+ type: Input
2306
+ }], deFocus: [{
2307
+ type: Input
2308
+ }], error: [{
2309
+ type: Input
2310
+ }], errorMessage: [{
2311
+ type: Input
2312
+ }], autocomplete: [{
2313
+ type: Input
2314
+ }], inputLoader: [{
2315
+ type: Input
2316
+ }], isAddNewItem: [{
2317
+ type: Input
2318
+ }], optionSelected: [{
2319
+ type: Output
2320
+ }], selectionCleared: [{
2321
+ type: Output
2322
+ }], addNewItemClicked: [{
2323
+ type: Output
2324
+ }], blurEvent: [{
2325
+ type: Output
2326
+ }], optionPatched: [{
2327
+ type: Output
2328
+ }], title: [{
2329
+ type: Input
2330
+ }], options: [{
2331
+ type: Input
2332
+ }], disabled: [{
2333
+ type: Input
2334
+ }], dropdown: [{
2335
+ type: ViewChild,
2336
+ args: ["dropdown", { static: false }]
2337
+ }], input: [{
2338
+ type: ViewChild,
2339
+ args: ["input", { static: false }]
2340
+ }] } });
2341
+
2342
+ class SwitchControl {
2343
+ _renderer;
2344
+ _elementRef;
2345
+ title;
2346
+ customClass;
2347
+ set disabled(value) {
2348
+ this._disabled = value;
2349
+ this.setDisabledState(value);
2350
+ }
2351
+ set checked(value) {
2352
+ this._checked = value;
2353
+ this.switchControl.setValue(value, { emitEvent: false }); // set without emitting
2354
+ }
2355
+ switchChange = new EventEmitter();
2356
+ blurEvent = new EventEmitter();
2357
+ switchContainer;
2358
+ get checked() {
2359
+ return this._checked;
2360
+ }
2361
+ _checked = false;
2362
+ _disabled = false;
2363
+ switchControl = new FormControl({ value: false, disabled: this._disabled });
2364
+ onChange = () => { };
2365
+ onTouched = () => { };
2366
+ constructor(_renderer, _elementRef) {
2367
+ this._renderer = _renderer;
2368
+ this._elementRef = _elementRef;
2369
+ this.switchControl.valueChanges.subscribe(value => {
2370
+ this.onChange(value ?? false);
2371
+ this.onTouched();
2372
+ this.switchChange.emit(value ?? false);
2373
+ });
2374
+ }
2375
+ writeValue(value) {
2376
+ if (value !== undefined) {
2377
+ this.switchControl.setValue(value, { emitEvent: false });
2378
+ }
2379
+ }
2380
+ registerOnChange(fn) {
2381
+ this.onChange = fn;
2382
+ }
2383
+ registerOnTouched(fn) {
2384
+ this.onTouched = fn;
2385
+ }
2386
+ // events
2387
+ onFocus() {
2388
+ this.switchContainer.nativeElement.classList.add('focused');
2389
+ }
2390
+ onBlur() {
2391
+ this.blurEvent.emit();
2392
+ this.onTouched();
2393
+ this.switchContainer.nativeElement.classList.remove('focused');
2394
+ }
2395
+ setDisabledState(isDisabled) {
2396
+ this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
2397
+ if (isDisabled) {
2398
+ this.switchControl.disable();
2399
+ }
2400
+ else {
2401
+ this.switchControl.enable();
2402
+ }
2403
+ }
2404
+ 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 });
2405
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.3", type: SwitchControl, isStandalone: true, selector: "switch-control", inputs: { title: "title", customClass: "customClass", disabled: "disabled", checked: "checked" }, outputs: { switchChange: "switchChange", blurEvent: "blurEvent" }, providers: [
2406
+ {
2407
+ provide: NG_VALUE_ACCESSOR,
2408
+ useExisting: forwardRef(() => SwitchControl),
2409
+ multi: true,
2410
+ },
2411
+ ], viewQueries: [{ propertyName: "switchContainer", first: true, predicate: ["switchContainer"], descendants: true }], ngImport: i0, template: "<label class=\"switch-container\" [ngClass]=\"customClass\" #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"] }] });
2412
+ }
2413
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: SwitchControl, decorators: [{
2414
+ type: Component,
2415
+ args: [{ selector: 'switch-control', standalone: true, imports: [
2416
+ CommonModule,
2417
+ ReactiveFormsModule
2418
+ ], providers: [
2419
+ {
2420
+ provide: NG_VALUE_ACCESSOR,
2421
+ useExisting: forwardRef(() => SwitchControl),
2422
+ multi: true,
2423
+ },
2424
+ ], template: "<label class=\"switch-container\" [ngClass]=\"customClass\" #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"] }]
2425
+ }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }], propDecorators: { title: [{
2426
+ type: Input
2427
+ }], customClass: [{
2428
+ type: Input
2429
+ }], disabled: [{
2430
+ type: Input
2431
+ }], checked: [{
2432
+ type: Input
2433
+ }], switchChange: [{
2434
+ type: Output
2435
+ }], blurEvent: [{
2436
+ type: Output
2437
+ }], switchContainer: [{
2438
+ type: ViewChild,
2439
+ args: ['switchContainer']
2440
+ }] } });
2441
+
2442
+ class TextEditor {
2443
+ header = true;
2444
+ media = true;
2445
+ link = true;
2446
+ placeholder = 'Type here...';
2447
+ editorRef;
2448
+ onChange = () => { };
2449
+ onTouch = () => { };
2450
+ undoStack = [];
2451
+ redoStack = [];
2452
+ ngOnInit() { }
2453
+ ngAfterViewInit() {
2454
+ this.saveState();
2455
+ }
2456
+ writeValue(value) {
2457
+ this.editorRef.nativeElement.innerHTML = value || '';
2458
+ this.saveState();
2459
+ }
2460
+ registerOnChange(fn) {
2461
+ this.onChange = fn;
2462
+ }
2463
+ registerOnTouched(fn) {
2464
+ this.onTouch = fn;
2465
+ }
2466
+ onInput() {
2467
+ const html = this.editorRef.nativeElement.innerHTML;
2468
+ this.onChange(html);
2469
+ this.saveState();
2470
+ }
2471
+ // events
2472
+ onTouched() {
2473
+ this.onTouch();
2474
+ }
2475
+ setDisabledState(isDisabled) {
2476
+ this.editorRef.nativeElement.contentEditable = (!isDisabled).toString();
2477
+ }
2478
+ // ---- Selection & Range Utilities ----
2479
+ getSelectionRange() {
2480
+ const selection = window.getSelection();
2481
+ if (selection && selection.rangeCount > 0) {
2482
+ return selection.getRangeAt(0);
2483
+ }
2484
+ return null;
2485
+ }
2486
+ wrapSelection(htmlTag, style) {
2487
+ const range = this.getSelectionRange();
2488
+ if (!range || range.collapsed)
2489
+ return; // no selection
2490
+ const selectedText = range.extractContents();
2491
+ const el = document.createElement(htmlTag);
2492
+ if (style) {
2493
+ el.setAttribute('style', style);
2494
+ }
2495
+ el.appendChild(selectedText);
2496
+ range.insertNode(el);
2497
+ this.mergeAdjacentSimilarElements(el);
2498
+ this.onInput();
2499
+ }
2500
+ // Merges adjacent spans with the same style to avoid clutter
2501
+ mergeAdjacentSimilarElements(el) {
2502
+ const parent = el.parentElement;
2503
+ if (!parent)
2504
+ return;
2505
+ const siblings = Array.from(parent.childNodes);
2506
+ for (let i = 0; i < siblings.length - 1; i++) {
2507
+ const current = siblings[i];
2508
+ const next = siblings[i + 1];
2509
+ if (current.nodeType === 1 && next?.nodeType === 1) {
2510
+ if (current.tagName === next.tagName && current.getAttribute('style') === next.getAttribute('style')) {
2511
+ // merge them
2512
+ while (next.firstChild) {
2513
+ current.appendChild(next.firstChild);
2514
+ }
2515
+ next.remove();
2516
+ }
2517
+ }
2518
+ }
2519
+ }
2520
+ // ---- Inline Formatting ----
2521
+ // applyInlineStyle() toggles inline styles by wrapping selection in a styled span
2522
+ applyInlineStyle(styleType) {
2523
+ let style = '';
2524
+ if (styleType === 'bold')
2525
+ style = 'font-weight:bold;';
2526
+ else if (styleType === 'italic')
2527
+ style = 'font-style:italic;';
2528
+ else if (styleType === 'underline')
2529
+ style = 'text-decoration:underline;';
2530
+ this.wrapSelection('span', style);
2531
+ }
2532
+ applyColor(event) {
2533
+ const input = event.target;
2534
+ const color = input.value;
2535
+ // Now `color` is a string and `input` is never null
2536
+ this.wrapSelection('span', `color:${color};`);
2537
+ }
2538
+ applyHighlight(event) {
2539
+ const input = event.target;
2540
+ const color = input.value;
2541
+ this.wrapSelection('span', `background-color:${color};`);
2542
+ }
2543
+ // ---- Block Formatting (Headings, Paragraph) ----
2544
+ // Replaces the parent block element containing selection with a new block type
2545
+ applyBlockFormat(blockTag) {
2546
+ const range = this.getSelectionRange();
2547
+ if (!range)
2548
+ return;
2549
+ // Find the nearest block element
2550
+ let block = this.findBlockAncestor(range.commonAncestorContainer);
2551
+ if (!block) {
2552
+ // If no block found, wrap the selection in a block
2553
+ const wrapper = document.createElement(blockTag);
2554
+ wrapper.appendChild(range.extractContents());
2555
+ range.insertNode(wrapper);
2556
+ }
2557
+ else {
2558
+ // Replace the block with a new block type
2559
+ const newBlock = document.createElement(blockTag);
2560
+ // Move children
2561
+ while (block.firstChild) {
2562
+ newBlock.appendChild(block.firstChild);
2563
+ }
2564
+ block.replaceWith(newBlock);
2565
+ }
2566
+ this.onInput();
2567
+ }
2568
+ findBlockAncestor(node) {
2569
+ while (node && node !== this.editorRef.nativeElement) {
2570
+ if (this.isBlockElement(node)) {
2571
+ return node;
2572
+ }
2573
+ node = node.parentNode;
2574
+ }
2575
+ return null;
2576
+ }
2577
+ isBlockElement(node) {
2578
+ if (node.nodeType !== 1)
2579
+ return false;
2580
+ const display = window.getComputedStyle(node).display;
2581
+ return display === 'block' || display === 'list-item';
2582
+ }
2583
+ // ---- Lists ----
2584
+ // Convert selected lines into a list
2585
+ applyList(listType) {
2586
+ const range = this.getSelectionRange();
2587
+ if (!range)
2588
+ return;
2589
+ const commonBlock = this.findBlockAncestor(range.commonAncestorContainer);
2590
+ // Get the text in the selection
2591
+ const content = range.extractContents();
2592
+ const lines = this.splitContentByLine(content);
2593
+ const listEl = document.createElement(listType);
2594
+ for (const line of lines) {
2595
+ const li = document.createElement('li');
2596
+ li.appendChild(line);
2597
+ listEl.appendChild(li);
2598
+ }
2599
+ if (commonBlock) {
2600
+ // Insert the list right where the block was or at the selection
2601
+ range.insertNode(listEl);
2602
+ }
2603
+ else {
2604
+ // If no common block, just insert at current position
2605
+ range.insertNode(listEl);
2606
+ }
2607
+ this.onInput();
2608
+ }
2609
+ splitContentByLine(fragment) {
2610
+ // A very naive approach: split by <br> or block elements.
2611
+ // For advanced logic, detect line breaks more thoroughly.
2612
+ const lines = [];
2613
+ let currentFrag = document.createDocumentFragment();
2614
+ Array.from(fragment.childNodes).forEach((node) => {
2615
+ if (node.nodeName === 'BR' || this.isBlockElement(node)) {
2616
+ // This node signifies a new line
2617
+ lines.push(currentFrag);
2618
+ currentFrag = document.createDocumentFragment();
2619
+ if (this.isBlockElement(node)) {
2620
+ while (node.firstChild) {
2621
+ currentFrag.appendChild(node.firstChild);
2622
+ }
2623
+ lines.push(currentFrag);
2624
+ currentFrag = document.createDocumentFragment();
2625
+ }
2626
+ }
2627
+ else {
2628
+ currentFrag.appendChild(node);
2629
+ }
2630
+ });
2631
+ if (currentFrag.childNodes.length > 0) {
2632
+ lines.push(currentFrag);
2633
+ }
2634
+ return lines;
2635
+ }
2636
+ // ---- Links ----
2637
+ createLink() {
2638
+ const url = prompt('Enter URL', 'https://');
2639
+ if (!url)
2640
+ return;
2641
+ const range = this.getSelectionRange();
2642
+ if (!range || range.collapsed)
2643
+ return;
2644
+ const selectedContent = range.extractContents();
2645
+ const a = document.createElement('a');
2646
+ a.href = url;
2647
+ a.appendChild(selectedContent);
2648
+ range.insertNode(a);
2649
+ this.onInput();
2650
+ }
2651
+ // ---- Images ----
2652
+ insertImage() {
2653
+ const url = prompt('Enter image URL:');
2654
+ if (!url)
2655
+ return;
2656
+ const range = this.getSelectionRange();
2657
+ if (!range)
2658
+ return;
2659
+ const img = document.createElement('img');
2660
+ img.src = url;
2661
+ range.insertNode(img);
2662
+ this.onInput();
2663
+ }
2664
+ // ---- Alignment ----
2665
+ setAlignment(alignment) {
2666
+ // Find the block and set text-align
2667
+ const range = this.getSelectionRange();
2668
+ if (!range)
2669
+ return;
2670
+ const block = this.findBlockAncestor(range.commonAncestorContainer);
2671
+ if (block) {
2672
+ block.style.textAlign = alignment;
2673
+ this.onInput();
2674
+ }
2675
+ }
2676
+ // ---- Clear Formatting ----
2677
+ clearFormatting() {
2678
+ const html = this.editorRef.nativeElement.innerText;
2679
+ // Convert innerText to a plain <p> block for simplicity
2680
+ this.editorRef.nativeElement.innerHTML = `<p>${html}</p>`;
2681
+ this.onInput();
2682
+ }
2683
+ // ---- Undo/Redo ----
2684
+ saveState() {
2685
+ const currentHtml = this.editorRef.nativeElement.innerHTML;
2686
+ if (this.undoStack.length === 0 || this.undoStack[this.undoStack.length - 1].html !== currentHtml) {
2687
+ this.undoStack.push({ html: currentHtml });
2688
+ this.redoStack = []; // clear redo on new input
2689
+ }
2690
+ }
2691
+ // clear options
2692
+ undo() {
2693
+ if (this.undoStack.length > 1) {
2694
+ const current = this.undoStack.pop();
2695
+ this.redoStack.push(current);
2696
+ const previous = this.undoStack[this.undoStack.length - 1];
2697
+ this.editorRef.nativeElement.innerHTML = previous.html;
2698
+ this.onChange(previous.html);
2699
+ }
2700
+ }
2701
+ redo() {
2702
+ if (this.redoStack.length > 0) {
2703
+ const state = this.redoStack.pop();
2704
+ this.undoStack.push(state);
2705
+ this.editorRef.nativeElement.innerHTML = state.html;
2706
+ this.onChange(state.html);
2707
+ }
2708
+ }
2709
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TextEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
2710
+ 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"] }] });
2711
+ }
2712
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TextEditor, decorators: [{
2713
+ type: Component,
2714
+ args: [{ selector: 'app-text-editor', imports: [CommonModule,
2715
+ ], 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"] }]
2716
+ }], propDecorators: { header: [{
2717
+ type: Input
2718
+ }], media: [{
2719
+ type: Input
2720
+ }], link: [{
2721
+ type: Input
2722
+ }], placeholder: [{
2723
+ type: Input
2724
+ }], editorRef: [{
2725
+ type: ViewChild,
2726
+ args: ['editor']
2727
+ }] } });
2728
+
2729
+ class TextareaControl {
2730
+ title;
2731
+ placeholder = '';
2732
+ customClass;
2733
+ clearVal = true;
2734
+ disabled = false;
2735
+ required = false;
2736
+ error = false; // New input for error state
2737
+ errorMessage = ''; // New input for error message
2738
+ textareaChange = new EventEmitter();
2739
+ selectionCleared = new EventEmitter();
2740
+ blurEvent = new EventEmitter();
2741
+ onChange = () => { };
2742
+ onTouched = () => { };
2743
+ textareaControl = new FormControl('');
2744
+ constructor() {
2745
+ this.textareaControl.valueChanges.subscribe(value => {
2746
+ this.onChange(value);
2747
+ this.onTouched();
2748
+ this.textareaChange.emit(value ?? '');
2749
+ });
2750
+ }
2751
+ writeValue(value) {
2752
+ if (value !== undefined) {
2753
+ this.textareaControl.setValue(value, { emitEvent: false });
2754
+ }
2755
+ if (this.textareaControl.disabled !== this.disabled) {
2756
+ this.setDisabledState(this.disabled);
2757
+ }
2758
+ }
2759
+ registerOnChange(fn) {
2760
+ this.onChange = fn;
2761
+ }
2762
+ registerOnTouched(fn) {
2763
+ this.onTouched = fn;
2764
+ }
2765
+ // events
2766
+ onBlur() {
2767
+ this.blurEvent.emit();
2768
+ this.onTouched();
2769
+ }
2770
+ setDisabledState(isDisabled) {
2771
+ if (this.disabled) {
2772
+ this.textareaControl.disable({ emitEvent: false });
2773
+ }
2774
+ else {
2775
+ this.textareaControl.enable({ emitEvent: false });
2776
+ }
2777
+ }
2778
+ // clear
2779
+ resetTextarea() {
2780
+ this.textareaControl.setValue('');
2781
+ this.selectionCleared.emit();
2782
+ }
2783
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TextareaControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
2784
+ 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", required: "required", error: "error", errorMessage: "errorMessage" }, outputs: { textareaChange: "textareaChange", selectionCleared: "selectionCleared", blurEvent: "blurEvent" }, providers: [
2785
+ {
2786
+ provide: NG_VALUE_ACCESSOR,
2787
+ useExisting: forwardRef(() => TextareaControl),
2788
+ multi: true,
2789
+ },
2790
+ ], 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 }\" (blur)=\"onBlur()\"></textarea>\r\n\r\n <span class=\"focus-border\"></span>\r\n\r\n @if (textareaControl.value && clearVal) {\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"] }] });
2791
+ }
2792
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TextareaControl, decorators: [{
2793
+ type: Component,
2794
+ args: [{ selector: 'textarea-control', standalone: true, imports: [
2795
+ CommonModule,
2796
+ ReactiveFormsModule
2797
+ ], providers: [
2798
+ {
2799
+ provide: NG_VALUE_ACCESSOR,
2800
+ useExisting: forwardRef(() => TextareaControl),
2801
+ multi: true,
2802
+ },
2803
+ ], 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 }\" (blur)=\"onBlur()\"></textarea>\r\n\r\n <span class=\"focus-border\"></span>\r\n\r\n @if (textareaControl.value && clearVal) {\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"] }]
2804
+ }], ctorParameters: () => [], propDecorators: { title: [{
2805
+ type: Input
2806
+ }], placeholder: [{
2807
+ type: Input
2808
+ }], customClass: [{
2809
+ type: Input
2810
+ }], clearVal: [{
2811
+ type: Input
2812
+ }], disabled: [{
2813
+ type: Input
2814
+ }], required: [{
2815
+ type: Input
2816
+ }], error: [{
2817
+ type: Input
2818
+ }], errorMessage: [{
2819
+ type: Input
2820
+ }], textareaChange: [{
2821
+ type: Output
2822
+ }], selectionCleared: [{
2823
+ type: Output
2824
+ }], blurEvent: [{
2825
+ type: Output
2826
+ }] } });
2827
+
2828
+ /*
2829
+ * Public API Surface of heal-lib
2830
+ */
2831
+
2832
+ /**
2833
+ * Generated bundle index. Do not edit.
2834
+ */
2835
+
2836
+ export { AutocompleteControl, CalendarControl, CheckboxControl, InputControl, MultiselectControl, SelectControl, SwitchControl, TextEditor, TextareaControl };
2837
+ //# sourceMappingURL=nexheal-lib.mjs.map