ngx-workflow 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -4552,28 +4552,6 @@ class DiagramComponent {
|
|
|
4552
4552
|
event.preventDefault(); // Prevent browser redo
|
|
4553
4553
|
this.diagramStateService.redo();
|
|
4554
4554
|
}
|
|
4555
|
-
onCopyKeyPress(event) {
|
|
4556
|
-
// Don't prevent default if user is typing in an input
|
|
4557
|
-
if (this.isInputActive(event))
|
|
4558
|
-
return;
|
|
4559
|
-
this.diagramStateService.copy();
|
|
4560
|
-
}
|
|
4561
|
-
onPasteKeyPress(event) {
|
|
4562
|
-
if (this.isInputActive(event))
|
|
4563
|
-
return;
|
|
4564
|
-
this.diagramStateService.paste();
|
|
4565
|
-
}
|
|
4566
|
-
onCutKeyPress(event) {
|
|
4567
|
-
if (this.isInputActive(event))
|
|
4568
|
-
return;
|
|
4569
|
-
this.diagramStateService.cut();
|
|
4570
|
-
}
|
|
4571
|
-
onDuplicateKeyPress(event) {
|
|
4572
|
-
if (this.isInputActive(event))
|
|
4573
|
-
return;
|
|
4574
|
-
event.preventDefault(); // Prevent browser bookmark
|
|
4575
|
-
this.diagramStateService.duplicate();
|
|
4576
|
-
}
|
|
4577
4555
|
// Duplicate Group/Ungroup listeners removed (superseded by new listeners below)
|
|
4578
4556
|
onBringToFrontKeyPress(event) {
|
|
4579
4557
|
if (this.isInputActive(event))
|
|
@@ -6439,7 +6417,7 @@ class DiagramComponent {
|
|
|
6439
6417
|
}
|
|
6440
6418
|
}
|
|
6441
6419
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: DiagramComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }, { token: DiagramStateService }, { token: ContextMenuService }, { token: ThemeService }, { token: ExportService }, { token: AutoSaveService }, { token: HandleRegistryService }, { token: LayoutService }, { token: NGX_WORKFLOW_NODE_TYPES, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
6442
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.13", type: DiagramComponent, isStandalone: true, selector: "ngx-workflow-diagram", inputs: { initialNodes: "initialNodes", initialEdges: "initialEdges", initialViewport: "initialViewport", nodesInput: ["nodes", "nodesInput"], edgesInput: ["edges", "edgesInput"], showZoomControls: "showZoomControls", minZoom: "minZoom", maxZoom: "maxZoom", backgroundImage: "backgroundImage", showUndoRedoControls: "showUndoRedoControls", showMinimap: "showMinimap", showBackground: "showBackground", backgroundVariant: "backgroundVariant", backgroundGap: "backgroundGap", backgroundSize: "backgroundSize", backgroundColor: "backgroundColor", backgroundBgColor: "backgroundBgColor", colorMode: "colorMode", gridSize: "gridSize", snapToGrid: "snapToGrid", showGrid: "showGrid", zIndexMode: "zIndexMode", showExportControls: "showExportControls", showLayoutControls: "showLayoutControls", autoSave: "autoSave", autoSaveInterval: "autoSaveInterval", maxVersions: "maxVersions", autoPanOnNodeDrag: "autoPanOnNodeDrag", autoPanOnConnect: "autoPanOnConnect", autoPanSpeed: "autoPanSpeed", autoPanEdgeThreshold: "autoPanEdgeThreshold", maxConnectionsPerHandle: "maxConnectionsPerHandle", preventNodeOverlap: "preventNodeOverlap", nodeSpacing: "nodeSpacing", validateConnection: "validateConnection", edgeTemplate: "edgeTemplate", defsTemplate: "defsTemplate", edgeReconnectable: "edgeReconnectable", proximityThreshold: "proximityThreshold", connectionValidator: "connectionValidator", nodesResizable: "nodesResizable", nodeTypes: "nodeTypes" }, outputs: { nodeClick: "nodeClick", edgeClick: "edgeClick", connect: "connect", nodesChange: "nodesChange", edgesChange: "edgesChange", nodeDoubleClick: "nodeDoubleClick", contextMenu: "contextMenu", nodeMouseEnter: "nodeMouseEnter", nodeMouseLeave: "nodeMouseLeave", nodeMouseMove: "nodeMouseMove", edgeMouseEnter: "edgeMouseEnter", edgeMouseLeave: "edgeMouseLeave", paneClick: "paneClick", paneScroll: "paneScroll", connectStart: "connectStart", connectEnd: "connectEnd", edgeDrop: "edgeDrop", connectionDrop: "connectionDrop", beforeDelete: "beforeDelete" }, host: { listeners: { "contextmenu": "onContextMenu($event)", "window:keydown.delete": "onDeleteKeyPress($event)", "window:keydown.control.z": "onUndoKeyPress($event)", "window:keydown.meta.z": "onUndoKeyPress($event)", "window:keydown.control.shift.z": "onRedoKeyPress($event)", "window:keydown.meta.shift.z": "onRedoKeyPress($event)", "window:keydown.control.c": "onCopyKeyPress($event)", "window:keydown.meta.c": "onCopyKeyPress($event)", "window:keydown.control.v": "onPasteKeyPress($event)", "window:keydown.meta.v": "onPasteKeyPress($event)", "window:keydown.control.x": "onCutKeyPress($event)", "window:keydown.meta.x": "onCutKeyPress($event)", "window:keydown.control.d": "onDuplicateKeyPress($event)", "window:keydown.meta.d": "onDuplicateKeyPress($event)", "window:keydown.control.]": "onBringToFrontKeyPress($event)", "window:keydown.meta.]": "onBringToFrontKeyPress($event)", "window:keydown.control.[": "onSendToBackKeyPress($event)", "window:keydown.meta.[": "onSendToBackKeyPress($event)", "window:keydown.control.shift.]": "onRaiseLayerKeyPress($event)", "window:keydown.meta.shift.]": "onRaiseLayerKeyPress($event)", "window:keydown.control.shift.[": "onLowerLayerKeyPress($event)", "window:keydown.meta.shift.[": "onLowerLayerKeyPress($event)", "window:keydown.control.a": "onSelectAllKeyPress($event)", "window:keydown.meta.a": "onSelectAllKeyPress($event)", "window:keydown.control.g": "onGroupKeyPress($event)", "window:keydown.meta.g": "onGroupKeyPress($event)", "window:keydown.control.shift.g": "onUngroupKeyPress($event)", "window:keydown.meta.shift.g": "onUngroupKeyPress($event)", "window:keydown.arrowup": "onArrowKeyPress($event)", "window:keydown.arrowdown": "onArrowKeyPress($event)", "window:keydown.arrowleft": "onArrowKeyPress($event)", "window:keydown.arrowright": "onArrowKeyPress($event)", "window:keyup": "onKeyUp($event)", "window:pointermove": "onWindowPointerMove($event)", "window:pointerup": "onWindowPointerUp($event)", "window:keydown": "onKeyDown($event)" } }, queries: [{ propertyName: "edgeLabelTemplate", first: true, predicate: ["edgeLabelTemplate"], descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "svgRef", first: true, predicate: ["svg"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"ngx-workflow__container\" [attr.data-lod]=\"lodLevel\">\r\n <ngx-workflow-background *ngIf=\"showBackground\" [variant]=\"backgroundVariant\" [gap]=\"backgroundGap\"\r\n [size]=\"backgroundSize\" [color]=\"backgroundColor\" [backgroundColor]=\"backgroundBgColor\"\r\n [backgroundImage]=\"backgroundImage\"></ngx-workflow-background>\r\n <ngx-workflow-grid-overlay *ngIf=\"showGrid\" [gridSize]=\"gridSize\"></ngx-workflow-grid-overlay>\r\n <svg #svg class=\"ngx-workflow__diagram\" (wheel)=\"onWheel($event)\" (pointerdown)=\"onPointerDown($event)\"\r\n (pointermove)=\"onPointerMove($event)\" (pointerup)=\"onPointerUp($event)\" (pointerleave)=\"onPointerUp($event)\"\r\n (dblclick)=\"onDiagramDoubleClick($event)\">\r\n <defs>\r\n <pattern id=\"ngx-workflow__grid-pattern\" x=\"0\" y=\"0\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\">\r\n <circle cx=\"0.5\" cy=\"0.5\" r=\"0.5\" fill=\"#ccc\" />\r\n </pattern>\r\n\r\n <!-- Arrow marker -->\r\n <marker id=\"ngx-workflow__arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\"\r\n orient=\"auto-start-reverse\">\r\n <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"context-stroke\" />\r\n </marker>\r\n\r\n <!-- Arrowclosed marker (filled) -->\r\n <marker id=\"ngx-workflow__arrowclosed\" viewBox=\"0 0 10 10\" refX=\"10\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\"\r\n orient=\"auto-start-reverse\">\r\n <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"context-stroke\" stroke=\"context-stroke\" />\r\n </marker>\r\n\r\n <!-- Dot marker -->\r\n <marker id=\"ngx-workflow__dot\" viewBox=\"0 0 10 10\" refX=\"5\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\">\r\n <circle cx=\"5\" cy=\"5\" r=\"4\" fill=\"context-stroke\" />\r\n </marker>\r\n\r\n <!-- Custom Definitions -->\r\n <ng-container *ngTemplateOutlet=\"defsTemplate\"></ng-container>\r\n </defs>\r\n\r\n <rect width=\"100%\" height=\"100%\" fill=\"url(#ngx-workflow__grid-pattern)\" class=\"ngx-workflow__background\" />\r\n\r\n <g class=\"ngx-workflow__viewport\" [attr.transform]=\"transform\">\r\n <!-- Group Backgrounds (Layer 1: Bottom) -->\r\n <g *ngFor=\"let node of sortedNodes(); trackBy: trackByNodeId\">\r\n <g *ngIf=\"node.type === 'group'\"\r\n [attr.transform]=\"'translate(' + (node._renderPosition?.x ?? node.position.x) + ',' + (node._renderPosition?.y ?? node.position.y) + ')'\"\r\n (pointerdown)=\"onNodePointerDown($event, node)\" (dblclick)=\"onNodeDoubleClick($event, node)\"\r\n (mouseenter)=\"onNodeMouseEnter($event, node)\" (mouseleave)=\"onNodeMouseLeave($event, node)\"\r\n (mousemove)=\"onNodeMouseMove($event, node)\" class=\"ngx-workflow__group-node --background-layer\"\r\n [class.ngx-workflow__group-node--hover]=\"hoveredGroupId() === node.id\">\r\n <!-- Only the background rect -->\r\n <rect [attr.width]=\"node.width\" [attr.height]=\"node.height\" rx=\"4\" ry=\"4\"\r\n [style.fill]=\"node.style?.['backgroundColor'] || 'rgba(240, 240, 240, 0.5)'\"\r\n [style.stroke]=\"node.selected ? '#2563eb' : (node.style?.['borderColor'] || '#e5e7eb')\"\r\n [style.stroke-width]=\"node.selected ? 2 : 1\"></rect>\r\n </g>\r\n </g>\r\n\r\n <!-- Edges (Layer 2: Middle) -->\r\n <g *ngFor=\"let edge of filteredEdges(); trackBy: trackByEdgeId\" [class.selected]=\"edge.selected\"\r\n [class.animated]=\"edge.animated && edge.animationType !== 'dot'\" [class.hidden]=\"edge.hidden\"\r\n [class.animated]=\"edge.animated && (edge.animationType === 'flow' || edge.animationType === 'both')\"\r\n [class.hidden]=\"edge.hidden\" [class.shadow]=\"edge.shadow\" class=\"ngx-workflow__edge\"\r\n (click)=\"onEdgeClick($event, edge)\" (dblclick)=\"onEdgeDoubleClick($event, edge)\"\r\n (mouseenter)=\"onEdgeMouseEnter($event, edge)\" (mouseleave)=\"onEdgeMouseLeave($event, edge)\">\r\n <!-- Edge path with markers -->\r\n <path [attr.id]=\"'edge-path-' + edge.id\" [attr.d]=\"getEdgePath(edge)\" class=\"ngx-workflow__edge-path\"\r\n [style]=\"edge.style\" [class.dashed-type]=\"edge.type === 'dashed'\"\r\n [attr.marker-start]=\"getMarkerUrl(edge.markerStart)\" [attr.marker-end]=\"getMarkerUrl(edge.markerEnd)\"\r\n [style.strokeDasharray]=\"edge.style?.['strokeDasharray'] || (edge.type === 'dashed' ? '5,5' : '')\"\r\n [style.animationDuration]=\"edge.animationDuration || '1s'\">\r\n </path>\r\n\r\n <!-- SVG Motion Animation (Dot) -->\r\n <circle *ngIf=\"edge.animated && (edge.animationType === 'dot' || edge.animationType === 'both')\" r=\"4\"\r\n [attr.fill]=\"edge.animationStyle?.['fill'] || '#3b82f6'\">\r\n <animateMotion [attr.dur]=\"edge.animationDuration || '2s'\" repeatCount=\"indefinite\">\r\n <mpath [attr.href]=\"'#edge-path-' + edge.id\"></mpath>\r\n </animateMotion>\r\n </circle>\r\n <!-- Invisible hitbox for easier clicking -->\r\n <path [attr.d]=\"getEdgePath(edge)\" class=\"ngx-workflow__edge-hitbox\"></path>\r\n\r\n <!-- Edge label -->\r\n <g *ngIf=\"edge.label || edge.data?.labelContent || editingEdgeId === edge.id\" class=\"ngx-workflow__edge-label\">\r\n <!-- Editing mode (input field) -->\r\n <foreignObject *ngIf=\"editingEdgeId === edge.id\" [attr.x]=\"getEdgeLabelPosition(edge).x - 50\"\r\n [attr.y]=\"getEdgeLabelPosition(edge).y - 15\" width=\"100\" height=\"30\">\r\n <input #edgeLabelInput type=\"text\" [value]=\"edge.label || ''\" (blur)=\"onEdgeLabelBlur(edge)\"\r\n (keydown.enter)=\"onEdgeLabelBlur(edge)\" (input)=\"onEdgeLabelInput($event)\"\r\n class=\"ngx-workflow__edge-label-input\" autofocus>\r\n </foreignObject>\r\n\r\n <!-- Custom template mode (Angular component) -->\r\n <foreignObject *ngIf=\"editingEdgeId !== edge.id && edgeLabelTemplate\"\r\n [attr.x]=\"getEdgeLabelPosition(edge).x - 75\" [attr.y]=\"getEdgeLabelPosition(edge).y - 20\" width=\"150\"\r\n height=\"40\" style=\"pointer-events: none;\">\r\n <div xmlns=\"http://www.w3.org/1999/xhtml\" class=\"edge-label-content\">\r\n <ng-container *ngTemplateOutlet=\"edgeLabelTemplate; context: { $implicit: edge, edge: edge }\">\r\n </ng-container>\r\n </div>\r\n </foreignObject>\r\n\r\n <!-- Fallback to text mode -->\r\n <text *ngIf=\"editingEdgeId !== edge.id && !edgeLabelTemplate && edge.label\"\r\n [attr.x]=\"getEdgeLabelPosition(edge).x\" [attr.y]=\"getEdgeLabelPosition(edge).y\" text-anchor=\"middle\"\r\n dominant-baseline=\"middle\" class=\"ngx-workflow__edge-label-text\" [style]=\"edge.labelStyle\">\r\n <!-- Label background -->\r\n <tspan *ngIf=\"edge.labelBgStyle\" class=\"ngx-workflow__edge-label-bg\" [style]=\"edge.labelBgStyle\"></tspan>\r\n {{ edge.label }}\r\n </text>\r\n </g>\r\n\r\n <!-- Edge Update Handles -->\r\n <g *ngIf=\"edge.selected && edgeReconnectable\">\r\n <!-- Source Handle -->\r\n <circle class=\"ngx-workflow__edge-reconnect-handle\" [attr.cx]=\"getEdgeHandlePosition(edge, 'source').x\"\r\n [attr.cy]=\"getEdgeHandlePosition(edge, 'source').y\" r=\"8\" fill=\"#3b82f6\" stroke=\"white\" stroke-width=\"2\"\r\n style=\"cursor: grab; pointer-events: all;\" (pointerdown)=\"startUpdatingEdge($event, edge, 'source')\">\r\n </circle>\r\n <!-- Target Handle -->\r\n <circle class=\"ngx-workflow__edge-reconnect-handle\" [attr.cx]=\"getEdgeHandlePosition(edge, 'target').x\"\r\n [attr.cy]=\"getEdgeHandlePosition(edge, 'target').y\" r=\"8\" fill=\"#3b82f6\" stroke=\"white\" stroke-width=\"2\"\r\n style=\"cursor: grab; pointer-events: all;\" (pointerdown)=\"startUpdatingEdge($event, edge, 'target')\">\r\n </circle>\r\n </g>\r\n </g>\r\n\r\n <!-- Render temporary edges (previews) -->\r\n <g *ngFor=\"let tempEdge of tempEdges(); trackBy: trackByEdgeId\" [class.animated]=\"tempEdge.animated\"\r\n class=\"ngx-workflow__edge\">\r\n <path [attr.d]=\"getEdgePath(tempEdge, true)\" class=\"ngx-workflow__edge-path\" [style]=\"tempEdge.style\"></path>\r\n </g>\r\n\r\n <!-- Selection Box (Rubber Band) -->\r\n <rect *ngIf=\"isBoxSelecting\" class=\"ngx-workflow__selection-box\" [attr.x]=\"getSelectionBox().x\"\r\n [attr.y]=\"getSelectionBox().y\" [attr.width]=\"getSelectionBox().width\"\r\n [attr.height]=\"getSelectionBox().height\" />\r\n\r\n <!-- Nodes -->\r\n <g *ngFor=\"let node of sortedNodes(); trackBy: trackByNodeId\" class=\"ngx-workflow__node\" [attr.data-id]=\"node.id\"\r\n [attr.transform]=\"'translate(' + (node._renderPosition?.x ?? node.position.x) + ',' + (node._renderPosition?.y ?? node.position.y) + ')'\"\r\n (pointerdown)=\"onNodePointerDown($event, node)\" (dblclick)=\"onNodeDoubleClick($event, node)\"\r\n (mouseenter)=\"onNodeMouseEnter($event, node)\" (mouseleave)=\"onNodeMouseLeave($event, node)\"\r\n (mousemove)=\"onNodeMouseMove($event, node)\">\r\n\r\n <!-- Custom Node Template -->\r\n <foreignObject *ngIf=\"isCustomNode(node)\" [attr.width]=\"node.width || defaultNodeWidth\"\r\n [attr.height]=\"node.height || defaultNodeHeight\" style=\"overflow: visible;\">\r\n <div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"width: 100%; height: 100%;\">\r\n <ng-container *ngComponentOutlet=\"getCustomNodeComponent(node.type); inputs: { \r\n id: node.id, \r\n data: node.data, \r\n selected: node.selected,\r\n type: node.type,\r\n pos: node.position,\r\n easyConnect: node.easyConnect,\r\n style: node.style,\r\n ports: node.ports,\r\n label: node.label\r\n }\"></ng-container>\r\n </div>\r\n </foreignObject>\r\n\r\n <!-- Default Node Template (Rect + Text) -->\r\n <g *ngIf=\"!isCustomNode(node) && node.type !== 'group'\">\r\n <!-- Node outline and background (visual + dragging) -->\r\n <rect [attr.width]=\"node.width || defaultNodeWidth\" [attr.height]=\"node.height || defaultNodeHeight\" rx=\"3\"\r\n ry=\"3\" class=\"ngx-workflow__node-rect\"\r\n [style.filter]=\"node.shadow === true ? 'drop-shadow(0 10px 15px rgba(0,0,0,0.2))' : (node.shadow || null)\"\r\n [style.stroke-dasharray]=\"node.borderStyle === 'dashed' ? '5,5' : (node.borderStyle === 'dotted' ? '2,2' : null)\"\r\n [style.stroke]=\"node.borderColor\" [style.stroke-width]=\"node.borderWidth\"\r\n [style.fill]=\"node.style?.['backgroundColor'] || 'var(--ngx-workflow-surface, #ffffff)'\"></rect>\r\n\r\n <!-- Node Badges -->\r\n <g *ngIf=\"node.badges?.length\">\r\n <g *ngFor=\"let badge of node.badges; let i = index\" class=\"ngx-workflow__node-badge\"\r\n [attr.transform]=\"getBadgeTransform(node, badge, i)\">\r\n <circle r=\"10\" [attr.fill]=\"badge.backgroundColor || '#ef4444'\" [attr.stroke]=\"'#ffffff'\"\r\n stroke-width=\"2\">\r\n </circle>\r\n <text dy=\"0.3em\" text-anchor=\"middle\" font-size=\"10px\" font-weight=\"bold\"\r\n [attr.fill]=\"badge.color || '#ffffff'\">{{ badge.content }}</text>\r\n </g>\r\n </g>\r\n\r\n <!-- Node Label -->\r\n <text [attr.x]=\"(node.width || defaultNodeWidth) / 2\" [attr.y]=\"(node.height || defaultNodeHeight) / 2\"\r\n text-anchor=\"middle\" dominant-baseline=\"middle\" class=\"ngx-workflow__node-label\"\r\n [style.fill]=\"node.style?.['color'] || 'black'\">\r\n {{ node.label }}\r\n </text>\r\n </g>\r\n\r\n <!-- Only render default handles if NOT custom node? Or maybe keep them? \r\n Usually custom nodes implement their own handles.\r\n Let's allow default handles to show only if NOT custom node for now to start clean.\r\n -->\r\n <ng-container *ngIf=\"!isCustomNode(node)\">\r\n <!-- Top Handle -->\r\n <g *ngIf=\"!node.ports || node.ports === 1 || node.ports === 2 || node.ports === 4\" ngx-workflow-handle\r\n class=\"ngx-workflow__handle ngx-workflow__handle--source ngx-workflow__handle--target\"\r\n [attr.data-nodeid]=\"node.id\" data-handleid=\"top\" [attr.data-type]=\"'source'\" [nodeId]=\"node.id\"\r\n handleId=\"top\" type=\"source\" [isConnectable]=\"node.handleConfig?.['top']?.isConnectable\"\r\n (pointerdown)=\"onHandlePointerDown($event, node, 'top')\">\r\n <!-- Hit area -->\r\n <circle [attr.cx]=\"(node.width || defaultNodeWidth) / 2\" cy=\"-10\" r=\"20\" fill=\"transparent\"\r\n pointer-events=\"all\"></circle>\r\n <!-- Visual -->\r\n <circle [attr.cx]=\"(node.width || defaultNodeWidth) / 2\" [attr.cy]=\"0\" r=\"6\"\r\n class=\"ngx-workflow__handle-circle\">\r\n </circle>\r\n </g>\r\n\r\n <!-- Right Handle -->\r\n <g *ngIf=\"!node.ports || node.ports === 3 || node.ports === 4\" ngx-workflow-handle\r\n class=\"ngx-workflow__handle ngx-workflow__handle--source ngx-workflow__handle--target\"\r\n [attr.data-nodeid]=\"node.id\" data-handleid=\"right\" [attr.data-type]=\"'source'\" [nodeId]=\"node.id\"\r\n handleId=\"right\" type=\"source\" [isConnectable]=\"node.handleConfig?.['right']?.isConnectable\"\r\n (pointerdown)=\"onHandlePointerDown($event, node, 'right')\">\r\n <!-- Hit area -->\r\n <circle [attr.cx]=\"(node.width || defaultNodeWidth) + 10\" [attr.cy]=\"(node.height || defaultNodeHeight) / 2\"\r\n r=\"20\" fill=\"transparent\" pointer-events=\"all\">\r\n </circle>\r\n <!-- Visual -->\r\n <circle [attr.cx]=\"(node.width || defaultNodeWidth)\" [attr.cy]=\"(node.height || defaultNodeHeight) / 2\"\r\n r=\"6\" class=\"ngx-workflow__handle-circle\"></circle>\r\n </g>\r\n <!-- Bottom Handle -->\r\n <g *ngIf=\"!node.ports || node.ports === 2 || node.ports === 4\" ngx-workflow-handle\r\n class=\"ngx-workflow__handle ngx-workflow__handle--source ngx-workflow__handle--target\"\r\n [attr.data-nodeid]=\"node.id\" data-handleid=\"bottom\" [attr.data-type]=\"'source'\" [nodeId]=\"node.id\"\r\n handleId=\"bottom\" type=\"source\" [isConnectable]=\"node.handleConfig?.['bottom']?.isConnectable\"\r\n (pointerdown)=\"onHandlePointerDown($event, node, 'bottom')\">\r\n <!-- Hit area -->\r\n <circle [attr.cx]=\"(node.width || defaultNodeWidth) / 2\" [attr.cy]=\"(node.height || defaultNodeHeight) + 10\"\r\n r=\"20\" fill=\"transparent\" pointer-events=\"all\">\r\n </circle>\r\n <!-- Visual -->\r\n <circle [attr.cx]=\"(node.width || defaultNodeWidth) / 2\" [attr.cy]=\"(node.height || defaultNodeHeight)\"\r\n r=\"6\" class=\"ngx-workflow__handle-circle\"></circle>\r\n </g>\r\n <!-- Left Handle -->\r\n <g *ngIf=\"!node.ports || node.ports === 3 || node.ports === 4\" ngx-workflow-handle\r\n class=\"ngx-workflow__handle ngx-workflow__handle--source ngx-workflow__handle--target\"\r\n [attr.data-nodeid]=\"node.id\" data-handleid=\"left\" [attr.data-type]=\"'source'\" [nodeId]=\"node.id\"\r\n handleId=\"left\" type=\"source\" [isConnectable]=\"node.handleConfig?.['left']?.isConnectable\"\r\n (pointerdown)=\"onHandlePointerDown($event, node, 'left')\">\r\n <!-- Hit area -->\r\n <circle cx=\"-10\" [attr.cy]=\"(node.height || defaultNodeHeight) / 2\" r=\"20\" fill=\"transparent\"\r\n pointer-events=\"all\"></circle>\r\n <!-- Visual -->\r\n <circle cx=\"0\" [attr.cy]=\"(node.height || defaultNodeHeight) / 2\" r=\"6\" class=\"ngx-workflow__handle-circle\">\r\n </circle>\r\n </g>\r\n </ng-container>\r\n\r\n <!-- Resize Handles (HTML implementation to ensure they are on top of custom nodes) -->\r\n <foreignObject *ngIf=\"node.selected && (node.resizable !== false) && nodesResizable\"\r\n [attr.width]=\"node.width || defaultNodeWidth\" [attr.height]=\"node.height || defaultNodeHeight\"\r\n style=\"overflow: visible; pointer-events: none;\">\r\n <div style=\"position: relative; width: 100%; height: 100%; pointer-events: none;\">\r\n <!-- NW Handle -->\r\n <div class=\"ngx-workflow__resize-handle\" data-handle=\"nw\"\r\n style=\"position: absolute; top: -5px; left: -5px; width: 10px; height: 10px; cursor: nw-resize; pointer-events: auto;\"\r\n (pointerdown)=\"startResizing($event, node, 'nw')\"></div>\r\n <!-- NE Handle -->\r\n <div class=\"ngx-workflow__resize-handle\" data-handle=\"ne\"\r\n style=\"position: absolute; top: -5px; right: -5px; width: 10px; height: 10px; cursor: ne-resize; pointer-events: auto;\"\r\n (pointerdown)=\"startResizing($event, node, 'ne')\"></div>\r\n <!-- SW Handle -->\r\n <div class=\"ngx-workflow__resize-handle\" data-handle=\"sw\"\r\n style=\"position: absolute; bottom: -5px; left: -5px; width: 10px; height: 10px; cursor: sw-resize; pointer-events: auto;\"\r\n (pointerdown)=\"startResizing($event, node, 'sw')\"></div>\r\n <!-- SE Handle -->\r\n <div class=\"ngx-workflow__resize-handle\" data-handle=\"se\"\r\n style=\"position: absolute; bottom: -5px; right: -5px; width: 10px; height: 10px; cursor: se-resize; pointer-events: auto;\"\r\n (pointerdown)=\"startResizing($event, node, 'se')\"></div>\r\n </div>\r\n </foreignObject>\r\n\r\n <!-- Group Node Template (Layer 3: Top - Content Only) -->\r\n <g *ngIf=\"node.type === 'group'\" class=\"ngx-workflow__group-node\">\r\n <!-- Background moved to Layer 1, but we keep a transparent hit area or just rely on content? \r\n actually Layer 1 handles interactions for the body.\r\n We just need Header and Content here.\r\n -->\r\n\r\n <!-- Header -->\r\n <rect [attr.width]=\"node.width\" height=\"30\" class=\"ngx-workflow__group-header\" rx=\"4\" ry=\"4\"></rect>\r\n <text [attr.x]=\"10\" [attr.y]=\"20\" class=\"ngx-workflow__group-label\">{{ node.label }}</text>\r\n\r\n <!-- Expand/Collapse Toggle -->\r\n <g class=\"ngx-workflow__group-toggle\" [attr.transform]=\"'translate(' + (node.width! - 25) + ', 5)'\"\r\n (click)=\"toggleGroup($event, node)\">\r\n <rect width=\"20\" height=\"20\" rx=\"4\" class=\"ngx-workflow__group-toggle-bg\"></rect>\r\n <path [attr.d]=\"node.expanded ? 'M 5 10 L 15 10' : 'M 5 10 L 15 10 M 10 5 L 10 15'\"\r\n class=\"ngx-workflow__group-toggle-icon\"></path>\r\n </g>\r\n </g>\r\n </g>\r\n\r\n <!-- Alignment Guides -->\r\n <g *ngFor=\"let guide of alignmentGuides()\" class=\"ngx-workflow__alignment-guide\">\r\n <line *ngIf=\"guide.type === 'horizontal'\" [attr.x1]=\"guide.start\" [attr.y1]=\"guide.position\"\r\n [attr.x2]=\"guide.end\" [attr.y2]=\"guide.position\" />\r\n <line *ngIf=\"guide.type === 'vertical'\" [attr.x1]=\"guide.position\" [attr.y1]=\"guide.start\"\r\n [attr.x2]=\"guide.position\" [attr.y2]=\"guide.end\" />\r\n </g>\r\n </g>\r\n\r\n </svg>\r\n\r\n\r\n <!-- Unified Top Bar -->\r\n <div class=\"ngx-workflow__top-bar\">\r\n <ngx-workflow-search-controls [nodes]=\"nodes()\" (searchResults)=\"onSearchResults($event)\"\r\n (resultSelected)=\"onSearchResultSelected($event)\" (close)=\"onSearchClose()\">\r\n </ngx-workflow-search-controls>\r\n\r\n <div class=\"divider\"></div>\r\n\r\n <ngx-workflow-layout-alignment-controls *ngIf=\"showLayoutControls\"\r\n (applyLayout)=\"onApplyLayout($any($event))\"></ngx-workflow-layout-alignment-controls>\r\n\r\n <div class=\"divider\"></div>\r\n\r\n <ngx-workflow-export-controls *ngIf=\"showExportControls\" (exportJSON)=\"exportToJSON()\"\r\n (importJSON)=\"triggerImport()\">\r\n </ngx-workflow-export-controls>\r\n </div>\r\n\r\n <ngx-workflow-minimap *ngIf=\"showMinimap\"></ngx-workflow-minimap>\r\n\r\n <ngx-workflow-zoom-controls *ngIf=\"showZoomControls\" [zoom]=\"viewport().zoom\" [minZoom]=\"minZoom\" [maxZoom]=\"maxZoom\"\r\n (zoomIn)=\"onZoomChange(viewport().zoom + 0.1)\" (zoomOut)=\"onZoomChange(viewport().zoom - 0.1)\"\r\n (fitView)=\"onZoomChange(1)\" (resetZoom)=\"onZoomChange(1)\"></ngx-workflow-zoom-controls>\r\n\r\n <ngx-workflow-properties-sidebar [node]=\"selectedNodeForEditing\" [edge]=\"selectedEdgeForEditing\"\r\n (close)=\"closeSidebar()\" (change)=\"onPropertiesChange($event)\"\r\n (edgeChange)=\"onEdgePropertiesChange($event)\"></ngx-workflow-properties-sidebar>\r\n\r\n <!-- Node Toolbars (projected content) -->\r\n <ng-content select=\"ngx-workflow-node-toolbar\"></ng-content>\r\n\r\n <!-- Panels (projected content) -->\r\n <ng-content select=\"ngx-workflow-panel\"></ng-content>\r\n\r\n <ngx-workflow-context-menu></ngx-workflow-context-menu>\r\n</div>", styles: [":root{--ngx-workflow-bg: #ffffff;--ngx-workflow-bg-pattern: #f0f0f0;--ngx-workflow-node-bg: #ffffff;--ngx-workflow-node-border: #d1d5db;--ngx-workflow-node-color: #1f2937;--ngx-workflow-node-selected-border: #3b82f6;--ngx-workflow-node-shadow: rgba(0, 0, 0, .1);--ngx-workflow-edge-stroke: #9ca3af;--ngx-workflow-edge-stroke-selected: #3b82f6;--ngx-workflow-edge-label-bg: #ffffff;--ngx-workflow-edge-label-color: #374151;--ngx-workflow-controls-bg: #ffffff;--ngx-workflow-controls-border: #d1d5db;--ngx-workflow-controls-color: #374151;--ngx-workflow-controls-hover-bg: #f3f4f6;--ngx-workflow-minimap-bg: #f9fafb;--ngx-workflow-minimap-border: #d1d5db;--ngx-workflow-minimap-node: #3b82f6;--ngx-workflow-minimap-viewport: rgba(59, 130, 246, .2);--ngx-workflow-sidebar-bg: #ffffff;--ngx-workflow-sidebar-border: #d1d5db;--ngx-workflow-sidebar-color: #1f2937;--ngx-workflow-menu-bg: #ffffff;--ngx-workflow-menu-border: #d1d5db;--ngx-workflow-menu-color: #374151;--ngx-workflow-menu-hover-bg: #f3f4f6;--ngx-workflow-search-bg: #ffffff;--ngx-workflow-search-border: #d1d5db;--ngx-workflow-search-color: #374151;--ngx-workflow-toolbar-bg: #ffffff;--ngx-workflow-toolbar-border: #d1d5db;--ngx-workflow-toolbar-shadow: rgba(0, 0, 0, .1);--ngx-workflow-panel-bg: transparent;--ngx-workflow-panel-z-index: 5;--ngx-workflow-panel-offset: 10px}html[data-theme=dark],html.dark-theme{--ngx-workflow-bg: #1f2937;--ngx-workflow-bg-pattern: #374151;--ngx-workflow-node-bg: #374151;--ngx-workflow-node-border: #4b5563;--ngx-workflow-node-color: #f9fafb;--ngx-workflow-node-selected-border: #60a5fa;--ngx-workflow-node-shadow: rgba(0, 0, 0, .3);--ngx-workflow-edge-stroke: #6b7280;--ngx-workflow-edge-stroke-selected: #60a5fa;--ngx-workflow-edge-label-bg: #374151;--ngx-workflow-edge-label-color: #f9fafb;--ngx-workflow-controls-bg: #374151;--ngx-workflow-controls-border: #4b5563;--ngx-workflow-controls-color: #f9fafb;--ngx-workflow-controls-hover-bg: #4b5563;--ngx-workflow-minimap-bg: #111827;--ngx-workflow-minimap-border: #4b5563;--ngx-workflow-minimap-node: #60a5fa;--ngx-workflow-minimap-viewport: rgba(96, 165, 250, .2);--ngx-workflow-sidebar-bg: #374151;--ngx-workflow-sidebar-border: #4b5563;--ngx-workflow-sidebar-color: #f9fafb;--ngx-workflow-menu-bg: #374151;--ngx-workflow-menu-border: #4b5563;--ngx-workflow-menu-color: #f9fafb;--ngx-workflow-menu-hover-bg: #4b5563;--ngx-workflow-search-bg: #374151;--ngx-workflow-search-border: #4b5563;--ngx-workflow-search-color: #f9fafb;--ngx-workflow-toolbar-bg: #374151;--ngx-workflow-toolbar-border: #4b5563;--ngx-workflow-toolbar-shadow: rgba(0, 0, 0, .5);--ngx-workflow-panel-bg: transparent}:host{--ngx-workflow-primary: #3b82f6;--ngx-workflow-primary-hover: #2563eb;--ngx-workflow-surface: var(--ngx-workflow-node-bg, #ffffff);--ngx-workflow-border: var(--ngx-workflow-node-border, #e2e8f0);--ngx-workflow-text-primary: var(--ngx-workflow-node-color, #1e293b);--ngx-workflow-text-secondary: #64748b;--ngx-workflow-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / .05);--ngx-workflow-shadow-md: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--ngx-workflow-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--ngx-workflow-glass-bg: rgba(255, 255, 255, .8);--ngx-workflow-glass-border: rgba(255, 255, 255, .5);--ngx-workflow-glass-blur: blur(12px);display:block;width:100%;height:100%;font-family:Inter,system-ui,-apple-system,sans-serif}:host-context(html[data-theme=dark]),:host-context(html.dark-theme){--ngx-workflow-surface: #374151;--ngx-workflow-border: #4b5563;--ngx-workflow-text-primary: #f9fafb;--ngx-workflow-glass-bg: rgba(55, 65, 81, .8);--ngx-workflow-glass-border: rgba(75, 85, 99, .5)}.ngx-workflow__container{position:relative;width:100%;height:100%;overflow:hidden;background-color:var(--ngx-workflow-bg, #f8fafc)}.ngx-workflow__container[data-lod=low] .ngx-workflow__node-label,.ngx-workflow__container[data-lod=low] .ngx-workflow__handle,.ngx-workflow__container[data-lod=low] .ngx-workflow__edge-label{display:none}.ngx-workflow__container[data-lod=low] .ngx-workflow__node-rect{stroke-width:1px}.ngx-workflow__container[data-lod=medium] .ngx-workflow__handle{opacity:0;pointer-events:none}.ngx-workflow__diagram{width:100%;height:100%;cursor:grab;position:relative;z-index:1}.ngx-workflow__diagram:active{cursor:grabbing}.ngx-workflow__background{fill:url(#ngx-workflow__grid-pattern);opacity:.6}.ngx-workflow__viewport{transform-origin:0 0}.ngx-workflow__node{cursor:grab;transition:transform .1s ease,opacity .2s ease}.ngx-workflow__node .ngx-workflow__node-rect{fill:var(--ngx-workflow-surface);stroke:var(--ngx-workflow-border);stroke-width:1.5px;filter:drop-shadow(0 2px 4px rgba(0,0,0,.05));transition:all .2s cubic-bezier(.4,0,.2,1);pointer-events:all}.ngx-workflow__node .ngx-workflow__node-label{font-size:12px;font-weight:500;fill:var(--ngx-workflow-text-primary);pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-workflow__node:hover .ngx-workflow__node-rect{stroke:var(--ngx-workflow-primary);filter:drop-shadow(0 4px 6px rgba(0,0,0,.08));transform:translateY(-1px)}.ngx-workflow__node.selected .ngx-workflow__node-rect{stroke:var(--ngx-workflow-primary);stroke-width:2px;filter:drop-shadow(0 0 0 2px rgba(59,130,246,.2))}.ngx-workflow__node.dragging{cursor:grabbing;opacity:.9}.ngx-workflow__node.dragging .ngx-workflow__node-rect{filter:drop-shadow(0 10px 15px rgba(0,0,0,.1));transform:scale(1.02)}.ngx-workflow__node.highlighted .ngx-workflow__node-rect{stroke:var(--ngx-workflow-primary);stroke-width:2px;filter:drop-shadow(0 0 0 4px rgba(59,130,246,.4))}.ngx-workflow__node.dimmed{opacity:.4}.ngx-workflow__node.colliding .ngx-workflow__node-rect{stroke:#ef4444!important;stroke-width:3px;animation:shake .3s ease-in-out}@keyframes shake{0%,to{transform:translate(0)}25%{transform:translate(-2px)}75%{transform:translate(2px)}}:host-context(html[data-theme=dark]) .ngx-workflow__node-rect,:host-context(html.dark-theme) .ngx-workflow__node-rect{filter:drop-shadow(0 2px 4px rgba(0,0,0,.3))}:host-context(html[data-theme=dark]) .ngx-workflow__node:hover .ngx-workflow__node-rect,:host-context(html.dark-theme) .ngx-workflow__node:hover .ngx-workflow__node-rect{filter:drop-shadow(0 4px 6px rgba(0,0,0,.4))}foreignObject{overflow:visible;pointer-events:none}foreignObject *{pointer-events:auto}.ngx-workflow__edge{pointer-events:none}.ngx-workflow__edge-path{fill:none;stroke:#94a3b8;stroke-width:2;transition:stroke .2s ease,stroke-width .2s ease;pointer-events:stroke;cursor:pointer}.ngx-workflow__edge-hitbox{fill:none;stroke:transparent;stroke-width:20;pointer-events:stroke;cursor:pointer}.ngx-workflow__edge:hover .ngx-workflow__edge-path{stroke:var(--ngx-workflow-primary)}.ngx-workflow__edge.selected .ngx-workflow__edge-path{stroke:var(--ngx-workflow-primary);stroke-width:3;filter:drop-shadow(0 1px 2px rgba(59,130,246,.3))}.ngx-workflow__edge.animated .ngx-workflow__edge-path{stroke-dasharray:10;animation:flowAnimation 1s linear infinite}.ngx-workflow__edge.hidden{opacity:0;pointer-events:none}.ngx-workflow__edge.shadow .ngx-workflow__edge-path{filter:drop-shadow(0 2px 3px rgba(0,0,0,.3))}.cursor-crosshair{cursor:crosshair!important}.cursor-grab{cursor:grab!important}.cursor-grabbing{cursor:grabbing!important}.cursor-pointer{cursor:pointer!important}@keyframes flowAnimation{0%{stroke-dashoffset:20}to{stroke-dashoffset:0}}.ngx-workflow__edge-label{pointer-events:none}.ngx-workflow__edge-label-text{font-family:Inter,sans-serif;font-size:12px;font-weight:500;fill:var(--ngx-workflow-text-secondary);text-anchor:middle;dominant-baseline:middle}.ngx-workflow__edge-label-bg{fill:var(--ngx-workflow-surface);rx:4;ry:4;filter:drop-shadow(0 1px 2px rgba(0,0,0,.1))}.ngx-workflow__edge-label-input{width:100%;height:100%;border:1px solid var(--ngx-workflow-primary);border-radius:4px;padding:2px 6px;font-size:12px;text-align:center;background:var(--ngx-workflow-surface);pointer-events:all;outline:none;box-shadow:var(--ngx-workflow-shadow-sm)}.edge-label-content{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.edge-label-content>*{pointer-events:all}.ngx-workflow__handle{opacity:1;transition:transform .2s cubic-bezier(.34,1.56,.64,1);pointer-events:all}.ngx-workflow__handle-circle{fill:var(--ngx-workflow-surface);stroke:var(--ngx-workflow-text-secondary);stroke-width:1.5;transition:all .2s ease}.ngx-workflow__handle-circle:hover{fill:var(--ngx-workflow-primary);stroke:var(--ngx-workflow-primary);r:7}.ngx-workflow__handle.ngx-workflow__handle--valid-target .ngx-workflow__handle-circle{fill:#10b981!important;stroke:#059669!important;r:8;stroke-width:2}.ngx-workflow__handle.ngx-workflow__handle--invalid-target .ngx-workflow__handle-circle{fill:#ef4444!important;stroke:#dc2626!important;r:7;stroke-width:2;opacity:.6;cursor:not-allowed}.ngx-workflow__selection-box{pointer-events:none;animation:selectionPulse 1.5s ease-in-out infinite}@keyframes selectionPulse{0%,to{opacity:.8}50%{opacity:1}}@keyframes invalidConnectionShake{0%,to{transform:translate(0)}25%{transform:translate(-4px)}75%{transform:translate(4px)}}.invalid-connection{animation:invalidConnectionShake .3s ease-in-out 2}.invalid-connection .ngx-workflow__node-rect{stroke:#ef4444!important;stroke-width:3px}.ngx-workflow__top-bar{position:absolute;top:16px;left:50%;transform:translate(-50%);z-index:10;display:flex;align-items:center;gap:8px;padding:6px;background:var(--ngx-workflow-glass-bg);-webkit-backdrop-filter:var(--ngx-workflow-glass-blur);backdrop-filter:var(--ngx-workflow-glass-blur);border:1px solid var(--ngx-workflow-glass-border);border-radius:12px;box-shadow:var(--ngx-workflow-shadow-md)}.ngx-workflow__top-bar .divider{width:1px;height:24px;background:#0000001a;margin:0 4px}.ngx-workflow__io-controls{position:absolute;top:16px;right:16px;z-index:10;display:flex;gap:8px;padding:8px;background:var(--ngx-workflow-glass-bg);-webkit-backdrop-filter:var(--ngx-workflow-glass-blur);backdrop-filter:var(--ngx-workflow-glass-blur);border:1px solid var(--ngx-workflow-glass-border);border-radius:12px;box-shadow:var(--ngx-workflow-shadow-md);align-items:center}.ngx-workflow__io-controls input[type=text]{padding:6px 12px;border:1px solid transparent;border-radius:8px;font-size:13px;background:#0000000d;transition:all .2s ease;width:160px}.ngx-workflow__io-controls input[type=text]:focus{background:#fff;border-color:var(--ngx-workflow-primary);outline:none;box-shadow:0 0 0 2px #3b82f61a}.ngx-workflow__io-controls select{padding:6px 12px;border:1px solid transparent;border-radius:8px;font-size:13px;background:#0000000d;cursor:pointer;transition:all .2s ease}.ngx-workflow__io-controls select:hover{background:#00000014}.ngx-workflow__io-controls select:focus{outline:none;border-color:var(--ngx-workflow-primary)}.ngx-workflow__io-controls .ngx-workflow__separator{width:1px;height:24px;background:#0000001a;margin:0 4px}.ngx-workflow__io-controls button{padding:6px 12px;background:transparent;border:1px solid transparent;border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;color:var(--ngx-workflow-text-primary);transition:all .2s ease}.ngx-workflow__io-controls button:hover{background:#0000000d;color:var(--ngx-workflow-primary)}.ngx-workflow__io-controls button:active{transform:translateY(1px)}.ngx-workflow__group-node--hover rect{stroke:var(--ngx-workflow-primary-hover)!important;stroke-width:2px!important;stroke-dasharray:4,4;transition:all .2s ease}.ngx-workflow__group-node .ngx-workflow__group-header{fill:#00000008;stroke:none}.ngx-workflow__group-node .ngx-workflow__group-label{font-family:Inter,sans-serif;font-size:11px;font-weight:600;fill:var(--ngx-workflow-text-secondary);text-transform:uppercase;letter-spacing:.5px}.ngx-workflow__group-node .ngx-workflow__group-toggle{cursor:pointer;opacity:.6;transition:opacity .2s}.ngx-workflow__group-node .ngx-workflow__group-toggle:hover{opacity:1}.ngx-workflow__group-node .ngx-workflow__group-toggle-bg{fill:#0000001a}.ngx-workflow__group-node .ngx-workflow__group-toggle-icon{stroke:var(--ngx-workflow-text-secondary);stroke-width:1.5;fill:none}.ngx-workflow__alignment-guide{pointer-events:none}.ngx-workflow__alignment-guide line{stroke:#ec4899;stroke-width:1;stroke-dasharray:4 4;opacity:.8}.ngx-workflow__resize-handle{transform-box:fill-box;transform-origin:center;fill:var(--ngx-workflow-surface);stroke:var(--ngx-workflow-primary);background-color:var(--ngx-workflow-surface);border:1.5px solid var(--ngx-workflow-primary);box-sizing:border-box;pointer-events:auto}.ngx-workflow__resize-handle:hover{fill:var(--ngx-workflow-primary);transform:scale(1.2)}.ngx-workflow__resize-handle[data-handle=nw]{cursor:nw-resize}.ngx-workflow__resize-handle[data-handle=ne]{cursor:ne-resize}.ngx-workflow__resize-handle[data-handle=sw]{cursor:sw-resize}.ngx-workflow__resize-handle[data-handle=se]{cursor:se-resize}.ngx-workflow__resize-handle[data-handle=n]{cursor:n-resize}.ngx-workflow__resize-handle[data-handle=e]{cursor:e-resize}.ngx-workflow__resize-handle[data-handle=s]{cursor:s-resize}.ngx-workflow__resize-handle[data-handle=w]{cursor:w-resize}.ngx-workflow__selection-box{fill:var(--ngx-workflow-primary, #3b82f6);fill-opacity:.1;stroke:var(--ngx-workflow-primary, #3b82f6);stroke-width:1.5;stroke-dasharray:5 5;pointer-events:none}.node-search-match .ngx-workflow__node-rect{stroke:var(--ngx-workflow-primary-color, #3b82f6);stroke-width:2px;filter:drop-shadow(0 0 8px rgba(59,130,246,.6))}.node-search-current .ngx-workflow__node-rect{stroke:var(--ngx-workflow-primary-color, #3b82f6);stroke-width:3px;filter:drop-shadow(0 0 12px rgba(59,130,246,.8));animation:pulse-glow 1.5s ease-in-out infinite}.node-search-dimmed{opacity:.3;transition:opacity .3s ease}@keyframes pulse-glow{0%,to{filter:drop-shadow(0 0 12px rgba(59,130,246,.8))}50%{filter:drop-shadow(0 0 16px rgb(59,130,246))}}.ngx-workflow__node-badge{pointer-events:none;filter:drop-shadow(0 2px 4px rgba(0,0,0,.2))}.ngx-workflow__node-badge circle{transition:all .2s ease}.ngx-workflow__node-badge text{font-family:Inter,sans-serif;-webkit-user-select:none;user-select:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ZoomControlsComponent, selector: "ngx-workflow-zoom-controls", inputs: ["zoom", "minZoom", "maxZoom"], outputs: ["zoomIn", "zoomOut", "fitView", "resetZoom"] }, { kind: "component", type: MinimapComponent, selector: "ngx-workflow-minimap", inputs: ["nodeColor", "nodeClass", "showNodeColors"] }, { kind: "component", type: BackgroundComponent, selector: "ngx-workflow-background", inputs: ["variant", "gap", "size", "color", "backgroundColor", "backgroundImage"] }, { kind: "component", type: GridOverlayComponent, selector: "ngx-workflow-grid-overlay", inputs: ["gridSize", "gridColor", "width", "height"] }, { kind: "component", type: PropertiesSidebarComponent, selector: "ngx-workflow-properties-sidebar", inputs: ["node", "edge"], outputs: ["close", "change", "edgeChange"] }, { kind: "component", type: SearchControlsComponent, selector: "ngx-workflow-search-controls", inputs: ["nodes"], outputs: ["resultSelected", "close", "searchResults"] }, { kind: "component", type: ContextMenuComponent, selector: "ngx-workflow-context-menu" }, { kind: "component", type: ExportControlsComponent, selector: "ngx-workflow-export-controls", outputs: ["exportJSON", "importJSON"] }, { kind: "component", type: LayoutAlignmentControlsComponent, selector: "ngx-workflow-layout-alignment-controls", outputs: ["applyLayout"] }, { kind: "component", type: HandleComponent, selector: "g[ngx-workflow-handle], ngx-workflow-handle", inputs: ["nodeId", "handleId", "type", "isConnectable", "isValidConnection"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6420
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.13", type: DiagramComponent, isStandalone: true, selector: "ngx-workflow-diagram", inputs: { initialNodes: "initialNodes", initialEdges: "initialEdges", initialViewport: "initialViewport", nodesInput: ["nodes", "nodesInput"], edgesInput: ["edges", "edgesInput"], showZoomControls: "showZoomControls", minZoom: "minZoom", maxZoom: "maxZoom", backgroundImage: "backgroundImage", showUndoRedoControls: "showUndoRedoControls", showMinimap: "showMinimap", showBackground: "showBackground", backgroundVariant: "backgroundVariant", backgroundGap: "backgroundGap", backgroundSize: "backgroundSize", backgroundColor: "backgroundColor", backgroundBgColor: "backgroundBgColor", colorMode: "colorMode", gridSize: "gridSize", snapToGrid: "snapToGrid", showGrid: "showGrid", zIndexMode: "zIndexMode", showExportControls: "showExportControls", showLayoutControls: "showLayoutControls", autoSave: "autoSave", autoSaveInterval: "autoSaveInterval", maxVersions: "maxVersions", autoPanOnNodeDrag: "autoPanOnNodeDrag", autoPanOnConnect: "autoPanOnConnect", autoPanSpeed: "autoPanSpeed", autoPanEdgeThreshold: "autoPanEdgeThreshold", maxConnectionsPerHandle: "maxConnectionsPerHandle", preventNodeOverlap: "preventNodeOverlap", nodeSpacing: "nodeSpacing", validateConnection: "validateConnection", edgeTemplate: "edgeTemplate", defsTemplate: "defsTemplate", edgeReconnectable: "edgeReconnectable", proximityThreshold: "proximityThreshold", connectionValidator: "connectionValidator", nodesResizable: "nodesResizable", nodeTypes: "nodeTypes" }, outputs: { nodeClick: "nodeClick", edgeClick: "edgeClick", connect: "connect", nodesChange: "nodesChange", edgesChange: "edgesChange", nodeDoubleClick: "nodeDoubleClick", contextMenu: "contextMenu", nodeMouseEnter: "nodeMouseEnter", nodeMouseLeave: "nodeMouseLeave", nodeMouseMove: "nodeMouseMove", edgeMouseEnter: "edgeMouseEnter", edgeMouseLeave: "edgeMouseLeave", paneClick: "paneClick", paneScroll: "paneScroll", connectStart: "connectStart", connectEnd: "connectEnd", edgeDrop: "edgeDrop", connectionDrop: "connectionDrop", beforeDelete: "beforeDelete" }, host: { listeners: { "contextmenu": "onContextMenu($event)", "window:keydown.delete": "onDeleteKeyPress($event)", "window:keydown.control.z": "onUndoKeyPress($event)", "window:keydown.meta.z": "onUndoKeyPress($event)", "window:keydown.control.shift.z": "onRedoKeyPress($event)", "window:keydown.meta.shift.z": "onRedoKeyPress($event)", "window:keydown.control.]": "onBringToFrontKeyPress($event)", "window:keydown.meta.]": "onBringToFrontKeyPress($event)", "window:keydown.control.[": "onSendToBackKeyPress($event)", "window:keydown.meta.[": "onSendToBackKeyPress($event)", "window:keydown.control.shift.]": "onRaiseLayerKeyPress($event)", "window:keydown.meta.shift.]": "onRaiseLayerKeyPress($event)", "window:keydown.control.shift.[": "onLowerLayerKeyPress($event)", "window:keydown.meta.shift.[": "onLowerLayerKeyPress($event)", "window:keydown.control.a": "onSelectAllKeyPress($event)", "window:keydown.meta.a": "onSelectAllKeyPress($event)", "window:keydown.control.g": "onGroupKeyPress($event)", "window:keydown.meta.g": "onGroupKeyPress($event)", "window:keydown.control.shift.g": "onUngroupKeyPress($event)", "window:keydown.meta.shift.g": "onUngroupKeyPress($event)", "window:keydown.arrowup": "onArrowKeyPress($event)", "window:keydown.arrowdown": "onArrowKeyPress($event)", "window:keydown.arrowleft": "onArrowKeyPress($event)", "window:keydown.arrowright": "onArrowKeyPress($event)", "window:keyup": "onKeyUp($event)", "window:pointermove": "onWindowPointerMove($event)", "window:pointerup": "onWindowPointerUp($event)", "window:keydown": "onKeyDown($event)" } }, queries: [{ propertyName: "edgeLabelTemplate", first: true, predicate: ["edgeLabelTemplate"], descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "svgRef", first: true, predicate: ["svg"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"ngx-workflow__container\" [attr.data-lod]=\"lodLevel\">\r\n <ngx-workflow-background *ngIf=\"showBackground\" [variant]=\"backgroundVariant\" [gap]=\"backgroundGap\"\r\n [size]=\"backgroundSize\" [color]=\"backgroundColor\" [backgroundColor]=\"backgroundBgColor\"\r\n [backgroundImage]=\"backgroundImage\"></ngx-workflow-background>\r\n <ngx-workflow-grid-overlay *ngIf=\"showGrid\" [gridSize]=\"gridSize\"></ngx-workflow-grid-overlay>\r\n <svg #svg class=\"ngx-workflow__diagram\" (wheel)=\"onWheel($event)\" (pointerdown)=\"onPointerDown($event)\"\r\n (pointermove)=\"onPointerMove($event)\" (pointerup)=\"onPointerUp($event)\" (pointerleave)=\"onPointerUp($event)\"\r\n (dblclick)=\"onDiagramDoubleClick($event)\">\r\n <defs>\r\n <pattern id=\"ngx-workflow__grid-pattern\" x=\"0\" y=\"0\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\">\r\n <circle cx=\"0.5\" cy=\"0.5\" r=\"0.5\" fill=\"#ccc\" />\r\n </pattern>\r\n\r\n <!-- Arrow marker -->\r\n <marker id=\"ngx-workflow__arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\"\r\n orient=\"auto-start-reverse\">\r\n <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"context-stroke\" />\r\n </marker>\r\n\r\n <!-- Arrowclosed marker (filled) -->\r\n <marker id=\"ngx-workflow__arrowclosed\" viewBox=\"0 0 10 10\" refX=\"10\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\"\r\n orient=\"auto-start-reverse\">\r\n <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"context-stroke\" stroke=\"context-stroke\" />\r\n </marker>\r\n\r\n <!-- Dot marker -->\r\n <marker id=\"ngx-workflow__dot\" viewBox=\"0 0 10 10\" refX=\"5\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\">\r\n <circle cx=\"5\" cy=\"5\" r=\"4\" fill=\"context-stroke\" />\r\n </marker>\r\n\r\n <!-- Custom Definitions -->\r\n <ng-container *ngTemplateOutlet=\"defsTemplate\"></ng-container>\r\n </defs>\r\n\r\n <rect width=\"100%\" height=\"100%\" fill=\"url(#ngx-workflow__grid-pattern)\" class=\"ngx-workflow__background\" />\r\n\r\n <g class=\"ngx-workflow__viewport\" [attr.transform]=\"transform\">\r\n <!-- Group Backgrounds (Layer 1: Bottom) -->\r\n <g *ngFor=\"let node of sortedNodes(); trackBy: trackByNodeId\">\r\n <g *ngIf=\"node.type === 'group'\"\r\n [attr.transform]=\"'translate(' + (node._renderPosition?.x ?? node.position.x) + ',' + (node._renderPosition?.y ?? node.position.y) + ')'\"\r\n (pointerdown)=\"onNodePointerDown($event, node)\" (dblclick)=\"onNodeDoubleClick($event, node)\"\r\n (mouseenter)=\"onNodeMouseEnter($event, node)\" (mouseleave)=\"onNodeMouseLeave($event, node)\"\r\n (mousemove)=\"onNodeMouseMove($event, node)\" class=\"ngx-workflow__group-node --background-layer\"\r\n [class.ngx-workflow__group-node--hover]=\"hoveredGroupId() === node.id\">\r\n <!-- Only the background rect -->\r\n <rect [attr.width]=\"node.width\" [attr.height]=\"node.height\" rx=\"4\" ry=\"4\"\r\n [style.fill]=\"node.style?.['backgroundColor'] || 'rgba(240, 240, 240, 0.5)'\"\r\n [style.stroke]=\"node.selected ? '#2563eb' : (node.style?.['borderColor'] || '#e5e7eb')\"\r\n [style.stroke-width]=\"node.selected ? 2 : 1\"></rect>\r\n </g>\r\n </g>\r\n\r\n <!-- Edges (Layer 2: Middle) -->\r\n <g *ngFor=\"let edge of filteredEdges(); trackBy: trackByEdgeId\" [class.selected]=\"edge.selected\"\r\n [class.animated]=\"edge.animated && edge.animationType !== 'dot'\" [class.hidden]=\"edge.hidden\"\r\n [class.animated]=\"edge.animated && (edge.animationType === 'flow' || edge.animationType === 'both')\"\r\n [class.hidden]=\"edge.hidden\" [class.shadow]=\"edge.shadow\" class=\"ngx-workflow__edge\"\r\n (click)=\"onEdgeClick($event, edge)\" (dblclick)=\"onEdgeDoubleClick($event, edge)\"\r\n (mouseenter)=\"onEdgeMouseEnter($event, edge)\" (mouseleave)=\"onEdgeMouseLeave($event, edge)\">\r\n <!-- Edge path with markers -->\r\n <path [attr.id]=\"'edge-path-' + edge.id\" [attr.d]=\"getEdgePath(edge)\" class=\"ngx-workflow__edge-path\"\r\n [style]=\"edge.style\" [class.dashed-type]=\"edge.type === 'dashed'\"\r\n [attr.marker-start]=\"getMarkerUrl(edge.markerStart)\" [attr.marker-end]=\"getMarkerUrl(edge.markerEnd)\"\r\n [style.strokeDasharray]=\"edge.style?.['strokeDasharray'] || (edge.type === 'dashed' ? '5,5' : '')\"\r\n [style.animationDuration]=\"edge.animationDuration || '1s'\">\r\n </path>\r\n\r\n <!-- SVG Motion Animation (Dot) -->\r\n <circle *ngIf=\"edge.animated && (edge.animationType === 'dot' || edge.animationType === 'both')\" r=\"4\"\r\n [attr.fill]=\"edge.animationStyle?.['fill'] || '#3b82f6'\">\r\n <animateMotion [attr.dur]=\"edge.animationDuration || '2s'\" repeatCount=\"indefinite\">\r\n <mpath [attr.href]=\"'#edge-path-' + edge.id\"></mpath>\r\n </animateMotion>\r\n </circle>\r\n <!-- Invisible hitbox for easier clicking -->\r\n <path [attr.d]=\"getEdgePath(edge)\" class=\"ngx-workflow__edge-hitbox\"></path>\r\n\r\n <!-- Edge label -->\r\n <g *ngIf=\"edge.label || edge.data?.labelContent || editingEdgeId === edge.id\" class=\"ngx-workflow__edge-label\">\r\n <!-- Editing mode (input field) -->\r\n <foreignObject *ngIf=\"editingEdgeId === edge.id\" [attr.x]=\"getEdgeLabelPosition(edge).x - 50\"\r\n [attr.y]=\"getEdgeLabelPosition(edge).y - 15\" width=\"100\" height=\"30\">\r\n <input #edgeLabelInput type=\"text\" [value]=\"edge.label || ''\" (blur)=\"onEdgeLabelBlur(edge)\"\r\n (keydown.enter)=\"onEdgeLabelBlur(edge)\" (input)=\"onEdgeLabelInput($event)\"\r\n class=\"ngx-workflow__edge-label-input\" autofocus>\r\n </foreignObject>\r\n\r\n <!-- Custom template mode (Angular component) -->\r\n <foreignObject *ngIf=\"editingEdgeId !== edge.id && edgeLabelTemplate\"\r\n [attr.x]=\"getEdgeLabelPosition(edge).x - 75\" [attr.y]=\"getEdgeLabelPosition(edge).y - 20\" width=\"150\"\r\n height=\"40\" style=\"pointer-events: none;\">\r\n <div xmlns=\"http://www.w3.org/1999/xhtml\" class=\"edge-label-content\">\r\n <ng-container *ngTemplateOutlet=\"edgeLabelTemplate; context: { $implicit: edge, edge: edge }\">\r\n </ng-container>\r\n </div>\r\n </foreignObject>\r\n\r\n <!-- Fallback to text mode -->\r\n <text *ngIf=\"editingEdgeId !== edge.id && !edgeLabelTemplate && edge.label\"\r\n [attr.x]=\"getEdgeLabelPosition(edge).x\" [attr.y]=\"getEdgeLabelPosition(edge).y\" text-anchor=\"middle\"\r\n dominant-baseline=\"middle\" class=\"ngx-workflow__edge-label-text\" [style]=\"edge.labelStyle\">\r\n <!-- Label background -->\r\n <tspan *ngIf=\"edge.labelBgStyle\" class=\"ngx-workflow__edge-label-bg\" [style]=\"edge.labelBgStyle\"></tspan>\r\n {{ edge.label }}\r\n </text>\r\n </g>\r\n\r\n <!-- Edge Update Handles -->\r\n <g *ngIf=\"edge.selected && edgeReconnectable\">\r\n <!-- Source Handle -->\r\n <circle class=\"ngx-workflow__edge-reconnect-handle\" [attr.cx]=\"getEdgeHandlePosition(edge, 'source').x\"\r\n [attr.cy]=\"getEdgeHandlePosition(edge, 'source').y\" r=\"8\" fill=\"#3b82f6\" stroke=\"white\" stroke-width=\"2\"\r\n style=\"cursor: grab; pointer-events: all;\" (pointerdown)=\"startUpdatingEdge($event, edge, 'source')\">\r\n </circle>\r\n <!-- Target Handle -->\r\n <circle class=\"ngx-workflow__edge-reconnect-handle\" [attr.cx]=\"getEdgeHandlePosition(edge, 'target').x\"\r\n [attr.cy]=\"getEdgeHandlePosition(edge, 'target').y\" r=\"8\" fill=\"#3b82f6\" stroke=\"white\" stroke-width=\"2\"\r\n style=\"cursor: grab; pointer-events: all;\" (pointerdown)=\"startUpdatingEdge($event, edge, 'target')\">\r\n </circle>\r\n </g>\r\n </g>\r\n\r\n <!-- Render temporary edges (previews) -->\r\n <g *ngFor=\"let tempEdge of tempEdges(); trackBy: trackByEdgeId\" [class.animated]=\"tempEdge.animated\"\r\n class=\"ngx-workflow__edge\">\r\n <path [attr.d]=\"getEdgePath(tempEdge, true)\" class=\"ngx-workflow__edge-path\" [style]=\"tempEdge.style\"></path>\r\n </g>\r\n\r\n <!-- Selection Box (Rubber Band) -->\r\n <rect *ngIf=\"isBoxSelecting\" class=\"ngx-workflow__selection-box\" [attr.x]=\"getSelectionBox().x\"\r\n [attr.y]=\"getSelectionBox().y\" [attr.width]=\"getSelectionBox().width\"\r\n [attr.height]=\"getSelectionBox().height\" />\r\n\r\n <!-- Nodes -->\r\n <g *ngFor=\"let node of sortedNodes(); trackBy: trackByNodeId\" class=\"ngx-workflow__node\" [attr.data-id]=\"node.id\"\r\n [attr.transform]=\"'translate(' + (node._renderPosition?.x ?? node.position.x) + ',' + (node._renderPosition?.y ?? node.position.y) + ')'\"\r\n (pointerdown)=\"onNodePointerDown($event, node)\" (dblclick)=\"onNodeDoubleClick($event, node)\"\r\n (mouseenter)=\"onNodeMouseEnter($event, node)\" (mouseleave)=\"onNodeMouseLeave($event, node)\"\r\n (mousemove)=\"onNodeMouseMove($event, node)\">\r\n\r\n <!-- Custom Node Template -->\r\n <foreignObject *ngIf=\"isCustomNode(node)\" [attr.width]=\"node.width || defaultNodeWidth\"\r\n [attr.height]=\"node.height || defaultNodeHeight\" style=\"overflow: visible;\">\r\n <div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"width: 100%; height: 100%;\">\r\n <ng-container *ngComponentOutlet=\"getCustomNodeComponent(node.type); inputs: { \r\n id: node.id, \r\n data: node.data, \r\n selected: node.selected,\r\n type: node.type,\r\n pos: node.position,\r\n easyConnect: node.easyConnect,\r\n style: node.style,\r\n ports: node.ports,\r\n label: node.label\r\n }\"></ng-container>\r\n </div>\r\n </foreignObject>\r\n\r\n <!-- Default Node Template (Rect + Text) -->\r\n <g *ngIf=\"!isCustomNode(node) && node.type !== 'group'\">\r\n <!-- Node outline and background (visual + dragging) -->\r\n <rect [attr.width]=\"node.width || defaultNodeWidth\" [attr.height]=\"node.height || defaultNodeHeight\" rx=\"3\"\r\n ry=\"3\" class=\"ngx-workflow__node-rect\"\r\n [style.filter]=\"node.shadow === true ? 'drop-shadow(0 10px 15px rgba(0,0,0,0.2))' : (node.shadow || null)\"\r\n [style.stroke-dasharray]=\"node.borderStyle === 'dashed' ? '5,5' : (node.borderStyle === 'dotted' ? '2,2' : null)\"\r\n [style.stroke]=\"node.borderColor\" [style.stroke-width]=\"node.borderWidth\"\r\n [style.fill]=\"node.style?.['backgroundColor'] || 'var(--ngx-workflow-surface, #ffffff)'\"></rect>\r\n\r\n <!-- Node Badges -->\r\n <g *ngIf=\"node.badges?.length\">\r\n <g *ngFor=\"let badge of node.badges; let i = index\" class=\"ngx-workflow__node-badge\"\r\n [attr.transform]=\"getBadgeTransform(node, badge, i)\">\r\n <circle r=\"10\" [attr.fill]=\"badge.backgroundColor || '#ef4444'\" [attr.stroke]=\"'#ffffff'\"\r\n stroke-width=\"2\">\r\n </circle>\r\n <text dy=\"0.3em\" text-anchor=\"middle\" font-size=\"10px\" font-weight=\"bold\"\r\n [attr.fill]=\"badge.color || '#ffffff'\">{{ badge.content }}</text>\r\n </g>\r\n </g>\r\n\r\n <!-- Node Label -->\r\n <text [attr.x]=\"(node.width || defaultNodeWidth) / 2\" [attr.y]=\"(node.height || defaultNodeHeight) / 2\"\r\n text-anchor=\"middle\" dominant-baseline=\"middle\" class=\"ngx-workflow__node-label\"\r\n [style.fill]=\"node.style?.['color'] || 'black'\">\r\n {{ node.label }}\r\n </text>\r\n </g>\r\n\r\n <!-- Only render default handles if NOT custom node? Or maybe keep them? \r\n Usually custom nodes implement their own handles.\r\n Let's allow default handles to show only if NOT custom node for now to start clean.\r\n -->\r\n <ng-container *ngIf=\"!isCustomNode(node)\">\r\n <!-- Top Handle -->\r\n <g *ngIf=\"!node.ports || node.ports === 1 || node.ports === 2 || node.ports === 4\" ngx-workflow-handle\r\n class=\"ngx-workflow__handle ngx-workflow__handle--source ngx-workflow__handle--target\"\r\n [attr.data-nodeid]=\"node.id\" data-handleid=\"top\" [attr.data-type]=\"'source'\" [nodeId]=\"node.id\"\r\n handleId=\"top\" type=\"source\" [isConnectable]=\"node.handleConfig?.['top']?.isConnectable\"\r\n (pointerdown)=\"onHandlePointerDown($event, node, 'top')\">\r\n <!-- Hit area -->\r\n <circle [attr.cx]=\"(node.width || defaultNodeWidth) / 2\" cy=\"-10\" r=\"20\" fill=\"transparent\"\r\n pointer-events=\"all\"></circle>\r\n <!-- Visual -->\r\n <circle [attr.cx]=\"(node.width || defaultNodeWidth) / 2\" [attr.cy]=\"0\" r=\"6\"\r\n class=\"ngx-workflow__handle-circle\">\r\n </circle>\r\n </g>\r\n\r\n <!-- Right Handle -->\r\n <g *ngIf=\"!node.ports || node.ports === 3 || node.ports === 4\" ngx-workflow-handle\r\n class=\"ngx-workflow__handle ngx-workflow__handle--source ngx-workflow__handle--target\"\r\n [attr.data-nodeid]=\"node.id\" data-handleid=\"right\" [attr.data-type]=\"'source'\" [nodeId]=\"node.id\"\r\n handleId=\"right\" type=\"source\" [isConnectable]=\"node.handleConfig?.['right']?.isConnectable\"\r\n (pointerdown)=\"onHandlePointerDown($event, node, 'right')\">\r\n <!-- Hit area -->\r\n <circle [attr.cx]=\"(node.width || defaultNodeWidth) + 10\" [attr.cy]=\"(node.height || defaultNodeHeight) / 2\"\r\n r=\"20\" fill=\"transparent\" pointer-events=\"all\">\r\n </circle>\r\n <!-- Visual -->\r\n <circle [attr.cx]=\"(node.width || defaultNodeWidth)\" [attr.cy]=\"(node.height || defaultNodeHeight) / 2\"\r\n r=\"6\" class=\"ngx-workflow__handle-circle\"></circle>\r\n </g>\r\n <!-- Bottom Handle -->\r\n <g *ngIf=\"!node.ports || node.ports === 2 || node.ports === 4\" ngx-workflow-handle\r\n class=\"ngx-workflow__handle ngx-workflow__handle--source ngx-workflow__handle--target\"\r\n [attr.data-nodeid]=\"node.id\" data-handleid=\"bottom\" [attr.data-type]=\"'source'\" [nodeId]=\"node.id\"\r\n handleId=\"bottom\" type=\"source\" [isConnectable]=\"node.handleConfig?.['bottom']?.isConnectable\"\r\n (pointerdown)=\"onHandlePointerDown($event, node, 'bottom')\">\r\n <!-- Hit area -->\r\n <circle [attr.cx]=\"(node.width || defaultNodeWidth) / 2\" [attr.cy]=\"(node.height || defaultNodeHeight) + 10\"\r\n r=\"20\" fill=\"transparent\" pointer-events=\"all\">\r\n </circle>\r\n <!-- Visual -->\r\n <circle [attr.cx]=\"(node.width || defaultNodeWidth) / 2\" [attr.cy]=\"(node.height || defaultNodeHeight)\"\r\n r=\"6\" class=\"ngx-workflow__handle-circle\"></circle>\r\n </g>\r\n <!-- Left Handle -->\r\n <g *ngIf=\"!node.ports || node.ports === 3 || node.ports === 4\" ngx-workflow-handle\r\n class=\"ngx-workflow__handle ngx-workflow__handle--source ngx-workflow__handle--target\"\r\n [attr.data-nodeid]=\"node.id\" data-handleid=\"left\" [attr.data-type]=\"'source'\" [nodeId]=\"node.id\"\r\n handleId=\"left\" type=\"source\" [isConnectable]=\"node.handleConfig?.['left']?.isConnectable\"\r\n (pointerdown)=\"onHandlePointerDown($event, node, 'left')\">\r\n <!-- Hit area -->\r\n <circle cx=\"-10\" [attr.cy]=\"(node.height || defaultNodeHeight) / 2\" r=\"20\" fill=\"transparent\"\r\n pointer-events=\"all\"></circle>\r\n <!-- Visual -->\r\n <circle cx=\"0\" [attr.cy]=\"(node.height || defaultNodeHeight) / 2\" r=\"6\" class=\"ngx-workflow__handle-circle\">\r\n </circle>\r\n </g>\r\n </ng-container>\r\n\r\n <!-- Resize Handles (HTML implementation to ensure they are on top of custom nodes) -->\r\n <foreignObject *ngIf=\"node.selected && (node.resizable !== false) && nodesResizable\"\r\n [attr.width]=\"node.width || defaultNodeWidth\" [attr.height]=\"node.height || defaultNodeHeight\"\r\n style=\"overflow: visible; pointer-events: none;\">\r\n <div style=\"position: relative; width: 100%; height: 100%; pointer-events: none;\">\r\n <!-- NW Handle -->\r\n <div class=\"ngx-workflow__resize-handle\" data-handle=\"nw\"\r\n style=\"position: absolute; top: -5px; left: -5px; width: 10px; height: 10px; cursor: nw-resize; pointer-events: auto;\"\r\n (pointerdown)=\"startResizing($event, node, 'nw')\"></div>\r\n <!-- NE Handle -->\r\n <div class=\"ngx-workflow__resize-handle\" data-handle=\"ne\"\r\n style=\"position: absolute; top: -5px; right: -5px; width: 10px; height: 10px; cursor: ne-resize; pointer-events: auto;\"\r\n (pointerdown)=\"startResizing($event, node, 'ne')\"></div>\r\n <!-- SW Handle -->\r\n <div class=\"ngx-workflow__resize-handle\" data-handle=\"sw\"\r\n style=\"position: absolute; bottom: -5px; left: -5px; width: 10px; height: 10px; cursor: sw-resize; pointer-events: auto;\"\r\n (pointerdown)=\"startResizing($event, node, 'sw')\"></div>\r\n <!-- SE Handle -->\r\n <div class=\"ngx-workflow__resize-handle\" data-handle=\"se\"\r\n style=\"position: absolute; bottom: -5px; right: -5px; width: 10px; height: 10px; cursor: se-resize; pointer-events: auto;\"\r\n (pointerdown)=\"startResizing($event, node, 'se')\"></div>\r\n </div>\r\n </foreignObject>\r\n\r\n <!-- Group Node Template (Layer 3: Top - Content Only) -->\r\n <g *ngIf=\"node.type === 'group'\" class=\"ngx-workflow__group-node\">\r\n <!-- Background moved to Layer 1, but we keep a transparent hit area or just rely on content? \r\n actually Layer 1 handles interactions for the body.\r\n We just need Header and Content here.\r\n -->\r\n\r\n <!-- Header -->\r\n <rect [attr.width]=\"node.width\" height=\"30\" class=\"ngx-workflow__group-header\" rx=\"4\" ry=\"4\"></rect>\r\n <text [attr.x]=\"10\" [attr.y]=\"20\" class=\"ngx-workflow__group-label\">{{ node.label }}</text>\r\n\r\n <!-- Expand/Collapse Toggle -->\r\n <g class=\"ngx-workflow__group-toggle\" [attr.transform]=\"'translate(' + (node.width! - 25) + ', 5)'\"\r\n (click)=\"toggleGroup($event, node)\">\r\n <rect width=\"20\" height=\"20\" rx=\"4\" class=\"ngx-workflow__group-toggle-bg\"></rect>\r\n <path [attr.d]=\"node.expanded ? 'M 5 10 L 15 10' : 'M 5 10 L 15 10 M 10 5 L 10 15'\"\r\n class=\"ngx-workflow__group-toggle-icon\"></path>\r\n </g>\r\n </g>\r\n </g>\r\n\r\n <!-- Alignment Guides -->\r\n <g *ngFor=\"let guide of alignmentGuides()\" class=\"ngx-workflow__alignment-guide\">\r\n <line *ngIf=\"guide.type === 'horizontal'\" [attr.x1]=\"guide.start\" [attr.y1]=\"guide.position\"\r\n [attr.x2]=\"guide.end\" [attr.y2]=\"guide.position\" />\r\n <line *ngIf=\"guide.type === 'vertical'\" [attr.x1]=\"guide.position\" [attr.y1]=\"guide.start\"\r\n [attr.x2]=\"guide.position\" [attr.y2]=\"guide.end\" />\r\n </g>\r\n </g>\r\n\r\n </svg>\r\n\r\n\r\n <!-- Unified Top Bar -->\r\n <div class=\"ngx-workflow__top-bar\">\r\n <ngx-workflow-search-controls [nodes]=\"nodes()\" (searchResults)=\"onSearchResults($event)\"\r\n (resultSelected)=\"onSearchResultSelected($event)\" (close)=\"onSearchClose()\">\r\n </ngx-workflow-search-controls>\r\n\r\n <div class=\"divider\"></div>\r\n\r\n <ngx-workflow-layout-alignment-controls *ngIf=\"showLayoutControls\"\r\n (applyLayout)=\"onApplyLayout($any($event))\"></ngx-workflow-layout-alignment-controls>\r\n\r\n <div class=\"divider\"></div>\r\n\r\n <ngx-workflow-export-controls *ngIf=\"showExportControls\" (exportJSON)=\"exportToJSON()\"\r\n (importJSON)=\"triggerImport()\">\r\n </ngx-workflow-export-controls>\r\n </div>\r\n\r\n <ngx-workflow-minimap *ngIf=\"showMinimap\"></ngx-workflow-minimap>\r\n\r\n <ngx-workflow-zoom-controls *ngIf=\"showZoomControls\" [zoom]=\"viewport().zoom\" [minZoom]=\"minZoom\" [maxZoom]=\"maxZoom\"\r\n (zoomIn)=\"onZoomChange(viewport().zoom + 0.1)\" (zoomOut)=\"onZoomChange(viewport().zoom - 0.1)\"\r\n (fitView)=\"onZoomChange(1)\" (resetZoom)=\"onZoomChange(1)\"></ngx-workflow-zoom-controls>\r\n\r\n <ngx-workflow-properties-sidebar [node]=\"selectedNodeForEditing\" [edge]=\"selectedEdgeForEditing\"\r\n (close)=\"closeSidebar()\" (change)=\"onPropertiesChange($event)\"\r\n (edgeChange)=\"onEdgePropertiesChange($event)\"></ngx-workflow-properties-sidebar>\r\n\r\n <!-- Node Toolbars (projected content) -->\r\n <ng-content select=\"ngx-workflow-node-toolbar\"></ng-content>\r\n\r\n <!-- Panels (projected content) -->\r\n <ng-content select=\"ngx-workflow-panel\"></ng-content>\r\n\r\n <ngx-workflow-context-menu></ngx-workflow-context-menu>\r\n</div>", styles: [":root{--ngx-workflow-bg: #ffffff;--ngx-workflow-bg-pattern: #f0f0f0;--ngx-workflow-node-bg: #ffffff;--ngx-workflow-node-border: #d1d5db;--ngx-workflow-node-color: #1f2937;--ngx-workflow-node-selected-border: #3b82f6;--ngx-workflow-node-shadow: rgba(0, 0, 0, .1);--ngx-workflow-edge-stroke: #9ca3af;--ngx-workflow-edge-stroke-selected: #3b82f6;--ngx-workflow-edge-label-bg: #ffffff;--ngx-workflow-edge-label-color: #374151;--ngx-workflow-controls-bg: #ffffff;--ngx-workflow-controls-border: #d1d5db;--ngx-workflow-controls-color: #374151;--ngx-workflow-controls-hover-bg: #f3f4f6;--ngx-workflow-minimap-bg: #f9fafb;--ngx-workflow-minimap-border: #d1d5db;--ngx-workflow-minimap-node: #3b82f6;--ngx-workflow-minimap-viewport: rgba(59, 130, 246, .2);--ngx-workflow-sidebar-bg: #ffffff;--ngx-workflow-sidebar-border: #d1d5db;--ngx-workflow-sidebar-color: #1f2937;--ngx-workflow-menu-bg: #ffffff;--ngx-workflow-menu-border: #d1d5db;--ngx-workflow-menu-color: #374151;--ngx-workflow-menu-hover-bg: #f3f4f6;--ngx-workflow-search-bg: #ffffff;--ngx-workflow-search-border: #d1d5db;--ngx-workflow-search-color: #374151;--ngx-workflow-toolbar-bg: #ffffff;--ngx-workflow-toolbar-border: #d1d5db;--ngx-workflow-toolbar-shadow: rgba(0, 0, 0, .1);--ngx-workflow-panel-bg: transparent;--ngx-workflow-panel-z-index: 5;--ngx-workflow-panel-offset: 10px}html[data-theme=dark],html.dark-theme{--ngx-workflow-bg: #1f2937;--ngx-workflow-bg-pattern: #374151;--ngx-workflow-node-bg: #374151;--ngx-workflow-node-border: #4b5563;--ngx-workflow-node-color: #f9fafb;--ngx-workflow-node-selected-border: #60a5fa;--ngx-workflow-node-shadow: rgba(0, 0, 0, .3);--ngx-workflow-edge-stroke: #6b7280;--ngx-workflow-edge-stroke-selected: #60a5fa;--ngx-workflow-edge-label-bg: #374151;--ngx-workflow-edge-label-color: #f9fafb;--ngx-workflow-controls-bg: #374151;--ngx-workflow-controls-border: #4b5563;--ngx-workflow-controls-color: #f9fafb;--ngx-workflow-controls-hover-bg: #4b5563;--ngx-workflow-minimap-bg: #111827;--ngx-workflow-minimap-border: #4b5563;--ngx-workflow-minimap-node: #60a5fa;--ngx-workflow-minimap-viewport: rgba(96, 165, 250, .2);--ngx-workflow-sidebar-bg: #374151;--ngx-workflow-sidebar-border: #4b5563;--ngx-workflow-sidebar-color: #f9fafb;--ngx-workflow-menu-bg: #374151;--ngx-workflow-menu-border: #4b5563;--ngx-workflow-menu-color: #f9fafb;--ngx-workflow-menu-hover-bg: #4b5563;--ngx-workflow-search-bg: #374151;--ngx-workflow-search-border: #4b5563;--ngx-workflow-search-color: #f9fafb;--ngx-workflow-toolbar-bg: #374151;--ngx-workflow-toolbar-border: #4b5563;--ngx-workflow-toolbar-shadow: rgba(0, 0, 0, .5);--ngx-workflow-panel-bg: transparent}:host{--ngx-workflow-primary: #3b82f6;--ngx-workflow-primary-hover: #2563eb;--ngx-workflow-surface: var(--ngx-workflow-node-bg, #ffffff);--ngx-workflow-border: var(--ngx-workflow-node-border, #e2e8f0);--ngx-workflow-text-primary: var(--ngx-workflow-node-color, #1e293b);--ngx-workflow-text-secondary: #64748b;--ngx-workflow-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / .05);--ngx-workflow-shadow-md: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--ngx-workflow-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--ngx-workflow-glass-bg: rgba(255, 255, 255, .8);--ngx-workflow-glass-border: rgba(255, 255, 255, .5);--ngx-workflow-glass-blur: blur(12px);display:block;width:100%;height:100%;font-family:Inter,system-ui,-apple-system,sans-serif}:host-context(html[data-theme=dark]),:host-context(html.dark-theme){--ngx-workflow-surface: #374151;--ngx-workflow-border: #4b5563;--ngx-workflow-text-primary: #f9fafb;--ngx-workflow-glass-bg: rgba(55, 65, 81, .8);--ngx-workflow-glass-border: rgba(75, 85, 99, .5)}.ngx-workflow__container{position:relative;width:100%;height:100%;overflow:hidden;background-color:var(--ngx-workflow-bg, #f8fafc)}.ngx-workflow__container[data-lod=low] .ngx-workflow__node-label,.ngx-workflow__container[data-lod=low] .ngx-workflow__handle,.ngx-workflow__container[data-lod=low] .ngx-workflow__edge-label{display:none}.ngx-workflow__container[data-lod=low] .ngx-workflow__node-rect{stroke-width:1px}.ngx-workflow__container[data-lod=medium] .ngx-workflow__handle{opacity:0;pointer-events:none}.ngx-workflow__diagram{width:100%;height:100%;cursor:grab;position:relative;z-index:1}.ngx-workflow__diagram:active{cursor:grabbing}.ngx-workflow__background{fill:url(#ngx-workflow__grid-pattern);opacity:.6}.ngx-workflow__viewport{transform-origin:0 0}.ngx-workflow__node{cursor:grab;transition:transform .1s ease,opacity .2s ease}.ngx-workflow__node .ngx-workflow__node-rect{fill:var(--ngx-workflow-surface);stroke:var(--ngx-workflow-border);stroke-width:1.5px;filter:drop-shadow(0 2px 4px rgba(0,0,0,.05));transition:all .2s cubic-bezier(.4,0,.2,1);pointer-events:all}.ngx-workflow__node .ngx-workflow__node-label{font-size:12px;font-weight:500;fill:var(--ngx-workflow-text-primary);pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-workflow__node:hover .ngx-workflow__node-rect{stroke:var(--ngx-workflow-primary);filter:drop-shadow(0 4px 6px rgba(0,0,0,.08));transform:translateY(-1px)}.ngx-workflow__node.selected .ngx-workflow__node-rect{stroke:var(--ngx-workflow-primary);stroke-width:2px;filter:drop-shadow(0 0 0 2px rgba(59,130,246,.2))}.ngx-workflow__node.dragging{cursor:grabbing;opacity:.9}.ngx-workflow__node.dragging .ngx-workflow__node-rect{filter:drop-shadow(0 10px 15px rgba(0,0,0,.1));transform:scale(1.02)}.ngx-workflow__node.highlighted .ngx-workflow__node-rect{stroke:var(--ngx-workflow-primary);stroke-width:2px;filter:drop-shadow(0 0 0 4px rgba(59,130,246,.4))}.ngx-workflow__node.dimmed{opacity:.4}.ngx-workflow__node.colliding .ngx-workflow__node-rect{stroke:#ef4444!important;stroke-width:3px;animation:shake .3s ease-in-out}@keyframes shake{0%,to{transform:translate(0)}25%{transform:translate(-2px)}75%{transform:translate(2px)}}:host-context(html[data-theme=dark]) .ngx-workflow__node-rect,:host-context(html.dark-theme) .ngx-workflow__node-rect{filter:drop-shadow(0 2px 4px rgba(0,0,0,.3))}:host-context(html[data-theme=dark]) .ngx-workflow__node:hover .ngx-workflow__node-rect,:host-context(html.dark-theme) .ngx-workflow__node:hover .ngx-workflow__node-rect{filter:drop-shadow(0 4px 6px rgba(0,0,0,.4))}foreignObject{overflow:visible;pointer-events:none}foreignObject *{pointer-events:auto}.ngx-workflow__edge{pointer-events:none}.ngx-workflow__edge-path{fill:none;stroke:#94a3b8;stroke-width:2;transition:stroke .2s ease,stroke-width .2s ease;pointer-events:stroke;cursor:pointer}.ngx-workflow__edge-hitbox{fill:none;stroke:transparent;stroke-width:20;pointer-events:stroke;cursor:pointer}.ngx-workflow__edge:hover .ngx-workflow__edge-path{stroke:var(--ngx-workflow-primary)}.ngx-workflow__edge.selected .ngx-workflow__edge-path{stroke:var(--ngx-workflow-primary);stroke-width:3;filter:drop-shadow(0 1px 2px rgba(59,130,246,.3))}.ngx-workflow__edge.animated .ngx-workflow__edge-path{stroke-dasharray:10;animation:flowAnimation 1s linear infinite}.ngx-workflow__edge.hidden{opacity:0;pointer-events:none}.ngx-workflow__edge.shadow .ngx-workflow__edge-path{filter:drop-shadow(0 2px 3px rgba(0,0,0,.3))}.cursor-crosshair{cursor:crosshair!important}.cursor-grab{cursor:grab!important}.cursor-grabbing{cursor:grabbing!important}.cursor-pointer{cursor:pointer!important}@keyframes flowAnimation{0%{stroke-dashoffset:20}to{stroke-dashoffset:0}}.ngx-workflow__edge-label{pointer-events:none}.ngx-workflow__edge-label-text{font-family:Inter,sans-serif;font-size:12px;font-weight:500;fill:var(--ngx-workflow-text-secondary);text-anchor:middle;dominant-baseline:middle}.ngx-workflow__edge-label-bg{fill:var(--ngx-workflow-surface);rx:4;ry:4;filter:drop-shadow(0 1px 2px rgba(0,0,0,.1))}.ngx-workflow__edge-label-input{width:100%;height:100%;border:1px solid var(--ngx-workflow-primary);border-radius:4px;padding:2px 6px;font-size:12px;text-align:center;background:var(--ngx-workflow-surface);pointer-events:all;outline:none;box-shadow:var(--ngx-workflow-shadow-sm)}.edge-label-content{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.edge-label-content>*{pointer-events:all}.ngx-workflow__handle{opacity:1;transition:transform .2s cubic-bezier(.34,1.56,.64,1);pointer-events:all}.ngx-workflow__handle-circle{fill:var(--ngx-workflow-surface);stroke:var(--ngx-workflow-text-secondary);stroke-width:1.5;transition:all .2s ease}.ngx-workflow__handle-circle:hover{fill:var(--ngx-workflow-primary);stroke:var(--ngx-workflow-primary);r:7}.ngx-workflow__handle.ngx-workflow__handle--valid-target .ngx-workflow__handle-circle{fill:#10b981!important;stroke:#059669!important;r:8;stroke-width:2}.ngx-workflow__handle.ngx-workflow__handle--invalid-target .ngx-workflow__handle-circle{fill:#ef4444!important;stroke:#dc2626!important;r:7;stroke-width:2;opacity:.6;cursor:not-allowed}.ngx-workflow__selection-box{pointer-events:none;animation:selectionPulse 1.5s ease-in-out infinite}@keyframes selectionPulse{0%,to{opacity:.8}50%{opacity:1}}@keyframes invalidConnectionShake{0%,to{transform:translate(0)}25%{transform:translate(-4px)}75%{transform:translate(4px)}}.invalid-connection{animation:invalidConnectionShake .3s ease-in-out 2}.invalid-connection .ngx-workflow__node-rect{stroke:#ef4444!important;stroke-width:3px}.ngx-workflow__top-bar{position:absolute;top:16px;left:50%;transform:translate(-50%);z-index:10;display:flex;align-items:center;gap:8px;padding:6px;background:var(--ngx-workflow-glass-bg);-webkit-backdrop-filter:var(--ngx-workflow-glass-blur);backdrop-filter:var(--ngx-workflow-glass-blur);border:1px solid var(--ngx-workflow-glass-border);border-radius:12px;box-shadow:var(--ngx-workflow-shadow-md)}.ngx-workflow__top-bar .divider{width:1px;height:24px;background:#0000001a;margin:0 4px}.ngx-workflow__io-controls{position:absolute;top:16px;right:16px;z-index:10;display:flex;gap:8px;padding:8px;background:var(--ngx-workflow-glass-bg);-webkit-backdrop-filter:var(--ngx-workflow-glass-blur);backdrop-filter:var(--ngx-workflow-glass-blur);border:1px solid var(--ngx-workflow-glass-border);border-radius:12px;box-shadow:var(--ngx-workflow-shadow-md);align-items:center}.ngx-workflow__io-controls input[type=text]{padding:6px 12px;border:1px solid transparent;border-radius:8px;font-size:13px;background:#0000000d;transition:all .2s ease;width:160px}.ngx-workflow__io-controls input[type=text]:focus{background:#fff;border-color:var(--ngx-workflow-primary);outline:none;box-shadow:0 0 0 2px #3b82f61a}.ngx-workflow__io-controls select{padding:6px 12px;border:1px solid transparent;border-radius:8px;font-size:13px;background:#0000000d;cursor:pointer;transition:all .2s ease}.ngx-workflow__io-controls select:hover{background:#00000014}.ngx-workflow__io-controls select:focus{outline:none;border-color:var(--ngx-workflow-primary)}.ngx-workflow__io-controls .ngx-workflow__separator{width:1px;height:24px;background:#0000001a;margin:0 4px}.ngx-workflow__io-controls button{padding:6px 12px;background:transparent;border:1px solid transparent;border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;color:var(--ngx-workflow-text-primary);transition:all .2s ease}.ngx-workflow__io-controls button:hover{background:#0000000d;color:var(--ngx-workflow-primary)}.ngx-workflow__io-controls button:active{transform:translateY(1px)}.ngx-workflow__group-node--hover rect{stroke:var(--ngx-workflow-primary-hover)!important;stroke-width:2px!important;stroke-dasharray:4,4;transition:all .2s ease}.ngx-workflow__group-node .ngx-workflow__group-header{fill:#00000008;stroke:none}.ngx-workflow__group-node .ngx-workflow__group-label{font-family:Inter,sans-serif;font-size:11px;font-weight:600;fill:var(--ngx-workflow-text-secondary);text-transform:uppercase;letter-spacing:.5px}.ngx-workflow__group-node .ngx-workflow__group-toggle{cursor:pointer;opacity:.6;transition:opacity .2s}.ngx-workflow__group-node .ngx-workflow__group-toggle:hover{opacity:1}.ngx-workflow__group-node .ngx-workflow__group-toggle-bg{fill:#0000001a}.ngx-workflow__group-node .ngx-workflow__group-toggle-icon{stroke:var(--ngx-workflow-text-secondary);stroke-width:1.5;fill:none}.ngx-workflow__alignment-guide{pointer-events:none}.ngx-workflow__alignment-guide line{stroke:#ec4899;stroke-width:1;stroke-dasharray:4 4;opacity:.8}.ngx-workflow__resize-handle{transform-box:fill-box;transform-origin:center;fill:var(--ngx-workflow-surface);stroke:var(--ngx-workflow-primary);background-color:var(--ngx-workflow-surface);border:1.5px solid var(--ngx-workflow-primary);box-sizing:border-box;pointer-events:auto}.ngx-workflow__resize-handle:hover{fill:var(--ngx-workflow-primary);transform:scale(1.2)}.ngx-workflow__resize-handle[data-handle=nw]{cursor:nw-resize}.ngx-workflow__resize-handle[data-handle=ne]{cursor:ne-resize}.ngx-workflow__resize-handle[data-handle=sw]{cursor:sw-resize}.ngx-workflow__resize-handle[data-handle=se]{cursor:se-resize}.ngx-workflow__resize-handle[data-handle=n]{cursor:n-resize}.ngx-workflow__resize-handle[data-handle=e]{cursor:e-resize}.ngx-workflow__resize-handle[data-handle=s]{cursor:s-resize}.ngx-workflow__resize-handle[data-handle=w]{cursor:w-resize}.ngx-workflow__selection-box{fill:var(--ngx-workflow-primary, #3b82f6);fill-opacity:.1;stroke:var(--ngx-workflow-primary, #3b82f6);stroke-width:1.5;stroke-dasharray:5 5;pointer-events:none}.node-search-match .ngx-workflow__node-rect{stroke:var(--ngx-workflow-primary-color, #3b82f6);stroke-width:2px;filter:drop-shadow(0 0 8px rgba(59,130,246,.6))}.node-search-current .ngx-workflow__node-rect{stroke:var(--ngx-workflow-primary-color, #3b82f6);stroke-width:3px;filter:drop-shadow(0 0 12px rgba(59,130,246,.8));animation:pulse-glow 1.5s ease-in-out infinite}.node-search-dimmed{opacity:.3;transition:opacity .3s ease}@keyframes pulse-glow{0%,to{filter:drop-shadow(0 0 12px rgba(59,130,246,.8))}50%{filter:drop-shadow(0 0 16px rgb(59,130,246))}}.ngx-workflow__node-badge{pointer-events:none;filter:drop-shadow(0 2px 4px rgba(0,0,0,.2))}.ngx-workflow__node-badge circle{transition:all .2s ease}.ngx-workflow__node-badge text{font-family:Inter,sans-serif;-webkit-user-select:none;user-select:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ZoomControlsComponent, selector: "ngx-workflow-zoom-controls", inputs: ["zoom", "minZoom", "maxZoom"], outputs: ["zoomIn", "zoomOut", "fitView", "resetZoom"] }, { kind: "component", type: MinimapComponent, selector: "ngx-workflow-minimap", inputs: ["nodeColor", "nodeClass", "showNodeColors"] }, { kind: "component", type: BackgroundComponent, selector: "ngx-workflow-background", inputs: ["variant", "gap", "size", "color", "backgroundColor", "backgroundImage"] }, { kind: "component", type: GridOverlayComponent, selector: "ngx-workflow-grid-overlay", inputs: ["gridSize", "gridColor", "width", "height"] }, { kind: "component", type: PropertiesSidebarComponent, selector: "ngx-workflow-properties-sidebar", inputs: ["node", "edge"], outputs: ["close", "change", "edgeChange"] }, { kind: "component", type: SearchControlsComponent, selector: "ngx-workflow-search-controls", inputs: ["nodes"], outputs: ["resultSelected", "close", "searchResults"] }, { kind: "component", type: ContextMenuComponent, selector: "ngx-workflow-context-menu" }, { kind: "component", type: ExportControlsComponent, selector: "ngx-workflow-export-controls", outputs: ["exportJSON", "importJSON"] }, { kind: "component", type: LayoutAlignmentControlsComponent, selector: "ngx-workflow-layout-alignment-controls", outputs: ["applyLayout"] }, { kind: "component", type: HandleComponent, selector: "g[ngx-workflow-handle], ngx-workflow-handle", inputs: ["nodeId", "handleId", "type", "isConnectable", "isValidConnection"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6443
6421
|
}
|
|
6444
6422
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImport: i0, type: DiagramComponent, decorators: [{
|
|
6445
6423
|
type: Component,
|
|
@@ -6609,30 +6587,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.13", ngImpo
|
|
|
6609
6587
|
}, {
|
|
6610
6588
|
type: HostListener,
|
|
6611
6589
|
args: ['window:keydown.meta.shift.z', ['$event']]
|
|
6612
|
-
}], onCopyKeyPress: [{
|
|
6613
|
-
type: HostListener,
|
|
6614
|
-
args: ['window:keydown.control.c', ['$event']]
|
|
6615
|
-
}, {
|
|
6616
|
-
type: HostListener,
|
|
6617
|
-
args: ['window:keydown.meta.c', ['$event']]
|
|
6618
|
-
}], onPasteKeyPress: [{
|
|
6619
|
-
type: HostListener,
|
|
6620
|
-
args: ['window:keydown.control.v', ['$event']]
|
|
6621
|
-
}, {
|
|
6622
|
-
type: HostListener,
|
|
6623
|
-
args: ['window:keydown.meta.v', ['$event']]
|
|
6624
|
-
}], onCutKeyPress: [{
|
|
6625
|
-
type: HostListener,
|
|
6626
|
-
args: ['window:keydown.control.x', ['$event']]
|
|
6627
|
-
}, {
|
|
6628
|
-
type: HostListener,
|
|
6629
|
-
args: ['window:keydown.meta.x', ['$event']]
|
|
6630
|
-
}], onDuplicateKeyPress: [{
|
|
6631
|
-
type: HostListener,
|
|
6632
|
-
args: ['window:keydown.control.d', ['$event']]
|
|
6633
|
-
}, {
|
|
6634
|
-
type: HostListener,
|
|
6635
|
-
args: ['window:keydown.meta.d', ['$event']]
|
|
6636
6590
|
}], onBringToFrontKeyPress: [{
|
|
6637
6591
|
type: HostListener,
|
|
6638
6592
|
args: ['window:keydown.control.]', ['$event']]
|