Skip to content

A pure TypeScript visual node connector for creating draggable connections between nodes. Framework-agnostic and easy to use

Notifications You must be signed in to change notification settings

Tem-man/power-link

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

20 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

power-link

npm version license

A pure TypeScript visual node connector for creating draggable connections between nodes. Framework-agnostic and easy to use.

Node Link Connector Demo

๐Ÿ“น Demo Video

Watch the demo video to see power-link in action! Download video

โœจ Features

  • ๐ŸŽฏ Visual Node Connections - Create beautiful bezier curve connections between nodes
  • ๐Ÿ–ฑ๏ธ Drag & Drop - Intuitive drag-and-drop connection creation
  • ๐Ÿ”„ Node Dragging - Move nodes around with automatic connection updates
  • ๐Ÿงฒ Smart Snapping - Automatic connection point detection and snapping
  • ๐ŸŽจ Customizable - Fully configurable colors, sizes, and behaviors
  • ๐Ÿšซ Delete Connections - Hover over connections to show delete button
  • ๐Ÿ“ฆ Zero Dependencies - Pure JavaScript, no framework required
  • ๐ŸŽญ Multiple Connection Points - Support for left and right connection dots
  • ๐Ÿ”Œ Event Callbacks - Listen to connection and disconnection events

๐Ÿ“ฆ Installation

npm install power-link

Or using yarn:

yarn add power-link

Or using pnpm:

pnpm add power-link

๐Ÿš€ Quick Start

Basic Usage

import Connector from "power-link";

// 1. Get container element
const container = document.getElementById("connector-container");

// 2. Create connector instance
const connector = new Connector({
  container: container,

  // Optional configuration
  lineColor: "#155BD4",
  lineWidth: 2,
  dotSize: 12,
  dotColor: "#155BD4",

  // Event callbacks
  onConnect: (connection) => {
    console.log("Connection created:", connection);
    // connection: { from: 'node1', to: 'node2', fromDot: 'right', toDot: 'left' }
  },

  onDisconnect: (connection) => {
    console.log("Connection removed:", connection);
  },

  onViewChange: (viewState) => {
    console.log("View changed:", viewState);
    // viewState: { scale: 1, translateX: 0, translateY: 0 }
    // Save view state to restore later
  }
});

// 3. Register nodes
const node1 = document.getElementById("node1");
const node2 = document.getElementById("node2");

connector.registerNode("node1", node1, {
  dotPositions: ["right"] // Only right connection dot
});

connector.registerNode("node2", node2, {
  dotPositions: ["left", "right"] // Both left and right dots
});

HTML Structure

<div
  id="connector-container"
  style="position: relative; height: 600px;"
>
  <div
    id="node1"
    style="position: absolute; left: 100px; top: 100px;"
  >
    Node 1
  </div>
  <div
    id="node2"
    style="position: absolute; left: 400px; top: 100px;"
  >
    Node 2
  </div>
</div>

๐Ÿ“– API Documentation

Constructor Options

Option Type Default Description
container HTMLElement Required Container element for the connector
lineColor String '#155BD4' Color of connection lines
lineWidth Number 2 Width of connection lines
dotSize Number 12 Size of connection dots
dotColor String '#155BD4' Color of connection dots
dotHoverScale Number 1.8 Scale factor when hovering over connection dots
deleteButtonSize Number 20 Size of delete button
enableNodeDrag Boolean true Enable node dragging
enableSnap Boolean true Enable connection snapping
snapDistance Number 20 Snap distance in pixels
enableZoom Boolean true Enable zoom functionality
enablePan Boolean true Enable pan functionality
minZoom Number 0.1 Minimum zoom level (10%)
maxZoom Number 4 Maximum zoom level (400%)
zoomStep Number 0.1 Zoom step size (10%)
onConnect Function () => {} Callback when connection is created
onDisconnect Function () => {} Callback when connection is removed
onViewChange Function () => {} Callback when view state changes (zoom/pan)

Methods

registerNode(id, element, options)

Register a node for connection.

Parameters:

  • id (String): Unique identifier for the node
  • element (HTMLElement): DOM element of the node
  • options (Object): Node configuration
    • dotPositions (String | Array): Connection dot positions
      • 'both': Both left and right dots
      • ['left', 'right']: Array format, both sides
      • ['left']: Only left dot
      • ['right']: Only right dot
    • info (Object): Node extraneous information

