nexheal-lib 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.editorconfig +17 -0
  2. package/.vscode/extensions.json +4 -0
  3. package/.vscode/launch.json +20 -0
  4. package/.vscode/tasks.json +42 -0
  5. package/README.md +15 -19
  6. package/angular.json +36 -0
  7. package/package.json +47 -23
  8. package/projects/nexheal-lib/README.md +63 -0
  9. package/projects/nexheal-lib/ng-package.json +9 -0
  10. package/projects/nexheal-lib/package.json +12 -0
  11. package/projects/nexheal-lib/src/directives/clickoutside.directive.ts +34 -0
  12. package/projects/nexheal-lib/src/lib/controls/autocomplete-control/autocomplete-control.component.html +52 -0
  13. package/projects/nexheal-lib/src/lib/controls/autocomplete-control/autocomplete-control.component.scss +22 -0
  14. package/projects/nexheal-lib/src/lib/controls/autocomplete-control/autocomplete-control.component.spec.ts +22 -0
  15. package/projects/nexheal-lib/src/lib/controls/autocomplete-control/autocomplete-control.component.ts +367 -0
  16. package/projects/nexheal-lib/src/lib/controls/calendar-control/calendar-control.component.html +152 -0
  17. package/projects/nexheal-lib/src/lib/controls/calendar-control/calendar-control.component.scss +194 -0
  18. package/projects/nexheal-lib/src/lib/controls/calendar-control/calendar-control.component.spec.ts +22 -0
  19. package/projects/nexheal-lib/src/lib/controls/calendar-control/calendar-control.component.ts +759 -0
  20. package/projects/nexheal-lib/src/lib/controls/checkbox-control/checkbox-control.component.html +4 -0
  21. package/projects/nexheal-lib/src/lib/controls/checkbox-control/checkbox-control.component.spec.ts +22 -0
  22. package/projects/nexheal-lib/src/lib/controls/checkbox-control/checkbox-control.component.ts +94 -0
  23. package/projects/nexheal-lib/src/lib/controls/input-control/input-control.component.html +61 -0
  24. package/projects/nexheal-lib/src/lib/controls/input-control/input-control.component.scss +132 -0
  25. package/projects/nexheal-lib/src/lib/controls/input-control/input-control.component.spec.ts +22 -0
  26. package/projects/nexheal-lib/src/lib/controls/input-control/input-control.component.ts +202 -0
  27. package/projects/nexheal-lib/src/lib/controls/multiselect-control/multiselect-control.component.html +72 -0
  28. package/projects/nexheal-lib/src/lib/controls/multiselect-control/multiselect-control.component.scss +90 -0
  29. package/projects/nexheal-lib/src/lib/controls/multiselect-control/multiselect-control.component.spec.ts +22 -0
  30. package/projects/nexheal-lib/src/lib/controls/multiselect-control/multiselect-control.component.ts +482 -0
  31. package/projects/nexheal-lib/src/lib/controls/select-control/select-control.component.html +53 -0
  32. package/projects/nexheal-lib/src/lib/controls/select-control/select-control.component.scss +19 -0
  33. package/projects/nexheal-lib/src/lib/controls/select-control/select-control.component.spec.ts +22 -0
  34. package/projects/nexheal-lib/src/lib/controls/select-control/select-control.component.ts +375 -0
  35. package/projects/nexheal-lib/src/lib/controls/switch-control/switch-control.component.html +4 -0
  36. package/projects/nexheal-lib/src/lib/controls/switch-control/switch-control.component.scss +53 -0
  37. package/projects/nexheal-lib/src/lib/controls/switch-control/switch-control.component.spec.ts +22 -0
  38. package/projects/nexheal-lib/src/lib/controls/switch-control/switch-control.component.ts +93 -0
  39. package/projects/nexheal-lib/src/lib/controls/text-editor/text-editor.component.html +88 -0
  40. package/projects/nexheal-lib/src/lib/controls/text-editor/text-editor.component.scss +122 -0
  41. package/projects/nexheal-lib/src/lib/controls/text-editor/text-editor.component.spec.ts +22 -0
  42. package/projects/nexheal-lib/src/lib/controls/text-editor/text-editor.component.ts +314 -0
  43. package/projects/nexheal-lib/src/lib/controls/textarea-control/textarea-control.component.html +19 -0
  44. package/projects/nexheal-lib/src/lib/controls/textarea-control/textarea-control.component.scss +15 -0
  45. package/projects/nexheal-lib/src/lib/controls/textarea-control/textarea-control.component.spec.ts +22 -0
  46. package/projects/nexheal-lib/src/lib/controls/textarea-control/textarea-control.component.ts +83 -0
  47. package/projects/nexheal-lib/src/public-api.ts +13 -0
  48. package/projects/nexheal-lib/src/styles/nexheal.scss +1 -0
  49. package/projects/nexheal-lib/tsconfig.lib.json +18 -0
  50. package/projects/nexheal-lib/tsconfig.lib.prod.json +11 -0
  51. package/projects/nexheal-lib/tsconfig.spec.json +14 -0
  52. package/tsconfig.json +39 -0
  53. package/fesm2022/nexheal-lib.mjs +0 -2837
  54. package/fesm2022/nexheal-lib.mjs.map +0 -1
  55. package/index.d.ts +0 -498
  56. package/src/styles/fonts/icomoon.eot +0 -0
  57. package/src/styles/fonts/icomoon.svg +0 -46
  58. package/src/styles/fonts/icomoon.ttf +0 -0
  59. package/src/styles/fonts/icomoon.woff +0 -0
  60. package/src/styles/icon.css +0 -133
  61. package/src/styles/nexheal.scss +0 -2
  62. /package/{src → projects/nexheal-lib/src}/styles/_formcontrols.scss +0 -0
