- Implemented DeesInputFileupload component with file upload functionality, including drag-and-drop support, file previews, and clear all option. - Developed DeesInputRichtext component featuring a rich text editor with a formatting toolbar, link management, and word count display. - Created demo for DeesInputRichtext showcasing various use cases including basic editing, placeholder text, different heights, and disabled state. - Added styles for both components to ensure a consistent and user-friendly interface. - Introduced types for toolbar buttons in the rich text editor for better type safety and maintainability.
		
			
				
	
	
		
			624 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			624 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import {
 | |
|   customElement,
 | |
|   type TemplateResult,
 | |
|   property,
 | |
|   state,
 | |
| } from '@design.estate/dees-element';
 | |
| import { DeesInputBase } from '../dees-input-base.js';
 | |
| import { demoFunc } from './demo.js';
 | |
| import { datepickerStyles } from './styles.js';
 | |
| import { renderDatepicker } from './template.js';
 | |
| import type { IDateEvent } from './types.js';
 | |
| import '../dees-icon.js';
 | |
| import '../dees-label.js';
 | |
| 
 | |
| 
 | |
| declare global {
 | |
|   interface HTMLElementTagNameMap {
 | |
|     'dees-input-datepicker': DeesInputDatepicker;
 | |
|   }
 | |
| }
 | |
| 
 | |
| @customElement('dees-input-datepicker')
 | |
| export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
 | |
|   public static demo = demoFunc;
 | |
| 
 | |
|   @property({ type: String })
 | |
|   public value: string = '';
 | |
| 
 | |
|   @property({ type: Boolean })
 | |
|   public enableTime: boolean = false;
 | |
| 
 | |
|   @property({ type: String })
 | |
|   public timeFormat: '24h' | '12h' = '24h';
 | |
| 
 | |
|   @property({ type: Number })
 | |
|   public minuteIncrement: number = 1;
 | |
| 
 | |
|   @property({ type: String })
 | |
|   public dateFormat: string = 'YYYY-MM-DD';
 | |
| 
 | |
|   @property({ type: String })
 | |
|   public minDate: string = '';
 | |
| 
 | |
|   @property({ type: String })
 | |
|   public maxDate: string = '';
 | |
| 
 | |
|   @property({ type: Array })
 | |
|   public disabledDates: string[] = [];
 | |
| 
 | |
|   @property({ type: Number })
 | |
|   public weekStartsOn: 0 | 1 = 1; // Default to Monday
 | |
| 
 | |
|   @property({ type: String })
 | |
|   public placeholder: string = 'YYYY-MM-DD';
 | |
| 
 | |
|   @property({ type: Boolean })
 | |
|   public enableTimezone: boolean = false;
 | |
| 
 | |
|   @property({ type: String })
 | |
|   public timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
 | |
| 
 | |
|   @property({ type: Array })
 | |
|   public events: IDateEvent[] = [];
 | |
| 
 | |
|   @state()
 | |
|   public isOpened: boolean = false;
 | |
| 
 | |
|   @state()
 | |
|   public opensToTop: boolean = false;
 | |
| 
 | |
|   @state()
 | |
|   public selectedDate: Date | null = null;
 | |
| 
 | |
|   @state()
 | |
|   public viewDate: Date = new Date();
 | |
| 
 | |
|   @state()
 | |
|   public selectedHour: number = 0;
 | |
| 
 | |
|   @state()
 | |
|   public selectedMinute: number = 0;
 | |
| 
 | |
|   public static styles = datepickerStyles;
 | |
| 
 | |
| 
 | |
| 
 | |