Returns: Node object

Example:

connector.registerNode("myNode", element, {
  dotPositions: ["right"],
  info: {
    id: "123",
    name: "apple",
    desc: "this is a red apple"
  }
});

createConnection(fromNodeId, toNodeId, fromDot, toDot, options)

Programmatically create a connection between nodes.

Parameters:

  • fromNodeId (String): Source node ID
  • toNodeId (String): Target node ID
  • fromDot (String): Source connection dot position - 'left' or 'right' (optional)
  • toDot (String): Target connection dot position - 'left' or 'right' (optional)
  • options (Object): Configuration options (optional)
    • silent (boolean): Whether to create silently (without triggering callbacks)

Returns: Connection object or undefined

Example:

// Create connection with callbacks
connector.createConnection("node1", "node2");

// Create connection with specific dot positions
connector.createConnection("node1", "node2", "right", "left");

// Create connection silently without triggering callbacks
connector.createConnection("node1", "node2", "right", "left", { silent: true });

disconnect(connectionId๏ผŒoptions)

Remove a connection.

Parameters:

  • connectionId (String): Connection ID (optional, if not provided, removes all connections)
  • options (Object): Configuration options (optional)
    • silent (boolean): Whether to disconnect silently (without triggering callbacks)

Example:

connector.disconnect(); // Remove all connections
connector.disconnect("connection-id"); // Remove specific connection
connector.disconnect("connection-id", { silent: true }); // Remove connection silently without triggering callbacks

getConnections()

Get all connections.

Returns: Array of connection information

Example:

const connections = connector.getConnections();
// [{ id: '...', from: 'node1', to: 'node2', fromDot: 'right', toDot: 'left' }]

getNodeConnections(nodeId)

Get all connections for a specific node.

Parameters:

  • nodeId (String): Node ID

Returns: Array of connection information

updateNodePosition(nodeId)

Update node position (called when node is moved).

Parameters:

  • nodeId (String): Node ID

destroy(options)

Destroy the connector and clean up all resources.

Parameters:

  • options (Object): Configuration options (optional)
    • silent (boolean): Whether to destroy silently (without triggering callbacks)

Example:

connector.destroy(); // Destroy silently by default (without triggering callbacks)
connector.destroy({ silent: false }); // Destroy non-silently (triggering callbacks)

setViewState(state)

Set the view state (for initialization or restoring view).

Parameters:

  • state (Object): View state object
    • scale (Number): View scale (optional)
    • translateX (Number): X-axis translation (optional)
    • translateY (Number): Y-axis translation (optional)

Example:

connector.setViewState({
  scale: 0.8,
  translateX: 50,
  translateY: 30
});

getViewState()

Get the current view state.

Returns: ViewState object with scale, translateX, and translateY properties

Example:

const viewState = connector.getViewState();
console.log(viewState); // { scale: 1, translateX: 0, translateY: 0 }

setZoom(scale)

Set the zoom level (centered on canvas).

Parameters:

  • scale (Number): Zoom scale (will be clamped to minZoom and maxZoom)

Example:

connector.setZoom(1.5); // Zoom to 150%

getZoom()

Get the current zoom level.

Returns: Number - Current zoom scale

Example:

const currentZoom = connector.getZoom();
console.log(currentZoom); // 1.0

zoomIn()

Zoom in by one step.

Example:

connector.zoomIn(); // Increase zoom by zoomStep

zoomOut()

Zoom out by one step.

Example:

connector.zoomOut(); // Decrease zoom by zoomStep

resetView()

Reset the view to default state (scale: 1, translateX: 0, translateY: 0).

Example:

connector.resetView(); // Reset to default view

updateAllConnections()

Update all connection line positions (useful when container size changes or after manual node position updates).

Example:

connector.updateAllConnections(); // Refresh all connection lines

๐ŸŽจ Usage Examples

Vue 3

<template>
  <div
    class="container"
    ref="containerRef"
  >
    <div
      class="node"
      ref="node1Ref"
    >
      Node 1
    </div>
    <div
      class="node"
      ref="node2Ref"
    >
      Node 2
    </div>
  </div>
</template>

