document.addEventListener('DOMContentLoaded', function() { // DOM elements const hourButton = document.querySelector('.timepicker-hour'); const minuteButton = document.querySelector('.timepicker-minute'); const clockWrapper = document.querySelector('.timepicker-clock-wrapper'); const clock = document.querySelector('.timepicker-clock'); const handPointer = document.querySelector('.timepicker-hand-pointer'); const amButton = document.querySelector('.timepicker-am'); const pmButton = document.querySelector('.timepicker-pm'); const clearButton = document.querySelector('.timepicker-clear'); const cancelButton = document.querySelector('.timepicker-cancel'); const submitButton = document.querySelector('.timepicker-submit'); // State variables let currentView = 'hours'; // 'hours' or 'minutes' let selectedHour = 12; let selectedMinute = 0; let isDragging = false; let isPM = pmButton.classList.contains('active'); // Initialize setupEventListeners(); renderClockFace(currentView); function setupEventListeners() { // Header hour/minute buttons hourButton.addEventListener('click', () => switchView('hours')); minuteButton.addEventListener('click', () => switchView('minutes')); // Clock face events clock.addEventListener('mousedown', handleClockMouseDown); document.addEventListener('mousemove', handleClockMouseMove); document.addEventListener('mouseup', handleClockMouseUp); // Prevent text selection on the clock clock.style.userSelect = 'none'; clock.style.webkitUserSelect = 'none'; clock.style.msUserSelect = 'none'; // AM/PM buttons amButton.addEventListener('click', () => setAmPm('AM')); pmButton.addEventListener('click', () => setAmPm('PM')); // Action buttons clearButton.addEventListener('click', clearTime); cancelButton.addEventListener('click', closeTimepicker); submitButton.addEventListener('click', submitTime); } function switchView(view) { currentView = view; // Update active state in header if (view === 'hours') { hourButton.classList.add('active'); minuteButton.classList.remove('active'); hourButton.style.pointerEvents = 'none'; minuteButton.style.pointerEvents = 'auto'; } else { hourButton.classList.remove('active'); minuteButton.classList.add('active'); hourButton.style.pointerEvents = 'auto'; minuteButton.style.pointerEvents = 'none'; } renderClockFace(view); } function renderClockFace(view) { // Clear existing time tips const existingTips = clock.querySelectorAll('.timepicker-time-tips-hours, .timepicker-time-tips-minutes'); existingTips.forEach(tip => tip.remove()); if (view === 'hours') { renderHoursFace(); updateHandPosition(selectedHour, 'hours'); } else { renderMinutesFace(); updateHandPosition(selectedMinute, 'minutes'); } } function renderHoursFace() { const clockRadius = clock.offsetWidth / 2; const tipRadius = clockRadius * 0.85; // Position closer to the edge (85% of radius) for (let hour = 1; hour <= 12; hour++) { // Adjust angle calculation to position numbers correctly // Start at 12 o'clock position and go clockwise const angle = ((hour * 30) - 90) * (Math.PI / 180); // Center the numbers on the clock face const left = clockRadius + tipRadius * Math.cos(angle); const top = clockRadius + tipRadius * Math.sin(angle); const tip = document.createElement('span'); tip.className = 'timepicker-time-tips-hours'; // Adjust positioning to center the numbers tip.style.left = `${left}px`; tip.style.top = `${top}px`; tip.style.position = 'absolute'; tip.style.transform = 'translate(-50%, -50%)'; // Center the element at its position // Prevent text selection tip.style.userSelect = 'none'; tip.style.webkitUserSelect = 'none'; tip.style.msUserSelect = 'none'; const tipElement = document.createElement('span'); tipElement.className = 'timepicker-tips-element'; tipElement.textContent = hour; tip.appendChild(tipElement); if (hour === selectedHour) { tip.classList.add('active'); } clock.appendChild(tip); } } function renderMinutesFace() { const clockRadius = clock.offsetWidth / 2; const tipRadius = clockRadius * 0.85; // Position closer to the edge (85% of radius) for (let minute = 0; minute < 60; minute += 5) { // Adjust angle calculation to position numbers correctly // Start at 12 o'clock position and go clockwise const angle = ((minute * 6) - 90) * (Math.PI / 180); // Center the numbers on the clock face const left = clockRadius + tipRadius * Math.cos(angle); const top = clockRadius + tipRadius * Math.sin(angle); const tip = document.createElement('span'); tip.className = 'timepicker-time-tips-minutes'; // Adjust positioning to center the numbers tip.style.left = `${left}px`; tip.style.top = `${top}px`; tip.style.position = 'absolute'; tip.style.transform = 'translate(-50%, -50%)'; // Center the element at its position // Prevent text selection tip.style.userSelect = 'none'; tip.style.webkitUserSelect = 'none'; tip.style.msUserSelect = 'none'; const tipElement = document.createElement('span'); tipElement.className = 'timepicker-tips-element'; tipElement.textContent = minute.toString().padStart(2, '0'); tip.appendChild(tipElement); if (minute === selectedMinute) { tip.classList.add('active'); } clock.appendChild(tip); } } function updateHandPosition(value, view) { let angle; if (view === 'hours') { // For hours, convert 12 to 0 for calculation purposes const hour = value === 12 ? 0 : value; angle = (hour / 12) * 360; } else { angle = (value / 60) * 360; } handPointer.style.transform = `rotateZ(${angle}deg)`; // Update the active circle on the hand const circle = handPointer.querySelector('.timepicker-circle'); if (circle) { circle.classList.add('active'); } } function handleClockMouseDown(event) { isDragging = true; updateTimeFromClockPosition(event); } function handleClockMouseMove(event) { if (isDragging) { updateTimeFromClockPosition(event); } } function handleClockMouseUp() { if (isDragging) { isDragging = false; // If we just finished selecting an hour, switch to minutes if (currentView === 'hours') { switchView('minutes'); } } } function updateTimeFromClockPosition(event) { const clockRect = clock.getBoundingClientRect(); const centerX = clockRect.left + clockRect.width / 2; const centerY = clockRect.top + clockRect.height / 2; // Calculate angle from center to mouse position const x = event.clientX - centerX; const y = event.clientY - centerY; // Calculate angle in degrees, starting from 12 o'clock position // atan2 returns angle in radians from -π to π, with 0 at 3 o'clock position // We add 90 degrees to start from 12 o'clock instead of 3 o'clock let angle = Math.atan2(y, x) * (180 / Math.PI) + 90; // Normalize angle to 0-360 if (angle < 0) { angle += 360; } if (currentView === 'hours') { // Convert angle to hour (1-12) // Each hour is 30 degrees (360/12) let hour = Math.round(angle / 30); // Handle edge cases for full circle if (hour === 0 || hour > 12) hour = 12; selectedHour = hour; hourButton.textContent = hour; updateHandPosition(hour, 'hours'); // Update active class on hour tips const hourTips = clock.querySelectorAll('.timepicker-time-tips-hours'); hourTips.forEach(tip => { const hourValue = parseInt(tip.querySelector('.timepicker-tips-element').textContent); if (hourValue === selectedHour) { tip.classList.add('active'); } else { tip.classList.remove('active'); } }); } else { // Convert angle to minute (0-55, step 5) // Each 5-minute increment is 30 degrees (360/12) // First, get the minute value from 0-59 let minute = Math.floor(angle / 6); // Round to nearest 5 minute = Math.round(minute / 5) * 5; // Handle edge case for full circle if (minute >= 60) minute = 0; selectedMinute = minute; minuteButton.textContent = minute.toString().padStart(2, '0'); updateHandPosition(minute, 'minutes'); // Update active class on minute tips const minuteTips = clock.querySelectorAll('.timepicker-time-tips-minutes'); minuteTips.forEach(tip => { const minuteValue = parseInt(tip.querySelector('.timepicker-tips-element').textContent); if (minuteValue === selectedMinute) { tip.classList.add('active'); } else { tip.classList.remove('active'); } }); } } function setAmPm(period) { if (period === 'AM') { amButton.classList.add('active'); pmButton.classList.remove('active'); isPM = false; } else { amButton.classList.remove('active'); pmButton.classList.add('active'); isPM = true; } } function clearTime() { selectedHour = 12; selectedMinute = 0; hourButton.textContent = '12'; minuteButton.textContent = '00'; setAmPm('PM'); switchView('hours'); } function closeTimepicker() { // You can implement closing the modal here console.log('Timepicker closed'); } function submitTime() { const formattedHour = selectedHour; const formattedMinute = selectedMinute.toString().padStart(2, '0'); const period = isPM ? 'PM' : 'AM'; console.log(`Selected time: ${formattedHour}:${formattedMinute} ${period}`); // Here you can pass the selected time to your application closeTimepicker(); } });