@qeydar/datepicker
Version:
A comprehensive Date and Time Picker for Angular with Jalali calendar support
932 lines (916 loc) • 108 kB
JavaScript
/**
* Time Picker Component
* A customizable time picker that supports 12/24 hour formats, seconds, and multiple locales.
*
* Features:
* - 12/24 hour format
* - Optional seconds
* - Localization support
* - String or Date value types
* - Min/Max time validation
* - Custom styling
*/
import { Component, forwardRef, Input, Output, EventEmitter, ViewChild, HostListener, ChangeDetectionStrategy } from '@angular/core';
import { NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { CdkOverlayOrigin, OverlayModule } from '@angular/cdk/overlay';
import { slideMotion } from '../utils/animation/slide';
import { QeydarDatePickerService } from '../date-picker.service';
import { DEFAULT_DATE_PICKER_POSITIONS, NzConnectedOverlayDirective } from "../utils/overlay/overlay";
import { NgFor, NgIf, NgTemplateOutlet } from '@angular/common';
import { DateMaskDirective } from '../utils/input-mask.directive';
import * as i0 from "@angular/core";
import * as i1 from "@angular/forms";
import * as i2 from "../date-picker.service";
import * as i3 from "../date-adapter";
import * as i4 from "@angular/cdk/overlay";
export class TimePickerComponent {
constructor(fb, elementRef, cdref, datePickerService, jalaliAdapter, gregorianAdapter) {
this.fb = fb;
this.elementRef = elementRef;
this.cdref = cdref;
this.datePickerService = datePickerService;
this.jalaliAdapter = jalaliAdapter;
this.gregorianAdapter = gregorianAdapter;
this.rtl = false;
this.placement = 'right';
this.valueType = 'string';
this.cssClass = '';
this.showIcon = true;
this.inline = false;
this.disableInputMask = false;
this.disabled = false;
this.allowEmpty = true;
this.readOnly = false;
this.readOnlyInput = false;
this.timeChange = new EventEmitter();
this.openChange = new EventEmitter();
this.timeFormat = '12';
this._displayFormat = 'hh:mm a';
this._value = null;
this._selectedDate = new Date();
this.onChange = () => { };
this.onTouched = () => { };
this.timeoutId = null;
this.showSeconds = false;
this.hours = [];
this.minutes = Array.from({ length: 60 }, (_, i) => i);
this.seconds = Array.from({ length: 60 }, (_, i) => i);
this.periods = [];
this.selectedTime = {
hour: 12,
minute: 0,
second: 0,
period: ''
};
this.isOpen = false;
this.overlayPositions = [...DEFAULT_DATE_PICKER_POSITIONS];
this.handleDocumentClick = (event) => {
if (!this.elementRef.nativeElement.contains(event.target) && this.isOpen) {
this.close();
this.handleTimeInput();
}
};
this.dateAdapter = this.gregorianAdapter;
this.initializeForm();
this.initializeLocale();
}
set displayFormat(value) {
this._displayFormat = value;
this.showSeconds = value.toLowerCase().includes('s');
// Infer time format from display format
this.timeFormat = this.getTimeFormatFromDisplayFormat(value);
this.updateHourRange();
this.updateTimeDisplay();
}
get displayFormat() {
return this._displayFormat;
}
set selectedDate(date) {
if (date) {
this._selectedDate = date;
}
}
get selectedDate() {
return this._selectedDate;
}
// Lifecycle hooks
ngOnInit() {
this.updateHourRange();
this.origin = new CdkOverlayOrigin(this.elementRef);
this.setupInputSubscription();
this.value = this.selectedDate;
// Only add document click listener for non-inline mode
if (!this.inline) {
document.addEventListener('click', this.handleDocumentClick);
}
// Auto-open for inline mode
if (this.inline) {
this.isOpen = true;
this.scrollToTime();
}
}
ngOnDestroy() {
this.cleanupTimeouts();
document.removeEventListener('click', this.handleDocumentClick);
}
ngOnChanges(changes) {
if (changes['rtl'] || changes['lang']) {
this.updateLocale();
}
if (changes['rtl'] && !changes['dateAdapter']) {
this.dateAdapter = this.rtl ? this.jalaliAdapter : this.gregorianAdapter;
}
}
// Initialization methods
initializeForm() {
this.form = this.fb.group({
timeInput: ['']
});
}
initializeLocale() {
this.lang = this.datePickerService.locale_en;
this.selectedTime.period = this.lang.am;
this.periods = [this.lang.am, this.lang.pm];
}
updateLocale() {
this.lang = this.rtl ? this.datePickerService.locale_fa : this.datePickerService.locale_en;
this.selectedTime.period = this.lang.am;
this.periods = [this.lang.am, this.lang.pm];
this.placeholder = this.lang.selectTime;
}
setupInputSubscription() {
this.form.get('timeInput')?.valueChanges.subscribe(value => {
if (!value)
return;
if (!this.isOpen) {
this.validateAndUpdateTime(value);
}
else {
this.parseTimeString(value);
this.scrollToTime();
}
});
}
// Time management
updateHourRange() {
const format = this.getTimeFormatFromDisplayFormat(this._displayFormat);
this.hours = format === '12'
? Array.from({ length: 12 }, (_, i) => i + 1)
: Array.from({ length: 24 }, (_, i) => i);
}
formatTime(date) {
if (!date && !this.dateAdapter)
return '';
const currentDate = date || this.updateDateFromSelection();
return this.dateAdapter.format(currentDate, this._displayFormat);
}
parseTimeString(value) {
if (!this.dateAdapter)
return;
const date = value instanceof Date ? value : this.dateAdapter.parse(value, this._displayFormat);
if (!date)
return;
const hours = this.dateAdapter.getHours(date);
const minutes = this.dateAdapter.getMinutes(date);
const seconds = this.dateAdapter.getSeconds(date);
if (hours === null || minutes === null || seconds === null)
return;
this.selectedTime = {
hour: hours,
minute: minutes,
second: seconds,
period: hours >= 12 ? this.lang.pm : this.lang.am
};
this.cdref.markForCheck();
}
// Value accessors and form control
get value() {
return this._value;
}
set value(val) {
this._value = val;
this.updateFromValue(val);
}
updateFromValue(value) {
if (!value) {
this.resetSelection();
return;
}
if (value instanceof Date) {
this.updateFromDate(value);
}
else {
this.parseTimeString(value);
}
}
updateFromDate(date) {
if (date && !isNaN(date.getTime()) && this.dateAdapter) {
const hours = this.dateAdapter.getHours(date);
if (hours === null)
return;
this.selectedTime = {
hour: hours,
minute: this.dateAdapter.getMinutes(date) ?? 0,
second: this.dateAdapter.getSeconds(date) ?? 0,
period: hours >= 12 ? this.lang.pm : this.lang.am
};
}
else {
this.resetSelection();
}
this.cdref.markForCheck();
}
resetSelection() {
this.selectedTime = {
hour: 0,
minute: 0,
second: 0,
period: this.lang.am
};
this.cdref.markForCheck();
}
writeValue(value) {
if (!value) {
this.value = null;
return;
}
if (value instanceof Date) {
this.value = value;
}
else if (value.trim()) {
const date = this.selectedDate;
this.value = !isNaN(date.getTime()) && this.valueType === 'date' ? date : value;
this.parseTimeString(value);
}
this.updateTimeDisplay();
this.save(false);
}
registerOnChange(fn) {
this.onChange = fn;
}
registerOnTouched(fn) {
this.onTouched = fn;
}
// UI Event handlers
handleKeydown(event) {
if (event.key === 'Tab' || event.key === 'Enter') {
this.handleTimeInput();
if (event.key === 'Tab')
this.close();
}
else if (event.key === 'Escape') {
this.close();
}
}
handleTimeInput() {
const currentValue = this.form.get('timeInput')?.value;
if (currentValue || (!currentValue && !this.allowEmpty)) {
this.validateAndUpdateTime(currentValue);
}
}
onFocusInput() {
if (!this.isOpen) {
this.open();
}
}
toggleTimePicker(event) {
event.stopPropagation();
this.isOpen ? this.close() : this.open();
}
// Picker operations
open() {
if (this.inline || this.disabled || this.readOnly)
return;
const wasOpen = this.isOpen;
this.isOpen = true;
this.openChange.emit(true);
this.scrollToTime();
if (!wasOpen) {
this.cdref.markForCheck();
}
}
close() {
if (this.inline)
return;
this.cleanupTimeouts();
if (this.isOpen) {
this.isOpen = false;
this.openChange.emit(false);
this.cdref.markForCheck();
}
}
// Selection methods
selectHour(hour) {
if (!this.isHourDisabled(hour)) {
this.selectedTime.hour = hour;
this.updateTimeDisplay();
this.scrollToSelectedItem(`h${hour}`);
if (this.inline)
this.save();
}
}
selectMinute(minute) {
if (!this.isMinuteDisabled(minute)) {
this.selectedTime.minute = minute;
this.updateTimeDisplay();
this.scrollToSelectedItem(`m${minute}`);
if (this.inline)
this.save();
}
}
selectSecond(second) {
if (!this.isSecondDisabled(second)) {
this.selectedTime.second = second;
this.updateTimeDisplay();
this.scrollToSelectedItem(`s${second}`);
if (this.inline)
this.save();
}
}
selectPeriod(period) {
this.selectedTime.period = period;
this.updateTimeDisplay();
}
selectNow() {
const now = this.selectedDate;
this.selectedTime = {
hour: now.getHours(),
minute: now.getMinutes(),
second: now.getSeconds(),
period: now.getHours() >= 12 ? this.lang.pm : this.lang.am
};
this.updateTimeDisplay();
this.scrollToTime();
this.save();
}
save(close = true) {
const date = this.updateDateFromSelection();
const { isValid, normalizedDate } = this.validateAndNormalizeTime(date);
if (!isValid || !normalizedDate)
return;
const outputValue = this.valueType === 'date'
? normalizedDate
: this.formatTime(normalizedDate);
const valueChanged = JSON.stringify(this._value) !== JSON.stringify(outputValue);
if (valueChanged) {
this._value = outputValue;
this.form.get('timeInput')?.setValue(this.formatTime(normalizedDate), { emitEvent: false });
this.onChange(outputValue);
this.timeChange.emit(outputValue);
this.cdref.markForCheck();
}
if (close && !this.inline) {
this.close();
}
}
// Validation methods
validateAndUpdateTime(value) {
if (!value || !this.dateAdapter) {
this.updateTimeDisplay();
return;
}
try {
const parsedDate = this.dateAdapter.parse(value, this._displayFormat);
if (!parsedDate) {
this.updateTimeDisplay();
return;
}
const { isValid, normalizedDate } = this.validateAndNormalizeTime(parsedDate);
const formattedTime = this.dateAdapter.format(normalizedDate, this._displayFormat);
this.form.get('timeInput')?.setValue(formattedTime, { emitEvent: false });
this.parseTimeString(normalizedDate);
const outputValue = this.valueType === 'date' ? normalizedDate : formattedTime;
this._value = outputValue;
this.onChange(outputValue);
this.timeChange.emit(outputValue);
}
catch (error) {
console.error('Error normalizing time:', error);
this.updateTimeDisplay();
}
}
isHourDisabled(hour) {
if (!this.dateAdapter)
return false;
return this.isFullHourDisabled(hour);
}
isMinuteDisabled(minute) {
if (!this.dateAdapter)
return false;
return this.isFullMinuteDisabled(minute);
}
isSecondDisabled(second) {
if (!this.dateAdapter)
return false;
const testConfig = { ...this.selectedTime, second };
const testDate = this.createDateWithTime(testConfig);
return this.isTimeDisabled(testDate);
}
isTimeDisabled(testDate) {
if (!this.dateAdapter)
return false;
if (this.minTime) {
const minDate = this.dateAdapter.parse(this.minTime, this._displayFormat);
if (minDate && this.dateAdapter.isBefore(testDate, minDate)) {
return true;
}
}
if (this.maxTime) {
const maxDate = this.dateAdapter.parse(this.maxTime, this._displayFormat);
if (maxDate && this.dateAdapter.isAfter(testDate, maxDate)) {
return true;
}
}
return this.disabledTimesFilter ? this.disabledTimesFilter(testDate) : false;
}
validateAndNormalizeTime(date) {
if (!this.dateAdapter) {
return { isValid: false, normalizedDate: null };
}
let isValid = true;
// Clone the date to avoid modifying the original
let normalizedDate = this.dateAdapter.clone(date);
if (this.isTimeDisabled(normalizedDate)) {
isValid = false;
// Try to find nearest valid time (check next and previous 48 intervals of 30 minutes)
for (let i = 1; i <= 48; i++) {
const nextTime = this.dateAdapter.addMinutes(date, i * 30);
const prevTime = this.dateAdapter.addMinutes(date, -i * 30);
if (!this.isTimeDisabled(nextTime)) {
normalizedDate = nextTime;
break;
}
if (!this.isTimeDisabled(prevTime)) {
normalizedDate = prevTime;
break;
}
}
// If still disabled after trying to find valid time
if (this.isTimeDisabled(normalizedDate)) {
return { isValid: false, normalizedDate: null };
}
}
return { isValid: isValid, normalizedDate };
}
isFullHourDisabled(hour) {
for (let minute = 0; minute < 60; minute++) {
const testConfig = {
...this.selectedTime,
hour,
minute,
second: 0
};
const testDate = this.createDateWithTime(testConfig);
if (!this.isTimeDisabled(testDate)) {
return false; // If any minute is enabled, hour is not fully disabled
}
}
return true; // All minutes in hour are disabled
}
isFullMinuteDisabled(minute) {
if (!this.showSeconds) {
const testConfig = {
...this.selectedTime,
minute,
second: 0
};
const testDate = this.createDateWithTime(testConfig);
return this.isTimeDisabled(testDate);
}
// If showing seconds, check each second
for (let second = 0; second < 60; second++) {
const testConfig = {
...this.selectedTime,
minute,
second
};
const testDate = this.createDateWithTime(testConfig);
if (!this.isTimeDisabled(testDate)) {
return false; // If any second is enabled, minute is not fully disabled
}
}
return true; // All seconds in minute are disabled
}
// Helper methods
createDateWithTime(config) {
if (!this.dateAdapter)
return this.selectedDate;
let testHour = config.hour;
if (this.timeFormat === '12') {
if (config.period === this.lang.pm && testHour < 12)
testHour += 12;
if (config.period === this.lang.am && testHour === 12)
testHour = 0;
}
let date = this.selectedDate;
date = this.dateAdapter.setHours(date, testHour);
date = this.dateAdapter.setMinutes(date, config.minute);
date = this.dateAdapter.setSeconds(date, config.second);
return date;
}
updateDateFromSelection() {
if (!this.dateAdapter)
return this.selectedDate;
let hours = this.selectedTime.hour;
if (this.timeFormat === '12') {
if (this.selectedTime.period === this.lang.pm && hours < 12)
hours += 12;
if (this.selectedTime.period === this.lang.am && hours === 12)
hours = 0;
}
let date = this._value instanceof Date ?
this.dateAdapter.clone(this._value) :
this.selectedDate;
date = this.dateAdapter.setHours(date, hours);
date = this.dateAdapter.setMinutes(date, this.selectedTime.minute);
date = this.dateAdapter.setSeconds(date, this.selectedTime.second);
return date;
}
updateTimeDisplay() {
if (this.form) {
this.form.get('timeInput')?.setValue(this.formatTime(), { emitEvent: false });
}
}
getTimeFormatFromDisplayFormat(format) {
// Check for 24-hour format indicators
const has24HourFormat = /\bH{1,2}\b/.test(format);
return has24HourFormat ? '24' : '12';
}
// UI Update methods
async scrollToTime() {
await this.scrollToSelectedItem(`h${this.selectedTime.hour}`, 'auto'),
await this.scrollToSelectedItem(`m${this.selectedTime.minute}`, 'auto'),
this.showSeconds ? await this.scrollToSelectedItem(`s${this.selectedTime.second}`, 'auto') : '';
}
scrollToSelectedItem(id, behavior = 'smooth') {
this.cleanupTimeouts();
return new Promise((resolve) => {
if (!id) {
resolve(false);
return;
}
this.timeoutId = window.setTimeout(() => {
const selectedElement = this.popupWrapper?.nativeElement.querySelector(`#selector_${id}`);
if (selectedElement) {
selectedElement.scrollIntoView({ behavior, block: 'center' });
}
resolve(true);
}, 0);
});
}
cleanupTimeouts() {
if (this.timeoutId !== null) {
window.clearTimeout(this.timeoutId);
this.timeoutId = null;
}
}
onPositionChange(position) {
this.cdref.detectChanges();
}
}
TimePickerComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TimePickerComponent, deps: [{ token: i1.FormBuilder }, { token: i0.ElementRef }, { token: i0.ChangeDetectorRef }, { token: i2.QeydarDatePickerService }, { token: i3.JalaliDateAdapter }, { token: i3.GregorianDateAdapter }], target: i0.ɵɵFactoryTarget.Component });
TimePickerComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: TimePickerComponent, isStandalone: true, selector: "qeydar-time-picker", inputs: { placeholder: "placeholder", rtl: "rtl", placement: "placement", minTime: "minTime", maxTime: "maxTime", lang: "lang", valueType: "valueType", cssClass: "cssClass", showIcon: "showIcon", dateAdapter: "dateAdapter", inline: "inline", disableInputMask: "disableInputMask", disabled: "disabled", disabledTimesFilter: "disabledTimesFilter", allowEmpty: "allowEmpty", readOnly: "readOnly", readOnlyInput: "readOnlyInput", displayFormat: "displayFormat", selectedDate: "selectedDate" }, outputs: { timeChange: "timeChange", openChange: "openChange" }, host: { listeners: { "click": "open()", "keydown": "handleKeydown($event)" } }, providers: [
QeydarDatePickerService,
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TimePickerComponent),
multi: true
}
], viewQueries: [{ propertyName: "timePickerInput", first: true, predicate: ["timePickerInput"], descendants: true }, { propertyName: "popupWrapper", first: true, predicate: ["popupWrapper"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
<div class="time-picker-wrapper" [formGroup]="form">
<!-- Regular input mode -->
<ng-container *ngIf="!inline">
<div class="input-wrapper" [class.focus]="isOpen" [class.disabled]="disabled">
<input
#timePickerInput
[qeydar-dateMask]="displayFormat"
[disableInputMask]="disableInputMask"
[class.disabled]="disabled"
type="text"
class="time-picker-input"
[class.focus]="isOpen"
formControlName="timeInput"
(focus)="onFocusInput()"
[placeholder]="placeholder"
[readonly]="readOnly || readOnlyInput"
[attr.disabled]="disabled? 'disabled':null"
>
<button *ngIf="showIcon" class="time-button" (click)="toggleTimePicker($event)" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<path d="M12 6v6l4 2"/>
</svg>
</button>
</div>
<ng-template
cdkConnectedOverlay
nzConnectedOverlay
[cdkConnectedOverlayOrigin]="origin"
[cdkConnectedOverlayOpen]="isOpen"
[cdkConnectedOverlayPositions]="overlayPositions"
[cdkConnectedOverlayTransformOriginOn]="'.time-picker-popup'"
[cdkConnectedOverlayHasBackdrop]="false"
(positionChange)="onPositionChange($event)"
(detach)="close()"
>
<ng-container *ngTemplateOutlet="timePickerContent"></ng-container>
</ng-template>
</ng-container>
<!-- Inline mode -->
<ng-container *ngIf="inline">
<ng-container *ngTemplateOutlet="timePickerContent"></ng-container>
</ng-container>
<!-- Time Picker Content Template -->
<ng-template #timePickerContent>
<div
#popupWrapper
[class]="'time-picker-popup ' + cssClass"
[@slideMotion]="'enter'"
[class.inline]="inline"
[class.disabled]="disabled"
style="position: relative"
(click)="$event.stopPropagation()"
>
<div class="time-picker-content">
<div class="time-columns">
<!-- Hours -->
<div class="time-column">
<div class="time-scroller">
<button
*ngFor="let hour of hours"
[id]="'selector_h'+hour"
[class.selected]="selectedTime.hour === hour"
[class.disabled]="isHourDisabled(hour)"
(click)="selectHour(hour)"
type="button"
>
{{ hour.toString().padStart(2, '0') }}
</button>
</div>
</div>
<div class="time-separator">:</div>
<!-- Minutes -->
<div class="time-column">
<div class="time-scroller">
<button
*ngFor="let minute of minutes"
[id]="'selector_m'+minute"
[class.selected]="selectedTime.minute === minute"
[class.disabled]="isMinuteDisabled(minute)"
(click)="selectMinute(minute)"
type="button"
>
{{ minute.toString().padStart(2, '0') }}
</button>
</div>
</div>
<!-- Seconds (if format includes seconds) -->
<ng-container *ngIf="showSeconds">
<div class="time-separator">:</div>
<div class="time-column">
<div class="time-scroller">
<button
*ngFor="let second of seconds"
[id]="'selector_s'+second"
[class.selected]="selectedTime.second === second"
[class.disabled]="isSecondDisabled(second)"
(click)="selectSecond(second)"
type="button"
>
{{ second.toString().padStart(2, '0') }}
</button>
</div>
</div>
</ng-container>
<!-- AM/PM (only in 12-hour format) -->
<ng-container *ngIf="timeFormat === '12'">
<div class="time-column period">
<button
*ngFor="let period of periods"
[class.selected]="selectedTime.period === period"
(click)="selectPeriod(period)"
type="button"
>
{{ period }}
</button>
</div>
</ng-container>
</div>
</div>
<div class="time-picker-footer" *ngIf="!inline">
<div class="footer-buttons">
<button class="now-btn" (click)="selectNow()" type="button">{{ lang.now }}</button>
</div>
<div class="footer-actions">
<button class="save-btn" (click)="save()" type="button">{{ lang.ok }}</button>
</div>
</div>
</div>
</ng-template>
</div>
`, isInline: true, styles: [":host *{font-family:inherit;font-weight:400;box-sizing:border-box;padding:0;margin:0}.time-picker-wrapper{display:inline-block}.input-wrapper{position:relative;display:inline-flex;align-items:center;border:1px solid #d9d9d9;border-radius:4px}.input-wrapper.focus{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;outline:none}.input-wrapper.disabled{background:#fafafa}input:focus{outline:none}input:hover{border-color:#40a9ff}input.time-picker-input{font-family:inherit;width:100%;padding:6px;border:none;border-radius:4px;font-size:14px;transition:all .3s}.time-button{background:none;border:none;cursor:pointer;padding:4px 4px 0}.time-picker-popup{background:white;border-radius:8px;box-shadow:0 6px 16px #00000014;width:fit-content;min-width:200px;overflow:hidden;margin-block:3px;height:40vh;max-height:280px}.time-picker-popup.inline{box-shadow:none;margin:0;border:1px solid #d9d9d9}.time-picker-popup.inline .time-picker-content{padding:4px;height:100%}.time-picker-popup.inline .time-columns{padding:4px}.time-picker-popup.inline .time-column::-webkit-scrollbar{width:6px}.time-picker-popup.inline .time-column::-webkit-scrollbar-thumb{background:#d9d9d9;border-radius:3px}.time-picker-popup.inline .time-column::-webkit-scrollbar-track{background:#f5f5f5}.time-picker-header{padding:16px;font-size:16px;font-weight:500;border-bottom:1px solid #f0f0f0}.time-picker-content{padding:8px;height:calc(100% - 59px)}.time-columns{display:flex;align-items:center;justify-content:center;padding:8px;gap:8px;height:100%}.time-column{flex:1;height:100%;overflow-y:auto;scrollbar-width:none}.time-column::-webkit-scrollbar{display:none}.time-column button{width:100%;padding:6px 8px;background:none;border:none;cursor:pointer;color:#666;font-size:14px;border-radius:4px}.time-column button:hover:not(.disabled){background:#f5f5f5}.time-column button.selected{background:#e6f4ff;color:#1890ff}.time-column button.disabled{color:#d9d9d9;cursor:not-allowed}.time-scroller{display:flex;flex-direction:column;align-items:center}.time-separator{padding:8px 0;color:#999;font-weight:700}.time-picker-footer{display:flex;justify-content:space-between;padding:8px;border-top:1px solid #f0f0f0}button{padding:4px 15px;border-radius:4px;border:1px solid #d9d9d9;background:white;cursor:pointer;font-size:14px;font-family:inherit}.save-btn{background:#1890ff;border-color:#1890ff;color:#fff}.save-btn:hover{background:#40a9ff;border-color:#40a9ff}.cancel-btn:hover{border-color:#40a9ff;color:#40a9ff}.footer-buttons,.footer-actions{display:flex;gap:8px}.now-btn{color:#1890ff;border-color:transparent;background:transparent;box-shadow:none;padding-left:0}.now-btn:hover{color:#40a9ff}.embedded-time-picker.time-picker-popup .time-picker-content{height:100%}.embedded-time-picker.time-picker-popup.inline{border:none;border-radius:0;height:286px;max-height:290px;direction:ltr}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: DateMaskDirective, selector: "[qeydar-dateMask]", inputs: ["qeydar-dateMask", "disableInputMask"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i4.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: NzConnectedOverlayDirective, selector: "[cdkConnectedOverlay][nzConnectedOverlay]", inputs: ["nzArrowPointAtCenter"], exportAs: ["nzConnectedOverlay"] }], animations: [slideMotion], changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TimePickerComponent, decorators: [{
type: Component,
args: [{ selector: 'qeydar-time-picker', changeDetection: ChangeDetectionStrategy.OnPush, template: `
<div class="time-picker-wrapper" [formGroup]="form">
<!-- Regular input mode -->
<ng-container *ngIf="!inline">
<div class="input-wrapper" [class.focus]="isOpen" [class.disabled]="disabled">
<input
#timePickerInput
[qeydar-dateMask]="displayFormat"
[disableInputMask]="disableInputMask"
[class.disabled]="disabled"
type="text"
class="time-picker-input"
[class.focus]="isOpen"
formControlName="timeInput"
(focus)="onFocusInput()"
[placeholder]="placeholder"
[readonly]="readOnly || readOnlyInput"
[attr.disabled]="disabled? 'disabled':null"
>
<button *ngIf="showIcon" class="time-button" (click)="toggleTimePicker($event)" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<path d="M12 6v6l4 2"/>
</svg>
</button>
</div>
<ng-template
cdkConnectedOverlay
nzConnectedOverlay
[cdkConnectedOverlayOrigin]="origin"
[cdkConnectedOverlayOpen]="isOpen"
[cdkConnectedOverlayPositions]="overlayPositions"
[cdkConnectedOverlayTransformOriginOn]="'.time-picker-popup'"
[cdkConnectedOverlayHasBackdrop]="false"
(positionChange)="onPositionChange($event)"
(detach)="close()"
>
<ng-container *ngTemplateOutlet="timePickerContent"></ng-container>
</ng-template>
</ng-container>
<!-- Inline mode -->
<ng-container *ngIf="inline">
<ng-container *ngTemplateOutlet="timePickerContent"></ng-container>
</ng-container>
<!-- Time Picker Content Template -->
<ng-template #timePickerContent>
<div
#popupWrapper
[class]="'time-picker-popup ' + cssClass"
[@slideMotion]="'enter'"
[class.inline]="inline"
[class.disabled]="disabled"
style="position: relative"
(click)="$event.stopPropagation()"
>
<div class="time-picker-content">
<div class="time-columns">
<!-- Hours -->
<div class="time-column">
<div class="time-scroller">
<button
*ngFor="let hour of hours"
[id]="'selector_h'+hour"
[class.selected]="selectedTime.hour === hour"
[class.disabled]="isHourDisabled(hour)"
(click)="selectHour(hour)"
type="button"
>
{{ hour.toString().padStart(2, '0') }}
</button>
</div>
</div>
<div class="time-separator">:</div>
<!-- Minutes -->
<div class="time-column">
<div class="time-scroller">
<button
*ngFor="let minute of minutes"
[id]="'selector_m'+minute"
[class.selected]="selectedTime.minute === minute"
[class.disabled]="isMinuteDisabled(minute)"
(click)="selectMinute(minute)"
type="button"
>
{{ minute.toString().padStart(2, '0') }}
</button>
</div>
</div>
<!-- Seconds (if format includes seconds) -->
<ng-container *ngIf="showSeconds">
<div class="time-separator">:</div>
<div class="time-column">
<div class="time-scroller">
<button
*ngFor="let second of seconds"
[id]="'selector_s'+second"
[class.selected]="selectedTime.second === second"
[class.disabled]="isSecondDisabled(second)"
(click)="selectSecond(second)"
type="button"
>
{{ second.toString().padStart(2, '0') }}
</button>
</div>
</div>
</ng-container>
<!-- AM/PM (only in 12-hour format) -->
<ng-container *ngIf="timeFormat === '12'">
<div class="time-column period">
<button
*ngFor="let period of periods"
[class.selected]="selectedTime.period === period"
(click)="selectPeriod(period)"
type="button"
>
{{ period }}
</button>
</div>
</ng-container>
</div>
</div>
<div class="time-picker-footer" *ngIf="!inline">
<div class="footer-buttons">
<button class="now-btn" (click)="selectNow()" type="button">{{ lang.now }}</button>
</div>
<div class="footer-actions">
<button class="save-btn" (click)="save()" type="button">{{ lang.ok }}</button>
</div>
</div>
</div>
</ng-template>
</div>
`, standalone: true, imports: [
NgIf,
NgFor,
ReactiveFormsModule,
NgTemplateOutlet,
DateMaskDirective,
OverlayModule,
NzConnectedOverlayDirective
], providers: [
QeydarDatePickerService,
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TimePickerComponent),
multi: true
}
], host: {
'(click)': 'open()'
}, animations: [slideMotion], styles: [":host *{font-family:inherit;font-weight:400;box-sizing:border-box;padding:0;margin:0}.time-picker-wrapper{display:inline-block}.input-wrapper{position:relative;display:inline-flex;align-items:center;border:1px solid #d9d9d9;border-radius:4px}.input-wrapper.focus{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;outline:none}.input-wrapper.disabled{background:#fafafa}input:focus{outline:none}input:hover{border-color:#40a9ff}input.time-picker-input{font-family:inherit;width:100%;padding:6px;border:none;border-radius:4px;font-size:14px;transition:all .3s}.time-button{background:none;border:none;cursor:pointer;padding:4px 4px 0}.time-picker-popup{background:white;border-radius:8px;box-shadow:0 6px 16px #00000014;width:fit-content;min-width:200px;overflow:hidden;margin-block:3px;height:40vh;max-height:280px}.time-picker-popup.inline{box-shadow:none;margin:0;border:1px solid #d9d9d9}.time-picker-popup.inline .time-picker-content{padding:4px;height:100%}.time-picker-popup.inline .time-columns{padding:4px}.time-picker-popup.inline .time-column::-webkit-scrollbar{width:6px}.time-picker-popup.inline .time-column::-webkit-scrollbar-thumb{background:#d9d9d9;border-radius:3px}.time-picker-popup.inline .time-column::-webkit-scrollbar-track{background:#f5f5f5}.time-picker-header{padding:16px;font-size:16px;font-weight:500;border-bottom:1px solid #f0f0f0}.time-picker-content{padding:8px;height:calc(100% - 59px)}.time-columns{display:flex;align-items:center;justify-content:center;padding:8px;gap:8px;height:100%}.time-column{flex:1;height:100%;overflow-y:auto;scrollbar-width:none}.time-column::-webkit-scrollbar{display:none}.time-column button{width:100%;padding:6px 8px;background:none;border:none;cursor:pointer;color:#666;font-size:14px;border-radius:4px}.time-column button:hover:not(.disabled){background:#f5f5f5}.time-column button.selected{background:#e6f4ff;color:#1890ff}.time-column button.disabled{color:#d9d9d9;cursor:not-allowed}.time-scroller{display:flex;flex-direction:column;align-items:center}.time-separator{padding:8px 0;color:#999;font-weight:700}.time-picker-footer{display:flex;justify-content:space-between;padding:8px;border-top:1px solid #f0f0f0}button{padding:4px 15px;border-radius:4px;border:1px solid #d9d9d9;background:white;cursor:pointer;font-size:14px;font-family:inherit}.save-btn{background:#1890ff;border-color:#1890ff;color:#fff}.save-btn:hover{background:#40a9ff;border-color:#40a9ff}.cancel-btn:hover{border-color:#40a9ff;color:#40a9ff}.footer-buttons,.footer-actions{display:flex;gap:8px}.now-btn{color:#1890ff;border-color:transparent;background:transparent;box-shadow:none;padding-left:0}.now-btn:hover{color:#40a9ff}.embedded-time-picker.time-picker-popup .time-picker-content{height:100%}.embedded-time-picker.time-picker-popup.inline{border:none;border-radius:0;height:286px;max-height:290px;direction:ltr}\n"] }]
}], ctorParameters: function () { return [{ type: i1.FormBuilder }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }, { type: i2.QeydarDatePickerService }, { type: i3.JalaliDateAdapter }, { type: i3.GregorianDateAdapter }]; }, propDecorators: { placeholder: [{
type: Input
}], rtl: [{
type: Input
}], placement: [{
type: Input
}], minTime: [{
type: Input
}], maxTime: [{
type: Input
}], lang: [{
type: Input
}], valueType: [{
type: Input
}], cssClass: [{
type: Input
}], showIcon: [{
type: Input
}], dateAdapter: [{
type: Input
}], inline: [{
type: Input
}], disableInputMask: [{
type: Input
}], disabled: [{
type: Input
}], disabledTimesFilter: [{
type: Input
}], allowEmpty: [{
type: Input
}], readOnly: [{
type: Input
}], readOnlyInput: [{
type: Input
}], displayFormat: [{
type: Input
}], selectedDate: [{
type: Input
}], timeChange: [{
type: Output
}], openChange: [{
type: Output
}], timePickerInput: [{
type: ViewChild,
args: ['timePickerInput']
}], popupWrapper: [{
type: ViewChild,
args: ['popupWrapper']
}], handleKeydown: [{
type: HostListener,
args: ['keydown', ['$event']]
}] } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGltZS1waWNrZXIuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvcWV5ZGFyLWRhdGVwaWNrZXIvc3JjL3RpbWUtcGlja2VyL3RpbWUtcGlja2VyLmNvbXBvbmVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7R0FXRztBQUNILE9BQU8sRUFBRSxTQUFTLEVBQWMsVUFBVSxFQUFFLEtBQUssRUFBVSxNQUFNLEVBQUUsWUFBWSxFQUFFLFNBQVMsRUFBYSxZQUFZLEVBQStDLHVCQUF1QixFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ2pOLE9BQU8sRUFBd0IsaUJBQWlCLEVBQTBCLG1CQUFtQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDdEgsT0FBTyxFQUFFLGdCQUFnQixFQUFrQyxhQUFhLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN2RyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFdkQsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFHakUsT0FBTyxFQUFFLDZCQUE2QixFQUFFLDJCQUEyQixFQUFFLE1BQU0sMEJBQTBCLENBQUE7QUFDckcsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNoRSxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQzs7Ozs7O0FBMEtsRSxNQUFNLE9BQU8sbUJBQW1CO0lBcUU5QixZQUNTLEVBQWUsRUFDZixVQUFzQixFQUN0QixLQUF3QixFQUN4QixpQkFBMEMsRUFDMUMsYUFBZ0MsRUFDaEMsZ0JBQXNDO1FBTHRDLE9BQUUsR0FBRixFQUFFLENBQWE7UUFDZixlQUFVLEdBQVYsVUFBVSxDQUFZO1FBQ3RCLFVBQUssR0FBTCxLQUFLLENBQW1CO1FBQ3hCLHNCQUFpQixHQUFqQixpQkFBaUIsQ0FBeUI7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