import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'; import { storeToRefs } from 'pinia'; import { useAppearanceSettingsStore } from '../stores'; export function useAuthenticatedLayoutResizable() { const navCollapsedPx = 64; const navDefaultPx = 240; const navMinPx = 180; const navMaxPx = 300; const asideMaxPx = 500; const appearanceStore = useAppearanceSettingsStore(); const { setAsideWidth, setNavWidth } = appearanceStore; const { asideWidth, isNavCollapsed, isSideBarTabShow, navWidth } = storeToRefs(appearanceStore); const fallbackWidth = typeof window !== 'undefined' && window.innerWidth ? window.innerWidth : 1200; const panelGroupRef = ref(null); const navPanelRef = ref(null); const asidePanelRef = ref(null); const navExpandedSize = ref(null); const groupWidth = ref(fallbackWidth); const draggingCount = ref(0); let resizeObserver = null; const getGroupWidthRaw = () => { const element = panelGroupRef.value?.$el ?? panelGroupRef.value; const width = element?.getBoundingClientRect?.().width; return Number.isFinite(width) ? width : null; }; const getGroupWidth = () => { const width = getGroupWidthRaw(); return Number.isFinite(width) && width > 0 ? width : fallbackWidth; }; const resolveDraggingPayload = (payload) => { if (typeof payload === 'boolean') { return payload; } if (payload && typeof payload === 'object') { if (typeof payload.detail === 'boolean') { return payload.detail; } if (typeof payload.dragging === 'boolean') { return payload.dragging; } } return Boolean(payload); }; const setIsDragging = (payload) => { const isDragging = resolveDraggingPayload(payload); const next = draggingCount.value + (isDragging ? 1 : -1); draggingCount.value = Math.max(0, next); }; const pxToPercent = (px, groupWidth, min = 1) => { const w = groupWidth ?? getGroupWidth(); return Math.min(100, Math.max(min, (px / w) * 100)); }; const percentToPx = (percent, groupWidth) => (percent / 100) * groupWidth; const isAsideCollapsed = (layout) => Array.isArray(layout) && layout.length >= 3 && layout[layout.length - 1] <= 1; const navCollapsedSize = computed(() => pxToPercent(navCollapsedPx, groupWidth.value) ); const navExpandedPx = computed(() => navWidth.value || navDefaultPx); const navDefaultSize = computed(() => isNavCollapsed.value ? navCollapsedSize.value : pxToPercent(navExpandedPx.value) ); const navMinSize = computed(() => isNavCollapsed.value ? navCollapsedSize.value : pxToPercent(navMinPx) ); const navMaxSize = computed(() => isNavCollapsed.value ? navCollapsedSize.value : pxToPercent(navMaxPx) ); const asideDefaultSize = computed(() => pxToPercent(asideWidth.value, undefined, 0) ); const asideMaxSize = computed(() => pxToPercent(asideMaxPx, undefined, 0)); const mainDefaultSize = computed( () => 100 - navDefaultSize.value - (isSideBarTabShow.value ? asideDefaultSize.value : 0) ); const handleLayout = (sizes) => { if (!Array.isArray(sizes) || sizes.length < 2) { return; } if (draggingCount.value === 0) { return; } const rawWidth = getGroupWidthRaw(); if (!Number.isFinite(rawWidth) || rawWidth <= 0) { return; } const width = rawWidth; const navSize = sizes[0]; if (!isNavCollapsed.value && Number.isFinite(navSize) && navSize > 0) { navExpandedSize.value = navSize; setNavWidth(Math.round(percentToPx(navSize, width))); } if (!isSideBarTabShow.value || sizes.length < 3) { return; } const asideSize = sizes[sizes.length - 1]; if (!Number.isFinite(asideSize)) { return; } if (asideSize <= 1) { setAsideWidth(0); return; } setAsideWidth(Math.round(percentToPx(asideSize, width))); }; const resizeNavPanel = (targetSize) => navPanelRef.value?.resize?.(targetSize); const resizeAsidePanel = (targetSize) => asidePanelRef.value?.resize?.(targetSize); const updateGroupWidth = () => { const width = getGroupWidth(); groupWidth.value = width; if (isNavCollapsed.value) { resizeNavPanel(navCollapsedSize.value); } else { const targetSize = pxToPercent(navExpandedPx.value, width); navExpandedSize.value = targetSize; resizeNavPanel(targetSize); } if (!isSideBarTabShow.value) { return; } const storedAsidePx = asideWidth.value; const asideTargetSize = storedAsidePx > 0 ? pxToPercent(storedAsidePx, width, 0) : 0; resizeAsidePanel(asideTargetSize); }; watch(isNavCollapsed, async (collapsed) => { await nextTick(); if (collapsed) { resizeNavPanel(navCollapsedSize.value); return; } const targetSize = navExpandedSize.value ?? pxToPercent(navExpandedPx.value); resizeNavPanel(targetSize); }); onMounted(async () => { await nextTick(); updateGroupWidth(); let panelSize = null; panelSize = navPanelRef.value?.getSize?.() ?? null; navExpandedSize.value = panelSize ?? navDefaultSize.value; if (isNavCollapsed.value) { resizeNavPanel(navCollapsedSize.value); } const element = panelGroupRef.value?.$el ?? panelGroupRef.value; if (element && typeof ResizeObserver !== 'undefined') { resizeObserver = new ResizeObserver(updateGroupWidth); resizeObserver.observe(element); } }); onUnmounted(() => { if (resizeObserver) { resizeObserver.disconnect(); resizeObserver = null; } }); return { panelGroupRef, navPanelRef, asidePanelRef, navDefaultSize, navMinSize, navMaxSize, asideDefaultSize, asideMaxSize, mainDefaultSize, handleLayout, setIsDragging, isAsideCollapsed, isNavCollapsed, isSideBarTabShow }; }