From a92ef0479ab7984dbff7f0875bb8b4fcfe90738a Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Wed, 20 Aug 2025 21:38:28 -0600 Subject: [PATCH] Refactor jogwheel to its own module. --- calendar.js | 36 ++++-------------------- jogwheel.js | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 jogwheel.js diff --git a/calendar.js b/calendar.js index c632c7d..8b736df 100644 --- a/calendar.js +++ b/calendar.js @@ -15,6 +15,7 @@ import { lunarPhaseSymbol } from './date-utils.js' import { EventManager } from './event-manager.js' +import { JogwheelManager } from './jogwheel.js' class InfiniteCalendar { constructor(config = {}) { @@ -33,10 +34,11 @@ class InfiniteCalendar { this.viewport = document.getElementById('calendar-viewport') this.content = document.getElementById('calendar-content') this.header = document.getElementById('calendar-header') - this.jogwheelViewport = document.getElementById('jogwheel-viewport') - this.jogwheelContent = document.getElementById('jogwheel-content') this.selectedDateInput = document.getElementById('selected-date') + // Initialize jogwheel manager after DOM elements are available + this.jogwheelManager = new JogwheelManager(this) + this.rowHeight = this.computeRowHeight() this.visibleWeeks = new Map() this.baseDate = new Date(2024, 0, 1) // 2024 begins with Monday @@ -45,7 +47,6 @@ class InfiniteCalendar { } init() { this.createHeader() this.setupScrollListener() - this.setupJogwheel() this.setupYearScroll() this.setupSelectionInput() this.setupCurrentDate() @@ -63,7 +64,7 @@ class InfiniteCalendar { this.totalVirtualWeeks = this.maxVirtualWeek - this.minVirtualWeek + 1 this.content.style.height = `${this.totalVirtualWeeks * this.rowHeight}px` - this.jogwheelContent.style.height = `${(this.totalVirtualWeeks * this.rowHeight) / 10}px` + this.jogwheelManager.updateHeight(this.totalVirtualWeeks, this.rowHeight) const initial = this.config.initial || this.today this.navigateTo(fromLocalString(initial)) } @@ -149,11 +150,7 @@ class InfiniteCalendar { else this.viewport.scrollTop = targetScrollTop const mainScrollable = Math.max(0, this.content.scrollHeight - this.viewport.clientHeight) - const jogScrollable = Math.max(0, this.jogwheelContent.scrollHeight - this.jogwheelViewport.clientHeight) - const jogwheelTarget = mainScrollable > 0 ? (targetScrollTop / mainScrollable) * jogScrollable : 0 - - if (smooth) this.jogwheelViewport.scrollTo({ top: jogwheelTarget, behavior: 'smooth' }) - else this.jogwheelViewport.scrollTop = jogwheelTarget + this.jogwheelManager.scrollTo(targetScrollTop, mainScrollable, smooth) if (forceUpdate) this.updateVisibleWeeks() return true @@ -415,27 +412,6 @@ class InfiniteCalendar { return weekDiv } - setupJogwheel() { - let lock = null - const sync = (fromEl, toEl, fromContent, toContent) => { - if (lock === toEl) return - lock = fromEl - const fromScrollable = Math.max(0, fromContent.scrollHeight - fromEl.clientHeight) - const toScrollable = Math.max(0, toContent.scrollHeight - toEl.clientHeight) - const ratio = fromScrollable > 0 ? fromEl.scrollTop / fromScrollable : 0 - toEl.scrollTop = ratio * toScrollable - setTimeout(() => { if (lock === fromEl) lock = null }, 50) - } - - this.jogwheelViewport.addEventListener('scroll', () => - sync(this.jogwheelViewport, this.viewport, this.jogwheelContent, this.content) - ) - - this.viewport.addEventListener('scroll', () => - sync(this.viewport, this.jogwheelViewport, this.content, this.jogwheelContent) - ) - } - setupSelectionInput() { if (this.config.select_days === 0) { this.selectedDateInput.style.display = 'none' diff --git a/jogwheel.js b/jogwheel.js new file mode 100644 index 0000000..1c70050 --- /dev/null +++ b/jogwheel.js @@ -0,0 +1,79 @@ +// jogwheel.js — Jogwheel synchronization for calendar scrolling + +export class JogwheelManager { + constructor(calendar) { + this.calendar = calendar + this.viewport = null + this.content = null + this.syncLock = null + this.init() + } + + init() { + this.viewport = document.getElementById('jogwheel-viewport') + this.content = document.getElementById('jogwheel-content') + + if (!this.viewport || !this.content) { + console.warn('Jogwheel elements not found - jogwheel functionality disabled') + return + } + + this.setupScrollSync() + } + + setupScrollSync() { + // Check if calendar viewport is available + if (!this.calendar.viewport || !this.calendar.content) { + console.warn('Calendar viewport not available - jogwheel sync disabled') + return + } + + // Bind sync function to maintain proper context + const sync = this.sync.bind(this) + + this.viewport.addEventListener('scroll', () => + sync(this.viewport, this.calendar.viewport, this.content, this.calendar.content) + ) + + this.calendar.viewport.addEventListener('scroll', () => + sync(this.calendar.viewport, this.viewport, this.calendar.content, this.content) + ) + } + + sync(fromEl, toEl, fromContent, toContent) { + if (this.syncLock === toEl) return + this.syncLock = fromEl + + const fromScrollable = Math.max(0, fromContent.scrollHeight - fromEl.clientHeight) + const toScrollable = Math.max(0, toContent.scrollHeight - toEl.clientHeight) + const ratio = fromScrollable > 0 ? fromEl.scrollTop / fromScrollable : 0 + toEl.scrollTop = ratio * toScrollable + + setTimeout(() => { + if (this.syncLock === fromEl) this.syncLock = null + }, 50) + } + + updateHeight(totalVirtualWeeks, rowHeight) { + if (this.content) { + this.content.style.height = `${(totalVirtualWeeks * rowHeight) / 10}px` + } + } + + scrollTo(targetScrollTop, mainScrollable, smooth = false) { + if (!this.viewport || !this.content) return + + const jogScrollable = Math.max(0, this.content.scrollHeight - this.viewport.clientHeight) + const jogwheelTarget = mainScrollable > 0 ? (targetScrollTop / mainScrollable) * jogScrollable : 0 + + if (smooth) { + this.viewport.scrollTo({ top: jogwheelTarget, behavior: 'smooth' }) + } else { + this.viewport.scrollTop = jogwheelTarget + } + } + + isAvailable() { + return !!(this.viewport && this.content) + } +}