113 lines
3.0 KiB
Vue
113 lines
3.0 KiB
Vue
<template>
|
|
<div class="jogwheel-viewport" ref="jogwheelViewport" @scroll="handleJogwheelScroll">
|
|
<div class="jogwheel-content" ref="jogwheelContent" :style="{ height: jogwheelHeight + 'px' }"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, watch } from 'vue'
|
|
|
|
const props = defineProps({
|
|
totalVirtualWeeks: { type: Number, required: true },
|
|
rowHeight: { type: Number, required: true },
|
|
viewportHeight: { type: Number, required: true },
|
|
scrollTop: { type: Number, required: true }
|
|
})
|
|
|
|
const emit = defineEmits(['scroll-to'])
|
|
|
|
const jogwheelViewport = ref(null)
|
|
const jogwheelContent = ref(null)
|
|
const syncLock = ref(null)
|
|
|
|
// Jogwheel content height is 1/10th of main calendar
|
|
const jogwheelHeight = computed(() => {
|
|
return (props.totalVirtualWeeks * props.rowHeight) / 10
|
|
})
|
|
|
|
const handleJogwheelScroll = () => {
|
|
if (syncLock.value === 'jogwheel') return
|
|
syncFromJogwheel()
|
|
}
|
|
|
|
const syncFromJogwheel = () => {
|
|
if (!jogwheelViewport.value || !jogwheelContent.value) return
|
|
|
|
syncLock.value = 'main'
|
|
|
|
const jogScrollable = Math.max(0, jogwheelContent.value.scrollHeight - jogwheelViewport.value.clientHeight)
|
|
const mainScrollable = Math.max(0, props.totalVirtualWeeks * props.rowHeight - props.viewportHeight)
|
|
|
|
if (jogScrollable > 0) {
|
|
const ratio = jogwheelViewport.value.scrollTop / jogScrollable
|
|
|
|
// Emit scroll event to parent to update main viewport
|
|
emit('scroll-to', ratio * mainScrollable)
|
|
}
|
|
|
|
setTimeout(() => {
|
|
if (syncLock.value === 'main') syncLock.value = null
|
|
}, 50)
|
|
}
|
|
|
|
const syncFromMain = (mainScrollTop) => {
|
|
if (!jogwheelViewport.value || !jogwheelContent.value) return
|
|
if (syncLock.value === 'main') return
|
|
|
|
syncLock.value = 'jogwheel'
|
|
|
|
const mainScrollable = Math.max(0, props.totalVirtualWeeks * props.rowHeight - props.viewportHeight)
|
|
const jogScrollable = Math.max(0, jogwheelContent.value.scrollHeight - jogwheelViewport.value.clientHeight)
|
|
|
|
if (mainScrollable > 0) {
|
|
const ratio = mainScrollTop / mainScrollable
|
|
jogwheelViewport.value.scrollTop = ratio * jogScrollable
|
|
}
|
|
|
|
setTimeout(() => {
|
|
if (syncLock.value === 'jogwheel') syncLock.value = null
|
|
}, 50)
|
|
}
|
|
|
|
// Watch for main calendar scroll changes
|
|
watch(() => props.scrollTop, (newScrollTop) => {
|
|
syncFromMain(newScrollTop)
|
|
})
|
|
|
|
defineExpose({
|
|
syncFromMain
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.jogwheel-viewport {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
width: 3rem; /* Use fixed width since minmax() doesn't work for absolute positioning */
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
scrollbar-width: none;
|
|
z-index: 20;
|
|
cursor: ns-resize;
|
|
background: rgba(0, 0, 0, 0.05); /* Temporary background to see the area */
|
|
/* Prevent text selection */
|
|
-webkit-user-select: none;
|
|
-moz-user-select: none;
|
|
-ms-user-select: none;
|
|
user-select: none;
|
|
-webkit-touch-callout: none;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
|
|
.jogwheel-viewport::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.jogwheel-content {
|
|
position: relative;
|
|
width: 100%;
|
|
}
|
|
</style>
|