Import it from @grafloria/engine.
tsclass NodeModel extends DiagramEntity
Properties
| Name | Type | Default | Description |
|---|---|---|---|
diagram? | DiagramModel | ||
position | Point | ||
size | Size | ||
rotation | number | 0 | |
scale | Point | ||
positionMode | PositioningMode | 'absolute' | |
transformOrigin | Point | ||
zIndex? | number | C — model-level stacking order (lower renders further back). DELIBERATELY OPTIONAL, unlike the group's zIndex = 0. undefined means "this node never expressed an opinion", which is what lets {@link getEffectiveZIndex} fall through to the legacy style.zIndex the renderer already honoured, and what keeps serialize() byte-identical for every node that doesn't use it. | |
flexConfig? | FlexItemConfig | ||
gridConfig? | GridItemConfig | ||
portRenderingConfig? | any | ||
dragHandlerConfig? | any | ||
connectionGroup? | string | ||
type | string | ||
systemType? | string | ||
definitionId? | string | ||
parentId? | string | ||
children | Set<string> | ||
depth | number | 0 | |
ports | Map<string, PortModel> | ||
state | NodeState | ||
behavior | NodeBehavior | ||
style | Partial<NodeStyle> | {} | |
classes | Set<string> | ||
data | Record<string, any> | {} | |
computed | Map<string, any> |
Methods
constructor(config: { id?: string; type: string; position: Point; size?: Size; systemType?: string; definitionId?: string; })setPosition(x: number, y: number, z?: number): void— Set positionmove(dx: number, dy: number, dz?: number): void— Move by deltasetSize(width: number, height: number, depth?: number): void— Set size.
A node growing inside a flex/grid container is a layout-invalidating event: its siblings have to move.
It notifies its LAYOUT CONTAINERS, and deliberately NOT the transform chain
setPosition uses: a parent's size does not move a relative child (the child's
offset is measured from the parent ORIGIN), so emitting transform-propagated
here would be noise that says something untrue.
resize(dw: number, dh: number, dd?: number): void— Resize by deltasetRotation(degrees: number): void— Set rotationrotate(degrees: number): void— Rotate by deltasetScale(x: number, y: number): void— Set scaleaddPort(port: PortModel): void— Add portremovePort(portId: string): PortModel | undefined— Remove portgetPort(portId: string): PortModel | undefined— Get port by IDgetPorts(): PortModel[]— Get all portsgetIncomingLinks(): import('./LinkModel').LinkModel[]— Links arriving at this node (resolved through the owning diagram; empty when the node isn't attached to a diagram yet)getOutgoingLinks(): import('./LinkModel').LinkModel[]— Links leaving this node (resolved through the owning diagram)getPortsByType(type: 'input' | 'output' | 'bi'): PortModel[]— Get ports by typegetPortBySide(side: 'top' | 'right' | 'bottom' | 'left'): PortModel | undefined— Get port by side Returns the first port found on the specified sidegetPortsBySide(side: 'top' | 'right' | 'bottom' | 'left'): PortModel[]— Get all ports on a specific side Useful for nodes with multiple ports per sidegetAvailablePorts(type?: 'input' | 'output' | 'bi'): PortModel[]— Get available ports that can accept connectionsgetConnectedPorts(): PortModel[]— Get ports that have active connectionssetState(state: Partial<NodeState>): void— Set state propertyreplaceState(state: Partial<NodeState>): void— REPLACE the DOCUMENT half ofstate, keeping THIS viewer's own view half.
The write setState cannot express, and the collab reducer's write path.
Why a merge was wrong
state is a value register: the op carries the whole (projected) object the author
now holds. Applying it with the merging setState meant a peer could GAIN a key and
never LOSE one — NodeState.error, warning, status and animateStatus are all
optional, so the author clears an error badge and every other peer keeps it FOREVER,
with no later edit able to correct it. Node style had exactly this defect and was
fixed with replaceStyle; this is the same fix for the register next to it.
Why it is not a plain wholesale replace either
selected / hovered / highlighted / focused are facts about a VIEWER, not about
the document. Capture strips them (see collab/capture.ts — syncing them meant your
cursor lit up my node and your click deselected it), so an incoming register value
never carries them. Replacing wholesale would therefore BLANK the receiving user's own
selection on every remote state edit — reintroducing the very bug through the back
door. So: durable keys replaced wholesale, view keys taken from what this replica
already had.
The read-only posture, deliberately UNCHANGED
This refuses outright while the document is locked, exactly like setPosition,
setStyle and replaceStyle.
• That filter exists so a LOCAL user can still select, hover and keyboard-navigate a
presentation-mode diagram. It is about input, not about the wire.
• It would be a no-op here anyway: capture strips the view keys, so an incoming
state value contains none of the keys the filter admits — a locked replica
already dropped remote state ops entirely, before this method existed. Behaviour
is therefore identical, and setState is left untouched.
• Making state the one register that DID reach a locked replica would be
incoherent: a read-only replica currently applies no remote document write at all
(verified — a locked peer ignores remote position and style too). That gap is
real and systemic, and it belongs to the lock, not to this register.
setBehavior(behavior: Partial<NodeBehavior>): void— Set behavior propertyisSelected(): boolean— Check if node is selectedsetSelected(selected: boolean): void— Set selection stateisHighlighted(): boolean— Check if node is highlighted (attention state, independent of selection)setHighlighted(highlighted: boolean): void— Set highlight state (attention emphasis without selecting the node)isSelectable(): boolean— Check if node is selectable (based on behavior) Note: Locked nodes are still selectable so users can unlock themisDraggable(): boolean— Check if node is draggable (based on behavior and state) Note: Locked nodes cannot be draggedsetStyle(style: Partial<NodeStyle>): void— Set style propertyreplaceStyle(style: Partial<NodeStyle>): void— REPLACE the whole style object — the writesetStylecannot express.
setStyle merges, so it can add a key and overwrite a key but can never REMOVE
one. Restoring a snapshot therefore has to assign wholesale, and the obvious way to
do that — node.style = snapshot — is a plain field write that never passes
trackChange(). That funnel is what collab captures from, so the direct assignment
reaches the renderer (via markDirty) and reaches no other peer at all. That is not
hypothetical: it is exactly how an undone LINK style stayed applied on every peer
but the one that pressed Ctrl+Z. See collab/style-undo.spec.ts.
Undo paths must use this, not the field.
addClass(className: string): void— Add CSS classremoveClass(className: string): void— Remove CSS classsetClasses(classNames: string[]): void— REPLACE the whole class collection.
classes is a Set, so it needs the same treatment members and ports needed: a
register whose in-memory form is a collection and whose wire form is an array must be
REBUILT on the receiving side, never assigned. The collab reducer writes it through
here.
A non-array is REFUSED rather than read as "no classes" — logs persisted before the
funnel fix carry a bare class name (the old addClass) or a bare null (the old
removeClass), and assigning either is what corrupted the Set in the first place.
setData(key: string, value: any): void— Set data propertygetData(key: string): any— Get data propertysetComputed(key: string, value: any): void— Set computed propertygetComputed(key: string): any— Get computed propertygetWorldPosition(): Point— Get world position (absolute coordinates accounting for parent chain) For nodes without parents, this is the same as position For child nodes, this walks up the parent chain and accumulates offsetsgetBoundingBox(): BoundingBox— Get bounding box in world coordinates For child nodes, this accounts for parent positiongetCenter(): Point— Get center point in world coordinatescontainsPoint(point: Point): boolean— Check if point is inside nodeintersectsBounds(bounds: BoundingBox): boolean— Check if intersects with bounding boxsetParent(parentId: string | undefined): void— Set parentaddChild(childId: string): void— Add childremoveChild(childId: string): void— Remove childsetChildren(childIds: string[]): void— REPLACE the whole child collection. The collab reducer's write path.
Same contract as {@link setClasses}: a Set in memory, an array on the wire, rebuilt rather than assigned, and a non-array refused so a pre-fix log degrades instead of destroying the collection.
This maintains only its own half of the hierarchy, exactly as addChild/removeChild
do — the child's parentId is its own register with its own op.
setTransformOrigin(x: number, y: number): void— Set transform origingetAbsoluteTransformOrigin(): Point— Get absolute transform origin in pixelsgetLocalPosition(): Point— Get local position Returns the position property as-isgetGlobalPosition(): Point— Get global position In absolute mode: returns position as-is In relative mode: transforms position by parent's hierarchy transformsetLocalPosition(x: number, y: number, z?: number): void— Set local position Sets position directly and switches to relative modesetGlobalPosition(x: number, y: number, z?: number): void— Set global position Converts global coordinates to local if parent existsgetLocalTransformMatrix(): TransformMatrix— Get local transform matrix Composes translation, rotation, and scale relative to transform origingetGlobalTransformMatrix(): TransformMatrix— Get global transform matrix In absolute mode: returns local matrix In relative mode: composes parent's global matrix with local matrixgetGlobalBounds(): BoundingBox— Get global bounding box Calculates bounds by transforming all 4 corners through global matrixgetChildren(): NodeModel[]— Get direct children nodesgetParent(): NodeModel | undefined— Get parent node Public version of getParentNodegetAncestors(): NodeModel[]— Get all ancestor nodes up to root Returns array with direct parent first, then grandparent, etc.getDescendants(): NodeModel[]— Get all descendant nodes recursivelygetRoot(): NodeModel— Get root node of hierarchy Returns self if this is the rootgetSiblings(): NodeModel[]— Get sibling nodes (same parent, excluding self)isAncestorOf(nodeId: string): boolean— Check if this node is an ancestor of another nodegetDepth(): number— Get depth in hierarchy Root nodes have depth 0, their children have depth 1, etc.validateHierarchy(): boolean— Validate hierarchy for circular referencesupdateHierarchyDepth(): void— Update depth for this node and all descendants Recalculates depth values based on current hierarchygetAffectedByTransform(): NodeModel[]— Get all nodes affected by transform changes Returns this node plus all descendants in relative positioning modegetEffectiveZIndex(): number— The stacking index a renderer should paint by.
Precedence is explicit-model-field → legacy style.zIndex → 0. That ordering
is the whole compatibility story: diagrams that restacked via style keep
working untouched, and the moment a node states a model z-index it wins — so
setZIndex(1) on a node styled zIndex: 4 does what it says instead of
silently losing to a stylesheet.
setZIndex(z: number | undefined): void— Set the stacking index (lower renders further back). Tracked for undo/diff.bringToFront(diagram?: DiagramModel): void— Bring this node in front of every other node in the diagram. Falls back to a relative bump when the node is detached, exactly asGroupModel.bringToFrontdoes.sendToBack(diagram?: DiagramModel): void— Send this node behind every other node in the diagram.setFlexItem(config: FlexItemConfig): void— Set flexbox item configurationclearFlexItem(): void— Clear flexbox item configurationgetFlexItem(): FlexItemConfig | undefined— Get flexbox item configurationhasFlexItem(): boolean— Check if node has flex item configurationsetGridItem(config: GridItemConfig): void— Set grid item configurationclearGridItem(): void— Clear grid item configurationgetGridItem(): GridItemConfig | undefined— Get grid item configurationhasGridItem(): boolean— Check if node has grid item configurationsetPortRenderingConfig(config: any): void— Set port rendering configurationgetPortRenderingConfig(): any | undefined— Get port rendering configurationgetPortRenderingMode(): 'svg' | 'html' | 'auto'— Get port rendering mode Auto-detects based on configuration and metadatasetDragHandlerConfig(config: any): void— Set drag handler configurationgetDragHandlerConfig(): any | undefined— Get drag handler configurationisDragHandler(): boolean— Check if this node is a drag handlersetConnectionGroup(group: string): void— Set connection groupgetConnectionGroup(): string | undefined— Get connection groupserialize(): SerializedNode— Serialize to JSONstatic fromJSON(data: SerializedNode): NodeModel(static) — Deserialize from JSON
Was this page helpful?