<template> <slot /> <div ref="resizeListener" class="resize-listener" /> <svg v-bind="$attrs" v-if="validLinks"> <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, toRefs, unref } from "vue"; import LinkVue from "./Link.vue"; const props = toRefs(defineProps<{ links: Link[] }>()); const validLinks = computed(() => unref(props.links.value).filter(link => { const n = nodes.value; return ( link.startNode.id in n && link.endNode.id in n && 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 => { delete nodes.value[id]; }); function updateNodes() { if (resizeListener.value != null) { Object.keys(nodes.value).forEach(id => updateNode(id)); } } function updateNode(id: string) { if (!(id in nodes.value)) { return; } const linkStartRect = nodes.value[id].element.getBoundingClientRect(); nodes.value[id].x = linkStartRect.x + linkStartRect.width / 2 - boundingRect.value.x; nodes.value[id].y = linkStartRect.y + linkStartRect.height / 2 - boundingRect.value.y; } function updateBounds() { if (resizeListener.value != null) { boundingRect.value = resizeListener.value.getBoundingClientRect(); updateNodes(); } } const observer = new MutationObserver(updateNodes); const resizeObserver = new ResizeObserver(updateBounds); const nodes = ref<Record<string, LinkNode>>({}); 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(); }); </script> <style scoped> svg, .resize-listener { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: -10; pointer-events: none; } </style>