UNPKG

@cnvx/nodal

Version:

A Svelte 5 library for creating interactive node diagrams with customizable connections and layouts

122 lines (121 loc) 4.5 kB
import { mount } from "svelte"; import { SvelteMap } from "svelte/reactivity"; import { writable } from "svelte/store"; import Surface from "./Surface.svelte"; let elementIdCounter = 0; function getElementId(el) { if (el.id) return `#${el.id}`; if (!el.dataset.nodalUid) { el.dataset.nodalUid = `nodal-${elementIdCounter++}`; } return `[data-nodal-uid="${el.dataset.nodalUid}"]`; } function getEdgeEndpointKey(endpoint) { if (typeof endpoint === "string") return endpoint; if (endpoint instanceof HTMLElement) return getElementId(endpoint); return endpoint.map(e => typeof e === "string" ? e : getElementId(e)).join(","); } function drawCall(surface) { // console.debug("Drawing nodal surface edges..."); const connections = surface.querySelectorAll("[data-nodal-connected='true']"); connections.forEach((conn) => { // console.debug("Processing connection for:", conn); const host = conn; if (!conn._nodalEdgeConnections || conn._nodalEdgeConnections.length === 0) { // console.debug("No edges found for host:", host); return; } conn._nodalEdgeConnections.forEach((edge) => { let edgeDef; if (typeof edge == "string") { edgeDef = { source: host, target: edge, }; } else { edgeDef = { source: "source" in edge ? edge.source : host, target: "target" in edge ? edge.target : host, ...edge, }; } const sourceKey = getEdgeEndpointKey(edgeDef.source); const targetKey = getEdgeEndpointKey(edgeDef.target); surface._nodalSurface.internals.edges.set(`${sourceKey}->${targetKey}`, edgeDef); }); }); } export function createNodal({ svgAttributes = {}, getNodeAnchor, fadeInDuration, onMount, } = {}) { return (element) => { console.debug("Creating nodal sruface.."); if (!Object.hasOwn(element, "_nodalSurface")) { const edges = new SvelteMap(); const exports = mount(Surface, { target: element, props: { hostElement: element, edges, width: writable(element.clientWidth), height: writable(element.clientHeight), svgAttributes, getNodeAnchor, fadeInDuration, }, }); const engine = { _isScheduled: false, draw: () => drawCall(element), schedule() { if (engine._isScheduled) return; engine._isScheduled = true; requestAnimationFrame(() => { this._isScheduled = false; drawCall(element); }); }, internals: exports }; Object.defineProperty(element, "_nodalSurface", { value: engine, }); const resizeObserver = new ResizeObserver((entries) => { requestAnimationFrame(() => { for (let entry of entries) { const { width, height } = entry.contentRect; // console.debug( // "Resizing nodal surface to:", // width, // height, // ); exports.width?.set(width); exports.height?.set(height); // redraw edges // drawCall(element as NodalSurfaceElement); engine.schedule(); } }); }); try { onMount?.(engine); } catch (e) { console.error("Error in onMount callback for nodal surface:", e); } resizeObserver.observe(element); engine.schedule(); // drawCall(element as NodalSurfaceElement); } }; } export function connectTo(...edges) { return (host) => { host.setAttribute("data-nodal-connected", "true"); host._nodalEdgeConnections = edges; }; }