<template> <slot /> <div ref="resizeListener" class="resize-listener" /> <svg v-if="validLinks" v-bind="$attrs"> <LinkVue v-for="(link, index) in validLinks" :key="index" :link="link" :startNode="nodes[link.startNode.id]!" :endNode="nodes[link.endNode.id]!" /> </svg> </template> <script setup lang="ts"> import { Link, LinkNode, RegisterLinkNodeInjectionKey, UnregisterLinkNodeInjectionKey } from "@/features/links"; import { computed, nextTick, onMounted, provide, ref, toRef } from "vue"; import LinkVue from "./Link.vue"; const _props = defineProps<{ links?: Link[] }>(); const links = toRef(_props, "links"); const observer = new MutationObserver(updateNodes); const resizeObserver = new ResizeObserver(updateBounds); const nodes = ref<Record<string, LinkNode | undefined>>({}); const boundingRect = ref(new DOMRect()); const resizeListener = ref<Element | null>(null); onMounted(() => { // ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element const resListener = resizeListener.value; if (resListener != null) { resizeObserver.observe(resListener); } updateNodes(); }); const validLinks = computed( () => links.value?.filter(link => { const n = nodes.value; return ( n[link.startNode.id]?.x != undefined && n[link.startNode.id]?.y != undefined && n[link.endNode.id]?.x != undefined && n[link.endNode.id]?.y != undefined ); }) ?? [] ); const observerOptions = { attributes: true, childList: true, subtree: true }; provide(RegisterLinkNodeInjectionKey, (id, element) => { nodes.value[id] = { element }; observer.observe(element, observerOptions); nextTick(() => { if (resizeListener.value != null) { updateNode(id); } }); }); provide(UnregisterLinkNodeInjectionKey, id => { nodes.value[id] = undefined; }); function updateNodes() { if (resizeListener.value != null) { Object.keys(nodes.value).forEach(id => updateNode(id)); } } function updateNode(id: string) { const node = nodes.value[id]; if (!node) { return; } const linkStartRect = node.element.getBoundingClientRect(); node.x = linkStartRect.x + linkStartRect.width / 2 - boundingRect.value.x; node.y = linkStartRect.y + linkStartRect.height / 2 - boundingRect.value.y; } function updateBounds() { if (resizeListener.value != null) { boundingRect.value = resizeListener.value.getBoundingClientRect(); updateNodes(); } } </script> <style scoped> svg, .resize-listener { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: -10; pointer-events: none; } </style>