import { customElement, type TemplateResult, property, state, html, css, cssManager, DeesElement, } from '@design.estate/dees-element'; import * as domtools from '@design.estate/dees-domtools'; import { zIndexRegistry } from '../../00zindex.js'; import { themeDefaultStyles } from '../../00theme.js'; import { DeesWindowLayer } from '../../00group-overlay/dees-windowlayer/dees-windowlayer.js'; import '../../00group-utility/dees-icon/dees-icon.js'; import type { IDateEvent } from './types.js'; declare global { interface HTMLElementTagNameMap { 'dees-input-datepicker-popup': DeesInputDatepickerPopup; } } @customElement('dees-input-datepicker-popup') export class DeesInputDatepickerPopup extends DeesElement { // Properties set by the parent @property({ attribute: false }) accessor triggerRect: DOMRect | null = null; @property({ attribute: false }) accessor ownerComponent: HTMLElement | null = null; @property({ type: Boolean }) accessor enableTime: boolean = false; @property({ type: String }) accessor timeFormat: '24h' | '12h' = '24h'; @property({ type: Number }) accessor minuteIncrement: number = 1; @property({ type: Number }) accessor weekStartsOn: 0 | 1 = 1; @property({ type: String }) accessor minDate: string = ''; @property({ type: String }) accessor maxDate: string = ''; @property({ type: Array }) accessor disabledDates: string[] = []; @property({ type: Boolean }) accessor enableTimezone: boolean = false; @property({ type: String }) accessor timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone; @property({ type: Array }) accessor events: IDateEvent[] = []; @property({ type: Boolean }) accessor opensToTop: boolean = false; // Internal state @state() accessor selectedDate: Date | null = null; @state() accessor viewDate: Date = new Date(); @state() accessor selectedHour: number = 0; @state() accessor selectedMinute: number = 0; @state() accessor menuZIndex: number = 1000; @state() accessor visible: boolean = false; private windowLayer: DeesWindowLayer | null = null; private isDestroying: boolean = false; public static styles = [ themeDefaultStyles, cssManager.defaultStyles, css` :host { position: fixed; top: 0; left: 0; width: 0; height: 0; pointer-events: none; } * { box-sizing: border-box; } .calendar-popup { position: fixed; pointer-events: auto; will-change: transform, opacity; transition: all 0.15s ease; opacity: 0; transform: translateY(-4px); background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')}; border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')}; box-shadow: ${cssManager.bdTheme( '0 10px 15px -3px hsl(0 0% 0% / 0.1), 0 4px 6px -4px hsl(0 0% 0% / 0.1)', '0 10px 15px -3px hsl(0 0% 0% / 0.2), 0 4px 6px -4px hsl(0 0% 0% / 0.2)' )}; border-radius: 6px; padding: 12px; user-select: none; min-width: 280px; } .calendar-popup.top { transform: translateY(4px); } .calendar-popup.show { transform: translateY(0); opacity: 1; } /* Calendar Header */ .calendar-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; gap: 8px; } .month-year-display { font-weight: 500; font-size: 14px; color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')}; flex: 1; text-align: center; } .nav-button { width: 28px; height: 28px; border: none; background: transparent; cursor: pointer; border-radius: 6px; display: flex; align-items: center; justify-content: center; color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')}; transition: all 0.2s ease; } .nav-button:hover { background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')}; color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')}; } /* Weekday headers */ .weekdays { display: grid; grid-template-columns: repeat(7, 1fr); margin-bottom: 4px; } .weekday { text-align: center; font-size: 12px; font-weight: 400; color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')}; padding: 0 0 8px 0; } /* Days grid */ .days-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; } .day { aspect-ratio: 1; display: flex; align-items: center; justify-content: center; cursor: pointer; border-radius: 6px; font-size: 14px; transition: all 0.2s ease; color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')}; border: none; width: 36px; height: 36px; background: transparent; position: relative; } .day:hover:not(.disabled) { background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')}; } .day.other-month { color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')}; opacity: 0.5; } .day.today { background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')}; font-weight: 500; } .day.selected { background: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')}; color: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(222.2 47.4% 11.2%)')}; font-weight: 500; } .day.disabled { color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')}; cursor: not-allowed; opacity: 0.3; } /* Event indicators */ .event-indicator { position: absolute; bottom: 4px; left: 50%; transform: translateX(-50%); display: flex; gap: 2px; } .event-dot { width: 4px; height: 4px; border-radius: 50%; background: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')}; } .event-dot.info { background: ${cssManager.bdTheme('hsl(211 70% 52%)', 'hsl(211 70% 62%)')}; } .event-dot.warning { background: ${cssManager.bdTheme('hsl(45 90% 45%)', 'hsl(45 90% 55%)')}; } .event-dot.success { background: ${cssManager.bdTheme('hsl(142 69% 45%)', 'hsl(142 69% 55%)')}; } .event-dot.error { background: ${cssManager.bdTheme('hsl(0 72% 51%)', 'hsl(0 72% 61%)')}; } .event-count { position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px; padding: 0 4px; background: ${cssManager.bdTheme('hsl(0 72% 51%)', 'hsl(0 72% 61%)')}; color: white; border-radius: 8px; font-size: 10px; font-weight: 600; display: flex; align-items: center; justify-content: center; } .event-tooltip { position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%); background: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')}; color: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 0%)')}; padding: 8px 12px; border-radius: 6px; font-size: 12px; white-space: nowrap; pointer-events: none; opacity: 0; transition: opacity 0.2s ease; z-index: 10; } .day.has-event:hover .event-tooltip { opacity: 1; } /* Time selector */ .time-selector { margin-top: 12px; padding-top: 12px; border-top: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')}; } .time-selector-title { font-size: 12px; font-weight: 500; margin-bottom: 8px; color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')}; } .time-inputs { display: flex; gap: 8px; align-items: center; } .time-input { width: 65px; height: 36px; border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')}; border-radius: 6px; padding: 0 12px; font-size: 14px; text-align: center; background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')}; color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')}; transition: all 0.2s ease; } .time-input:focus { outline: none; border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')}; box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(222.2 47.4% 11.2% / 0.1)', 'hsl(210 20% 98% / 0.1)')}; } .time-separator { font-size: 14px; font-weight: 500; color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')}; } .am-pm-selector { display: flex; gap: 4px; margin-left: 8px; } .am-pm-button { padding: 6px 12px; border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')}; background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')}; border-radius: 6px; font-size: 12px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')}; } .am-pm-button.selected { background: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')}; color: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(222.2 47.4% 11.2%)')}; border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')}; } .am-pm-button:hover:not(.selected) { background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')}; } /* Timezone selector */ .timezone-selector { margin-top: 12px; padding-top: 12px; border-top: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')}; } .timezone-selector-title { font-size: 12px; font-weight: 500; margin-bottom: 8px; color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')}; } .timezone-select { width: 100%; height: 36px; border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')}; border-radius: 6px; padding: 0 12px; font-size: 14px; background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')}; color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')}; cursor: pointer; } /* Action buttons */ .calendar-actions { display: flex; gap: 8px; margin-top: 12px; padding-top: 12px; border-top: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')}; } .action-button { flex: 1; height: 36px; border: none; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; } .today-button { background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')}; border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')}; color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')}; } .today-button:hover { background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')}; } .clear-action-button { background: transparent; border: 1px solid transparent; color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')}; } .clear-action-button:hover { background: ${cssManager.bdTheme('hsl(0 72.2% 50.6% / 0.1)', 'hsl(0 62.8% 30.6% / 0.1)')}; color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')}; } `, ]; private static readonly MONTH_NAMES = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; private static readonly TIMEZONES = [ { 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: '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 { if (!this.triggerRect) return html``; const posStyle = this.computePositionStyle(); const weekDays = this.weekStartsOn === 1 ? ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'] : ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']; const days = this.getDaysInMonth(); const isAM = this.selectedHour < 12; return html`