import { computed, shallowRef, watch, h, type VNode, ref } from 'vue' import { useRoute, useRouter, loadRouteLocation, type RouteLocationNormalizedLoaded, type RouteLocationRaw } from 'vue-router' import router from '@/router' // this is adapted from vue-router // https://github.com/vuejs/vue-router-next/blob/798cab0d1e21f9b4d45a2bd12b840d2c7415f38a/src/RouterView.ts#L125 function getRouteProps(route: RouteLocationNormalizedLoaded) { const routePropsOption = route.matched[0]?.props.default return routePropsOption ? routePropsOption === true ? route.params : typeof routePropsOption === 'function' ? routePropsOption(route) : routePropsOption : {} } function resolveAndLoadRoute(route: RouteLocationRaw) { return loadRouteLocation(router.resolve(route)) } export function useRouteWithModal() { const router = useRouter() const route = useRoute() const historyStateBackdropRoutePath = computed(() => { // every time the fullPath changes we check the history state // this happens also initially return route.fullPath ? window.history.state?.backdropRoutePath : undefined }) const routerIsReady = ref(false) router.isReady().then(() => { routerIsReady.value = true }) const hasModal = ref(false) const baseRoute = shallowRef() watch( [routerIsReady, historyStateBackdropRoutePath], async () => { if (routerIsReady.value === false || !route.fullPath) { // wait until we can work with routes hasModal.value = false return } if (historyStateBackdropRoutePath.value === undefined) { if (route.meta?.showAsModal !== true) { hasModal.value = false baseRoute.value = route return } // TODO: maybe load parent route here in the future, // via: route.matched[route.matched.length - 2] // see: https://router.vuejs.org/guide/migration/#removal-of-parent-from-route-locations hasModal.value = true baseRoute.value = await resolveAndLoadRoute({ name: 'home' }) return } // we get the resolved route from the fullpath // and wait for the route component to be loaded before we assign it hasModal.value = true baseRoute.value = await resolveAndLoadRoute(historyStateBackdropRoutePath.value) }, {immediate: true}, ) const backdropRoute = computed(() => route.fullPath !== baseRoute.value?.fullPath ? baseRoute.value : undefined) const modalRoute = shallowRef() watch( () => [hasModal.value, backdropRoute.value, route], () => { if (hasModal.value === false) { modalRoute.value = undefined return } const props = getRouteProps(route) props.isModal = true props.backdropRoutePath = backdropRoute.value?.fullPath props.onClose = closeModal const component = route.matched[0]?.components?.default if (!component) { modalRoute.value = undefined return } modalRoute.value = h(component, props) }, {immediate: true}, ) async function closeModal() { await router.isReady() if (backdropRoute.value !== undefined) { // TODO: Dialog modals might want to replace the route here via router.replace() return router.push(backdropRoute.value) } if (window.history.state === undefined) { return router.push({ name: 'home' }) } else { router.back() // this should never happen throw new Error('') } } return { baseRoute, modalRoute, closeModal, } }