Refactor jogwheel to its own module.

This commit is contained in:
Leo Vasanko 2025-08-20 21:38:28 -06:00
parent b8b8575c6d
commit a92ef0479a
2 changed files with 85 additions and 30 deletions

View File

@ -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'

79
jogwheel.js Normal file
View File

@ -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)
}
}