Node Class
The Node
class represents a single node within the builder's tree structure. Each node encapsulates its own data, manages its children, and provides methods for tree manipulation and change notification.
Properties
id: string
: A unique identifier for the node, automatically generated upon creation.data: TData
: An object containing the custom data associated with this node.TData
extendsNodeData
.parent: Node<TData> | null
: A reference to the parent node.null
if it's the root node.children: Node<TData>[]
: An array of child nodes.listeners: NodeChangeListener<TData>[]
: An internal array of change listeners.
Change Types and Details
The Node
class uses specific types to describe changes within the node tree:
ChangeType
Defines the type of change that occurred:
"appendChild"
: A child node was added."removeChild"
: A child node was removed."insertBefore"
: A new node was inserted before a reference node."replaceChild"
: An existing child node was replaced by a new one."dataChanged"
: Thedata
property of the node was modified."childOrderChanged"
: The order of child nodes changed (currently not explicitly used for notification, but part of the type)."descendantChanged"
: A change occurred in one of the node's descendants.
ChangeDetails<TData>
Provides specific details about the change:
{ type: "dataChanged"; key: keyof TData; value: TData[keyof TData] }
: FordataChanged
events, specifies the key and new value.{ type: "appendChild"; child: Node<TData> }
: ForappendChild
events, specifies the added child.{ type: "removeChild"; child: Node<TData> }
: ForremoveChild
events, specifies the removed child.{ type: "insertBefore"; newNode: Node<TData>; referenceNode: Node<TData> | null }
: ForinsertBefore
events, specifies the new node and the reference node.{ type: "replaceChild"; newNode: Node<TData>; oldNode: Node<TData> }
: ForreplaceChild
events, specifies the new and old nodes.{ type: "childOrderChanged" }
: ForchildOrderChanged
events (no specific details beyond type).{ type: "descendantChanged"; originalChangeType: ChangeType; changedNode: Node<TData>; originalDetails?: ChangeDetails<TData> }
: FordescendantChanged
events, provides the original change type, the node that actually changed, and its original details.
Methods
constructor(data: TData)
Creates a new Node
instance. The data
object is used to initialize the node's data. Any children
property within the initial data
is ignored, as children should be added using appendChild
or insertBefore
.
onChange(listener: NodeChangeListener<TData>): () => void
Registers a listener function that will be called whenever the node or its descendants change. Returns an unlisten
function to remove the listener.
_notifyChange(type: ChangeType, details?: ChangeDetails<TData>): void
(Private)
An internal method used to notify listeners of a change. It also propagates descendantChanged
events up to the parent nodes.
setData<K extends keyof TData>(key: K, value: TData[K]): void
Updates a specific key-value pair in the node's data
. Triggers a dataChanged
event.
Warning: Do not use this method to modify the children
property. Use appendChild
, removeChild
, etc., for child manipulation.
prev: Node<TData> | null
(Getter)
Returns the previous sibling node, or null
if it's the first child or has no parent.
next: Node<TData> | null
(Getter)
Returns the next sibling node, or null
if it's the last child or has no parent.
siblings: Node<TData>[]
(Getter)
Returns an array of all sibling nodes (excluding itself).
_detach(): void
(Private)
An internal method to remove the node from its current parent.
appendChild(childNode: Node<TData>): Node<TData>
Adds a childNode
to the end of the current node's children. If the childNode
already has a parent, it will be detached from its old parent first.
removeChild(childNode: Node<TData>): Node<TData> | null
Removes a childNode
from the current node's children. Returns the removed child or null
if not found.
remove(): void
Removes the current node from its parent.
insertBefore(newNode: Node<TData>, referenceNode: Node<TData> | null): Node<TData>
Inserts newNode
before referenceNode
. If referenceNode
is null
, newNode
is appended to the end. If newNode
already has a parent, it will be detached first.
replaceChild(newNode: Node<TData>, oldNode: Node<TData>): Node<TData>
Replaces oldNode
with newNode
in the current node's children. oldNode
must be a direct child.
before(newNode: Node<TData>): void
Inserts newNode
immediately before the current node in its parent's children list. Throws an error if the node has no parent.
after(newNode: Node<TData>): void
Inserts newNode
immediately after the current node in its parent's children list. Throws an error if the node has no parent.
clone(deep: boolean = false): Node<TData>
Creates a clone of the current node.
- If
deep
istrue
, it recursively clones all descendant nodes. - If
deep
isfalse
(default), only the node's data is cloned, and children are not copied.
toJSON(): SerializedNode<TData>
Serializes the node and its children into a plain JavaScript object, suitable for JSON serialization. The children
property is only included if the node has children.
Usage
import { Node } from "@keyx/builder/node"; // Note the specific import path
// Create a root node
const rootNode = new Node({ type: "document", title: "My Document" });
// Create child nodes
const paragraph1 = new Node({
type: "paragraph",
textContent: "This is the first paragraph.",
});
const paragraph2 = new Node({
type: "paragraph",
textContent: "This is the second paragraph.",
});
// Append children
rootNode.appendChild(paragraph1);
rootNode.appendChild(paragraph2);
// Listen for changes
const unlisten = rootNode.onChange((node, type, details) => {
console.log(`Node ${node.id} changed: ${type}`, details);
});
// Update data
paragraph1.setData("textContent", "Updated first paragraph.");
// Insert a new node
const heading = new Node({
type: "heading",
level: 1,
textContent: "Introduction",
});
rootNode.insertBefore(heading, paragraph1);
// Remove a node
rootNode.removeChild(paragraph2);
// Clone a node
const clonedRoot = rootNode.clone(true); // Deep clone
// Serialize to JSON
const jsonOutput = rootNode.toJSON();
console.log(JSON.stringify(jsonOutput, null, 2));
// Don't forget to unlisten when no longer needed
unlisten();