|   public getTimezones(): { value: string; label: string }[] {
 | |
|     // Common timezones with their display names
 | |
|     return [
 | |
|       { value: 'UTC', label: 'UTC (Coordinated Universal Time)' },
 | |
|       { value: 'America/New_York', label: 'Eastern Time (US & Canada)' },
 | |
|       { value: 'America/Chicago', label: 'Central Time (US & Canada)' },
 | |
|       { value: 'America/Denver', label: 'Mountain Time (US & Canada)' },
 | |
|       { value: 'America/Los_Angeles', label: 'Pacific Time (US & Canada)' },
 | |
|       { value: 'America/Phoenix', label: 'Arizona' },
 | |
|       { value: 'America/Anchorage', label: 'Alaska' },
 | |
|       { value: 'Pacific/Honolulu', label: 'Hawaii' },
 | |
|       { value: 'Europe/London', label: 'London' },
 | |
|       { value: 'Europe/Paris', label: 'Paris' },
 | |
|       { value: 'Europe/Berlin', label: 'Berlin' },
 | |
|       { value: 'Europe/Moscow', label: 'Moscow' },
 | |
|       { value: 'Asia/Dubai', label: 'Dubai' },
 | |
|       { value: 'Asia/Kolkata', label: 'India Standard Time' },
 | |
|       { value: 'Asia/Shanghai', label: 'China Standard Time' },
 | |
|       { value: 'Asia/Tokyo', label: 'Tokyo' },
 | |
|       { value: 'Australia/Sydney', label: 'Sydney' },
 | |
|       { value: 'Pacific/Auckland', label: 'Auckland' },
 | |
|     ];
 | |
|   }
 | |
| 
 | |
|   public render(): TemplateResult {
 | |
|     return renderDatepicker(this);
 | |
|   }
 | |
| 
 | |
| 
 | |
| 
 | |
|   async connectedCallback() {
 | |
|     super.connectedCallback();
 | |
|     this.handleClickOutside = this.handleClickOutside.bind(this);
 | |
|   }
 | |
| 
 | |
|   async disconnectedCallback() {
 | |
|     await super.disconnectedCallback();
 | |
|     document.removeEventListener('click', this.handleClickOutside);
 | |
|   }
 | |
| 
 | |
