
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
node-editor
Advanced tools
A zero dependency, unopinionated node editor built as a reusable web component.
This library is distributed as an ESM module and released on npm.
npm install node-editor
While the module itself exports all of the public types if you need them, it is not strictly necessary to use the library, and the code is otherwise self-contained/-executing.
Assuming you're using a bundler or a <script type="module"> tag,
you simply need to import it.
import 'node-editor';
From there, you can create HTML tags via document.createElement()
or with plain HTML directly in an .html file; the library will
automatically upgrade any existing elements found on the page.
A note on stability:
node-editoris currently in beta stage, and thus the public API/tags might change slightly. Expect bugs (seriously, the logic is quite hairy). Use at your own risk, and please report any issues you face!
node-editor provides absolutely no "functionality", processing
engine, or built-in nodes. It merely gives you the display components
for building your own nodes and receiving information about
how the user has connected them together.
How the node system reacts to inputs is entirely up to the application
developer; node-editor makes no assumptions there - it is purely
the display component.
A very basic overview of the components that are exposed:
<node-map>
<node-editor name="unique-name" width="200">
<node-title>
This part is draggable, and moves the entire editor. Use it for node
titles, title bars, etc.
</node-title>
<node-port name="unique-name" color="red">
A port is created here. Any content is automatically resized and the
port handle is automatically placed in the vertical-middle. By
default, ports are inputs...
</node-port>
<node-port out name="unique-name2" color="blue">
... but can be specified as outputs by adding the `out` attribute.
</node-port>
</node-editor>
<!-- in/out ports must be children of their respective editors -->
<node-link
from="src-editor-name"
to="dest-editor-name"
out="src-port-name"
in="dest-port-name"
>
<!--
You MUST have a closing tag, but nothing
here will actually display. For now, please
treat it as a "reserved" space and keep it
empty, as there might be a feature added
that uses this space later on.
-->
</node-link>
</node-map>
<node-map> (NodeEditorElement -> HTMLElement)The NodeMapElement is the root element for a node editor. All other
node-editor components must lay within a <node-map> element; at best,
they will simply do nothing if they are not.
Note that coordinates in almost all cases refer to "world coordinates", or the coorindates that make up the infinite scrollable plane of the editor map itself. That is to say, zooming and panning the editor does not change the world coordinates of the elements within it.
NOTE: By design,
<node-map>elements are nestable (as in, you should be able to create a<node-map>within a<node-editor>without issue). This is largely untested, but should work. Please open any issues you have if you end up relying on this feature.
The <node-map> element creates an event boundary; all node-editor-related
events will stop bubbling once they hit their respective <node-map> element.
transform (NodeEditorTransformEvent) - an editor was moved or changed size
event.target - the <node-editor> (NodeEditorElement) elementevent.x / event.y - the new map position of the editor in world coordinatesevent.width / event.height - the new size of the editor in world coordinatesevent.didResize - true if the editor resizedevent.didMove - true if the editor changed positionposition (NodePortPositionEvent) - a port within an editor changed position
event.target - the <node-port> (NodePortElement) elementevent.x / event.y - the new position of the port in world coordinatescolor (NodePortColorEvent) - a port within an editor changed its color
event.target - the <node-port> (NodePortElement) elementevent.color - the new color, as a CSS color stringonline (NodePortOnlineEvent) - a port was created within an editor
event.target - the <node-port> (NodePortElement) elementevent.port - same as event.targetoffline (NodePortOfflineEvent) - a port was deleted from an editor
event.target - the <node-editor> (NodeEditorElement) element that held the portevent.port - the <node-port> (NodePortElement) that was deletedadd (NodeEditorAddEvent) - an editor was added to the map
event.target - the <node-editor> (NodeEditorElement) element that was addedevent.editor - same as event.targetremove (NodeEditorRemoveEvent) - an editor was removed from the map
event.target - the <node-map> (NodeMapElement) element from which the editor was removedevent.editor - the removed <node-editor> (NodeEditorElement) elementlink (NodeLinkEvent) - a <node-link> was added to the map
(note: does NOT mean a connection was made - see the connect event)
event.target - the <node-link> (NodeLinkElement) elementevent.link - same as event.targetunlink (NodeUnlinkEvent) - a <node-link> was removed from the map
event.target - the <node-map> (NodeMapElement) element from which the link was removedevent.link - the <node-link> (NodeLinkElement) that was removedunlink event will immediately be followed
by a link event. In such a case, the unlink event's event.link's attributes
will always reflect the updated values, and do not track the old attribute values
in any case. It is up to the application developer to keep track of old link
values if they need them.connect (NodeConnectEvent) - a <node-link> successfully formed a connection between two ports
event.target - the <node-link> (NodeLinkElement) elementevent.link - same as event.targetdisconnect (NodeDisconnectEvent) - a <node-link> lost its connection
event.target - the <node-map> (NodeMapElement) element from which the link was removedevent.link - the <node-link> (NodeLinkElement) that was removedevent.link's attributes will always reflect the updated values, and do not
track the old attribute values in any case. It is up to the application developer to
keep track of old link values if they need them.name (NodeNameEvent) - a <node-editor> or <node-port> changed its name attribute
event.target - the <node-editor> (NodeEditorElement) or <node-port> (NodePortElement)event.name - the new name, or null if the name attribute was removedevent.oldName - the old name, or null if the name attribute was just addedzoom (read-only) - the current zoom value (1 = 100%, lesser values = zoomed out)getEditor(name) - get an editor by its name attribute; returns null if not found::part(background) - targets the SVG background element. Use this selector
to specify e.g. cursor: grab.::part(link) - target the SVG bezier curves that constitute link lines.
Use this selector to specify e.g. cursor: finger (to indicate that the link can be
double-clicked to be removed)..dragging - added during click+drag panning event. Use this class to
specify e.g. cursor: grabbing.<node-editor> (NodeEditorElement -> HTMLElement)The <node-editor> tag is the container for individual draggable/resizable nodes.
All <node-editor> tags must be defined within a <node-map>.
The following events might be dispatched from <node-editor> elements.
See the documentation under <node-map> for what they mean. Additional
notes or exceptions are added as sub-points.
transformpositioncoloronlineofflineaddremove
event.target is the <node-element> (NodeEditorElement) that was removedevent.editor.nodeMap is still valid during the callback, and refers to the
<node-map> (NodeMapElement) from which the editor was removed.connectdisconnectnamenodeMap (read-only) - the <node-map> (NodeMapElement) that parents this element (or null
if this editor is orphaned)x/y - the top-left position of the editor in world coordinatesname - the name attribute, or null if not setwidth/height - the editor's width/height in world coordinates; retrieved either
from the width/height attributes respectively, or calculated based on the editors's
contents.The following <node-editor> attributes mirror their respective properties; see the above
section for more information.
name
<node-map>) to be functional.
A console warning will be emitted if the name collides with another editor.x / y (optional)
0width/height (optional)
width attribute; height will still
automatically resize as expected.getPort(name) - gets a <node-port> (NodePortElement) by its name attribute, or null if
no port exists by that name::part(frame) - targets the root element of the editor. Use this selector
to define the basic 'frame' shape, color, etc (e.g. background, border-radius,
box-shadow, etc.)<node-port> (NodePortElement -> HTMLElement)The <node-port> is a container element that creates either an input or an output port
on a node editor. Ports can be connected to one another to indicate "links", typically
indicating some relationship or flowing of data between two points in the node system.
Since node-editor is largely unopinionated, it is up to the application developer
what a port or a link actually represents - only the following is enforced by this
library:
The following are expressly not enforced (and thus would have to be implemented by the application if it so chooses):
color attribute.Port "handles" (the visible element where link lines connect to) are by default
colored circles, but can be overridden and thus customized by adding elements
to the handle slot (see Slots below).
The following events might be dispatched from <node-port> elements.
See the documentation under <node-map> for what they mean. Additional
notes or exceptions are added as sub-points.
positioncoloronlineoffline
event.target is the <node-port> (NodePortElement) that was removedevent.target.nodeEditor is still valid during the callback, and refers to the
<node-editor> (NodeEditorElement) from which the port was removed.connect
<node-link> itself.disconnect
<node-link> itself.nameNOTE: The way that the
connectanddisconnectevents are dispatched may change prior to the v1 release.
numConnections (read-only) - the current number of connected links to this portconnections (read-only) - an array of connected links to this port (in no particular
order; could be random from reference to reference!)name - the name attribute, or null if not setnodeEditor (read-only) - the <node-editor> (NodeEditorElement) that parents
this port, or null if this port is an orphancolor - the CSS color string
of the port; affects the link line color, which will gradient if the two ports have
different colors. Pulls first from the color attribute if specified, or otherwise
returns the internally defined default color - always returning a string.isOutputPort - Whether or not the port is an output port. Returns true if out exists
as an attribute on the element (the value is ignored). Assigning true to this property
adds the out attribute; assigning false removes it.handleX / handleY (read-only) - The position of the port's handle in world coordinatesNOTE: You can get the
NodeMapEditorreference for a particular port via the.nodeEditor?.nodeMapproperty.
The following <node-port> attributes mirror their respective properties; see the above
section for more information.
name
<node-editor>) to be functional.
A console warning will be emitted if the name collides with another port.out
<node-port> as an output port.in attribute, but you're welcome to specify one if it helps.
A lack of out attribute indicates an input port.colorslot="handle" - places the element within the port handle element, replacing
the default port handle. The handle is positioned squarely over the handle 'hotspot'
location, so that the center of the content is the hotspot point. Note that
CSS transform: translate(...) can be used to offset the visual position of the
port handle without affecting the functional positioning of the handle itself.var(--port-color) - useful for custom handles (via slot="handle")
to be reusable but also respond to the color attribute.The following are not unique or specific to the library and could be achieved anyway using normal CSS, but are worthwhile to mention as more of a 'cookbook'.
[out] - selects all output ports. Useful for applying e.g.
text-align: right[connections] - selects all connected ports. Useful for applying
e.g. visibility: hidden. Especially useful when composed as
node-port[connections]:not([out]) > *:not([slot='handle']), which only targets
input node ports that should be hidden if they have connections, but that
don't also hide custom port handles (if you use them).
connections attribute only exists
if at least once connection exists (i.e. there is never a connections="0"
attribute).<node-title> (NodeTitleElement -> HTMLElement)The <node-title> element is a container element that does nothing other than to
mark an area within a <node-editor> as the repositioning handle.
The area making up a <node-title> can be clicked+dragged to move the parent
editor element.
If the <node-title> is not a child of a <node-editor>, it does nothing.
A <node-editor> may have multiple <node-title> elements.
NOTE: This element may be expanded and thus renamed prior to the v1 release.
nodeEditor (read-only) - the <node-editor> (NodeEditorElement) that parents
this title<node-link> (NodeLinkElement -> HTMLElement)A <node-link> is a nuclear (non-container) element that represents
a connection between an output port and an input <node-port> of two separate
<node-editor> elements.
NOTE: Despite not being a container element, the Web Components standard still mandates that it must have a full closing tag (i.e.
<node-link ...></node_link>) and thus cannot be self-closing (e.g.<node-link ... />is, unfortunately, not allowed). The latter will cause all of your links to become nested within each other.
NOTE: Right now, the contents (child nodes) of
<node-link>elements are simply ignored and do not display anything. However, this may change before the v1 release. Please treat the inner contents of<node-link>s as 'reserved', as adding any content there might cause unexpected results when upgrading in the future.
Links can exist without all of their attributes present, or with attributes that refer to editor(s) and/or port(s) that do not exist. They are not automatically removed, but instead have a secondary state indicating whether or not they are "connected".
Links that are missing any one of the four required attributes (from, to, in
and out), or in the case where any one of those attributes refers to a missing
editor or port name, remain in the disconnected state and do not result in
a link to show up in the editor.
In the event a link is disconnected and a port/editor is either created or has its
name changed thus that the four required attributes become collectively 'valid',
then the link enters the connected state whereby the connect event is dispatched
and a visual line is formed between the two referenced ports. This also activates
the fromPort and toPort properties, which hold live references to the connected
<node-port> (NodePortElement) elements.
<node-link> elements present in the DOM but that are otherwise invalid (disconnected)
are never automatically removed from the DOM, either at load or during operation.
Connected links between two ports of differing colors result in a color gradient between the two.
Double clicking a connected link line removes it, including from the DOM. This
is the only built-in means by which a <node-link> can be removed from the DOM.
Of course, application developers are free to remove such elements themselves;
the links will be destroyed as one would expect.
The following events might be dispatched from <node-link> elements.
See the documentation under <node-map> for what they mean. Additional
notes or exceptions are added as sub-points.
linkunlinkconnectdisconnect
event.target - the <node-link> that was removedevent.target.nodeMap is still valid during this callbacknodeMap (read-only) - the <node-map> (NodeMapElement) that parents
this link (or null if this link is an orphan)fromName / toName - the string names of the <node-editor> elements
this link pairs, retrieving directly from the from and to attributes
respectively, or returning null if those attributes are not presentoutName / inName - the string names of the <node-port> elements
this link pairs, retrieving directly from the out and in attributes
respectively, or returning null if those attributes are not presentoutPort / inPort (read-only) - references to the connected
<node-port> (NodePortElement) elements, or null if the link is
not connectedThe following <node-link> attributes mirror their respective properties; see the above
section for more information.
from (mirrors the fromName property)to (mirrors the toName property)out (mirrors the outName property)in (mirrors the inName property)Note that all four of these attributes must be specified in order for
a <node-link> to be eligible for connection, and a connection is only
created if all four refer to valid editor/port names.
Some questions that aren't so much "frequently" asked, but might come up as you're using the library.
link/unlink and connect/disconnect events?One of the "unopinionated" design aspects of the library is that it doesn't
immediately remove any <node-link> elements that might have existed
prior to the library being loaded and the custom components being registered.
Thus, a <node-link> element can exist/be added to a <node-map> that
refers to non-existant editors/ports, which immediately fires off a link
event.
It's not until the editors/ports referred to by the <node-link> element
come into existence that the connect event is fired.
In most cases, you will only care about the connect and disconnect
events.
This may change prior to the v1 release - the usefulness of this approach has yet to be determined. The motivation was "be as un-destructive as possible" to the data you feed the components.
node-editor support?Technically this library works as far back as Chrome 81, but
will leak events and memory all over the place. You need at least
Chrome 88 for this to work properly. This is due to the extensive
use of AbortController in addEventListener()s, which was
added in Chrome 88.
All other evergreen browsers released around the same time as Chrome 88 should work. If you'd like to contribute a formal table of browser support, a PR is welcome!
<foreignObject>?No. Foreign objects had a lot of quirks that I didn't particularly care for, so instead the component uses two layers - an SVG background for the dots pattern and links' bezier curves, and an upper plain HTML layer that holds the editor nodes themselves.
Both layers have their viewports synchronized/scaled in tandem, which
achieves the same effect as a <foreignObject> but without all of
the limitations/quirks.
It depends. I'll try to give you an idea of what is considered in/out of scope for this library (though it never hurts to open an issue anyway).
Things that are within the scope of node-editor:
Things that are outside the scope of node-editor:
Even if you're unsure, open an issue anyway and ask! Just don't be upset
if it's considered outside the scope of the library (node-editor is
intentionally very unopinionated about how editors look or behave).
(dis)connectedCallback()/attributeChangedCallback() manually?Please don't. These are intended to be called directly by the Web Components system in browsers and thus expect to actually be called in response to some lifecycle event. While all internal ("private") methods are hidden away from the public API using symbols, these callbacks couldn't be. This doesn't mean they should be called by user code.
NodeXyzElement.observedAttributes?Nothing's stopping you, it has to exist for the web components to work correctly, and referring to them is guaranteed not to have any side effects.
Copyright © 2022 by Josh Junon. Released under the MIT License.
FAQs
Generic node editor web components
The npm package node-editor receives a total of 10 weekly downloads. As such, node-editor popularity was classified as not popular.
We found that node-editor demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.