UNPKG

@viren_chudasama/year-picker

Version:

A simple Bootstrap-based year picker for Angular

377 lines (366 loc) 17.7 kB
import * as i0 from '@angular/core'; import { Injectable, EventEmitter, HostListener, Input, Output, Component, NgModule } from '@angular/core'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; class YearPickerService { constructor() { } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: YearPickerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: YearPickerService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: YearPickerService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); class YearPickerComponent { elementRef; customClass = ''; minYear = 1900; maxYear = new Date().getFullYear(); selectedYear = null; yearSelected = new EventEmitter(); currentYearView = new Date().getFullYear(); yearRows = []; yearsPerPage = 9; isOpen = false; disabled = false; onDocumentClick(event) { if (this.isOpen && !this.elementRef.nativeElement.contains(event.target)) { this.isOpen = false; } } // ControlValueAccessor methods onChange = () => { }; onTouched = () => { }; constructor(elementRef) { this.elementRef = elementRef; } ngOnInit() { this.currentYearView = this.selectedYear || new Date().getFullYear(); this.clampCurrentYearView(); this.generateYearGrid(); } togglePicker() { if (!this.disabled) { this.isOpen = !this.isOpen; if (this.isOpen) { this.currentYearView = this.selectedYear || new Date().getFullYear(); this.clampCurrentYearView(); // Ensure currentYearView is valid this.generateYearGrid(); } } } generateYearGrid() { // Calculate the start year for the grid const startYear = Math.floor(this.currentYearView / this.yearsPerPage) * this.yearsPerPage; this.yearRows = []; let years = []; // Generate years within the minYear and maxYear boundaries for (let i = 0; i < this.yearsPerPage; i++) { const year = startYear + i; // Only include years within the valid range if (year >= this.minYear && year <= this.maxYear) { years.push(year); } // Group years into rows of 3 if (years.length === 3 || i === this.yearsPerPage - 1) { if (years.length > 0) { this.yearRows.push([...years]); years = []; } } } // If no years were added (e.g., startYear is way off), reset to a valid range if (this.yearRows.length === 0) { this.currentYearView = this.maxYear; this.generateYearGrid(); // Recursively call to regenerate with a valid currentYearView } } clampCurrentYearView() { if (this.currentYearView < this.minYear) { this.currentYearView = this.minYear; } else if (this.currentYearView > this.maxYear) { this.currentYearView = this.maxYear; } } selectYear(year) { this.selectedYear = year; this.onChange(year); this.onTouched(); this.yearSelected.emit(year); this.isOpen = false; } navigateToPreviousYears() { this.currentYearView -= this.yearsPerPage; this.clampCurrentYearView(); this.generateYearGrid(); } navigateToNextYears() { this.currentYearView = Number(this.currentYearView); this.currentYearView += this.yearsPerPage; this.clampCurrentYearView(); this.generateYearGrid(); } isYearDisabled(year) { return year < this.minYear || year > this.maxYear; } isYearSelected(year) { return this.selectedYear === year; } // ControlValueAccessor interface implementation writeValue(value) { this.selectedYear = value; if (value) { this.currentYearView = value; this.clampCurrentYearView(); this.generateYearGrid(); } } registerOnChange(fn) { this.onChange = fn; } registerOnTouched(fn) { this.onTouched = fn; } setDisabledState(isDisabled) { this.disabled = isDisabled; if (!isDisabled) { // When re-enabled, reset currentYearView to a valid value this.currentYearView = this.selectedYear || new Date().getFullYear(); this.clampCurrentYearView(); this.generateYearGrid(); } } // Keyboard navigation onKeyDown(event) { if (!this.isOpen) { if (event.key === "Enter" || event.key === " ") { this.togglePicker(); event.preventDefault(); } return; } switch (event.key) { case "Escape": this.isOpen = false; event.preventDefault(); break; case "ArrowLeft": this.handleArrowLeft(); event.preventDefault(); break; case "ArrowRight": this.handleArrowRight(); event.preventDefault(); break; case "ArrowUp": this.handleArrowUp(); event.preventDefault(); break; case "ArrowDown": this.handleArrowDown(); event.preventDefault(); break; case "Enter": case " ": if (this.currentYearView >= this.minYear && this.currentYearView <= this.maxYear) { this.selectYear(this.currentYearView); } event.preventDefault(); break; case "Home": this.currentYearView = this.minYear; this.generateYearGrid(); event.preventDefault(); break; case "End": this.currentYearView = this.maxYear; this.generateYearGrid(); event.preventDefault(); break; case "PageUp": this.navigateToPreviousYears(); event.preventDefault(); break; case "PageDown": this.navigateToNextYears(); event.preventDefault(); break; } } handleArrowLeft() { if (this.currentYearView > this.minYear) { this.currentYearView--; this.generateYearGrid(); } } handleArrowRight() { if (this.currentYearView < this.maxYear) { this.currentYearView++; this.generateYearGrid(); } } handleArrowUp() { if (this.currentYearView - 4 >= this.minYear) { this.currentYearView -= 4; this.generateYearGrid(); } } handleArrowDown() { if (this.currentYearView + 4 <= this.maxYear) { this.currentYearView += 4; this.generateYearGrid(); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: YearPickerComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: YearPickerComponent, isStandalone: true, selector: "lib-year-picker", inputs: { customClass: "customClass", minYear: "minYear", maxYear: "maxYear", selectedYear: "selectedYear", disabled: "disabled" }, outputs: { yearSelected: "yearSelected" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, ngImport: i0, template: ` <div class="year-picker-container position-relative" (keydown)="onKeyDown($event)"> <div class="input-group"> <input type="text" [value]="selectedYear || ''" readonly [disabled]="disabled" class="form-control rounded-pill rounded-end-0 custom-year-picker" placeholder="Select Year" (click)="togglePicker()" aria-haspopup="true" [attr.aria-expanded]="isOpen" tabindex="0" /> <span class="input-group-text rounded-pill rounded-start-0 text-secondary" [class.disabled]="disabled" (click)="disabled? null:togglePicker()"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16"> <path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z" /> </svg> </span> </div> <div class="dropdown-menu w-100 p-0 shadow" [class.show]="isOpen" style="min-width: 16rem;"> <div class="d-flex align-items-center justify-content-between p-2 border-bottom"> <button type="button" class="btn btn-sm btn-link text-decoration-none" (click)="navigateToPreviousYears()" [disabled]="minYear >= yearRows[0]?.[0]" aria-label="Previous years"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-left" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z" /> </svg> </button> <span class="fw-bold"> {{ (yearRows[0]?.[0] >= minYear && yearRows[0]?.[0] <= maxYear) ? yearRows[0]?.[0] : minYear }} - {{ (yearRows[yearRows.length - 1]?.[yearRows[yearRows.length - 1].length - 1] <= maxYear && yearRows[yearRows.length - 1]?.[yearRows[yearRows.length - 1].length - 1] >= minYear) ? yearRows[yearRows.length - 1]?.[yearRows[yearRows.length - 1].length - 1] : maxYear }} </span> <button type="button" class="btn btn-sm btn-link text-decoration-none" (click)="navigateToNextYears()" [disabled]="maxYear <= yearRows[yearRows.length - 1]?.[yearRows[yearRows.length - 1].length - 1]" aria-label="Next years"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-right" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" /> </svg> </button> </div> <div class="p-2"> <div class="row g-1" *ngFor="let row of yearRows"> <div class="col-4" *ngFor="let year of row"> <button type="button" class="btn btn-sm w-100" [class.btn-primary]="isYearSelected(year)" [class.btn-outline-secondary]="!isYearSelected(year) && !isYearDisabled(year)" [class.disabled]="isYearDisabled(year)" [disabled]="isYearDisabled(year)" (click)="selectYear(year)" tabindex="0"> {{ year }} </button> </div> </div> </div> </div> </div> `, isInline: true, styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: YearPickerComponent, decorators: [{ type: Component, args: [{ selector: 'lib-year-picker', standalone: true, imports: [CommonModule], template: ` <div class="year-picker-container position-relative" (keydown)="onKeyDown($event)"> <div class="input-group"> <input type="text" [value]="selectedYear || ''" readonly [disabled]="disabled" class="form-control rounded-pill rounded-end-0 custom-year-picker" placeholder="Select Year" (click)="togglePicker()" aria-haspopup="true" [attr.aria-expanded]="isOpen" tabindex="0" /> <span class="input-group-text rounded-pill rounded-start-0 text-secondary" [class.disabled]="disabled" (click)="disabled? null:togglePicker()"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16"> <path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z" /> </svg> </span> </div> <div class="dropdown-menu w-100 p-0 shadow" [class.show]="isOpen" style="min-width: 16rem;"> <div class="d-flex align-items-center justify-content-between p-2 border-bottom"> <button type="button" class="btn btn-sm btn-link text-decoration-none" (click)="navigateToPreviousYears()" [disabled]="minYear >= yearRows[0]?.[0]" aria-label="Previous years"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-left" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z" /> </svg> </button> <span class="fw-bold"> {{ (yearRows[0]?.[0] >= minYear && yearRows[0]?.[0] <= maxYear) ? yearRows[0]?.[0] : minYear }} - {{ (yearRows[yearRows.length - 1]?.[yearRows[yearRows.length - 1].length - 1] <= maxYear && yearRows[yearRows.length - 1]?.[yearRows[yearRows.length - 1].length - 1] >= minYear) ? yearRows[yearRows.length - 1]?.[yearRows[yearRows.length - 1].length - 1] : maxYear }} </span> <button type="button" class="btn btn-sm btn-link text-decoration-none" (click)="navigateToNextYears()" [disabled]="maxYear <= yearRows[yearRows.length - 1]?.[yearRows[yearRows.length - 1].length - 1]" aria-label="Next years"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-right" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" /> </svg> </button> </div> <div class="p-2"> <div class="row g-1" *ngFor="let row of yearRows"> <div class="col-4" *ngFor="let year of row"> <button type="button" class="btn btn-sm w-100" [class.btn-primary]="isYearSelected(year)" [class.btn-outline-secondary]="!isYearSelected(year) && !isYearDisabled(year)" [class.disabled]="isYearDisabled(year)" [disabled]="isYearDisabled(year)" (click)="selectYear(year)" tabindex="0"> {{ year }} </button> </div> </div> </div> </div> </div> ` }] }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { customClass: [{ type: Input }], minYear: [{ type: Input }], maxYear: [{ type: Input }], selectedYear: [{ type: Input }], yearSelected: [{ type: Output }], disabled: [{ type: Input }], onDocumentClick: [{ type: HostListener, args: ['document:click', ['$event']] }] } }); class YearPickerModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: YearPickerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: YearPickerModule, imports: [CommonModule, YearPickerComponent], exports: [YearPickerComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: YearPickerModule, imports: [CommonModule, YearPickerComponent] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: YearPickerModule, decorators: [{ type: NgModule, args: [{ imports: [CommonModule, YearPickerComponent], exports: [YearPickerComponent] }] }] }); /* * Public API Surface of year-picker */ /** * Generated bundle index. Do not edit. */ export { YearPickerComponent, YearPickerModule, YearPickerService }; //# sourceMappingURL=year-picker.mjs.map