freesched/timepicker-modal/timepicker-modal.js

311 lines
10 KiB
JavaScript
Raw Permalink Normal View History

2025-02-27 22:14:07 +00:00
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();
}
});