<script setup>
  import { ref, onMounted, onBeforeUnmount } from "vue";
  import Connector from "power-link";

  const containerRef = ref(null);
  const node1Ref = ref(null);
  const node2Ref = ref(null);

  let connector = null;

  onMounted(() => {
    connector = new Connector({
      container: containerRef.value,
      onConnect: (connection) => {
        console.log("Connection created:", connection);
      },
      onDisconnect: (connection) => {
        console.log("Connection removed:", connection);
      }
    });

    connector.registerNode("node1", node1Ref.value, {
      dotPositions: ["right"],
      info: {
        id: "123",
        name: "apple",
        desc: "this is a red apple"
      }
    });

    connector.registerNode("node2", node2Ref.value, {
      dotPositions: ["left"],
      info: {
        id: "456",
        name: "pear",
        desc: "this is a yellow pear"
      }
    });
  });

  onBeforeUnmount(() => {
    if (connector) {
      connector.destroy();
    }
  });
</script>

<style scoped>
  .container {
    position: relative;
    height: 600px;
    background: #f5f5f5;
  }

  .node {
    position: absolute;
    padding: 20px;
    background: white;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    cursor: move;
  }
</style>

React

import { useEffect, useRef } from "react";
import Connector from "power-link";

function App() {
  const containerRef = useRef(null);
  const node1Ref = useRef(null);
  const node2Ref = useRef(null);
  const connectorRef = useRef(null);

  useEffect(() => {
    if (!containerRef.current) return;

    connectorRef.current = new Connector({
      container: containerRef.current,
      onConnect: (connection) => {
        console.log("Connection created:", connection);
      },
      onDisconnect: (connection) => {
        console.log("Connection removed:", connection);
      }
    });

    connectorRef.current.registerNode("node1", node1Ref.current, {
      dotPositions: ["right"],
      info: {
        id: "123",
        name: "apple",
        desc: "this is a red apple"
      }
    });

    connectorRef.current.registerNode("node2", node2Ref.current, {
      dotPositions: ["left"],
      info: {
        id: "456",
        name: "pear",
        desc: "this is a yellow pear"
      }
    });

    return () => {
      if (connectorRef.current) {
        connectorRef.current.destroy();
      }
    };
  }, []);

  return (
    <div
      ref={containerRef}
      style={{ position: "relative", height: "600px" }}
    >
      <div
        ref={node1Ref}
        style={{ position: "absolute", left: "100px", top: "100px" }}
      >
        Node 1
      </div>
      <div
        ref={node2Ref}
        style={{ position: "absolute", left: "400px", top: "100px" }}
      >
        Node 2
      </div>
    </div>
  );
}

Vanilla JavaScript

<!DOCTYPE html>
<html>
  <head>
    <style>
      #container {
        position: relative;
        height: 600px;
        background: #f5f5f5;
      }
      .node {
        position: absolute;
        padding: 20px;
        background: white;
        border-radius: 8px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        cursor: move;
      }
    </style>
  </head>
  <body>
    <div id="container">
      <div
        id="node1"
        class="node"
        style="left: 100px; top: 100px;"
      >
        Node 1
      </div>
      <div
        id="node2"
        class="node"
        style="left: 400px; top: 100px;"
      >
        Node 2
      </div>
    </div>

    <script type="module">
      import Connector from "power-link";

      const connector = new Connector({
        container: document.getElementById("container"),
        onConnect: (connection) => {
          console.log("Connection created:", connection);
        }
      });

      connector.registerNode("node1", document.getElementById("node1"), {
        dotPositions: ["right"],
        info: {
          id: "123",
          name: "apple",
          desc: "this is a red apple"
        }
      });

      connector.registerNode("node2", document.getElementById("node2"), {
        dotPositions: ["left"],
        info: {
          id: "456",
          name: "pear",
          desc: "this is a yellow pear"
        }
      });
    </script>
  </body>
</html>

๐ŸŽฏ Advanced Features

Multiple Connection Points

// Node with both left and right connection points
connector.registerNode("centerNode", element, {
  dotPositions: ["left", "right"]
});

// Node with only left connection point
connector.registerNode("endNode", element, {
  dotPositions: ["left"],
  info: {
    id: "456",
    name: "pear",
    desc: "this is a yellow pear"
  }
});

// Node with only right connection point
connector.registerNode("startNode", element, {
  dotPositions: ["right"]
});

Silent Operations (No Callbacks)

Sometimes you may want to perform operations without triggering callbacks, such as when initializing connections from saved data or bulk operations.