|   async firstUpdated() {
 | |
|     // Initialize with empty value if not set
 | |
|     if (!this.value) {
 | |
|       this.value = '';
 | |
|     }
 | |
| 
 | |
|     // Initialize view date and selected time
 | |
|     if (this.value) {
 | |
|       try {
 | |
|         const date = new Date(this.value);
 | |
|         if (!isNaN(date.getTime())) {
 | |
|           this.selectedDate = date;
 | |
|           this.viewDate = new Date(date);
 | |
|           this.selectedHour = date.getHours();
 | |
|           this.selectedMinute = date.getMinutes();
 | |
|         }
 | |
|       } catch {
 | |
|         // Invalid date
 | |
|       }
 | |
|     } else {
 | |
|       const now = new Date();
 | |
|       this.viewDate = new Date(now);
 | |
|       this.selectedHour = now.getHours();
 | |
|       this.selectedMinute = 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   public formatDate(isoString: string): string {
 | |
|     if (!isoString) return '';
 | |
| 
 | |
|     try {
 | |
|       const date = new Date(isoString);
 | |
|       if (isNaN(date.getTime())) return '';
 | |
| 
 | |
|       let formatted = this.dateFormat;
 | |
|       
 | |
|       // Basic date formatting
 | |
|       const day = date.getDate().toString().padStart(2, '0');
 | |
|       const month = (date.getMonth() + 1).toString().padStart(2, '0');
 | |
|       const year = date.getFullYear().toString();
 | |
|       
 | |
|       // Replace in correct order to avoid conflicts
 | |
|       formatted = formatted.replace('YYYY', year);
 | |
|       formatted = formatted.replace('YY', year.slice(-2));
 | |
|       formatted = formatted.replace('MM', month);
 | |
|       formatted = formatted.replace('DD', day);
 | |
| 
 | |
|       // Time formatting if enabled
 | |
|       if (this.enableTime) {
 | |
|         const hours24 = date.getHours();
 | |
|         const hours12 = hours24 === 0 ? 12 : hours24 > 12 ? hours24 - 12 : hours24;
 | |
|         const minutes = date.getMinutes().toString().padStart(2, '0');
 | |
|         const ampm = hours24 >= 12 ? 'PM' : 'AM';
 | |
| 
 | |
|         if (this.timeFormat === '12h') {
 | |
|           formatted += ` ${hours12}:${minutes} ${ampm}`;
 | |
|         } else {
 | |
|           formatted += ` ${hours24.toString().padStart(2, '0')}:${minutes}`;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Timezone formatting if enabled
 | |
|       if (this.enableTimezone) {
 | |
|         const formatter = new Intl.DateTimeFormat('en-US', {
 | |
|           timeZoneName: 'short',
 | |
|           timeZone: this.timezone
 | |
|         });
 | |
|         const parts = formatter.formatToParts(date);
 | |
|         const tzPart = parts.find(part => part.type === 'timeZoneName');
 | |
|         if (tzPart) {
 | |
|           formatted += ` ${tzPart.value}`;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       return formatted;
 | |
|     } catch {
 | |
|       return '';
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   private handleClickOutside = (event: MouseEvent) => {
 | |
|     const path = event.composedPath();
 | |
|     if (!path.includes(this)) {
 | |
|       this.isOpened = false;
 | |
|       document.removeEventListener('click', this.handleClickOutside);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   public async toggleCalendar(): Promise<void> {
 | |
|     if (this.disabled) return;
 | |
| 
 | |
|     this.isOpened = !this.isOpened;
 | |
| 
 | |
|     if (this.isOpened) {
 | |
|       // Check available space and set position
 | |
|       const inputContainer = this.shadowRoot!.querySelector('.input-container') as HTMLElement;
 | |
|       const rect = inputContainer.getBoundingClientRect();
 | |
|       const spaceBelow = window.innerHeight - rect.bottom;
 | |
|       const spaceAbove = rect.top;
 | |
|       
 | |
|       // Determine if we should open upwards (approximate height of 400px)
 | |
|       this.opensToTop = spaceBelow < 400 && spaceAbove > spaceBelow;
 | |
| 
 | |
|       // Add click outside listener
 | |
|       setTimeout(() => {
 | |
|         document.addEventListener('click', this.handleClickOutside);
 | |
|       }, 0);
 | |
|     } else {
 | |
|       document.removeEventListener('click', this.handleClickOutside);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   public getDaysInMonth(): Date[] {
 | |
|     const year = this.viewDate.getFullYear();
 | |
|     const month = this.viewDate.getMonth();
 | |
|     const firstDay = new Date(year, month, 1);
 | |
|     const lastDay = new Date(year, month + 1, 0);
 | |
|     const days: Date[] = [];
 | |
| 
 | |
|     // Adjust for week start
 | |
|     const startOffset = this.weekStartsOn === 1 
 | |
|       ? (firstDay.getDay() === 0 ? 6 : firstDay.getDay() - 1)
 | |
|       : firstDay.getDay();
 | |
| 
 | |
|     // Add days from previous month
 | |
|     for (let i = startOffset; i > 0; i--) {
 | |
|       days.push(new Date(year, month, 1 - i));
 | |
|     }
 | |
| 
 | |
|     // Add days of current month
 | |
|     for (let i = 1; i <= lastDay.getDate(); i++) {
 | |
|       days.push(new Date(year, month, i));
 | |
|     }
 | |
| 
 | |
|     // Add days from next month to complete the grid (6 rows)
 | |
|     const remainingDays = 42 - days.length;
 | |
|     for (let i = 1; i <= remainingDays; i++) {
 | |
|       days.push(new Date(year, month + 1, i));
 | |
|     }
 | |
| 
 | |
|     return days;
 | |
|   }
 | |
| 
 | |
|   public isToday(date: Date): boolean {
 | |
|     const today = new Date();
 | |
|     return date.getDate() === today.getDate() &&
 | |
|            date.getMonth() === today.getMonth() &&
 | |
|            date.getFullYear() === today.getFullYear();
 | |
|   }
 | |
| 
 | |
|   public isSelected(date: Date): boolean {
 | |
|     if (!this.selectedDate) return false;
 | |
|     return date.getDate() === this.selectedDate.getDate() &&
 | |
|            date.getMonth() === this.selectedDate.getMonth() &&
 | |
|            date.getFullYear() === this.selectedDate.getFullYear();
 | |
|   }
 | |
| 
 | |
|   public isDisabled(date: Date): boolean {
 | |
|     // Check min date
 | |
|     if (this.minDate) {
 | |
|       const min = new Date(this.minDate);
 | |
|       if (date < min) return true;
 | |
|     }
 | |
| 
 | |
|     // Check max date
 | |
|     if (this.maxDate) {
 | |
|       const max = new Date(this.maxDate);
 | |
|       if (date > max) return true;
 | |
|     }
 | |
| 
 | |
|     // Check disabled dates
 | |
|     if (this.disabledDates && this.disabledDates.length > 0) {
 | |
|       return this.disabledDates.some(disabledStr => {
 | |
|         try {
 | |
|           const disabled = new Date(disabledStr);
 | |
|           return date.getDate() === disabled.getDate() &&
 | |
|                  date.getMonth() === disabled.getMonth() &&
 | |
|                  date.getFullYear() === disabled.getFullYear();
 | |
|         } catch {
 | |
|           return false;
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   public getEventsForDate(date: Date): IDateEvent[] {
 | |
|     if (!this.events || this.events.length === 0) return [];
 | |
|     
 | |
|     const dateStr = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
 | |
|     return this.events.filter(event => event.date === dateStr);
 | |
|   }
 | |
| 
 | |
|   public selectDate(date: Date): void {
 | |
|     this.selectedDate = new Date(
 | |
|       date.getFullYear(),
 | |
|       date.getMonth(),
 | |
|       date.getDate(),
 | |
|       this.selectedHour,
 | |
|       this.selectedMinute
 | |
|     );
 | |
|     
 | |
|     this.value = this.formatValueWithTimezone(this.selectedDate);
 | |
|     this.changeSubject.next(this);
 | |
|     
 | |
|     if (!this.enableTime) {
 | |
|       this.isOpened = false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   public selectToday(): void {
 | |
|     const today = new Date();
 | |
|     this.selectedDate = today;
 | |
|     this.viewDate = new Date(today);
 | |
|     this.selectedHour = today.getHours();
 | |
|     this.selectedMinute = today.getMinutes();
 | |
|     
 | |
|     this.value = this.formatValueWithTimezone(this.selectedDate);
 | |
|     this.changeSubject.next(this);
 | |
|     
 | |
|     if (!this.enableTime) {
 | |
|       this.isOpened = false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   public clear(): void {
 | |
|     this.value = '';
 | |
|     this.selectedDate = null;
 | |
|     this.changeSubject.next(this);
 | |
|     this.isOpened = false;
 | |
|   }
 | |
| 
 | |
|   public previousMonth(): void {
 | |
|     this.viewDate = new Date(this.viewDate.getFullYear(), this.viewDate.getMonth() - 1, 1);
 | |
|   }
 | |
| 
 | |
|   public nextMonth(): void {
 | |
|     this.viewDate = new Date(this.viewDate.getFullYear(), this.viewDate.getMonth() + 1, 1);
 | |
|   }
 | |
| 
 | |
|   public handleHourInput(e: InputEvent): void {
 | |
|     const input = e.target as HTMLInputElement;
 | |
|     let value = parseInt(input.value) || 0;
 | |
|     
 | |
|     if (this.timeFormat === '12h') {
 | |
|       value = Math.max(1, Math.min(12, value));
 | |
|       // Convert to 24h format
 | |
|       if (this.selectedHour >= 12 && value !== 12) {
 | |
|         this.selectedHour = value + 12;
 | |
|       } else if (this.selectedHour < 12 && value === 12) {
 | |
|         this.selectedHour = 0;
 | |
|       } else {
 | |
|         this.selectedHour = value;
 | |
|       }
 | |
|     } else {
 | |
|       this.selectedHour = Math.max(0, Math.min(23, value));
 | |
|     }
 | |
|     
 | |
|     this.updateSelectedDateTime();
 | |
|   }
 | |
| 
 | |
|   public handleMinuteInput(e: InputEvent): void {
 | |
|     const input = e.target as HTMLInputElement;
 | |
|     let value = parseInt(input.value) || 0;
 | |
|     value = Math.max(0, Math.min(59, value));
 | |
|     
 | |
|     if (this.minuteIncrement && this.minuteIncrement > 1) {
 | |
|       value = Math.round(value / this.minuteIncrement) * this.minuteIncrement;
 | |
|     }
 | |
|     
 | |
|     this.selectedMinute = value;
 | |
|     this.updateSelectedDateTime();
 | |
|   }
 | |
| 
 | |
|   public setAMPM(period: 'am' | 'pm'): void {
 | |
|     if (period === 'am' && this.selectedHour >= 12) {
 | |
|       this.selectedHour -= 12;
 | |
|     } else if (period === 'pm' && this.selectedHour < 12) {
 | |
|       this.selectedHour += 12;
 | |
|     }
 | |
|     this.updateSelectedDateTime();
 | |
|   }
 | |
| 
 | |
|   private updateSelectedDateTime(): void {
 | |
|     if (this.selectedDate) {
 | |
|       this.selectedDate = new Date(
 | |
|         this.selectedDate.getFullYear(),
 | |
|         this.selectedDate.getMonth(),
 | |
|         this.selectedDate.getDate(),
 | |
|         this.selectedHour,
 | |
|         this.selectedMinute
 | |
|       );
 | |
|       this.value = this.formatValueWithTimezone(this.selectedDate);
 | |
|       this.changeSubject.next(this);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   public handleTimezoneChange(e: Event): void {
 | |
|     const select = e.target as HTMLSelectElement;
 | |
|     this.timezone = select.value;
 | |
|     this.updateSelectedDateTime();
 | |
|   }
 | |
| 
 | |
|   private formatValueWithTimezone(date: Date): string {
 | |
|     if (!this.enableTimezone) {
 | |
|       return date.toISOString();
 | |
|     }
 | |
|     
 | |
|     // Format the date with timezone offset
 | |
|     const formatter = new Intl.DateTimeFormat('en-US', {
 | |
|       year: 'numeric',
 | |
|       month: '2-digit',
 | |
|       day: '2-digit',
 | |
|       hour: '2-digit',
 | |
|       minute: '2-digit',
 | |
|       second: '2-digit',
 | |
|       hour12: false,
 | |
|       timeZone: this.timezone,
 | |
|       timeZoneName: 'short'
 | |
|     });
 | |
|     
 | |
|     const parts = formatter.formatToParts(date);
 | |
|     const dateParts: any = {};
 | |
|     parts.forEach(part => {
 | |
|       dateParts[part.type] = part.value;
 | |
|     });
 | |
|     
 | |
|     // Create ISO-like format with timezone
 | |
|     const isoString = `${dateParts.year}-${dateParts.month}-${dateParts.day}T${dateParts.hour}:${dateParts.minute}:${dateParts.second}`;
 | |
|     
 | |
|     // Get timezone offset
 | |
|     const tzOffset = this.getTimezoneOffset(date, this.timezone);
 | |
|     return `${isoString}${tzOffset}`;
 | |
|   }
 | |
| 
 | |
|   private getTimezoneOffset(date: Date, timezone: string): string {
 | |
|     // Create a date in the target timezone
 | |
|     const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone }));
 | |
|     const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
 | |
|     
 | |
|     const offsetMinutes = (tzDate.getTime() - utcDate.getTime()) / (1000 * 60);
 | |
|     const hours = Math.floor(Math.abs(offsetMinutes) / 60);
 | |
|     const minutes = Math.abs(offsetMinutes) % 60;
 | |
|     const sign = offsetMinutes >= 0 ? '+' : '-';
 | |
|     
 | |
|     return `${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
 | |
|   }
 | |
| 
 | |
|   public handleKeydown(e: KeyboardEvent): void {
 | |
|     if (e.key === 'Enter' || e.key === ' ') {
 | |
|       e.preventDefault();
 | |
|       this.toggleCalendar();
 | |
|     } else if (e.key === 'Escape' && this.isOpened) {
 | |
|       e.preventDefault();
 | |
|       this.isOpened = false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   public clearValue(e: Event): void {
 | |
|     e.stopPropagation();
 | |
|     this.value = '';
 | |
|     this.selectedDate = null;
 | |
|     this.changeSubject.next(this);
 | |
|   }
 | |
| 
 | |
|   public handleManualInput(e: InputEvent): void {
 | |
|     const input = e.target as HTMLInputElement;
 | |
|     const inputValue = input.value.trim();
 | |
|     
 | |
|     if (!inputValue) {
 | |
|       // Clear the value if input is empty
 | |
|       this.value = '';
 | |
|       this.selectedDate = null;
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     const parsedDate = this.parseManualDate(inputValue);
 | |
|     if (parsedDate && !isNaN(parsedDate.getTime())) {
 | |
|       // Update internal state without triggering re-render of input
 | |
|       this.value = parsedDate.toISOString();
 | |
|       this.selectedDate = parsedDate;
 | |
|       this.viewDate = new Date(parsedDate);
 | |
|       this.selectedHour = parsedDate.getHours();
 | |
|       this.selectedMinute = parsedDate.getMinutes();
 | |
|       this.changeSubject.next(this);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   public handleInputBlur(e: FocusEvent): void {
 | |
|     const input = e.target as HTMLInputElement;
 | |
|     const inputValue = input.value.trim();
 | |
|     
 | |
|     if (!inputValue) {
 | |
|       this.value = '';
 | |
|       this.selectedDate = null;
 | |
|       this.changeSubject.next(this);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     const parsedDate = this.parseManualDate(inputValue);
 | |
|     if (parsedDate && !isNaN(parsedDate.getTime())) {
 | |
|       this.value = parsedDate.toISOString();
 | |
|       this.selectedDate = parsedDate;
 | |
|       this.viewDate = new Date(parsedDate);
 | |
|       this.selectedHour = parsedDate.getHours();
 | |
|       this.selectedMinute = parsedDate.getMinutes();
 | |
|       this.changeSubject.next(this);
 | |
|       // Update the input with formatted date
 | |
|       input.value = this.formatDate(this.value);
 | |
|     } else {
 | |
|       // Revert to previous valid value on blur if parsing failed
 | |
|       input.value = this.formatDate(this.value);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   private parseManualDate(input: string): Date | null {
 | |
|     if (!input) return null;
 | |
| 
 | |
|     // Split date and time parts if present
 | |
|     const parts = input.split(' ');
 | |
|     let datePart = parts[0];
 | |
|     let timePart = parts[1] || '';
 | |
| 
 | |
|     let parsedDate: Date | null = null;
 | |
| 
 | |
|     // Try different date formats
 | |
|     // Format 1: YYYY-MM-DD (ISO-like)
 | |
|     const isoMatch = datePart.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
 | |
|     if (isoMatch) {
 | |
|       const [_, year, month, day] = isoMatch;
 | |
|       parsedDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
 | |
|     }
 | |
| 
 | |
|     // Format 2: DD.MM.YYYY (European)
 | |
|     if (!parsedDate) {
 | |
|       const euMatch = datePart.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/);
 | |
|       if (euMatch) {
 | |
|         const [_, day, month, year] = euMatch;
 | |
|         parsedDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Format 3: MM/DD/YYYY (US)
 | |
|     if (!parsedDate) {
 | |
|       const usMatch = datePart.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
 | |
|       if (usMatch) {
 | |
|         const [_, month, day, year] = usMatch;
 | |
|         parsedDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // If no date was parsed, return null
 | |
|     if (!parsedDate || isNaN(parsedDate.getTime())) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     // Parse time if present (HH:MM format)
 | |
|     if (timePart) {
 | |
|       const timeMatch = timePart.match(/^(\d{1,2}):(\d{2})$/);
 | |
|       if (timeMatch) {
 | |
|         const [_, hours, minutes] = timeMatch;
 | |
|         parsedDate.setHours(parseInt(hours));
 | |
|         parsedDate.setMinutes(parseInt(minutes));
 | |
|       }
 | |
|     } else if (!this.enableTime) {
 | |
|       // If time is not enabled and not provided, use current time
 | |
|       const now = new Date();
 | |
|       parsedDate.setHours(now.getHours());
 | |
|       parsedDate.setMinutes(now.getMinutes());
 | |
|       parsedDate.setSeconds(0);
 | |
|       parsedDate.setMilliseconds(0);
 | |
|     }
 | |
| 
 | |
|     return parsedDate;
 | |
|   }
 | |
| 
 | |
|   public getValue(): string {
 | |
|     return this.value;
 | |
|   }
 | |
| 
 | |
|   public setValue(value: string): void {
 | |
|     this.value = value;
 | |
|     if (value) {
 | |
|       try {
 | |
|         const date = new Date(value);
 | |
|         if (!isNaN(date.getTime())) {
 | |
|           this.selectedDate = date;
 | |
|           this.viewDate = new Date(date);
 | |
|           this.selectedHour = date.getHours();
 | |
|           this.selectedMinute = date.getMinutes();
 | |
|         }
 | |
|       } catch {
 | |
|         // Invalid date
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| } |