2026-06-11 00:24:40 -04:00
|
|
|
import { nextTick, onBeforeUnmount, onMounted, ref, type Ref } from 'vue'
|
|
|
|
|
|
|
|
|
|
export function useCollapsingHeaderTabs(
|
|
|
|
|
headerRef: Ref<HTMLElement | null>,
|
|
|
|
|
primaryRef: Ref<HTMLElement | null>,
|
|
|
|
|
tabsProbeRef: Ref<HTMLElement | null>,
|
2026-06-11 01:16:21 -04:00
|
|
|
minSearchWidth = 176,
|
|
|
|
|
minTabsWidth = 260
|
2026-06-11 00:24:40 -04:00
|
|
|
) {
|
|
|
|
|
const collapsed = ref(false)
|
|
|
|
|
let resizeObserver: ResizeObserver | null = null
|
|
|
|
|
|
|
|
|
|
function measure() {
|
|
|
|
|
const header = headerRef.value
|
|
|
|
|
const probe = tabsProbeRef.value
|
|
|
|
|
if (!header || !probe) return
|
|
|
|
|
|
|
|
|
|
const primaryWidth = primaryRef.value?.getBoundingClientRect().width ?? 0
|
|
|
|
|
const tabsWidth = probe.getBoundingClientRect().width
|
|
|
|
|
const gapWidth = 48
|
2026-06-11 01:16:21 -04:00
|
|
|
const fullTabsFit = primaryWidth + tabsWidth + minSearchWidth + gapWidth <= header.clientWidth
|
|
|
|
|
const usableTabsFit = primaryWidth + minTabsWidth + minSearchWidth + gapWidth <= header.clientWidth
|
|
|
|
|
collapsed.value = !fullTabsFit && !usableTabsFit
|
2026-06-11 00:24:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function scheduleMeasure() {
|
|
|
|
|
nextTick(() => requestAnimationFrame(measure))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
scheduleMeasure()
|
|
|
|
|
resizeObserver = new ResizeObserver(scheduleMeasure)
|
|
|
|
|
if (headerRef.value) resizeObserver.observe(headerRef.value)
|
|
|
|
|
if (primaryRef.value) resizeObserver.observe(primaryRef.value)
|
|
|
|
|
if (tabsProbeRef.value) resizeObserver.observe(tabsProbeRef.value)
|
|
|
|
|
window.addEventListener('resize', scheduleMeasure)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
resizeObserver?.disconnect()
|
|
|
|
|
window.removeEventListener('resize', scheduleMeasure)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return { collapsed, measure: scheduleMeasure }
|
|
|
|
|
}
|