import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
/**
* The docked panel's width: the persisted preference, the viewport clamp, and
* the pointer drag that resizes it. Below the desktop breakpoint the panel is
* a full-screen drawer and none of this applies.
*/
const DRAWER_DESKTOP_BREAKPOINT = 980
const CHAT_MIN_WIDTH = 500
const DRAWER_WIDTH_STORAGE_KEY = 'navi:artifacts-drawer-width'
export function useDrawerWidth() {
const isResizing = ref(false)
const viewportWidth = ref(typeof window === 'undefined' ? 0 : window.innerWidth)
const drawerWidth = ref(readSavedDrawerWidth())
const isDesktop = computed(() => viewportWidth.value > DRAWER_DESKTOP_BREAKPOINT)
const panelStyle = computed(() => {
if (!isDesktop.value) return {}
return { '--artifacts-width': `${currentDrawerWidth()}px` }
})
function minDrawerWidth() {
return Math.round(Math.min(600, viewportWidth.value * 0.42))
}
function maxDrawerWidth() {
return Math.max(minDrawerWidth(), viewportWidth.value - CHAT_MIN_WIDTH)
}
function clampDrawerWidth(width) {
return Math.min(Math.max(Math.round(width), minDrawerWidth()), maxDrawerWidth())
}
function currentDrawerWidth() {
return clampDrawerWidth(drawerWidth.value ?? minDrawerWidth())
}
function syncViewportWidth() {
viewportWidth.value = window.innerWidth
if (drawerWidth.value !== null) drawerWidth.value = currentDrawerWidth()
}
function startResize(event) {
if (!isDesktop.value) return
event.preventDefault()
isResizing.value = true
document.body.style.cursor = 'col-resize'
document.body.style.userSelect = 'none'
window.addEventListener('pointermove', resizeDrawer)
window.addEventListener('pointerup', stopResize, { once: true })
}
function resizeDrawer(event) {
drawerWidth.value = clampDrawerWidth(viewportWidth.value - event.clientX)
}
function stopResize() {
isResizing.value = false
saveDrawerWidth()
document.body.style.cursor = ''
document.body.style.userSelect = ''
window.removeEventListener('pointermove', resizeDrawer)
window.removeEventListener('pointerup', stopResize)
}
function readSavedDrawerWidth() {
if (typeof window === 'undefined') return null
try {
const value = Number(window.localStorage.getItem(DRAWER_WIDTH_STORAGE_KEY))
return Number.isFinite(value) && value > 0 ? value : null
} catch {
return null
}
}
function saveDrawerWidth() {
if (typeof window === 'undefined' || drawerWidth.value === null) return
try {
window.localStorage.setItem(DRAWER_WIDTH_STORAGE_KEY, String(currentDrawerWidth()))
} catch {
// Storage may be unavailable in restricted browser contexts.
}
}
onMounted(() => {
window.addEventListener('resize', syncViewportWidth)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', syncViewportWidth)
window.removeEventListener('pointermove', resizeDrawer)
window.removeEventListener('pointerup', stopResize)
if (isResizing.value) stopResize()
})
return { isResizing, isDesktop, panelStyle, startResize }
}