infinite-agents-public/src_enhanced/ui_enhanced_10.html

1213 lines
43 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Date Picker Enhanced</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #2563eb;
--primary-hover: #1d4ed8;
--primary-light: #dbeafe;
--secondary: #64748b;
--text: #1e293b;
--text-light: #64748b;
--bg: #ffffff;
--bg-hover: #f8fafc;
--border: #e2e8f0;
--shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
--radius: 12px;
--transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
color: var(--text);
}
main {
width: 100%;
max-width: 1200px;
}
h1 {
font-size: 2.5rem;
font-weight: 800;
text-align: center;
margin-bottom: 3rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.component-showcase {
background: var(--bg);
border-radius: 16px;
padding: 3rem;
box-shadow: var(--shadow);
position: relative;
}
.demo-container {
display: grid;
gap: 2rem;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.demo-section {
background: var(--bg-hover);
border-radius: var(--radius);
padding: 1.5rem;
}
.demo-section h3 {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 1rem;
color: var(--text);
}
/* Date Input Styling */
.date-input-wrapper {
position: relative;
margin-bottom: 1rem;
}
.date-input {
width: 100%;
padding: 0.75rem 2.5rem 0.75rem 1rem;
border: 2px solid var(--border);
border-radius: 8px;
font-size: 1rem;
transition: var(--transition);
background: var(--bg);
cursor: pointer;
}
.date-input:hover {
border-color: var(--primary-light);
}
.date-input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.calendar-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
pointer-events: none;
color: var(--text-light);
}
/* Date Picker Container */
.date-picker {
position: absolute;
top: calc(100% + 8px);
left: 0;
z-index: 1000;
background: var(--bg);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 1rem;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: var(--transition);
min-width: 320px;
}
.date-picker.active {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
/* Date Picker Header */
.picker-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
gap: 1rem;
}
.month-year-select {
display: flex;
align-items: center;
gap: 0.5rem;
}
.month-select, .year-select {
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--bg);
font-size: 0.875rem;
cursor: pointer;
transition: var(--transition);
}
.month-select:hover, .year-select:hover {
border-color: var(--primary);
}
.nav-buttons {
display: flex;
gap: 0.5rem;
}
.nav-btn {
width: 32px;
height: 32px;
border: none;
background: var(--bg-hover);
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
color: var(--text);
}
.nav-btn:hover {
background: var(--primary-light);
color: var(--primary);
}
.nav-btn:active {
transform: scale(0.95);
}
/* Calendar Grid */
.calendar-wrapper {
position: relative;
overflow: hidden;
height: 280px;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 2px;
position: absolute;
width: 100%;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.calendar-grid.prev {
transform: translateX(-100%);
}
.calendar-grid.next {
transform: translateX(100%);
}
.calendar-grid.animating-prev {
animation: slideInFromLeft 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.calendar-grid.animating-next {
animation: slideInFromRight 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes slideInFromLeft {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
@keyframes slideInFromRight {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
.day-header {
font-size: 0.75rem;
font-weight: 600;
text-align: center;
padding: 0.5rem;
color: var(--text-light);
}
.day-cell {
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
cursor: pointer;
font-size: 0.875rem;
position: relative;
transition: var(--transition);
background: var(--bg);
border: 1px solid transparent;
}
.day-cell:hover:not(.disabled):not(.other-month) {
background: var(--bg-hover);
border-color: var(--border);
transform: scale(1.05);
}
.day-cell.today {
background: var(--primary-light);
color: var(--primary);
font-weight: 600;
}
.day-cell.selected {
background: var(--primary);
color: white;
font-weight: 600;
}
.day-cell.in-range {
background: var(--primary-light);
color: var(--primary);
}
.day-cell.range-start {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.day-cell.range-end {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.day-cell.disabled {
color: var(--text-light);
opacity: 0.3;
cursor: not-allowed;
}
.day-cell.other-month {
color: var(--text-light);
opacity: 0.5;
}
.week-number {
font-size: 0.625rem;
color: var(--text-light);
position: absolute;
top: 2px;
right: 4px;
opacity: 0.6;
}
/* Quick Presets */
.presets {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border);
}
.preset-btn {
padding: 0.375rem 0.75rem;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--bg);
font-size: 0.75rem;
cursor: pointer;
transition: var(--transition);
white-space: nowrap;
}
.preset-btn:hover {
background: var(--primary-light);
border-color: var(--primary);
color: var(--primary);
}
/* Time Picker */
.time-picker {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 1rem;
border-top: 1px solid var(--border);
}
.time-input {
width: 60px;
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 6px;
text-align: center;
font-size: 0.875rem;
}
.time-separator {
font-weight: 600;
color: var(--text-light);
}
/* Keyboard shortcuts hint */
.keyboard-hint {
position: absolute;
bottom: -40px;
left: 0;
right: 0;
text-align: center;
font-size: 0.75rem;
color: var(--text-light);
opacity: 0;
transition: opacity 0.3s;
}
.date-picker:focus-within .keyboard-hint {
opacity: 1;
}
/* Multi-select styles */
.multi-select-badge {
position: absolute;
top: -6px;
right: -6px;
background: var(--primary);
color: white;
font-size: 0.625rem;
width: 18px;
height: 18px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
}
/* Mobile optimizations */
@media (max-width: 640px) {
.date-picker {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100vw - 40px);
max-width: 360px;
}
.date-picker.active {
transform: translate(-50%, -50%);
}
.backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
opacity: 0;
visibility: hidden;
transition: var(--transition);
}
.backdrop.active {
opacity: 1;
visibility: visible;
}
}
/* Focus indicators */
.day-cell:focus {
outline: 2px solid var(--primary);
outline-offset: -1px;
}
/* Loading state */
.loading {
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.9);
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius);
}
.spinner {
width: 24px;
height: 24px;
border: 3px solid var(--border);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<main>
<h1>Date Picker - Enhanced</h1>
<div class="component-showcase">
<div class="demo-container">
<!-- Single Date Picker -->
<div class="demo-section">
<h3>Single Date Selection</h3>
<div class="date-input-wrapper">
<input type="text" class="date-input" id="singleDate" placeholder="Select a date" readonly>
<svg class="calendar-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
<p style="font-size: 0.875rem; color: var(--text-light); margin-top: 0.5rem;">
Use arrow keys to navigate, Enter to select
</p>
</div>
<!-- Date Range Picker -->
<div class="demo-section">
<h3>Date Range Selection</h3>
<div class="date-input-wrapper">
<input type="text" class="date-input" id="dateRange" placeholder="Select date range" readonly>
<svg class="calendar-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
<p style="font-size: 0.875rem; color: var(--text-light); margin-top: 0.5rem;">
Click start date, then end date
</p>
</div>
<!-- Date Time Picker -->
<div class="demo-section">
<h3>Date & Time Selection</h3>
<div class="date-input-wrapper">
<input type="text" class="date-input" id="dateTime" placeholder="Select date and time" readonly>
<svg class="calendar-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<p style="font-size: 0.875rem; color: var(--text-light); margin-top: 0.5rem;">
Includes time picker integration
</p>
</div>
<!-- Multiple Dates Picker -->
<div class="demo-section">
<h3>Multiple Date Selection</h3>
<div class="date-input-wrapper">
<input type="text" class="date-input" id="multiDate" placeholder="Select multiple dates" readonly>
<svg class="calendar-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
</div>
<p style="font-size: 0.875rem; color: var(--text-light); margin-top: 0.5rem;">
Ctrl/Cmd + click to select multiple
</p>
</div>
</div>
<div class="backdrop" id="backdrop"></div>
</div>
</main>
<script>
class EnhancedDatePicker {
constructor(input, options = {}) {
this.input = input;
this.options = {
mode: 'single', // single, range, multi
showTime: false,
showWeekNumbers: false,
minDate: null,
maxDate: null,
disabledDates: [],
locale: 'en-US',
firstDayOfWeek: 0, // 0 = Sunday
...options
};
this.selectedDates = [];
this.rangeStart = null;
this.rangeEnd = null;
this.currentMonth = new Date().getMonth();
this.currentYear = new Date().getFullYear();
this.isAnimating = false;
this.init();
}
init() {
this.createPicker();
this.attachEventListeners();
this.updateCalendar();
}
createPicker() {
// Create picker container
this.picker = document.createElement('div');
this.picker.className = 'date-picker';
this.picker.setAttribute('tabindex', '-1');
// Create header
const header = document.createElement('div');
header.className = 'picker-header';
// Month/Year selects
const monthYearSelect = document.createElement('div');
monthYearSelect.className = 'month-year-select';
this.monthSelect = document.createElement('select');
this.monthSelect.className = 'month-select';
const months = [];
for (let i = 0; i < 12; i++) {
const date = new Date(2000, i, 1);
months.push(date.toLocaleDateString(this.options.locale, { month: 'long' }));
}
months.forEach((month, index) => {
const option = document.createElement('option');
option.value = index;
option.textContent = month;
this.monthSelect.appendChild(option);
});
this.yearSelect = document.createElement('select');
this.yearSelect.className = 'year-select';
const currentYear = new Date().getFullYear();
for (let year = currentYear - 100; year <= currentYear + 100; year++) {
const option = document.createElement('option');
option.value = year;
option.textContent = year;
this.yearSelect.appendChild(option);
}
monthYearSelect.appendChild(this.monthSelect);
monthYearSelect.appendChild(this.yearSelect);
// Navigation buttons
const navButtons = document.createElement('div');
navButtons.className = 'nav-buttons';
this.prevBtn = document.createElement('button');
this.prevBtn.className = 'nav-btn';
this.prevBtn.innerHTML = '';
this.prevBtn.setAttribute('aria-label', 'Previous month');
this.nextBtn = document.createElement('button');
this.nextBtn.className = 'nav-btn';
this.nextBtn.innerHTML = '';
this.nextBtn.setAttribute('aria-label', 'Next month');
navButtons.appendChild(this.prevBtn);
navButtons.appendChild(this.nextBtn);
header.appendChild(monthYearSelect);
header.appendChild(navButtons);
// Calendar wrapper
this.calendarWrapper = document.createElement('div');
this.calendarWrapper.className = 'calendar-wrapper';
// Presets
if (this.options.mode !== 'multi') {
this.presetsContainer = document.createElement('div');
this.presetsContainer.className = 'presets';
const presets = [
{ label: 'Today', value: () => [new Date()] },
{ label: 'Yesterday', value: () => [new Date(Date.now() - 86400000)] },
{ label: 'Last Week', value: () => {
const end = new Date();
const start = new Date(Date.now() - 7 * 86400000);
return [start, end];
}},
{ label: 'Last Month', value: () => {
const end = new Date();
const start = new Date(Date.now() - 30 * 86400000);
return [start, end];
}}
];
presets.forEach(preset => {
const btn = document.createElement('button');
btn.className = 'preset-btn';
btn.textContent = preset.label;
btn.addEventListener('click', () => {
const dates = preset.value();
if (this.options.mode === 'range' && dates.length === 2) {
this.rangeStart = dates[0];
this.rangeEnd = dates[1];
this.updateInput();
this.close();
} else if (dates.length === 1) {
this.selectedDates = dates;
this.updateInput();
this.close();
}
});
this.presetsContainer.appendChild(btn);
});
}
// Time picker
if (this.options.showTime) {
this.timePicker = document.createElement('div');
this.timePicker.className = 'time-picker';
this.hourInput = document.createElement('input');
this.hourInput.className = 'time-input';
this.hourInput.type = 'number';
this.hourInput.min = '0';
this.hourInput.max = '23';
this.hourInput.value = '12';
const separator = document.createElement('span');
separator.className = 'time-separator';
separator.textContent = ':';
this.minuteInput = document.createElement('input');
this.minuteInput.className = 'time-input';
this.minuteInput.type = 'number';
this.minuteInput.min = '0';
this.minuteInput.max = '59';
this.minuteInput.value = '00';
this.timePicker.appendChild(this.hourInput);
this.timePicker.appendChild(separator);
this.timePicker.appendChild(this.minuteInput);
}
// Keyboard hint
const keyboardHint = document.createElement('div');
keyboardHint.className = 'keyboard-hint';
keyboardHint.textContent = '↑↓←→ Navigate • Enter Select • PgUp/PgDn Month • Esc Close';
// Assemble picker
this.picker.appendChild(header);
this.picker.appendChild(this.calendarWrapper);
if (this.presetsContainer) {
this.picker.appendChild(this.presetsContainer);
}
if (this.timePicker) {
this.picker.appendChild(this.timePicker);
}
this.picker.appendChild(keyboardHint);
// Add to wrapper
this.input.parentElement.appendChild(this.picker);
}
updateCalendar() {
if (this.isAnimating) return;
this.monthSelect.value = this.currentMonth;
this.yearSelect.value = this.currentYear;
const newGrid = this.createCalendarGrid();
if (this.currentGrid) {
this.isAnimating = true;
this.calendarWrapper.appendChild(newGrid);
setTimeout(() => {
this.currentGrid.remove();
this.currentGrid = newGrid;
this.isAnimating = false;
}, 300);
} else {
this.calendarWrapper.appendChild(newGrid);
this.currentGrid = newGrid;
}
}
createCalendarGrid() {
const grid = document.createElement('div');
grid.className = 'calendar-grid';
// Day headers
const dayNames = [];
for (let i = 0; i < 7; i++) {
const date = new Date(2000, 0, 2 + i); // January 2, 2000 was a Sunday
dayNames.push(date.toLocaleDateString(this.options.locale, { weekday: 'short' }));
}
// Reorder based on firstDayOfWeek
const orderedDayNames = [
...dayNames.slice(this.options.firstDayOfWeek),
...dayNames.slice(0, this.options.firstDayOfWeek)
];
orderedDayNames.forEach(day => {
const header = document.createElement('div');
header.className = 'day-header';
header.textContent = day.substring(0, 2);
grid.appendChild(header);
});
// Calculate days
const firstDay = new Date(this.currentYear, this.currentMonth, 1);
const lastDay = new Date(this.currentYear, this.currentMonth + 1, 0);
const prevLastDay = new Date(this.currentYear, this.currentMonth, 0);
let startDay = firstDay.getDay() - this.options.firstDayOfWeek;
if (startDay < 0) startDay += 7;
// Previous month days
for (let i = startDay - 1; i >= 0; i--) {
const date = new Date(this.currentYear, this.currentMonth - 1, prevLastDay.getDate() - i);
grid.appendChild(this.createDayCell(date, true));
}
// Current month days
for (let day = 1; day <= lastDay.getDate(); day++) {
const date = new Date(this.currentYear, this.currentMonth, day);
grid.appendChild(this.createDayCell(date, false));
}
// Next month days
const remainingCells = 42 - grid.children.length + 7; // +7 for headers
for (let day = 1; day <= remainingCells - 7; day++) {
const date = new Date(this.currentYear, this.currentMonth + 1, day);
grid.appendChild(this.createDayCell(date, true));
}
return grid;
}
createDayCell(date, isOtherMonth) {
const cell = document.createElement('button');
cell.className = 'day-cell';
cell.setAttribute('role', 'gridcell');
cell.setAttribute('aria-label', date.toLocaleDateString(this.options.locale));
if (isOtherMonth) {
cell.classList.add('other-month');
}
// Check if today
const today = new Date();
if (this.isSameDay(date, today)) {
cell.classList.add('today');
}
// Check if selected
if (this.options.mode === 'single' || this.options.mode === 'multi') {
this.selectedDates.forEach(selected => {
if (this.isSameDay(date, selected)) {
cell.classList.add('selected');
}
});
} else if (this.options.mode === 'range') {
if (this.rangeStart && this.isSameDay(date, this.rangeStart)) {
cell.classList.add('selected', 'range-start');
}
if (this.rangeEnd && this.isSameDay(date, this.rangeEnd)) {
cell.classList.add('selected', 'range-end');
}
if (this.rangeStart && this.rangeEnd &&
date > this.rangeStart && date < this.rangeEnd) {
cell.classList.add('in-range');
}
}
// Check if disabled
if (this.isDateDisabled(date)) {
cell.classList.add('disabled');
cell.disabled = true;
}
// Day number
const dayNumber = document.createElement('span');
dayNumber.textContent = date.getDate();
cell.appendChild(dayNumber);
// Week number
if (this.options.showWeekNumbers && date.getDay() === this.options.firstDayOfWeek) {
const weekNumber = document.createElement('span');
weekNumber.className = 'week-number';
weekNumber.textContent = this.getWeekNumber(date);
cell.appendChild(weekNumber);
}
// Multi-select badge
if (this.options.mode === 'multi') {
const count = this.selectedDates.filter(d => this.isSameDay(d, date)).length;
if (count > 1) {
const badge = document.createElement('span');
badge.className = 'multi-select-badge';
badge.textContent = count;
cell.appendChild(badge);
}
}
// Click handler
cell.addEventListener('click', () => this.selectDate(date));
return cell;
}
selectDate(date) {
if (this.isDateDisabled(date)) return;
if (this.options.mode === 'single') {
this.selectedDates = [date];
this.updateInput();
this.close();
} else if (this.options.mode === 'range') {
if (!this.rangeStart || (this.rangeStart && this.rangeEnd)) {
this.rangeStart = date;
this.rangeEnd = null;
} else {
if (date < this.rangeStart) {
this.rangeEnd = this.rangeStart;
this.rangeStart = date;
} else {
this.rangeEnd = date;
}
this.updateInput();
this.close();
}
} else if (this.options.mode === 'multi') {
const index = this.selectedDates.findIndex(d => this.isSameDay(d, date));
if (index > -1) {
this.selectedDates.splice(index, 1);
} else {
this.selectedDates.push(date);
}
this.updateInput();
}
this.updateCalendar();
}
attachEventListeners() {
// Input click
this.input.addEventListener('click', () => this.toggle());
// Month/Year change
this.monthSelect.addEventListener('change', (e) => {
this.currentMonth = parseInt(e.target.value);
this.updateCalendar();
});
this.yearSelect.addEventListener('change', (e) => {
this.currentYear = parseInt(e.target.value);
this.updateCalendar();
});
// Navigation buttons
this.prevBtn.addEventListener('click', () => this.navigate(-1));
this.nextBtn.addEventListener('click', () => this.navigate(1));
// Keyboard navigation
this.picker.addEventListener('keydown', (e) => this.handleKeyboard(e));
// Click outside
document.addEventListener('click', (e) => {
if (!this.picker.contains(e.target) && !this.input.contains(e.target)) {
this.close();
}
});
// Mobile backdrop
const backdrop = document.getElementById('backdrop');
if (backdrop) {
backdrop.addEventListener('click', () => this.close());
}
}
handleKeyboard(e) {
const focusedDate = this.getFocusedDate();
let newDate;
switch(e.key) {
case 'ArrowLeft':
e.preventDefault();
newDate = new Date(focusedDate);
newDate.setDate(newDate.getDate() - 1);
this.focusDate(newDate);
break;
case 'ArrowRight':
e.preventDefault();
newDate = new Date(focusedDate);
newDate.setDate(newDate.getDate() + 1);
this.focusDate(newDate);
break;
case 'ArrowUp':
e.preventDefault();
newDate = new Date(focusedDate);
newDate.setDate(newDate.getDate() - 7);
this.focusDate(newDate);
break;
case 'ArrowDown':
e.preventDefault();
newDate = new Date(focusedDate);
newDate.setDate(newDate.getDate() + 7);
this.focusDate(newDate);
break;
case 'PageUp':
e.preventDefault();
this.navigate(-1);
break;
case 'PageDown':
e.preventDefault();
this.navigate(1);
break;
case 'Enter':
e.preventDefault();
this.selectDate(focusedDate);
break;
case 'Escape':
e.preventDefault();
this.close();
break;
}
}
getFocusedDate() {
const focused = this.picker.querySelector('.day-cell:focus');
if (focused) {
// Extract date from focused cell
const day = parseInt(focused.textContent);
return new Date(this.currentYear, this.currentMonth, day);
}
return new Date();
}
focusDate(date) {
if (date.getMonth() !== this.currentMonth || date.getFullYear() !== this.currentYear) {
this.currentMonth = date.getMonth();
this.currentYear = date.getFullYear();
this.updateCalendar();
}
setTimeout(() => {
const cells = this.picker.querySelectorAll('.day-cell:not(.other-month)');
cells.forEach(cell => {
if (parseInt(cell.textContent) === date.getDate()) {
cell.focus();
}
});
}, 100);
}
navigate(direction) {
if (this.isAnimating) return;
const oldGrid = this.currentGrid;
this.currentMonth += direction;
if (this.currentMonth > 11) {
this.currentMonth = 0;
this.currentYear++;
} else if (this.currentMonth < 0) {
this.currentMonth = 11;
this.currentYear--;
}
this.monthSelect.value = this.currentMonth;
this.yearSelect.value = this.currentYear;
// Create new grid
const newGrid = this.createCalendarGrid();
// Animate transition
this.isAnimating = true;
if (direction > 0) {
newGrid.classList.add('next');
this.calendarWrapper.appendChild(newGrid);
setTimeout(() => {
oldGrid.classList.add('prev');
newGrid.classList.remove('next');
newGrid.classList.add('animating-next');
}, 10);
} else {
newGrid.classList.add('prev');
this.calendarWrapper.appendChild(newGrid);
setTimeout(() => {
oldGrid.classList.add('next');
newGrid.classList.remove('prev');
newGrid.classList.add('animating-prev');
}, 10);
}
setTimeout(() => {
oldGrid.remove();
newGrid.classList.remove('animating-prev', 'animating-next');
this.currentGrid = newGrid;
this.isAnimating = false;
}, 300);
}
updateInput() {
let value = '';
if (this.options.mode === 'single' && this.selectedDates.length > 0) {
value = this.formatDate(this.selectedDates[0]);
} else if (this.options.mode === 'range' && this.rangeStart && this.rangeEnd) {
value = `${this.formatDate(this.rangeStart)} - ${this.formatDate(this.rangeEnd)}`;
} else if (this.options.mode === 'multi' && this.selectedDates.length > 0) {
const sorted = [...this.selectedDates].sort((a, b) => a - b);
value = sorted.map(d => this.formatDate(d)).join(', ');
if (value.length > 30) {
value = `${this.selectedDates.length} dates selected`;
}
}
this.input.value = value;
}
formatDate(date) {
if (this.options.showTime) {
const hours = this.hourInput ? this.hourInput.value : date.getHours();
const minutes = this.minuteInput ? this.minuteInput.value : date.getMinutes();
date.setHours(hours, minutes);
return date.toLocaleString(this.options.locale, {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
return date.toLocaleDateString(this.options.locale, {
year: 'numeric',
month: 'short',
day: 'numeric'
});
}
isSameDay(date1, date2) {
return date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate();
}
isDateDisabled(date) {
if (this.options.minDate && date < this.options.minDate) return true;
if (this.options.maxDate && date > this.options.maxDate) return true;
return this.options.disabledDates.some(disabled =>
this.isSameDay(date, disabled)
);
}
getWeekNumber(date) {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
return Math.ceil((((d - yearStart) / 86400000) + 1)/7);
}
toggle() {
if (this.picker.classList.contains('active')) {
this.close();
} else {
this.open();
}
}
open() {
this.picker.classList.add('active');
// Mobile backdrop
const backdrop = document.getElementById('backdrop');
if (backdrop && window.innerWidth <= 640) {
backdrop.classList.add('active');
}
// Position picker
this.positionPicker();
// Focus picker
setTimeout(() => this.picker.focus(), 100);
}
close() {
this.picker.classList.remove('active');
// Mobile backdrop
const backdrop = document.getElementById('backdrop');
if (backdrop) {
backdrop.classList.remove('active');
}
}
positionPicker() {
if (window.innerWidth <= 640) return; // Fixed position on mobile
const inputRect = this.input.getBoundingClientRect();
const pickerRect = this.picker.getBoundingClientRect();
// Check if picker would go off-screen
if (inputRect.bottom + pickerRect.height > window.innerHeight) {
// Position above input
this.picker.style.top = 'auto';
this.picker.style.bottom = `calc(100% + 8px)`;
} else {
// Position below input
this.picker.style.top = `calc(100% + 8px)`;
this.picker.style.bottom = 'auto';
}
if (inputRect.left + pickerRect.width > window.innerWidth) {
// Align to right edge
this.picker.style.left = 'auto';
this.picker.style.right = '0';
} else {
// Align to left edge
this.picker.style.left = '0';
this.picker.style.right = 'auto';
}
}
}
// Initialize demo pickers
document.addEventListener('DOMContentLoaded', () => {
// Single date picker
new EnhancedDatePicker(document.getElementById('singleDate'), {
mode: 'single',
showWeekNumbers: true
});
// Date range picker
new EnhancedDatePicker(document.getElementById('dateRange'), {
mode: 'range'
});
// Date time picker
new EnhancedDatePicker(document.getElementById('dateTime'), {
mode: 'single',
showTime: true
});
// Multiple dates picker
new EnhancedDatePicker(document.getElementById('multiDate'), {
mode: 'multi',
showWeekNumbers: true
});
});
</script>
</body>
</html>