@@ -0,0 +1,367 @@
1
+ import { CommonModule } from "@angular/common";
2
+ import {
3
+ Component,
4
+ EventEmitter,
5
+ Input,
6
+ OnDestroy,
7
+ OnInit,
8
+ Output,
9
+ ViewChild,
10
+ ElementRef,
11
+ AfterViewInit,
12
+ } from "@angular/core";
13
+ import {
14
+ ControlValueAccessor,
15
+ FormControl,
16
+ ReactiveFormsModule,
17
+ NG_VALUE_ACCESSOR,
18
+ NG_VALIDATORS,
19
+ } from "@angular/forms";
20
+ import { debounceTime, distinctUntilChanged, Subscription } from "rxjs";
21
+ import { createPopper, Instance as PopperInstance } from "@popperjs/core";
22
+
23
+ @Component({
24
+ selector: "autocomplete-control",
25
+ standalone: true,
26
+ imports: [CommonModule, ReactiveFormsModule],
27
+ templateUrl: "./autocomplete-control.component.html",
28
+ styleUrls: ["./autocomplete-control.component.scss"],
29
+ providers: [
30
+ {
31
+ provide: NG_VALUE_ACCESSOR,
32
+ useExisting: AutocompleteControl,
33
+ multi: true,
34
+ },
35
+ ],
36
+ })
37
+ export class AutocompleteControl
38
+ implements ControlValueAccessor, OnInit, OnDestroy, AfterViewInit {
39
+
40
+ private subscription: Subscription = new Subscription();
41
+
42
+ @Input() title!: string;
43
+ @Input() required: boolean = false;
44
+ @Input() placeholder: string = "";
45
+ @Input() customClass!: string;
46
+ @Input() clearVal: boolean = true;
47
+ @Input() field: string = "";
48
+ @Input() error: boolean = false;
49
+ @Input() errorMessage: string = "";
50
+ @Input() autocomplete: string = "";
51
+ @Input() inputLoader: boolean = false;
52
+ @Input() isAddNewItem: boolean = false;
53
+ @Input() optionDisplayProperty: string = "displayname";
54
+ @Output() optionSelected = new EventEmitter<any>();
55
+ @Output() search = new EventEmitter<string>();
56
+ @Output() selectionCleared = new EventEmitter<void>();
57
+ @Output() addNewItemClicked: EventEmitter<void> = new EventEmitter<void>();
58
+ @Output() blurEvent = new EventEmitter<void>();
59
+ @Output() optionPatched = new EventEmitter<any>();
60
+
61
+ private _disabled: boolean = false;
62
+ private _options: any[] = [];
63
+ @Input() set options(value: any[]) {
64
+ this._options = value || [];
65
+ this.processValueBuffer();
66
+ }
67
+ get options(): any[] {
68
+ return this._options;
69
+ }
70
+ @Input() get disabled(): boolean {
71
+ return this._disabled;
72
+ }
73
+ set disabled(value: boolean) {
74
+ this._disabled = value;
75
+ if (this.inputControl) {
76
+ if (value) {
77
+ this.inputControl.disable();
78
+ } else {
79
+ this.inputControl.enable();
80
+ }
81
+ }
82
+ }
83
+ private valueBuffer: any = null;
84
+ private popperInstance!: PopperInstance;
85
+ private preventDropdownReopen: boolean = false;
86
+ private preventClearOnBlur: boolean = false;
87
+ private onChange: any = () => { };
88
+ private onTouched: any = () => { };
89
+
90
+ @ViewChild("inputElement") inputElement!: ElementRef;
91
+ @ViewChild("dropdownElement") dropdownElement!: ElementRef;
92
+
93
+ inputControl = new FormControl("");
94
+ isDropdownOpen: boolean = false;
95
+ hasFocus: boolean = false;
96
+ selectedItems: any;
97
+ filteredSuggestions: any[] = [];
98
+ highlightedIndex: number | null = null;
99
+
100
+ constructor() {
101
+ this.inputControl.valueChanges
102
+ .pipe(debounceTime(10), distinctUntilChanged())
103
+ .subscribe((value) => {
104
+ if (this.preventDropdownReopen) {
105
+ this.preventDropdownReopen = false;
106
+ return;
107
+ }
108
+ if (value && value.trim().length > 0) {
109
+ const filterValue = value;
110
+ this.filterSuggestions(filterValue);
111
+ this.search.emit(filterValue);
112
+ } else {
113
+ this.isDropdownOpen = false;
114
+ }
115
+ });
116
+ }
117
+
118
+ ngOnInit() {
119
+ this.inputControl.markAsPristine();
120
+ }
121
+
122
+ ngAfterViewInit() {
123
+ this.createPopperInstance();
124
+ }
125
+
126
+ ngOnDestroy() {
127
+ this.subscription.unsubscribe();
128
+ if (this.popperInstance) {
129
+ this.popperInstance.destroy();
130
+ }
131
+ }
132
+
133
+ writeValue(value: any): void {
134
+ this.preventDropdownReopen = true;
135
+ if (value == null) {
136
+ // Clear everything if we get null
137
+ this.valueBuffer = null;
138
+ this.selectedItems = null;
139
+ this.preventDropdownReopen = true;
140
+ this.inputControl.setValue("", { emitEvent: false });
141
+ } else {
142
+ // Otherwise, treat as normal
143
+ this.valueBuffer = value;
144
+ this.processValueBuffer();
145
+ }
146
+ }
147
+ private processValueBuffer() {
148
+ if (this.valueBuffer == null) {
149
+ this.selectedItems = null;
150
+ this.preventDropdownReopen = true;
151
+ // this.inputControl.setValue("", { emitEvent: false });
152
+ return;
153
+ }
154
+ if (this._options && this._options.length > 0) {
155
+ const matchedSuggestion = this._options.find(
156
+ (s) => s.id === this.valueBuffer
157
+ );
158
+ if (matchedSuggestion) {
159
+ this.preventDropdownReopen = true;
160
+ this.inputControl.setValue(
161
+ matchedSuggestion[this.optionDisplayProperty],
162
+ { emitEvent: false }
163
+ );
164
+ this.selectedItems = matchedSuggestion;
165
+ this.onChange(matchedSuggestion.id);
166
+ this.optionPatched.emit(matchedSuggestion);
167
+ this.valueBuffer = null;
168
+ return;
169
+ }
170
+ }
171
+ this.inputControl.setValue("", { emitEvent: false });
172
+ this.selectedItems = null;
173
+ }
174
+
175
+ registerOnChange(fn: any): void {
176
+ this.onChange = fn;
177
+ }
178
+ registerOnTouched(fn: any): void {
179
+ this.onTouched = fn;
180
+ }
181
+
182
+ setDisabledState?(isDisabled: boolean): void {
183
+ if (isDisabled) {
184
+ this.inputControl.disable();
185
+ } else {
186
+ this.inputControl.enable();
187
+ }
188
+ }
189
+
190
+ filterSuggestions(value: string) {
191
+ if (this.preventDropdownReopen || !this.hasFocus) {
192
+ this.preventDropdownReopen = false;
193
+ return;
194
+ }
195
+ if (value === "") {
196
+ this.filteredSuggestions = [];
197
+ this.isDropdownOpen = false;
198
+ return;
199
+ }
200
+ const filterValue = value.toString().toLowerCase();
201
+ this.filteredSuggestions = this.options.filter((suggestion) =>
202
+ suggestion[this.optionDisplayProperty]
203
+ ?.toLowerCase()
204
+ .includes(filterValue)
205
+ );
206
+ this.highlightedIndex = this.filteredSuggestions.length > 0 ? 0 : null;
207
+ this.isDropdownOpen = true;
208
+ setTimeout(() => {
209
+ this.createPopperInstance();
210
+ }, 0);
211
+ }
212
+ selectSuggestion(suggestion: any) {
213
+ this.preventDropdownReopen = true;
214
+ this.inputControl.setValue(suggestion[this.optionDisplayProperty], {
215
+ emitEvent: false,
216
+ });
217
+ this.onChange(suggestion.id);
218
+ this.inputControl.markAsTouched();
219
+ this.inputControl.updateValueAndValidity(); // Trigger validation update
220
+ this.isDropdownOpen = false;
221
+ if (this.selectedItems?.id !== suggestion.id) {
222
+ this.selectedItems = suggestion;
223
+ this.optionSelected.emit(suggestion);
224
+ }
225
+ }
226
+
227
+ // events
228
+ onFocus() {
229
+ this.hasFocus = true;
230
+ }
231
+ onBlur() {
232
+ this.blurEvent.emit();
233
+ this.onTouched();
234
+ // Delay onBlur handling so that mousedown and click can occur first.
235
+ setTimeout(() => {
236
+ this.hasFocus = false;
237
+ this.isDropdownOpen = false;
238
+
239
+ // If an option click is in progress, do not clear the input.
240
+ if (!this.preventClearOnBlur) {
241
+ // Optionally: check if the current value is valid against options.
242
+ // If not found, clear the input.
243
+ const currentValue = this.inputControl.value;
244
+ const found = this._options.find(
245
+ (opt) =>
246
+ String(opt[this.optionDisplayProperty]).toLowerCase() ===
247
+ String(currentValue).toLowerCase()
248
+ );
249
+ if (!found) {
250
+ this.inputControl.setValue("");
251
+ this.selectedItems = null;
252
+ this.onChange(null);
253
+ }
254
+ }
255
+
256
+ // Reset the flag after handling blur.
257
+ this.preventClearOnBlur = false;
258
+ }, 300);
259
+ }
260
+ onKeyDown(event: KeyboardEvent) {
261
+ if (!this.isDropdownOpen) {
262
+ return;
263
+ }
264
+ switch (event.key) {
265
+ case "ArrowDown":
266
+ this.highlightedIndex =
267
+ this.highlightedIndex === null ||
268
+ this.highlightedIndex === this.filteredSuggestions.length - 1
269
+ ? 0
270
+ : this.highlightedIndex + 1;
271
+ event.preventDefault();
272
+ this.scrollHighlightedItemIntoView();
273
+ break;
274
+ case "ArrowUp":
275
+ this.highlightedIndex =
276
+ this.highlightedIndex === null || this.highlightedIndex === 0
277
+ ? this.filteredSuggestions.length - 1
278
+ : this.highlightedIndex - 1;
279
+ event.preventDefault();
280
+ this.scrollHighlightedItemIntoView();
281
+ break;
282
+ case "Enter":
283
+ if (
284
+ this.highlightedIndex !== null &&
285
+ this.filteredSuggestions.length > 0
286
+ ) {
287
+ this.selectSuggestion(
288
+ this.filteredSuggestions[this.highlightedIndex]
289
+ );
290
+ } else {
291
+ this.isDropdownOpen = false;
292
+ }
293
+ event.preventDefault();
294
+ break;
295
+ case "Escape":
296
+ this.isDropdownOpen = false;
297
+ break;
298
+ default:
299
+ break;
300
+ }
301
+ }
302
+ onOptionMouseDown(): void {
303
+ this.preventClearOnBlur = true;
304
+ }
305
+ onMouseOver(index: number) {
306
+ this.highlightedIndex = index;
307
+ }
308
+
309
+ // actions
310
+ onAddNewItemClick(): void {
311
+ this.addNewItemClicked.emit();
312
+ }
313
+
314
+ // popper
315
+ private createPopperInstance() {
316
+ if (this.popperInstance) {
317
+ this.popperInstance.destroy();
318
+ }
319
+ if (this.inputElement && this.dropdownElement) {
320
+ this.popperInstance = createPopper(
321
+ this.inputElement.nativeElement,
322
+ this.dropdownElement.nativeElement,
323
+ {
324
+ placement: "bottom-start",
325
+ modifiers: [
326
+ {
327
+ name: "offset",
328
+ options: {
329
+ offset: [0, 1],
330
+ },
331
+ },
332
+ {
333
+ name: "flip",
334
+ options: {
335
+ fallbackPlacements: ["top-start", "bottom-start"],
336
+ },
337
+ },
338
+ ],
339
+ }
340
+ );
341
+ }
342
+ }
343
+ private scrollHighlightedItemIntoView() {
344
+ if (this.highlightedIndex !== null) {
345
+ const highlightedItem =
346
+ this.dropdownElement.nativeElement.querySelectorAll(".list-item")[
347
+ this.highlightedIndex
348
+ ];
349
+ if (highlightedItem) {
350
+ highlightedItem.scrollIntoView({ block: "nearest" });
351
+ }
352
+ }
353
+ }
354
+
355
+ // clear
356
+ resetInput() {
357
+ this.inputControl.setValue("");
358
+ this.selectionCleared.emit();
359
+ this.selectedItems = null;
360
+ this.valueBuffer = null;
361
+ }
362
+ removeItem(item: any) {
363
+ this.selectedItems = this.selectedItems.filter((i: any) => i !== item);
364
+ this.onChange(this.selectedItems);
365
+ this.optionSelected.emit(this.selectedItems);
366
+ }
367
+ }
@@ -0,0 +1,152 @@
1
+ <div class="form-group calendar" [ngClass]="customClass">
2
+ <label class="inp-label" [ngClass]="{'required' : required}" *ngIf="title">{{ title }}</label>
3
+ <div class="form-group calendar" #root (click)="openCalendar()" >
4
+ <input type="text" #inputEl [placeholder]="(inputPlaceholder && placeholder) ? placeholder : ''"
5
+ [formControl]="inputControl" class="form-control" [ngClass]="{ 'is-invalid': error }" (blur)="onBlur()"
6
+ (focus)="onFocus()" (click)="openCalendar(); $event.stopPropagation()" (keydown)="onInputKeydown($event)" />
7
+
8
+ <span class="focus-border" *ngIf="deFocus"></span>
9
+ <span class="calendar-icon">
10
+ <i class="he" [ngClass]="!timeOnly ? 'he-calendar-blank' : 'he-clock'"></i>
11
+ </span>
12
+ <label class="clear" *ngIf="!inputLoader && (selectedDate && !disabled) && clearVal" (click)="clearDate($event)">
13
+ <i class="he he-close"></i>
14
+ </label>
15
+ <label *ngIf="inputLoader" class="loader input-loader"></label>
16
+ <div *ngIf="error" class="val-msg">{{ errorMessage }}</div>
17
+
18
+ <div class="datepicker-group" #datePicker *ngIf="isOpen" (click)="$event.stopPropagation()">
19
+
20
+ <!-- time picker -->
21
+ <ng-container *ngIf="timeOnly">
22
+ <div class="time-picker">
23
+ <div class="time-select">
24
+ <button (click)="incrementHour()"><i class="he he-chevron-up"></i></button>
25
+ <ng-container *ngIf="hourFormat === '12'; else show24">
26
+ <div class="time-value">
27
+ {{ ((selectedHour % 12) || 12) | number:'2.0' }}
28
+ </div>
29
+ </ng-container>
30
+ <ng-template #show24>
31
+ <div class="time-value">
32
+ {{ selectedHour | number:'2.0' }}
33
+ </div>
34
+ </ng-template>
35
+
36
+ <button (click)="decrementHour()"><i class="he he-chevron-down"></i></button>
37
+ </div>
38
+ <span class="time-separator">:</span>
39
+ <div class="time-select">
40
+ <button (click)="incrementMinute()"><i class="he he-chevron-up"></i></button>
41
+ <div class="time-value">{{ selectedMinute | number:'2.0' }}</div>
42
+ <button (click)="decrementMinute()"><i class="he he-chevron-down"></i></button>
43
+ </div>
44
+ <div class="ampm-toggle" *ngIf="hourFormat === '12'">
45
+ <button type="button" [class.active]="meridian === 'AM'" (click)="setMeridian('AM')">AM</button>
46
+ <button type="button" [class.active]="meridian === 'PM'" (click)="setMeridian('PM')">PM</button>
47
+ </div>
48
+ </div>
49
+ </ng-container>
50
+
51
+ <!-- day view -->
52
+ <ng-container *ngIf="!timeOnly && currentView === 'day'">
53
+ <div class="header">
54
+ <button class="calendar-arrow" (click)="prevMonth()"><i class="he he-chevron-left"></i></button>
55
+ <div class="title" (click)="goToMonthView()">
56
+ <div>{{ displayMonthName }}</div>
57
+ <div>{{ displayYear }}</div>
58
+ </div>
59
+ <button class="calendar-arrow" (click)="nextMonth()"><i class="he he-chevron-right"></i></button>
60
+ </div>
61
+ <div class="week-header">
62
+ <div>Sun</div>
63
+ <div>Mon</div>
64
+ <div>Tue</div>
65
+ <div>Wed</div>
66
+ <div>Thu</div>
67
+ <div>Fri</div>
68
+ <div>Sat</div>
69
+ </div>
70
+ <div class="days-grid">
71
+ <div class="day-cell" *ngFor="let day of daysInMonth" (click)="selectDay(day)"
72
+ [class.disabled]="day !== null && dayClassMap[day].disabled"
73
+ [class.selected]="day !== null && dayClassMap[day].selected" [class.today]="
74
+ day !== null &&
75
+ displayYear === todayYear &&
76
+ displayMonth === todayMonth &&
77
+ day === todayDate
78
+ ">
79
+ {{day ? day : ''}}
80
+ </div>
81
+ </div>
82
+ <ng-container *ngIf="showTime">
83
+ <div class="time-picker">
84
+ <div class="time-select">
85
+ <button type="button" (click)="incrementHour()">
86
+ <i class="he he-chevron-up"></i>
87
+ </button>
88
+ <ng-container *ngIf="hourFormat === '12'; else show24">
89
+ <div class="time-value">
90
+ {{
91
+ selectedHour % 12 === 0
92
+ ? 12
93
+ : selectedHour % 12
94
+ | number:'2.0' }}
95
+ </div>
96
+ </ng-container>
97
+ <ng-template #show24>
98
+ <div class="time-value">{{ selectedHour | number:'2.0' }}</div>
99
+ </ng-template>
100
+ <button type="button" (click)="decrementHour()">
101
+ <i class="he he-chevron-down"></i>
102
+ </button>
103
+ </div>
104
+ <span class="time-separator">:</span>
105
+ <div class="time-select">
106
+ <button type="button" (click)="incrementMinute()">
107
+ <i class="he he-chevron-up"></i>
108
+ </button>
109
+ <div class="time-value">
110
+ {{ selectedMinute < 10 ? '0' +selectedMinute : selectedMinute }} </div>
111
+ <button type="button" (click)="decrementMinute()">
112
+ <i class="he he-chevron-down"></i>
113
+ </button>
114
+ </div>
115
+ <div class="ampm-toggle" *ngIf="hourFormat === '12'">
116
+ <button type="button" [class.active]="meridian === 'AM'" (click)="setMeridian('AM')">AM</button>
117
+ <button type="button" [class.active]="meridian === 'PM'" (click)="setMeridian('PM')">PM</button>
118
+ </div>
119
+ </div>
120
+ </ng-container>
121
+ </ng-container>
122
+
123
+ <!-- month view -->
124
+ <ng-container *ngIf="!timeOnly && currentView === 'month'">
125
+ <div class="header">
126
+ <button class="calendar-arrow" (click)="displayYear = displayYear - 1"><i
127
+ class="he he-chevron-left"></i></button>
128
+ <div class="title" (click)="goToYearRangeView()">{{ displayYear }}</div>
129
+ <button class="calendar-arrow" (click)="displayYear = displayYear + 1"><i
130
+ class="he he-chevron-right"></i></button>
131
+ </div>
132
+ <div class="month-grid">
133
+ <div class="month-cell" *ngFor="let m of months; index as i" (click)="selectMonth(i)">{{ m }}
134
+ </div>
135
+ </div>
136
+ </ng-container>
137
+
138
+ <!-- year range view -->
139
+ <ng-container *ngIf="!timeOnly && currentView === 'yearRange'">
140
+ <div class="header">
141
+ <button class="calendar-arrow" (click)="prevYearRange()"><i class="he he-chevron-left"></i></button>
142
+ <div class="title">{{ yearRange[0] }} ~ {{ yearRange[yearRangeSize-1] }}</div>
143
+ <button class="calendar-arrow" (click)="nextYearRange()"><i class="he he-chevron-right"></i></button>
144
+ </div>
145
+ <div class="year-grid">
146
+ <div class="year-cell" *ngFor="let y of yearRange" (click)="selectYear(y)">{{ y }}</div>
147
+ </div>
148
+ </ng-container>
149
+
150
+ </div>
151
+ </div>
152
+ </div>