311 lines
10 KiB
JavaScript
311 lines
10 KiB
JavaScript
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();
|
|
}
|
|
});
|