Major new version #2
@ -124,6 +124,10 @@ const todayString = computed(() => {
|
|||||||
const visibleWeeks = ref([])
|
const visibleWeeks = ref([])
|
||||||
let lastScrollRange = { startVW: null, endVW: null }
|
let lastScrollRange = { startVW: null, endVW: null }
|
||||||
let pendingRebuild = false
|
let pendingRebuild = false
|
||||||
|
// Week label column drag scrolling state (no momentum)
|
||||||
|
const isWeekColDragging = ref(false)
|
||||||
|
let weekColDragStartY = 0
|
||||||
|
let weekColDragStartScroll = 0
|
||||||
|
|
||||||
function scheduleRebuild(reason) {
|
function scheduleRebuild(reason) {
|
||||||
if (pendingRebuild) return
|
if (pendingRebuild) return
|
||||||
@ -498,6 +502,49 @@ function calculateSelection(anchorStr, otherStr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------- Week label column drag scrolling ----------------
|
||||||
|
function getWeekLabelRect() {
|
||||||
|
// Prefer header year label width as stable reference
|
||||||
|
const headerYear = document.querySelector('.calendar-header .year-label')
|
||||||
|
if (headerYear) return headerYear.getBoundingClientRect()
|
||||||
|
const weekLabel = viewport.value?.querySelector('.week-row .week-label')
|
||||||
|
return weekLabel ? weekLabel.getBoundingClientRect() : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWeekColMouseDown(e) {
|
||||||
|
if (e.button !== 0) return
|
||||||
|
if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return
|
||||||
|
if (!viewport.value) return
|
||||||
|
const rect = getWeekLabelRect()
|
||||||
|
if (!rect) return
|
||||||
|
if (e.clientX < rect.left || e.clientX > rect.right) return
|
||||||
|
isWeekColDragging.value = true
|
||||||
|
weekColDragStartY = e.clientY
|
||||||
|
weekColDragStartScroll = viewport.value.scrollTop
|
||||||
|
window.addEventListener('mousemove', handleWeekColMouseMove, { passive: false })
|
||||||
|
window.addEventListener('mouseup', handleWeekColMouseUp, { passive: false })
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWeekColMouseMove(e) {
|
||||||
|
if (!isWeekColDragging.value || !viewport.value) return
|
||||||
|
const dy = e.clientY - weekColDragStartY
|
||||||
|
// Natural: drag down moves view to earlier content (scroll up)
|
||||||
|
viewport.value.scrollTop = Math.max(0, weekColDragStartScroll - dy)
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
// (momentum removed per requirements)
|
||||||
|
|
||||||
|
function handleWeekColMouseUp(e) {
|
||||||
|
if (!isWeekColDragging.value) return
|
||||||
|
isWeekColDragging.value = false
|
||||||
|
window.removeEventListener('mousemove', handleWeekColMouseMove)
|
||||||
|
window.removeEventListener('mouseup', handleWeekColMouseUp)
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
const onScroll = () => {
|
const onScroll = () => {
|
||||||
if (viewport.value) scrollTop.value = viewport.value.scrollTop
|
if (viewport.value) scrollTop.value = viewport.value.scrollTop
|
||||||
scheduleRebuild('scroll')
|
scheduleRebuild('scroll')
|
||||||
@ -517,6 +564,8 @@ onMounted(() => {
|
|||||||
viewportHeight.value = viewport.value.clientHeight
|
viewportHeight.value = viewport.value.clientHeight
|
||||||
viewport.value.scrollTop = initialScrollTop.value
|
viewport.value.scrollTop = initialScrollTop.value
|
||||||
viewport.value.addEventListener('scroll', onScroll)
|
viewport.value.addEventListener('scroll', onScroll)
|
||||||
|
// Capture mousedown in viewport to allow dragging via week label column
|
||||||
|
viewport.value.addEventListener('mousedown', handleWeekColMouseDown, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
@ -541,6 +590,7 @@ onMounted(() => {
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (viewport.value) {
|
if (viewport.value) {
|
||||||
viewport.value.removeEventListener('scroll', onScroll)
|
viewport.value.removeEventListener('scroll', onScroll)
|
||||||
|
viewport.value.removeEventListener('mousedown', handleWeekColMouseDown, true)
|
||||||
}
|
}
|
||||||
if (rowProbeObserver && rowProbe.value) {
|
if (rowProbeObserver && rowProbe.value) {
|
||||||
try {
|
try {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
totalVirtualWeeks: { type: Number, required: true },
|
totalVirtualWeeks: { type: Number, required: true },
|
||||||
@ -19,6 +19,11 @@ const emit = defineEmits(['scroll-to'])
|
|||||||
const jogwheelViewport = ref(null)
|
const jogwheelViewport = ref(null)
|
||||||
const jogwheelContent = ref(null)
|
const jogwheelContent = ref(null)
|
||||||
const syncLock = ref(null)
|
const syncLock = ref(null)
|
||||||
|
// Drag state (no momentum, 1:1 mapping)
|
||||||
|
const isDragging = ref(false)
|
||||||
|
let dragStartY = 0
|
||||||
|
let mainStartScroll = 0
|
||||||
|
let dragScale = 1 // mainScrollPixels per mouse pixel
|
||||||
|
|
||||||
// Jogwheel content height is 1/10th of main calendar
|
// Jogwheel content height is 1/10th of main calendar
|
||||||
const jogwheelHeight = computed(() => {
|
const jogwheelHeight = computed(() => {
|
||||||
@ -30,6 +35,58 @@ const handleJogwheelScroll = () => {
|
|||||||
syncFromJogwheel()
|
syncFromJogwheel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onDragMouseDown(e) {
|
||||||
|
if (e.button !== 0) return
|
||||||
|
isDragging.value = true
|
||||||
|
dragStartY = e.clientY
|
||||||
|
mainStartScroll = props.scrollTop
|
||||||
|
// Precompute scale between jogwheel scrollable range and main scrollable range
|
||||||
|
const mainScrollable = Math.max(0, props.totalVirtualWeeks * props.rowHeight - props.viewportHeight)
|
||||||
|
let jogScrollable = 0
|
||||||
|
if (jogwheelViewport.value && jogwheelContent.value) {
|
||||||
|
jogScrollable = Math.max(0, jogwheelContent.value.scrollHeight - jogwheelViewport.value.clientHeight)
|
||||||
|
}
|
||||||
|
dragScale = jogScrollable > 0 ? mainScrollable / jogScrollable : 1
|
||||||
|
if (!isFinite(dragScale) || dragScale <= 0) dragScale = 1
|
||||||
|
window.addEventListener('mousemove', onDragMouseMove, { passive: false })
|
||||||
|
window.addEventListener('mouseup', onDragMouseUp, { passive: false })
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragMouseMove(e) {
|
||||||
|
if (!isDragging.value) return
|
||||||
|
const dy = e.clientY - dragStartY
|
||||||
|
// Natural content drag (drag down => scrollTop decreases)
|
||||||
|
let desired = mainStartScroll - dy * dragScale
|
||||||
|
if (desired < 0) desired = 0
|
||||||
|
const maxScroll = Math.max(0, props.totalVirtualWeeks * props.rowHeight - props.viewportHeight)
|
||||||
|
if (desired > maxScroll) desired = maxScroll
|
||||||
|
emit('scroll-to', desired)
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragMouseUp(e) {
|
||||||
|
if (!isDragging.value) return
|
||||||
|
isDragging.value = false
|
||||||
|
window.removeEventListener('mousemove', onDragMouseMove)
|
||||||
|
window.removeEventListener('mouseup', onDragMouseUp)
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (jogwheelViewport.value) {
|
||||||
|
jogwheelViewport.value.addEventListener('mousedown', onDragMouseDown)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (jogwheelViewport.value) {
|
||||||
|
jogwheelViewport.value.removeEventListener('mousedown', onDragMouseDown)
|
||||||
|
}
|
||||||
|
window.removeEventListener('mousemove', onDragMouseMove)
|
||||||
|
window.removeEventListener('mouseup', onDragMouseUp)
|
||||||
|
})
|
||||||
|
|
||||||
const syncFromJogwheel = () => {
|
const syncFromJogwheel = () => {
|
||||||
if (!jogwheelViewport.value || !jogwheelContent.value) return
|
if (!jogwheelViewport.value || !jogwheelContent.value) return
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user