Fixes and improvements
This commit is contained in:
		| @@ -9,16 +9,18 @@ export const connectionStyles = css` | |||||||
|     transition: stroke 0.3s ease, stroke-width 0.3s ease; |     transition: stroke 0.3s ease, stroke-width 0.3s ease; | ||||||
|     pointer-events: all; |     pointer-events: all; | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
|  |     stroke: var(--connection-stroke-color, #999); | ||||||
|  |     stroke-width: var(--connection-stroke-width, 3); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .connections path:hover { |   .connections path:hover { | ||||||
|     stroke: #999; |     stroke: var(--connection-stroke-color-hover, #999); | ||||||
|     stroke-width: 3; |     stroke-width: var(--connection-stroke-width-hover, 3); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .connections path.selected { |   .connections path.selected { | ||||||
|     stroke: #3498db; |     stroke: var(--connection-stroke-color-selected, #3498db); | ||||||
|     stroke-width: 3; |     stroke-width: var(--connection-stroke-width-selected, 3); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .delete-button-group { |   .delete-button-group { | ||||||
| @@ -55,13 +57,27 @@ export const connections = function(superClass) { | |||||||
|       this.connections = []; |       this.connections = []; | ||||||
|       this.draggingConnection = null; |       this.draggingConnection = null; | ||||||
|       this.mousePosition = { x: 0, y: 0 }; |       this.mousePosition = { x: 0, y: 0 }; | ||||||
|  |       this._updatePreviewConnection = this._updatePreviewConnection.bind(this) | ||||||
|  |       this._conDocClick = this._conDocClick.bind(this); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     firstUpdated() { |     firstUpdated() { | ||||||
|       this.addEventListener('port-click', this._handlePortClick); |       super.firstUpdated(); | ||||||
|       document.addEventListener('mousemove', this._updatePreviewConnection.bind(this)); |  | ||||||
|  |  | ||||||
|       document.addEventListener('click', (e) => { |       this.addEventListener('port-click', this._handlePortClick); | ||||||
|  |       document.addEventListener('mousemove', this._updatePreviewConnection); | ||||||
|  |       document.addEventListener('click', this._conDocClick); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     disconnectedCallback() { | ||||||
|  |       super.disconnectedCallback(); | ||||||
|  |        | ||||||
|  |       this.removeEventListener('port-click', this._handlePortClick); | ||||||
|  |       document.removeEventListener('mousemove', this._updatePreviewConnection); | ||||||
|  |       document.removeEventListener('click', this._conDocClick); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     _conDocClick(e) { | ||||||
|       // Check if clicking on an output port |       // Check if clicking on an output port | ||||||
|       const path = e.composedPath(); |       const path = e.composedPath(); | ||||||
|       const isOutputPort = path.some(el =>  |       const isOutputPort = path.some(el =>  | ||||||
| @@ -91,9 +107,6 @@ export const connections = function(superClass) { | |||||||
|         this.selectedConnection = null; |         this.selectedConnection = null; | ||||||
|         this.requestUpdate(); |         this.requestUpdate(); | ||||||
|       } |       } | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       super.firstUpdated(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     _updatePreviewConnection(e) { |     _updatePreviewConnection(e) { | ||||||
| @@ -117,6 +130,7 @@ export const connections = function(superClass) { | |||||||
|         width: ${bounds.width}px;  |         width: ${bounds.width}px;  | ||||||
|         height: ${bounds.height}px;  |         height: ${bounds.height}px;  | ||||||
|         pointer-events: none; |         pointer-events: none; | ||||||
|  |         z-index: -1; | ||||||
|       ` : ` |       ` : ` | ||||||
|         position: absolute; |         position: absolute; | ||||||
|         top: 0; |         top: 0; | ||||||
| @@ -124,6 +138,7 @@ export const connections = function(superClass) { | |||||||
|         width: 100%; |         width: 100%; | ||||||
|         height: 100%; |         height: 100%; | ||||||
|         pointer-events: none; |         pointer-events: none; | ||||||
|  |         z-index: -1; | ||||||
|       `; |       `; | ||||||
|      |      | ||||||
|       return html` |       return html` | ||||||
| @@ -143,7 +158,7 @@ export const connections = function(superClass) { | |||||||
|       if (!sourceNode) return ''; |       if (!sourceNode) return ''; | ||||||
|  |  | ||||||
|       const sourcePort = sourceNode.shadowRoot.querySelector( |       const sourcePort = sourceNode.shadowRoot.querySelector( | ||||||
|         `.output-ports [data-port-id="${this.draggingConnection.sourcePortId}"]` |         `.output-ports [portid="${this.draggingConnection.sourcePortId}"]` | ||||||
|       ); |       ); | ||||||
|       if (!sourcePort) return ''; |       if (!sourcePort) return ''; | ||||||
|  |  | ||||||
| @@ -190,8 +205,8 @@ export const connections = function(superClass) { | |||||||
|        |        | ||||||
|       if (!sourceNode?.shadowRoot || !targetNode?.shadowRoot) return ''; |       if (!sourceNode?.shadowRoot || !targetNode?.shadowRoot) return ''; | ||||||
|  |  | ||||||
|       const sourcePort = sourceNode.shadowRoot.querySelector(`.output-ports [data-port-id="${conn.sourcePortId}"]`); |       const sourcePort = sourceNode.shadowRoot.querySelector(`.output-ports [portid="${conn.sourcePortId}"]`); | ||||||
|       const targetPort = targetNode.shadowRoot.querySelector(`.input-ports [data-port-id="${conn.targetPortId}"]`); |       const targetPort = targetNode.shadowRoot.querySelector(`.input-ports [portid="${conn.targetPortId}"]`); | ||||||
|        |        | ||||||
|       if (!sourcePort || !targetPort) return ''; |       if (!sourcePort || !targetPort) return ''; | ||||||
|  |  | ||||||
| @@ -312,18 +327,31 @@ export const connections = function(superClass) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     _handlePortClick(e) { |     _handlePortClick(e) { | ||||||
|       const { nodeId, portType, portId, portName } = e.detail; |       const { node, port } = e.detail; | ||||||
|  |       const nodeId = node.id; | ||||||
|  |       const { portType, portId, portName } = port; | ||||||
|        |        | ||||||
|       if (!this.draggingConnection) { |       if (!this.draggingConnection) { | ||||||
|         if (portType === 'output') { |         if (portType === 'output') { | ||||||
|           this.draggingConnection = { |           this.draggingConnection = { | ||||||
|             sourceNodeId: nodeId, |             sourceNodeId: nodeId, | ||||||
|             sourcePortId: portId, |             sourcePortId: portId, | ||||||
|             sourcePortType: portType |             sourcePortType: portType, | ||||||
|  |             sourceNode: node | ||||||
|           }; |           }; | ||||||
|         } |         } | ||||||
|       } else { |       } else { | ||||||
|         if (portType === 'input' && nodeId !== this.draggingConnection.sourceNodeId) { |         if (portType === 'input' && nodeId !== this.draggingConnection.sourceNodeId) { | ||||||
|  |           const targetNode = node; | ||||||
|  |           const sourceNode = this.draggingConnection.sourceNode; | ||||||
|  |  | ||||||
|  |           // Validate the connection | ||||||
|  |           const isValid = targetNode.validateConnection(sourceNode, this.draggingConnection.sourcePortId, portId); | ||||||
|  |  | ||||||
|  |           if (!isValid) { | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |  | ||||||
|           const connectionExists = this.connections.some(conn =>  |           const connectionExists = this.connections.some(conn =>  | ||||||
|             conn.sourceNodeId === this.draggingConnection.sourceNodeId &&  |             conn.sourceNodeId === this.draggingConnection.sourceNodeId &&  | ||||||
|             conn.sourcePortId === this.draggingConnection.sourcePortId && |             conn.sourcePortId === this.draggingConnection.sourcePortId && | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@tp/tp-flow-nodes", |   "name": "@tp/tp-flow-nodes", | ||||||
|   "version": "1.0.0", |   "version": "1.1.0", | ||||||
|   "description": "", |   "description": "", | ||||||
|   "main": "tp-flow-nodes.js", |   "main": "tp-flow-nodes.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
| @@ -13,6 +13,8 @@ | |||||||
|   "author": "trading_peter", |   "author": "trading_peter", | ||||||
|   "license": "Apache-2.0", |   "license": "Apache-2.0", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "lit": "^3.0.0" |     "lit": "^3.0.0", | ||||||
|  |     "@tp/helpers": "^2.0.0", | ||||||
|  |     "@tp/tp-timeout-strip": "^1.0.0" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								panning.js
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								panning.js
									
									
									
									
									
								
							| @@ -7,6 +7,8 @@ export const panning = function(superClass) { | |||||||
|         isDragging: { type: Boolean }, |         isDragging: { type: Boolean }, | ||||||
|         currentX: { type: Number }, |         currentX: { type: Number }, | ||||||
|         currentY: { type: Number }, |         currentY: { type: Number }, | ||||||
|  |         currentNodeX: { type: Number }, | ||||||
|  |         currentNodeY: { type: Number }, | ||||||
|         initialX: { type: Number }, |         initialX: { type: Number }, | ||||||
|         initialY: { type: Number }, |         initialY: { type: Number }, | ||||||
|         xOffset: { type: Number }, |         xOffset: { type: Number }, | ||||||
| @@ -20,6 +22,8 @@ export const panning = function(superClass) { | |||||||
|       this.isDragging = false; |       this.isDragging = false; | ||||||
|       this.currentX = 0; |       this.currentX = 0; | ||||||
|       this.currentY = 0; |       this.currentY = 0; | ||||||
|  |       this.currentNodeX = 0; | ||||||
|  |       this.currentNodeY = 0; | ||||||
|       this.initialX = 0; |       this.initialX = 0; | ||||||
|       this.initialY = 0; |       this.initialY = 0; | ||||||
|       this.xOffset = 0; |       this.xOffset = 0; | ||||||
| @@ -51,8 +55,17 @@ export const panning = function(superClass) { | |||||||
|     _bringToFront(element) { |     _bringToFront(element) { | ||||||
|       if (!(element instanceof TpFlowNode)) return; |       if (!(element instanceof TpFlowNode)) return; | ||||||
|        |        | ||||||
|       this.highestZIndex++; |       // Get all elements and filter for TpFlowNode instances | ||||||
|       element.style.zIndex = this.highestZIndex; |       const nodes = Array.from(this.shadowRoot.querySelector('.canvas').children) | ||||||
|  |         .filter(node => node instanceof TpFlowNode); | ||||||
|  |  | ||||||
|  |       for (const node of nodes) { | ||||||
|  |         node.style.zIndex = 1; | ||||||
|  |         node.classList.remove('focused'); | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       element.style.zIndex = 2; | ||||||
|  |       element.classList.add('focused'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     _startDrag(e) { |     _startDrag(e) { | ||||||
| @@ -61,6 +74,11 @@ export const panning = function(superClass) { | |||||||
|       if (topNode) { |       if (topNode) { | ||||||
|         this.targetElement = topNode; |         this.targetElement = topNode; | ||||||
|         this._bringToFront(topNode); |         this._bringToFront(topNode); | ||||||
|  |  | ||||||
|  |         // Check if a element with the "drag-node" attribute is part of the event path. Only then we can start dragging. | ||||||
|  |         if (!e.composedPath().some(el => typeof el.hasAttribute === 'function' && el.hasAttribute('drag-node'))) { | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|       } else { |       } else { | ||||||
|         this.targetElement = this.canvas; |         this.targetElement = this.canvas; | ||||||
|       } |       } | ||||||
| @@ -96,10 +114,10 @@ export const panning = function(superClass) { | |||||||
|             `translate(${this.currentX}px, ${this.currentY}px) scale(${this.scale})`; |             `translate(${this.currentX}px, ${this.currentY}px) scale(${this.scale})`; | ||||||
|         } else { |         } else { | ||||||
|           // For nodes, compensate for canvas scale |           // For nodes, compensate for canvas scale | ||||||
|           this.currentX = (e.clientX - this.initialX) / this.scale; |           this.currentNodeX = (e.clientX - this.initialX) / this.scale; | ||||||
|           this.currentY = (e.clientY - this.initialY) / this.scale; |           this.currentNodeY = (e.clientY - this.initialY) / this.scale; | ||||||
|           this.targetElement.style.transform =  |           this.targetElement.style.transform =  | ||||||
|             `translate(${this.currentX}px, ${this.currentY}px)`; |             `translate(${this.currentNodeX}px, ${this.currentNodeY}px)`; | ||||||
|         } |         } | ||||||
|      |      | ||||||
|         this.requestUpdate(); |         this.requestUpdate(); | ||||||
| @@ -113,8 +131,8 @@ export const panning = function(superClass) { | |||||||
|           data: { |           data: { | ||||||
|             nodeId: this.targetElement.id, |             nodeId: this.targetElement.id, | ||||||
|             position: { |             position: { | ||||||
|               x: this.currentX, |               x: this.currentNodeX, | ||||||
|               y: this.currentY |               y: this.currentNodeY | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
|   | |||||||
							
								
								
									
										109
									
								
								tp-flow-node-port.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								tp-flow-node-port.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | |||||||
|  | /** | ||||||
|  | @license | ||||||
|  | Copyright (c) 2025 trading_peter | ||||||
|  | This program is available under Apache License Version 2.0 | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | import '@tp/tp-timeout-strip/tp-timeout-strip.js'; | ||||||
|  | import { LitElement, html, css } from 'lit'; | ||||||
|  |  | ||||||
|  | class TpFlowNodePort extends LitElement { | ||||||
|  |   static get styles() { | ||||||
|  |     return [ | ||||||
|  |       css` | ||||||
|  |         :host { | ||||||
|  |           display: block; | ||||||
|  |           position: relative; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .connectionPoint { | ||||||
|  |           width: 12px; | ||||||
|  |           height: 12px; | ||||||
|  |           background: #666; | ||||||
|  |           border-radius: 50%; | ||||||
|  |           cursor: pointer; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .connectionPoint:hover { | ||||||
|  |           background: #888; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         .tag { | ||||||
|  |           position: absolute; | ||||||
|  |           background: #888; | ||||||
|  |           visibility: hidden; | ||||||
|  |           pointer-events: none; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         :host([portType="input"]) .tag { | ||||||
|  |           left: 0; | ||||||
|  |           top: 0; | ||||||
|  |           transform: translateX(calc(-100% - 10px)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         :host([portType="output"]) .tag { | ||||||
|  |           right: 0; | ||||||
|  |           top: 0; | ||||||
|  |           transform: translateX(calc(100% + 10px)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         :host(:hover) .tag, | ||||||
|  |         .tag[visible] { | ||||||
|  |           visibility: visible; | ||||||
|  |           pointer-events: all; | ||||||
|  |         } | ||||||
|  |       ` | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   render() { | ||||||
|  |     const { showTag, errorMsg, tagContent } = this; | ||||||
|  |  | ||||||
|  |     return html` | ||||||
|  |       <div class="connectionPoint" part="connection-point"></div> | ||||||
|  |        | ||||||
|  |       ${showTag || Boolean(tagContent) ? html` | ||||||
|  |         <div class="tag" ?visible=${showTag} part="tag"> | ||||||
|  |           ${errorMsg ? html` | ||||||
|  |             <div part="tag-error"> | ||||||
|  |               ${errorMsg} | ||||||
|  |             </div> | ||||||
|  |             ` : null} | ||||||
|  |           ${tagContent ? html` | ||||||
|  |             <div class="tag-content" part="tag-content">${tagContent}</div> | ||||||
|  |           ` : null} | ||||||
|  |           <tp-timeout-strip part="tag-timeout" @timeout=${this._onTimeout}></tp-timeout-strip> | ||||||
|  |         </div> | ||||||
|  |         ` : null} | ||||||
|  |     `; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static get properties() { | ||||||
|  |     return { | ||||||
|  |       portType: { type: String, reflect: true }, | ||||||
|  |       portId: { type: Number, reflect: true }, | ||||||
|  |       portName: { type: String }, | ||||||
|  |       tagContent: { type: String }, | ||||||
|  |       errorMsg: { type: String }, | ||||||
|  |       showTag: { type: Boolean }, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   showConnectionError(msg, timeout = 0) { | ||||||
|  |     this.errorMsg = msg; | ||||||
|  |     this.showTag = true; | ||||||
|  |  | ||||||
|  |     if (timeout > 0) { | ||||||
|  |       this.updateComplete.then(() => { | ||||||
|  |         this.shadowRoot.querySelector('tp-timeout-strip').show(timeout); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   _onTimeout() { | ||||||
|  |     this.showTag = false; | ||||||
|  |     this.errorMsg = null; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | window.customElements.define('tp-flow-node-port', TpFlowNodePort); | ||||||
| @@ -4,6 +4,7 @@ Copyright (c) 2024 trading_peter | |||||||
| This program is available under Apache License Version 2.0 | This program is available under Apache License Version 2.0 | ||||||
| */ | */ | ||||||
|  |  | ||||||
|  | import './tp-flow-node-port.js'; | ||||||
| import { LitElement, html, css } from 'lit'; | import { LitElement, html, css } from 'lit'; | ||||||
|  |  | ||||||
| // tp-flow-node.js | // tp-flow-node.js | ||||||
| @@ -46,24 +47,14 @@ export class TpFlowNode extends LitElement { | |||||||
|           align-items: flex-end; |           align-items: flex-end; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .port { |         header { | ||||||
|           width: 12px; |           display: flex; | ||||||
|           height: 12px; |           align-items: center; | ||||||
|           background: #666; |           column-gap: 10px; | ||||||
|           border-radius: 50%; |           padding: 2px 5px; | ||||||
|           cursor: pointer; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         .port:hover { |  | ||||||
|           background: #888; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .delete-btn { |         .delete-btn { | ||||||
|           position: absolute; |  | ||||||
|           top: 5px; |  | ||||||
|           right: 5px; |  | ||||||
|           width: 16px; |  | ||||||
|           height: 16px; |  | ||||||
|           cursor: pointer; |           cursor: pointer; | ||||||
|           opacity: 0.7; |           opacity: 0.7; | ||||||
|           transition: opacity 0.2s; |           transition: opacity 0.2s; | ||||||
| @@ -78,16 +69,17 @@ export class TpFlowNode extends LitElement { | |||||||
|  |  | ||||||
|   render() { |   render() { | ||||||
|     return html` |     return html` | ||||||
|  |       ${this.renderNodeHeader()} | ||||||
|       <div class="node-body"> |       <div class="node-body"> | ||||||
|         <div class="delete-btn" @click="${this._handleDelete}">✕</div> |  | ||||||
|         <div class="input-ports"> |         <div class="input-ports"> | ||||||
|           ${this.inputs.map((input, idx) => html` |           ${this.inputs.map((input, idx) => html` | ||||||
|             <div class="port"  |             <tp-flow-node-port class="port" exportparts="connectionPoint"  | ||||||
|                  data-port-type="input" |               portType="input" | ||||||
|                  data-port-id="${idx}" |               .portId=${idx} | ||||||
|                  data-port-name="${input.name}" |               .portName=${input.name} | ||||||
|  |               .tagContent=${input.tagContent} | ||||||
|               @mousedown="${this._handlePortClick}"> |               @mousedown="${this._handlePortClick}"> | ||||||
|             </div> |             </tp-flow-node-port> | ||||||
|           `)} |           `)} | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
| @@ -97,18 +89,28 @@ export class TpFlowNode extends LitElement { | |||||||
|  |  | ||||||
|         <div class="output-ports"> |         <div class="output-ports"> | ||||||
|           ${this.outputs.map((output, idx) => html` |           ${this.outputs.map((output, idx) => html` | ||||||
|             <div class="port" |             <tp-flow-node-port class="port" exportparts="connectionPoint" | ||||||
|                  data-port-type="output" |               portType="output" | ||||||
|                  data-port-id="${idx}" |               .portId=${idx} | ||||||
|                  data-port-name="${output.name}" |               .portName=${output.name} | ||||||
|  |               .tagContent=${output.tagContent} | ||||||
|               @mousedown="${this._handlePortClick}"> |               @mousedown="${this._handlePortClick}"> | ||||||
|             </div> |             </tp-flow-node-port> | ||||||
|           `)} |           `)} | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   renderNodeHeader() { | ||||||
|  |     return html` | ||||||
|  |       <header drag-node> | ||||||
|  |         A Node | ||||||
|  |         <div class="delete-btn" @click="${this._handleDelete}">✕</div> | ||||||
|  |       </header> | ||||||
|  |     `; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   renderNodeContent() { |   renderNodeContent() { | ||||||
|     console.warn('Your node should override the renderNodeContent method.'); |     console.warn('Your node should override the renderNodeContent method.'); | ||||||
|     return null; |     return null; | ||||||
| @@ -133,19 +135,17 @@ export class TpFlowNode extends LitElement { | |||||||
|     this.data = {}; |     this.data = {}; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   updated(changes) { | ||||||
|  |     super.updated(changes); | ||||||
|  |  | ||||||
|  |     this.dispatchEvent(new CustomEvent('update-layout', { detail: this, bubbles: true, composed: true })); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   _handlePortClick(e) { |   _handlePortClick(e) { | ||||||
|     e.stopPropagation();  // Prevents the event from getting caught by the panning action. |     e.stopPropagation();  // Prevents the event from getting caught by the panning action. | ||||||
|  |  | ||||||
|     const portEl = e.target; |  | ||||||
|     const detail = { |  | ||||||
|       nodeId: this.id, |  | ||||||
|       portType: portEl.dataset.portType, |  | ||||||
|       portId: portEl.dataset.portId, |  | ||||||
|       portName: portEl.dataset.portName |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     this.dispatchEvent(new CustomEvent('port-click', { |     this.dispatchEvent(new CustomEvent('port-click', { | ||||||
|       detail, |       detail: { node: this, port: e.target }, | ||||||
|       bubbles: true, |       bubbles: true, | ||||||
|       composed: true |       composed: true | ||||||
|     })); |     })); | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import { LitElement, html, css } from 'lit'; | |||||||
| import { connections, connectionStyles } from './connections.js'; | import { connections, connectionStyles } from './connections.js'; | ||||||
| import { panning } from './panning.js'; | import { panning } from './panning.js'; | ||||||
| import { zoom } from './zoom.js'; | import { zoom } from './zoom.js'; | ||||||
|  | import { repeat } from 'lit/directives/repeat.js'; | ||||||
|  |  | ||||||
| export class TpFlowNodes extends zoom(panning(connections(LitElement))) { | export class TpFlowNodes extends zoom(panning(connections(LitElement))) { | ||||||
|   static nodeTypes = new Map(); |   static nodeTypes = new Map(); | ||||||
| @@ -36,10 +37,11 @@ export class TpFlowNodes extends zoom(panning(connections(LitElement))) { | |||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Using the repeat directive to render the nodes is important here to prevent the nodes from running into a inconsistent data state when nodes are removed. | ||||||
|   render() { |   render() { | ||||||
|     return html` |     return html` | ||||||
|       <div class="canvas"> |       <div class="canvas"> | ||||||
|         ${this.nodes.map(node => html`${node}`)} |         ${repeat(this.nodes, node => node.id, node => html`${node}`)} | ||||||
|         ${this._renderConnections()} |         ${this._renderConnections()} | ||||||
|       </div> |       </div> | ||||||
|     `; |     `; | ||||||
| @@ -61,6 +63,9 @@ export class TpFlowNodes extends zoom(panning(connections(LitElement))) { | |||||||
|   connectedCallback() { |   connectedCallback() { | ||||||
|     super.connectedCallback(); |     super.connectedCallback(); | ||||||
|     this.addEventListener('node-delete-requested', this._boundDeleteHandler); |     this.addEventListener('node-delete-requested', this._boundDeleteHandler); | ||||||
|  |     this.addEventListener('update-layout', () => { | ||||||
|  |       this.requestUpdate(); | ||||||
|  |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   disconnectedCallback() { |   disconnectedCallback() { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user