// Silent connection creation (won't trigger onConnect callback)
connector.createConnection("node1", "node2", "right", "left", { silent: true });

// Silent disconnection (won't trigger onDisconnect callback)
connector.disconnect("connection-id", { silent: true });

// Silent destroy (won't trigger callbacks, default behavior)
connector.destroy(); // Default is silent
connector.destroy({ silent: false }); // Non-silent destroy (triggers callbacks)

// Example: Restore connections from saved data without triggering callbacks
const savedConnections = [
  { from: "node1", to: "node2", fromDot: "right", toDot: "left" },
  { from: "node2", to: "node3", fromDot: "right", toDot: "left" }
];

savedConnections.forEach((conn) => {
  connector.createConnection(
    conn.from,
    conn.to,
    conn.fromDot,
    conn.toDot,
    { silent: true }
  );
});

Node Info and Connection Data

You can attach custom information to nodes using the info parameter, which will be available in connection callbacks.

// Register node with custom info
const items = [
  { id: "node1", name: "Apple", desc: "This is a red apple", type: "fruit" },
  { id: "node2", name: "Pear", desc: "This is a yellow pear", type: "fruit" }
];

items.forEach((item) => {
  const nodeElement = document.getElementById(item.id);
  connector.registerNode(item.id, nodeElement, {
    dotPositions: ["left"],
    info: item // Attach custom info to the node
  });
});

// Access node info in connection callbacks
const connector = new Connector({
  container: container,
  onConnect: async (connection) => {
    console.log("Connection created:", connection);
    console.log("From node info:", connection.fromInfo);
    // { id: "node1", name: "Apple", desc: "This is a red apple", type: "fruit" }
    console.log("To node info:", connection.toInfo);
    // { id: "node2", name: "Pear", desc: "This is a yellow pear", type: "fruit" }

    // You can use the info for saving to database, validation, etc.
    await saveConnection({
      from: connection.from,
      to: connection.to,
      fromInfo: connection.fromInfo,
      toInfo: connection.toInfo
    });
  },

  onDisconnect: (connection) => {
    console.log("Connection removed:", connection);
    console.log("From node info:", connection.fromInfo);
    console.log("To node info:", connection.toInfo);
  }
});

Custom Styling

const connector = new Connector({
  container: container,
  lineColor: "#FF6B6B", // Red connections
  lineWidth: 3, // Thicker lines
  dotSize: 16, // Larger dots
  dotColor: "#4ECDC4", // Teal dots
  deleteButtonSize: 24 // Larger delete button
});

Event Handling

const connector = new Connector({
  container: container,

  onConnect: (connection) => {
    console.log("New connection:", connection);
    // { from: 'node1', to: 'node2', fromDot: 'right', toDot: 'left' }

    // Save to database, update state, etc.
    saveConnection(connection);
  },

  onDisconnect: (connection) => {
    console.log("Connection removed:", connection);

    // Update database, state, etc.
    removeConnection(connection);
  },

  onViewChange: (viewState) => {
    console.log("View changed:", viewState);
    // { scale: 1, translateX: 0, translateY: 0 }

    // Save view state to restore later
    saveViewState(viewState);
  }
});

View Management

// Get current view state
const viewState = connector.getViewState();
console.log(viewState); // { scale: 1, translateX: 0, translateY: 0 }

// Set view state (restore saved view)
connector.setViewState({
  scale: 0.8,
  translateX: 100,
  translateY: 50
});

// Zoom controls
connector.setZoom(1.5); // Set zoom to 150%
connector.zoomIn(); // Zoom in by one step
connector.zoomOut(); // Zoom out by one step
const currentZoom = connector.getZoom(); // Get current zoom

// Reset view
connector.resetView(); // Reset to default (scale: 1, translateX: 0, translateY: 0)

// Update all connections (useful after manual node position changes)
connector.updateAllConnections();

๐Ÿ”ง Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

๐Ÿ“ License

MIT License

๐Ÿค Contributing

Contributions, issues and feature requests are welcome!

๐Ÿ“ฎ Support

If you have any questions or need help, please open an issue on GitHub.

๐ŸŒŸ Show Your Support

Give a โญ๏ธ if this project helped you!


Made with โค๏ธ by the power-link team

About

A pure TypeScript visual node connector for creating draggable connections between nodes. Framework-agnostic and easy to use

Resources

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors